| [ Index ] |
PHP Cross Reference of Limb3 |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 * Limb PHP Framework 4 * 5 * @link http://limb-project.com 6 * @copyright Copyright © 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->