/htdocs/lithium/0.9.9/libraries/lithium/util/Collection.php
PHP | 532 lines | 173 code | 40 blank | 319 comment | 17 complexity | 0d74400f964a7817715fb3cc552ab1e3 MD5 | raw file
Possible License(s): LGPL-3.0, Apache-2.0, BSD-3-Clause, ISC, AGPL-3.0, LGPL-2.1
- <?php
- /**
- * Lithium: the most rad php framework
- *
- * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
- * @license http://opensource.org/licenses/bsd-license.php The BSD License
- */
- namespace lithium\util;
- /**
- * The parent class for all collection objects. Contains methods for collection iteration,
- * conversion, and filtering. Implements `ArrayAccess`, `Iterator`, and `Countable`.
- *
- * Collection objects can act very much like arrays. This is especially evident in creating new
- * objects, or by converting Collection into an actual array:
- *
- * {{{
- * $coll = new Collection();
- * $coll[] = 'foo';
- * // $coll[0] --> 'foo'
- *
- * $coll = new Collection(array('data' => array('foo')));
- * // $coll[0] --> 'foo'
- *
- * $array = $coll->to('array');
- * }}}
- *
- * Apart from array-like data access, Collections allow for filtering and iteration methods:
- *
- * {{{
- *
- * $coll = new Collection(array('data' => array(0, 1, 2, 3, 4)));
- *
- * $coll->first(); // 1 (the first non-empty value)
- * $coll->current(); // 0
- * $coll->next(); // 1
- * $coll->next(); // 2
- * $coll->next(); // 3
- * $coll->prev(); // 2
- * $coll->rewind(); // 0
- * }}}
- *
- * The primary purpose of the `Collection` class is to enable simple, efficient access to groups
- * of similar objects, and to perform operations against these objects using anonymous functions.
- *
- * The `map()` and `each()` methods allow you to perform operations against the entire set of values
- * in a `Collection`, while `find()` and `first()` allow you to search through values and pick out
- * one or more.
- *
- * The `Collection` class also supports dispatching methods against a set of objects, if the method
- * is supported by all objects. For example: {{{
- * class Task {
- * public function run($when) {
- * // Do some work
- * }
- * }
- *
- * $data = array(
- * new Task(array('task' => 'task 1')),
- * new Task(array('task' => 'task 2')),
- * new Task(array('task' => 'task 3'))
- * );
- * $tasks = new Collection(compact('data'));
- *
- * // $result will contain an array, and each element will be the return
- * // value of a run() method call:
- * $result = $tasks->invoke('run', array('now'));
- *
- * // Alternatively, the method can be called natively, with the same result:
- * $result = $tasks->run('now');
- * }}}
- *
- * @link http://us.php.net/manual/en/class.arrayaccess.php
- * @link http://us.php.net/manual/en/class.iterator.php
- * @link http://us.php.net/manual/en/class.countable.php
- */
- class Collection extends \lithium\core\Object implements \ArrayAccess, \Iterator, \Countable {
- /**
- * A central registry of global format handlers for `Collection` objects and subclasses.
- * Accessed via the `formats()` method.
- *
- * @see lithium\util\Collection::formats()
- * @var array
- */
- protected static $_formats = array(
- 'array' => 'lithium\util\Collection::toArray'
- );
- /**
- * The items contained in the collection.
- *
- * @var array
- */
- protected $_data = array();
- /**
- * Indicates whether the current position is valid or not.
- *
- * @var boolean
- * @see lithium\util\Collection::valid()
- */
- protected $_valid = false;
- /**
- * Allows a collection's items to be automatically assigned from class construction options.
- *
- * @var array
- */
- protected $_autoConfig = array('data');
- /**
- * Accessor method for adding format handlers to instances and subclasses of `Collection`.
- * The values assigned are used by `Collection::to()` to convert `Collection` instances into
- * different formats, i.e. JSON.
- *
- * This can be accomplished in two ways. First, format handlers may be registered on a
- * case-by-case basis, as in the following:
- *
- * {{{
- * Collection::formats('json', function($collection, $options) {
- * return json_encode($collection->to('array'));
- * });
- *
- * // You can also implement the above as a static class method, and register it as follows:
- * Collection::formats('json', '\my\custom\Formatter::toJson');
- * }}}
- *
- * Alternatively, you can implement a class that can handle several formats. This class must
- * implement two static methods:
- *
- * - A `formats()` method, which returns an array indicating what formats it handles.
- *
- * - A `to()` method, which handles the actual conversion.
- *
- * Once a class implements these methods, it may be registered per the followng:
- * {{{
- * Collection::formats('\lithium\net\http\Media');
- * }}}
- *
- * For reference on how to implement these methods, see the `Media` class.
- *
- * Once a handler is registered, any instance of `Collection` or a subclass can be converted to
- * the format(s) supported by the class or handler, using the `to()` method.
- *
- * @see lithium\net\http\Media::to()
- * @see lithium\net\http\Media::formats()
- * @see lithium\util\Collection::to()
- * @param string $format A string representing the name of the format that a `Collection` can
- * be converted to. This corresponds to the `$format` parameter in the `to()`
- * method. Alternatively, the fully-namespaced class name of a format-handler
- * class.
- * @param mixed $handler If `$format` is the name of a format string, `$handler` should be the
- * function that handles the conversion, either an anonymous function, or a
- * reference to a method name in `"Class::method"` form. If `$format` is a class
- * name, can be `null`.
- * @return mixed Returns the value of the format handler assigned.
- */
- public static function formats($format, $handler = null) {
- if ($format === false) {
- return static::$_formats = array('array' => '\lithium\util\Collection::toArray');
- }
- if ((is_null($handler)) && class_exists($format)) {
- return static::$_formats[] = $format;
- }
- return static::$_formats[$format] = $handler;
- }
- /**
- * Initializes the collection object by merging in collection items and removing redundant
- * object properties.
- *
- * @return void
- */
- protected function _init() {
- parent::_init();
- unset($this->_config['data']);
- }
- /**
- * Handles dispatching of methods against all items in the collection.
- *
- * @param string $method The name of the method to call on each instance in the collection.
- * @param array $params The parameters to pass on each method call.
- * @param array $options Specifies options for how to run the given method against the object
- * collection. The available options are:
- * - `'collect'`: If `true`, the results of this method call will be returned
- * wrapped in a new `Collection` object or subclass.
- * - `'merge'`: Used primarily if the method being invoked returns an array. If
- * set to `true`, merges all results arrays into one.
- * @todo Implement filtering.
- * @return mixed Returns either an array of the return values of the methods, or the return
- * values wrapped in a `Collection` instance.
- */
- public function invoke($method, array $params = array(), array $options = array()) {
- $class = get_class($this);
- $defaults = array('merge' => false, 'collect' => false);
- $options += $defaults;
- $data = array();
- foreach ($this as $object) {
- $value = call_user_func_array(array(&$object, $method), $params);
- ($options['merge']) ? $data = array_merge($data, $value) : $data[$this->key()] = $value;
- }
- return ($options['collect']) ? new $class(compact('data')) : $data;
- }
- /**
- * Hook to handle dispatching of methods against all items in the collection.
- *
- * @param string $method
- * @param array $parameters
- * @return mixed
- */
- public function __call($method, $parameters = array()) {
- return $this->invoke($method, $parameters);
- }
- /**
- * Converts a `Collection` object to another type of object, or a simple type such as an array.
- * The supported values of `$format` depend on the format handlers registered in the static
- * property `Collection::$_formats`. The `Collection` class comes with built-in support for
- * array conversion, but other formats may be registered.
- *
- * Once the appropriate handlers are registered, a `Collection` instance can be converted into
- * any handler-supported format, i.e.: {{{
- * $collection->to('json'); // returns a JSON string
- * $collection->to('xml'); // returns an XML string
- * }}}
- *
- * _Please note that Lithium does not ship with a default XML handler, but one can be
- * configured easily._
- *
- * @see lithium\util\Collection::formats()
- * @see lithium\util\Collection::$_formats
- * @param string $format By default the only supported value is `'array'`. However, additional
- * format handlers can be registered using the `formats()` method.
- * @param $options Options for converting this collection:
- * - `'internal'` _boolean_: Indicates whether the current internal representation of the
- * collection should be exported. Defaults to `false`, which uses the standard iterator
- * interfaces. This is useful for exporting record sets, where records are lazy-loaded,
- * and the collection must be iterated in order to fetch all objects.
- * @return mixed The object converted to the value specified in `$format`; usually an array or
- * string.
- */
- public function to($format, array $options = array()) {
- $defaults = array('internal' => false);
- $options += $defaults;
- $data = $options['internal'] ? $this->_data : $this;
- if (is_object($format) && is_callable($format)) {
- return $format($data, $options);
- }
- if (isset(static::$_formats[$format]) && is_callable(static::$_formats[$format])) {
- $handler = static::$_formats[$format];
- $handler = is_string($handler) ? explode('::', $handler, 2) : $handler;
- if (is_array($handler)) {
- list($class, $method) = $handler;
- return $class::$method($data, $options);
- }
- return $handler($data, $options);
- }
- foreach (static::$_formats as $key => $handler) {
- if (!is_int($key)) {
- continue;
- }
- if (in_array($format, $handler::formats($format, $data, $options))) {
- return $handler::to($format, $data, $options);
- }
- }
- }
- /**
- * Filters a copy of the items in the collection.
- *
- * @param callback $filter Callback to use for filtering.
- * @param array $options The available options are:
- * - `'collect'`: If `true`, the results will be returned wrapped
- * in a new `Collection` object or subclass.
- * @return array|object The filtered items.
- */
- public function find($filter, array $options = array()) {
- $defaults = array('collect' => true);
- $options += $defaults;
- $data = array_filter($this->_data, $filter);
- if ($options['collect']) {
- $class = get_class($this);
- $data = new $class(compact('data'));
- }
- return $data;
- }
- /**
- * Returns the first non-empty value in the collection after a filter is applied, or rewinds the
- * collection and returns the first value.
- *
- * @see lithium\util\Collection::rewind()
- * @param callback $filter A closure through which collection values will be
- * passed. If the return value of this function is non-empty,
- * it will be returned as the result of the method call. If `null`, the
- * collection is rewound (see `rewind()`) and the first item is returned.
- * @return mixed Returns the first non-empty collection value returned from `$filter`.
- */
- public function first($filter = null) {
- if (!$filter) {
- return $this->rewind();
- }
- foreach ($this as $item) {
- if ($filter($item)) {
- return $item;
- }
- }
- }
- /**
- * Applies a callback to all items in the collection.
- *
- * @param callback $filter The filter to apply.
- * @return object This collection instance.
- */
- public function each($filter) {
- $this->_data = array_map($filter, $this->_data);
- return $this;
- }
- /**
- * Applies a callback to a copy of all data in the collection
- * and returns the result.
- *
- * @param callback $filter The filter to apply.
- * @param array $options The available options are:
- * - `'collect'`: If `true`, the results will be returned wrapped
- * in a new `Collection` object or subclass.
- * @return array|object The filtered data.
- */
- public function map($filter, array $options = array()) {
- $defaults = array('collect' => true);
- $options += $defaults;
- $data = array_map($filter, $this->_data);
- if ($options['collect']) {
- $class = get_class($this);
- return new $class(compact('data'));
- }
- return $data;
- }
- /**
- * Checks whether or not an offset exists.
- *
- * @param string $offset An offset to check for.
- * @return boolean `true` if offset exists, `false` otherwise.
- */
- public function offsetExists($offset) {
- return isset($this->_data[$offset]);
- }
- /**
- * Returns the value at specified offset.
- *
- * @param string $offset The offset to retrieve.
- * @return mixed Value at offset.
- */
- public function offsetGet($offset) {
- return $this->_data[$offset];
- }
- /**
- * Assigns a value to the specified offset.
- *
- * @param string $offset The offset to assign the value to.
- * @param mixed $value The value to set.
- * @return mixed The value which was set.
- */
- public function offsetSet($offset, $value) {
- if (is_null($offset)) {
- return $this->_data[] = $value;
- }
- return $this->_data[$offset] = $value;
- }
- /**
- * Unsets an offset.
- *
- * @param string $offset The offset to unset.
- * @return void
- */
- public function offsetUnset($offset) {
- unset($this->_data[$offset]);
- }
- /**
- * Rewinds to the first item.
- *
- * @return mixed The current item after rewinding.
- */
- public function rewind() {
- $this->_valid = (reset($this->_data) !== false);
- return current($this->_data);
- }
- /**
- * Moves forward to the last item.
- *
- * @return mixed The current item after moving.
- */
- public function end() {
- $this->_valid = (end($this->_data) !== false);
- return current($this->_data);
- }
- /**
- * Checks if current position is valid.
- *
- * @return boolean `true` if valid, `false` otherwise.
- */
- public function valid() {
- return $this->_valid;
- }
- /**
- * Returns the current item.
- *
- * @return mixed The current item.
- */
- public function current() {
- return current($this->_data);
- }
- /**
- * Returns the key of the current item.
- *
- * @return scalar Scalar on success `0` on failure.
- */
- public function key() {
- return key($this->_data);
- }
- /**
- * Moves backward to the previous item. If already at the first item,
- * moves to the last one.
- *
- * @return mixed The current item after moving.
- */
- public function prev() {
- if (!prev($this->_data)) {
- end($this->_data);
- }
- return current($this->_data);
- }
- /**
- * Move forwards to the next item.
- *
- * @return The current item after moving.
- */
- public function next() {
- $this->_valid = (next($this->_data) !== false);
- return current($this->_data);
- }
- /**
- * Appends an item.
- *
- * @param mixed $value The item to append.
- * @return void
- */
- public function append($value) {
- is_object($value) ? $this->_data[] =& $value : $this->_data[] = $value;
- }
- /**
- * Counts the items of the object.
- *
- * @return integer Returns the number of items in the collection.
- */
- public function count() {
- $count = iterator_count($this);
- $this->rewind();
- return $count;
- }
- /**
- * Returns the item keys.
- *
- * @return array The keys of the items.
- */
- public function keys() {
- return array_keys($this->_data);
- }
- /**
- * Exports a `Collection` instance to an array. Used by `Collection::to()`.
- *
- * @param mixed $data Either a `Collection` instance, or an array representing a `Collection`'s
- * internal state.
- * @return array Returns the value of `$data` as a pure PHP array, recursively converting all
- * sub-objects and other values to their closest array or scalar equivalents.
- */
- public static function toArray($data) {
- $result = array();
- foreach ($data as $key => $item) {
- switch (true) {
- case (!is_object($item)):
- $result[$key] = $item;
- break;
- case (method_exists($item, 'to')):
- $result[$key] = $item->to('array');
- break;
- case ($vars = get_object_vars($item)):
- $result[$key] = $vars;
- break;
- case (method_exists($item, '__toString')):
- $result[$key] = (string) $item;
- break;
- default:
- $result[$key] = $item;
- break;
- }
- }
- return $result;
- }
- }
- ?>