PageRenderTime 67ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/alvinpd/monsterninja
PHP | 1361 lines | 782 code | 191 blank | 388 comment | 59 complexity | 4f7aae00fd1001702b8b6b4ab09af16c MD5 | raw file
  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. * $Id: ORM.php 4427 2009-06-19 23:31:36Z jheathco $
  12. *
  13. * @package ORM
  14. * @author Kohana Team
  15. * @copyright (c) 2007-2009 Kohana Team
  16. * @license http://kohanaphp.com/license.html
  17. */
  18. class Kohana_ORM {
  19. // Current relationships
  20. protected $_has_one = array();
  21. protected $_belongs_to = array();
  22. protected $_has_many = array();
  23. // Relationships that should always be joined
  24. protected $_load_with = array();
  25. // Validation members
  26. protected $_validate = NULL;
  27. protected $_rules = array();
  28. protected $_callbacks = array();
  29. protected $_filters = array();
  30. protected $_labels = array();
  31. // Current object
  32. protected $_object = array();
  33. protected $_changed = array();
  34. protected $_related = array();
  35. protected $_loaded = FALSE;
  36. protected $_saved = FALSE;
  37. protected $_sorting;
  38. // Foreign key suffix
  39. protected $_foreign_key_suffix = '_id';
  40. // Model table information
  41. protected $_object_name;
  42. protected $_object_plural;
  43. protected $_table_name;
  44. protected $_table_columns;
  45. protected $_ignored_columns = array();
  46. // Auto-update columns for creation and updates
  47. protected $_updated_column = NULL;
  48. protected $_created_column = NULL;
  49. // Table primary key and value
  50. protected $_primary_key = 'id';
  51. protected $_primary_val = 'name';
  52. // Model configuration
  53. protected $_table_names_plural = TRUE;
  54. protected $_reload_on_wakeup = TRUE;
  55. // Database configuration
  56. protected $_db = 'default';
  57. protected $_db_applied = array();
  58. protected $_db_pending = array();
  59. protected $_db_reset = TRUE;
  60. protected $_db_builder;
  61. // With calls already applied
  62. protected $_with_applied = array();
  63. // Data to be loaded into the model from a database call cast
  64. protected $_preload_data = array();
  65. // Stores column information for ORM models
  66. protected static $_column_cache = array();
  67. // Callable database methods
  68. protected static $_db_methods = array
  69. (
  70. 'where', 'and_where', 'or_where', 'where_open', 'and_where_open', 'or_where_open', 'where_close',
  71. 'and_where_close', 'or_where_close', 'distinct', 'select', 'from', 'join', 'on', 'group_by',
  72. 'having', 'and_having', 'or_having', 'having_open', 'and_having_open', 'or_having_open',
  73. 'having_close', 'and_having_close', 'or_having_close', 'order_by', 'limit', 'offset', 'cached'
  74. );
  75. // Members that have access methods
  76. protected static $_properties = array
  77. (
  78. 'object_name', 'object_plural', 'loaded', 'saved', // Object
  79. 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
  80. 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'load_with', // Relationships
  81. 'validate', 'rules', 'callbacks', 'filters', 'labels' // Validation
  82. );
  83. /**
  84. * Creates and returns a new model.
  85. *
  86. * @chainable
  87. * @param string model name
  88. * @param mixed parameter for find()
  89. * @return ORM
  90. */
  91. public static function factory($model, $id = NULL)
  92. {
  93. // Set class name
  94. $model = 'Model_'.ucfirst($model);
  95. return new $model($id);
  96. }
  97. /**
  98. * Prepares the model database connection and loads the object.
  99. *
  100. * @param mixed parameter for find or object to load
  101. * @return void
  102. */
  103. public function __construct($id = NULL)
  104. {
  105. // Set the object name and plural name
  106. $this->_object_name = strtolower(substr(get_class($this), 6));
  107. $this->_object_plural = Inflector::plural($this->_object_name);
  108. if ( ! isset($this->_sorting))
  109. {
  110. // Default sorting
  111. $this->_sorting = array($this->_primary_key => 'ASC');
  112. }
  113. if ( ! empty($this->_ignored_columns))
  114. {
  115. // Optimize for performance
  116. $this->_ignored_columns = array_combine($this->_ignored_columns, $this->_ignored_columns);
  117. }
  118. // Initialize database
  119. $this->_initialize();
  120. // Clear the object
  121. $this->clear();
  122. if ($id !== NULL)
  123. {
  124. if (is_array($id))
  125. {
  126. foreach ($id as $column => $value)
  127. {
  128. // Passing an array of column => values
  129. $this->where($column, '=', $value);
  130. }
  131. $this->find();
  132. }
  133. else
  134. {
  135. // Passing the primary key
  136. // Set the object's primary key, but don't load it until needed
  137. $this->_object[$this->_primary_key] = $id;
  138. // Object is considered saved until something is set
  139. $this->_saved = TRUE;
  140. }
  141. }
  142. elseif ( ! empty($this->_preload_data))
  143. {
  144. // Load preloaded data from a database call cast
  145. $this->_load_values($this->_preload_data);
  146. $this->_preload_data = array();
  147. }
  148. }
  149. /**
  150. * Checks if object data is set.
  151. *
  152. * @param string column name
  153. * @return boolean
  154. */
  155. public function __isset($column)
  156. {
  157. $this->_load();
  158. return
  159. (
  160. isset($this->_object[$column]) OR
  161. isset($this->_related[$column]) OR
  162. isset($this->_has_one[$column]) OR
  163. isset($this->_belongs_to[$column]) OR
  164. isset($this->_has_many[$column])
  165. );
  166. }
  167. /**
  168. * Unsets object data.
  169. *
  170. * @param string column name
  171. * @return void
  172. */
  173. public function __unset($column)
  174. {
  175. $this->_load();
  176. unset($this->_object[$column], $this->_changed[$column], $this->_related[$column]);
  177. }
  178. /**
  179. * Displays the primary key of a model when it is converted to a string.
  180. *
  181. * @return string
  182. */
  183. public function __toString()
  184. {
  185. return (string) $this->pk();
  186. }
  187. /**
  188. * Allows serialization of only the object data and state, to prevent
  189. * "stale" objects being unserialized, which also requires less memory.
  190. *
  191. * @return array
  192. */
  193. public function __sleep()
  194. {
  195. // Store only information about the object
  196. return array('_object_name', '_object', '_changed', '_loaded', '_saved', '_sorting');
  197. }
  198. /**
  199. * Prepares the database connection and reloads the object.
  200. *
  201. * @return void
  202. */
  203. public function __wakeup()
  204. {
  205. // Initialize database
  206. $this->_initialize();
  207. if ($this->_reload_on_wakeup === TRUE)
  208. {
  209. // Reload the object
  210. $this->reload();
  211. }
  212. }
  213. /**
  214. * Handles pass-through to database methods. Calls to query methods
  215. * (query, get, insert, update) are not allowed. Query builder methods
  216. * are chainable.
  217. *
  218. * @param string method name
  219. * @param array method arguments
  220. * @return mixed
  221. */
  222. public function __call($method, array $args)
  223. {
  224. if (in_array($method, ORM::$_properties))
  225. {
  226. if ($method === 'loaded')
  227. {
  228. if ( ! isset($this->_object_name))
  229. {
  230. // Calling loaded method prior to the object being fully initialized
  231. return FALSE;
  232. }
  233. $this->_load();
  234. }
  235. elseif ($method === 'validate')
  236. {
  237. if ( ! isset($this->_validate))
  238. {
  239. // Initialize the validation object
  240. $this->_validate();
  241. }
  242. }
  243. // Return the property
  244. return $this->{'_'.$method};
  245. }
  246. elseif (in_array($method, ORM::$_db_methods))
  247. {
  248. // Add pending database call which is executed after query type is determined
  249. $this->_db_pending[] = array('name' => $method, 'args' => $args);
  250. return $this;
  251. }
  252. else
  253. {
  254. throw new Kohana_Exception('Invalid method :method called in :class',
  255. array(':method' => $method, ':class' => get_class($this)));
  256. }
  257. }
  258. /**
  259. * Handles retrieval of all model values, relationships, and metadata.
  260. *
  261. * @param string column name
  262. * @return mixed
  263. */
  264. public function __get($column)
  265. {
  266. if (array_key_exists($column, $this->_object))
  267. {
  268. $this->_load();
  269. return $this->_object[$column];
  270. }
  271. elseif (isset($this->_related[$column]) AND $this->_related[$column]->_loaded)
  272. {
  273. // Return related model that has already been loaded
  274. return $this->_related[$column];
  275. }
  276. elseif (isset($this->_belongs_to[$column]))
  277. {
  278. $this->_load();
  279. $model = $this->_related($column);
  280. // Use this model's column and foreign model's primary key
  281. $col = $model->_table_name.'.'.$model->_primary_key;
  282. $val = $this->_object[$this->_belongs_to[$column]['foreign_key']];
  283. $model->where($col, '=', $val)->find();
  284. return $this->_related[$column] = $model;
  285. }
  286. elseif (isset($this->_has_one[$column]))
  287. {
  288. $model = $this->_related($column);
  289. // Use this model's primary key value and foreign model's column
  290. $col = $model->_table_name.'.'.$this->_has_one[$column]['foreign_key'];
  291. $val = $this->pk();
  292. $model->where($col, '=', $val)->find();
  293. return $this->_related[$column] = $model;
  294. }
  295. elseif (isset($this->_has_many[$column]))
  296. {
  297. $model = ORM::factory($this->_has_many[$column]['model']);
  298. if (isset($this->_has_many[$column]['through']))
  299. {
  300. // Grab has_many "through" relationship table
  301. $through = $this->_has_many[$column]['through'];
  302. // Join on through model's target foreign key (far_key) and target model's primary key
  303. $join_col1 = $through.'.'.$this->_has_many[$column]['far_key'];
  304. $join_col2 = $model->_table_name.'.'.$model->_primary_key;
  305. $model->join($through)->on($join_col1, '=', $join_col2);
  306. // Through table's source foreign key (foreign_key) should be this model's primary key
  307. $col = $through.'.'.$this->_has_many[$column]['foreign_key'];
  308. $val = $this->pk();
  309. }
  310. else
  311. {
  312. // Simple has_many relationship, search where target model's foreign key is this model's primary key
  313. $col = $model->_table_name.'.'.$this->_has_many[$column]['foreign_key'];
  314. $val = $this->pk();
  315. }
  316. return $model->where($col, '=', $val);
  317. }
  318. else
  319. {
  320. throw new Kohana_Exception('The :property property does not exist in the :class class',
  321. array(':property' => $column, ':class' => get_class($this)));
  322. }
  323. }
  324. /**
  325. * Handles setting of all model values, and tracks changes between values.
  326. *
  327. * @param string column name
  328. * @param mixed column value
  329. * @return void
  330. */
  331. public function __set($column, $value)
  332. {
  333. if ( ! isset($this->_object_name))
  334. {
  335. // Object not yet constructed, so we're loading data from a database call cast
  336. $this->_preload_data[$column] = $value;
  337. return;
  338. }
  339. if (array_key_exists($column, $this->_ignored_columns))
  340. {
  341. // No processing for ignored columns, just store it
  342. $this->_object[$column] = $value;
  343. }
  344. elseif (array_key_exists($column, $this->_object))
  345. {
  346. $this->_object[$column] = $value;
  347. if (isset($this->_table_columns[$column]))
  348. {
  349. // Data has changed
  350. $this->_changed[$column] = $column;
  351. // Object is no longer saved
  352. $this->_saved = FALSE;
  353. }
  354. }
  355. elseif (isset($this->_belongs_to[$column]))
  356. {
  357. // Update related object itself
  358. $this->_related[$column] = $value;
  359. // Update the foreign key of this model
  360. $this->_object[$this->_belongs_to[$column]['foreign_key']] = $value->pk();
  361. $this->_changed[$column] = $this->_belongs_to[$column]['foreign_key'];
  362. }
  363. else
  364. {
  365. throw new Kohana_Exception('The :property: property does not exist in the :class: class',
  366. array(':property:' => $column, ':class:' => get_class($this)));
  367. }
  368. }
  369. /**
  370. * Set values from an array with support for one-one relationships. This method should be used
  371. * for loading in post data, etc.
  372. *
  373. * @param array array of key => val
  374. * @return ORM
  375. */
  376. public function values($values)
  377. {
  378. foreach ($values as $key => $value)
  379. {
  380. if (array_key_exists($key, $this->_object) OR array_key_exists($key, $this->_ignored_columns))
  381. {
  382. // Property of this model
  383. $this->__set($key, $value);
  384. }
  385. elseif (isset($this->_belongs_to[$key]) OR isset($this->_has_one[$key]))
  386. {
  387. // Value is an array of properties for the related model
  388. $this->_related[$key] = $value;
  389. }
  390. }
  391. return $this;
  392. }
  393. /**
  394. * Prepares the model database connection, determines the table name,
  395. * and loads column information.
  396. *
  397. * @return void
  398. */
  399. protected function _initialize()
  400. {
  401. if ( ! is_object($this->_db))
  402. {
  403. // Get database instance
  404. $this->_db = Database::instance($this->_db);
  405. }
  406. if (empty($this->_table_name))
  407. {
  408. // Table name is the same as the object name
  409. $this->_table_name = $this->_object_name;
  410. if ($this->_table_names_plural === TRUE)
  411. {
  412. // Make the table name plural
  413. $this->_table_name = Inflector::plural($this->_table_name);
  414. }
  415. }
  416. foreach ($this->_belongs_to as $alias => $details)
  417. {
  418. $defaults['model'] = $alias;
  419. $defaults['foreign_key'] = $alias.$this->_foreign_key_suffix;
  420. $this->_belongs_to[$alias] = array_merge($defaults, $details);
  421. }
  422. foreach ($this->_has_one as $alias => $details)
  423. {
  424. $defaults['model'] = $alias;
  425. $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix;
  426. $this->_has_one[$alias] = array_merge($defaults, $details);
  427. }
  428. foreach ($this->_has_many as $alias => $details)
  429. {
  430. $defaults['model'] = Inflector::singular($alias);
  431. $defaults['foreign_key'] = $this->_object_name.$this->_foreign_key_suffix;
  432. $defaults['through'] = NULL;
  433. $defaults['far_key'] = Inflector::singular($alias).$this->_foreign_key_suffix;
  434. $this->_has_many[$alias] = array_merge($defaults, $details);
  435. }
  436. // Load column information
  437. $this->reload_columns();
  438. }
  439. /**
  440. * Initializes validation rules, callbacks, filters, and labels
  441. *
  442. * @return void
  443. */
  444. protected function _validate()
  445. {
  446. $this->_validate = Validate::factory($this->_object);
  447. foreach ($this->_rules as $field => $rules)
  448. {
  449. $this->_validate->rules($field, $rules);
  450. }
  451. foreach ($this->_filters as $field => $filters)
  452. {
  453. $this->_validate->filters($field, $filters);
  454. }
  455. // Use column names by default for labels
  456. $columns = array_keys($this->_table_columns);
  457. // Merge user-defined labels
  458. $labels = array_merge(array_combine($columns, $columns), $this->_labels);
  459. foreach ($labels as $field => $label)
  460. {
  461. $this->_validate->label($field, $label);
  462. }
  463. foreach ($this->_callbacks as $field => $callbacks)
  464. {
  465. foreach ($callbacks as $callback)
  466. {
  467. if (is_string($callback) AND method_exists($this, $callback))
  468. {
  469. // Callback method exists in current ORM model
  470. $this->_validate->callback($field, array($this, $callback));
  471. }
  472. else
  473. {
  474. // Try global function
  475. $this->_validate->callback($field, $callback);
  476. }
  477. }
  478. }
  479. }
  480. /**
  481. * Returns the values of this object as an array, including any related one-one
  482. * models that have already been loaded using with()
  483. *
  484. * @return array
  485. */
  486. public function as_array()
  487. {
  488. $object = array();
  489. foreach ($this->_object as $key => $val)
  490. {
  491. // Call __get for any user processing
  492. $object[$key] = $this->__get($key);
  493. }
  494. foreach ($this->_related as $key => $model)
  495. {
  496. // Include any related objects that are already loaded
  497. $object[$key] = $model->as_array();
  498. }
  499. return $object;
  500. }
  501. /**
  502. * Binds another one-to-one object to this model. One-to-one objects
  503. * can be nested using 'object1:object2' syntax
  504. *
  505. * @param string target model to bind to
  506. * @return void
  507. */
  508. public function with($target_path)
  509. {
  510. if (isset($this->_with_applied[$target_path]))
  511. {
  512. // Don't join anything already joined
  513. return $this;
  514. }
  515. // Split object parts
  516. $aliases = explode(':', $target_path);
  517. $target = $this;
  518. foreach ($aliases as $alias)
  519. {
  520. // Go down the line of objects to find the given target
  521. $parent = $target;
  522. $target = $parent->_related($alias);
  523. if ( ! $target)
  524. {
  525. // Can't find related object
  526. return $this;
  527. }
  528. }
  529. // Target alias is at the end
  530. $target_alias = $alias;
  531. // Pop-off top alias to get the parent path (user:photo:tag becomes user:photo - the parent table prefix)
  532. array_pop($aliases);
  533. $parent_path = implode(':', $aliases);
  534. if (empty($parent_path))
  535. {
  536. // Use this table name itself for the parent path
  537. $parent_path = $this->_table_name;
  538. }
  539. else
  540. {
  541. if( ! isset($this->_with_applied[$parent_path]))
  542. {
  543. // If the parent path hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
  544. $this->with($parent_path);
  545. }
  546. }
  547. // Add to with_applied to prevent duplicate joins
  548. $this->_with_applied[$target_path] = TRUE;
  549. // Use the keys of the empty object to determine the columns
  550. foreach (array_keys($target->_object) as $column)
  551. {
  552. $name = $target_path.'.'.$column;
  553. $alias = $target_path.':'.$column;
  554. // Add the prefix so that load_result can determine the relationship
  555. $this->select(array($name, $alias));
  556. }
  557. if (isset($parent->_belongs_to[$target_alias]))
  558. {
  559. // Parent belongs_to target, use target's primary key and parent's foreign key
  560. $join_col1 = $target_path.'.'.$target->_primary_key;
  561. $join_col2 = $parent_path.'.'.$parent->_belongs_to[$target_alias]['foreign_key'];
  562. }
  563. else
  564. {
  565. // Parent has_one target, use parent's primary key as target's foreign key
  566. $join_col1 = $parent_path.'.'.$parent->_primary_key;
  567. $join_col2 = $target_path.'.'.$parent->_has_one[$target_alias]['foreign_key'];
  568. }
  569. // Join the related object into the result
  570. $this->join(array($target->_table_name, $target_path), 'LEFT')->on($join_col1, '=', $join_col2);
  571. return $this;
  572. }
  573. /**
  574. * Initializes the Database Builder to given query type
  575. *
  576. * @param int Type of Database query
  577. * @return ORM
  578. */
  579. protected function _build($type)
  580. {
  581. // Construct new builder object based on query type
  582. switch ($type)
  583. {
  584. case Database::SELECT:
  585. $this->_db_builder = DB::select();
  586. break;
  587. case Database::UPDATE:
  588. $this->_db_builder = DB::update($this->_table_name);
  589. break;
  590. case Database::DELETE:
  591. $this->_db_builder = DB::delete($this->_table_name);
  592. }
  593. // Process pending database method calls
  594. foreach ($this->_db_pending as $method)
  595. {
  596. $name = $method['name'];
  597. $args = $method['args'];
  598. $this->_db_applied[$name] = $name;
  599. switch (count($args))
  600. {
  601. case 0:
  602. $this->_db_builder->$name();
  603. break;
  604. case 1:
  605. $this->_db_builder->$name($args[0]);
  606. break;
  607. case 2:
  608. $this->_db_builder->$name($args[0], $args[1]);
  609. break;
  610. case 3:
  611. $this->_db_builder->$name($args[0], $args[1], $args[2]);
  612. break;
  613. case 4:
  614. $this->_db_builder->$name($args[0], $args[1], $args[2], $args[3]);
  615. break;
  616. default:
  617. // Here comes the snail...
  618. call_user_func_array(array($this->_db_builder, $name), $args);
  619. break;
  620. }
  621. }
  622. return $this;
  623. }
  624. /**
  625. * Loads the given model
  626. *
  627. * @return ORM
  628. */
  629. protected function _load()
  630. {
  631. if ( ! $this->_loaded AND ! $this->empty_pk() AND ! isset($this->_changed[$this->_primary_key]))
  632. {
  633. // Only load if it hasn't been loaded, and a primary key is specified and hasn't been modified
  634. return $this->find($this->pk());
  635. }
  636. }
  637. /**
  638. * Finds and loads a single database row into the object.
  639. *
  640. * @chainable
  641. * @param mixed primary key
  642. * @return ORM
  643. */
  644. public function find($id = NULL)
  645. {
  646. if ( ! empty($this->_load_with))
  647. {
  648. foreach ($this->_load_with as $alias)
  649. {
  650. // Bind relationship
  651. $this->with($alias);
  652. }
  653. }
  654. $this->_build(Database::SELECT);
  655. if ($id !== NULL)
  656. {
  657. // Search for a specific column
  658. $this->_db_builder->where($this->_table_name.'.'.$this->_primary_key, '=', $id);
  659. }
  660. return $this->_load_result(FALSE);
  661. }
  662. /**
  663. * Finds multiple database rows and returns an iterator of the rows found.
  664. *
  665. * @chainable
  666. * @return Database_Result
  667. */
  668. public function find_all()
  669. {
  670. if ( ! empty($this->_load_with))
  671. {
  672. foreach ($this->_load_with as $alias)
  673. {
  674. // Bind relationship
  675. $this->with($alias);
  676. }
  677. }
  678. $this->_build(Database::SELECT);
  679. return $this->_load_result(TRUE);
  680. }
  681. /**
  682. * Validates the current model's data
  683. *
  684. * @return boolean
  685. */
  686. public function check()
  687. {
  688. if ( ! isset($this->_validate))
  689. {
  690. // Initialize the validation object
  691. $this->_validate();
  692. }
  693. else
  694. {
  695. // Validation object has been created, just exchange the data array
  696. $this->_validate->exchangeArray($this->_object);
  697. }
  698. if ($this->_validate->check())
  699. {
  700. // Fields may have been modified by filters
  701. $this->_object = array_merge($this->_object, $this->_validate->getArrayCopy());
  702. return TRUE;
  703. }
  704. else
  705. {
  706. return FALSE;
  707. }
  708. }
  709. /**
  710. * Saves the current object.
  711. *
  712. * @chainable
  713. * @return ORM
  714. */
  715. public function save()
  716. {
  717. if (empty($this->_changed))
  718. return $this;
  719. $data = array();
  720. foreach ($this->_changed as $column)
  721. {
  722. // Compile changed data
  723. $data[$column] = $this->_object[$column];
  724. }
  725. if ( ! $this->empty_pk() AND ! isset($this->_changed[$this->_primary_key]))
  726. {
  727. // Primary key isn't empty and hasn't been changed so do an update
  728. if (is_array($this->_updated_column))
  729. {
  730. // Fill the updated column
  731. $column = $this->_updated_column['column'];
  732. $format = $this->_updated_column['format'];
  733. $data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
  734. }
  735. $query = DB::update($this->_table_name)
  736. ->set($data)
  737. ->where($this->_primary_key, '=', $this->pk())
  738. ->execute($this->_db);
  739. // Object has been saved
  740. $this->_saved = TRUE;
  741. }
  742. else
  743. {
  744. if (is_array($this->_created_column))
  745. {
  746. // Fill the created column
  747. $column = $this->_created_column['column'];
  748. $format = $this->_created_column['format'];
  749. $data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
  750. }
  751. $result = DB::insert($this->_table_name)
  752. ->columns(array_keys($data))
  753. ->values(array_values($data))
  754. ->execute($this->_db);
  755. if ($result)
  756. {
  757. if ($this->empty_pk())
  758. {
  759. // Load the insert id as the primary key
  760. // $result is array(insert_id, total_rows)
  761. $this->_object[$this->_primary_key] = $result[0];
  762. }
  763. // Object is now loaded and saved
  764. $this->_loaded = $this->_saved = TRUE;
  765. }
  766. }
  767. if ($this->_saved === TRUE)
  768. {
  769. // All changes have been saved
  770. $this->_changed = array();
  771. }
  772. return $this;
  773. }
  774. /**
  775. * Updates all existing records
  776. *
  777. * @chainable
  778. * @return ORM
  779. */
  780. public function save_all()
  781. {
  782. $this->_build(Database::UPDATE);
  783. if (empty($this->_changed))
  784. return $this;
  785. $data = array();
  786. foreach ($this->_changed as $column)
  787. {
  788. // Compile changed data omitting ignored columns
  789. $data[$column] = $this->_object[$column];
  790. }
  791. if (is_array($this->_updated_column))
  792. {
  793. // Fill the updated column
  794. $column = $this->_updated_column['column'];
  795. $format = $this->_updated_column['format'];
  796. $data[$column] = $this->_object[$column] = ($format === TRUE) ? time() : date($format);
  797. }
  798. $this->_db_builder->set($data)->execute($this->_db);
  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->pk();
  815. }
  816. if ( ! empty($id) OR $id === '0')
  817. {
  818. // Delete the object
  819. DB::delete($this->_table_name)
  820. ->where($this->_primary_key, '=', $id)
  821. ->execute($this->_db);
  822. }
  823. return $this;
  824. }
  825. /**
  826. * Delete all objects in the associated table. This does NOT destroy
  827. * relationships that have been created with other objects.
  828. *
  829. * @chainable
  830. * @return ORM
  831. */
  832. public function delete_all()
  833. {
  834. $this->_build(Database::DELETE);
  835. $this->_db_builder->execute($this->_db);
  836. return $this->clear();
  837. }
  838. /**
  839. * Unloads the current object and clears the status.
  840. *
  841. * @chainable
  842. * @return ORM
  843. */
  844. public function clear()
  845. {
  846. // Create an array with all the columns set to NULL
  847. $values = array_combine(array_keys($this->_table_columns), array_fill(0, count($this->_table_columns), NULL));
  848. // Replace the object and reset the object status
  849. $this->_object = $this->_changed = $this->_related = array();
  850. // Replace the current object with an empty one
  851. $this->_load_values($values);
  852. $this->reset();
  853. return $this;
  854. }
  855. /**
  856. * Reloads the current object from the database.
  857. *
  858. * @chainable
  859. * @return ORM
  860. */
  861. public function reload()
  862. {
  863. $primary_key = $this->pk();
  864. // Replace the object and reset the object status
  865. $this->_object = $this->_changed = $this->_related = array();
  866. return $this->find($primary_key);
  867. }
  868. /**
  869. * Reload column definitions.
  870. *
  871. * @chainable
  872. * @param boolean force reloading
  873. * @return ORM
  874. */
  875. public function reload_columns($force = FALSE)
  876. {
  877. if ($force === TRUE OR empty($this->_table_columns))
  878. {
  879. if (isset(ORM::$_column_cache[$this->_object_name]))
  880. {
  881. // Use cached column information
  882. $this->_table_columns = ORM::$_column_cache[$this->_object_name];
  883. }
  884. else
  885. {
  886. // Grab column information from database
  887. $this->_table_columns = $this->list_columns(TRUE);
  888. // Load column cache
  889. ORM::$_column_cache[$this->_object_name] = $this->_table_columns;
  890. }
  891. }
  892. return $this;
  893. }
  894. /**
  895. * Tests if this object has a relationship to a different model.
  896. *
  897. * @param string alias of the has_many "through" relationship
  898. * @param ORM related ORM model
  899. * @return boolean
  900. */
  901. public function has($alias, $model)
  902. {
  903. // Return count of matches as boolean
  904. return (bool) DB::select(array('COUNT("*")', 'records_found'))
  905. ->from($this->_has_many[$alias]['through'])
  906. ->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk())
  907. ->where($this->_has_many[$alias]['far_key'], '=', $model->pk())
  908. ->execute($this->_db)
  909. ->get('records_found');
  910. }
  911. /**
  912. * Adds a new relationship to between this model and another.
  913. *
  914. * @param string alias of the has_many "through" relationship
  915. * @param ORM related ORM model
  916. * @param array additional data to store in "through"/pivot table
  917. * @return ORM
  918. */
  919. public function add($alias, ORM $model, $data = NULL)
  920. {
  921. $columns = array($this->_has_many[$alias]['foreign_key'], $this->_has_many[$alias]['far_key']);
  922. $values = array($this->pk(), $model->pk());
  923. if ($data !== NULL)
  924. {
  925. // Additional data stored in pivot table
  926. $columns = array_merge($columns, array_keys($data));
  927. $values = array_merge($values, array_values($data));
  928. }
  929. DB::insert($this->_has_many[$alias]['through'])
  930. ->columns($columns)
  931. ->values($values)
  932. ->execute($this->_db);
  933. return $this;
  934. }
  935. /**
  936. * Removes a relationship between this model and another.
  937. *
  938. * @param string alias of the has_many "through" relationship
  939. * @param ORM related ORM model
  940. * @return ORM
  941. */
  942. public function remove($alias, ORM $model)
  943. {
  944. DB::delete($this->_has_many[$alias]['through'])
  945. ->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk())
  946. ->where($this->_has_many[$alias]['far_key'], '=', $model->pk())
  947. ->execute($this->_db);
  948. return $this;
  949. }
  950. /**
  951. * Count the number of records in the table.
  952. *
  953. * @return integer
  954. */
  955. public function count_all()
  956. {
  957. $selects = array();
  958. foreach ($this->_db_pending as $key => $method)
  959. {
  960. if ($method['name'] == 'select')
  961. {
  962. // Ignore any selected columns for now
  963. $selects[] = $method;
  964. unset($this->_db_pending[$key]);
  965. }
  966. }
  967. $this->_build(Database::SELECT);
  968. $records = $this->_db_builder->from($this->_table_name)
  969. ->select(array('COUNT("*")', 'records_found'))
  970. ->execute($this->_db)
  971. ->get('records_found');
  972. // Add back in selected columns
  973. $this->_db_pending += $selects;
  974. $this->reset();
  975. // Return the total number of records in a table
  976. return $records;
  977. }
  978. /**
  979. * Proxy method to Database list_columns.
  980. *
  981. * @return array
  982. */
  983. public function list_columns()
  984. {
  985. // Proxy to database
  986. return $this->_db->list_columns($this->_table_name);
  987. }
  988. /**
  989. * Proxy method to Database field_data.
  990. *
  991. * @chainable
  992. * @param string SQL query to clear
  993. * @return ORM
  994. */
  995. public function clear_cache($sql = NULL)
  996. {
  997. // Proxy to database
  998. $this->_db->clear_cache($sql);
  999. ORM::$_column_cache = array();
  1000. return $this;
  1001. }
  1002. /**
  1003. * Returns an ORM model for the given one-one related alias
  1004. *
  1005. * @param string alias name
  1006. * @return ORM
  1007. */
  1008. protected function _related($alias)
  1009. {
  1010. if (isset($this->_related[$alias]))
  1011. {
  1012. return $this->_related[$alias];
  1013. }
  1014. elseif (isset($this->_has_one[$alias]))
  1015. {
  1016. return $this->_related[$alias] = ORM::factory($this->_has_one[$alias]['model']);
  1017. }
  1018. elseif (isset($this->_belongs_to[$alias]))
  1019. {
  1020. return $this->_related[$alias] = ORM::factory($this->_belongs_to[$alias]['model']);
  1021. }
  1022. else
  1023. {
  1024. return FALSE;
  1025. }
  1026. }
  1027. /**
  1028. * Loads an array of values into into the current object.
  1029. *
  1030. * @chainable
  1031. * @param array values to load
  1032. * @return ORM
  1033. */
  1034. protected function _load_values(array $values)
  1035. {
  1036. if (array_key_exists($this->_primary_key, $values))
  1037. {
  1038. // Set the loaded and saved object status based on the primary key
  1039. $this->_loaded = $this->_saved = ($values[$this->_primary_key] !== NULL);
  1040. }
  1041. // Related objects
  1042. $related = array();
  1043. foreach ($values as $column => $value)
  1044. {
  1045. if (strpos($column, ':') === FALSE)
  1046. {
  1047. if ( ! isset($this->_changed[$column]))
  1048. {
  1049. $this->_object[$column] = $value;
  1050. }
  1051. }
  1052. else
  1053. {
  1054. list ($prefix, $column) = explode(':', $column, 2);
  1055. $related[$prefix][$column] = $value;
  1056. }
  1057. }
  1058. if ( ! empty($related))
  1059. {
  1060. foreach ($related as $object => $values)
  1061. {
  1062. // Load the related objects with the values in the result
  1063. $this->_related($object)->_load_values($values);
  1064. }
  1065. }
  1066. return $this;
  1067. }
  1068. /**
  1069. * Loads a database result, either as a new object for this model, or as
  1070. * an iterator for multiple rows.
  1071. *
  1072. * @chainable
  1073. * @param boolean return an iterator or load a single row
  1074. * @return ORM for single rows
  1075. * @return ORM_Iterator for multiple rows
  1076. */
  1077. protected function _load_result($multiple = FALSE)
  1078. {
  1079. $this->_db_builder->from($this->_table_name);
  1080. if ($multiple === FALSE)
  1081. {
  1082. // Only fetch 1 record
  1083. $this->_db_builder->limit(1);
  1084. }
  1085. // Select all columns by default
  1086. $this->_db_builder->select($this->_table_name.'.*');
  1087. if ( ! isset($this->_db_applied['order_by']) AND ! empty($this->_sorting))
  1088. {
  1089. foreach ($this->_sorting as $column => $direction)
  1090. {
  1091. if (strpos($column, '.') === FALSE)
  1092. {
  1093. // Sorting column for use in JOINs
  1094. $column = $this->_table_name.'.'.$column;
  1095. }
  1096. $this->_db_builder->order_by($column, $direction);
  1097. }
  1098. }
  1099. if ($multiple === TRUE)
  1100. {
  1101. // Return database iterator casting to this object type
  1102. $result = $this->_db_builder->as_object(get_class($this))->execute($this->_db);
  1103. $this->reset();
  1104. return $result;
  1105. }
  1106. else
  1107. {
  1108. // Load the result as an associative array
  1109. $result = $this->_db_builder->as_assoc()->execute($this->_db);
  1110. $this->reset();
  1111. if ($result->count() === 1)
  1112. {
  1113. // Load object values
  1114. $this->_load_values($result->current());
  1115. }
  1116. else
  1117. {
  1118. // Clear the object, nothing was found
  1119. $this->clear();
  1120. }
  1121. return $this;
  1122. }
  1123. }
  1124. /**
  1125. * Returns the value of the primary key
  1126. *
  1127. * @return mixed primary key
  1128. */
  1129. public function pk()
  1130. {
  1131. return $this->_object[$this->_primary_key];
  1132. }
  1133. /**
  1134. * Returns whether or not primary key is empty
  1135. *
  1136. * @return bool
  1137. */
  1138. protected function empty_pk()
  1139. {
  1140. return (empty($this->_object[$this->_primary_key]) AND $this->_object[$this->_primary_key] !== '0');
  1141. }
  1142. /**
  1143. * Returns last executed query
  1144. *
  1145. * @return string
  1146. */
  1147. public function last_query()
  1148. {
  1149. return $this->_db->last_query;
  1150. }
  1151. /**
  1152. * Clears query builder. Passing FALSE is useful to keep the existing
  1153. * query conditions for another query.
  1154. *
  1155. * @param bool Pass FALSE to avoid resetting on the next call
  1156. */
  1157. public function reset($next = TRUE)
  1158. {
  1159. if ($next AND $this->_db_reset)
  1160. {
  1161. $this->_db_pending = array();
  1162. $this->_db_applied = array();
  1163. $this->_db_builder = NULL;
  1164. $this->_with_applied = array();
  1165. }
  1166. // Reset on the next call?
  1167. $this->_db_reset = $next;
  1168. return $this;
  1169. }
  1170. } // End ORM