model.php 12 KB

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