/kirby/toolkit/lib/collection.php
PHP | 601 lines | 295 code | 108 blank | 198 comment | 50 complexity | 4d78cf233761140311001e9e6e904827 MD5 | raw file
- <?php
- if(!defined('SORT_NATURAL')) define('SORT_NATURAL', 'natural');
- /**
- * Collection
- *
- * @package Kirby Toolkit
- * @author Bastian Allgeier <bastian@getkirby.com>
- * @link http://getkirby.com
- * @copyright Bastian Allgeier
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
- */
- class Collection extends I {
- static public $filters = array();
- protected $pagination;
- /**
- * Returns a slice of the collection
- *
- * @param int $offset The optional index to start the slice from
- * @param int $limit The optional number of elements to return
- * @return Collection
- */
- public function slice($offset = null, $limit = null) {
- if($offset === null and $limit === null) return $this;
- $collection = clone $this;
- $collection->data = array_slice($collection->data, $offset, $limit);
- return $collection;
- }
- /**
- * Returns a new collection with a limited number of elements
- *
- * @param int $limit The number of elements to return
- * @return Collection
- */
- public function limit($limit) {
- return $this->slice(0, $limit);
- }
- /**
- * Returns a new collection starting from the given offset
- *
- * @param int $offset The index to start from
- * @return Collection
- */
- public function offset($offset) {
- return $this->slice($offset);
- }
- /**
- * Returns the array in reverse order
- *
- * @return Collection
- */
- public function flip() {
- $collection = clone $this;
- $collection->data = array_reverse($collection->data, true);
- return $collection;
- }
- /**
- * Counts all elements in the array
- *
- * @return int
- */
- public function count() {
- return count($this->data);
- }
- /**
- * Returns the first element from the array
- *
- * @return mixed
- */
- public function first() {
- $array = $this->data;
- return array_shift($array);
- }
- /**
- * Returns the last element from the array
- *
- * @return mixed
- */
- public function last() {
- $array = $this->data;
- return array_pop($array);
- }
- /**
- * Returns the nth element from the array
- *
- * @return mixed
- */
- public function nth($n) {
- $array = array_values($this->data);
- return (isset($array[$n])) ? $array[$n] : false;
- }
- /**
- * Converts the current object into an array
- *
- * @return array
- */
- public function toArray($callback = null) {
- if(is_null($callback)) return $this->data;
- return array_map($callback, $this->data);
- }
- /**
- * Converts the current object into a json string
- *
- * @return string
- */
- public function toJson() {
- return json_encode($this->data);
- }
- /**
- * Appends an element to the data array
- *
- * @param string $key
- * @param mixed $object
- * @return Collection
- */
- public function append($key, $object) {
- $this->data = $this->data + array($key => $object);
- return $this;
- }
- /**
- * Prepends an element to the data array
- *
- * @param string $key
- * @param mixed $object
- * @return Collection
- */
- public function prepend($key, $object) {
- $this->data = array($key => $object) + $this->data;
- return $this;
- }
- /**
- * Returns a new collection without the given element(s)
- *
- * @param args any number of keys, passed as individual arguments
- * @return Collection
- */
- public function not() {
- $collection = clone $this;
- foreach(func_get_args() as $kill) {
- unset($collection->data[$kill]);
- }
- return $collection;
- }
- /**
- * Returns a new collection without the given element(s)
- *
- * @param args any number of keys, passed as individual arguments
- * @return Collection
- */
- public function without() {
- return call_user_func_array(array($this, 'not'), func_get_args());
- }
- /**
- * Shuffle all elements in the array
- *
- * @return object a new shuffled collection
- */
- public function shuffle() {
- $collection = clone $this;
- $keys = array_keys($collection->data);
- shuffle($keys);
- $collection->data = array_merge(array_flip($keys), $collection->data);
- return $collection;
- }
- /**
- * Returns an array of all keys in the collection
- *
- * @return array
- */
- public function keys() {
- return array_keys($this->data);
- }
- /**
- * Tries to find the key for the given element
- *
- * @param mixed $needle the element to search for
- * @return mixed the name of the key or false
- */
- public function keyOf($needle) {
- return array_search($needle, $this->data);
- }
- /**
- * Tries to find the index number for the given element
- *
- * @param mixed $needle the element to search for
- * @return mixed the name of the key or false
- */
- public function indexOf($needle) {
- return array_search($needle, array_values($this->data));
- }
- /**
- * Filter the elements in the array by a callback function
- *
- * @param func $callback the callback function
- * @return Collection
- */
- public function filter($callback) {
- $collection = clone $this;
- $collection->data = array_filter($collection->data, $callback);
- return $collection;
- }
- /**
- * Find a single item by a key and value pair
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- public function findBy($key, $value) {
- foreach($this->data as $item) {
- if($this->extractValue($item, $key) == $value) return $item;
- }
- }
- /**
- * Filters the current collection by a field, operator and search value
- *
- * @return Collection
- */
- public function filterBy() {
- $args = func_get_args();
- $operator = '==';
- $field = @$args[0];
- $value = @$args[1];
- $split = @$args[2];
- $collection = clone $this;
- if(is_string($value) and array_key_exists($value, static::$filters)) {
- $operator = $value;
- $value = @$args[2];
- $split = @$args[3];
- }
- if(array_key_exists($operator, static::$filters)) {
- $collection = call_user_func_array(static::$filters[$operator], array(
- $collection,
- $field,
- $value,
- $split
- ));
- }
- return $collection;
- }
- /**
- * Makes sure to provide a valid value for each filter method
- * no matter if an object or an array is given
- *
- * @param mixed $item
- * @param string $field
- * @return mixed
- */
- static public function extractValue($item, $field) {
- if(is_array($item) and isset($item[$field])) {
- return $item[$field];
- } else if(is_object($item)) {
- return $item->$field();
- } else {
- return false;
- }
- }
- /**
- * Sorts the collection by any number of fields
- *
- * @return Collection
- */
- public function sortBy() {
- $args = func_get_args();
- $collection = clone $this;
- $array = $collection->data;
- $params = array();
- if(empty($array)) return $collection;
- foreach($args as $i => $param) {
- if(is_string($param)) {
- if(strtolower($param) === 'desc') {
- ${"param_$i"} = SORT_DESC;
- } else if(strtolower($param) === 'asc') {
- ${"param_$i"} = SORT_ASC;
- } else {
- ${"param_$i"} = array();
- foreach($array as $index => $row) {
- ${"param_$i"}[$index] = is_array($row) ? str::lower($row[$param]) : str::lower($row->$param());
- }
- }
- } else {
- ${"param_$i"} = $args[$i];
- }
- $params[] = &${"param_$i"};
- }
- $params[] = &$array;
- call_user_func_array('array_multisort', $params);
- $collection->data = $array;
- return $collection;
- }
- /**
- * Add pagination
- *
- * @param int $limit the number of items per page
- * @param array $options and optional array with options for the pagination class
- * @return object a sliced set of data
- */
- public function paginate($limit, $options = array()) {
- if(is_a($limit, 'Pagination')) {
- $this->pagination = $limit;
- return $this;
- }
- $pagination = new Pagination($this->count(), $limit, $options);
- $pages = $this->slice($pagination->offset(), $pagination->limit());
- $pages->pagination = $pagination;
- return $pages;
- }
- /**
- * Get the previously added pagination object
- *
- * @return object
- */
- public function pagination() {
- return $this->pagination;
- }
- /**
- * Map a function to each item in the collection
- *
- * @param function $callback
- * @return Collection
- */
- public function map($callback) {
- $this->data = array_map($callback, $this->data);
- return $this;
- }
- /**
- * Extracts all values for a single field into
- * a new array
- *
- * @param string $field
- * @return array
- */
- public function pluck($field, $split = null, $unique = false) {
- $result = array();
- foreach($this->data as $item) {
- $row = $this->extractValue($item, $field);
- if($split) {
- $result = array_merge($result, str::split($row, $split));
- } else {
- $result[] = $row;
- }
- }
- if($unique) {
- $result = array_unique($result);
- }
- return array_values($result);
- }
- /**
- * Groups the collection by a given field
- *
- * @param string $field
- * @return object A new collection with an item for each group and a subcollection in each group
- */
- public function groupBy($field, $i = true) {
- $groups = array();
- foreach($this->data as $key => $item) {
- // get the value to group by
- $value = $this->extractValue($item, $field);
- // make sure that there's always a proper value to group by
- if(!$value) throw new Exception('Invalid grouping value for key: ' . $key);
- // make sure we have a proper key for each group
- if(is_object($value) or is_array($value)) throw new Exception('You cannot group by arrays or objects');
- // ignore upper/lowercase for group names
- if($i) $value = str::lower($value);
- if(!isset($groups[$value])) {
- // create a new entry for the group if it does not exist yet
- $groups[$value] = new static(array($key => $item));
- } else {
- // add the item to an existing group
- $groups[$value]->set($key, $item);
- }
- }
- return new static($groups);
- }
- public function set($key, $value) {
- if(is_array($key)) {
- $this->data = array_merge($this->data, $key);
- return $this;
- }
- $this->data[$key] = $value;
- return $this;
- }
- public function __set($key, $value) {
- $this->set($key, $value);
- }
- public function get($key, $default = null) {
- if(isset($this->data[$key])) {
- return $this->data[$key];
- } else {
- $lowerkeys = array_change_key_case($this->data, CASE_LOWER);
- if(isset($lowerkeys[strtolower($key)])) {
- return $lowerkeys[$key];
- } else {
- return $default;
- }
- }
- }
- public function __get($key) {
- return $this->get($key);
- }
- public function __call($key, $arguments) {
- return $this->get($key);
- }
- /**
- * Makes it possible to echo the entire object
- *
- * @return string
- */
- public function __toString() {
- return implode('<br />', array_map(function($item) {
- return (string)$item;
- }, $this->data));
- }
- }
- /**
- * Add all available collection filters
- * Those can be extended by creating your own:
- * collection::$filters['your operator'] = function($collection, $field, $value, $split = false) {
- * // your filter code
- * };
- */
- // take all matching elements
- collection::$filters['=='] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if($split) {
- $values = str::split((string)collection::extractValue($item, $field), $split);
- if(!in_array($value, $values)) unset($collection->$key);
- } else if(collection::extractValue($item, $field) != $value) {
- unset($collection->$key);
- }
- }
- return $collection;
- };
- // take all elements that won't match
- collection::$filters['!='] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if($split) {
- $values = str::split((string)collection::extractValue($item, $field), $split);
- if(in_array($value, $values)) unset($collection->$key);
- } else if(collection::extractValue($item, $field) == $value) {
- unset($collection->$key);
- }
- }
- return $collection;
- };
- // take all elements that partly match
- collection::$filters['*='] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if($split) {
- $values = str::split((string)collection::extractValue($item, $field), $split);
- foreach($values as $val) {
- if(strpos($val, $value) === false) {
- unset($collection->$key);
- break;
- }
- }
- } else if(strpos(collection::extractValue($item, $field), $value) === false) {
- unset($collection->$key);
- }
- }
- return $collection;
- };
- // greater than
- collection::$filters['>'] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if(collection::extractValue($item, $field) > $value) continue;
- unset($collection->$key);
- }
- return $collection;
- };
- // greater and equals
- collection::$filters['>='] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if(collection::extractValue($item, $field) >= $value) continue;
- unset($collection->$key);
- }
- return $collection;
- };
- // less than
- collection::$filters['<'] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if(collection::extractValue($item, $field) < $value) continue;
- unset($collection->$key);
- }
- return $collection;
- };
- // less and equals
- collection::$filters['<='] = function($collection, $field, $value, $split = false) {
- foreach($collection->data as $key => $item) {
- if(collection::extractValue($item, $field) <= $value) continue;
- unset($collection->$key);
- }
- return $collection;
- };