PageRenderTime 67ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lithium-0.6/libraries/lithium/util/Collection.php

https://github.com/gwoo/framework-benchs
PHP | 460 lines | 185 code | 43 blank | 232 comment | 23 complexity | 1b5a4e41ce4ae842849b5d308f6c951c MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\util;
  9. /**
  10. * The parent class for all collection objects. Contains methods for collection iteration,
  11. * conversion, and filtering. Implements `ArrayAccess`, `Iterator`, and `Countable`.
  12. *
  13. * Collection objects can act very much like arrays. This is especially evident in creating new
  14. * objects, or by converting Collection into an actual array:
  15. *
  16. * {{{
  17. * $coll = new Collection();
  18. * $coll[] = 'foo';
  19. * // $coll[0] --> 'foo'
  20. *
  21. * $coll = new Collection(array('items' => array('foo')));
  22. * // $coll[0] --> 'foo'
  23. *
  24. * $array = $coll->to('array);
  25. * }}}
  26. *
  27. * Apart from array-like data access, Collections allow for filtering and iteration methods:
  28. *
  29. * {{{
  30. *
  31. * $coll = new Collection(array('items' => array(0, 1, 2, 3, 4)));
  32. *
  33. * $coll->first(); // 1 (the first non-empty value)
  34. * $coll->current(); // 0
  35. * $coll->next(); // 1
  36. * $coll->next(); // 2
  37. * $coll->next(); // 3
  38. * $coll->prev(); // 2
  39. * $coll->rewind(); // 0
  40. * }}}
  41. *
  42. * @link http://us.php.net/manual/en/class.arrayaccess.php
  43. * @link http://us.php.net/manual/en/class.iterator.php
  44. * @link http://us.php.net/manual/en/class.countable.php
  45. */
  46. class Collection extends \lithium\core\Object implements \ArrayAccess, \Iterator, \Countable {
  47. /**
  48. * A central registry of global format handlers for `Collection` objects and subclasses.
  49. * Accessed via the `formats()` method.
  50. *
  51. * @see \lithium\util\Collection::formats()
  52. * @var array
  53. */
  54. protected static $_formats = array(
  55. 'array' => '\lithium\util\Collection::_toArray'
  56. );
  57. /**
  58. * The items contained in the collection.
  59. *
  60. * @var array
  61. */
  62. protected $_items = array();
  63. /**
  64. * Indicates whether the current position is valid or not.
  65. *
  66. * @var boolean
  67. * @see lithium\util\Collection::valid()
  68. */
  69. protected $_valid = false;
  70. /**
  71. * Allows a collection's items to be automatically assigned from class construction options.
  72. *
  73. * @var array
  74. */
  75. protected $_autoConfig = array('items');
  76. /**
  77. * Accessor method for adding format handlers to instances and subclasses of `Collection`.
  78. *
  79. * @param string $format
  80. * @param mixed $handler
  81. * @return mixed
  82. */
  83. public static function formats($format, $handler = null) {
  84. if ($format === false) {
  85. return static::$_formats = array();
  86. }
  87. if ((is_null($handler)) && class_exists($format)) {
  88. return static::$_formats[] = $format;
  89. }
  90. return static::$_formats[$format] = $handler;
  91. }
  92. /**
  93. * Initializes the collection object by merging in collection items and removing redundant
  94. * object properties.
  95. *
  96. * @return void
  97. */
  98. protected function _init() {
  99. parent::_init();
  100. unset($this->_config['items']);
  101. }
  102. /**
  103. * Handles dispatching of methods against all items in the collection.
  104. *
  105. * @param string $method
  106. * @param array $parameters
  107. * @param array $options Specifies options for how to run the given method against the object
  108. * collection. The available options are:
  109. * - `'collect'`: If `true`, the results of this method call will be returned
  110. * wrapped in a new Collection object or subclass.
  111. * - `'merge'`: Used primarily if the method being invoked returns an array. If
  112. * set to `true`, merges all results arrays into one.
  113. * @todo Implement filtering.
  114. * @return mixed
  115. */
  116. public function invoke($method, $parameters = array(), $options = array()) {
  117. $defaults = array('merge' => false, 'collect' => false);
  118. $options += $defaults;
  119. $results = array();
  120. $isCore = null;
  121. foreach ($this->_items as $key => $value) {
  122. if (is_null($isCore)) {
  123. $isCore = (method_exists(current($this->_items), 'invokeMethod'));
  124. }
  125. if ($isCore) {
  126. $result = $this->_items[$key]->invokeMethod($method, $parameters);
  127. } else {
  128. $result = call_user_func_array(array(&$this->_items[$key], $method), $parameters);
  129. }
  130. if (!empty($options['merge'])) {
  131. $results = array_merge($results, $result);
  132. } else {
  133. $results[$key] = $result;
  134. }
  135. }
  136. if ($options['collect']) {
  137. $class = get_class($this);
  138. $results = new $class(array('items' => $results));
  139. }
  140. return $results;
  141. }
  142. /**
  143. * Hook to handle dispatching of methods against all items in the collection.
  144. *
  145. * @param string $method
  146. * @param array $parameters
  147. * @return mixed
  148. */
  149. public function __call($method, $parameters = array()) {
  150. return $this->invoke($method, $parameters);
  151. }
  152. /**
  153. * Converts the Collection object to another type of object, or a simple type such as an array.
  154. *
  155. * @param string $format Currently only `'array'` is supported.
  156. * @param $options Options for converting this collection:
  157. * - 'internal': Boolean indicating whether the current internal representation of the
  158. * collection should be exported. Defaults to `false`, which uses the standard iterator
  159. * interfaces. This is useful for exporting record sets, where records are lazy-loaded,
  160. * and the collection must be iterated in order to fetch all objects.
  161. * @return mixed The converted object.
  162. */
  163. public function to($format, $options = array()) {
  164. $defaults = array('internal' => false);
  165. $options += $defaults;
  166. $data = $options['internal'] ? $this->_items : $this;
  167. if (is_object($format) && is_callable($format)) {
  168. return $format($data, $options);
  169. }
  170. if (isset(static::$_formats[$format]) && is_callable(static::$_formats[$format])) {
  171. $handler = static::$_formats[$format];
  172. $handler = is_string($handler) ? explode('::', $handler, 2) : $handler;
  173. if (is_array($handler)) {
  174. list($class, $method) = $handler;
  175. return $class::$method($data, $options);
  176. }
  177. return $handler($data, $options);
  178. }
  179. foreach (static::$_formats as $key => $handler) {
  180. if (!is_int($key)) {
  181. continue;
  182. }
  183. if (in_array($format, $handler::formats($format, $data, $options))) {
  184. return $handler::to($format, $data, $options);
  185. }
  186. }
  187. }
  188. /**
  189. * Filters a copy of the items in the collection.
  190. *
  191. * @param callback $filter Callback to use for filtering.
  192. * @param array $options The available options are:
  193. * - `'collect'`: If `true`, the results will be returned wrapped
  194. * in a new Collection object or subclass.
  195. * @return array|object The filtered items.
  196. */
  197. public function find($filter, $options = array()) {
  198. $defaults = array('collect' => true);
  199. $options += $defaults;
  200. $items = array_filter($this->_items, $filter);
  201. if ($options['collect']) {
  202. $class = get_class($this);
  203. $items = new $class(compact('items'));
  204. }
  205. return $items;
  206. }
  207. /**
  208. * Returns the first non-empty value in the collection after a filter is applied, or rewinds the
  209. * collection and returns the first value.
  210. *
  211. * @param callback $filter A closure through which collection values will be
  212. * passed. If the return value of this function is non-empty,
  213. * it will be returned as the result of the method call. If `null`, the
  214. * collection is rewound (see `rewind()`) and the first item is returned.
  215. * @return mixed Returns the first non-empty collection value returned from `$filter`.
  216. * @see lithium\util\Collection::rewind()
  217. */
  218. public function first($filter = null) {
  219. if (empty($filter)) {
  220. return $this->rewind();
  221. }
  222. foreach ($this->_items as $item) {
  223. if ($value = $filter($item)) {
  224. return $value;
  225. }
  226. }
  227. }
  228. /**
  229. * Applies a callback to all items in the collection.
  230. *
  231. * @param callback $filter The filter to apply.
  232. * @return object This collection instance.
  233. */
  234. public function each($filter) {
  235. $this->_items = array_map($filter, $this->_items);
  236. return $this;
  237. }
  238. /**
  239. * Applies a callback to a copy of all items in the collection
  240. * and returns the result.
  241. *
  242. * @param callback $filter The filter to apply.
  243. * @param array $options The available options are:
  244. * - `'collect'`: If `true`, the results will be returned wrapped
  245. * in a new Collection object or subclass.
  246. * @return array|object The filtered items.
  247. */
  248. public function map($filter, $options = array()) {
  249. $defaults = array('collect' => true);
  250. $options += $defaults;
  251. $items = array_map($filter, $this->_items);
  252. if ($options['collect']) {
  253. $class = get_class($this);
  254. return new $class(compact('items'));
  255. }
  256. return $items;
  257. }
  258. /**
  259. * Checks whether or not an offset exists.
  260. *
  261. * @param string $offset An offset to check for.
  262. * @return boolean `true` if offset exists, `false` otherwise.
  263. */
  264. public function offsetExists($offset) {
  265. return isset($this->_items[$offset]);
  266. }
  267. /**
  268. * Returns the value at specified offset.
  269. *
  270. * @param string $offset The offset to retrieve.
  271. * @return mixed Value at offset.
  272. */
  273. public function offsetGet($offset) {
  274. return $this->_items[$offset];
  275. }
  276. /**
  277. * Assigns a value to the specified offset.
  278. *
  279. * @param string $offset The offset to assign the value to.
  280. * @param mixed $value The value to set.
  281. * @return mixed The value which was set.
  282. */
  283. public function offsetSet($offset, $value) {
  284. if (is_null($offset)) {
  285. return $this->_items[] = $value;
  286. }
  287. return $this->_items[$offset] = $value;
  288. }
  289. /**
  290. * Unsets an offset.
  291. *
  292. * @param string $offset The offset to unset.
  293. * @return void
  294. */
  295. public function offsetUnset($offset) {
  296. unset($this->_items[$offset]);
  297. }
  298. /**
  299. * Rewinds to the first item.
  300. *
  301. * @return mixed The current item after rewinding.
  302. */
  303. public function rewind() {
  304. $this->_valid = (reset($this->_items) !== false);
  305. return current($this->_items);
  306. }
  307. /**
  308. * Moves forward to the last item.
  309. *
  310. * @return mixed The current item after moving.
  311. */
  312. public function end() {
  313. $this->_valid = (end($this->_items) !== false);
  314. return current($this->_items);
  315. }
  316. /**
  317. * Checks if current position is valid.
  318. *
  319. * @return boolean `true` if valid, `false` otherwise.
  320. */
  321. public function valid() {
  322. return $this->_valid;
  323. }
  324. /**
  325. * Returns the current item.
  326. *
  327. * @return mixed The current item.
  328. */
  329. public function current() {
  330. return current($this->_items);
  331. }
  332. /**
  333. * Returns the key of the current item.
  334. *
  335. * @return scalar Scalar on success `0` on failure.
  336. */
  337. public function key() {
  338. return key($this->_items);
  339. }
  340. /**
  341. * Moves backward to the previous item. If already at the first item,
  342. * moves to the last one.
  343. *
  344. * @return mixed The current item after moving.
  345. */
  346. public function prev() {
  347. if (!prev($this->_items)) {
  348. end($this->_items);
  349. }
  350. return current($this->_items);
  351. }
  352. /**
  353. * Move forwards to the next item.
  354. *
  355. * @return The current item after moving.
  356. */
  357. public function next() {
  358. $this->_valid = (next($this->_items) !== false);
  359. return current($this->_items);
  360. }
  361. /**
  362. * Appends an item.
  363. *
  364. * @param mixed $value The item to append.
  365. * @return void
  366. */
  367. public function append($value) {
  368. is_object($value) ? $this->_items[] =& $value : $this->_items[] = $value;
  369. }
  370. /**
  371. * Counts the items of the object.
  372. *
  373. * @return integer Number of items.
  374. */
  375. public function count() {
  376. $count = iterator_count($this);
  377. $this->rewind();
  378. return $count;
  379. }
  380. /**
  381. * Returns the item keys.
  382. *
  383. * @return array The keys of the items.
  384. */
  385. public function keys() {
  386. return array_keys($this->_items);
  387. }
  388. /**
  389. * Exports a `Collection` instance to an array. Used by `Collection::to()`.
  390. *
  391. * @param mixed $data Either a `Collection` instance, or an array representing a `Collection`'s
  392. * internal state.
  393. * @return array Returns the value of `$data` as a pure PHP array, recursively converting all
  394. * sub-objects and other values to their closest array or scalar equivalents.
  395. */
  396. protected static function _toArray($data) {
  397. $result = array();
  398. foreach ($data as $key => $item) {
  399. switch (true) {
  400. case (!is_object($item)):
  401. $result[$key] = $item;
  402. break;
  403. case (method_exists($item, 'to')):
  404. $result[$key] = $item->to('array');
  405. break;
  406. case ($vars = get_object_vars($item)):
  407. $result[$key] = $vars;
  408. break;
  409. case (method_exists($item, '__toString')):
  410. $result[$key] = (string) $item;
  411. break;
  412. }
  413. }
  414. return $result;
  415. }
  416. }
  417. ?>