PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendors/row/database/Model.php

https://github.com/rudiedirkx/Rudie-on-wheels
PHP | 502 lines | 288 code | 100 blank | 114 comment | 42 complexity | 2906c3e55b202c795355a5774dfddf1c MD5 | raw file
  1. <?php
  2. namespace row\database;
  3. use row\core\Vendors;
  4. //use row\core\Extendable AS ModelParent;
  5. use row\core\Object AS ModelParent;
  6. use row\database\Adapter; // abstract
  7. use row\core\RowException;
  8. use ErrorException;
  9. use row\core\Chain;
  10. class ModelException extends RowException {}
  11. class NotEnoughFoundException extends ModelException {}
  12. class TooManyFoundException extends ModelException {}
  13. abstract class Model extends ModelParent {
  14. static public $chain;
  15. public function __tostring() {
  16. return empty(static::$_title) || !$this->_exists(static::$_title) ? basename(get_class($this)).' model' : $this->{static::$_title};
  17. }
  18. static public $_db_key = '_db';
  19. static public $_db;
  20. /**
  21. *
  22. */
  23. static public function dbObject( Adapter $db = null ) {
  24. $dbk = static::$_db_key;
  25. if ( $db ) {
  26. self::$$dbk = $db;
  27. }
  28. return self::$$dbk;
  29. }
  30. static public $_table = '';
  31. static public $_pk = array();
  32. static public $_title = '';
  33. const GETTER_ONE = 1;
  34. const GETTER_ALL = 2;
  35. const GETTER_FUNCTION = 3;
  36. const GETTER_FIRST = 4;
  37. static public $_getters = array(
  38. // 'author' => array( self::GETTER_ONE, true, 'User', 'author_id', 'user_id' ), // Exactly one ('mandatory') connected User
  39. // 'comments' => array( self::GETTER_ALL, true, 'Comment', 'post_id', 'post_id' ), // >= 0 Comment objects
  40. // 'first_comment' => array( self::GETTER_FIRST, true, 'Comment', 'post_id', 'parent_post_id' ), // First available comment (might not exist)
  41. // 'language' => array( self::GETTER_FIRST, true, 'Language', 'primary_language_id', 'language_id' ), // Optional language (might not be set (eg NULL))
  42. // 'followers' => array( self::GETTER_FUNCTION, true, 'getFollowerUserObjects' ), // Execute and return custom code
  43. );
  44. static public $_on = array();
  45. static public $_cache = array();
  46. /**
  47. * Enables calling of Post::update with defined function _update
  48. */
  49. static public function __callStatic( $method, $args ) {
  50. $calledMethod = $method;
  51. if ( '_' != substr($method, 0, 1) ) {
  52. $method = '_'.$method;
  53. }
  54. if ( !method_exists(get_called_class(), $method) ) {
  55. throw new ErrorException('Call to undefined method '.get_called_class().'::'.$calledMethod.'()');
  56. }
  57. return call_user_func_array(array('static', $method), $args);
  58. } // END __callStatic() */
  59. /**
  60. *
  61. */
  62. public static function _query() {
  63. // return 'SELECT * FROM '.static::$_table.' WHERE '.$conditions; // .' /*'.get_called_class().'*/';
  64. return array(
  65. 'tables' => array(static::$_table),
  66. 'fields' => array('*'),
  67. );
  68. }
  69. /**
  70. *
  71. */
  72. static public function _byQuery( $query, $justFirst = false, $params = array() ) {
  73. // don't require $justFirst, so params can be passed as 2nd argument (in $justFirst)
  74. if ( is_array($justFirst) ) {
  75. $_jf = $justFirst;
  76. // allow reverse arguments: justFirst in $params
  77. $justFirst = is_bool($params) ? $params : false;
  78. $params = $_jf;
  79. }
  80. $class = get_called_class();
  81. if ( Vendors::class_exists($class.'Record') && is_a($class.'Record', get_called_class()) ) {
  82. $class = $class.'Record';
  83. }
  84. return static::dbObject()->fetch($query, array(
  85. 'class' => $class,
  86. 'single' => $justFirst,
  87. 'params' => $params
  88. ));
  89. }
  90. /**
  91. *
  92. */
  93. static public function _fetch( $conditions, $params = array() ) {
  94. // $conditions = static::dbObject()->replaceholders($conditions, $params);
  95. $query = static::_query();
  96. $query['conditions'][] = array($conditions, $params);
  97. return static::_byQuery($query);
  98. }
  99. /**
  100. *
  101. */
  102. static public function _all( $conditions = null, $params = array() ) {
  103. $conditions or $conditions = '1';
  104. return static::_fetch($conditions, $params);
  105. }
  106. /**
  107. * Returns exactly one object with the matching conditions OR throws a model exception
  108. */
  109. static public function _one( $conditions, $params = array() ) {
  110. //var_dump($conditions);
  111. $query = static::_query();
  112. $query['conditions'][] = array($conditions, $params);
  113. $query['limit'] = array(0, 2);
  114. /* experimental */
  115. $cacheKey = md5(serialize($query['conditions']));
  116. if ( false !== static::$_cache ) {
  117. $_c = get_called_class();
  118. if ( isset(static::$_cache[$_c][$cacheKey]) ) {
  119. return static::$_cache[$_c][$cacheKey];
  120. }
  121. }
  122. /* experimental */
  123. $objects = static::_byQuery($query);
  124. if ( !isset($objects[0]) || isset($objects[1]) ) {
  125. $toomany = isset($objects[1]);
  126. $class = $toomany ? 'row\database\TooManyFoundException' : 'row\database\NotEnoughFoundException';
  127. throw new $class('Found '.( $toomany ? '>' : '<' ).' 1 of '.get_called_class().'.');
  128. }
  129. $r = $objects[0];
  130. /* experimental */
  131. if ( false !== static::$_cache ) {
  132. static::$_cache[$_c][$cacheKey] = $r;
  133. }
  134. /* experimental */
  135. return $r;
  136. }
  137. /**
  138. * Returns null or the first object with the matching conditions
  139. */
  140. static public function _first( $conditions, $params = array() ) {
  141. $query = static::_query();
  142. $query['conditions'][] = array($conditions, $params);
  143. $query['limit'] = array(0, 1);
  144. $r = static::_byQuery($query, true);
  145. return $r;
  146. }
  147. /**
  148. *
  149. */
  150. static public function _get( $pkValues ) {
  151. $pkValues = (array)$pkValues;
  152. $model = get_called_class();
  153. $key = $model . ':' . implode(':', $pkValues);
  154. return _cache($key, function() use ($model, $pkValues) {
  155. $pkColumns = (array)$model::$_pk;
  156. if ( count($pkValues) !== count($pkColumns) ) {
  157. throw new NotEnoughFoundException('Invalid number of PK arguments ('.count($pkValues).' instead of '.count($pkColumns).').');
  158. }
  159. $pkValues = array_combine($pkColumns, $pkValues);
  160. $conditions = $model::dbObject()->stringifyConditions($pkValues, 'AND', $model::$_table);
  161. return $model::_one($conditions);
  162. });
  163. }
  164. /**
  165. *
  166. */
  167. static public function _delete( $conditions, $params = array() ) {
  168. $chain = static::event(__FUNCTION__);
  169. $chain->first(function($self, $args, $chain, $native = true) {
  170. // actual methods body //
  171. if ( $self::dbObject()->delete($self::$_table, $args->conditions, $args->params) ) {
  172. return $self::dbObject()->affectedRows();
  173. }
  174. return $self::dbObject()->except();
  175. // actual methods body //
  176. });
  177. return $chain->start(get_called_class(), options(compact('conditions', 'params')));
  178. }
  179. /**
  180. *
  181. */
  182. static public function _update( $values, $conditions, $params = array() ) {
  183. $chain = static::event(__FUNCTION__);
  184. $chain->first(function($self, $args, $chain, $native = true) {
  185. // actual methods body //
  186. if ( $self::dbObject()->update($self::$_table, $args->values, $args->conditions, $args->params) ) {
  187. return $self::dbObject()->affectedRows();
  188. }
  189. return $self::dbObject()->except();
  190. // actual methods body //
  191. });
  192. return $chain->start(get_called_class(), options(compact('values', 'conditions', 'params')));
  193. }
  194. /**
  195. *
  196. */
  197. static public function _insert( $values, $_method = 'insert' ) {
  198. $chain = static::event('_'.$_method);
  199. $chain->first(function($self, $args, $chain, $native = true) use ( $_method ) {
  200. // actual method body //
  201. if ( $self::dbObject()->$_method($self::$_table, $args->values) ) {
  202. return (int)$self::dbObject()->insertId();
  203. }
  204. return $self::dbObject()->except();
  205. // actual method body //
  206. });
  207. return $chain->start(get_called_class(), options(compact('values')));
  208. }
  209. /**
  210. *
  211. */
  212. static public function _replace( $values ) {
  213. return static::_insert($values, 'replace');
  214. }
  215. /**
  216. *
  217. */
  218. static public function _count( $conditions = '', $params = array() ) {
  219. return static::dbObject()->count(static::$_table, $conditions, $params);
  220. }
  221. /**
  222. *
  223. */
  224. public function __construct( $init = false ) {
  225. $chain = static::event('construct');
  226. $chain->first(function($self, $args, $chain) {
  227. // actual method body //
  228. if ( true === $args->init || is_array($args->init) ) {
  229. $self->_fill($args->init);
  230. }
  231. $self->_fire('init');
  232. // actual method body //
  233. // no chain->next
  234. // no return (cos it's __construct)
  235. });
  236. return $chain->start($this, options(compact('init')));
  237. }
  238. /**
  239. *
  240. */
  241. public function _fill( $data ) {
  242. $chain = static::event('fill');
  243. $chain->first(function($self, $args, $chain, $native = true) {
  244. // actual methods body //
  245. if ( is_array($args->data) ) {
  246. foreach ( (array)$args->data AS $k => $v ) {
  247. if ( $k || '0' === (string)$k ) {
  248. $self->$k = $v;
  249. }
  250. }
  251. }
  252. $self->_fire('post_fill');
  253. // actual methods body //
  254. });
  255. return $chain->start($this, options(compact('data')));
  256. }
  257. /**
  258. * Returns an associative array of PK keys + values
  259. */
  260. public function _pkValue( $strict = true ) {
  261. return $this->_values((array)static::$_pk, $strict);
  262. }
  263. /**
  264. * Returns an associative array of keys + values
  265. */
  266. public function _values( Array $columns, $strict = false ) {
  267. $values = array();
  268. foreach ( (array)$columns AS $field ) {
  269. if ( $this->_exists($field) ) {
  270. $values[$field] = $this->$field;
  271. }
  272. else if ( $strict ) {
  273. return false;
  274. }
  275. }
  276. return $values;
  277. }
  278. /**
  279. *
  280. */
  281. protected function __getter( $key ) {
  282. $getter = $this::$_getters[$key];
  283. $type = $getter[0];
  284. $cache = $getter[1];
  285. $class = $function = $getter[2];
  286. switch ( $type ) {
  287. case self::GETTER_ONE:
  288. case self::GETTER_ALL:
  289. case self::GETTER_FIRST:
  290. $localColumns = (array)$getter[3];
  291. $localValues = $this->_values($localColumns);
  292. $foreignTable = $class::$_table;
  293. $foreignColumns = isset($getter[4]) ? (array)$getter[4] : $localColumns;
  294. $conditions = array_combine($foreignColumns, $localValues);
  295. $conditions = static::dbObject()->stringifyConditions($conditions, 'AND', $foreignTable);
  296. $retrievalMethods = array(
  297. self::GETTER_ONE => '_one',
  298. self::GETTER_ALL => '_all',
  299. self::GETTER_FIRST => '_first',
  300. );
  301. $retrievalMethod = $retrievalMethods[$type];
  302. /* experimental */
  303. $_name = '_parent';
  304. if ( isset($getter[4]) ) {
  305. $cc = get_called_class();
  306. foreach ( $class::$_getters AS $name => $gt ) {
  307. if ( isset($gt[4]) ) {
  308. if ( in_array($gt[0], array(self::GETTER_ONE, self::GETTER_FIRST)) ) {
  309. if ( $gt[2] == $cc && $gt[3] == $getter[4] && $gt[4] == $getter[3] ) {
  310. $_name = $name;
  311. break;
  312. }
  313. }
  314. }
  315. }
  316. }
  317. //var_dump($class, $cc.'->'.$key, $_name);
  318. $_parent = $this;
  319. $evName = 'tmpEvent'.rand(0, 999);
  320. $_chain = $class::event('construct');
  321. //echo "\n[ ".$evName." ".count($_chain->events)." events PRE ]\n";
  322. $_chain->add(function( $self, $args, $chain, $semiNative = true ) use ($_parent, $_name, $class) {
  323. $r = $chain($self, $args);
  324. $self->$_name = $_parent;
  325. return $r;
  326. }, $evName);
  327. //echo "\n[ ".$evName." ".count($_chain->events)." events +1 ]\n";
  328. /* experimental */
  329. // Get object(s)
  330. $r = call_user_func(array($class, $retrievalMethod), $conditions);
  331. /* experimental */
  332. //echo "\n[ ".$evName." ".count($_chain->events)." events SAME AS +1 ]\n";
  333. $_chain->remove($evName);
  334. //echo "\n[ ".$evName." ".count($_chain->events)." events POST: -1 ]\n";
  335. /* experimental */
  336. if ( $cache ) {
  337. $this->$key = $r;
  338. }
  339. return $r;
  340. break;
  341. case self::GETTER_FUNCTION:
  342. $r = $this->$function();
  343. if ( $cache ) {
  344. $this->$key = $r;
  345. }
  346. return $r;
  347. break;
  348. }
  349. // if you're here, you cheat
  350. }
  351. /**
  352. *
  353. */
  354. public function __get( $key ) {
  355. if ( isset($this::$_getters[$key]) ) {
  356. return $this->__getter($key);
  357. }
  358. else if ( $this->_exists($key) ) {
  359. return $this->$key;
  360. }
  361. }
  362. /**
  363. *
  364. */
  365. public function update( $values ) {
  366. return $this->_chain(__FUNCTION__, function($self, $args, $chain, $native = true) {
  367. // actual method body //
  368. if ( is_array($args->values) ) {
  369. $self->_fill((array)$args->values);
  370. }
  371. $conditions = $self->_pkValue(true);
  372. return $self::_update($args->values, $conditions);
  373. // actual method body //
  374. }, compact('values'));
  375. }
  376. /**
  377. *
  378. */
  379. public function delete() {
  380. return $this->_chain(__FUNCTION__, function($self, $args, $chain, $native = true) {
  381. // actual method body //
  382. return $self::_delete($self->_pkValue(true));
  383. // actual method body //
  384. });
  385. }
  386. /**
  387. *
  388. */
  389. public function isEmpty() {
  390. return (array)$this == array();
  391. }
  392. /**
  393. *
  394. */
  395. public function toArray() {
  396. $arr = (array)$this;
  397. foreach ( static::$_getters AS $k => $x ) {
  398. unset($arr[$x]);
  399. }
  400. return $arr;
  401. }
  402. } // END Class Model