PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/orm/classes/kohana/orm.php

https://bitbucket.org/waqar4at/scrumie
PHP | 1639 lines | 793 code | 235 blank | 611 comment | 61 complexity | 05be1bd2ce2b8d87afc9f6d773c19c9e MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. <?php defined('SYSPATH') or die('No direct script access.');
  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. * @package Kohana/ORM
  12. * @author Kohana Team
  13. * @copyright (c) 2007-2010 Kohana Team
  14. * @license http://kohanaframework.org/license
  15. *
  16. *
  17. * @method ORM where()
  18. * @method ORM and_where()
  19. * @method ORM or_where()
  20. * @method ORM where_open()
  21. * @method ORM and_where_open()
  22. * @method ORM or_where_open()
  23. * @method ORM where_close()
  24. * @method ORM and_where_close()
  25. * @method ORM or_where_close()
  26. * @method ORM distinct()
  27. * @method ORM select()
  28. * @method ORM from()
  29. * @method ORM join()
  30. * @method ORM on()
  31. * @method ORM group_by()
  32. * @method ORM having()
  33. * @method ORM and_having()
  34. * @method ORM or_having()
  35. * @method ORM having_open()
  36. * @method ORM and_having_open()
  37. * @method ORM or_having_open()
  38. * @method ORM having_close()
  39. * @method ORM and_having_close()
  40. * @method ORM or_having_close()
  41. * @method ORM order_by()
  42. * @method ORM limit()
  43. * @method ORM offset()
  44. * @method ORM cached()
  45. * @method Validation validation()
  46. *
  47. * @property string $object_name Name of the model
  48. * @property string $object_plural Plural name of the model
  49. * @property bool $loaded ORM object was loaded?
  50. * @property bool $saved ORM object was saved?
  51. * @property mixed $primary_key
  52. * @property mixed $primary_val
  53. * @property string $table_name
  54. * @property string $table_columns
  55. * @property array $has_one
  56. * @property array $belongs_to
  57. * @property array $has_many
  58. * @property array $has_many_through
  59. * @property array $load_with
  60. * @property string $updated_column
  61. * @property string $created_column
  62. */
  63. class Kohana_ORM extends Model implements serializable {
  64. /**
  65. * Stores column information for ORM models
  66. * @var array
  67. */
  68. protected static $_column_cache = array();
  69. /**
  70. * Callable database methods
  71. * @var array
  72. */
  73. protected static $_db_methods = array
  74. (
  75. 'where', 'and_where', 'or_where', 'where_open', 'and_where_open', 'or_where_open', 'where_close',
  76. 'and_where_close', 'or_where_close', 'distinct', 'select', 'from', 'join', 'on', 'group_by',
  77. 'having', 'and_having', 'or_having', 'having_open', 'and_having_open', 'or_having_open',
  78. 'having_close', 'and_having_close', 'or_having_close', 'order_by', 'limit', 'offset', 'cached',
  79. );
  80. /**
  81. * Members that have access methods
  82. * @var array
  83. */
  84. protected static $_properties = array
  85. (
  86. 'object_name', 'object_plural', 'loaded', 'saved', // Object
  87. 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
  88. 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'load_with', // Relationships
  89. 'updated_column', 'created_column',
  90. 'validation',
  91. );
  92. /**
  93. * Creates and returns a new model.
  94. *
  95. * @chainable
  96. * @param string $model Model name
  97. * @param mixed $id Parameter for find()
  98. * @return ORM
  99. */
  100. public static function factory($model, $id = NULL)
  101. {
  102. // Set class name
  103. $model = 'Model_'.ucfirst($model);
  104. return new $model($id);
  105. }
  106. /**
  107. * "Has one" relationships
  108. * @var array
  109. */
  110. protected $_has_one = array();
  111. /**
  112. * "Belongs to" relationships
  113. * @var array
  114. */
  115. protected $_belongs_to = array();
  116. /**
  117. * "Has many" relationships
  118. * @var array
  119. */
  120. protected $_has_many = array();
  121. /**
  122. * Relationships that should always be joined
  123. * @var array
  124. */
  125. protected $_load_with = array();
  126. /**
  127. * Validation object created before saving/updating
  128. * @var Validation
  129. */
  130. protected $_validation = NULL;
  131. /**
  132. * Current object
  133. * @var array
  134. */
  135. protected $_object = array();
  136. /**
  137. * @var array
  138. */
  139. protected $_changed = array();
  140. /**
  141. * @var array
  142. */
  143. protected $_related = array();
  144. /**
  145. * @var bool
  146. */
  147. protected $_valid = FALSE;
  148. /**
  149. * @var bool
  150. */
  151. protected $_loaded = FALSE;
  152. /**
  153. * @var bool
  154. */
  155. protected $_saved = FALSE;
  156. /**
  157. * @var array
  158. */
  159. protected $_sorting;
  160. /**
  161. * Foreign key suffix
  162. * @var string
  163. */
  164. protected $_foreign_key_suffix = '_id';
  165. /**
  166. * Model name
  167. * @var string
  168. */
  169. protected $_object_name;
  170. /**
  171. * Plural model name
  172. * @var string
  173. */
  174. protected $_object_plural;
  175. /**
  176. * Table name
  177. * @var string
  178. */
  179. protected $_table_name;
  180. /**
  181. * Table columns
  182. * @var array
  183. */
  184. protected $_table_columns;
  185. /**
  186. * Auto-update columns for updates
  187. * @var string
  188. */
  189. protected $_updated_column = NULL;
  190. /**
  191. * Auto-update columns for creation
  192. * @var string
  193. */
  194. protected $_created_column = NULL;
  195. /**
  196. * Table primary key
  197. * @var string
  198. */
  199. protected $_primary_key = 'id';
  200. /**
  201. * Primary key value
  202. * @var mixed
  203. */
  204. protected $_primary_key_value;
  205. /**
  206. * Model configuration, table names plural?
  207. * @var bool
  208. */
  209. protected $_table_names_plural = TRUE;
  210. /**
  211. * Model configuration, reload on wakeup?
  212. * @var bool
  213. */
  214. protected $_reload_on_wakeup = TRUE;
  215. /**
  216. * Database Object
  217. * @var Database
  218. */
  219. protected $_db = NULL;
  220. /**
  221. * Database config group
  222. * @var String
  223. */
  224. protected $_db_group = NULL;
  225. /**
  226. * Database methods applied
  227. * @var array
  228. */
  229. protected $_db_applied = array();
  230. /**
  231. * Database methods pending
  232. * @var array
  233. */
  234. protected $_db_pending = array();
  235. /**
  236. * Reset builder
  237. * @var bool
  238. */
  239. protected $_db_reset = TRUE;
  240. /**
  241. * Database query builder
  242. * @var Database_Query_Builder_Where
  243. */
  244. protected $_db_builder;
  245. /**
  246. * With calls already applied
  247. * @var array
  248. */
  249. protected $_with_applied = array();
  250. /**
  251. * Data to be loaded into the model from a database call cast
  252. * @var array
  253. */
  254. protected $_cast_data = array();
  255. /**
  256. * Constructs a new model and loads a record if given
  257. *
  258. * @param mixed $id Parameter for find or object to load
  259. * @return void
  260. */
  261. public function __construct($id = NULL)
  262. {
  263. $this->_initialize();
  264. if ($id !== NULL)
  265. {
  266. if (is_array($id))
  267. {
  268. foreach ($id as $column => $value)
  269. {
  270. // Passing an array of column => values
  271. $this->where($column, '=', $value);
  272. }
  273. $this->find();
  274. }
  275. else
  276. {
  277. // Passing the primary key
  278. $this->where($this->_table_name.'.'.$this->_primary_key, '=', $id)->find();
  279. }
  280. }
  281. elseif ( ! empty($this->_cast_data))
  282. {
  283. // Load preloaded data from a database call cast
  284. $this->_load_values($this->_cast_data);
  285. $this->_cast_data = array();
  286. }
  287. }
  288. /**
  289. * Prepares the model database connection, determines the table name,
  290. * and loads column information.
  291. *
  292. * @return void
  293. */
  294. protected function _initialize()
  295. {
  296. // Set the object name and plural name
  297. $this->_object_name = strtolower(substr(get_class($this), 6));
  298. $this->_object_plural = Inflector::plural($this->_object_name);
  299. if ( ! is_object($this->_db))
  300. {
  301. // Get database instance
  302. $this->_db = Database::instance($this->_db_group);
  303. }
  304. if (empty($this->_table_name))
  305. {
  306. // Table name is the same as the object name
  307. $this->_table_name = $this->_object_name;
  308. if ($this->_table_names_plural === TRUE)
  309. {
  310. // Make the table name plural
  311. $this->_table_name = Inflector::plural($this->_table_name);
  312. }
  313. }
  314. foreach ($this->_belongs_to as $alias => $details)
  315. {
  316. $defaults['model'] = $alias;
  317. $defaults['foreign_key'] = $alias.$this->_foreign_key_suffix;
  318. $this->_belongs_to[$alias] = array_merge($defaults, $details);
  319. }
  320. foreach ($this->_has_one as $alias => $details)
  321. {
  322. $defaults['model'] = $alias;
  323. $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix;
  324. $this->_has_one[$alias] = array_merge($defaults, $details);
  325. }
  326. foreach ($this->_has_many as $alias => $details)
  327. {
  328. $defaults['model'] = Inflector::singular($alias);
  329. $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix;
  330. $defaults['through'] = NULL;
  331. $defaults['far_key'] = Inflector::singular($alias).$this->_foreign_key_suffix;
  332. $this->_has_many[$alias] = array_merge($defaults, $details);
  333. }
  334. // Load column information
  335. $this->reload_columns();
  336. // Clear initial model state
  337. $this->clear();
  338. }
  339. /**
  340. * Initializes validation rules, and labels
  341. *
  342. * @return void
  343. */
  344. protected function _validation()
  345. {
  346. // Build the validation object with its rules
  347. $this->_validation = Validation::factory($this->_object)
  348. ->bind(':model', $this);
  349. foreach ($this->rules() as $field => $rules)
  350. {
  351. $this->_validation->rules($field, $rules);
  352. }
  353. // Use column names by default for labels
  354. $columns = array_keys($this->_table_columns);
  355. // Merge user-defined labels
  356. $labels = array_merge(array_combine($columns, $columns), $this->labels());
  357. foreach ($labels as $field => $label)
  358. {
  359. $this->_validation->label($field, $label);
  360. }
  361. }
  362. /**
  363. * Reload column definitions.
  364. *
  365. * @chainable
  366. * @param boolean $force Force reloading
  367. * @return ORM
  368. */
  369. public function reload_columns($force = FALSE)
  370. {
  371. if ($force === TRUE OR empty($this->_table_columns))
  372. {
  373. if (isset(ORM::$_column_cache[$this->_object_name]))
  374. {
  375. // Use cached column information
  376. $this->_table_columns = ORM::$_column_cache[$this->_object_name];
  377. }
  378. else
  379. {
  380. // Grab column information from database
  381. $this->_table_columns = $this->list_columns(TRUE);
  382. // Load column cache
  383. ORM::$_column_cache[$this->_object_name] = $this->_table_columns;
  384. }
  385. }
  386. return $this;
  387. }
  388. /**
  389. * Unloads the current object and clears the status.
  390. *
  391. * @chainable
  392. * @return ORM
  393. */
  394. public function clear()
  395. {
  396. // Create an array with all the columns set to NULL
  397. $values = array_combine(array_keys($this->_table_columns), array_fill(0, count($this->_table_columns), NULL));
  398. // Replace the object and reset the object status
  399. $this->_object = $this->_changed = $this->_related = array();
  400. // Replace the current object with an empty one
  401. $this->_load_values($values);
  402. // Reset primary key
  403. $this->_primary_key_value = NULL;
  404. $this->reset();
  405. return $this;
  406. }
  407. /**
  408. * Reloads the current object from the database.
  409. *
  410. * @chainable
  411. * @return ORM
  412. */
  413. public function reload()
  414. {
  415. $primary_key = $this->pk();
  416. // Replace the object and reset the object status
  417. $this->_object = $this->_changed = $this->_related = array();
  418. // Only reload the object if we have one to reload
  419. if ($this->_loaded)
  420. return $this->clear()
  421. ->where($this->_table_name.'.'.$this->_primary_key, '=', $primary_key)
  422. ->find();
  423. else
  424. return $this->clear();
  425. }
  426. /**
  427. * Checks if object data is set.
  428. *
  429. * @param string $column Column name
  430. * @return boolean
  431. */
  432. public function __isset($column)
  433. {
  434. return (isset($this->_object[$column]) OR
  435. isset($this->_related[$column]) OR
  436. isset($this->_has_one[$column]) OR
  437. isset($this->_belongs_to[$column]) OR
  438. isset($this->_has_many[$column]));
  439. }
  440. /**
  441. * Unsets object data.
  442. *
  443. * @param string $column Column name
  444. * @return void
  445. */
  446. public function __unset($column)
  447. {
  448. unset($this->_object[$column], $this->_changed[$column], $this->_related[$column]);
  449. }
  450. /**
  451. * Displays the primary key of a model when it is converted to a string.
  452. *
  453. * @return string
  454. */
  455. public function __toString()
  456. {
  457. return (string) $this->pk();
  458. }
  459. /**
  460. * Allows serialization of only the object data and state, to prevent
  461. * "stale" objects being unserialized, which also requires less memory.
  462. *
  463. * @return array
  464. */
  465. public function serialize()
  466. {
  467. // Store only information about the object
  468. foreach (array('_primary_key_value', '_object', '_changed', '_loaded', '_saved', '_sorting') as $var)
  469. {
  470. $data[$var] = $this->{$var};
  471. }
  472. return serialize($data);
  473. }
  474. /**
  475. * Prepares the database connection and reloads the object.
  476. *
  477. * @param string $data String for unserialization
  478. * @return void
  479. */
  480. public function unserialize($data)
  481. {
  482. // Initialize model
  483. $this->_initialize();
  484. foreach (unserialize($data) as $name => $var)
  485. {
  486. $this->{$name} = $var;
  487. }
  488. if ($this->_reload_on_wakeup === TRUE)
  489. {
  490. // Reload the object
  491. $this->reload();
  492. }
  493. }
  494. /**
  495. * Handles pass-through to database methods. Calls to query methods
  496. * (query, get, insert, update) are not allowed. Query builder methods
  497. * are chainable.
  498. *
  499. * @param string $method Method name
  500. * @param array $args Method arguments
  501. * @return mixed
  502. */
  503. public function __call($method, array $args)
  504. {
  505. if (in_array($method, ORM::$_properties))
  506. {
  507. if ($method === 'validation')
  508. {
  509. if ( ! isset($this->_validation))
  510. {
  511. // Initialize the validation object
  512. $this->_validation();
  513. }
  514. }
  515. // Return the property
  516. return $this->{'_'.$method};
  517. }
  518. elseif (in_array($method, ORM::$_db_methods))
  519. {
  520. // Add pending database call which is executed after query type is determined
  521. $this->_db_pending[] = array('name' => $method, 'args' => $args);
  522. return $this;
  523. }
  524. else
  525. {
  526. throw new Kohana_Exception('Invalid method :method called in :class',
  527. array(':method' => $method, ':class' => get_class($this)));
  528. }
  529. }
  530. /**
  531. * Handles retrieval of all model values, relationships, and metadata.
  532. *
  533. * @param string $column Column name
  534. * @return mixed
  535. */
  536. public function __get($column)
  537. {
  538. if (array_key_exists($column, $this->_object))
  539. {
  540. return $this->_object[$column];
  541. }
  542. elseif (isset($this->_related[$column]))
  543. {
  544. // Return related model that has already been fetched
  545. return $this->_related[$column];
  546. }
  547. elseif (isset($this->_belongs_to[$column]))
  548. {
  549. $model = $this->_related($column);
  550. // Use this model's column and foreign model's primary key
  551. $col = $model->_table_name.'.'.$model->_primary_key;
  552. $val = $this->_object[$this->_belongs_to[$column]['foreign_key']];
  553. $model->where($col, '=', $val)->find();
  554. return $this->_related[$column] = $model;
  555. }
  556. elseif (isset($this->_has_one[$column]))
  557. {
  558. $model = $this->_related($column);
  559. // Use this model's primary key value and foreign model's column
  560. $col = $model->_table_name.'.'.$this->_has_one[$column]['foreign_key'];
  561. $val = $this->pk();
  562. $model->where($col, '=', $val)->find();
  563. return $this->_related[$column] = $model;
  564. }
  565. elseif (isset($this->_has_many[$column]))
  566. {
  567. $model = ORM::factory($this->_has_many[$column]['model']);
  568. if (isset($this->_has_many[$column]['through']))
  569. {
  570. // Grab has_many "through" relationship table
  571. $through = $this->_has_many[$column]['through'];
  572. // Join on through model's target foreign key (far_key) and target model's primary key
  573. $join_col1 = $through.'.'.$this->_has_many[$column]['far_key'];
  574. $join_col2 = $model->_table_name.'.'.$model->_primary_key;
  575. $model->join($through)->on($join_col1, '=', $join_col2);
  576. // Through table's source foreign key (foreign_key) should be this model's primary key
  577. $col = $through.'.'.$this->_has_many[$column]['foreign_key'];
  578. $val = $this->pk();
  579. }
  580. else
  581. {
  582. // Simple has_many relationship, search where target model's foreign key is this model's primary key
  583. $col = $model->_table_name.'.'.$this->_has_many[$column]['foreign_key'];
  584. $val = $this->pk();
  585. }
  586. return $model->where($col, '=', $val);
  587. }
  588. else
  589. {
  590. throw new Kohana_Exception('The :property property does not exist in the :class class',
  591. array(':property' => $column, ':class' => get_class($this)));
  592. }
  593. }
  594. /**
  595. * Base set method - this should not be overridden.
  596. *
  597. * @param string $column Column name
  598. * @param mixed $value Column value
  599. * @return void
  600. */
  601. public function __set($column, $value)
  602. {
  603. if ( ! isset($this->_object_name))
  604. {
  605. // Object not yet constructed, so we're loading data from a database call cast
  606. $this->_cast_data[$column] = $value;
  607. }
  608. else
  609. {
  610. // Set the model's column to given value
  611. $this->set($column, $value);
  612. }
  613. }
  614. /**
  615. * Handles setting of column
  616. *
  617. * @param string $column Column name
  618. * @param mixed $value Column value
  619. * @return void
  620. */
  621. public function set($column, $value)
  622. {
  623. if (array_key_exists($column, $this->_object))
  624. {
  625. // Filter the data
  626. $value = $this->run_filter($column, $value);
  627. // See if the data really changed
  628. if ($value !== $this->_object[$column])
  629. {
  630. $this->_object[$column] = $value;
  631. // Data has changed
  632. $this->_changed[$column] = $column;
  633. // Object is no longer saved or valid
  634. $this->_saved = $this->_valid = FALSE;
  635. }
  636. }
  637. elseif (isset($this->_belongs_to[$column]))
  638. {
  639. // Update related object itself
  640. $this->_related[$column] = $value;
  641. // Update the foreign key of this model
  642. $this->_object[$this->_belongs_to[$column]['foreign_key']] = $value->pk();
  643. $this->_changed[$column] = $this->_belongs_to[$column]['foreign_key'];
  644. }
  645. else
  646. {
  647. throw new Kohana_Exception('The :property: property does not exist in the :class: class',
  648. array(':property:' => $column, ':class:' => get_class($this)));
  649. }
  650. return $this;
  651. }
  652. /**
  653. * Set values from an array with support for one-one relationships. This method should be used
  654. * for loading in post data, etc.
  655. *
  656. * @param array $values Array of column => val
  657. * @param array $expected Array of keys to take from $values
  658. * @return ORM
  659. */
  660. public function values(array $values, array $expected = NULL)
  661. {
  662. // Default to expecting everything except the primary key
  663. if ($expected === NULL)
  664. {
  665. $expected = array_keys($this->_table_columns);
  666. // Don't set the primary key by default
  667. unset($values[$this->_primary_key]);
  668. }
  669. foreach ($expected as $key => $column)
  670. {
  671. if (is_string($key))
  672. {
  673. // isset() fails when the value is NULL (we want it to pass)
  674. if ( ! array_key_exists($key, $values))
  675. continue;
  676. // Try to set values to a related model
  677. $this->{$key}->values($values[$key], $column);
  678. }
  679. else
  680. {
  681. // isset() fails when the value is NULL (we want it to pass)
  682. if ( ! array_key_exists($column, $values))
  683. continue;
  684. // Update the column, respects __set()
  685. $this->$column = $values[$column];
  686. }
  687. }
  688. return $this;
  689. }
  690. /**
  691. * Returns the values of this object as an array, including any related one-one
  692. * models that have already been loaded using with()
  693. *
  694. * @return array
  695. */
  696. public function as_array()
  697. {
  698. $object = array();
  699. foreach ($this->_object as $column => $value)
  700. {
  701. // Call __get for any user processing
  702. $object[$column] = $this->__get($column);
  703. }
  704. foreach ($this->_related as $column => $model)
  705. {
  706. // Include any related objects that are already loaded
  707. $object[$column] = $model->as_array();
  708. }
  709. return $object;
  710. }
  711. /**
  712. * Binds another one-to-one object to this model. One-to-one objects
  713. * can be nested using 'object1:object2' syntax
  714. *
  715. * @param string $target_path Target model to bind to
  716. * @return void
  717. */
  718. public function with($target_path)
  719. {
  720. if (isset($this->_with_applied[$target_path]))
  721. {
  722. // Don't join anything already joined
  723. return $this;
  724. }
  725. // Split object parts
  726. $aliases = explode(':', $target_path);
  727. $target = $this;
  728. foreach ($aliases as $alias)
  729. {
  730. // Go down the line of objects to find the given target
  731. $parent = $target;
  732. $target = $parent->_related($alias);
  733. if ( ! $target)
  734. {
  735. // Can't find related object
  736. return $this;
  737. }
  738. }
  739. // Target alias is at the end
  740. $target_alias = $alias;
  741. // Pop-off top alias to get the parent path (user:photo:tag becomes user:photo - the parent table prefix)
  742. array_pop($aliases);
  743. $parent_path = implode(':', $aliases);
  744. if (empty($parent_path))
  745. {
  746. // Use this table name itself for the parent path
  747. $parent_path = $this->_table_name;
  748. }
  749. else
  750. {
  751. if ( ! isset($this->_with_applied[$parent_path]))
  752. {
  753. // If the parent path hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
  754. $this->with($parent_path);
  755. }
  756. }
  757. // Add to with_applied to prevent duplicate joins
  758. $this->_with_applied[$target_path] = TRUE;
  759. // Use the keys of the empty object to determine the columns
  760. foreach (array_keys($target->_object) as $column)
  761. {
  762. $name = $target_path.'.'.$column;
  763. $alias = $target_path.':'.$column;
  764. // Add the prefix so that load_result can determine the relationship
  765. $this->select(array($name, $alias));
  766. }
  767. if (isset($parent->_belongs_to[$target_alias]))
  768. {
  769. // Parent belongs_to target, use target's primary key and parent's foreign key
  770. $join_col1 = $target_path.'.'.$target->_primary_key;
  771. $join_col2 = $parent_path.'.'.$parent->_belongs_to[$target_alias]['foreign_key'];
  772. }
  773. else
  774. {
  775. // Parent has_one target, use parent's primary key as target's foreign key
  776. $join_col1 = $parent_path.'.'.$parent->_primary_key;
  777. $join_col2 = $target_path.'.'.$parent->_has_one[$target_alias]['foreign_key'];
  778. }
  779. // Join the related object into the result
  780. $this->join(array($target->_table_name, $target_path), 'LEFT')->on($join_col1, '=', $join_col2);
  781. return $this;
  782. }
  783. /**
  784. * Initializes the Database Builder to given query type
  785. *
  786. * @param integer $type Type of Database query
  787. * @return ORM
  788. */
  789. protected function _build($type)
  790. {
  791. // Construct new builder object based on query type
  792. switch ($type)
  793. {
  794. case Database::SELECT:
  795. $this->_db_builder = DB::select();
  796. break;
  797. case Database::UPDATE:
  798. $this->_db_builder = DB::update($this->_table_name);
  799. break;
  800. case Database::DELETE:
  801. $this->_db_builder = DB::delete($this->_table_name);
  802. }
  803. // Process pending database method calls
  804. foreach ($this->_db_pending as $method)
  805. {
  806. $name = $method['name'];
  807. $args = $method['args'];
  808. $this->_db_applied[$name] = $name;
  809. call_user_func_array(array($this->_db_builder, $name), $args);
  810. }
  811. return $this;
  812. }
  813. /**
  814. * Finds and loads a single database row into the object.
  815. *
  816. * @chainable
  817. * @return ORM
  818. */
  819. public function find()
  820. {
  821. if ($this->_loaded)
  822. throw new Kohana_Exception('Method find() cannot be called on loaded objects');
  823. if ( ! empty($this->_load_with))
  824. {
  825. foreach ($this->_load_with as $alias)
  826. {
  827. // Bind auto relationships
  828. $this->with($alias);
  829. }
  830. }
  831. $this->_build(Database::SELECT);
  832. return $this->_load_result(FALSE);
  833. }
  834. /**
  835. * Finds multiple database rows and returns an iterator of the rows found.
  836. *
  837. * @return Database_Result
  838. */
  839. public function find_all()
  840. {
  841. if ($this->_loaded)
  842. throw new Kohana_Exception('Method find_all() cannot be called on loaded objects');
  843. if ( ! empty($this->_load_with))
  844. {
  845. foreach ($this->_load_with as $alias)
  846. {
  847. // Bind auto relationships
  848. $this->with($alias);
  849. }
  850. }
  851. $this->_build(Database::SELECT);
  852. return $this->_load_result(TRUE);
  853. }
  854. /**
  855. * Loads a database result, either as a new record for this model, or as
  856. * an iterator for multiple rows.
  857. *
  858. * @chainable
  859. * @param bool $multiple Return an iterator or load a single row
  860. * @return ORM|Database_Result
  861. */
  862. protected function _load_result($multiple = FALSE)
  863. {
  864. $this->_db_builder->from($this->_table_name);
  865. if ($multiple === FALSE)
  866. {
  867. // Only fetch 1 record
  868. $this->_db_builder->limit(1);
  869. }
  870. // Select all columns by default
  871. $this->_db_builder->select($this->_table_name.'.*');
  872. if ( ! isset($this->_db_applied['order_by']) AND ! empty($this->_sorting))
  873. {
  874. foreach ($this->_sorting as $column => $direction)
  875. {
  876. if (strpos($column, '.') === FALSE)
  877. {
  878. // Sorting column for use in JOINs
  879. $column = $this->_table_name.'.'.$column;
  880. }
  881. $this->_db_builder->order_by($column, $direction);
  882. }
  883. }
  884. if ($multiple === TRUE)
  885. {
  886. // Return database iterator casting to this object type
  887. $result = $this->_db_builder->as_object(get_class($this))->execute($this->_db);
  888. $this->reset();
  889. return $result;
  890. }
  891. else
  892. {
  893. // Load the result as an associative array
  894. $result = $this->_db_builder->as_assoc()->execute($this->_db);
  895. $this->reset();
  896. if ($result->count() === 1)
  897. {
  898. // Load object values
  899. $this->_load_values($result->current());
  900. }
  901. else
  902. {
  903. // Clear the object, nothing was found
  904. $this->clear();
  905. }
  906. return $this;
  907. }
  908. }
  909. /**
  910. * Loads an array of values into into the current object.
  911. *
  912. * @chainable
  913. * @param array $values Values to load
  914. * @return ORM
  915. */
  916. protected function _load_values(array $values)
  917. {
  918. if (array_key_exists($this->_primary_key, $values))
  919. {
  920. if ($values[$this->_primary_key] !== NULL)
  921. {
  922. // Flag as loaded, saved, and valid
  923. $this->_loaded = $this->_saved = $this->_valid = TRUE;
  924. // Store primary key
  925. $this->_primary_key_value = $values[$this->_primary_key];
  926. }
  927. else
  928. {
  929. // Not loaded, saved, or valid
  930. $this->_loaded = $this->_saved = $this->_valid = FALSE;
  931. }
  932. }
  933. // Related objects
  934. $related = array();
  935. foreach ($values as $column => $value)
  936. {
  937. if (strpos($column, ':') === FALSE)
  938. {
  939. // Load the value to this model
  940. $this->_object[$column] = $value;
  941. }
  942. else
  943. {
  944. // Column belongs to a related model
  945. list ($prefix, $column) = explode(':', $column, 2);
  946. $related[$prefix][$column] = $value;
  947. }
  948. }
  949. if ( ! empty($related))
  950. {
  951. foreach ($related as $object => $values)
  952. {
  953. // Load the related objects with the values in the result
  954. $this->_related($object)->_load_values($values);
  955. }
  956. }
  957. return $this;
  958. }
  959. /**
  960. * Rule definitions for validation
  961. *
  962. * @return array
  963. */
  964. public function rules()
  965. {
  966. return array();
  967. }
  968. /**
  969. * Filters a value for a specific column
  970. *
  971. * @param string $field The column name
  972. * @param string $value The value to filter
  973. * @return string
  974. */
  975. protected function run_filter($field, $value)
  976. {
  977. $filters = $this->filters();
  978. // Get the filters for this column
  979. $wildcards = empty($filters[TRUE]) ? array() : $filters[TRUE];
  980. // Merge in the wildcards
  981. $filters = empty($filters[$field]) ? $wildcards : array_merge($wildcards, $filters[$field]);
  982. // Bind the field name and model so they can be used in the filter method
  983. $_bound = array
  984. (
  985. ':field' => $field,
  986. ':model' => $this,
  987. );
  988. foreach ($filters as $array)
  989. {
  990. // Value needs to be bound inside the loop so we are always using the
  991. // version that was modified by the filters that already ran
  992. $_bound[':value'] = $value;
  993. // Filters are defined as array($filter, $params)
  994. $filter = $array[0];
  995. $params = Arr::get($array, 1, array(':value'));
  996. foreach ($params as $key => $param)
  997. {
  998. if (is_string($param) AND array_key_exists($param, $_bound))
  999. {
  1000. // Replace with bound value
  1001. $params[$key] = $_bound[$param];
  1002. }
  1003. }
  1004. if (is_array($filter) OR ! is_string($filter))
  1005. {
  1006. // This is either a callback as an array or a lambda
  1007. $value = call_user_func_array($filter, $params);
  1008. }
  1009. elseif (strpos($filter, '::') === FALSE)
  1010. {
  1011. // Use a function call
  1012. $function = new ReflectionFunction($filter);
  1013. // Call $function($this[$field], $param, ...) with Reflection
  1014. $value = $function->invokeArgs($params);
  1015. }
  1016. else
  1017. {
  1018. // Split the class and method of the rule
  1019. list($class, $method) = explode('::', $filter, 2);
  1020. // Use a static method call
  1021. $method = new ReflectionMethod($class, $method);
  1022. // Call $Class::$method($this[$field], $param, ...) with Reflection
  1023. $value = $method->invokeArgs(NULL, $params);
  1024. }
  1025. }
  1026. return $value;
  1027. }
  1028. /**
  1029. * Filter definitions for validation
  1030. *
  1031. * @return array
  1032. */
  1033. public function filters()
  1034. {
  1035. return array();
  1036. }
  1037. /**
  1038. * Label definitions for validation
  1039. *
  1040. * @return array
  1041. */
  1042. public function labels()
  1043. {
  1044. return array();
  1045. }
  1046. /**
  1047. * Validates the current model's data
  1048. *
  1049. * @param Validation $extra_validation Validation object
  1050. * @return ORM
  1051. */
  1052. public function check(Validation $extra_validation = NULL)
  1053. {
  1054. // Determine if any external validation failed
  1055. $extra_errors = ($extra_validation AND ! $extra_validation->check());
  1056. // Always build a new validation object
  1057. $this->_validation();
  1058. $array = $this->_validation;
  1059. if (($this->_valid = $array->check()) === FALSE OR $extra_errors)
  1060. {
  1061. $exception = new ORM_Validation_Exception($this->_object_name, $array);
  1062. if ($extra_errors)
  1063. {
  1064. // Merge any possible errors from the external object
  1065. $exception->add_object('_external', $extra_validation);
  1066. }
  1067. throw $exception;
  1068. }
  1069. return $this;
  1070. }
  1071. /**
  1072. * Insert a new object to the database
  1073. * @param Validation $validation Validation object
  1074. * @return ORM
  1075. */
  1076. public function create(Validation $validation = NULL)
  1077. {
  1078. if ($this->_loaded)
  1079. throw new Kohana_Exception('Cannot create :model model because it is already loaded.', array(':model' => $this->_object_name));
  1080. // Require model validation before saving
  1081. if ( ! $this->_valid)
  1082. {
  1083. $this->check($validation);
  1084. }
  1085. $data = array();
  1086. foreach ($this->_changed as $column)
  1087. {
  1088. // Generate list of column => values
  1089. $data[$column] = $this->_object[$column];
  1090. }
  1091. if (is_array($this->_created_column))
  1092. {
  1093. // Fill the created column
  1094. $column = $this->_created_column['column'];
  1095. $format = $this->_created_column['format'];
  1096. $data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
  1097. }
  1098. $result = DB::insert($this->_table_name)
  1099. ->columns(array_keys($data))
  1100. ->values(array_values($data))
  1101. ->execute($this->_db);
  1102. if ( ! array_key_exists($this->_primary_key, $data))
  1103. {
  1104. // Load the insert id as the primary key if it was left out
  1105. $this->_object[$this->_primary_key] = $this->_primary_key_value = $result[0];
  1106. }
  1107. // Object is now loaded and saved
  1108. $this->_loaded = $this->_saved = TRUE;
  1109. // All changes have been saved
  1110. $this->_changed = array();
  1111. return $this;
  1112. }
  1113. /**
  1114. * Updates a single record or multiple records
  1115. *
  1116. * @chainable
  1117. * @param Validation $validation Validation object
  1118. * @return ORM
  1119. */
  1120. public function update(Validation $validation = NULL)
  1121. {
  1122. if ( ! $this->_loaded)
  1123. throw new Kohana_Exception('Cannot update :model model because it is not loaded.', array(':model' => $this->_object_name));
  1124. if (empty($this->_changed))
  1125. {
  1126. // Nothing to update
  1127. return $this;
  1128. }
  1129. // Require model validation before saving
  1130. if ( ! $this->_valid)
  1131. {
  1132. $this->check($validation);
  1133. }
  1134. $data = array();
  1135. foreach ($this->_changed as $column)
  1136. {
  1137. // Compile changed data
  1138. $data[$column] = $this->_object[$column];
  1139. }
  1140. if (is_array($this->_updated_column))
  1141. {
  1142. // Fill the updated column
  1143. $column = $this->_updated_column['column'];
  1144. $format = $this->_updated_column['format'];
  1145. $data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
  1146. }
  1147. // Use primary key value
  1148. $id = $this->pk();
  1149. // Update a single record
  1150. DB::update($this->_table_name)
  1151. ->set($data)
  1152. ->where($this->_primary_key, '=', $id)
  1153. ->execute($this->_db);
  1154. if (isset($data[$this->_primary_key]))
  1155. {
  1156. // Primary key was changed, reflect it
  1157. $this->_primary_key_value = $data[$this->_primary_key];
  1158. }
  1159. // Object has been saved
  1160. $this->_saved = TRUE;
  1161. // All changes have been saved
  1162. $this->_changed = array();
  1163. return $this;
  1164. }
  1165. /**
  1166. * Updates or Creates the record depending on loaded()
  1167. *
  1168. * @chainable
  1169. * @param Validation $validation Validation object
  1170. * @return ORM
  1171. */
  1172. public function save(Validation $validation = NULL)
  1173. {
  1174. return $this->loaded() ? $this->update($validation) : $this->create($validation);
  1175. }
  1176. /**
  1177. * Deletes a single record or multiple records, ignoring relationships.
  1178. *
  1179. * @chainable
  1180. * @return ORM
  1181. */
  1182. public function delete()
  1183. {
  1184. if ( ! $this->_loaded)
  1185. throw new Kohana_Exception('Cannot delete :model model because it is not loaded.', array(':model' => $this->_object_name));
  1186. // Use primary key value
  1187. $id = $this->pk();
  1188. // Delete the object
  1189. DB::delete($this->_table_name)
  1190. ->where($this->_primary_key, '=', $id)
  1191. ->execute($this->_db);
  1192. return $this->clear();
  1193. }
  1194. /**
  1195. * Tests if this object has a relationship to a different model,
  1196. * or an array of different models.
  1197. *
  1198. * // Check if $model has the login role
  1199. * $model->has('roles', ORM::factory('role', array('name' => 'login')));
  1200. * // Check for the login role if you know the roles.id is 5
  1201. * $model->has('roles', 5);
  1202. * // Check for all of the following roles
  1203. * $model->has('roles', array(1, 2, 3, 4));
  1204. * @param string $alias Alias of the has_many "through" relationship
  1205. * @param mixed $far_keys Related model, primary key, or an array of primary keys
  1206. * @return Database_Result
  1207. */
  1208. public function has($alias, $far_keys)
  1209. {
  1210. $far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys;
  1211. // We need an array to simplify the logic
  1212. $far_keys = (array) $far_keys;
  1213. // Nothing to check if the model isn't loaded or we don't have any far_keys
  1214. if ( ! $far_keys OR ! $this->_loaded)
  1215. return FALSE;
  1216. $count = (int) DB::select(array('COUNT("*")', 'records_found'))
  1217. ->from($this->_has_many[$alias]['through'])
  1218. ->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk())
  1219. ->where($this->_has_many[$alias]['far_key'], 'IN', $far_keys)
  1220. ->execute($this->_db)->get('records_found');
  1221. // Rows found need to match the rows searched
  1222. return $count === count($far_keys);
  1223. }
  1224. /**
  1225. * Adds a new relationship to between this model and another.
  1226. *
  1227. * // Add the login role using a model instance
  1228. * $model->add('roles', ORM::factory('role', array('name' => 'login')));
  1229. * // Add the login role if you know the roles.id is 5
  1230. * $model->add('roles', 5);
  1231. * // Add multiple roles (for example, from checkboxes on a form)
  1232. * $model->add('roles', array(1, 2, 3, 4));
  1233. *
  1234. * @param string $alias Alias of the has_many "through" relationship
  1235. * @param mixed $far_keys Related model, primary key, or an array of primary keys
  1236. * @return ORM
  1237. */
  1238. public function add($alias, $far_keys)
  1239. {
  1240. $far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys;
  1241. $columns = array($this->_has_many[$alias]['foreign_key'], $this->_has_many[$alias]['far_key']);
  1242. $foreign_key = $this->pk();
  1243. $query = DB::insert($this->_has_many[$alias]['through'], $columns);
  1244. foreach ( (array) $far_keys as $key)
  1245. {
  1246. $query->values(array($foreign_key, $key));
  1247. }
  1248. $query->execute($this->_db);
  1249. return $this;
  1250. }
  1251. /**
  1252. * Removes a relationship between this model and another.
  1253. *
  1254. * // Remove a role using a model instance
  1255. * $model->remove('roles', ORM::factory('role', array('name' => 'login')));
  1256. * // Remove the role knowing the primary key
  1257. * $model->remove('roles', 5);
  1258. * // Remove multiple roles (for example, from checkboxes on a form)
  1259. * $model->remove('roles', array(1, 2, 3, 4));
  1260. * // Remove all related roles
  1261. * $model->remove('roles');
  1262. *
  1263. * @param string $alias Alias of the has_many "through" relationship
  1264. * @param mixed $far_keys Related model, primary key, or an array of primary keys
  1265. * @return ORM
  1266. */
  1267. public function remove($alias, $far_keys = NULL)
  1268. {
  1269. $far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys;
  1270. $query = DB::delete($this->_has_many[$alias]['through'])
  1271. ->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk());
  1272. if ($far_keys !== NULL)
  1273. {
  1274. // Remove all the relationships in the array
  1275. $query->where($this->_has_many[$alias]['far_key'], 'IN', (array) $far_keys);
  1276. }
  1277. $query->execute($this->_db);
  1278. return $this;
  1279. }
  1280. /**
  1281. * Count the number of records in the table.
  1282. *
  1283. * @return integer
  1284. */
  1285. public function count_all()
  1286. {
  1287. $selects = array();
  1288. foreach ($this->_db_pending as $key => $method)
  1289. {
  1290. if ($method['name'] == 'select')
  1291. {
  1292. // Ignore any selected columns for now
  1293. $selects[] = $method;
  1294. unset($this->_db_pending[$key]);
  1295. }
  1296. }
  1297. if ( ! empty($this->_load_with))
  1298. {
  1299. foreach ($this->_load_with as $alias)
  1300. {
  1301. // Bind relationship
  1302. $this->with($alias);
  1303. }
  1304. }
  1305. $this->_build(Database::SELECT);
  1306. $records = $this->_db_builder->from($this->_table_name)
  1307. ->select(array('COUNT("*")', 'records_found'))
  1308. ->execute($this->_db)
  1309. ->get('records_found');
  1310. // Add back in selected columns
  1311. $this->_db_pending += $selects;
  1312. $this->reset();
  1313. // Return the total number of records in a table
  1314. return $records;
  1315. }
  1316. /**
  1317. * Proxy method to Database list_columns.
  1318. *
  1319. * @return array
  1320. */
  1321. public function list_columns()
  1322. {
  1323. // Proxy to database
  1324. return $this->_db->list_columns($this->_table_name);
  1325. }
  1326. /**
  1327. * Proxy method to Database field_data.
  1328. *
  1329. * @chainable
  1330. * @param string $sql SQL query to clear
  1331. * @return ORM
  1332. */
  1333. public function clear_cache($sql = NULL)
  1334. {
  1335. // Proxy to database
  1336. $this->_db->clear_cache($sql);
  1337. ORM::$_column_cache = array();
  1338. return $this;
  1339. }
  1340. /**
  1341. * Returns an ORM model for the given one-one related alias
  1342. *
  1343. * @param string $alias Alias name
  1344. * @return ORM
  1345. */
  1346. protected function _related($alias)
  1347. {
  1348. if (isset($this->_related[$alias]))
  1349. {
  1350. return $this->_related[$alias];
  1351. }
  1352. elseif (isset($this->_has_one[$alias]))
  1353. {
  1354. return $this->_related[$alias] = ORM::factory($this->_has_one[$alias]['model']);
  1355. }
  1356. elseif (isset($this->_belongs_to[$alias]))
  1357. {
  1358. return $this->_related[$alias] = ORM::factory($this->_belongs_to[$alias]['model']);
  1359. }
  1360. else
  1361. {
  1362. return FALSE;
  1363. }
  1364. }
  1365. /**
  1366. * Returns the value of the primary key
  1367. *
  1368. * @return mixed Primary key
  1369. */
  1370. public function pk()
  1371. {
  1372. return $this->_primary_key_value;
  1373. }
  1374. /**
  1375. * Returns last executed query
  1376. *
  1377. * @return string
  1378. */
  1379. public function last_query()
  1380. {
  1381. return $this->_db->last_query;
  1382. }
  1383. /**
  1384. * Clears query builder. Passing FALSE is useful to keep the existing
  1385. * query conditions for another query.
  1386. *
  1387. * @param bool $next Pass FALSE to avoid resetting on the next call
  1388. * @return ORM
  1389. */
  1390. public function reset($next = TRUE)
  1391. {
  1392. if ($next AND $this->_db_reset)
  1393. {
  1394. $this->_db_pending = array();
  1395. $this->_db_applied = array();
  1396. $this->_db_builder = NULL;
  1397. $this->_with_applied = array();
  1398. }
  1399. // Reset on the next call?
  1400. $this->_db_reset = $next;
  1401. return $this;
  1402. }
  1403. } // End ORM