PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/fuel/packages/orm/classes/model.php

https://bitbucket.org/sriedel/iccrm-wip
PHP | 1885 lines | 1200 code | 213 blank | 472 comment | 122 complexity | ddb34463010a5c155330305445b3f75c MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Fuel is a fast, lightweight, community driven PHP5 framework.
  4. *
  5. * @package Fuel
  6. * @version 1.0
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2012 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Orm;
  13. /**
  14. * Record Not Found Exception
  15. */
  16. class RecordNotFound extends \OutOfBoundsException {}
  17. /**
  18. * Frozen Object Exception
  19. */
  20. class FrozenObject extends \RuntimeException {}
  21. class Model implements \ArrayAccess, \Iterator
  22. {
  23. /* ---------------------------------------------------------------------------
  24. * Static usage
  25. * --------------------------------------------------------------------------- */
  26. /**
  27. * @var string connection to use
  28. */
  29. // protected static $_connection = null;
  30. /**
  31. * @var string table name to overwrite assumption
  32. */
  33. // protected static $_table_name;
  34. /**
  35. * @var array array of object properties
  36. */
  37. // protected static $_properties;
  38. /**
  39. * @var array array of views with additional properties
  40. */
  41. // protected static $_views;
  42. /**
  43. * @var array array of observer classes to use
  44. */
  45. // protected static $_observers;
  46. /**
  47. * @var array relationship properties
  48. */
  49. // protected static $_has_one;
  50. // protected static $_belongs_to;
  51. // protected static $_has_many;
  52. // protected static $_many_many;
  53. // protected static $_eav;
  54. /**
  55. * @var array name or names of the primary keys
  56. */
  57. protected static $_primary_key = array('id');
  58. /**
  59. * @var array name or columns that need to be excluded from any to_array() result
  60. */
  61. protected static $_to_array_exclude = array();
  62. /**
  63. * @var array cached tables
  64. */
  65. protected static $_table_names_cached = array();
  66. /**
  67. * @var array cached properties
  68. */
  69. protected static $_properties_cached = array();
  70. /**
  71. * @var array cached properties
  72. */
  73. protected static $_views_cached = array();
  74. /**
  75. * @var string relationships
  76. */
  77. protected static $_relations_cached = array();
  78. /**
  79. * @var array cached observers
  80. */
  81. protected static $_observers_cached = array();
  82. /**
  83. * @var array array of fetched objects
  84. */
  85. protected static $_cached_objects = array();
  86. /**
  87. * @var array array of valid relation types
  88. */
  89. protected static $_valid_relations = array(
  90. 'belongs_to' => 'Orm\\BelongsTo',
  91. 'has_one' => 'Orm\\HasOne',
  92. 'has_many' => 'Orm\\HasMany',
  93. 'many_many' => 'Orm\\ManyMany',
  94. );
  95. public static function forge($data = array(), $new = true, $view = null)
  96. {
  97. return new static($data, $new, $view);
  98. }
  99. /**
  100. * Fetch the database connection name to use
  101. *
  102. * @return null|string
  103. */
  104. public static function connection()
  105. {
  106. $class = get_called_class();
  107. return property_exists($class, '_connection') ? static::$_connection : null;
  108. }
  109. /**
  110. * Get the table name for this class
  111. *
  112. * @return string
  113. */
  114. public static function table()
  115. {
  116. $class = get_called_class();
  117. // Table name unknown
  118. if ( ! array_key_exists($class, static::$_table_names_cached))
  119. {
  120. // Table name set in Model
  121. if (property_exists($class, '_table_name'))
  122. {
  123. static::$_table_names_cached[$class] = static::$_table_name;
  124. }
  125. else
  126. {
  127. static::$_table_names_cached[$class] = \Inflector::tableize($class);
  128. }
  129. }
  130. return static::$_table_names_cached[$class];
  131. }
  132. /**
  133. * Get a defined condition for this class
  134. *
  135. * @param string type of condition to return
  136. * @return array
  137. */
  138. public static function condition($type = null)
  139. {
  140. $class = get_called_class();
  141. // a specific condition requested?
  142. if (property_exists($class, '_conditions'))
  143. {
  144. if ($type !== null)
  145. {
  146. return isset(static::$_conditions[$type]) ? static::$_conditions[$type] : array();
  147. }
  148. else
  149. {
  150. return static::$_conditions;
  151. }
  152. }
  153. else
  154. {
  155. return array();
  156. }
  157. }
  158. /**
  159. * Attempt to retrieve an earlier loaded object
  160. *
  161. * @param array|Model $obj
  162. * @param null|string $class
  163. * @return Model|false
  164. */
  165. public static function cached_object($obj, $class = null)
  166. {
  167. $class = $class ?: get_called_class();
  168. $id = (is_int($obj) or is_string($obj)) ? (string) $obj : $class::implode_pk($obj);
  169. $result = ( ! empty(static::$_cached_objects[$class][$id])) ? static::$_cached_objects[$class][$id] : false;
  170. return $result;
  171. }
  172. /**
  173. * Get the primary key(s) of this class
  174. *
  175. * @return array
  176. */
  177. public static function primary_key()
  178. {
  179. return static::$_primary_key;
  180. }
  181. /**
  182. * Implode the primary keys within the data into a string
  183. *
  184. * @param array
  185. * @return string
  186. */
  187. public static function implode_pk($data)
  188. {
  189. if (count(static::$_primary_key) == 1)
  190. {
  191. $p = reset(static::$_primary_key);
  192. return (is_object($data)
  193. ? strval($data->{$p})
  194. : (isset($data[$p])
  195. ? strval($data[$p])
  196. : null));
  197. }
  198. $pk = '';
  199. foreach (static::$_primary_key as $p)
  200. {
  201. if (is_null((is_object($data) ? $data->{$p} : (isset($data[$p]) ? $data[$p] : null))))
  202. {
  203. return null;
  204. }
  205. $pk .= '['.(is_object($data) ? $data->{$p} : $data[$p]).']';
  206. }
  207. return $pk;
  208. }
  209. /**
  210. * Get the class's properties
  211. *
  212. * @return array
  213. */
  214. public static function properties()
  215. {
  216. $class = get_called_class();
  217. // If already determined
  218. if (array_key_exists($class, static::$_properties_cached))
  219. {
  220. return static::$_properties_cached[$class];
  221. }
  222. // Try to grab the properties from the class...
  223. if (property_exists($class, '_properties'))
  224. {
  225. $properties = static::$_properties;
  226. foreach ($properties as $key => $p)
  227. {
  228. if (is_string($p))
  229. {
  230. unset($properties[$key]);
  231. $properties[$p] = array();
  232. }
  233. }
  234. }
  235. // ...if the above failed, run DB query to fetch properties
  236. if (empty($properties))
  237. {
  238. try
  239. {
  240. $properties = \DB::list_columns(static::table(), null, static::connection());
  241. }
  242. catch (\Exception $e)
  243. {
  244. throw new \FuelException('Listing columns failed, you have to set the model properties with a '.
  245. 'static $_properties setting in the model. Original exception: '.$e->getMessage());
  246. }
  247. }
  248. // cache the properties for next usage
  249. static::$_properties_cached[$class] = $properties;
  250. return static::$_properties_cached[$class];
  251. }
  252. /**
  253. * Fetches a property description array, or specific data from it
  254. *
  255. * @param string property or property.key
  256. * @param mixed return value when key not present
  257. * @return mixed
  258. */
  259. public static function property($key, $default = null)
  260. {
  261. $class = get_called_class();
  262. // If already determined
  263. if ( ! array_key_exists($class, static::$_properties_cached))
  264. {
  265. static::properties();
  266. }
  267. return \Arr::get(static::$_properties_cached[$class], $key, $default);
  268. }
  269. /**
  270. * Fetch the model's views
  271. *
  272. * @return array
  273. */
  274. public static function views()
  275. {
  276. $class = get_called_class();
  277. if ( ! isset(static::$_views_cached[$class]))
  278. {
  279. static::$_views_cached[$class] = array();
  280. if (property_exists($class, '_views'))
  281. {
  282. $views = $class::$_views;
  283. foreach ($views as $k => $v)
  284. {
  285. if ( ! isset($v['columns']))
  286. {
  287. throw new \InvalidArgumentException('Database view '.$k.' is defined without columns.');
  288. }
  289. $v['columns'] = (array) $v['columns'];
  290. if ( ! isset($v['view']))
  291. {
  292. $v['view'] = $k;
  293. }
  294. static::$_views_cached[$class][$k] = $v;
  295. }
  296. }
  297. }
  298. return static::$_views_cached[$class];
  299. }
  300. /**
  301. * Get the class's relations
  302. *
  303. * @param string
  304. * @return array
  305. */
  306. public static function relations($specific = false)
  307. {
  308. $class = get_called_class();
  309. if ( ! array_key_exists($class, static::$_relations_cached))
  310. {
  311. $relations = array();
  312. foreach (static::$_valid_relations as $rel_name => $rel_class)
  313. {
  314. if (property_exists($class, '_'.$rel_name))
  315. {
  316. foreach (static::${'_'.$rel_name} as $key => $settings)
  317. {
  318. $name = is_string($settings) ? $settings : $key;
  319. $settings = is_array($settings) ? $settings : array();
  320. $relations[$name] = new $rel_class($class, $name, $settings);
  321. }
  322. }
  323. }
  324. static::$_relations_cached[$class] = $relations;
  325. }
  326. if ($specific === false)
  327. {
  328. return static::$_relations_cached[$class];
  329. }
  330. else
  331. {
  332. if ( ! array_key_exists($specific, static::$_relations_cached[$class]))
  333. {
  334. return false;
  335. }
  336. return static::$_relations_cached[$class][$specific];
  337. }
  338. }
  339. /**
  340. * Get the class's observers and what they observe
  341. *
  342. * @param string specific observer to retrieve info of, allows direct param access by using dot notation
  343. * @param mixed default return value when specific key wasn't found
  344. * @return array
  345. */
  346. public static function observers($specific = null, $default = null)
  347. {
  348. $class = get_called_class();
  349. if ( ! array_key_exists($class, static::$_observers_cached))
  350. {
  351. $observers = array();
  352. if (property_exists($class, '_observers'))
  353. {
  354. foreach (static::$_observers as $obs_k => $obs_v)
  355. {
  356. if (is_int($obs_k))
  357. {
  358. $observers[$obs_v] = array();
  359. }
  360. else
  361. {
  362. if (is_string($obs_v) or (is_array($obs_v) and is_int(key($obs_v))))
  363. {
  364. // @TODO deprecated until v1.4
  365. logger(\Fuel::L_WARNING, 'Passing observer events as array is deprecated, they must be
  366. inside another array under a key "events". Check the docs for more info.', __METHOD__);
  367. $observers[$obs_k] = array('events' => (array) $obs_v);
  368. }
  369. else
  370. {
  371. $observers[$obs_k] = $obs_v;
  372. }
  373. }
  374. }
  375. }
  376. static::$_observers_cached[$class] = $observers;
  377. }
  378. if ($specific)
  379. {
  380. return \Arr::get(static::$_observers_cached[$class], $specific, $default);
  381. }
  382. return static::$_observers_cached[$class];
  383. }
  384. /**
  385. * Register an observer
  386. *
  387. * @param string class name of the observer (including namespace)
  388. * @param mixed observer options
  389. *
  390. * @return void
  391. */
  392. public static function register_observer($name, $options = null)
  393. {
  394. $class = get_called_class();
  395. $new_observer = is_null($options) ? array($name) : array($name => $options);
  396. static::$_observers_cached[$class] = static::observers() + $new_observer;
  397. }
  398. /**
  399. * Unregister an observer
  400. *
  401. * @param string class name of the observer (including namespace)
  402. * @return void
  403. */
  404. public static function unregister_observer($name)
  405. {
  406. $class = get_called_class();
  407. foreach (static::observers() as $key => $value)
  408. {
  409. if ((is_array($value) and $key == $name) or $value == $name)
  410. {
  411. unset(static::$_observers_cached[$class][$key]);
  412. }
  413. }
  414. }
  415. /**
  416. * Find one or more entries
  417. *
  418. * @param mixed
  419. * @param array
  420. * @return object|array
  421. */
  422. public static function find($id = null, array $options = array())
  423. {
  424. // Return Query object
  425. if (is_null($id))
  426. {
  427. return static::query($options);
  428. }
  429. // Return all that match $options array
  430. elseif ($id === 'all')
  431. {
  432. return static::query($options)->get();
  433. }
  434. // Return first or last row that matches $options array
  435. elseif ($id === 'first' or $id === 'last')
  436. {
  437. $query = static::query($options);
  438. foreach(static::primary_key() as $pk)
  439. {
  440. $query->order_by($pk, $id == 'first' ? 'ASC' : 'DESC');
  441. }
  442. return $query->get_one();
  443. }
  444. // Return specific request row by ID
  445. else
  446. {
  447. $cache_pk = $where = array();
  448. $id = (array) $id;
  449. foreach (static::primary_key() as $pk)
  450. {
  451. $where[] = array($pk, '=', current($id));
  452. $cache_pk[$pk] = current($id);
  453. next($id);
  454. }
  455. if (array_key_exists(get_called_class(), static::$_cached_objects)
  456. and array_key_exists(static::implode_pk($cache_pk), static::$_cached_objects[get_called_class()]))
  457. {
  458. return static::$_cached_objects[get_called_class()][static::implode_pk($cache_pk)];
  459. }
  460. array_key_exists('where', $options) and $where = array_merge($options['where'], $where);
  461. $options['where'] = $where;
  462. return static::query($options)->get_one();
  463. }
  464. }
  465. /**
  466. * Creates a new query with optional settings up front
  467. *
  468. * @param array
  469. * @return Query
  470. */
  471. public static function query($options = array())
  472. {
  473. return Query::forge(get_called_class(), static::connection(), $options);
  474. }
  475. /**
  476. * Count entries, optionally only those matching the $options
  477. *
  478. * @param array
  479. * @return int
  480. */
  481. public static function count(array $options = array())
  482. {
  483. return Query::forge(get_called_class(), static::connection(), $options)->count();
  484. }
  485. /**
  486. * Find the maximum
  487. *
  488. * @param mixed
  489. * @param array
  490. * @return object|array
  491. */
  492. public static function max($key = null)
  493. {
  494. return Query::forge(get_called_class(), static::connection())->max($key ?: static::primary_key());
  495. }
  496. /**
  497. * Find the minimum
  498. *
  499. * @param mixed
  500. * @param array
  501. * @return object|array
  502. */
  503. public static function min($key = null)
  504. {
  505. return Query::forge(get_called_class(), static::connection())->min($key ?: static::primary_key());
  506. }
  507. public static function __callStatic($method, $args)
  508. {
  509. // Start with count_by? Get counting!
  510. if (strpos($method, 'count_by') === 0)
  511. {
  512. $find_type = 'count';
  513. $fields = substr($method, 9);
  514. }
  515. // Otherwise, lets find stuff
  516. elseif (strpos($method, 'find_') === 0)
  517. {
  518. $find_type = strncmp($method, 'find_all_by_', 12) === 0 ? 'all' : (strncmp($method, 'find_by_', 8) === 0 ? 'first' : false);
  519. $fields = $find_type === 'first' ? substr($method, 8) : substr($method, 12);
  520. }
  521. // God knows, complain
  522. else
  523. {
  524. throw new \FuelException('Invalid method call. Method '.$method.' does not exist.', 0);
  525. }
  526. $where = $or_where = array();
  527. if (($and_parts = explode('_and_', $fields)))
  528. {
  529. foreach ($and_parts as $and_part)
  530. {
  531. $or_parts = explode('_or_', $and_part);
  532. if (count($or_parts) == 1)
  533. {
  534. $where[] = array($or_parts[0] => array_shift($args));
  535. }
  536. else
  537. {
  538. foreach($or_parts as $or_part)
  539. {
  540. $or_where[] = array($or_part => array_shift($args));
  541. }
  542. }
  543. }
  544. }
  545. $options = count($args) > 0 ? array_pop($args) : array();
  546. if ( ! array_key_exists('where', $options))
  547. {
  548. $options['where'] = $where;
  549. }
  550. else
  551. {
  552. $options['where'] = array_merge($where, $options['where']);
  553. }
  554. if ( ! array_key_exists('or_where', $options))
  555. {
  556. $options['or_where'] = $or_where;
  557. }
  558. else
  559. {
  560. $options['or_where'] = array_merge($or_where, $options['or_where']);
  561. }
  562. if ($find_type == 'count')
  563. {
  564. return static::count($options);
  565. }
  566. else
  567. {
  568. return static::find($find_type, $options);
  569. }
  570. // min_...($options)
  571. // max_...($options)
  572. }
  573. /* ---------------------------------------------------------------------------
  574. * Object usage
  575. * --------------------------------------------------------------------------- */
  576. /**
  577. * @var bool keeps track of whether it's a new object
  578. */
  579. protected $_is_new = true;
  580. /**
  581. * @var bool keeps to object frozen
  582. */
  583. protected $_frozen = false;
  584. /**
  585. * @var array keeps the current state of the object
  586. */
  587. protected $_data = array();
  588. /**
  589. * @var array storage for custom properties on this object
  590. */
  591. protected $_custom_data = array();
  592. /**
  593. * @var array keeps a copy of the object as it was retrieved from the database
  594. */
  595. protected $_original = array();
  596. /**
  597. * @var array
  598. */
  599. protected $_data_relations = array();
  600. /**
  601. * @var array keeps a copy of the relation ids that were originally retrieved from the database
  602. */
  603. protected $_original_relations = array();
  604. /**
  605. * @var array keeps track of relations that need to be reset before saving the new ones
  606. */
  607. protected $_reset_relations = array();
  608. /**
  609. * @var string view name when used
  610. */
  611. protected $_view;
  612. /**
  613. * Constructor
  614. *
  615. * @param array
  616. * @param bool
  617. */
  618. public function __construct(array $data = array(), $new = true, $view = null)
  619. {
  620. // This is to deal with PHP's native hydration from that happens before constructor is called
  621. // for example using the DB's as_object() function
  622. if( ! empty($this->_data))
  623. {
  624. $this->_original = $this->_data;
  625. $new = false;
  626. }
  627. if ($new)
  628. {
  629. $properties = $this->properties();
  630. foreach ($properties as $prop => $settings)
  631. {
  632. if (array_key_exists($prop, $data))
  633. {
  634. $this->_data[$prop] = $data[$prop];
  635. }
  636. elseif (array_key_exists('default', $settings))
  637. {
  638. $this->_data[$prop] = $settings['default'];
  639. }
  640. }
  641. }
  642. else
  643. {
  644. $this->_update_original($data);
  645. $this->_data = array_merge($this->_data, $data);
  646. if ($view and array_key_exists($view, $this->views()))
  647. {
  648. $this->_view = $view;
  649. }
  650. }
  651. if ($new === false)
  652. {
  653. static::$_cached_objects[get_class($this)][static::implode_pk($data)] = $this;
  654. $this->_is_new = false;
  655. $this->observe('after_load');
  656. }
  657. else
  658. {
  659. $this->observe('after_create');
  660. }
  661. }
  662. /**
  663. * Update the original setting for this object
  664. *
  665. * @param array|null $original
  666. */
  667. public function _update_original($original = null)
  668. {
  669. $original = is_null($original) ? $this->_data : $original;
  670. $this->_original = array_merge($this->_original, $original);
  671. $this->_update_original_relations();
  672. }
  673. /**
  674. * Update the original relations for this object
  675. */
  676. public function _update_original_relations($relations = null)
  677. {
  678. if (is_null($relations))
  679. {
  680. $this->_original_relations = array();
  681. $relations = $this->_data_relations;
  682. }
  683. else
  684. {
  685. foreach ($relations as $key => $rel)
  686. {
  687. // Unload the just fetched relation from the originals
  688. unset($this->_original_relations[$rel]);
  689. // Unset the numeric key and set the data to update by the relation name
  690. unset($relations[$key]);
  691. $relations[$rel] = $this->_data_relations[$rel];
  692. }
  693. }
  694. foreach ($relations as $rel => $data)
  695. {
  696. if (is_array($data))
  697. {
  698. $this->_original_relations[$rel] = array();
  699. foreach ($data as $obj)
  700. {
  701. $this->_original_relations[$rel][] = $obj ? $obj->implode_pk($obj) : null;
  702. }
  703. }
  704. else
  705. {
  706. $this->_original_relations[$rel] = $data ? $data->implode_pk($data) : null;
  707. }
  708. }
  709. }
  710. /**
  711. * Fetch or set relations on this object
  712. * To be used only after having fetched them from the database!
  713. *
  714. * @param array|null $rels
  715. * @return void|array
  716. */
  717. public function _relate($rels = false)
  718. {
  719. if ($this->_frozen)
  720. {
  721. throw new FrozenObject('No changes allowed.');
  722. }
  723. if ($rels === false)
  724. {
  725. return $this->_data_relations;
  726. }
  727. elseif (is_array($rels))
  728. {
  729. $this->_data_relations = $rels;
  730. }
  731. else
  732. {
  733. throw new \FuelException('Invalid input for _relate(), should be an array.');
  734. }
  735. }
  736. /**
  737. * Fetch a property or relation
  738. *
  739. * @param string
  740. * @return mixed
  741. */
  742. public function & __get($property)
  743. {
  744. return $this->get($property);
  745. }
  746. /**
  747. * Set a property or relation
  748. *
  749. * @param string
  750. * @param mixed
  751. */
  752. public function __set($property, $value)
  753. {
  754. return $this->set($property, $value);
  755. }
  756. /**
  757. * Check whether a property exists, only return true for table columns, relations and custom data
  758. *
  759. * @param string $property
  760. * @return bool
  761. */
  762. public function __isset($property)
  763. {
  764. if (array_key_exists($property, static::properties()))
  765. {
  766. return true;
  767. }
  768. elseif (static::relations($property))
  769. {
  770. return true;
  771. }
  772. elseif (array_key_exists($property, $this->_custom_data))
  773. {
  774. return true;
  775. }
  776. return false;
  777. }
  778. /**
  779. * Empty a property, relation or custom data
  780. *
  781. * @param string $property
  782. */
  783. public function __unset($property)
  784. {
  785. if (array_key_exists($property, static::properties()))
  786. {
  787. $this->_data[$property] = null;
  788. }
  789. elseif ($rel = static::relations($property))
  790. {
  791. $this->_data_relations[$property] = $rel->singular ? null : array();
  792. }
  793. elseif (array_key_exists($property, $this->_custom_data))
  794. {
  795. unset($this->_custom_data[$property]);
  796. }
  797. }
  798. /**
  799. * Allow for getter, setter and unset methods
  800. *
  801. * @param string $method
  802. * @param array $args
  803. * @return mixed
  804. * @throws \BadMethodCallException
  805. */
  806. public function __call($method, $args)
  807. {
  808. if (substr($method, 0, 4) == 'get_')
  809. {
  810. return $this->get(substr($method, 4));
  811. }
  812. elseif (substr($method, 0, 4) == 'set_')
  813. {
  814. return $this->set(substr($method, 4), reset($args));
  815. }
  816. elseif (substr($method, 0, 6) == 'unset_')
  817. {
  818. return $this->__unset(substr($method, 6));
  819. }
  820. // Throw an exception
  821. throw new \BadMethodCallException('Call to undefined method '.get_class($this).'::'.$method.'()');
  822. }
  823. /**
  824. * Allow object cloning to new object
  825. */
  826. public function __clone()
  827. {
  828. // Reset primary keys
  829. foreach (static::$_primary_key as $pk)
  830. {
  831. $this->_data[$pk] = null;
  832. }
  833. // This is a new object
  834. $this->_is_new = true;
  835. $this->_original = array();
  836. $this->_original_relations = array();
  837. // Cleanup relations
  838. foreach ($this->relations() as $name => $rel)
  839. {
  840. // singular relations (hasone, belongsto) can't be copied, neither can HasMany
  841. if ($rel->singular or $rel instanceof HasMany)
  842. {
  843. unset($this->_data_relations[$name]);
  844. }
  845. }
  846. $this->observe('after_clone');
  847. }
  848. /**
  849. * Get
  850. *
  851. * Gets a property or
  852. * relation from the
  853. * object
  854. *
  855. * @access public
  856. * @param string $property
  857. * @return mixed
  858. */
  859. public function & get($property)
  860. {
  861. if (array_key_exists($property, static::properties()))
  862. {
  863. if ( ! array_key_exists($property, $this->_data))
  864. {
  865. // avoid a notice, we're returning by reference
  866. $var = null;
  867. return $var;
  868. }
  869. return $this->_data[$property];
  870. }
  871. elseif ($rel = static::relations($property))
  872. {
  873. if ( ! array_key_exists($property, $this->_data_relations))
  874. {
  875. $this->_data_relations[$property] = $rel->get($this);
  876. $this->_update_original_relations(array($property));
  877. }
  878. return $this->_data_relations[$property];
  879. }
  880. elseif ($value = $this->_get_eav($property))
  881. {
  882. return $value;
  883. }
  884. elseif ($this->_view and in_array($property, static::$_views_cached[get_class($this)][$this->_view]['columns']))
  885. {
  886. return $this->_data[$property];
  887. }
  888. elseif (array_key_exists($property, $this->_custom_data))
  889. {
  890. return $this->_custom_data[$property];
  891. }
  892. else
  893. {
  894. throw new \OutOfBoundsException('Property "'.$property.'" not found for '.get_called_class().'.');
  895. }
  896. }
  897. /**
  898. * Set
  899. *
  900. * Sets a property or
  901. * relation of the
  902. * object
  903. *
  904. * @access public
  905. * @param string|array $property
  906. * @param string $value in case $property is a string
  907. * @return Orm\Model
  908. */
  909. public function set($property, $value = null)
  910. {
  911. if ($this->_frozen)
  912. {
  913. throw new FrozenObject('No changes allowed.');
  914. }
  915. if (is_array($property))
  916. {
  917. foreach ($property as $p => $v)
  918. {
  919. $this->set($p, $v);
  920. }
  921. }
  922. else
  923. {
  924. if (func_num_args() < 2)
  925. {
  926. throw new \InvalidArgumentException('You need to pass both a property name and a value to set().');
  927. }
  928. if (in_array($property, static::primary_key()) and $this->{$property} !== null)
  929. {
  930. throw new \FuelException('Primary key cannot be changed.');
  931. }
  932. if (array_key_exists($property, static::properties()))
  933. {
  934. $this->_data[$property] = $value;
  935. }
  936. elseif (static::relations($property))
  937. {
  938. $this->is_fetched($property) or $this->_reset_relations[$property] = true;
  939. $this->_data_relations[$property] = $value;
  940. }
  941. elseif ( ! $this->_set_eav($property, $value))
  942. {
  943. $this->_custom_data[$property] = $value;
  944. }
  945. }
  946. return $this;
  947. }
  948. /**
  949. * Save the object and it's relations, create when necessary
  950. *
  951. * @param mixed $cascade
  952. * null = use default config,
  953. * bool = force/prevent cascade,
  954. * array cascades only the relations that are in the array
  955. */
  956. public function save($cascade = null, $use_transaction = false)
  957. {
  958. if ($this->frozen())
  959. {
  960. return false;
  961. }
  962. if ($use_transaction)
  963. {
  964. $db = \Database_Connection::instance(static::connection());
  965. $db->start_transaction();
  966. }
  967. try
  968. {
  969. $this->observe('before_save');
  970. $this->freeze();
  971. foreach($this->relations() as $rel_name => $rel)
  972. {
  973. if (array_key_exists($rel_name, $this->_reset_relations))
  974. {
  975. method_exists($rel, 'delete_related') and $rel->delete_related($this);
  976. unset($this->_reset_relations[$rel_name]);
  977. }
  978. if (array_key_exists($rel_name, $this->_data_relations))
  979. {
  980. $rel->save($this, $this->{$rel_name},
  981. array_key_exists($rel_name, $this->_original_relations) ? $this->_original_relations[$rel_name] : null,
  982. false, is_array($cascade) ? in_array($rel_name, $cascade) : $cascade
  983. );
  984. }
  985. }
  986. $this->unfreeze();
  987. // Insert or update
  988. $return = $this->_is_new ? $this->create() : $this->update();
  989. $this->freeze();
  990. foreach($this->relations() as $rel_name => $rel)
  991. {
  992. if (array_key_exists($rel_name, $this->_data_relations))
  993. {
  994. $rel->save($this, $this->{$rel_name},
  995. array_key_exists($rel_name, $this->_original_relations) ? $this->_original_relations[$rel_name] : null,
  996. true, is_array($cascade) ? in_array($rel_name, $cascade) : $cascade
  997. );
  998. }
  999. }
  1000. $this->unfreeze();
  1001. $this->_update_original();
  1002. $this->observe('after_save');
  1003. $use_transaction and $db->commit_transaction();
  1004. }
  1005. catch (\Exception $e)
  1006. {
  1007. $use_transaction and $db->rollback_transaction();
  1008. throw $e;
  1009. }
  1010. return $return;
  1011. }
  1012. /**
  1013. * Save using INSERT
  1014. */
  1015. protected function create()
  1016. {
  1017. // Only allow creation with new object, otherwise: clone first, create later
  1018. if ( ! $this->is_new())
  1019. {
  1020. return false;
  1021. }
  1022. $this->observe('before_insert');
  1023. // Set all current values
  1024. $query = Query::forge(get_called_class(), static::connection());
  1025. $primary_key = static::primary_key();
  1026. $properties = array_keys(static::properties());
  1027. foreach ($properties as $p)
  1028. {
  1029. if ( ! (in_array($p, $primary_key) and is_null($this->{$p})))
  1030. {
  1031. $query->set($p, $this->{$p});
  1032. }
  1033. }
  1034. // Insert!
  1035. $id = $query->insert();
  1036. // when there's one PK it might be auto-incremented, get it and set it
  1037. if (count($primary_key) == 1 and $id !== false)
  1038. {
  1039. $pk = reset($primary_key);
  1040. // only set it if it hasn't been set manually
  1041. is_null($this->{$pk}) and $this->{$pk} = $id;
  1042. }
  1043. // update the original properties on creation and cache object for future retrieval in this request
  1044. $this->_is_new = false;
  1045. static::$_cached_objects[get_class($this)][static::implode_pk($this)] = $this;
  1046. $this->observe('after_insert');
  1047. return $id !== false;
  1048. }
  1049. /**
  1050. * Save using UPDATE
  1051. */
  1052. protected function update()
  1053. {
  1054. // New objects can't be updated, neither can frozen
  1055. if ($this->is_new())
  1056. {
  1057. return false;
  1058. }
  1059. // Non changed objects don't have to be saved, but return true anyway (no reason to fail)
  1060. if ( ! $this->is_changed(array_keys(static::properties())))
  1061. {
  1062. return true;
  1063. }
  1064. $this->observe('before_update');
  1065. // Create the query and limit to primary key(s)
  1066. $query = Query::forge(get_called_class(), static::connection());
  1067. $primary_key = static::primary_key();
  1068. $properties = array_keys(static::properties());
  1069. foreach ($primary_key as $pk)
  1070. {
  1071. $query->where($pk, '=', $this->_data[$pk]);
  1072. }
  1073. // Set all current values
  1074. foreach ($properties as $p)
  1075. {
  1076. if ( ! in_array($p, $primary_key))
  1077. {
  1078. if (array_key_exists($p, $this->_original))
  1079. {
  1080. $this->{$p} !== $this->_original[$p] and $query->set($p, isset($this->_data[$p]) ? $this->_data[$p] : null);
  1081. }
  1082. else
  1083. {
  1084. array_key_exists($p, $this->_data) and $query->set($p, $this->_data[$p]);
  1085. }
  1086. }
  1087. if ( ! in_array($p, $primary_key) and (array_key_exists($p, $this->_data) or array_key_exists($p, $this->_original)))
  1088. {
  1089. }
  1090. }
  1091. // Return false when update fails
  1092. if ( ! $query->update())
  1093. {
  1094. return false;
  1095. }
  1096. // update the original property on success
  1097. $this->observe('after_update');
  1098. return true;
  1099. }
  1100. /**
  1101. * Delete current object
  1102. *
  1103. * @param mixed $cascade
  1104. * null = use default config,
  1105. * bool = force/prevent cascade,
  1106. * array cascades only the relations that are in the array
  1107. * @return Model this instance as a new object without primary key(s)
  1108. */
  1109. public function delete($cascade = null, $use_transaction = false)
  1110. {
  1111. // New objects can't be deleted, neither can frozen
  1112. if ($this->is_new() or $this->frozen())
  1113. {
  1114. return false;
  1115. }
  1116. if ($use_transaction)
  1117. {
  1118. $db = \Database_Connection::instance(static::connection());
  1119. $db->start_transaction();
  1120. }
  1121. try
  1122. {
  1123. $this->observe('before_delete');
  1124. $this->freeze();
  1125. foreach($this->relations() as $rel_name => $rel)
  1126. {
  1127. $rel->delete($this, $this->{$rel_name}, false, is_array($cascade) ? in_array($rel_name, $cascade) : $cascade);
  1128. }
  1129. $this->unfreeze();
  1130. // Create the query and limit to primary key(s)
  1131. $query = Query::forge(get_called_class(), static::connection())->limit(1);
  1132. $primary_key = static::primary_key();
  1133. foreach ($primary_key as $pk)
  1134. {
  1135. $query->where($pk, '=', $this->{$pk});
  1136. }
  1137. // Return success of update operation
  1138. if ( ! $query->delete())
  1139. {
  1140. return false;
  1141. }
  1142. $this->freeze();
  1143. foreach($this->relations() as $rel_name => $rel)
  1144. {
  1145. $rel->delete($this, $this->{$rel_name}, true, is_array($cascade) ? in_array($rel_name, $cascade) : $cascade);
  1146. }
  1147. $this->unfreeze();
  1148. // Perform cleanup:
  1149. // remove from internal object cache, remove PK's, set to non saved object, remove db original values
  1150. if (array_key_exists(get_called_class(), static::$_cached_objects)
  1151. and array_key_exists(static::implode_pk($this), static::$_cached_objects[get_called_class()]))
  1152. {
  1153. unset(static::$_cached_objects[get_called_class()][static::implode_pk($this)]);
  1154. }
  1155. foreach ($this->primary_key() as $pk)
  1156. {
  1157. unset($this->_data[$pk]);
  1158. }
  1159. $this->_is_new = true;
  1160. $this->_original = array();
  1161. $this->observe('after_delete');
  1162. $use_transaction and $db->commit_transaction();
  1163. }
  1164. catch (\Exception $e)
  1165. {
  1166. $use_transaction and $db->rollback_transaction();
  1167. throw $e;
  1168. }
  1169. return $this;
  1170. }
  1171. /**
  1172. * Reset values to those gotten from the database
  1173. */
  1174. public function reset()
  1175. {
  1176. foreach ($this->_original as $p => $val)
  1177. {
  1178. $this->_data[$p] = $val;
  1179. }
  1180. }
  1181. /**
  1182. * Calls all observers for the current event
  1183. *
  1184. * @param string
  1185. */
  1186. public function observe($event)
  1187. {
  1188. foreach ($this->observers() as $observer => $settings)
  1189. {
  1190. $events = isset($settings['events']) ? $settings['events'] : array();
  1191. if (empty($events) or in_array($event, $events))
  1192. {
  1193. if ( ! class_exists($observer))
  1194. {
  1195. $observer_class = \Inflector::get_namespace($observer).'Observer_'.\Inflector::denamespace($observer);
  1196. if ( ! class_exists($observer_class))
  1197. {
  1198. throw new \UnexpectedValueException($observer);
  1199. }
  1200. // Add the observer with the full classname for next usage
  1201. unset(static::$_observers_cached[$observer]);
  1202. static::$_observers_cached[$observer_class] = $events;
  1203. $observer = $observer_class;
  1204. }
  1205. try
  1206. {
  1207. call_user_func(array($observer, 'orm_notify'), $this, $event);
  1208. }
  1209. catch (\Exception $e)
  1210. {
  1211. // Unfreeze before failing
  1212. $this->unfreeze();
  1213. throw $e;
  1214. }
  1215. }
  1216. }
  1217. }
  1218. /**
  1219. * Compare current state with the retrieved state
  1220. *
  1221. * @param string|array $property
  1222. * @return bool
  1223. */
  1224. public function is_changed($property = null)
  1225. {
  1226. $properties = static::properties();
  1227. $relations = static::relations();
  1228. $property = (array) $property ?: array_merge(array_keys($properties), array_keys($relations));
  1229. foreach ($property as $p)
  1230. {
  1231. if (isset($properties[$p]))
  1232. {
  1233. if (array_key_exists($p, $this->_original))
  1234. {
  1235. if ($this->{$p} !== $this->_original[$p])
  1236. {
  1237. return true;
  1238. }
  1239. }
  1240. else
  1241. {
  1242. if (array_key_exists($p, $this->_data))
  1243. {
  1244. return true;
  1245. }
  1246. }
  1247. }
  1248. elseif (isset($relations[$p]))
  1249. {
  1250. if ($relations[$p]->singular)
  1251. {
  1252. if (empty($this->_original_relations[$p]) !== empty($this->_data_relations[$p])
  1253. or ( ! empty($this->_original_relations[$p])
  1254. and $this->_original_relations[$p] !== $this->_data_relations[$p]->implode_pk($this->{$p})))
  1255. {
  1256. return true;
  1257. }
  1258. }
  1259. else
  1260. {
  1261. if (empty($this->_original_relations[$p]))
  1262. {
  1263. if ( ! empty($this->_data_relations[$p]))
  1264. {
  1265. return true;
  1266. }
  1267. continue;
  1268. }
  1269. $orig_rels = $this->_original_relations[$p];
  1270. foreach ($this->{$p} as $rk => $r)
  1271. {
  1272. if ( ! in_array($r->implode_pk($r), $orig_rels))
  1273. {
  1274. return true;
  1275. }
  1276. unset($orig_rels[array_search($rk, $orig_rels)]);
  1277. }
  1278. if ( ! empty($orig_rels))
  1279. {
  1280. return true;
  1281. }
  1282. }
  1283. }
  1284. else
  1285. {
  1286. throw new \OutOfBoundsException('Unknown property or relation: '.$p);
  1287. }
  1288. }
  1289. return false;
  1290. }
  1291. /**
  1292. * Generates an array with keys new & old that contain ONLY the values that differ between the original and
  1293. * the current unsaved model.
  1294. * Note: relations are given as single or array of imploded pks
  1295. *
  1296. * @return array
  1297. */
  1298. public function get_diff()
  1299. {
  1300. $diff = array(0 => array(), 1 => array());
  1301. foreach ($this->_data as $key => $val)
  1302. {
  1303. if ($this->is_changed($key))
  1304. {
  1305. $diff[0][$key] = array_key_exists($key, $this->_original) ? $this->_original[$key] : null;
  1306. $diff[1][$key] = $val;
  1307. }
  1308. }
  1309. foreach ($this->_data_relations as $key => $val)
  1310. {
  1311. $rel = static::relations($key);
  1312. if ($rel->singular)
  1313. {
  1314. if (empty($this->_original_relations[$key]) !== empty($val)
  1315. or ( ! empty($this->_original_relations[$key])
  1316. and $new_pk = $val->implode_pk($val)
  1317. and $this->_original_relations[$key] !== $new_pk))
  1318. {
  1319. $diff[0][$key] = isset($this->_original_relations[$key]) ? $this->_original_relations[$key] : null;
  1320. $diff[1][$key] = isset($val) ? $new_pk : null;
  1321. }
  1322. }
  1323. else
  1324. {
  1325. $original_pks = $this->_original_relations[$key];
  1326. $new_pks = array();
  1327. foreach ($val as $v)
  1328. {
  1329. if ( ! in_array(($new_pk = $v->implode_pk($v)), $original_pks))
  1330. {
  1331. $new_pks[] = $new_pk;
  1332. }
  1333. else
  1334. {
  1335. $original_pks = array_diff($original_pks, array($new_pk));
  1336. }
  1337. }
  1338. if ( ! empty($original_pks) or ! empty($new_pks)) {
  1339. $diff[0][$key] = empty($original_pks) ? null : $original_pks;
  1340. $diff[1][$key] = empty($new_pks) ? null : $new_pks;
  1341. }
  1342. }
  1343. }
  1344. return $diff;
  1345. }
  1346. /***
  1347. * Returns whether the given relation is fetched. If no relation is
  1348. *
  1349. * @return bool
  1350. */
  1351. public function is_fetched($relation)
  1352. {
  1353. if (static::relations($relation))
  1354. {
  1355. return array_key_exists($relation, $this->_data_relations);
  1356. }
  1357. return false;
  1358. }
  1359. /***
  1360. * Returns whether this is a saved or a new object
  1361. *
  1362. * @return bool
  1363. */
  1364. public function is_new()
  1365. {
  1366. return $this->_is_new;
  1367. }
  1368. /**
  1369. * Check whether the object was frozen
  1370. *
  1371. * @return boolean
  1372. */
  1373. public function frozen()
  1374. {
  1375. return $this->_frozen;
  1376. }
  1377. /**
  1378. * Freeze the object to disallow changing it or saving it
  1379. */
  1380. public function freeze()
  1381. {
  1382. $this->_frozen = true;
  1383. }
  1384. /**
  1385. * Unfreeze the object to allow changing it or saving it again
  1386. */
  1387. public function unfreeze()
  1388. {
  1389. $this->_frozen = false;
  1390. }
  1391. /**
  1392. * Method for use with Fieldset::add_model()
  1393. *
  1394. * @param Fieldset Fieldset instance to add fields to
  1395. * @param array|Model Model instance or array for use to repopulate
  1396. */
  1397. public static function set_form_fields($form, $instance = null)
  1398. {
  1399. Observer_Validation::set_fields($instance instanceof static ? $instance : get_called_class(), $form);
  1400. $instance and $form->populate($instance, true);
  1401. }
  1402. /**
  1403. * Allow populating this object from an array
  1404. *
  1405. * @return array
  1406. */
  1407. public function from_array(array $values)
  1408. {
  1409. foreach($values as $property => $value)
  1410. {
  1411. if (array_key_exists($property, static::properties()) and ! in_array($property, static::primary_key()))
  1412. {
  1413. $this->_data[$property] = $value;
  1414. }
  1415. }
  1416. }
  1417. /**
  1418. * Allow converting this object to an array
  1419. *
  1420. * @param bool whether or not to include the custom data array
  1421. * @return array
  1422. */
  1423. public function to_array($custom = false, $recurse = false)
  1424. {
  1425. static $references = array();
  1426. $array = array();
  1427. // reset the references array on first call
  1428. $recurse or $references = array();
  1429. // make sure all data is scalar or array
  1430. if ($custom)
  1431. {
  1432. foreach ($this->_custom_data as $key => $val)
  1433. {
  1434. if (is_object($val))
  1435. {
  1436. if (method_exists($val, '__toString'))
  1437. {
  1438. $val = (string) $val;
  1439. }
  1440. else
  1441. {
  1442. $val = get_object_vars($val);
  1443. }
  1444. }
  1445. $array[$key] = $val;
  1446. }
  1447. }
  1448. // make sure all data is scalar or array
  1449. foreach ($this->_data as $key => $val)
  1450. {
  1451. if (is_object($val))
  1452. {
  1453. if (method_exists($val, '__toString'))
  1454. {
  1455. $val = (string) $val;
  1456. }
  1457. else
  1458. {
  1459. $val = get_object_vars($val);
  1460. }
  1461. }
  1462. $array[$key] = $val;
  1463. }
  1464. // convert relations
  1465. foreach ($this->_data_relations as $name => $rel)
  1466. {
  1467. if (is_array($rel))
  1468. {
  1469. $array[$name] = array();
  1470. if ( ! empty($rel))
  1471. {
  1472. foreach ($rel as $id => $r)
  1473. {
  1474. $array[$name][$id] = $r->to_array($custom, true);
  1475. }
  1476. $references[] = get_class($r);
  1477. }
  1478. }
  1479. else
  1480. {
  1481. if ( ! in_array(get_class($rel), $references))
  1482. {
  1483. if (is_null($rel))
  1484. {
  1485. $array[$name] = null;
  1486. }
  1487. else
  1488. {
  1489. $array[$name] = $rel->to_array($custom, true);
  1490. $references[] = get_class($rel);
  1491. }
  1492. }
  1493. }
  1494. }
  1495. // strip any excluded values from the array
  1496. foreach (static::$_to_array_exclude as $key)
  1497. {
  1498. if (array_key_exists($key, $array))
  1499. {
  1500. unset($array[$key]);
  1501. }
  1502. }
  1503. return $array;
  1504. }
  1505. /**
  1506. * Allow converting this object to a real object
  1507. *
  1508. * @return object
  1509. */
  1510. public function to_object()
  1511. {
  1512. return (object) $this->to_array();
  1513. }
  1514. /**
  1515. * EAV attribute getter
  1516. *
  1517. * @param string $attribute, the attribute value to get
  1518. *
  1519. * @return mixed
  1520. * @throws OutOfBoundsException if the defined EAV relation does not exist or of the wrong type
  1521. */
  1522. protected function _get_eav($attribute)
  1523. {
  1524. // get the current class name
  1525. $class = get_called_class();
  1526. // don't do anything unless we actually have an EAV container
  1527. if (property_exists($class, '_eav'))
  1528. {
  1529. // loop through the defined EAV containers
  1530. foreach (static::$_eav as $rel => $settings)
  1531. {
  1532. // normalize the container definition, could be string or array
  1533. if (is_string($settings))
  1534. {
  1535. $rel = $settings;
  1536. $settings = array();
  1537. }
  1538. // fetch the relation object for this EAV container
  1539. if ( ! $rel = static::relations($rel))
  1540. {
  1541. throw new \OutOfBoundsException('EAV container defines a relation that does not exist in '.get_called_class().'.');
  1542. }
  1543. // EAV containers must be of the "Many type"
  1544. if ($rel instanceOf \Orm\HasOne or $rel instanceOf \Orm\BelongsTo )
  1545. {
  1546. throw new \OutOfBoundsException('EAV containers can only be defined on "HasMany" or "ManyMany" relations in '.get_called_class().'.');
  1547. }
  1548. // determine attribute and value column names
  1549. $attr = isset($settings['attribute']) ? $settings['attribute'] : 'attribute';
  1550. $val = isset($settings['value']) ? $settings['value'] : 'value';
  1551. // loop over the resultset
  1552. foreach ($this->{$rel->name} as $key => $record)
  1553. {
  1554. // and return the value if found
  1555. if ($record->{$attr} === $attribute)
  1556. {
  1557. return $record->{$val};
  1558. }
  1559. }
  1560. }
  1561. }
  1562. return false;
  1563. }
  1564. /**
  1565. * EAV attribute setter
  1566. *
  1567. * @param string $attribute
  1568. * @param string $value
  1569. *
  1570. * @return mixed
  1571. */
  1572. protected function _set_eav($attribute, $value)
  1573. {
  1574. // get the current class name
  1575. $class = get_called_class();
  1576. // don't do anything unless we actually have an EAV container
  1577. if (property_exists($class, '_eav'))
  1578. {
  1579. // loop through the defined EAV containers
  1580. foreach (static::$_eav as $rel => $settings)
  1581. {
  1582. // normalize the container definition, could be string or array
  1583. if (is_string($settings))
  1584. {
  1585. $rel = $settings;
  1586. $settings = array();
  1587. }
  1588. // fetch the relation object for this EAV container
  1589. if ( ! $rel = static::relations($rel))
  1590. {
  1591. throw new \OutOfBoundsException('EAV container defines a relation that does not exist in '.get_called_class().'.');
  1592. }
  1593. // EAV containers must be of the "Many type"
  1594. if ($rel instanceOf \Orm\HasOne or $rel instanceOf \Orm\BelongsTo )
  1595. {
  1596. throw new \OutOfBoundsException('EAV containers can only be defined on "HasMany" or "ManyMany" relations in '.get_called_class().'.');
  1597. }
  1598. // determine attribute and value column names
  1599. $attr = isset($settings['attribute']) ? $settings['attribute'] : 'attribute';
  1600. $val = isset($settings['value']) ? $settings['value'] : 'value';
  1601. // loop over the resultset
  1602. foreach ($this->{$rel->name} as $key => $record)
  1603. {
  1604. if ($record->{$attr} === $attribute)
  1605. {
  1606. $record->{$val} = $value;
  1607. return true;
  1608. }
  1609. }
  1610. }
  1611. }
  1612. return false;
  1613. }
  1614. /***************************************************************************
  1615. * Implementation of ArrayAccess
  1616. **************************************************************************/
  1617. public function offsetSet($offset, $value)
  1618. {
  1619. try
  1620. {
  1621. $this->__set($offset, $value);
  1622. }
  1623. catch (\Exception $e)
  1624. {
  1625. return false;
  1626. }
  1627. }
  1628. public function offsetExists($offset)
  1629. {
  1630. return $this->__isset($offset);
  1631. }
  1632. public function offsetUnset($offset)
  1633. {
  1634. $this->__unset($offset);
  1635. }
  1636. public function offsetGet($offset)
  1637. {
  1638. try
  1639. {
  1640. return $this->__get($offset);
  1641. }
  1642. catch (\Exception $e)
  1643. {
  1644. return false;
  1645. }
  1646. }
  1647. /***************************************************************************
  1648. * Implementation of Iterable
  1649. **************************************************************************/
  1650. protected $_iterable = array();
  1651. public function rewind()
  1652. {
  1653. $this->_iterable = array_merge($this->_custom_data, $this->_data, $this->_data_relations);
  1654. reset($this->_iterable);
  1655. }
  1656. public function current()
  1657. {
  1658. return current($this->_iterable);
  1659. }
  1660. public function key()
  1661. {
  1662. return key($this->_iterable);
  1663. }
  1664. public function next()
  1665. {
  1666. return next($this->_iterable);
  1667. }
  1668. public function valid()
  1669. {
  1670. return key($this->_iterable) !== null;
  1671. }
  1672. }