eloquent.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <?php namespace System\DB;
  2. use System\Str;
  3. use System\Config;
  4. use System\Inflector;
  5. abstract class Eloquent {
  6. /**
  7. * Indicates if the model exists in the database.
  8. *
  9. * @var bool
  10. */
  11. public $exists = false;
  12. /**
  13. * The model's attributes.
  14. *
  15. * Typically, a model has an attribute for each column on the table.
  16. *
  17. * @var array
  18. */
  19. public $attributes = array();
  20. /**
  21. * The model's dirty attributes.
  22. *
  23. * @var array
  24. */
  25. public $dirty = array();
  26. /**
  27. * The model's ignored attributes.
  28. *
  29. * Ignored attributes will not be saved to the database, and are
  30. * primarily used to hold relationships.
  31. *
  32. * @var array
  33. */
  34. public $ignore = array();
  35. /**
  36. * The relationships that should be eagerly loaded.
  37. *
  38. * @var array
  39. */
  40. public $includes = array();
  41. /**
  42. * The relationship type the model is currently resolving.
  43. *
  44. * @var string
  45. */
  46. public $relating;
  47. /**
  48. * The foreign key of the "relating" relationship.
  49. *
  50. * @var string
  51. */
  52. public $relating_key;
  53. /**
  54. * The table name of the model being resolved.
  55. *
  56. * This is used during many-to-many eager loading.
  57. *
  58. * @var string
  59. */
  60. public $relating_table;
  61. /**
  62. * The model query instance.
  63. *
  64. * @var Query
  65. */
  66. public $query;
  67. /**
  68. * Create a new Eloquent model instance.
  69. *
  70. * @param array $attributes
  71. * @return void
  72. */
  73. public function __construct($attributes = array())
  74. {
  75. foreach ($attributes as $key => $value)
  76. {
  77. $this->$key = $value;
  78. }
  79. }
  80. /**
  81. * Get the table name for a model.
  82. *
  83. * @param string $class
  84. * @return string
  85. */
  86. public static function table($class)
  87. {
  88. if (property_exists($class, 'table'))
  89. {
  90. return $class::$table;
  91. }
  92. return strtolower(Inflector::plural($class));
  93. }
  94. /**
  95. * Factory for creating new Eloquent model instances.
  96. *
  97. * @param string $class
  98. * @return object
  99. */
  100. public static function make($class)
  101. {
  102. $model = new $class;
  103. // -----------------------------------------------------
  104. // Since this method is only used for instantiating
  105. // models for querying purposes, we will go ahead and
  106. // set the Query instance on the model.
  107. // -----------------------------------------------------
  108. $model->query = Query::table(static::table($class));
  109. return $model;
  110. }
  111. /**
  112. * Create a new model instance and set the relationships
  113. * that should be eagerly loaded.
  114. *
  115. * @return mixed
  116. */
  117. public static function with()
  118. {
  119. $model = static::make(get_called_class());
  120. $model->includes = func_get_args();
  121. return $model;
  122. }
  123. /**
  124. * Get a model by the primary key.
  125. *
  126. * @param int $id
  127. * @return mixed
  128. */
  129. public static function find($id)
  130. {
  131. return static::make(get_called_class())->where('id', '=', $id)->first();
  132. }
  133. /**
  134. * Get an array of models from the database.
  135. *
  136. * @return array
  137. */
  138. private function _get()
  139. {
  140. return Eloquent\Hydrator::hydrate($this);
  141. }
  142. /**
  143. * Get the first model result
  144. *
  145. * @return mixed
  146. */
  147. private function _first()
  148. {
  149. return (count($results = Eloquent\Hydrator::hydrate($this->take(1))) > 0) ? reset($results) : null;
  150. }
  151. /**
  152. * Retrieve the query for a 1:1 relationship.
  153. *
  154. * @param string $model
  155. * @param string $foreign_key
  156. * @return mixed
  157. */
  158. public function has_one($model, $foreign_key = null)
  159. {
  160. $this->relating = __FUNCTION__;
  161. return $this->has_one_or_many($model, $foreign_key);
  162. }
  163. /**
  164. * Retrieve the query for a 1:* relationship.
  165. *
  166. * @param string $model
  167. * @param string $foreign_key
  168. * @return mixed
  169. */
  170. public function has_many($model, $foreign_key = null)
  171. {
  172. $this->relating = __FUNCTION__;
  173. return $this->has_one_or_many($model, $foreign_key);
  174. }
  175. /**
  176. * Retrieve the query for a 1:1 or 1:* relationship.
  177. *
  178. * @param string $model
  179. * @param string $foreign_key
  180. * @return mixed
  181. */
  182. private function has_one_or_many($model, $foreign_key)
  183. {
  184. // -----------------------------------------------------
  185. // The default foreign key for has one and has many
  186. // relationships is the name of the model with an
  187. // appended _id.
  188. //
  189. // For example, the foreign key for a User model would
  190. // be user_id. Photo would be photo_id, etc.
  191. // -----------------------------------------------------
  192. $this->relating_key = (is_null($foreign_key)) ? strtolower(get_class($this)).'_id' : $foreign_key;
  193. return static::make($model)->where($this->relating_key, '=', $this->id);
  194. }
  195. /**
  196. * Retrieve the query for a 1:1 belonging relationship.
  197. *
  198. * @param string $model
  199. * @param string $foreign_key
  200. * @return mixed
  201. */
  202. public function belongs_to($model, $foreign_key = null)
  203. {
  204. $this->relating = __FUNCTION__;
  205. if ( ! is_null($foreign_key))
  206. {
  207. $this->relating_key = $foreign_key;
  208. }
  209. else
  210. {
  211. // -----------------------------------------------------
  212. // The default foreign key for belonging relationships
  213. // is the name of the relationship method name with _id.
  214. //
  215. // So, if a model has a "manager" method returning a
  216. // belongs_to relationship, the key would be manager_id.
  217. // -----------------------------------------------------
  218. list(, $caller) = debug_backtrace(false);
  219. $this->relating_key = $caller['function'].'_id';
  220. }
  221. return static::make($model)->where('id', '=', $this->attributes[$this->relating_key]);
  222. }
  223. /**
  224. * Retrieve the query for a *:* relationship.
  225. *
  226. * @param string $model
  227. * @param string $table
  228. * @return mixed
  229. */
  230. public function has_and_belongs_to_many($model, $table = null)
  231. {
  232. $this->relating = __FUNCTION__;
  233. if ( ! is_null($table))
  234. {
  235. $this->relating_table = $table;
  236. }
  237. else
  238. {
  239. // -----------------------------------------------------
  240. // By default, the intermediate table name is the plural
  241. // names of the models arranged alphabetically and
  242. // concatenated with an underscore.
  243. // -----------------------------------------------------
  244. $models = array(Inflector::plural($model), Inflector::plural(get_class($this)));
  245. sort($models);
  246. $this->relating_table = strtolower($models[0].'_'.$models[1]);
  247. }
  248. // -----------------------------------------------------
  249. // The default foreign key for many-to-many relations
  250. // is the name of the model with an appended _id.
  251. // appended _id.
  252. //
  253. // This is the same convention as has_one and has_many.
  254. // -----------------------------------------------------
  255. $this->relating_key = strtolower(get_class($this)).'_id';
  256. return static::make($model)
  257. ->select(static::table($model).'.*')
  258. ->join($this->relating_table, static::table($model).'.id', '=', $this->relating_table.'.'.strtolower($model).'_id')
  259. ->where($this->relating_table.'.'.$this->relating_key, '=', $this->id);
  260. }
  261. /**
  262. * Save the model to the database.
  263. *
  264. * @return bool
  265. */
  266. public function save()
  267. {
  268. // -----------------------------------------------------
  269. // If the model doesn't have any dirty attributes, there
  270. // is no need to save it to the database.
  271. // -----------------------------------------------------
  272. if ($this->exists and count($this->dirty) == 0)
  273. {
  274. return true;
  275. }
  276. $model = get_class($this);
  277. // -----------------------------------------------------
  278. // Since the model was instantiated using "new", a query
  279. // instance has not been set. We'll do it now.
  280. // -----------------------------------------------------
  281. $this->query = Query::table(static::table($model));
  282. // -----------------------------------------------------
  283. // Set the creation and update timestamps.
  284. // -----------------------------------------------------
  285. if (property_exists($model, 'timestamps') and $model::$timestamps)
  286. {
  287. $this->updated_at = date('Y-m-d H:i:s');
  288. if ( ! $this->exists)
  289. {
  290. $this->created_at = $this->updated_at;
  291. }
  292. }
  293. // -----------------------------------------------------
  294. // If the model already exists in the database, we only
  295. // need to update it. Otherwise, we'll insert it.
  296. // -----------------------------------------------------
  297. if ($this->exists)
  298. {
  299. $result = $this->query->where('id', '=', $this->attributes['id'])->update($this->dirty) == 1;
  300. }
  301. else
  302. {
  303. $this->attributes['id'] = $this->query->insert_get_id($this->attributes);
  304. $result = $this->exists = is_numeric($this->id);
  305. }
  306. $this->dirty = array();
  307. return $result;
  308. }
  309. /**
  310. * Delete a model from the database.
  311. *
  312. * @param int $id
  313. * @return int
  314. */
  315. public function delete($id = null)
  316. {
  317. // -----------------------------------------------------
  318. // If the method is being called from an existing model,
  319. // only delete that model from the database.
  320. // -----------------------------------------------------
  321. if ($this->exists)
  322. {
  323. return Query::table(static::table(get_class($this)))->delete($this->id);
  324. }
  325. return $this->query->delete($id);
  326. }
  327. /**
  328. * Magic method for retrieving model attributes.
  329. */
  330. public function __get($key)
  331. {
  332. // -----------------------------------------------------
  333. // Check the ignored attributes first. These attributes
  334. // hold all of the loaded relationships.
  335. // -----------------------------------------------------
  336. if (array_key_exists($key, $this->ignore))
  337. {
  338. return $this->ignore[$key];
  339. }
  340. // -----------------------------------------------------
  341. // Is the attribute actually a relationship method?
  342. // -----------------------------------------------------
  343. if (method_exists($this, $key))
  344. {
  345. $model = $this->$key();
  346. return ($this->relating == 'has_one' or $this->relating == 'belongs_to')
  347. ? $this->ignore[$key] = $model->first()
  348. : $this->ignore[$key] = $model->get();
  349. }
  350. return (array_key_exists($key, $this->attributes)) ? $this->attributes[$key] : null;
  351. }
  352. /**
  353. * Magic Method for setting model attributes.
  354. */
  355. public function __set($key, $value)
  356. {
  357. // -----------------------------------------------------
  358. // If the key is a relationship, add it to the ignored.
  359. // Otherwise, we can simply add it as an attribute.
  360. // -----------------------------------------------------
  361. if (method_exists($this, $key))
  362. {
  363. $this->ignore[$key] = $value;
  364. }
  365. else
  366. {
  367. $this->attributes[$key] = $value;
  368. $this->dirty[$key] = $value;
  369. }
  370. }
  371. /**
  372. * Magic Method for determining if a model attribute is set.
  373. */
  374. public function __isset($key)
  375. {
  376. return (array_key_exists($key, $this->attributes) or array_key_exists($key, $this->ignore));
  377. }
  378. /**
  379. * Magic Method for unsetting model attributes.
  380. */
  381. public function __unset($key)
  382. {
  383. unset($this->attributes[$key]);
  384. unset($this->ignore[$key]);
  385. unset($this->dirty[$key]);
  386. }
  387. /**
  388. * Magic Method for handling dynamic method calls.
  389. */
  390. public function __call($method, $parameters)
  391. {
  392. if ($method == 'get')
  393. {
  394. return $this->_get();
  395. }
  396. if ($method == 'first')
  397. {
  398. return $this->_first();
  399. }
  400. // -----------------------------------------------------
  401. // Pass aggregate methods to the query instance.
  402. // -----------------------------------------------------
  403. if (in_array($method, array('count', 'sum', 'min', 'max', 'avg')))
  404. {
  405. return call_user_func_array(array($this->query, $method), $parameters);
  406. }
  407. // -----------------------------------------------------
  408. // Pass the method to the query instance. This allows
  409. // the chaining of methods from the query builder.
  410. // -----------------------------------------------------
  411. call_user_func_array(array($this->query, $method), $parameters);
  412. return $this;
  413. }
  414. /**
  415. * Magic Method for handling dynamic static method calls.
  416. */
  417. public static function __callStatic($method, $parameters)
  418. {
  419. $model = static::make(get_called_class());
  420. if ($method == 'get' or $method == 'all')
  421. {
  422. return $model->_get();
  423. }
  424. if ($method == 'first')
  425. {
  426. return $model->_first();
  427. }
  428. // -----------------------------------------------------
  429. // Pass aggregate methods to the query instance.
  430. // -----------------------------------------------------
  431. if (in_array($method, array('count', 'sum', 'min', 'max', 'avg')))
  432. {
  433. return call_user_func_array(array($model->query, $method), $parameters);
  434. }
  435. // -----------------------------------------------------
  436. // Pass the method to the query instance. This allows
  437. // the chaining of methods from the query builder.
  438. // -----------------------------------------------------
  439. call_user_func_array(array($model->query, $method), $parameters);
  440. return $model;
  441. }
  442. }