* class Book extends ActiveRecord\Model { * static $attr_accessible = array('id', 'name'); * } * * // Index all book names by their id. * $books = Book::all(); // Find a list of books * $collection = new Collection($books); // Put the list in a collection * $indexed = $collection->indexBy('id'); // Create indexes for all books * $names = $indexed->getAttribute('name'); // Get the values of a single attribute * // $names now contains something like array(1 => 'Some book', 2 => 'Another book') * * // Same as above: * $names = Collection::create(Book::all())->indexBy('id')->getAttribute('name'); * * * Example 2: Execute a method for each item in a list. * * // Delete all books * Collection::create(Book::all())->mapMethod('delete'); * * * @package WebBasics */ class Collection extends Base { /** * The set of items item that is being manipulated, as an associoative array. * * @var array */ private $items; /** * Create a new Collection object. * * @param array $items Initial item set (optional). */ function __construct(array $items=array()) { $this->items = $items; } /** * Add an item to the collection. * * @param mixed $item The item to add. */ function add($item) { $this->items[] = $item; } /** * Insert an item at a specific index in the collection. * * If the index is numeric, all existing numeric indices from that * index will be shifted one position to the right. * * @param mixed $item The item to insert. * @param int|string $index The index at which to insert the item. * @throws \InvalidArgumentException If the added index already exists, and is non-numeric. */ function insert($item, $index) { if (isset($this->items[$index])) { if (!is_int($index)) throw new \InvalidArgumentException(sprintf('Index "%s" already exists in this collection.', $index)); for ($i = count($this->items) - 1; $i >= $index; $i--) $this->items[$i + 1] = $this->items[$i]; } $this->items[$index] = $item; } /** * Get all items in the collection as an array. * * @return array The items in the collection. */ function all() { return $this->items; } /** * Get the first item in the collection. * * @return mixed * @throws \OutOfBoundsException if the collection is empty. */ function first() { if (!$this->count()) throw new \OutOfBoundsException(sprintf('Cannot get first item: collection is empty.')); return $this->items[0]; } /** * Get the last item in the collection. * * @return mixed * @throws \OutOfBoundsException if the collection is empty. */ function last() { if (!$this->count()) throw new \OutOfBoundsException(sprintf('Cannot get last item: collection is empty.')); return end($this->items); } /** * Get the number of items in the collection. * * @return int The number of items in the collection. */ function count() { return count($this->items); } /** * Check if an item with the given index exists. * * @param int|string $index The index to check existance of. * @return bool Whether the index exists. */ function indexExists($index) { return isset($this->items[$index]); } /** * Get an item from the collection by its index. * * @param int|string $index The index to the item to get. * @return mixed The value corresponding to the specified index. */ function get($index) { return $this->items[$index]; } /** * Delete an item from the collection by its index. * * @param int|string $index The index to the item to delete. */ function deleteIndex($index) { unset($this->items[$index]); } /** * Delete an item from the collection. * * @param mixed $item The item to delete. */ function delete($item) { $this->deleteIndex(array_search($item, $this->items)); } /** * Create a new item set with the same class name as this object. * * @param array $items The new items to create a set with. * @param bool $clone If TRUE, the item set will overwrite the current * object's item set and not create a new object. * @return Collection A collection with the new item set. */ private function setItems(array $items, $clone=true) { if ($clone) return new self($items); $this->items = $items; return $this; } /** * Remove duplicates from the current item set. * * @param bool $clone Whether to create a new object, or overwrite the current item set. * @return Collection A collection without duplicates. */ function uniques($clone=false) { return $this->setItems(array_values(array_unique($this->items)), $clone); } /** * Filter items from the collection. * * @param callable $callback Function that receives an item from the collection and * returns TRUE if the item should be present in the * resulting collection. * @param bool $clone Whether to create a new object, or overwrite the current item set. * @return Collection A collection with the filtered set of items. */ function filter($callback, $clone=true) { return $this->setItems(array_values(array_filter($this->items, $callback)), $clone); } /** * Find a subset of items in the collection using property value conditions. * * The conditions are specified as an associative array of property names * pointing to values. Only items whose properties have these values will * appear in the resulting collection. The items in the collection have to * be objects or associative arrays, or an error will occur. * * @param array $conditions The conditions that items in the subset should meet. * @param bool $clone Whether to create a new object, or overwrite the current item set. * @throws \UnexpectedValueException If a non-object and non-array value is encountered. * @return Collection */ function find(array $conditions, $clone=true) { return $this->filter(function($item) use ($conditions) { if (is_object($item)) { // Object, match property values foreach ($conditions as $property => $value) if ($item->{$property} != $value) return false; } elseif (is_array($item)) { // Array item, match array values foreach ($conditions as $property => $value) if ($item[$property] != $value) return false; } else { // Other, incompatible type -> throw exception throw new \UnexpectedValueException( sprintf('Collection::find encountered a non-object and non-array item "%s".', $item) ); } return true; }, $clone); } /** * Get an attribute value for each of the items in the collection. * * The items are assumed to be objects with the specified attribute. * * @param string $attribute The name of the attribute to get the value of. * @return array The original item keys, pointing to single attribute values. */ function getAttribute($attribute) { return array_map(function($item) use ($attribute) { return $item->{$attribute}; }, $this->items); } /** * Use an attribute of each item in the collection as an index to that item. * * The items are assumed to be objects with the specified index attribute. * * @param string $attribute The name of the attribute to use as index value. * @param bool $clone Whether to create a new object, or overwrite the current item set. * @return Collection A collection object with the values of the attribute used as indices. */ function indexBy($attribute, $clone=true) { $indexed = array(); foreach ($this->items as $item) $indexed[$item->$attribute] = $item; return $this->setItems($indexed, $clone); } /** * Execute a callback for each of the items in the collection. * * @param callable $callback Function that receives an item from the collection. * @param bool $clone Whether to create a new object, or overwrite the current item set. * @return Collection A collection with return values of the callback calls. */ function map($callback, $clone=true) { return $this->setItems(array_map($callback, $this->items), $clone); } /** * Execute an object method for each item in the collection. * * The items are assumed to be objects with the specified method. * * @param string $method_name The name of the method to execute. * @param array $args Any arguments to pass to the method. * @param bool $clone Whether to create a new object, or overwrite the current item set. * @return Collection A collection with return values of the method calls. */ function mapMethod($method_name, array $args=array(), $clone=true) { $items = array(); foreach ($this->items as $item) $items[] = call_user_func_array(array($item, $method_name), $args); return $this->setItems($items); } } ?>