eloquent.php 12 KB

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