[ Index ]

PHP Cross Reference of Limb3

title

Body

[close]

/active_record/src/ -> lmbActiveRecord.class.php (source)

   1  <?php
   2  /*

   3   * Limb PHP Framework

   4   *

   5   * @link http://limb-project.com

   6   * @copyright  Copyright &copy; 2004-2007 BIT(http://bit-creative.com)

   7   * @license    LGPL http://www.gnu.org/copyleft/lesser.html

   8   */
   9  lmb_require('limb/core/src/lmbObject.class.php');
  10  lmb_require('limb/core/src/lmbDelegate.class.php');
  11  lmb_require('limb/core/src/lmbCollection.class.php');
  12  lmb_require('limb/dbal/src/lmbTableGateway.class.php');
  13  lmb_require('limb/dbal/src/criteria/lmbSQLCriteria.class.php');
  14  lmb_require('limb/dbal/src/drivers/lmbDbTypeInfo.class.php');
  15  lmb_require('limb/dbal/toolkit.inc.php');
  16  lmb_require('limb/validation/src/lmbValidator.class.php');
  17  lmb_require('limb/validation/src/lmbErrorList.class.php');
  18  lmb_require('limb/validation/src/exception/lmbValidationException.class.php');
  19  lmb_require('limb/active_record/src/lmbARException.class.php');
  20  lmb_require('limb/active_record/src/lmbARNotFoundException.class.php');
  21  lmb_require('limb/active_record/src/lmbARRecordSetDecorator.class.php');
  22  lmb_require('limb/active_record/src/lmbAROneToManyCollection.class.php');
  23  lmb_require('limb/active_record/src/lmbARManyToManyCollection.class.php');
  24  
  25  /**

  26   * Base class responsible for ActiveRecord design pattern implementation. Inspired by Rails ActiveRecord class.

  27   *

  28   * @version $Id: lmbActiveRecord.class.php 6008 2007-06-20 08:32:14Z serega $

  29   * @package active_record

  30   */
  31  class lmbActiveRecord extends lmbObject
  32  {
  33    /**

  34     * @var string database column name used to store object class name for single table inheritance

  35     */
  36    protected static $_inheritance_field = 'kind';
  37    /**

  38     * @var string database column name used to store object's create time

  39     */
  40    protected static $_ctime_field = 'ctime';
  41    /**

  42     * @var string database column name used to store object's update time

  43     */
  44    protected static $_utime_field = 'utime';
  45    /**

  46     * @var array global event listeners which receieve events from ALL lmbActiveRecord instances

  47     */
  48    protected static $_global_listeners = array();
  49    /**

  50     * @var object database connection which is shared by all lmbActiveRecord instances

  51     *             if no connection passed explicitly into constructor

  52     */
  53    protected static $_default_db_conn;
  54    /**

  55     * @var object current object's database connection

  56     *  @see lmbDbConnection

  57     */
  58    protected $_db_conn;
  59    /**

  60     * @var object lmbTableGateway instance used to access underlying db table

  61     */
  62    protected $_db_table;
  63    /**

  64     * @var string name of class database table to store instance fields, if not set lmbActiveRecord tries to guess it

  65     */
  66    protected $_db_table_name;
  67    /**

  68     * @var boolean reflects new or loaded status of an object

  69     */
  70    protected $_is_new = true;
  71    /**

  72     * @var object error list instance used to store validation errors

  73     */
  74    protected $_error_list;
  75    /**

  76     * @var array all has-one relations of an object

  77     */
  78    protected $_has_one = array();
  79    /**

  80     * @var array all belongs-to relations of an object

  81     */
  82    protected $_belongs_to = array();
  83    /**

  84     * @var array all many-belongs-to relations of an object

  85     */
  86    protected $_many_belongs_to = array();
  87    /**

  88     * @var array all has-many relations of an object

  89     */
  90    protected $_has_many = array();
  91    /**

  92     * @var array all has-many-to-many relations of an object

  93     */
  94    protected $_has_many_to_many = array();
  95    /**

  96     * @var array all value object relations of an object

  97     */
  98    protected $_composed_of = array();
  99    /**

 100     * @var boolean true during the object's saving procedure

 101     */
 102    protected $_is_being_saved = false;
 103    /**

 104     * @var boolean true during the object's removal procedure

 105     */
 106    protected $_is_being_destroyed = false;
 107    /**

 108     * @var boolean object's dirtiness status

 109     */
 110    protected $_is_dirty = false;
 111    /**

 112     * @var boolean we can explicitly mark object inheritable or not, if not set lmbActiveRecord looks if inheritance field is present in db

 113     */
 114    protected $_is_inheritable;
 115    /**

 116     * @var array array of attributes which should not be loaded at once but only on demand

 117     */
 118    protected $_lazy_attributes = array();
 119    /**

 120     * @var array array of dirty(changed) attributes of an object

 121     */
 122    protected $_dirty_props = array();
 123    /**

 124     * @var array sort params used to order objects during database retrieval

 125     */
 126    protected $_default_sort_params;
 127    /**

 128     * @var object database metainfo object

 129     */
 130    protected $_db_meta_info;
 131  
 132    /**#@+

 133     * Event type constants

 134     */
 135    const ON_BEFORE_SAVE             = 1;
 136    const ON_AFTER_SAVE              = 2;
 137    const ON_BEFORE_UPDATE           = 3;
 138    const ON_UPDATE                  = 4;
 139    const ON_AFTER_UPDATE            = 5;
 140    const ON_BEFORE_CREATE           = 6;
 141    const ON_CREATE                  = 7;
 142    const ON_AFTER_CREATE            = 8;
 143    const ON_BEFORE_DESTROY          = 9;
 144    const ON_AFTER_DESTROY           = 10;
 145    /**#@-*/

 146  
 147    /**

 148     * @var array event listeners attached to the concrete object instance

 149     */
 150    protected $_listeners = array();
 151  
 152    /**

 153     * Note, this property is not guarded with "_" prefix since we need it to be imported/exported

 154     * @var array An array of attached value objects

 155     */
 156    protected $raw_value_objects = array();
 157  
 158    /**

 159     *  Constructor.

 160     *  Creates an instance of lmbActiveRecord object in different ways depending on passed argument

 161     *  <code>

 162     *  //plain vanilla instance

 163     *  $b = new Book();

 164     *  //fills instance with passed properties

 165     *  $b = new Book(array('title' => 'Alice in Wonderland'));

 166     *  //tries to load instance from database using 1 as a primary key identifier

 167     *  $b = new Book(1);

 168     *  </code>

 169     *  @param array|integer Depending on argument type the new object is filled with properties or loaded from database

 170     */
 171    function __construct($magic_params = null, $conn = null)
 172    {
 173      parent :: __construct();
 174  
 175      $this->_defineRelations();
 176  
 177      if(is_object($conn))
 178        $this->_db_conn = $conn;
 179      else
 180        $this->_db_conn = self :: getDefaultConnection();
 181  
 182      $this->_db_meta_info = lmbToolkit :: instance()->getActiveRecordMetaInfo($this, $this->_db_conn);
 183  
 184      $this->_db_table = $this->_db_meta_info->getDbTable();
 185      $this->_db_table_name = $this->_db_table->getTableName();
 186      $this->_error_list = new lmbErrorList();
 187  
 188      if(is_int($magic_params))
 189        $this->loadById($magic_params);
 190      elseif(is_array($magic_params) || is_object($magic_params))
 191        $this->import($magic_params);
 192    }
 193    /**

 194     *  Sets database resource identifier used for database access

 195     *  @param string DSN, e.g. mysql://root:secret@localhost/mydb

 196     */
 197    static function setDefaultDSN($dsn)
 198    {
 199      self :: $_default_db_conn = lmbToolkit :: instance()->createDbConnection($dsn);
 200    }
 201    /**

 202     *  Sets default database connection object

 203     *  @param object instance of concrete lmbDbConnection interface implementation

 204     *  @return object previous connection object

 205     *  @see lmbDbConnection

 206     */
 207    static function setDefaultConnection($conn)
 208    {
 209      $prev = self :: $_default_db_conn;
 210      self :: $_default_db_conn = $conn;
 211      return $prev;
 212    }
 213    /**

 214     *  Returns current default database connection object

 215     *  @return object instance of concrete lmbDbConnection interface implementation

 216     *  @see lmbDbConnection

 217     */
 218    static function getDefaultConnection()
 219    {
 220      if(is_object(self :: $_default_db_conn))
 221        return self :: $_default_db_conn;
 222      return lmbToolkit :: instance()->getDefaultDbConnection();
 223    }
 224    /**

 225     *  Returns current single table inheritance column name

 226     *  @return string

 227     */
 228    static function getInheritanceField()
 229    {
 230      return self :: $_inheritance_field;
 231    }
 232    /**

 233     *  Allows to override default single table inheritance column name

 234     *  @param string

 235     */
 236    static function setInheritanceField($field)
 237    {
 238      return self :: $_inheritance_field = $field;
 239    }
 240    /**

 241     *  Returns name of database table

 242     *  @return string

 243     */
 244    function getTableName()
 245    {
 246      return $this->_db_table_name;
 247    }
 248    /**

 249     *  Returns table gateway instance used for all db interactions

 250     *  @return object

 251     */
 252    function getDbTable()
 253    {
 254      return $this->_db_table;
 255    }
 256    /**

 257     *  Returns error list object with all validation errors

 258     *  @return object

 259     */
 260    function getErrorList()
 261    {
 262      return $this->_error_list;
 263    }
 264  
 265    protected function _defineRelations(){}
 266  
 267    protected function _hasOne($relation_name, $info)
 268    {
 269      $this->_has_one[$relation_name] = $info;
 270    }
 271  
 272    protected function _hasMany($relation_name, $info)
 273    {
 274      $this->_has_many[$relation_name] = $info;
 275    }
 276  
 277    protected function _hasManyToMany($relation_name, $info)
 278    {
 279      $this->_has_many_to_many[$relation_name] = $info;
 280    }
 281  
 282    protected function _belongsTo($relation_name, $info)
 283    {
 284      $this->_belongs_to[$relation_name] = $info;
 285    }
 286  
 287    protected function _manyBelongsTo($relation_name, $info)
 288    {
 289      $this->_many_belongs_to[$relation_name] = $info;
 290    }
 291  
 292    protected function _composedOf($relation_name, $info)
 293    {
 294      $this->_composed_of[$relation_name] = $info;
 295    }
 296  
 297    /**

 298     *  Returns relation info array defined during class declaration

 299     *  @return array

 300     */
 301    function getRelationInfo($relation)
 302    {
 303      $relations = $this->_getAllRelations();
 304      if(isset($relations[$relation]))
 305        return $relations[$relation];
 306    }
 307  
 308    protected function _getAllRelations()
 309    {
 310       return array_merge($this->_has_one,
 311                          $this->_has_many,
 312                          $this->_has_many_to_many,
 313                          $this->_belongs_to,
 314                          $this->_many_belongs_to,
 315                          $this->_composed_of);
 316    }
 317    /**

 318     *  Returns all relations info for one-to-many

 319     *  @return array

 320     */
 321    function getOneToManyRelationsInfo()
 322    {
 323      return $this->_has_many;
 324    }
 325    /**

 326     *  Returns all relations info for many-to-many

 327     *  @return array

 328     */
 329    function getManyToManyRelationsInfo()
 330    {
 331      return $this->_has_many_to_many;
 332    }
 333    /**

 334     *  Returns all relations info for belongs-to

 335     *  @return array

 336     */
 337    function getBelongsToRelationsInfo()
 338    {
 339      return $this->_belongs_to;
 340    }
 341    /**

 342     *  Returns all relations info for many-belongs-to

 343     *  @return array

 344     */
 345    function getManyBelongsToRelationsInfo()
 346    {
 347      return $this->_many_belongs_to;
 348    }
 349    /**

 350     *  Returns default sort params

 351     *  @return array

 352     */
 353    function getDefaultSortParams()
 354    {
 355      if(!$this->_default_sort_params)
 356        $this->_default_sort_params = array($this->_db_table_name . '.id' => 'ASC');
 357  
 358      return $this->_default_sort_params;
 359    }
 360  
 361    protected function _createTableObjectByAlias($class_path_alias)
 362    {
 363      $class_path = new lmbClassPath($class_path_alias);
 364      return $class_path->createObject();
 365    }
 366    /**

 367     *  Returns common validator for create and update operations. It should be overridden

 368     *  if you want to have a custom validator, e.g:

 369     *

 370     *  <code>

 371     *  $validator = new lmbValidator();

 372     *  $validator->addRequiredRule('title');

 373     *  return $validator;

 374     *  </code>

 375     *  @return object

 376     */
 377    protected function _createValidator()
 378    {
 379      return new lmbValidator();
 380    }
 381    /**

 382     *  Returns validator for create operations only.

 383     *  @see _createValidator()

 384     *  @return object

 385     */
 386    protected function _createInsertValidator()
 387    {
 388      return $this->_createValidator();
 389    }
 390    /**

 391     *  Returns validator for update operations only.

 392     *  @see _createValidator()

 393     *  @return object

 394     */
 395    protected function _createUpdateValidator()
 396    {
 397      return $this->_createValidator();
 398    }
 399  
 400    protected function _savePreRelations()
 401    {
 402      foreach($this->_has_one as $property => $info)
 403        $this->_savePreRelationObject($property, $info, true);
 404  
 405      foreach($this->_many_belongs_to as $property => $info)
 406        $this->_savePreRelationObject($property, $info, false);
 407    }
 408  
 409    protected function _savePreRelationObject($property, $info, $save_relation_obj = true)
 410    {
 411      if($this->isDirtyProperty($info['field']) && !$this->isDirtyProperty($property))
 412      {
 413        $value = $this->_getRaw($info['field']);
 414        if(is_null($value))
 415          $this->_setRaw($property, null);
 416        return;
 417      }
 418  
 419      $object = $this->_getRaw($property);
 420      if(is_object($object))
 421      {
 422        if($object->isNew() || (!$object->isNew() && $save_relation_obj))
 423          $object->save($this->_error_list);
 424        $object_id = $object->getId();
 425        if($this->_getRaw($info['field']) != $object_id)
 426          $this->_setRaw($info['field'], $object->getId());
 427      }
 428      elseif(is_null($object) && $this->isDirtyProperty($property) &&
 429             isset($info['can_be_null']) && $info['can_be_null'])
 430        $this->_setRaw($info['field'], null);
 431    }
 432  
 433    protected function _savePostRelations()
 434    {
 435      foreach($this->_has_many as $property => $info)
 436        $this->_savePostRelationCollection($property, $info);
 437  
 438      foreach($this->_has_many_to_many as $property => $info)
 439        $this->_savePostRelationCollection($property, $info);
 440  
 441      foreach($this->_belongs_to as $property => $info)
 442        $this->_savePostRelationObject($property, $info);
 443    }
 444  
 445    protected function _savePostRelationCollection($property, $info)
 446    {
 447      $collection = $this->_getRaw($property);
 448      if(is_object($collection))
 449        $collection->save($this->_error_list);
 450    }
 451  
 452    protected function _savePostRelationObject($property, $info)
 453    {
 454      $object = $this->_getRaw($property);
 455      if(is_object($object))
 456      {
 457        $object->set($info['field'], $this->getId());
 458        $object->save($this->_error_list);
 459      }
 460    }
 461  
 462    protected function __call($method, $args = array())
 463    {
 464      if($property = $this->_mapGetToProperty($method))
 465        return $this->get($property);
 466  
 467      if($property = $this->mapAddToProperty($method))
 468      {
 469        $this->_addToProperty($property, $args[0]);
 470        return;
 471      }
 472      return parent :: __call($method, $args);
 473    }
 474  
 475    protected function _addToProperty($property, $value)
 476    {
 477      $collection = $this->get($property);
 478      if(!is_object($collection))
 479        throw new lmbARException("Collection object info for property '$property' is missing");
 480  
 481      $collection->add($value);
 482    }
 483  
 484    protected function _izLazyAttribute($property)
 485    {
 486      return in_array($property, $this->_lazy_attributes);
 487    }
 488  
 489    protected function _hasLazyAttributes()
 490    {
 491      if(!$this->_lazy_attributes)
 492        return false;
 493  
 494      foreach($this->_lazy_attributes as $attribute)
 495        if(!$this->hasAttribute($attribute))
 496          return true;
 497  
 498      return false;
 499    }
 500  
 501    protected function _loadLazyAttribute($property)
 502    {
 503      $record = $this->_db_table->selectRecordById($this->getId(), array($property));
 504      $processed = $this->_decodeDbValues($record);
 505      $this->_setDbValue($property, $processed[$property]);
 506    }
 507  
 508    protected function _loadLazyAttributes()
 509    {
 510      foreach($this->_lazy_attributes as $attribute)
 511        $this->_loadLazyAttribute($attribute);
 512    }
 513    /**

 514     *  Generic magic getter for any attribute

 515     *  @param string property name

 516     *  @return mixed

 517     */
 518    function get($property)
 519    {
 520      if(!$this->isNew() && $this->_izLazyAttribute($property) && !$this->hasAttribute($property))
 521        $this->_loadLazyAttribute($property);
 522  
 523      if($this->_hasValueObjectRelation($property))
 524        return $this->_getValueObject($property);
 525  
 526      $value = parent :: get($property);
 527  
 528      if(isset($value))
 529        return $value;
 530  
 531      if(!$this->isNew() && $this->_hasBelongsToRelation($property))
 532      {
 533        $object = $this->_loadBelongsToObject($property);
 534        $this->_setRaw($property, $object);
 535        return $object;
 536      }
 537  
 538      if(!$this->isNew() && $this->_hasManyBelongsToRelation($property))
 539      {
 540        $object = $this->_loadManyBelongsToObject($property);
 541        $this->_setRaw($property, $object);
 542        return $object;
 543      }
 544  
 545      if(!$this->isNew() && $this->_hasOneToOneRelation($property))
 546      {
 547        $object = $this->_loadOneToOneObject($property);
 548        $this->