PageRenderTime 85ms CodeModel.GetById 36ms RepoModel.GetById 6ms app.codeStats 0ms

/library/Dja/Db/Model.php

https://github.com/buldezir/dja_on_php
PHP | 509 lines | 300 code | 44 blank | 165 comment | 45 complexity | 145569fcc084a244051337ddc95471cb MD5 | raw file
  1. <?php
  2. abstract class Dja_Db_Model
  3. {
  4. /**
  5. * define db table name or it will be choose automaticaly
  6. * @var sting
  7. */
  8. protected static $_dbTableName = null;
  9. /**
  10. * MUST be provided in each model !
  11. * @var array
  12. */
  13. protected static $_fieldConfig;
  14. /**
  15. * true for empty model, set false when cloning
  16. * @var bool
  17. */
  18. protected $_readOnly = false;
  19. /**
  20. * loaded from db (update on save) or created (insert on save)
  21. * @var bool
  22. */
  23. protected $_isNew = true;
  24. /**
  25. * The data for each column in the row (column_name => value).
  26. * The keys must match the physical names of columns in the
  27. * table for which this row is defined.
  28. *
  29. * @var array
  30. */
  31. protected $_data = array();
  32. /**
  33. * This is array of lazy-loaded relation objects (models)
  34. *
  35. * @var array
  36. */
  37. protected $_relationData = array();
  38. /**
  39. * This is set to a copy of $_data when the data is fetched from
  40. * a database, specified as a new tuple in the constructor, or
  41. * when dirty data is posted to the database with save().
  42. *
  43. * @var array
  44. */
  45. protected $_cleanData = array();
  46. /**
  47. * Tracks columns where data has been updated. Allows more specific insert and
  48. * update operations.
  49. *
  50. * @var array
  51. */
  52. protected $_modifiedFields = array();
  53. /**
  54. * field options
  55. * @var Dja_Db_Model_Metadata
  56. */
  57. protected static $_metaData = array();
  58. /**
  59. *
  60. * @var array
  61. */
  62. protected static $_helpers = array();
  63. /**
  64. * Creates or returns the only instance of the a class.
  65. *
  66. * @return Dja_Db_Model_Metadata
  67. */
  68. final public static function metadata()
  69. {
  70. $calledClassName = get_called_class();
  71. if( !isset(self::$_metaData[$calledClassName]) ) {
  72. self::$_metaData[$calledClassName] = new Dja_Db_Model_Metadata($calledClassName);
  73. }
  74. return self::$_metaData[$calledClassName];
  75. }
  76. /**
  77. * usage: MyModel::objects()->filter('field', 'value')->order('-field') ...
  78. * @return Dja_Db_Model_Query
  79. */
  80. public static function objects()
  81. {
  82. //$className = get_called_class();
  83. return new Dja_Db_Model_Query(static::metadata());
  84. }
  85. /**
  86. *
  87. * @param string $name
  88. * @throws Dja_Db_Exception
  89. * @return Dja_Db_Model_Helper_Abstract
  90. */
  91. protected static function _getHelper($name)
  92. {
  93. if (isset(static::$_helpers[$name])) {
  94. if (!static::$_helpers[$name] instanceof Dja_Db_Model_Helper_Abstract) {
  95. static::$_helpers[$name] = new static::$_helpers[$name];
  96. }
  97. return static::$_helpers[$name];
  98. }
  99. throw new Dja_Db_Exception('No helper registered with name "'.$name.'"');
  100. }
  101. /**
  102. * shortcut
  103. * @param string $field
  104. * @return Dja_Db_Model_Metadata | Dja_Db_Model_Field_Base
  105. */
  106. final protected function _f($field = null)
  107. {
  108. $metadata = self::metadata();
  109. if ($field !== null) {
  110. return $metadata->getField($field);
  111. }
  112. return $metadata;
  113. }
  114. /**
  115. *
  116. */
  117. final public function __construct(array $data = array(), $isNew = true)
  118. {
  119. $this->_isNew = $isNew;
  120. if ($isNew) {
  121. $this->_data = $this->_f()->getDefaultValues();
  122. }
  123. if (!empty($data)) {
  124. $this->setFromArray($data, !$isNew);
  125. }
  126. $this->init();
  127. }
  128. protected function init()
  129. {
  130. }
  131. public function __call($method, $args)
  132. {
  133. array_unshift($args, $this);
  134. $matches = array();
  135. if (preg_match('#^get(\w+)By(\w+)$#', $method, $matches)) {
  136. $class = $matches[1];
  137. $field = strtolower($matches[2]);
  138. $relType = $this->_f()->getBackRel($class, $field);
  139. if ($relType !== false) {
  140. $modelClassName = 'Model_'.$class; // !!! change
  141. switch ($relType) {
  142. case Dja_Db_Model_Field_Base::REL_ONE2ONE:
  143. return $modelClassName::objects()->get();
  144. break;
  145. case Dja_Db_Model_Field_Base::REL_FOREIGNKEY:
  146. return $modelClassName::objects()->get();
  147. break;
  148. case Dja_Db_Model_Field_Base::REL_MANY2MANY:
  149. return $modelClassName::objects()->get();
  150. break;
  151. }
  152. } else {
  153. throw new Dja_Db_Exception('Relation not found !');
  154. }
  155. } else {
  156. $h = static::_getHelper($method);
  157. return call_user_func_array(array($h, 'call'), $args);
  158. }
  159. }
  160. public function __callStatic($method, $args)
  161. {
  162. array_unshift($args, get_called_class());
  163. $h = static::_getHelper($method);
  164. return call_user_func_array(array($h, 'callStatic'), $args);
  165. }
  166. /**
  167. * return php-representaion of field value
  168. *
  169. * int|string|bool|array for simple fields
  170. * Dja_Db_Model for OneToOne and ForeignKey fields
  171. * Dja_Db_Model_Query for ManyToMany fields
  172. *
  173. * @param string $key
  174. * @return mixed
  175. */
  176. protected function _get($key)
  177. {
  178. $metadata = self::metadata();
  179. $fieldObj = $metadata->getField($key);
  180. if ($metadata->isLocal($key)) {
  181. return $this->_data[$key];
  182. } elseif ($metadata->isVirtual($key)) {
  183. if (!$fieldObj->isRelation()) {
  184. return $this->_data[$fieldObj->name];
  185. } else {
  186. if (!array_key_exists($key, $this->_relationData)) {
  187. if (!empty($this->_data[$fieldObj->db_column])) {
  188. $this->_relationData[$key] = $fieldObj->getRelObject($this->_data[$fieldObj->db_column]);
  189. } else {
  190. return $this->_data[$fieldObj->db_column];
  191. }
  192. }
  193. return $this->_relationData[$key];
  194. }
  195. } elseif ($metadata->isM2M($key)) {
  196. if (!isset($this->_relationData[$key])) {
  197. $this->_relationData[$key] = $fieldObj->getRelQuery($this);
  198. }
  199. return $this->_relationData[$key];
  200. }
  201. }
  202. /**
  203. * set field value after validation
  204. * @param $key
  205. * @param $value
  206. * @param bool $force means it is actual data from db
  207. * @return void
  208. */
  209. protected function _set($key, $value, $force = false)
  210. {
  211. if ($this->isReadOnly()) {
  212. throw new Dja_Db_Exception("object is read-only");
  213. }
  214. $metadata = self::metadata();
  215. $fieldObj = $metadata->getField($key);
  216. if ($force === false && $fieldObj->editable === false) {
  217. throw new Dja_Db_Exception('Field "'.$key.'" is read-only');
  218. }
  219. if ($metadata->isLocal($key)) {
  220. if ($force) {
  221. $value = $fieldObj->toPhp($value);
  222. }
  223. $this->_data[$key] = $value;
  224. $this->_modifiedFields[$key] = true;
  225. } elseif ($metadata->isVirtual($key)) {
  226. if (!$fieldObj->isRelation()) {
  227. if ($force) {
  228. $value = $fieldObj->toPhp($value);
  229. }
  230. $this->_data[$fieldObj->name] = $value;
  231. $this->_modifiedFields[$fieldObj->name] = true;
  232. } else {
  233. if ($fieldObj->isValid($value)) {
  234. $this->_relationData[$key] = $value;
  235. $this->_data[$fieldObj->db_column] = $value->getPrimaryKeyValue();
  236. $this->_modifiedFields[$fieldObj->db_column] = true;
  237. } else {
  238. throw new Dja_Db_Exception("You provide invalid value for '{$key}'!");
  239. }
  240. }
  241. } elseif ($metadata->isM2M($key)) {
  242. if ($fieldObj->isValid($value)) {
  243. $this->_relationData[$key] = $value;
  244. } else {
  245. throw new Dja_Db_Exception("You provide invalid value for '{$key}'!");
  246. }
  247. }
  248. }
  249. public function __get($key)
  250. {
  251. return $this->_get($key);
  252. }
  253. public function __set($key, $value)
  254. {
  255. $this->_set($key, $value);
  256. }
  257. public function __isset($key)
  258. {
  259. return isset($this->_data[$key]);
  260. }
  261. public function getPrimaryKeyValue()
  262. {
  263. $fieldObj = $this->_f()->getPrimaryKey();
  264. if ($fieldObj !== null) {
  265. return $this->_get($fieldObj->name);
  266. }
  267. return null;
  268. }
  269. /**
  270. *
  271. * @param array $data
  272. * @param bool $force means it is actual data from db
  273. * @return void
  274. */
  275. public function setFromArray(array $data, $force = false)
  276. {
  277. $metadata = self::metadata();
  278. foreach ($data as $key => $value) {
  279. if (isset($metadata->$key)) {
  280. $this->_set($key, $value, $force);
  281. }
  282. }
  283. if ($force) {
  284. $this->_cleanData = $this->_data;
  285. $this->_modifiedFields = array();
  286. }
  287. return $this;
  288. }
  289. /**
  290. * convert all data (objects) to simple representation
  291. * @return array
  292. */
  293. public function toArray()
  294. {
  295. /*$result = array();
  296. foreach ($this->_data as $key => $value) {
  297. $fieldObj = $this->_f($key);
  298. if ($fieldObj->isRelation()) {
  299. $result[$fieldObj->db_column] = $value->toArray();
  300. } else {
  301. $result[$fieldObj->db_column] = $value;
  302. }
  303. }
  304. return $result;*/
  305. return $this->_data;
  306. }
  307. public function dump()
  308. {
  309. var_dump($this->_data);
  310. var_dump($this->_relationData);
  311. }
  312. /**
  313. * the same as toArray(), but returns data as object
  314. * @return stdClass
  315. */
  316. public function toObject()
  317. {
  318. return (object) $this->toArray();
  319. }
  320. public function save()
  321. {
  322. if ($this->isDirty() === false) {
  323. return false;
  324. }
  325. if ($this->fireBeforeSave()) {
  326. $pkfield = $this->_f()->getPrimaryKey();
  327. $pk = $this->_get($pkfield->db_column);
  328. $db = $this->_f()->getAdapter();
  329. if ($this->_isNew === false) { // update
  330. $db->update($this->_f()->getDbTableName(), $this->_data, array("{$pkfield->db_column} = ?" => $pk));
  331. } else { // insert
  332. $db->insert($this->_f()->getDbTableName(), $this->_data);
  333. $this->_set($pkfield->db_column, $db->lastInsertId());
  334. }
  335. // update actual data:
  336. $this->_cleanData = $this->_data;
  337. $this->_modifiedFields = array();
  338. $this->_isNew = false;
  339. $this->fireAfterSave();
  340. return true;
  341. } else {
  342. return false;
  343. }
  344. }
  345. /**
  346. * delete current object from db (not destroy this instance)
  347. * @return bool
  348. */
  349. public function delete()
  350. {
  351. if ($this->_isNew === true) {
  352. return false;
  353. }
  354. if ($this->fireBeforeDelete()) {
  355. $pkfield = $this->_f()->getPrimaryKey();
  356. $pk = $this->_get($pkfield->db_column);
  357. $db = $this->_f()->getAdapter();
  358. $tableName = $this->_f()->getDbTableName();
  359. $db->delete($tableName, array("{$pkfield->db_column} = ?" => $pk));
  360. return true;
  361. } else {
  362. return false;
  363. }
  364. }
  365. /**
  366. * beforeSave signal, should cancel action if return false;
  367. * @return bool
  368. */
  369. final public function fireBeforeSave()
  370. {
  371. return true;
  372. if ($this->_beforeSave()) {
  373. return $this->_f()->fireBeforeSave($this);
  374. }
  375. return false;
  376. }
  377. /**
  378. * end-user Model-defined signal
  379. * @return bool
  380. */
  381. protected function _beforeSave()
  382. {
  383. return true;
  384. }
  385. /**
  386. * afterSave signal
  387. * @return void
  388. */
  389. final public function fireAfterSave()
  390. {
  391. return true;
  392. $this->_afterSave();
  393. $this->_f()->fireAfterSave($this);
  394. }
  395. /**
  396. * end-user Model-defined signal
  397. * @return bool
  398. */
  399. protected function _afterSave()
  400. {
  401. return true;
  402. }
  403. /**
  404. * beforeDelete signal, should cancel action if return false;
  405. * @return bool
  406. */
  407. final public function fireBeforeDelete()
  408. {
  409. return true;
  410. if ($this->_beforeDelete()) {
  411. return $this->_f()->fireBeforeDelete($this);
  412. }
  413. return false;
  414. }
  415. /**
  416. * end-user Model-defined signal
  417. * @return bool
  418. */
  419. protected function _beforeDelete()
  420. {
  421. return true;
  422. }
  423. /**
  424. * revert all changes
  425. * @return void
  426. */
  427. public function rollback()
  428. {
  429. $this->_data = $this->_cleanData;
  430. return $this;
  431. }
  432. /**
  433. * if there any changes ?
  434. * @return bool
  435. */
  436. public function isDirty()
  437. {
  438. //return ($this->_data != $this->_cleanData);
  439. return count($this->_modifiedFields) > 0;
  440. }
  441. /**
  442. *
  443. * @return void
  444. */
  445. public function __clone()
  446. {
  447. //$this->_readOnly = false;
  448. }
  449. /**
  450. * if false - method __set is not accessable
  451. * @return bool
  452. */
  453. public function isReadOnly()
  454. {
  455. return $this->_readOnly;
  456. }
  457. /**
  458. * is new ?
  459. * @return bool
  460. */
  461. public function isNew()
  462. {
  463. return $this->_isNew;
  464. }
  465. }