PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/app/protected/extensions/zurmoinc/framework/models/RedBeanModel.php

https://bitbucket.org/sanbrar/zurmo_invoice
PHP | 2903 lines | 2053 code | 140 blank | 710 comment | 248 complexity | 0406663f0b373d5451d7792997afe41b MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, LGPL-3.0, BSD-2-Clause, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*********************************************************************************
  3. * Zurmo is a customer relationship management program developed by
  4. * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
  5. *
  6. * Zurmo is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU General Public License version 3 as published by the
  8. * Free Software Foundation with the addition of the following permission added
  9. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10. * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
  11. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12. *
  13. * Zurmo is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU General Public License along with
  19. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21. * 02110-1301 USA.
  22. *
  23. * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
  24. * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
  25. ********************************************************************************/
  26. /**
  27. * Abstraction over the top of an application database accessed via
  28. * <a href="http://www.redbeanphp.com/">RedBean</a>. The base class for
  29. * an MVC model. Replaces the M part of MVC in Yii. Yii maps from the
  30. * database scheme to the objects, (good for database guys, not so good
  31. * for OO guys), this maps from objects to the database schema.
  32. *
  33. * A domain model is created by extending RedBeanModel and supplying
  34. * a getDefaultMetadata() method.
  35. *
  36. * Static getXxxx() methods can be supplied to query for the given domain
  37. * models, and instance methods should supply additional behaviour.
  38. *
  39. * getDefaultMetadata() returns an array of the class name mapped to
  40. * an array containing 'members' mapped to an array of member names,
  41. * (to be accessed as $model->memberName).
  42. *
  43. * It can then optionally have, 'relations' mapped
  44. * to an array of relation names, (to be accessed as $model->relationName),
  45. * mapped to its type, (the extending model class to which it relates).
  46. *
  47. * And it can then optionally have as well, 'rules' mapped to an array of
  48. * attribute names, (attributes are members and relations), a validator name,
  49. * and the parameters to the validator, if any, as per the Yii::CModel::rules()
  50. * method.See http://www.yiiframework.com/wiki/56/reference-model-rules-validation.
  51. *
  52. * These are used to automatically and dynamically create the database
  53. * schema on the fly as opposed to Yii's getting attributes from an
  54. * already existing schema.
  55. */
  56. abstract class RedBeanModel extends ObservableComponent implements Serializable
  57. {
  58. /**
  59. * Models that have not been saved yet have no id as far
  60. * as the database is concerned. Until they are saved they are
  61. * assigned a negative id, so that they have identity.
  62. * @var integer
  63. */
  64. private static $nextPseudoId = -1;
  65. /**
  66. * Array of static models. Used by Observers @see ObservableComponent to add events to a class.
  67. * @var array
  68. */
  69. private static $_models = array();
  70. /*
  71. * The id of an unsaved model.
  72. * @var integer
  73. */
  74. private $pseudoId;
  75. /**
  76. * When creating the class heirarchy for bean creation and maintenence, which class is the last class in the
  77. * lineage to create a bean for? Normally the RedBeanModel is the lastClass in the line, and therefore there
  78. * will not be a table redbeanmodel. Some classes that extend RedBeanModel might want the line to stop before
  79. * RedBeanModel since creating a table with just an 'id' would be pointless. @see OwnedModel
  80. * @var string
  81. */
  82. protected static $lastClassInBeanHeirarchy = 'RedBeanModel';
  83. // A model maps to one or more beans. If Person extends RedBeanModel
  84. // there is one bean, but if User then extends Person a User model
  85. // has two beans, the one holding the person data and the one holding
  86. // the extended User data. In this way in inheritance hierarchy from
  87. // model is normalized over several tables, one for each extending
  88. // class.
  89. private $modelClassNameToBean = array();
  90. private $attributeNameToBeanAndClassName = array();
  91. private $attributeNamesNotBelongsToOrManyMany = array();
  92. private $relationNameToRelationTypeModelClassNameAndOwns = array();
  93. private $relationNameToRelatedModel = array();
  94. private $unlinkedRelationNames = array();
  95. private $validators = array();
  96. private $attributeNameToErrors = array();
  97. private $scenarioName = '';
  98. // An object is automatcally savable if it is new or contains
  99. // modified members or related objects.
  100. // If it is newly created and has never had any data put into it
  101. // it can be saved explicitly but it wont be saved automatically
  102. // when it is a related model and will be redispensed next
  103. // time it is referenced.
  104. protected $modified = false;
  105. protected $deleted = false;
  106. protected $isInIsModified = false;
  107. protected $isInHasErrors = false;
  108. protected $isInGetErrors = false;
  109. protected $isValidating = false;
  110. protected $isSaving = false;
  111. protected $isNewModel = false;
  112. /**
  113. * Can this model be saved when save is called from a related model? True if it can, false if it cannot.
  114. * Setting this value to false can reduce unnecessary queries to the database. If the models of a class do
  115. * not change often then it can make sense to set this to false. An example is @see Currency.
  116. * @var boolean
  117. */
  118. protected $isSavableFromRelation = true;
  119. // Mapping of Yii validators to validators doing things that
  120. // are either required for RedBean, or that simply implement
  121. // The semantics that we want.
  122. private static $yiiValidatorsToRedBeanValidators = array(
  123. 'CDefaultValueValidator' => 'RedBeanModelDefaultValueValidator',
  124. 'CNumberValidator' => 'RedBeanModelNumberValidator',
  125. 'CTypeValidator' => 'RedBeanModelTypeValidator',
  126. 'CRequiredValidator' => 'RedBeanModelRequiredValidator',
  127. 'CUniqueValidator' => 'RedBeanModelUniqueValidator',
  128. 'defaultCalculatedDate' => 'RedBeanModelDefaultCalculatedDateValidator',
  129. 'readOnly' => 'RedBeanModelReadOnlyValidator',
  130. 'dateTimeDefault' => 'RedBeanModelDateTimeDefaultValueValidator',
  131. );
  132. /**
  133. * Used in an extending class's getDefaultMetadata() method to specify
  134. * that a relation is 1:1 and that the class on the side of the relationship where this is not a column in that
  135. * model's table. Example: model X HAS_ONE Y. There will be a y_id on the x table. But in Y you would have
  136. * HAS_ONE_BELONGS_TO X and there would be no column in the y table.
  137. */
  138. const HAS_ONE_BELONGS_TO = 0;
  139. /**
  140. * Used in an extending class's getDefaultMetadata() method to specify
  141. * that a relation is 1:M and that the class on the M side of the
  142. * relation.
  143. * Note: Currently if you have a relation that is set to HAS_MANY_BELONGS_TO, then that relation name
  144. * must be the strtolower() same as the related model class name. This is the current support for this
  145. * relation type. If something different is set, an exception will be thrown.
  146. */
  147. const HAS_MANY_BELONGS_TO = 1;
  148. /**
  149. * Used in an extending class's getDefaultMetadata() method to specify
  150. * that a relation is 1:1.
  151. */
  152. const HAS_ONE = 2;
  153. /**
  154. * Used in an extending class's getDefaultMetadata() method to specify
  155. * that a relation is 1:M and that the class is on the 1 side of the
  156. * relation.
  157. */
  158. const HAS_MANY = 3;
  159. /**
  160. * Used in an extending class's getDefaultMetadata() method to specify
  161. * that a relation is M:N and that the class on the either side of the
  162. * relation.
  163. */
  164. const MANY_MANY = 4;
  165. /**
  166. * Used in an extending class's getDefaultMetadata() method to specify
  167. * that a 1:1 or 1:M relation is one in which the left side of the relation
  168. * owns the model or models on the right side, meaning that if the model
  169. * is deleted it owns the related models and they are deleted along with it.
  170. * If not specified the related model is independent and is not deleted.
  171. */
  172. const OWNED = true;
  173. /**
  174. * Returns the static model of the specified AR class.
  175. * The model returned is a static instance of the AR class.
  176. * It is provided for invoking class-level methods (something similar to static class methods.)
  177. *
  178. * EVERY derived AR class must override this method as follows,
  179. * <pre>
  180. * public static function model($className=__CLASS__)
  181. * {
  182. * return parent::model($className);
  183. * }
  184. * </pre>
  185. *
  186. * @param string $className active record class name.
  187. * @return CActiveRecord active record model instance.
  188. */
  189. public static function model($className = null)
  190. {
  191. if ($className == null)
  192. {
  193. $className = get_called_class();
  194. }
  195. if (isset(self::$_models[$className]))
  196. {
  197. return self::$_models[$className];
  198. }
  199. else
  200. {
  201. $model = self::$_models[$className] = new $className(false);
  202. return $model;
  203. }
  204. }
  205. /**
  206. * Gets all the models from the database of the named model type.
  207. * @param $orderBy TODO
  208. * @param $modelClassName Pass only when getting it at runtime
  209. * gets the wrong name.
  210. * @return An array of models of the type of the extending model.
  211. */
  212. public static function getAll($orderBy = null, $sortDescending = false, $modelClassName = null)
  213. {
  214. assert('$orderBy === null || is_string($orderBy) && $orderBy != ""');
  215. assert('is_bool($sortDescending)');
  216. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  217. $quote = DatabaseCompatibilityUtil::getQuote();
  218. $orderBySql = null;
  219. if ($orderBy !== null)
  220. {
  221. $orderBySql = "$quote$orderBy$quote";
  222. if ($sortDescending)
  223. {
  224. $orderBySql .= ' desc';
  225. }
  226. }
  227. return static::getSubset(null, null, null, null, $orderBySql, $modelClassName);
  228. }
  229. /**
  230. * Gets a range of models from the database of the named model type.
  231. * @param $modelClassName
  232. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  233. * @param $offset The zero based index of the first model to be returned.
  234. * @param $count The number of models to be returned.
  235. * @param $where
  236. * @param $orderBy - sql string. Example 'a desc' or 'a.b desc'. Currently only supports non-related attributes
  237. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  238. * @return An array of models of the type of the extending model.
  239. */
  240. public static function getSubset(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  241. $offset = null, $count = null,
  242. $where = null, $orderBy = null,
  243. $modelClassName = null,
  244. $selectDistinct = false)
  245. {
  246. assert('$offset === null || is_integer($offset) && $offset >= 0');
  247. assert('$count === null || is_integer($count) && $count >= 1');
  248. assert('$where === null || is_string ($where) && $where != ""');
  249. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  250. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  251. if ($modelClassName === null)
  252. {
  253. $modelClassName = get_called_class();
  254. }
  255. if ($joinTablesAdapter == null)
  256. {
  257. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  258. }
  259. $tableName = self::getTableName($modelClassName);
  260. $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, $offset, $count, $where,
  261. $orderBy, false, $selectDistinct);
  262. $ids = R::getCol($sql);
  263. $tableName = self::getTableName($modelClassName);
  264. $beans = R::batch ($tableName, $ids);
  265. return self::makeModels($beans, $modelClassName);
  266. }
  267. /**
  268. * @param boolean $selectCount If true then make this a count query. If false, select ids from rows.
  269. * @param array $quotedExtraSelectColumnNameAndAliases - extra columns to select.
  270. * @return string - sql statement.
  271. */
  272. public static function makeSubsetOrCountSqlQuery($tableName,
  273. RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter,
  274. $offset = null, $count = null,
  275. $where = null, $orderBy = null,
  276. $selectCount = false,
  277. $selectDistinct = false,
  278. array $quotedExtraSelectColumnNameAndAliases = array())
  279. {
  280. assert('is_string($tableName) && $tableName != ""');
  281. assert('$offset === null || is_integer($offset) && $offset >= 0');
  282. assert('$count === null || is_integer($count) && $count >= 1');
  283. assert('$where === null || is_string ($where) && $where != ""');
  284. assert('$orderBy === null || is_string ($orderBy) && $orderBy != ""');
  285. assert('is_bool($selectCount)');
  286. assert('is_bool($selectDistinct)');
  287. $selectQueryAdapter = new RedBeanModelSelectQueryAdapter($selectDistinct);
  288. if ($selectCount)
  289. {
  290. $selectQueryAdapter->addCountClause($tableName);
  291. }
  292. else
  293. {
  294. $selectQueryAdapter->addClause($tableName, 'id', 'id');
  295. }
  296. foreach ($quotedExtraSelectColumnNameAndAliases as $columnName => $columnAlias)
  297. {
  298. $selectQueryAdapter->addClauseWithColumnNameOnlyAndNoEnclosure($columnName, $columnAlias);
  299. }
  300. return SQLQueryUtil::
  301. makeQuery($tableName, $selectQueryAdapter, $joinTablesAdapter, $offset, $count, $where, $orderBy);
  302. }
  303. /**
  304. * @param $modelClassName
  305. * @param $joinTablesAdapter null or instance of joinTablesAdapter.
  306. * @param $modelClassName Pass only when getting it at runtime gets the wrong name.
  307. */
  308. public static function getCount(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter = null,
  309. $where = null, $modelClassName = null, $selectDistinct = false)
  310. {
  311. assert('$where === null || is_string($where)');
  312. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  313. if ($modelClassName === null)
  314. {
  315. $modelClassName = get_called_class();
  316. }
  317. if ($joinTablesAdapter == null)
  318. {
  319. $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter($modelClassName);
  320. }
  321. $tableName = self::getTableName($modelClassName);
  322. $sql = static::makeSubsetOrCountSqlQuery($tableName, $joinTablesAdapter, null, null, $where, null, true,
  323. $selectDistinct);
  324. $count = R::getCell($sql);
  325. if ($count === null)
  326. {
  327. $count = 0;
  328. }
  329. return $count;
  330. }
  331. /**
  332. * Gets a model from the database by Id.
  333. * @param $id Integer Id.
  334. * @param $modelClassName Pass only when getting it at runtime
  335. * gets the wrong name.
  336. * @return A model of the type of the extending model.
  337. */
  338. public static function getById($id, $modelClassName = null)
  339. {
  340. assert('is_integer($id) && $id > 0');
  341. assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
  342. // I would have thought it was correct to user R::load() and get
  343. // a null, or error or something if the bean doesn't exist, but
  344. // it still returns a bean. So until I've investigated further
  345. // I'm using Finder.
  346. if ($modelClassName === null)
  347. {
  348. $modelClassName = get_called_class();
  349. }
  350. $tableName = self::getTableName($modelClassName);
  351. $beans = RedBean_Plugin_Finder::where($tableName, "id = '$id'");
  352. assert('count($beans) <= 1');
  353. if (count($beans) == 0)
  354. {
  355. throw new NotFoundException();
  356. }
  357. return RedBeanModel::makeModel(end($beans), $modelClassName);
  358. }
  359. public function getIsNewModel()
  360. {
  361. return $this->isNewModel;
  362. }
  363. /**
  364. * Constructs a new model.
  365. * Important:
  366. * Models are only constructed with beans by the RedBeanModel. Beans are
  367. * never used by the application directly.
  368. * The application can construct a new model object by constructing a
  369. * model without specifying a bean. In other words, if Php had
  370. * overloading a constructor with $setDefaults would be public, and
  371. * a constructor taking a $bean and $forceTreatAsCreation would be private.
  372. * @param $setDefaults. If false the default validators will not be run
  373. * on construction. The Yii way is that defaults are
  374. * filled in after the fact, which is counter the usual
  375. * for objects.
  376. * @param $bean A bean. Never specified by an application.
  377. * @param $forceTreatAsCreation. Never specified by an application.
  378. * @see getById()
  379. * @see makeModel()
  380. * @see makeModels()
  381. */
  382. public function __construct($setDefaults = true, RedBean_OODBBean $bean = null, $forceTreatAsCreation = false)
  383. {
  384. $this->pseudoId = self::$nextPseudoId--;
  385. $this->init();
  386. if ($bean === null)
  387. {
  388. foreach (array_reverse(RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy)) as $modelClassName)
  389. {
  390. $tableName = self::getTableName($modelClassName);
  391. $newBean = R::dispense($tableName);
  392. $this->modelClassNameToBean[$modelClassName] = $newBean;
  393. $this->mapAndCacheMetadataAndSetHints($modelClassName, $newBean);
  394. }
  395. // The yii way of doing defaults is the the default validator
  396. // fills in the defaults on attributes that don't have values
  397. // when you validator, or save. This weird, since when you get
  398. // a model the things with defaults have not been defaulted!
  399. // We want that semantic.
  400. if ($setDefaults)
  401. {
  402. $this->runDefaultValidators();
  403. }
  404. $forceTreatAsCreation = true;
  405. }
  406. else
  407. {
  408. assert('$bean->id > 0');
  409. $first = true;
  410. foreach (RuntimeUtil::getClassHierarchy(get_class($this), static::$lastClassInBeanHeirarchy) as $modelClassName)
  411. {
  412. if ($first)
  413. {
  414. $lastBean = $bean;
  415. $first = false;
  416. }
  417. else
  418. {
  419. $tableName = self::getTableName($modelClassName);
  420. $lastBean = R::getBean($lastBean, $tableName);
  421. if ($lastBean === null)
  422. {
  423. throw new MissingBeanException();
  424. }
  425. assert('$lastBean->id > 0');
  426. }
  427. $this->modelClassNameToBean[$modelClassName] = $lastBean;
  428. $this->mapAndCacheMetadataAndSetHints($modelClassName, $lastBean);
  429. }
  430. $this->modelClassNameToBean = array_reverse($this->modelClassNameToBean);
  431. }
  432. $this->constructDerived($bean, $setDefaults);
  433. if ($forceTreatAsCreation)
  434. {
  435. $this->onCreated();
  436. }
  437. else
  438. {
  439. $this->onLoaded();
  440. RedBeanModelsCache::cacheModel($this);
  441. }
  442. $this->modified = false;
  443. }
  444. // Derived classes can insert additional steps into the construction.
  445. protected function constructDerived($bean, $setDefaults)
  446. {
  447. assert('$bean === null || $bean instanceof RedBean_OODBBean');
  448. assert('is_bool($setDefaults)');
  449. }
  450. /**
  451. * Utilized when pieces of information need to be constructed on an existing model, that can potentially be
  452. * missing. For example, if a model is created, then a custom field is added, it is possible the cached model
  453. * is missing the custom field customFieldData.
  454. * @param unknown_type $bean
  455. */
  456. protected function constructIncomplete($bean)
  457. {
  458. assert('$bean === null || $bean instanceof RedBean_OODBBean');
  459. $this->init();
  460. }
  461. public function serialize()
  462. {
  463. return serialize(array(
  464. $this->pseudoId,
  465. $this->modelClassNameToBean,
  466. $this->attributeNameToBeanAndClassName,
  467. $this->attributeNamesNotBelongsToOrManyMany,
  468. $this->relationNameToRelationTypeModelClassNameAndOwns,
  469. $this->validators,
  470. ));
  471. }
  472. public function unserialize($data)
  473. {
  474. try
  475. {
  476. $data = unserialize($data);
  477. assert('is_array($data)');
  478. if (count($data) != 6)
  479. {
  480. return null;
  481. }
  482. $this->pseudoId = $data[0];
  483. $this->modelClassNameToBean = $data[1];
  484. $this->attributeNameToBeanAndClassName = $data[2];
  485. $this->attributeNamesNotBelongsToOrManyMany = $data[3];
  486. $this->relationNameToRelationTypeModelClassNameAndOwns = $data[4];
  487. $this->validators = $data[5];
  488. $this->relationNameToRelatedModel = array();
  489. $this->unlinkedRelationNames = array();
  490. $this->attributeNameToErrors = array();
  491. $this->scenarioName = '';
  492. $this->modified = false;
  493. $this->deleted = false;
  494. $this->isInIsModified = false;
  495. $this->isInHasErrors = false;
  496. $this->isInGetErrors = false;
  497. $this->isValidating = false;
  498. $this->isSaving = false;
  499. }
  500. catch (Exception $e)
  501. {
  502. return null;
  503. }
  504. }
  505. /**
  506. * Overriding constructors must call this function to ensure that
  507. * they leave the newly constructed instance not modified since
  508. * anything modifying the class during constructionm will set it
  509. * modified automatically.
  510. */
  511. protected function setNotModified()
  512. {
  513. $this->modified = false; // This sets this class to the right state.
  514. assert('!$this->isModified()'); // This tests that related classes are in the right state.
  515. }
  516. /**
  517. * By default the table name is the lowercased class name. If this
  518. * conflicts with a database keyword override to return true.
  519. * RedBean does not quote table names in most cases.
  520. */
  521. // Public for unit testing.
  522. public static function mangleTableName()
  523. {
  524. return false;
  525. }
  526. /**
  527. * Returns the table name for a class.
  528. * For use by RedBeanModelDataProvider. It will not
  529. * be of any use to an application. Applications
  530. * should not be doing anything table related.
  531. * Derived classes can refer directly to the
  532. * table name.
  533. */
  534. public static function getTableName($modelClassName)
  535. {
  536. assert('is_string($modelClassName) && $modelClassName != ""');
  537. $tableName = strtolower($modelClassName);
  538. if ($modelClassName::mangleTableName())
  539. {
  540. $tableName = '_' . $tableName;
  541. }
  542. return $tableName;
  543. }
  544. /**
  545. * Returns the table names for an array of classes.
  546. * For use by RedBeanModelDataProvider. It will not
  547. * be of any use to an application.
  548. */
  549. public static function getTableNames($classNames)
  550. {
  551. $tableNames = array();
  552. foreach ($classNames as $className)
  553. {
  554. $tableNames[] = self::getTableName($className);
  555. }
  556. return $tableNames;
  557. }
  558. /**
  559. * Used by classes such as containers which use sql to
  560. * optimize getting models from the database.
  561. */
  562. public static function getForeignKeyName($modelClassName, $relationName)
  563. {
  564. assert('is_string($modelClassName)');
  565. assert('$modelClassName != ""');
  566. $metadata = $modelClassName::getMetadata();
  567. foreach ($metadata as $modelClassName => $modelClassMetadata)
  568. {
  569. if (isset($metadata[$modelClassName]["relations"]) &&
  570. array_key_exists($relationName, $metadata[$modelClassName]["relations"]))
  571. {
  572. $relatedModelClassName = $metadata[$modelClassName]['relations'][$relationName][1];
  573. $relatedModelTableName = self::getTableName($relatedModelClassName);
  574. $columnName = '';
  575. if (strtolower($relationName) != strtolower($relatedModelClassName))
  576. {
  577. $columnName = strtolower($relationName) . '_';
  578. }
  579. $columnName .= $relatedModelTableName . '_id';
  580. return $columnName;
  581. }
  582. }
  583. throw new NotSupportedException;
  584. }
  585. /**
  586. * Called on construction when a new model is created.
  587. */
  588. protected function onCreated()
  589. {
  590. }
  591. /**
  592. * Called on construction when a model is loaded.
  593. */
  594. protected function onLoaded()
  595. {
  596. }
  597. /**
  598. * Called when a model is modified.
  599. */
  600. protected function onModified()
  601. {
  602. }
  603. /**
  604. * Used for mixins.
  605. */
  606. protected function mapAndCacheMetadataAndSetHints($modelClassName, RedBean_OODBBean $bean)
  607. {
  608. assert('is_string($modelClassName)');
  609. assert('$modelClassName != ""');
  610. $metadata = $this->getMetadata();
  611. if (isset($metadata[$modelClassName]))
  612. {
  613. $hints = array();
  614. if (isset($metadata[$modelClassName]['members']))
  615. {
  616. foreach ($metadata[$modelClassName]['members'] as $memberName)
  617. {
  618. $this->attributeNameToBeanAndClassName[$memberName] = array($bean, $modelClassName);
  619. $this->attributeNamesNotBelongsToOrManyMany[] = $memberName;
  620. if (substr($memberName, -2) == 'Id')
  621. {
  622. $columnName = strtolower($memberName);
  623. $hints[$columnName] = 'id';
  624. }
  625. }
  626. }
  627. if (isset($metadata[$modelClassName]['relations']))
  628. {
  629. foreach ($metadata[$modelClassName]['relations'] as $relationName => $relationTypeModelClassNameAndOwns)
  630. {
  631. assert('in_array(count($relationTypeModelClassNameAndOwns), array(2, 3))');
  632. $relationType = $relationTypeModelClassNameAndOwns[0];
  633. $relationModelClassName = $relationTypeModelClassNameAndOwns[1];
  634. if ($relationType == self::HAS_MANY_BELONGS_TO &&
  635. strtolower($relationName) != strtolower($relationModelClassName))
  636. {
  637. $label = 'Relations of type HAS_MANY_BELONGS_TO must have the relation name ' .
  638. 'the same as the related model class name. Relation: {relationName} ' .
  639. 'Relation model class name: {relationModelClassName}';
  640. throw new NotSupportedException(Yii::t('Default', $label,
  641. array('{relationName}' => $relationName,
  642. '{relationModelClassName}' => $relationModelClassName)));
  643. }
  644. if (count($relationTypeModelClassNameAndOwns) == 3)
  645. {
  646. $relationTypeModelClassNameAndOwns[2] == self::OWNED;
  647. $owns = true;
  648. }
  649. else
  650. {
  651. $owns = false;
  652. }
  653. assert('in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, ' .
  654. 'self::HAS_ONE, self::HAS_MANY, self::MANY_MANY))');
  655. $this->attributeNameToBeanAndClassName[$relationName] = array($bean, $modelClassName);
  656. $this->relationNameToRelationTypeModelClassNameAndOwns[$relationName] = array($relationType, $relationModelClassName, $owns);
  657. if (!in_array($relationType, array(self::HAS_ONE_BELONGS_TO, self::HAS_MANY_BELONGS_TO, self::MANY_MANY)))
  658. {
  659. $this->attributeNamesNotBelongsToOrManyMany[] = $relationName;
  660. }
  661. }
  662. }
  663. if (isset($metadata[$modelClassName]['rules']))
  664. {
  665. foreach ($metadata[$modelClassName]['rules'] as $validatorMetadata)
  666. {
  667. assert('isset($validatorMetadata[0])');
  668. assert('isset($validatorMetadata[1])');
  669. $attributeName = $validatorMetadata[0];
  670. // Each rule in RedBeanModel must specify one attribute name.
  671. // This was just better style, now it is mandatory.
  672. assert('strpos($attributeName, " ") === false');
  673. $validatorName = $validatorMetadata[1];
  674. $validatorParameters = array_slice($validatorMetadata, 2);
  675. if (isset(CValidator::$builtInValidators[$validatorName]))
  676. {
  677. $validatorName = CValidator::$builtInValidators[$validatorName];
  678. }
  679. if (isset(self::$yiiValidatorsToRedBeanValidators[$validatorName]))
  680. {
  681. $validatorName = self::$yiiValidatorsToRedBeanValidators[$validatorName];
  682. }
  683. $validator = CValidator::createValidator($validatorName, $this, $attributeName, $validatorParameters);
  684. switch ($validatorName)
  685. {
  686. case 'RedBeanModelTypeValidator':
  687. case 'TypeValidator':
  688. $columnName = strtolower($attributeName);
  689. if (array_key_exists($columnName, $hints))
  690. {
  691. unset($hints[$columnName]);
  692. }
  693. if (in_array($validator->type, array('date', 'datetime', 'blob', 'longblob')))
  694. {
  695. $hints[$columnName] = $validator->type;
  696. }
  697. break;
  698. case 'CBooleanValidator':
  699. $columnName = strtolower($attributeName);
  700. $hints[$columnName] = 'boolean';
  701. break;
  702. case 'RedBeanModelUniqueValidator':
  703. if (!$this->isRelation($attributeName))
  704. {
  705. $bean->setMeta("buildcommand.unique", array(array($attributeName)));
  706. }
  707. else
  708. {
  709. $relatedModelClassName = $this->relationNameToRelationTypeModelClassNameAndOwns[$attributeName][1];
  710. $relatedModelTableName = self::getTableName($relatedModelClassName);
  711. $columnName = strtolower($attributeName);
  712. if ($columnName != $relatedModelTableName)
  713. {
  714. $columnName .= '_' . $relatedModelTableName;
  715. }
  716. $columnName .= '_id';
  717. $bean->setMeta("buildcommand.unique", array(array($columnName)));
  718. }
  719. break;
  720. }
  721. $this->validators[] = $validator;
  722. }
  723. }
  724. $bean->setMeta('hint', $hints);
  725. }
  726. }
  727. /**
  728. * Used for mixins.
  729. */
  730. protected function runDefaultValidators()
  731. {
  732. foreach ($this->validators as $validator)
  733. {
  734. if ($validator instanceof CDefaultValueValidator)
  735. {
  736. $validator->validate($this);
  737. }
  738. }
  739. }
  740. /**
  741. * For use only by RedBeanModel and RedBeanModels. Beans are
  742. * never used by the application directly.
  743. */
  744. public function getPrimaryBean()
  745. {
  746. return end($this->modelClassNameToBean);
  747. }
  748. /**
  749. * Used for optimization.
  750. */
  751. public function getClassId($modelClassName)
  752. {
  753. assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
  754. return intval($this->getClassBean($modelClassName)->id); // Trying to combat the slop.
  755. }
  756. public function getClassBean($modelClassName)
  757. {
  758. assert('is_string($modelClassName)');
  759. assert('$modelClassName != ""');
  760. assert('array_key_exists($modelClassName, $this->modelClassNameToBean)');
  761. return $this->modelClassNameToBean[$modelClassName];
  762. }
  763. /**
  764. * Used for mixins.
  765. */
  766. protected function setClassBean($modelClassName, RedBean_OODBBean $bean)
  767. {
  768. assert('is_string($modelClassName)');
  769. assert('$modelClassName != ""');
  770. assert('!array_key_exists($modelClassName, $this->modelClassNameToBean)');
  771. $this->modelClassNameToBean = array_merge(array($modelClassName => $bean),
  772. $this->modelClassNameToBean);
  773. }
  774. public function getModelIdentifier()
  775. {
  776. return get_class($this) . strval($this->getPrimaryBean()->id);
  777. }
  778. /**
  779. * Returns metadata for the model. Attempts to cache metadata, if it is not already cached.
  780. * @see getDefaultMetadata()
  781. * @returns An array of metadata.
  782. */
  783. public static function getMetadata()
  784. {
  785. try
  786. {
  787. return GeneralCache::getEntry(get_called_class() . 'Metadata');
  788. }
  789. catch (NotFoundException $e)
  790. {
  791. $className = get_called_Class();
  792. $defaultMetadata = $className::getDefaultMetadata();
  793. $metadata = array();
  794. foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
  795. {
  796. if ($modelClassName::canSaveMetadata())
  797. {
  798. try
  799. {
  800. $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
  801. $metadata[$modelClassName] = unserialize($globalMetadata->serializedMetadata);
  802. }
  803. catch (NotFoundException $e)
  804. {
  805. if (isset($defaultMetadata[$modelClassName]))
  806. {
  807. $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
  808. }
  809. }
  810. }
  811. else
  812. {
  813. if (isset($defaultMetadata[$modelClassName]))
  814. {
  815. $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
  816. }
  817. }
  818. }
  819. if (YII_DEBUG)
  820. {
  821. self::assertMetadataIsValid($metadata);
  822. }
  823. GeneralCache::cacheEntry(get_called_class() . 'Metadata', $metadata);
  824. return $metadata;
  825. }
  826. }
  827. /**
  828. * By default models cannot save their metadata, allowing
  829. * them to be loaded quickly because the loading of of
  830. * metadata can be avoided as much as possible.
  831. * To make a model able to save its metadata override
  832. * this method to return true. PUT it before the
  833. * getDefaultMetadata in the derived class.
  834. */
  835. public static function canSaveMetadata()
  836. {
  837. return false;
  838. }
  839. /**
  840. * Sets metadata for the model.
  841. * @see getDefaultMetadata()
  842. * @returns An array of metadata.
  843. */
  844. public static function setMetadata(array $metadata)
  845. {
  846. if (YII_DEBUG)
  847. {
  848. self::assertMetadataIsValid($metadata);
  849. }
  850. $className = get_called_class();
  851. foreach (array_reverse(RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy)) as $modelClassName)
  852. {
  853. if ($modelClassName::canSaveMetadata())
  854. {
  855. if (isset($metadata[$modelClassName]))
  856. {
  857. try
  858. {
  859. $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
  860. }
  861. catch (NotFoundException $e)
  862. {
  863. $globalMetadata = new GlobalMetadata();
  864. $globalMetadata->className = $modelClassName;
  865. }
  866. $globalMetadata->serializedMetadata = serialize($metadata[$modelClassName]);
  867. $saved = $globalMetadata->save();
  868. // TODO: decide how to deal with this properly if it fails.
  869. // ie: throw or return false, or something other than
  870. // this naughty assert.
  871. assert('$saved');
  872. }
  873. }
  874. }
  875. RedBeanModelsCache::forgetAllByModelType(get_called_class());
  876. GeneralCache::forgetEntry(get_called_class() . 'Metadata');
  877. }
  878. /**
  879. * Returns the default meta data for the class.
  880. * It must be appended to the meta data
  881. * from the parent model, if any.
  882. */
  883. public static function getDefaultMetadata()
  884. {
  885. return array();
  886. }
  887. protected static function assertMetadataIsValid(array $metadata)
  888. {
  889. $className = get_called_Class();
  890. foreach (RuntimeUtil::getClassHierarchy($className, static::$lastClassInBeanHeirarchy) as $modelClassName)
  891. {
  892. if (isset($metadata[$modelClassName]['members']))
  893. {
  894. assert('is_array($metadata[$modelClassName]["members"])');
  895. foreach ($metadata[$modelClassName]["members"] as $memberName)
  896. {
  897. assert('ctype_lower($memberName{0})');
  898. }
  899. }
  900. if (isset($metadata[$modelClassName]['relations']))
  901. {
  902. assert('is_array($metadata[$modelClassName]["relations"])');
  903. foreach ($metadata[$modelClassName]["relations"] as $relationName => $notUsed)
  904. {
  905. assert('ctype_lower($relationName{0})');
  906. }
  907. }
  908. if (isset($metadata[$modelClassName]['rules']))
  909. {
  910. assert('is_array($metadata[$modelClassName]["rules"])');
  911. }
  912. if (isset($metadata[$modelClassName]['defaultSortAttribute']))
  913. {
  914. assert('is_string($metadata[$modelClassName]["defaultSortAttribute"])');
  915. }
  916. if (isset($metadata[$modelClassName]['rollupRelations']))
  917. {
  918. assert('is_array($metadata[$modelClassName]["rollupRelations"])');
  919. }
  920. // Todo: add more rules here as I think of them.
  921. }
  922. }
  923. /**
  924. * Downcasting in general is a bad concept, but when pulling
  925. * a Person from the database it would require a lot of
  926. * jumping through hoops to make the RedBeanModel automatically
  927. * figure out if that person is really a User, Contact, Customer
  928. * or whatever might be derived from Person. So to avoid that
  929. * complication and performance hit where it is not necessary
  930. * this method can be used to convert a model to one of
  931. * a given set of derivatives. If model is not one
  932. * of those NotFoundException is thrown.
  933. */
  934. public function castDown(array $derivedModelClassNames)
  935. {
  936. $bean = $this->getPrimaryBean();
  937. $thisModelClassName = get_called_class();
  938. $key = strtolower($thisModelClassName) . '_id';
  939. foreach ($derivedModelClassNames as $modelClassNames)
  940. {
  941. if (is_string($modelClassNames))
  942. {
  943. $nextModelClassName = $modelClassNames;
  944. if (get_class($this) == $nextModelClassName)
  945. {
  946. return $this;
  947. }
  948. $nextBean = self::findNextDerivativeBean($bean, $thisModelClassName, $nextModelClassName);
  949. }
  950. else
  951. {
  952. assert('is_array($modelClassNames)');
  953. $targetModelClassName = end($modelClassNames);
  954. if (get_class($this) == $targetModelClassName)
  955. {
  956. return $this;
  957. }
  958. $currentModelClassName = $thisModelClassName;
  959. $nextBean = $bean;
  960. foreach ($modelClassNames as $nextModelClassName)
  961. {
  962. $nextBean = self::findNextDerivativeBean($nextBean, $currentModelClassName, $nextModelClassName);
  963. if ($nextBean === null)
  964. {
  965. break;
  966. }
  967. $currentModelClassName = $nextModelClassName;
  968. }
  969. }
  970. if ($nextBean !== null)
  971. {
  972. return self::makeModel($nextBean, $nextModelClassName);
  973. }
  974. }
  975. throw new NotFoundException();
  976. }
  977. private static function findNextDerivativeBean($bean, $modelClassName1, $modelClassName2)
  978. {
  979. $key = strtolower($modelClassName1) . '_id';
  980. $tableName = self::getTableName($modelClassName2);
  981. $beans = R::find($tableName, "$key = :id", array('id' => $bean->id));
  982. if (count($beans) == 1)
  983. {
  984. return reset($beans);
  985. }
  986. return null;
  987. }
  988. /**
  989. * Returns whether the given object is of the same type with the
  990. * same id.
  991. */
  992. public function isSame(RedBeanModel $model)
  993. {
  994. // The two models are the same if they have the
  995. // same root model, and if for that model they
  996. // have the same id.
  997. $rootId1 = reset($this ->modelClassNameToBean)->id;
  998. $rootId2 = reset($model->modelClassNameToBean)->id;
  999. if ($rootId1 == 0)
  1000. {
  1001. $rootId1 = $this->pseudoId;
  1002. }
  1003. if ($rootId2 == 0)
  1004. {
  1005. $rootId2 = $model->pseudoId;
  1006. }
  1007. return $rootId1 == $rootId2 && $rootId1 != 0 &&
  1008. key($this ->modelClassNameToBean) ==
  1009. key($model->modelClassNameToBean);
  1010. }
  1011. /**
  1012. * Returns the displayable string for the class. Should be
  1013. * overridden in any model that can provide a meaningful string
  1014. * representation of itself.
  1015. * @return A string.
  1016. */
  1017. public function __toString()
  1018. {
  1019. return Yii::t('Default', '(None)');
  1020. }
  1021. /**
  1022. * Exposes the members and relations of the model as if
  1023. * they were actual attributes of the model. See __set().
  1024. * @param $attributeName A non-empty string that is the name of a
  1025. * member or relation.
  1026. * @see attributeNames()
  1027. * @return A value or model of the type specified as valid for the
  1028. * member or relation by the meta data supplied by the extending
  1029. * class's getMetadata() method.
  1030. */
  1031. public function __get($attributeName)
  1032. {
  1033. return $this->unrestrictedGet($attributeName);
  1034. }
  1035. /**
  1036. * A protected version of __get() for models to talk to themselves
  1037. * to use their dynamically created members from 'members'
  1038. * and 'relations' in its metadata.
  1039. */
  1040. protected function unrestrictedGet($attributeName)
  1041. {
  1042. assert('is_string($attributeName)');
  1043. assert('$attributeName != ""');
  1044. assert("property_exists(\$this, '$attributeName') || \$this->isAttribute('$attributeName')");
  1045. if (property_exists($this, $attributeName))
  1046. {
  1047. return $this->$attributeName;
  1048. }
  1049. elseif ($attributeName == 'id')
  1050. {
  1051. $id = intval($this->getPrimaryBean

Large files files are truncated, but you can click here to view the full file