PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

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

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