PageRenderTime 48ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/3.0/obsolete/web_client/system/libraries/ORM.php

http://github.com/gallery/gallery3-contrib
PHP | 1586 lines | 924 code | 197 blank | 465 comment | 103 complexity | 257abbb09b0b36182041dab6aba47782 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, LGPL-2.1
  1. <?php defined('SYSPATH') OR die('No direct access allowed.');
  2. /**
  3. * [Object Relational Mapping][ref-orm] (ORM) is a method of abstracting database
  4. * access to standard PHP calls. All table rows are represented as model objects,
  5. * with object properties representing row data. ORM in Kohana generally follows
  6. * the [Active Record][ref-act] pattern.
  7. *
  8. * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
  9. * [ref-act]: http://wikipedia.org/wiki/Active_record
  10. *
  11. * $Id: ORM.php 4711 2009-12-10 20:40:52Z isaiah $
  12. *
  13. * @package ORM
  14. * @author Kohana Team
  15. * @copyright (c) 2007-2009 Kohana Team
  16. * @license http://kohanaphp.com/license
  17. */
  18. class ORM_Core {
  19. // Current relationships
  20. protected $has_one = array();
  21. protected $belongs_to = array();
  22. protected $has_many = array();
  23. protected $has_and_belongs_to_many = array();
  24. protected $has_many_through = array();
  25. // Relationships that should always be joined
  26. protected $load_with = array();
  27. // Current object
  28. protected $object = array();
  29. protected $changed = array();
  30. protected $related = array();
  31. protected $_valid = FALSE;
  32. protected $_loaded = FALSE;
  33. protected $_saved = FALSE;
  34. protected $sorting;
  35. protected $rules = array();
  36. // Related objects
  37. protected $object_relations = array();
  38. protected $changed_relations = array();
  39. // Model table information
  40. protected $object_name;
  41. protected $object_plural;
  42. protected $table_name;
  43. protected $table_columns;
  44. protected $ignored_columns;
  45. // Auto-update columns for creation and updates
  46. protected $updated_column = NULL;
  47. protected $created_column = NULL;
  48. // Table primary key and value
  49. protected $primary_key = 'id';
  50. protected $primary_val = 'name';
  51. // Array of foreign key name overloads
  52. protected $foreign_key = array();
  53. // Model configuration
  54. protected $table_names_plural = TRUE;
  55. protected $reload_on_wakeup = TRUE;
  56. // Database configuration
  57. protected $db = 'default';
  58. protected $db_applied = array();
  59. protected $db_builder;
  60. // With calls already applied
  61. protected $with_applied = array();
  62. // Stores column information for ORM models
  63. protected static $column_cache = array();
  64. /**
  65. * Creates and returns a new model.
  66. *
  67. * @chainable
  68. * @param string model name
  69. * @param mixed parameter for find()
  70. * @return ORM
  71. */
  72. public static function factory($model, $id = NULL)
  73. {
  74. // Set class name
  75. $model = ucfirst($model).'_Model';
  76. return new $model($id);
  77. }
  78. /**
  79. * Prepares the model database connection and loads the object.
  80. *
  81. * @param mixed parameter for find or object to load
  82. * @return void
  83. */
  84. public function __construct($id = NULL)
  85. {
  86. // Set the object name and plural name
  87. $this->object_name = strtolower(substr(get_class($this), 0, -6));
  88. $this->object_plural = inflector::plural($this->object_name);
  89. if ( ! isset($this->sorting))
  90. {
  91. // Default sorting
  92. $this->sorting = array($this->primary_key => 'asc');
  93. }
  94. // Initialize database
  95. $this->__initialize();
  96. // Clear the object
  97. $this->clear();
  98. if (is_object($id))
  99. {
  100. // Load an object
  101. $this->_load_values((array) $id);
  102. }
  103. elseif ( ! empty($id))
  104. {
  105. // Set the object's primary key, but don't load it until needed
  106. $this->object[$this->primary_key] = $id;
  107. // Object is considered saved until something is set
  108. $this->_saved = TRUE;
  109. }
  110. }
  111. /**
  112. * Prepares the model database connection, determines the table name,
  113. * and loads column information.
  114. *
  115. * @return void
  116. */
  117. public function __initialize()
  118. {
  119. if ( ! is_object($this->db))
  120. {
  121. // Get database instance
  122. $this->db = Database::instance($this->db);
  123. }
  124. if (empty($this->table_name))
  125. {
  126. // Table name is the same as the object name
  127. $this->table_name = $this->object_name;
  128. if ($this->table_names_plural === TRUE)
  129. {
  130. // Make the table name plural
  131. $this->table_name = inflector::plural($this->table_name);
  132. }
  133. }
  134. if (is_array($this->ignored_columns))
  135. {
  136. // Make the ignored columns mirrored = mirrored
  137. $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
  138. }
  139. // Load column information
  140. $this->reload_columns();
  141. // Initialize the builder
  142. $this->db_builder = db::build();
  143. }
  144. /**
  145. * Allows serialization of only the object data and state, to prevent
  146. * "stale" objects being unserialized, which also requires less memory.
  147. *
  148. * @return array
  149. */
  150. public function __sleep()
  151. {
  152. // Store only information about the object
  153. return array('object_name', 'object', 'changed', '_loaded', '_saved', 'sorting');
  154. }
  155. /**
  156. * Prepares the database connection and reloads the object.
  157. *
  158. * @return void
  159. */
  160. public function __wakeup()
  161. {
  162. // Initialize database
  163. $this->__initialize();
  164. if ($this->reload_on_wakeup === TRUE)
  165. {
  166. // Reload the object
  167. $this->reload();
  168. }
  169. }
  170. /**
  171. * Handles pass-through to database methods. Calls to query methods
  172. * (query, get, insert, update) are not allowed. Query builder methods
  173. * are chainable.
  174. *
  175. * @param string method name
  176. * @param array method arguments
  177. * @return mixed
  178. */
  179. public function __call($method, array $args)
  180. {
  181. if (method_exists($this->db_builder, $method))
  182. {
  183. if (in_array($method, array('execute', 'insert', 'update', 'delete')))
  184. throw new Kohana_Exception('Query methods cannot be used through ORM');
  185. // Method has been applied to the database
  186. $this->db_applied[$method] = $method;
  187. // Number of arguments passed
  188. $num_args = count($args);
  189. if ($method === 'select' AND $num_args > 3)
  190. {
  191. // Call select() manually to avoid call_user_func_array
  192. $this->db_builder->select($args);
  193. }
  194. else
  195. {
  196. // We use switch here to manually call the database methods. This is
  197. // done for speed: call_user_func_array can take over 300% longer to
  198. // make calls. Most database methods are 4 arguments or less, so this
  199. // avoids almost any calls to call_user_func_array.
  200. switch ($num_args)
  201. {
  202. case 0:
  203. if (in_array($method, array('open', 'and_open', 'or_open', 'close', 'cache')))
  204. {
  205. // Should return ORM, not Database
  206. $this->db_builder->$method();
  207. }
  208. else
  209. {
  210. // Support for things like reset_select, reset_write, list_tables
  211. return $this->db_builder->$method();
  212. }
  213. break;
  214. case 1:
  215. $this->db_builder->$method($args[0]);
  216. break;
  217. case 2:
  218. $this->db_builder->$method($args[0], $args[1]);
  219. break;
  220. case 3:
  221. $this->db_builder->$method($args[0], $args[1], $args[2]);
  222. break;
  223. case 4:
  224. $this->db_builder->$method($args[0], $args[1], $args[2], $args[3]);
  225. break;
  226. default:
  227. // Here comes the snail...
  228. call_user_func_array(array($this->db, $method), $args);
  229. break;
  230. }
  231. }
  232. return $this;
  233. }
  234. else
  235. {
  236. throw new Kohana_Exception('Invalid method :method called in :class',
  237. array(':method' => $method, ':class' => get_class($this)));
  238. }
  239. }
  240. /**
  241. * Handles retrieval of all model values, relationships, and metadata.
  242. *
  243. * @param string column name
  244. * @return mixed
  245. */
  246. public function __get($column)
  247. {
  248. if (array_key_exists($column, $this->object))
  249. {
  250. if( ! $this->loaded() AND ! $this->empty_primary_key())
  251. {
  252. // Column asked for but the object hasn't been loaded yet, so do it now
  253. // Ignore loading of any columns that have been changed
  254. $this->find($this->object[$this->primary_key], TRUE);
  255. }
  256. return $this->object[$column];
  257. }
  258. elseif (isset($this->related[$column]))
  259. {
  260. return $this->related[$column];
  261. }
  262. elseif ($column === 'primary_key_value')
  263. {
  264. if( ! $this->loaded() AND ! $this->empty_primary_key() AND $this->unique_key($this->object[$this->primary_key]) !== $this->primary_key)
  265. {
  266. // Load if object hasn't been loaded and the key given isn't the primary_key
  267. // that we need (i.e. passing an email address to ORM::factory rather than the id)
  268. $this->find($this->object[$this->primary_key], TRUE);
  269. }
  270. return $this->object[$this->primary_key];
  271. }
  272. elseif ($model = $this->related_object($column))
  273. {
  274. // This handles the has_one and belongs_to relationships
  275. if (in_array($model->object_name, $this->belongs_to))
  276. {
  277. if ( ! $this->loaded() AND ! $this->empty_primary_key())
  278. {
  279. // Load this object first so we know what id to look for in the foreign table
  280. $this->find($this->object[$this->primary_key], TRUE);
  281. }
  282. // Foreign key lies in this table (this model belongs_to target model)
  283. $where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]);
  284. }
  285. else
  286. {
  287. // Foreign key lies in the target table (this model has_one target model)
  288. $where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
  289. }
  290. // one<>alias:one relationship
  291. return $this->related[$column] = $model->find($where);
  292. }
  293. elseif (isset($this->has_many_through[$column]))
  294. {
  295. // Load the "middle" model
  296. $through = ORM::factory(inflector::singular($this->has_many_through[$column]));
  297. // Load the "end" model
  298. $model = ORM::factory(inflector::singular($column));
  299. // Join ON target model's primary key set to 'through' model's foreign key
  300. // User-defined foreign keys must be defined in the 'through' model
  301. $join_table = $through->table_name;
  302. $join_col1 = $through->foreign_key($model->object_name, $join_table);
  303. $join_col2 = $model->foreign_key(TRUE);
  304. // one<>alias:many relationship
  305. return $model
  306. ->join($join_table, $join_col1, $join_col2)
  307. ->where($through->foreign_key($this->object_name, $join_table), '=', $this->primary_key_value);
  308. }
  309. elseif (isset($this->has_many[$column]))
  310. {
  311. // one<>many aliased relationship
  312. $model_name = $this->has_many[$column];
  313. $model = ORM::factory(inflector::singular($model_name));
  314. return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
  315. }
  316. elseif (in_array($column, $this->has_many))
  317. {
  318. // one<>many relationship
  319. $model = ORM::factory(inflector::singular($column));
  320. return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
  321. }
  322. elseif (in_array($column, $this->has_and_belongs_to_many))
  323. {
  324. // Load the remote model, always singular
  325. $model = ORM::factory(inflector::singular($column));
  326. if ($this->has($model, TRUE))
  327. {
  328. // many<>many relationship
  329. return $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]);
  330. }
  331. else
  332. {
  333. // empty many<>many relationship
  334. return $model->where($model->foreign_key(TRUE), 'IS', NULL);
  335. }
  336. }
  337. elseif (isset($this->ignored_columns[$column]))
  338. {
  339. return NULL;
  340. }
  341. elseif (in_array($column, array
  342. (
  343. 'object_name', 'object_plural','_valid', // Object
  344. 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
  345. 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships
  346. )))
  347. {
  348. // Model meta information
  349. return $this->$column;
  350. }
  351. else
  352. {
  353. throw new Kohana_Exception('The :property property does not exist in the :class class',
  354. array(':property' => $column, ':class' => get_class($this)));
  355. }
  356. }
  357. /**
  358. * Tells you if the Model has been loaded or not
  359. *
  360. * @return bool
  361. */
  362. public function loaded() {
  363. if ( ! $this->_loaded AND ! $this->empty_primary_key())
  364. {
  365. // If returning the loaded member and no load has been attempted, do it now
  366. $this->find($this->object[$this->primary_key], TRUE);
  367. }
  368. return $this->_loaded;
  369. }
  370. /**
  371. * Tells you if the model was saved successfully or not
  372. *
  373. * @return bool
  374. */
  375. public function saved() {
  376. return $this->_saved;
  377. }
  378. /**
  379. * Handles setting of all model values, and tracks changes between values.
  380. *
  381. * @param string column name
  382. * @param mixed column value
  383. * @return void
  384. */
  385. public function __set($column, $value)
  386. {
  387. if (isset($this->ignored_columns[$column]))
  388. {
  389. return NULL;
  390. }
  391. elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object))
  392. {
  393. if (isset($this->table_columns[$column]))
  394. {
  395. // Data has changed
  396. $this->changed[$column] = $column;
  397. // Object is no longer saved
  398. $this->_saved = FALSE;
  399. }
  400. $this->object[$column] = $this->load_type($column, $value);
  401. }
  402. elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
  403. {
  404. // Load relations
  405. $model = ORM::factory(inflector::singular($column));
  406. if ( ! isset($this->object_relations[$column]))
  407. {
  408. // Load relations
  409. $this->has($model);
  410. }
  411. // Change the relationships
  412. $this->changed_relations[$column] = $value;
  413. if (isset($this->related[$column]))
  414. {
  415. // Force a reload of the relationships
  416. unset($this->related[$column]);
  417. }
  418. }
  419. else
  420. {
  421. throw new Kohana_Exception('The :property: property does not exist in the :class: class',
  422. array(':property:' => $column, ':class:' => get_class($this)));
  423. }
  424. }
  425. /**
  426. * Chainable set method
  427. *
  428. * @param string name of field or array of key => val
  429. * @param mixed value
  430. * @return ORM
  431. */
  432. public function set($name, $value = NULL)
  433. {
  434. if (is_array($name))
  435. {
  436. foreach ($name as $key => $value)
  437. {
  438. $this->__set($key, $value);
  439. }
  440. }
  441. else
  442. {
  443. $this->__set($name, $value);
  444. }
  445. return $this;
  446. }
  447. /**
  448. * Checks if object data is set.
  449. *
  450. * @param string column name
  451. * @return boolean
  452. */
  453. public function __isset($column)
  454. {
  455. return (isset($this->object[$column]) OR isset($this->related[$column]));
  456. }
  457. /**
  458. * Unsets object data.
  459. *
  460. * @param string column name
  461. * @return void
  462. */
  463. public function __unset($column)
  464. {
  465. unset($this->object[$column], $this->changed[$column], $this->related[$column]);
  466. }
  467. /**
  468. * Returns the values of this object as an array.
  469. *
  470. * @return array
  471. */
  472. public function as_array()
  473. {
  474. $object = array();
  475. foreach ($this->object as $key => $val)
  476. {
  477. // Reconstruct the array (calls __get)
  478. $object[$key] = $this->$key;
  479. }
  480. foreach ($this->with_applied as $model => $enabled)
  481. {
  482. // Generate arrays for relationships
  483. if ($enabled)
  484. {
  485. $object[$model] = $this->$model->as_array();
  486. }
  487. }
  488. return $object;
  489. }
  490. /**
  491. * Binds another one-to-one object to this model. One-to-one objects
  492. * can be nested using 'object1:object2' syntax
  493. *
  494. * @param string target model to bind to
  495. * @return void
  496. */
  497. public function with($target_path)
  498. {
  499. if (isset($this->with_applied[$target_path]))
  500. {
  501. // Don't join anything already joined
  502. return $this;
  503. }
  504. // Split object parts
  505. $objects = explode(':', $target_path);
  506. $target = $this;
  507. foreach ($objects as $object)
  508. {
  509. // Go down the line of objects to find the given target
  510. $parent = $target;
  511. $target = $parent->related_object($object);
  512. if ( ! $target)
  513. {
  514. // Can't find related object
  515. return $this;
  516. }
  517. }
  518. $target_name = $object;
  519. // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix)
  520. array_pop($objects);
  521. $parent_path = implode(':', $objects);
  522. if (empty($parent_path))
  523. {
  524. // Use this table name itself for the parent object
  525. $parent_path = $this->table_name;
  526. }
  527. else
  528. {
  529. if( ! isset($this->with_applied[$parent_path]))
  530. {
  531. // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
  532. $this->with($parent_path);
  533. }
  534. }
  535. // Add to with_applied to prevent duplicate joins
  536. $this->with_applied[$target_path] = TRUE;
  537. $select = array();
  538. // Use the keys of the empty object to determine the columns
  539. foreach (array_keys($target->object) as $column)
  540. {
  541. // Add the prefix so that load_result can determine the relationship
  542. $select[$target_path.':'.$column] = $target_path.'.'.$column;
  543. }
  544. // Select all of the prefixed keys in the object
  545. $this->db_builder->select($select);
  546. if (in_array($target->object_name, $parent->belongs_to))
  547. {
  548. // Parent belongs_to target, use target's primary key as join column
  549. $join_col1 = $target->foreign_key(TRUE, $target_path);
  550. $join_col2 = $parent->foreign_key($target_name, $parent_path);
  551. }
  552. else
  553. {
  554. // Parent has_one target, use parent's primary key as join column
  555. $join_col2 = $parent->foreign_key(TRUE, $parent_path);
  556. $join_col1 = $parent->foreign_key($target_name, $target_path);
  557. }
  558. // This trick allows for models to use different table prefixes (sharing the same database)
  559. $join_table = array($this->db->quote_table($target_path) => $target->db->quote_table($target->table_name));
  560. // Join the related object into the result
  561. // Use Database_Expression to disable prefixing
  562. $this->db_builder->join(new Database_Expression($join_table), $join_col1, $join_col2, 'LEFT');
  563. return $this;
  564. }
  565. /**
  566. * Finds and loads a single database row into the object.
  567. *
  568. * @chainable
  569. * @param mixed primary key or an array of clauses
  570. * @param bool ignore loading of columns that have been modified
  571. * @return ORM
  572. */
  573. public function find($id = NULL, $ignore_changed = FALSE)
  574. {
  575. if ($id !== NULL)
  576. {
  577. if (is_array($id))
  578. {
  579. // Search for all clauses
  580. $this->db_builder->where(array($id));
  581. }
  582. else
  583. {
  584. // Search for a specific column
  585. $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id);
  586. }
  587. }
  588. return $this->load_result(FALSE, $ignore_changed);
  589. }
  590. /**
  591. * Finds multiple database rows and returns an iterator of the rows found.
  592. *
  593. * @chainable
  594. * @param integer SQL limit
  595. * @param integer SQL offset
  596. * @return ORM_Iterator
  597. */
  598. public function find_all($limit = NULL, $offset = NULL)
  599. {
  600. if ($limit !== NULL AND ! isset($this->db_applied['limit']))
  601. {
  602. // Set limit
  603. $this->limit($limit);
  604. }
  605. if ($offset !== NULL AND ! isset($this->db_applied['offset']))
  606. {
  607. // Set offset
  608. $this->offset($offset);
  609. }
  610. return $this->load_result(TRUE);
  611. }
  612. /**
  613. * Creates a key/value array from all of the objects available. Uses find_all
  614. * to find the objects.
  615. *
  616. * @param string key column
  617. * @param string value column
  618. * @return array
  619. */
  620. public function select_list($key = NULL, $val = NULL)
  621. {
  622. if ($key === NULL)
  623. {
  624. $key = $this->primary_key;
  625. }
  626. if ($val === NULL)
  627. {
  628. $val = $this->primary_val;
  629. }
  630. // Return a select list from the results
  631. return $this->select($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val);
  632. }
  633. /**
  634. * Validates the current object. This method requires that rules are set to be useful, if called with out
  635. * any rules set, or if a Validation object isn't passed, nothing happens.
  636. *
  637. * @param object Validation array
  638. * @param boolean Save on validate
  639. * @return ORM
  640. * @chainable
  641. */
  642. public function validate(Validation $array = NULL)
  643. {
  644. if ($array === NULL)
  645. $array = new Validation($this->object);
  646. if (count($this->rules) > 0)
  647. {
  648. foreach ($this->rules as $field => $parameters)
  649. {
  650. foreach ($parameters as $type => $value) {
  651. switch ($type) {
  652. case 'pre_filter':
  653. $array->pre_filter($value,$field);
  654. break;
  655. case 'rules':
  656. $rules = array_merge(array($field),$value);
  657. call_user_func_array(array($array,'add_rules'), $rules);
  658. break;
  659. case 'callbacks':
  660. $callbacks = array_merge(array($field),$value);
  661. call_user_func_array(array($array,'add_callbacks'), $callbacks);
  662. break;
  663. }
  664. }
  665. }
  666. }
  667. // Validate the array
  668. if (($this->_valid = $array->validate()) === FALSE)
  669. {
  670. ORM_Validation_Exception::handle_validation($this->table_name, $array);
  671. }
  672. // Fields may have been modified by filters
  673. $this->object = array_merge($this->object, $array->getArrayCopy());
  674. // Return validation status
  675. return $this;
  676. }
  677. /**
  678. * Saves the current object.
  679. *
  680. * @chainable
  681. * @return ORM
  682. */
  683. public function save()
  684. {
  685. if ( ! empty($this->changed))
  686. {
  687. // Require model validation before saving
  688. if ( ! $this->_valid)
  689. {
  690. $this->validate();
  691. }
  692. $data = array();
  693. foreach ($this->changed as $column)
  694. {
  695. // Compile changed data
  696. $data[$column] = $this->object[$column];
  697. }
  698. if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key]))
  699. {
  700. // Primary key isn't empty and hasn't been changed so do an update
  701. if (is_array($this->updated_column))
  702. {
  703. // Fill the updated column
  704. $column = $this->updated_column['column'];
  705. $format = $this->updated_column['format'];
  706. $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
  707. }
  708. $query = db::update($this->table_name)
  709. ->set($data)
  710. ->where($this->primary_key, '=', $this->primary_key_value)
  711. ->execute($this->db);
  712. // Object has been saved
  713. $this->_saved = TRUE;
  714. }
  715. else
  716. {
  717. if (is_array($this->created_column))
  718. {
  719. // Fill the created column
  720. $column = $this->created_column['column'];
  721. $format = $this->created_column['format'];
  722. $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
  723. }
  724. $result = db::insert($this->table_name)
  725. ->columns(array_keys($data))
  726. ->values(array_values($data))
  727. ->execute($this->db);
  728. if ($result->count() > 0)
  729. {
  730. if (empty($this->object[$this->primary_key]))
  731. {
  732. // Load the insert id as the primary key
  733. $this->object[$this->primary_key] = $result->insert_id();
  734. }
  735. // Object is now loaded and saved
  736. $this->_loaded = $this->_saved = TRUE;
  737. }
  738. }
  739. if ($this->saved() === TRUE)
  740. {
  741. // All changes have been saved
  742. $this->changed = array();
  743. }
  744. }
  745. if ($this->saved() === TRUE AND ! empty($this->changed_relations))
  746. {
  747. foreach ($this->changed_relations as $column => $values)
  748. {
  749. // All values that were added
  750. $added = array_diff($values, $this->object_relations[$column]);
  751. // All values that were saved
  752. $removed = array_diff($this->object_relations[$column], $values);
  753. if (empty($added) AND empty($removed))
  754. {
  755. // No need to bother
  756. continue;
  757. }
  758. // Clear related columns
  759. unset($this->related[$column]);
  760. // Load the model
  761. $model = ORM::factory(inflector::singular($column));
  762. if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE)
  763. continue;
  764. if (is_int($join_table))
  765. {
  766. // No "through" table, load the default JOIN table
  767. $join_table = $model->join_table($this->table_name);
  768. }
  769. // Foreign keys for the join table
  770. $object_fk = $this->foreign_key($join_table);
  771. $related_fk = $model->foreign_key($join_table);
  772. if ( ! empty($added))
  773. {
  774. foreach ($added as $id)
  775. {
  776. // Insert the new relationship
  777. db::insert($join_table)
  778. ->columns($object_fk, $related_fk)
  779. ->values($this->primary_key_value, $id)
  780. ->execute($this->db);
  781. }
  782. }
  783. if ( ! empty($removed))
  784. {
  785. db::delete($join_table)
  786. ->where($object_fk, '=', $this->primary_key_value)
  787. ->where($related_fk, 'IN', $removed)
  788. ->execute($this->db);
  789. }
  790. // Clear all relations for this column
  791. unset($this->object_relations[$column], $this->changed_relations[$column]);
  792. }
  793. }
  794. if ($this->saved() === TRUE)
  795. {
  796. // Clear the per-request database cache
  797. $this->db->clear_cache(NULL, Database::PER_REQUEST);
  798. }
  799. return $this;
  800. }
  801. /**
  802. * Deletes the current object from the database. This does NOT destroy
  803. * relationships that have been created with other objects.
  804. *
  805. * @chainable
  806. * @param mixed id to delete
  807. * @return ORM
  808. */
  809. public function delete($id = NULL)
  810. {
  811. if ($id === NULL)
  812. {
  813. // Use the the primary key value
  814. $id = $this->primary_key_value;
  815. }
  816. // Delete this object
  817. db::delete($this->table_name)
  818. ->where($this->primary_key, '=', $id)
  819. ->execute($this->db);
  820. // Clear the per-request database cache
  821. $this->db->clear_cache(NULL, Database::PER_REQUEST);
  822. return $this->clear();
  823. }
  824. /**
  825. * Delete all objects in the associated table. This does NOT destroy
  826. * relationships that have been created with other objects.
  827. *
  828. * @chainable
  829. * @param array ids to delete
  830. * @return ORM
  831. */
  832. public function delete_all($ids = NULL)
  833. {
  834. if (is_array($ids))
  835. {
  836. // Delete only given ids
  837. db::delete($this->table_name)
  838. ->where($this->primary_key, 'IN', $ids)
  839. ->execute($this->db);
  840. }
  841. elseif ($ids === NULL)
  842. {
  843. // Delete all records
  844. db::delete($this->table_name)
  845. ->execute($this->db);
  846. }
  847. else
  848. {
  849. // Do nothing - safeguard
  850. return $this;
  851. }
  852. // Clear the per-request database cache
  853. $this->db->clear_cache(NULL, Database::PER_REQUEST);
  854. return $this->clear();
  855. }
  856. /**
  857. * Unloads the current object and clears the status.
  858. *
  859. * @chainable
  860. * @return ORM
  861. */
  862. public function clear()
  863. {
  864. // Create an array with all the columns set to NULL
  865. $columns = array_keys($this->table_columns);
  866. $values = array_combine($columns, array_fill(0, count($columns), NULL));
  867. // Replace the current object with an empty one
  868. $this->_load_values($values);
  869. return $this;
  870. }
  871. /**
  872. * Reloads the current object from the database.
  873. *
  874. * @chainable
  875. * @return ORM
  876. */
  877. public function reload()
  878. {
  879. return $this->find($this->object[$this->primary_key]);
  880. }
  881. /**
  882. * Reload column definitions.
  883. *
  884. * @chainable
  885. * @param boolean force reloading
  886. * @return ORM
  887. */
  888. public function reload_columns($force = FALSE)
  889. {
  890. if ($force === TRUE OR empty($this->table_columns))
  891. {
  892. if (isset(ORM::$column_cache[$this->object_name]))
  893. {
  894. // Use cached column information
  895. $this->table_columns = ORM::$column_cache[$this->object_name];
  896. }
  897. else
  898. {
  899. // Load table columns
  900. ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields();
  901. }
  902. }
  903. return $this;
  904. }
  905. /**
  906. * Tests if this object has a relationship to a different model.
  907. *
  908. * @param object related ORM model
  909. * @param boolean check for any relations to given model
  910. * @return boolean
  911. */
  912. public function has(ORM $model, $any = FALSE)
  913. {
  914. // Determine plural or singular relation name
  915. $related = ($model->table_names_plural === TRUE) ? $model->object_plural : $model->object_name;
  916. if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE)
  917. return FALSE;
  918. if (is_int($join_table))
  919. {
  920. // No "through" table, load the default JOIN table
  921. $join_table = $model->join_table($this->table_name);
  922. }
  923. if ( ! isset($this->object_relations[$related]))
  924. {
  925. // Load the object relationships
  926. $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
  927. }
  928. if( ! $model->loaded() AND ! $model->empty_primary_key())
  929. {
  930. // Load the related model if it hasn't already been
  931. $model->find($model->object[$model->primary_key]);
  932. }
  933. if ( ! $model->empty_primary_key())
  934. {
  935. // Check if a specific object exists
  936. return in_array($model->primary_key_value, $this->changed_relations[$related]);
  937. }
  938. elseif ($any)
  939. {
  940. // Check if any relations to given model exist
  941. return ! empty($this->changed_relations[$related]);
  942. }
  943. else
  944. {
  945. return FALSE;
  946. }
  947. }
  948. /**
  949. * Adds a new relationship to between this model and another.
  950. *
  951. * @param object related ORM model
  952. * @return boolean
  953. */
  954. public function add(ORM $model)
  955. {
  956. if ($this->has($model))
  957. return TRUE;
  958. // Get the faked column name
  959. $column = $model->object_plural;
  960. // Add the new relation to the update
  961. $this->changed_relations[$column][] = $model->primary_key_value;
  962. if (isset($this->related[$column]))
  963. {
  964. // Force a reload of the relationships
  965. unset($this->related[$column]);
  966. }
  967. return TRUE;
  968. }
  969. /**
  970. * Adds a new relationship to between this model and another.
  971. *
  972. * @param object related ORM model
  973. * @return boolean
  974. */
  975. public function remove(ORM $model)
  976. {
  977. if ( ! $this->has($model))
  978. return FALSE;
  979. // Get the faked column name
  980. $column = $model->object_plural;
  981. if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE)
  982. return FALSE;
  983. // Remove the relationship
  984. unset($this->changed_relations[$column][$key]);
  985. if (isset($this->related[$column]))
  986. {
  987. // Force a reload of the relationships
  988. unset($this->related[$column]);
  989. }
  990. return TRUE;
  991. }
  992. /**
  993. * Count the number of records in the table.
  994. *
  995. * @return integer
  996. */
  997. public function count_all()
  998. {
  999. // Return the total number of records in a table
  1000. return $this->db_builder->count_records($this->table_name);
  1001. }
  1002. /**
  1003. * Proxy method to Database list_fields.
  1004. *
  1005. * @return array
  1006. */
  1007. public function list_fields()
  1008. {
  1009. // Proxy to database
  1010. return $this->db->list_fields($this->table_name);
  1011. }
  1012. /**
  1013. * Proxy method to Database clear_cache.
  1014. *
  1015. * @chainable
  1016. * @param string SQL query to clear
  1017. * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
  1018. * @return ORM
  1019. */
  1020. public function clear_cache($sql = NULL, $type = NULL)
  1021. {
  1022. // Proxy to database
  1023. $this->db->clear_cache($sql, $type);
  1024. ORM::$column_cache = array();
  1025. return $this;
  1026. }
  1027. /**
  1028. * Returns the unique key for a specific value. This method is expected
  1029. * to be overloaded in models if the model has other unique columns.
  1030. *
  1031. * @param mixed unique value
  1032. * @return string
  1033. */
  1034. public function unique_key($id)
  1035. {
  1036. return $this->primary_key;
  1037. }
  1038. /**
  1039. * Determines the name of a foreign key for a specific table.
  1040. *
  1041. * @param string related table name
  1042. * @param string prefix table name (used for JOINs)
  1043. * @return string
  1044. */
  1045. public function foreign_key($table = NULL, $prefix_table = NULL)
  1046. {
  1047. if ($table === TRUE)
  1048. {
  1049. if (is_string($prefix_table))
  1050. {
  1051. // Use prefix table name and this table's PK
  1052. return $prefix_table.'.'.$this->primary_key;
  1053. }
  1054. else
  1055. {
  1056. // Return the name of this table's PK
  1057. return $this->table_name.'.'.$this->primary_key;
  1058. }
  1059. }
  1060. if (is_string($prefix_table))
  1061. {
  1062. // Add a period for prefix_table.column support
  1063. $prefix_table .= '.';
  1064. }
  1065. if (isset($this->foreign_key[$table]))
  1066. {
  1067. // Use the defined foreign key name, no magic here!
  1068. $foreign_key = $this->foreign_key[$table];
  1069. }
  1070. else
  1071. {
  1072. if ( ! is_string($table) OR ! array_key_exists($table.'_'.$this->primary_key, $this->object))
  1073. {
  1074. // Use this table
  1075. $table = $this->table_name;
  1076. if (strpos($table, '.') !== FALSE)
  1077. {
  1078. // Hack around support for PostgreSQL schemas
  1079. list ($schema, $table) = explode('.', $table, 2);
  1080. }
  1081. if ($this->table_names_plural === TRUE)
  1082. {
  1083. // Make the key name singular
  1084. $table = inflector::singular($table);
  1085. }
  1086. }
  1087. $foreign_key = $table.'_'.$this->primary_key;
  1088. }
  1089. return $prefix_table.$foreign_key;
  1090. }
  1091. /**
  1092. * This uses alphabetical comparison to choose the name of the table.
  1093. *
  1094. * Example: The joining table of users and roles would be roles_users,
  1095. * because "r" comes before "u". Joining products and categories would
  1096. * result in categories_products, because "c" comes before "p".
  1097. *
  1098. * Example: zoo > zebra > robber > ocean > angel > aardvark
  1099. *
  1100. * @param string table name
  1101. * @return string
  1102. */
  1103. public function join_table($table)
  1104. {
  1105. if ($this->table_name > $table)
  1106. {
  1107. $table = $table.'_'.$this->table_name;
  1108. }
  1109. else
  1110. {
  1111. $table = $this->table_name.'_'.$table;
  1112. }
  1113. return $table;
  1114. }
  1115. /**
  1116. * Returns an ORM model for the given object name;
  1117. *
  1118. * @param string object name
  1119. * @return ORM
  1120. */
  1121. protected function related_object($object)
  1122. {
  1123. if (isset($this->has_one[$object]))
  1124. {
  1125. $object = ORM::factory($this->has_one[$object]);
  1126. }
  1127. elseif (isset($this->belongs_to[$object]))
  1128. {
  1129. $object = ORM::factory($this->belongs_to[$object]);
  1130. }
  1131. elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to))
  1132. {
  1133. $object = ORM::factory($object);
  1134. }
  1135. else
  1136. {
  1137. return FALSE;
  1138. }
  1139. return $object;
  1140. }
  1141. /**
  1142. * Loads an array of values into into the current object.
  1143. *
  1144. * @chainable
  1145. * @param array values to load
  1146. * @return ORM
  1147. */
  1148. public function load_values(array $values)
  1149. {
  1150. // Related objects
  1151. $related = array();
  1152. foreach ($values as $column => $value)
  1153. {
  1154. if (strpos($column, ':') === FALSE)
  1155. {
  1156. if ( ! isset($this->changed[$column]))
  1157. {
  1158. if (isset($this->table_columns[$column]))
  1159. {
  1160. //Update the column, respects __get()
  1161. $this->$column = $value;
  1162. }
  1163. }
  1164. }
  1165. else
  1166. {
  1167. list ($prefix, $column) = explode(':', $column, 2);
  1168. $related[$prefix][$column] = $value;
  1169. }
  1170. }
  1171. if ( ! empty($related))
  1172. {
  1173. foreach ($related as $object => $values)
  1174. {
  1175. // Load the related objects with the values in the result
  1176. $this->related[$object] = $this->related_object($object)->load_values($values);
  1177. }
  1178. }
  1179. return $this;
  1180. }
  1181. /**
  1182. * Loads an array of values into into the current object. Only used internally
  1183. *
  1184. * @chainable
  1185. * @param array values to load
  1186. * @param bool ignore loading of columns that have been modified
  1187. * @return ORM
  1188. */
  1189. public function _load_values(array $values, $ignore_changed = FALSE)
  1190. {
  1191. if (array_key_exists($this->primary_key, $values))
  1192. {
  1193. if ( ! $ignore_changed)
  1194. {
  1195. // Replace the object and reset the object status
  1196. $this->object = $this->changed = $this->related = array();
  1197. }
  1198. // Set the loaded and saved object status based on the primary key
  1199. $this->_loaded = $this->_saved = ($values[$this->primary_key] !== NULL);
  1200. }
  1201. // Related objects
  1202. $related = array();
  1203. foreach ($values as $column => $value)
  1204. {
  1205. if (strpos($column, ':') === FALSE)
  1206. {
  1207. if ( ! $ignore_changed OR ! isset($this->changed[$column]))
  1208. {
  1209. if (isset($this->table_columns[$column]))
  1210. {
  1211. // The type of the value can be determined, convert the value
  1212. $value = $this->load_type($column, $value);
  1213. }
  1214. $this->object[$column] = $value;
  1215. }
  1216. }
  1217. else
  1218. {
  1219. list ($prefix, $column) = explode(':', $column, 2);
  1220. $related[$prefix][$column] = $value;
  1221. }
  1222. }
  1223. if ( ! empty($related))
  1224. {
  1225. foreach ($related as $object => $values)
  1226. {
  1227. // Load the related objects with the values in the result
  1228. $this->related[$object] = $this->related_object($object)->_load_values($values);
  1229. }
  1230. }
  1231. return $this;
  1232. }
  1233. /**
  1234. * Loads a value according to the types defined by the column metadata.
  1235. *
  1236. * @param string column name
  1237. * @param mixed value to load
  1238. * @return mixed
  1239. */
  1240. protected function load_type($column, $value)
  1241. {
  1242. $type = gettype($value);
  1243. if ($type == 'object' OR $type == 'array' OR ! isset($this->table_columns[$column]))
  1244. return $value;
  1245. // Load column data
  1246. $column = $this->table_columns[$column];
  1247. if ($value === NULL AND ! empty($column['nullable']))
  1248. return $value;
  1249. if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
  1250. {
  1251. // Use boolean for BINARY(1) fields
  1252. $column['type'] = 'boolean';
  1253. }
  1254. switch ($column['type'])
  1255. {
  1256. case 'int':
  1257. if ($value === '' AND ! empty($column['nullable']))
  1258. {
  1259. // Forms will only submit strings, so empty integer values must be null
  1260. $value = NULL;
  1261. }
  1262. elseif ((float) $value > PHP_INT_MAX)
  1263. {
  1264. // This number cannot be represented by a PHP integer, so we convert it to a string
  1265. $value = (string) $value;
  1266. }
  1267. else
  1268. {
  1269. $value = (int) $value;
  1270. }
  1271. break;
  1272. case 'float':
  1273. $value = (float) $value;
  1274. break;
  1275. case 'boolean':
  1276. $value = (bool) $value;
  1277. break;
  1278. case 'string':
  1279. $value = (string) $value;
  1280. break;
  1281. }
  1282. return $value;
  1283. }
  1284. /**
  1285. * Loads a database result, either as a new object for this model, or as
  1286. * an iterator for multiple rows.
  1287. *
  1288. * @chainable
  1289. * @param boolean return an iterator or load a single row
  1290. * @param boolean ignore loading of columns that have been modified
  1291. * @return ORM for single rows
  1292. * @return ORM_Iterator for multiple rows
  1293. */
  1294. protected function load_result($array = FALSE, $ignore_changed = FALSE)
  1295. {
  1296. $this->db_builder->from($this->table_name);
  1297. if ($array === FALSE)
  1298. {
  1299. // Only fetch 1 record
  1300. $this->db_builder->limit(1);
  1301. }
  1302. if ( ! isset($this->db_applied['select']))
  1303. {
  1304. // Select all columns by default
  1305. $this->db_builder->select($this->table_name.'.*');
  1306. }
  1307. if ( ! empty($this->load_with))
  1308. {
  1309. foreach ($this->load_with as $alias => $object)
  1310. {
  1311. // Join each object into the results
  1312. if (is_string($alias))
  1313. {
  1314. // Use alias
  1315. $this->with($alias);
  1316. }
  1317. else
  1318. {
  1319. // Use object
  1320. $this->with($object);
  1321. }
  1322. }
  1323. }
  1324. if ( ! isset($this->db_applied['order_by']) AND ! empty($this->sorting))
  1325. {
  1326. $sorting = array();
  1327. foreach ($this->sorting as $column => $direction)
  1328. {
  1329. if (strpos($column, '.') === FALSE)
  1330. {
  1331. // Keeps sorting working properly when using JOINs on
  1332. // tables with columns of the same name
  1333. $column = $this->table_name.'.'.$column;
  1334. }
  1335. $sorting[$column] = $direction;
  1336. }
  1337. // Apply the user-defined sorting
  1338. $this->db_builder->order_by($sorting);
  1339. }
  1340. // Load the result
  1341. $result = $this->db_builder->execute($this->db);
  1342. if ($array === TRUE)
  1343. {
  1344. // Return an iterated result
  1345. return new ORM_Iterator($this, $result);
  1346. }
  1347. if ($result->count() === 1)
  1348. {
  1349. // Load object values
  1350. $this->_load_values($result->as_array()->current(), $ignore_changed);
  1351. }
  1352. else
  1353. {
  1354. // Clear the object, nothing was found
  1355. $this->clear();
  1356. }
  1357. return $this;
  1358. }
  1359. /**
  1360. * Return an array of all the primary keys of the related table.
  1361. *
  1362. * @param string table name
  1363. * @param object ORM model to find relations of
  1364. * @return array
  1365. */
  1366. protected function load_relations($table, ORM $model)
  1367. {
  1368. $result = db::select(array('id' => $model->foreign_key($table)))
  1369. ->from($table)
  1370. ->where($this->foreign_key($table, $table), '=', $this->primary_key_value)
  1371. ->execute($this->db)
  1372. ->as_object();
  1373. $relations = array();
  1374. foreach ($result as $row)
  1375. {
  1376. $relations[] = $row->id;
  1377. }
  1378. return $relations;
  1379. }
  1380. /**
  1381. * Returns whether or not primary key is empty
  1382. *
  1383. * @return bool
  1384. */
  1385. protected function empty_primary_key()
  1386. {
  1387. return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0');
  1388. }
  1389. } // End ORM