Orion PHP  0.11.12
The PHP5.3 framework
model.php
Go to the documentation of this file.
00001 <?php
00002 
00003 namespace Orion\Core;
00004 
00005 
00006 /**
00007  * \Orion\Core\Model
00008  * 
00009  * Orion abstract model class.
00010  *
00011  * @author Thibaut Despoulain
00012  * @license BSD 4-clauses
00013  * @version 0.11.12
00014  *
00015  * @abstract
00016  */
00017 class Model extends Object
00018 {
00019 //------------------------------------------------------------------------------
00020 // Protected
00021 //------------------------------------------------------------------------------
00022     
00023     /**
00024      * List of unupdatable fields (defined using Model->lock(...))
00025      * @var String[]
00026      */
00027     protected $_lockedFields = array();
00028     
00029 //------------------------------------------------------------------------------
00030 // Protected Static @override
00031 //------------------------------------------------------------------------------
00032 
00033     /**
00034      * DB Table used by the model
00035      * @var String
00036      */
00037     protected static $table;
00038     
00039     /**
00040      * Attribute storing fields assigned using the define() function.
00041      * @var \Orion\Core\Model\Field[]
00042      */
00043     protected static $fields = array( );
00044 
00045     /**
00046      * Array containing the names of the primary fields.
00047      * @var String[]
00048      */
00049     protected static $primaryKeys = array( );
00050     
00051     /**
00052      * Enable/Disable events
00053      * @var Boolean[]
00054      */
00055     protected static $events = array( );
00056 
00057     /**
00058      * Override this function to bind model's attributes to corresponding \Orion\Model\Field's.
00059      * Usage of self::has() inside this function is advised.
00060      * Be sure to first declare the required $table, $fields, $primaryKeys and $events static properties.
00061      * @abstract
00062      */
00063     protected static function describe()
00064     {
00065         // -
00066     }
00067 
00068 //------------------------------------------------------------------------------
00069 // Protected Static
00070 //------------------------------------------------------------------------------
00071 
00072     /**
00073      * Define and bind a new field to current model.
00074      * @param \Orion\Model\Field $field The field definition to bind
00075      */
00076     protected static final function has( $field )
00077     {
00078         static::$fields[ $field->getName() ] = $field;
00079 
00080         if ( $field->isPrimary() )
00081             static::$primaryKeys[ ] = $field->getName();
00082 
00083         if ( method_exists( $field, 'onDelete' ) )
00084             static::$events[ 'delete' ] = true;
00085 
00086         if ( method_exists( $field, 'onSave' ) )
00087             static::$events[ 'save' ] = true;
00088 
00089         if ( method_exists( $field, 'onUpdate' ) )
00090             static::$events[ 'update' ] = true;
00091     }
00092 
00093     /**
00094      * Check if model's fields require event handling for given event.
00095      * 
00096      * @param string $type the type of event to check
00097      * @return Boolean
00098      */
00099     protected static final function hasEvent( $type )
00100     {
00101         if ( empty( static::$fields ) )
00102         {
00103             static::describe();
00104             if ( empty( static::$fields ) )
00105                 throw new Exception( 'No field bound to model.', E_ERROR, get_called_class() );
00106         }
00107 
00108         return isset( static::$events[ $type ] );
00109     }
00110 
00111     /**
00112      * Prepare model for Query::Factory().
00113      * 
00114      * Call describe() if fields attribute is not set.
00115      * Check table binding.
00116      * 
00117      * @return String The model class name to be used by the Query::Factory 
00118      */
00119     protected static final function prepare()
00120     {
00121         if ( empty( static::$fields ) )
00122         {
00123             static::describe();
00124             if ( empty( static::$fields ) )
00125                 throw new Exception( 'No field bound to model.', E_ERROR, get_called_class() );
00126         }
00127 
00128         if ( empty( static::$table ) )
00129             throw new Exception( 'No table bound to model.', E_ERROR, get_called_class() );
00130 
00131         return get_called_class();
00132     }
00133 
00134 //------------------------------------------------------------------------------
00135 // Public Static
00136 //------------------------------------------------------------------------------
00137 
00138     /**
00139      * Return a new model object instance based on an array of keys/values
00140      * 
00141      * @param array $array The associative array to retrieve data from
00142      * @param \Orion\Core\Model $object If given, data will be appended to $object
00143      * @param boolean $mergeEmpty Merge fields even if empty post data ?
00144      * @return \Orion\Core\Model
00145      */
00146     public static final function &fetchArrayData( &$array, &$object=null, $mergeEmpty=false )
00147     {
00148         if ( !( $object instanceof \Orion\Core\Model ) )
00149         {
00150             $class = get_called_class();
00151             $object = new $class();
00152         }
00153         
00154         foreach ( self::getFields() as $key => $field )
00155             if ( isset( $array[ $key ] )
00156                     && (
00157                     ( $mergeEmpty )
00158                     || ( !$mergeEmpty && !$field->isEmptyValue( $array[ $key ] ) )
00159                     )
00160             )
00161             {
00162                 $object->{$key} = $array[ $key ];
00163             }
00164 
00165         return $object;
00166     }
00167     
00168     /**
00169      * Return a new model object instance filled using POST vars
00170      * 
00171      * @param \Orion\Core\Model $object If given, data will be appended to $object
00172      * @param boolean $mergeEmpty Merge fields even if empty post data ?
00173      * @return \Orion\Core\Model
00174      */
00175     public static final function &fetchPostData( &$object=null, $mergeEmpty=false )
00176     {
00177         return self::fetchArrayData($_POST, $object, $mergeEmpty);
00178     }
00179 
00180     /**
00181      * Return a new model object instance filled using PUT vars from php input stream (php://input).
00182      * 
00183      * @param \Orion\Core\Model $object If given, data will be appended to $object
00184      * @param boolean $mergeEmpty Merge fields even if empty post data ?
00185      * @return \Orion\Core\Model 
00186      */
00187     public static final function &fetchRestData( &$object=null, $mergeEmpty=false )
00188     {
00189         $data = null;
00190         parse_str( file_get_contents( "php://input" ), $data );
00191 
00192         return self::fetchArrayData($data, $object, $mergeEmpty);
00193     }
00194 
00195     /**
00196      * Shortcut function. Similar to self::query()->select( $args ).
00197      * 
00198      * @param String... $args Fields to select (variable-length argument list)
00199      * @return \Orion\Model\Query\Base 
00200      */
00201     public static final function get( $args=null )
00202     {
00203         $query = self::query();
00204         return $query->select( is_array( $args ) ? $args : func_get_args() );
00205     }
00206 
00207     /**
00208      * Get child class name (extended model)
00209      * @return string
00210      */
00211     public static final function getClass()
00212     {
00213         return get_called_class();
00214     }
00215 
00216     /**
00217      * Get a field of the model by its name.
00218      * @param String $name The name (binding identifier) of the field
00219      * @return \Orion\Model\Field
00220      */
00221     public static final function getField( $name )
00222     {
00223         if ( empty( static::$fields ) )
00224             static::describe();
00225         return static::$fields[ $name ];
00226     }
00227 
00228     /**
00229      * Get all of the model fields as an array of fields
00230      * @return \Orion\Model\Field[]
00231      */
00232     public static final function getFields()
00233     {
00234         if ( empty( static::$fields ) )
00235             static::describe();
00236         return static::$fields;
00237     }
00238     
00239     /**
00240      * Get an array of bound fields' names/keys
00241      * @return String[]
00242      */
00243     public static function getFieldsKeys()
00244     {
00245         if ( empty( static::$fields ) )
00246             static::describe();
00247         return array_keys(static::$fields);
00248     }
00249 
00250     /**
00251      * Get provided field's linked table if it exists
00252      * @param string $field The field identifier/name
00253      * @return string The linked table 
00254      */
00255     public static final function getLinkedTable( $field )
00256     {
00257         if ( !self::isLinked( $field ) )
00258             throw new Exception( 'Field [' . Core\Security::preventInjection( $field ) . '] is not linked, unable to get table.', E_WARNING, self::getClass() );
00259         
00260         $model = self::getField( $field )->getModel();
00261         return $model::getTable();
00262     }
00263 
00264     /**
00265      * Get bound table name.
00266      * @return String
00267      */
00268     public static final function getTable()
00269     {
00270         return static::$table;
00271     }
00272 
00273     /**
00274      * Check wether provided field is bound to model.
00275      * @param string $fieldname
00276      * @return boolean
00277      */
00278     public static final function hasField( $fieldname )
00279     {
00280         return array_key_exists( $fieldname, self::getFields() );
00281     }
00282 
00283     /**
00284      * Check wether a field is linked to another model
00285      * @param String $field
00286      * @return boolean
00287      */
00288     public static final function isLinked( $field )
00289     {
00290         return self::getField( $field )->isLinked();
00291     }
00292 
00293     /**
00294      * Check wether provided field is primary or not.
00295      * @param string $field
00296      * @return boolean
00297      */
00298     public static final function isPrimary( $field )
00299     {
00300         return self::getField( $field )->isPrimary();
00301     }
00302 
00303     /**
00304      * (Factory) Create and return a new Query instance.
00305      * @return \Orion\Core\Query\Base
00306      */
00307     public static final function query()
00308     {
00309         return Query::Factory( self::prepare() );
00310     }
00311 
00312 //------------------------------------------------------------------------------
00313 // Public Dynamic
00314 //------------------------------------------------------------------------------
00315 
00316     /**
00317      * Delete current object from database.
00318      * Object must have its primary keys defined.
00319      * @return \Orion\Core\Query
00320      */
00321     public function delete()
00322     {
00323         $query = self::query();
00324 
00325         // Setup where clause using primaryKeys
00326         foreach ( static::$primaryKeys as $key )
00327         {
00328             if ( !isset( $this->{$key} ) || $this->{$key} == null )
00329                 throw new Exception( 'Primary key [' . $key . '] value not provided in object to delete.', E_USER_WARNING, get_class() );
00330 
00331             $query->andWhere( $key, Query::EQUAL, $this->{$key} );
00332         }
00333 
00334         if ( self::hasEvent( 'delete' ) )
00335         {
00336             // Retrieve old data for onDelete event
00337             $oldData = $query->select()->fetch();
00338             // Trigger onDelete event
00339             foreach ( self::getFields() as $key => $field )
00340                 $field->onDelete( $oldData->{$key} );
00341         }
00342 
00343         return $query->delete();
00344     }
00345 
00346     /**
00347      * Given that primary keys are provided in object, fills out remaining attributes using an automatic select Query.
00348      * @param String... $args Fields to fill out (variable-length argument list)
00349      * @return \Orion\Core\Model
00350      */
00351     public function fillout( $args )
00352     {
00353         $query = $this->get( is_array( $args ) ? $args : func_get_args() );
00354 
00355         // Setup where clause using primaryKeys
00356         foreach ( static::$primaryKeys as $key )
00357         {
00358             if ( !isset( $this->{$key} ) || $this->{$key} == null )
00359                 throw new Exception( 'Primary key [' . $key . '] value not provided in object to fill out.', E_USER_WARNING, get_class() );
00360 
00361             $query->andWhere( $key, Query::EQUAL, $this->{$key} );
00362         }
00363 
00364         return $query->select()->fetch();
00365     }
00366     
00367     /**
00368      * Lock given fields for update query. (Make them uneditable)
00369      * @param Mixed $args Array or VL list of fields to deny update possibility.
00370      */
00371     public function &lock( $args=null )
00372     {
00373         if( $args == null )
00374             $this->_lockedFields = array();
00375         else
00376         {
00377             $fields = is_array( $args ) ? $args : func_get_args();
00378             $diff = array_diff($fields, $this->_lockedFields);
00379             $this->_lockedFields = array_merge($this->_lockedFields, $diff);
00380         }
00381         
00382         return $this;
00383     }
00384 
00385     /**
00386      * Save current object into database.
00387      * Object must have its primary keys defined.
00388      * @return \Orion\Core\Query\Base
00389      */
00390     public function save()
00391     {
00392         $query = self::query();
00393         $savedPairs = array( );
00394 
00395         foreach ( self::getFields() as $key => $field )
00396         {
00397             $value = $this->{$key};
00398             if ( !$field->isEmptyValue( $value ) && !in_array( $key, $this->_lockedFields) )
00399             {
00400                 if ( !$field->validate( $value ) )
00401                     throw new Exception( 'Unable to save object to database. Field [' . $key . '] in ['.self::getClass().'] is not valid.', E_USER_ERROR, get_class() );
00402 
00403                 $query->set( $field->getName(), $value );
00404 
00405                 $savedPairs[ $field->getName() ] = $value;
00406             }
00407         }
00408 
00409         // Trigger onSave event
00410         if ( self::hasEvent( 'save' ) )
00411             foreach ( $savedPairs as $key => $value )
00412                 self::getField( $key )->onSave( $value );
00413 
00414         return $query->save();
00415     }
00416 
00417     /**
00418      * Check the uniqueness of the object for provided fields
00419      * @param Mixed $fields Can either be a single field name, an array of field names, 
00420      *      or even a variable-length argument list of field names. 
00421      *      Leave blank for a check on all primary fields.
00422      * @param string Field name
00423      * @return Boolean
00424      */
00425     public function unique( $fields )
00426     {
00427         if ( func_num_args() == 0 || $fields == null )
00428             $cols = array( self::$primaryKeys );
00429         else
00430         {
00431             if ( is_array( $fields ) )
00432                 $cols = $fields;
00433             else
00434                 $cols = func_get_args();
00435         }
00436         
00437         $query = self::get( $cols );
00438 
00439         foreach ( $cols as $key )
00440         {
00441             $query->andWhere( $key, Query::EQUAL, $this->{$key} );
00442         }
00443 
00444         $result = $query->limit( 1 )->fetch();
00445 
00446         if ( $result === false )
00447             return true;
00448 
00449         return false;
00450     }
00451 
00452     /**
00453      * Update current object into database.
00454      * Object must have its primary keys defined.
00455      * @return \Orion\Core\Query\Base
00456      */
00457     public function update($fields=null)
00458     {
00459         $query = self::query();
00460 
00461         // Setup where clause using primaryKeys
00462         foreach ( static::$primaryKeys as $key )
00463         {
00464             if ( !isset( $this->{$key} ) || $this->{$key} == null )
00465                 throw new Exception( 'Primary key [' . $key . '] value not provided in object to update.', E_USER_WARNING, get_class() );
00466 
00467             $query->andWhere( $key, Query::EQUAL, $this->{$key} );
00468         }
00469 
00470         // Retrieve old data for onUpdate event
00471         if ( self::hasEvent( 'update' ) )
00472             $oldData = $query->select()->fetch();
00473 
00474         // Setup updated keys/values
00475         $savedPairs = array( );
00476         
00477 
00478         if($fields != null)
00479         {
00480             if(is_array($fields))
00481                 $fieldsToUpdate = $fields;
00482             else
00483                 $fieldsToUpdate = func_get_args();
00484         }
00485         else
00486         {
00487             $fieldsToUpdate = self::getFieldsKeys();
00488         }
00489         foreach ( $fieldsToUpdate as $key )
00490         {   
00491             if(!self::hasField( $key ))
00492                 throw new Exception( 'Field ['.$key.'] is not defined in model ['.self::getClass().'].' );
00493             
00494             $field = self::getField( $key );
00495             $value = $this->{$key};
00496             
00497             if ( !$field->isEmptyValue( $value ) && !in_array( $key, $this->_lockedFields) )
00498             {
00499                 if ( !$field->validate( $value ) )
00500                     throw new Exception( 'Unable to update object to database. Field [' . $key . '] is not valid.' );
00501                                 
00502                 $query->set( $field->getName(), $value );
00503 
00504                 $savedPairs[ $field->getName() ] = $value;
00505             }
00506         }
00507 
00508         // Trigger onUpdate event
00509         if ( self::hasEvent( 'update' ) )
00510             foreach ( $savedPairs as $key => $value )
00511                 self::getField( $key )->onUpdate( $value, $oldData->{$key} );
00512 
00513         return $query->update();
00514     }
00515 
00516 }
00517 
00518 ?>