model.php 13 KB

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