PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

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

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