PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/system/libraries/ORM.php

https://github.com/lmorchard/friendfeedarchiver
PHP | 878 lines | 517 code | 103 blank | 258 comment | 47 complexity | d8bca772ad034c3505b1127a23558e42 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Object Relational Mapping (ORM) is a method of abstracting database
  4. * access to standard PHP calls. All table rows are represented as a model.
  5. *
  6. * @see http://en.wikipedia.org/wiki/Active_record
  7. * @see http://en.wikipedia.org/wiki/Object-relational_mapping
  8. *
  9. * $Id: ORM.php 2260 2008-03-10 04:54:57Z Shadowhand $
  10. *
  11. * @package Core
  12. * @author Kohana Team
  13. * @copyright (c) 2007-2008 Kohana Team
  14. * @license http://kohanaphp.com/license.html
  15. */
  16. class ORM_Core {
  17. // Database field caching
  18. protected static $fields = array();
  19. // Database instance
  20. protected static $db;
  21. // Automatic saving on model destruction
  22. protected $auto_save = FALSE;
  23. // This table
  24. protected $class;
  25. protected $table;
  26. // SQL building status
  27. protected $select = FALSE;
  28. protected $where = FALSE;
  29. // Currently loaded object
  30. protected $object;
  31. // Changed object keys
  32. protected $changed = array();
  33. // Object Relationships
  34. protected $has_one = array();
  35. protected $has_many = array();
  36. protected $belongs_to = array();
  37. protected $belongs_to_many = array();
  38. protected $has_and_belongs_to_many = array();
  39. /**
  40. * Factory method. Creates an instance of an ORM model and returns it.
  41. *
  42. * @param string model name
  43. * @param mixed id to load
  44. * @return object
  45. */
  46. public static function factory($model = FALSE, $id = FALSE)
  47. {
  48. $model = empty($model) ? __CLASS__ : ucfirst($model).'_Model';
  49. return new $model($id);
  50. }
  51. /**
  52. * Initialize database, setup internal variables, find requested object.
  53. *
  54. * @return void
  55. */
  56. public function __construct($id = FALSE)
  57. {
  58. // Fetch table name
  59. empty($this->class) and $this->class = strtolower(substr(get_class($this), 0, -6));
  60. empty($this->table) and $this->table = inflector::plural($this->class);
  61. // Connect to the database
  62. $this->connect();
  63. if (is_object($id))
  64. {
  65. // Preloaded object
  66. $this->object = $id;
  67. }
  68. else
  69. {
  70. if (empty($id))
  71. {
  72. // Load an empty object
  73. $this->clear();
  74. }
  75. else
  76. {
  77. // Query and load object
  78. $this->find($id);
  79. }
  80. }
  81. }
  82. /**
  83. * Enables automatic saving of the object when the model is destroyed.
  84. *
  85. * @return void
  86. */
  87. public function __destruct()
  88. {
  89. if ($this->auto_save == TRUE)
  90. {
  91. // Automatically save the model
  92. $this->save();
  93. }
  94. }
  95. /**
  96. * Reloads the database when the object is unserialized.
  97. *
  98. * @return void
  99. */
  100. public function __wakeup()
  101. {
  102. // Connect to the database
  103. $this->connect();
  104. // Reload the internal object
  105. empty($this->object->id) or $this->find($this->object->id);
  106. }
  107. /**
  108. * Magic method for getting object and model keys.
  109. *
  110. * @param string key name
  111. * @return mixed
  112. */
  113. public function __get($key)
  114. {
  115. if (isset($this->object->$key))
  116. {
  117. return $this->object->$key;
  118. }
  119. elseif ( ! empty($this->object->id) AND (in_array($key, $this->has_one) OR in_array($key, $this->belongs_to)))
  120. {
  121. // Set the model name
  122. $model = ucfirst($key).'_Model';
  123. // Set the child id name
  124. $child_id = $key.'_id';
  125. $this->object->$key = new $model
  126. (
  127. isset($this->object->$child_id)
  128. // Get the foreign object using the key defined in this object
  129. ? $this->object->$child_id
  130. // Get the foreign object using the primary key of this object
  131. : array($this->class.'_id' => $this->object->id)
  132. );
  133. // Return the model
  134. return $this->object->$key;
  135. }
  136. else
  137. {
  138. switch($key)
  139. {
  140. case 'table_name':
  141. return $this->table;
  142. break;
  143. case 'class_name':
  144. return $this->class;
  145. break;
  146. case 'auto_save':
  147. return $this->auto_save;
  148. break;
  149. }
  150. }
  151. }
  152. /**
  153. * Magic method for setting object and model keys.
  154. *
  155. * @param string key name
  156. * @param mixed value to set
  157. * @return void
  158. */
  159. public function __set($key, $value)
  160. {
  161. if ($key != 'id' AND isset(self::$fields[$this->table][$key]))
  162. {
  163. if ($this->object->$key != $value)
  164. {
  165. // Set new value
  166. $this->object->$key = $value;
  167. // Data has changed
  168. $this->changed[$key] = $key;
  169. }
  170. }
  171. else
  172. {
  173. switch($key)
  174. {
  175. case 'auto_save':
  176. $this->auto_save = (bool) $value;
  177. break;
  178. }
  179. }
  180. }
  181. /**
  182. * Magic method for calling ORM methods. This handles:
  183. * - as_array
  184. * - find_by_*
  185. * - find_all_by_*
  186. * - find_related_*
  187. * - has_*
  188. * - add_*
  189. * - remove_*
  190. */
  191. public function __call($method, $args)
  192. {
  193. if ($method === 'as_array')
  194. {
  195. // Return all of the object data as an array
  196. return (array) $this->object;
  197. }
  198. if (substr($method, 0, 8) === 'find_by_' OR ($all = substr($method, 0, 12)) === 'find_all_by_')
  199. {
  200. $method = isset($all) ? substr($method, 12) : substr($method, 8);
  201. // WHERE is manually set
  202. $this->where = TRUE;
  203. // split method name into $keys array by "_and_" or "_or_"
  204. if (is_array($keys = $this->find_keys($method)))
  205. {
  206. if (strpos($method, '_or_') === FALSE)
  207. {
  208. // Use AND WHERE
  209. self::$db->where(array_combine($keys, $args));
  210. }
  211. else
  212. {
  213. if (count($args) === 1)
  214. {
  215. $val = current($args);
  216. foreach($keys as $key)
  217. {
  218. // Use OR WHERE, with a single value
  219. self::$db->orwhere(array($key => $val));
  220. }
  221. }
  222. else
  223. {
  224. // Use OR WHERE, with multiple values
  225. self::$db->orwhere(array_combine($keys, $args));
  226. }
  227. }
  228. }
  229. else
  230. {
  231. // Set WHERE
  232. self::$db->where(array($keys => current($args)));
  233. }
  234. if (isset($all))
  235. {
  236. // Array of results
  237. return $this->load_result(TRUE);
  238. }
  239. else
  240. {
  241. // Allow chains
  242. return $this->find();
  243. }
  244. }
  245. if (substr($method, 0, 13) === 'find_related_')
  246. {
  247. // Get table name
  248. $table = substr($method, 13);
  249. // Construct a new model
  250. $model = $this->load_model($table);
  251. // Remote reference to this object
  252. $remote = array($this->class.'_id' => $this->object->id);
  253. if (in_array($table, $this->has_one))
  254. {
  255. // Find one<>one relationships
  256. return $model->where($remote)->find();
  257. }
  258. elseif (in_array($table, $this->has_many))
  259. {
  260. // Find one<>many relationships
  261. $model->where($remote);
  262. }
  263. elseif (in_array($table, $this->has_and_belongs_to_many))
  264. {
  265. // Find many<>many relationships, via a JOIN
  266. $this->related_join($table);
  267. }
  268. else
  269. {
  270. // This table does not have ownership
  271. return FALSE;
  272. }
  273. return $model->load_result(TRUE);
  274. }
  275. if (preg_match('/^(has|add|remove)_(.+)/', $method, $matches))
  276. {
  277. $action = $matches[1];
  278. $model = is_object(current($args)) ? current($args) : $this->load_model($matches[2]);
  279. // Real foreign table name
  280. $table = $model->table_name;
  281. // Sanity check, make sure that this object has ownership
  282. if (in_array($matches[2], $this->has_one))
  283. {
  284. $ownership = 1;
  285. }
  286. elseif (in_array($table, $this->has_many))
  287. {
  288. $ownership = 2;
  289. }
  290. elseif (in_array($table, $this->has_and_belongs_to_many))
  291. {
  292. $ownership = 3;
  293. }
  294. else
  295. {
  296. // Model does not have ownership, abort now
  297. return FALSE;
  298. }
  299. // Primary key related to this object
  300. $primary = $this->class.'_id';
  301. // Related foreign key
  302. $foreign = $model->class_name.'_id';
  303. if ( ! is_object(current($args)))
  304. {
  305. if ($action === 'add' AND is_array(current($args)))
  306. {
  307. foreach(current($args) as $key => $val)
  308. {
  309. // Fill object with data from array
  310. $model->$key = $val;
  311. }
  312. }
  313. else
  314. {
  315. if ($ownership === 1 OR $ownership === 2)
  316. {
  317. // Make sure the related key matches this object id
  318. self::$db->where($primary, $this->object->id);
  319. }
  320. // Load the related object
  321. $model->find(current($args));
  322. }
  323. }
  324. if ($ownership === 3)
  325. {
  326. // The many<>many relationship, via a joining table
  327. $relationship = array
  328. (
  329. $primary => $this->object->id,
  330. $foreign => $model->id
  331. );
  332. }
  333. switch($action)
  334. {
  335. case 'add':
  336. if (isset($relationship))
  337. {
  338. // Insert for many<>many relationship
  339. self::$db->insert($this->related_table($table), $relationship);
  340. }
  341. else
  342. {
  343. // Set the related key to this object id
  344. $model->$primary = $this->object->id;
  345. }
  346. return $model->save();
  347. break;
  348. case 'has':
  349. if (isset($relationship))
  350. {
  351. // Find the many<>many relationship
  352. return (bool) count
  353. (
  354. self::$db
  355. ->select($primary)
  356. ->from($this->related_table($table))
  357. ->where($relationship)
  358. ->limit(1)
  359. ->get()
  360. );
  361. }
  362. return ($model->$primary === $this->object->id);
  363. break;
  364. case 'remove':
  365. if (isset($relationship))
  366. {
  367. // Attempt to delete the many<>many relationship
  368. return (bool) count(self::$db->delete($this->related_table($table), $relationship));
  369. }
  370. elseif ($model->$primary === $this->object->id)
  371. {
  372. // Delete the related object
  373. return $model->delete();
  374. }
  375. else
  376. {
  377. // Massive failure
  378. return FALSE;
  379. }
  380. break;
  381. }
  382. // This should never be executed
  383. return FALSE;
  384. }
  385. if (method_exists(self::$db, $method))
  386. {
  387. // Do not allow query methods
  388. if (preg_match('/query|get|list_fields|field_data/', $method))
  389. return $this;
  390. if ($method === 'select')
  391. {
  392. $this->select = TRUE;
  393. }
  394. elseif (preg_match('/where|like|regex/', $method))
  395. {
  396. $this->where = TRUE;
  397. }
  398. // Pass through to Database, manually calling up to 2 args, for speed.
  399. switch(count($args))
  400. {
  401. case 1:
  402. self::$db->$method(current($args));
  403. break;
  404. case 2:
  405. self::$db->$method(current($args), next($args));
  406. break;
  407. default:
  408. call_user_func_array(array(self::$db, $method), $args);
  409. break;
  410. }
  411. return $this;
  412. }
  413. }
  414. /**
  415. * Finds the key for a WHERE statement. Usually this should be overloaded
  416. * in the model, if you want to do: new Foo_Model('name') or similar.
  417. *
  418. * @return string name of key for the id
  419. */
  420. protected function where_key($id = NULL)
  421. {
  422. return 'id';
  423. }
  424. /**
  425. * Find and load data for the current object.
  426. *
  427. * @param string id of the object to find, or ALL
  428. * @param boolean return the result, or load it into the current object
  429. * @return object object instance
  430. * @return array if ALL is used
  431. */
  432. public function find($id = FALSE, $return = FALSE)
  433. {
  434. // Allows the use of find(ALL)
  435. if ($id === ALL)
  436. return $this->find_all();
  437. // Generate WHERE
  438. if ($this->where === FALSE AND ! empty($id))
  439. {
  440. if (is_array($id))
  441. {
  442. self::$db->where($id);
  443. }
  444. else
  445. {
  446. self::$db->where($this->where_key($id), $id);
  447. }
  448. }
  449. // Only one result will be returned
  450. self::$db->limit(1);
  451. // Load the result of the query
  452. return $this->load_result(FALSE, $return);
  453. }
  454. /**
  455. * Find and load an array of objects.
  456. *
  457. * @return array all objects in a simple array
  458. */
  459. public function find_all()
  460. {
  461. // Return an array of objects
  462. return $this->load_result(TRUE, TRUE);
  463. }
  464. /**
  465. * Saves the current object.
  466. *
  467. * @return bool
  468. */
  469. public function save()
  470. {
  471. // No data was changed
  472. if (empty($this->changed))
  473. return TRUE;
  474. $data = array();
  475. foreach($this->changed as $key)
  476. {
  477. // Get changed data
  478. $data[$key] = $this->object->$key;
  479. }
  480. if ($this->object->id == '')
  481. {
  482. // Perform an insert
  483. $query = self::$db->insert($this->table, $data);
  484. if (count($query) === 1)
  485. {
  486. // Set current object id by the insert id
  487. $this->object->id = $query->insert_id();
  488. }
  489. }
  490. else
  491. {
  492. // Perform an update
  493. $query = self::$db->update($this->table, $data, array('id' => $this->object->id));
  494. }
  495. if (count($query) === 1)
  496. {
  497. // Reset changed data
  498. $this->changed = array();
  499. return TRUE;
  500. }
  501. return FALSE;
  502. }
  503. /**
  504. * Deletes this object, or all objects in this table.
  505. *
  506. * @param int use ALL to delete all rows in the table
  507. * @return bool FALSE if the object cannot be deleted
  508. * @return int number of rows deleted
  509. */
  510. public function delete($all = FALSE)
  511. {
  512. if ($all === ALL)
  513. {
  514. // WHERE for ALL: "WHERE 1" (h4x)
  515. $where = ($this->where === TRUE) ? NULL : TRUE;
  516. }
  517. else
  518. {
  519. // Can't delete something that does not exist
  520. if (empty($this->object->id))
  521. return FALSE;
  522. if ( ! empty($this->has_and_belongs_to_many))
  523. {
  524. // Foreign WHERE for this object
  525. $where = array($this->class.'_id' => $this->object->id);
  526. foreach($this->has_and_belongs_to_many as $table)
  527. {
  528. // Delete all many<>many relationships for this object
  529. self::$db->delete($this->table_name.'_'.$table, $where);
  530. }
  531. }
  532. // WHERE for this object
  533. $where = array('id' => $this->object->id);
  534. }
  535. // Clear this object
  536. $this->clear();
  537. // Return the number of rows deleted
  538. return count(self::$db->delete($this->table, $where));
  539. }
  540. /**
  541. * Delete all rows in the table.
  542. *
  543. * @return int number of rows deleted
  544. */
  545. public function delete_all()
  546. {
  547. // Proxy to delete(ALL)
  548. return $this->delete(ALL);
  549. }
  550. /**
  551. * Clears the current object by creating an empty object and assigning empty
  552. * values to each of the object fields. At the same time, the WHERE and
  553. * SELECT statements are cleared and the changed keys are reset.
  554. *
  555. * @return void
  556. */
  557. public function clear()
  558. {
  559. // Create an empty object
  560. $this->object = new StdClass();
  561. // Empty the object
  562. foreach(self::$fields[$this->table] as $field => $data)
  563. {
  564. $this->object->$field = '';
  565. }
  566. // Reset object status
  567. $this->changed = array();
  568. $this->select = FALSE;
  569. $this->where = FALSE;
  570. }
  571. /**
  572. * Helper for __call, breaks a string into WHERE keys.
  573. */
  574. protected function find_keys($keys)
  575. {
  576. if (strpos($keys, '_or_'))
  577. {
  578. $keys = explode('_or_', $keys);
  579. }
  580. elseif (strpos($keys, '_and_'))
  581. {
  582. $keys = explode('_and_', $keys);
  583. }
  584. return $keys;
  585. }
  586. /**
  587. * Loads a database object result.
  588. *
  589. * @param boolean force the return to be an array
  590. * @param boolean $return
  591. * @return boolean|array TRUE for single result, FALSE for an empty result, or array of rows
  592. */
  593. protected function load_result($array = FALSE, $return = FALSE)
  594. {
  595. // Make sure there is something to select
  596. $this->select or self::$db->select($this->table.'.*');
  597. // Fetch the query result
  598. $result = self::$db->get($this->table)->result(TRUE);
  599. if ($array === TRUE)
  600. {
  601. // Create a new ORM iterator of the result
  602. return new ORM_Iterator(get_class($this), $result);
  603. }
  604. else
  605. {
  606. if ($return === TRUE)
  607. {
  608. // Return the first result
  609. return ORM::factory($this->class, $result->current());
  610. }
  611. // Load the first result, if there is only one result
  612. if ($result->count() === 1)
  613. {
  614. $this->object = $result->current();
  615. }
  616. else
  617. {
  618. $this->clear();
  619. }
  620. }
  621. // Clear the changed keys, a new object has been loaded
  622. $this->changed = array();
  623. $this->select = FALSE;
  624. $this->where = FALSE;
  625. // Return this object
  626. return $this;
  627. }
  628. /**
  629. * Creates a model from a table name.
  630. *
  631. * @param string table name
  632. * @return object ORM instance
  633. */
  634. protected function load_model($table)
  635. {
  636. // Create and return the object
  637. return ORM::factory(inflector::singular($table));
  638. }
  639. /**
  640. * Loads the database if it is not already loaded. Used during initialization
  641. * and unserialization.
  642. *
  643. * @return void
  644. */
  645. protected function connect()
  646. {
  647. if (self::$db === NULL)
  648. {
  649. // Load database, if not already loaded
  650. isset(Kohana::instance()->db) or Kohana::instance()->load->database();
  651. // Insert db into this object
  652. self::$db = Kohana::instance()->db;
  653. // Define ALL
  654. defined('ALL') or define('ALL', -1);
  655. }
  656. if (empty(self::$fields[$this->table]))
  657. {
  658. foreach(self::$db->list_fields($this->table) as $field => $data)
  659. {
  660. // Cache the column names
  661. self::$fields[$this->table][$field] = $data;
  662. }
  663. }
  664. }
  665. /**
  666. * Finds the many<>many relationship table.
  667. *
  668. * @param string table name
  669. * @return string
  670. */
  671. protected function related_table($table)
  672. {
  673. if (in_array($table, $this->has_and_belongs_to_many))
  674. {
  675. return $this->table.'_'.$table;
  676. }
  677. elseif (in_array($table, $this->belongs_to_many))
  678. {
  679. return $table.'_'.$this->table;
  680. }
  681. else
  682. {
  683. return $table;
  684. }
  685. }
  686. /**
  687. * Execute a join to a table.
  688. *
  689. * @param string table name
  690. * @return void
  691. */
  692. protected function related_join($table)
  693. {
  694. $join = $this->related_table($table);
  695. // Primary and foreign keys
  696. $primary = $this->class.'_id';
  697. $foreign = inflector::singular($table).'_id';
  698. // Where has been set
  699. $this->where = TRUE;
  700. // Execute the join
  701. self::$db->where("$join.$primary", $this->object->id)->join($join, "$join.$foreign", "$table.id");
  702. }
  703. } // End ORM
  704. /**
  705. * ORM iterator.
  706. */
  707. class ORM_Iterator implements Iterator, Countable {
  708. // ORM class name
  709. protected $class;
  710. // Database result object
  711. protected $result;
  712. public function __construct($class, $result)
  713. {
  714. $this->class = $class;
  715. $this->result = $result;
  716. }
  717. /**
  718. * Returns an array of all the
  719. */
  720. public function as_array()
  721. {
  722. // Import class name
  723. $class = $this->class;
  724. $array = array();
  725. foreach ($this->result->result_array(TRUE) as $obj)
  726. {
  727. $array[] = new $class($obj);
  728. }
  729. return $array;
  730. }
  731. /**
  732. * Iterator: current
  733. */
  734. public function current()
  735. {
  736. // Import class name
  737. $class = $this->class;
  738. return ($row = $this->result->current()) ? new $class($row) : FALSE;
  739. }
  740. /**
  741. * Iterator: key
  742. */
  743. public function key()
  744. {
  745. return $this->result->key();
  746. }
  747. /**
  748. * Iterator: next
  749. */
  750. public function next()
  751. {
  752. return $this->result->next();
  753. }
  754. /**
  755. * Iterator: rewind
  756. */
  757. public function rewind()
  758. {
  759. $this->result->rewind();
  760. }
  761. /**
  762. * Iterator: valid
  763. */
  764. public function valid()
  765. {
  766. return $this->result->valid();
  767. }
  768. /**
  769. * Countable: count
  770. */
  771. public function count()
  772. {
  773. return $this->result->count();
  774. }
  775. } // End ORM Iterator