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('items' => 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('items' => 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
}}}
function testAdd() { $scope = __FUNCTION__; Redis::config(array('format' => $scope)); // simplest call $expected = array('bar'); $this->assertEqual(1, Lists::add('foo', 'bar')); $this->assertEqual($expected, $this->redis->lRange("{$scope}:lists:foo", 0, -1)); $expected = array('bar', 'baz'); $this->assertEqual(2, Lists::add('foo', 'baz')); $this->assertEqual($expected, $this->redis->lRange("{$scope}:lists:foo", 0, -1)); // call with array $expected = array('bar', 'baz'); $this->assertEqual(2, Lists::add('withArray', array('bar', 'baz'))); $this->assertEqual($expected, $this->redis->lRange("{$scope}:lists:withArray", 0, -1)); // call with big array $expected = array_fill(0, 100, 'blub'); $this->assertEqual(100, Lists::add('manyItems', $expected)); $this->assertEqual($expected, $this->redis->lRange("{$scope}:lists:manyItems", 0, -1)); // call with bigger array $expected = array_fill(0, 1000, 'blub'); $this->assertEqual(1000, Lists::add('lotsItems', $expected)); $this->assertEqual($expected, $this->redis->lRange("{$scope}:lists:lotsItems", 0, -1)); // call with collection $data = array('lassy', 'barker', 'wuff', 'patty'); $dogs = new Collection(compact('data')); $expected = $dogs->to('array'); $this->assertEqual(4, Lists::add('dogs', $dogs)); $this->assertEqual($expected, $this->redis->lRange("{$scope}:lists:dogs", 0, -1)); // offset $this->assertEqual(array('barker'), $this->redis->lRange("{$scope}:lists:dogs", 1, 1)); }
public function schema() { $data = Libraries::locate('models'); $models = new Collection(compact('data')); if ($this->request->is('json')) { $models->each(function ($model) { $schema = is_callable(array($model, 'schema')) ? $model::schema() : array(); return array($model => $schema ? $schema->fields() : array()); }); } return compact('models'); }
/** * auto init for setting up items passed into constructor * * @return void */ protected function _init() { parent::_init(); $data = $this->_data; $this->_data = array(); foreach ($data as $item) { $this->add($item); } }
/** * auto init for setting up items passed into constructor * * @return void */ protected function _init() { parent::_init(); $items = $this->_items; $this->_items = array(); foreach ($items as $item) { $this->add($item); } }
/** * Adds conversions checks to ensure certain class types and embedded values are properly cast. * * @param string $format Currently only `array` is supported. * @param array $options * @return mixed */ public function to($format, array $options = array()) { $defaults = array('handlers' => array( 'MongoId' => function($value) { return (string) $value; }, 'MongoDate' => function($value) { return $value->sec; } )); if ($format == 'array') { $options += $defaults; return Collection::toArray($this->_data, $options); } return parent::to($format, $options); }
/** * List directory content * * @param string $path * @return boolean */ public function ls($path = null) { $path = "{$this->_location}/{$path}"; if (is_dir($path)) { $directory = new DirectoryIterator($path); } else { return false; } $collection = new Collection(); foreach ($directory as $d) { if ($d->isDot()) { continue; } $url = rtrim($this->_config['url'], '/'); $path = rtrim(substr($d->getPath(), strlen($this->_location)), '/') . '/'; if ($url) { $url .= "{$path}{$d->getFilename()}"; } $collection->append(new Entity(array('name' => $d->getFilename(), 'dir' => $d->isDir(), 'url' => $url, 'path' => $path, 'size' => $d->isDir() ? null : $d->getSize(), 'mode' => substr(sprintf('%o', $d->getPerms()), -4), 'adapter' => $this))); } return $collection; }
/** * Read the configuration or access the connections you have set up. * * Usage: * {{{ * // Gets the names of all available configurations * $configurations = Connections::get(); * * // Gets the configuration array for the connection named 'db' * $config = Connections::get('db', array('config' => true)); * * // Gets the instance of the connection object, configured with the settings defined for * // this object in Connections::add() * $dbConnection = Connection::get('db'); * * // Gets the connection object, but only if it has already been built. * // Otherwise returns null. * $dbConnection = Connection::get('db', array('autoCreate' => false)); * }}} * * @param string $name The name of the connection to get, as defined in the first parameter of * `add()`, when the connection was initially created. * @param array $options Options to use when returning the connection: * - `'autoCreate'`: If `false`, the connection object is only returned if it has * already been instantiated by a previous call. * - `'config'`: If `true`, returns an array representing the connection's internal * configuration, instead of the connection itself. * @return mixed A configured instance of the connection, or an array of the configuration used. */ public static function get($name = null, $options = array()) { $defaults = array('config' => false, 'autoCreate' => true); $options += $defaults; if (empty($name)) { return static::$_configurations->keys(); } if (!isset(static::$_configurations[$name])) { return null; } if ($options['config']) { return static::_config($name); } $settings = static::$_configurations[$name]; if (!isset($settings[0]['adapter']) || !is_object($settings[0]['adapter'])) { if (!$options['autoCreate']) { return null; } } return static::adapter($name); }
/** * Converts the data in the record set to a different format, i.e. an array. * * @param string $format * @param array $options * @return mixed */ public function to($format, $options = array()) { $defaults = array('indexed' => true); $options += $defaults; $result = null; $this->offsetGet(null); switch ($format) { case 'array': $result = array_map(function ($r) { return $r->to('array'); }, $this->_items); if (is_scalar(current($this->_index)) && $options['indexed']) { $result = array_combine($this->_index, $result); } break; default: $result = parent::to($format, $options); break; } return $result; }
public function testTo() { Collection::formats('\lithium\net\http\Media'); $this->assertFalse(isset($this->_recordSet[0])); $expected = array( 1 => array('id' => 1, 'data' => 'data1'), 2 => array('id' => 2, 'data' => 'data2'), 3 => array('id' => 3, 'data' => 'data3'), 4 => array('id' => 4, 'data' => 'data4') ); $this->assertEqual($expected, $this->_recordSet->to('array')); $expected = '{"1":{"id":1,"data":"data1"},"2":{"id":2,"data":"data2"},' . '"3":{"id":3,"data":"data3"},"4":{"id":4,"data":"data4"}}'; $this->assertEqual($expected, $this->_recordSet->to('json')); }
/** * Finds a pattern in a block of code. * * @param string $code * @param string $pattern * @param array $options The list of options to be used when parsing / matching `$code`: * - 'ignore': An array of token names to ignore while parsing, defaults to * `array('T_WHITESPACE')` * - 'lineBreaks': If true, all tokens in a single pattern match must appear on the * same line of code, defaults to false * - 'startOfLine': If true, the pattern must match starting with the beginning of * the line of code to be matched, defaults to false * @return array */ public static function find($code, $pattern, $options = array()) { $defaults = array('all' => true, 'capture' => array(), 'ignore' => array('T_WHITESPACE'), 'return' => true, 'lineBreaks' => false, 'startOfLine' => false); $options += $defaults; $results = array(); $matches = array(); $patternMatch = array(); $ret = $options['return']; $tokens = new Collection(array('items' => static::tokenize($code, $options))); $pattern = new Collection(array('items' => static::tokenize($pattern, $options))); $breaks = function ($token) use(&$tokens, &$matches, &$patternMatch, $options) { if (!$options['lineBreaks']) { return true; } if (empty($patternMatch) && !$options['startOfLine']) { return true; } if (empty($patternMatch)) { $prev = $tokens->prev(); $tokens->next(); } else { $prev = reset($patternMatch); } if (empty($patternMatch) && $options['startOfLine']) { return $token['line'] > $prev['line']; } return $token['line'] == $prev['line']; }; $capture = function ($token) use(&$matches, &$patternMatch, $tokens, $breaks, $options) { if (is_null($token)) { $matches = $patternMatch = array(); return false; } if (empty($patternMatch)) { $prev = $tokens->prev(); $tokens->next(); if ($options['startOfLine'] && $token['line'] == $prev['line']) { $patternMatch = $matches = array(); return false; } } $patternMatch[] = $token; if (empty($options['capture']) || !in_array($token['name'], $options['capture'])) { return true; } if (!$breaks($token)) { $matches = array(); return true; } $matches[] = $token; return true; }; $executors = array('*' => function (&$tokens, &$pattern) use($options, $capture) { $closing = $pattern->next(); $tokens->prev(); while (($t = $tokens->next()) && !Parser::matchToken($closing, $t)) { $capture($t); } $pattern->next(); }); $tokens->rewind(); $pattern->rewind(); while ($tokens->valid()) { if (!$pattern->valid()) { $pattern->rewind(); if (!empty($matches)) { $results[] = array_map(function ($i) use($ret) { return isset($i[$ret]) ? $i[$ret] : $i; }, $matches); } $capture(null); } $p = $pattern->current(); $t = $tokens->current(); switch (true) { case static::matchToken($p, $t): $capture($t) ? $pattern->next() : $pattern->rewind(); break; case isset($executors[$p['name']]): $exec = $executors[$p['name']]; $exec($tokens, $pattern); break; default: $capture(null); $pattern->rewind(); break; } $tokens->next(); } return $results; }
/** * Applies a callback to a copy of all data in the collection * and returns the result. * * Overriden to load any data that has not yet been loaded. * * @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 object The filtered data. */ public function map($filter, array $options = array()) { $defaults = array('collect' => true); $options += $defaults; if (!$this->closed()) { while ($this->next()) { } } $data = parent::map($filter, $options); if ($options['collect']) { foreach (array('_model', '_schema', '_pathKey') as $key) { $data->{$key} = $this->{$key}; } } return $data; }
public function testUnsetInForeach() { $data = array('Hello', 'Delete me', 'Delete me', 'Delete me', 'Delete me', 'Delete me', 'Hello again!', 'Delete me'); $collection = new Collection(array('data' => $data)); $this->assertIdentical($data, $collection->to('array')); foreach ($collection as $i => $word) { if ($word == 'Delete me') { unset($collection[$i]); } } $expected = array(0 => 'Hello', 6 => 'Hello again!'); $results = $collection->to('array'); $this->assertIdentical($expected, $results); }
public function __construct($config = array()) { if (isset($config['data']) && !isset($config['items'])) { $config['items'] = $config['data']; unset($config['data']); } parent::__construct($config); $this->_items = (array) $this->_items; }
/** * Provides short-hand convenience syntax for filter chaining. * * @param object $self The object instance that owns the filtered method. * @param array $params An associative array containing the parameters passed to the filtered * method. * @param array $chain The Filters object instance containing this chain of filters. * @return mixed Returns the return value of the next filter in the chain. * @see lithium\core\Object::applyFilter() * @see lithium\core\Object::_filter() * @todo Implement checks allowing params to be null, to traverse filter chain */ public function next($self, $params, $chain) { if (empty($self) || empty($chain)) { return parent::next(); } return parent::next()->__invoke($self, $params, $chain); }
public function testCollectionFormatConversion() { Collection::formats('\\lithium\\http\\Media'); $items = array('hello', 'goodbye', 'foo' => array('bar', 'baz' => 'dib')); $collection = new Collection(compact('items')); $expected = json_encode($items); $result = $collection->to('json'); $this->assertEqual($result, $expected); $this->assertNull($collection->to('badness')); }
/** * Gets an array of classes and their corresponding definition files, or examines a file and * returns the classes it defines. * * @param array $options * @return array Associative of classes and their corresponding definition files * @todo Document valid options */ public static function classes($options = array()) { $defaults = array('group' => 'classes', 'file' => null); $options += $defaults; $list = get_declared_classes(); $classes = array(); if (!empty($options['file'])) { $loaded = new Collection(array('items' => array_map(function ($class) { return new ReflectionClass($class); }, $list))); if (!in_array($options['file'], $loaded->getFileName())) { include $options['file']; $list = array_diff(get_declared_classes(), $list); } else { $file = $options['file']; $filter = function ($class) use($file) { return $class->getFileName() == $file; }; $list = $loaded->find($filter)->getName(); } } foreach ($list as $class) { $inspector = new ReflectionClass($class); if ($options['group'] == 'classes') { $inspector->getFileName() ? $classes[$class] = $inspector->getFileName() : null; } elseif ($options['group'] == 'files') { $classes[$inspector->getFileName()][] = $inspector; } } return $classes; }
* (`RecordSet` and `Document`) provides a way to manage data collections in a very flexible and * intuitive way, using closures and SPL interfaces. The `to()` method allows a `Collection` (or * subclass) to be converted to another format, such as an array. The `Collection` class also allows * other classes to be connected as handlers to convert `Collection` objects to other formats. * * The following connects the `Media` class as a format handler, which allows `Collection`s to be * exported to any format with a handler provided by `Media`, i.e. JSON. This enables things like * the following: * {{{ * $posts = Post::find('all'); * return $posts->to('json'); * }}} */ use \lithium\util\Collection; Collection::formats('\lithium\net\http\Media'); /** * This filter is a convenience method which allows you to automatically route requests for static * assets stored within active plugins. For example, given a JavaScript file `bar.js` inside the * `li3_foo` plugin installed in an application, requests to `http://app/path/li3_foo/js/bar.js` * will be routed to `/path/to/app/libraries/plugins/li3_foo/webroot/js/bar.js` on the filesystem. * In production, it is recommended that you disable this filter in favor of symlinking each * plugin's `webroot` directory into your main application's `webroot` directory, or adding routing * rules in your web server's configuration. */ use \lithium\action\Dispatcher; use \lithium\core\Libraries; use \lithium\net\http\Media; Dispatcher::applyFilter('_callable', function($self, $params, $chain) {
/** * Provides short-hand convenience syntax for filter chaining. * * @see lithium\core\Object::applyFilter() * @see lithium\core\Object::_filter() * @param object $self The object instance that owns the filtered method. * @param array $params An associative array containing the parameters passed to the filtered * method. * @param array $chain The Filters object instance containing this chain of filters. * @return mixed Returns the return value of the next filter in the chain. */ public function next($self, $params, $chain) { if (empty($self) || empty($chain)) { return parent::next(); } $next = parent::next(); return $next($self, $params, $chain); }
public function testToInternal() { Collection::formats('lithium\\net\\http\\Media'); $expected = array(array('id' => 1, 'data' => 'data1'), array('id' => 2, 'data' => 'data2'), array('id' => 3, 'data' => 'data3'), array('id' => 4, 'data' => 'data4')); $this->assertEqual($expected, $this->_recordSet->to('array', array('indexed' => false))); $expected = '{"1":{"id":1,"data":"data1"},"2":{"id":2,"data":"data2"},'; $expected .= '"3":{"id":3,"data":"data3"},"4":{"id":4,"data":"data4"}}'; $this->assertEqual($expected, $this->_recordSet->to('json')); $expected = '[{"id":1,"data":"data1"},{"id":2,"data":"data2"},'; $expected .= '{"id":3,"data":"data3"},{"id":4,"data":"data4"}]'; $result = $this->_recordSet->to('json', array('indexed' => false)); $this->assertEqual($expected, $result); }
/** * Tests that various types of handlers can be registered with `Collection::formats()`, and * that collection instances are converted correctly. * * @return void */ public function testCollectionFormatConversion() { Collection::formats('lithium\\net\\http\\Media'); $data = array('hello', 'goodbye', 'foo' => array('bar', 'baz' => 'dib')); $collection = new Collection(compact('data')); $expected = json_encode($data); $result = $collection->to('json'); $this->assertEqual($expected, $result); $this->assertNull($collection->to('badness')); Collection::formats(false); $this->assertNull($collection->to('json')); Collection::formats('json', function ($collection, $options) { return json_encode($collection->to('array')); }); $result = $collection->to('json'); $this->assertEqual($expected, $result); $result = $collection->to(function ($collection) { $value = array_map(function ($i) { return is_array($i) ? join(',', $i) : $i; }, $collection->to('array')); return join(',', $value); }); $expected = 'hello,goodbye,bar,dib'; $this->assertEqual($expected, $result); }
/** * Convert entity to some other format * * @param string $format Conversion format eg. `'array'` * @param array $options Options that will be passed to `\lithium\util\Collection::toArray()` * * @return mixed `$this` or `lithium\util\Collection::toArray()` * @see lithium\util\Collection::toArray() */ public function to($format, array $options = array()) { switch ($format) { case 'array': return Collection::toArray(array('name' => $this->_name, 'dir' => $this->_dir, 'url' => $this->_url, 'path' => $this->_path, 'size' => $this->_size, 'mode' => $this->_mode), $options); default: return $this; } }
public function testTo() { Collection::formats('lithium\\net\\http\\Media'); $result = new MockResult(array('data' => array(array('_id' => '4c8f86167675abfabdbf0300', 'title' => 'bar'), array('_id' => '5c8f86167675abfabdbf0301', 'title' => 'foo'), array('_id' => '6c8f86167675abfabdbf0302', 'title' => 'dib')))); $doc = new DocumentSet(array('model' => $this->_model, 'result' => $result)); $expected = array('4c8f86167675abfabdbf0300' => array('_id' => '4c8f86167675abfabdbf0300', 'title' => 'bar'), '5c8f86167675abfabdbf0301' => array('_id' => '5c8f86167675abfabdbf0301', 'title' => 'foo'), '6c8f86167675abfabdbf0302' => array('_id' => '6c8f86167675abfabdbf0302', 'title' => 'dib')); $this->assertEqual($expected, $doc->to('array')); $expected = array(array('_id' => '4c8f86167675abfabdbf0300', 'title' => 'bar'), array('_id' => '5c8f86167675abfabdbf0301', 'title' => 'foo'), array('_id' => '6c8f86167675abfabdbf0302', 'title' => 'dib')); $this->assertEqual($expected, $doc->to('array', array('indexed' => false))); }
/** * Converts the data in the record set to a different format, i.e. an array. * * @param string $format currently only `array` * @param array $options * @return mixed */ public function to($format, array $options = array()) { switch ($format) { case 'array': $result = Col::toArray($this->_data); break; default: $result = $this; break; } return $result; }
/** * Sorts the objects in the collection, useful in situations where * you are already using the underlying datastore to sort results. * * Overriden to load any data that has not yet been loaded. * * @param mixed $field The field to sort the data on, can also be a callback * to a custom sort function. * @param array $options The available options are: * - No options yet implemented * @return $this, useful for chaining this with other methods. */ public function sort($field = 'id', array $options = array()) { $this->offsetGet(null); if (is_string($field)) { $sorter = function ($a, $b) use($field) { if (is_array($a)) { $a = (object) $a; } if (is_array($b)) { $b = (object) $b; } return strcmp($a->{$field}, $b->{$field}); }; } else { if (is_callable($field)) { $sorter = $field; } } return parent::sort($sorter, $options); }
public function export(Source $dataSource, array $options = array()) { $defaults = array('atomic' => true); $options += $defaults; list($data, $nested) = $this->_exportRecursive($dataSource, $options); if ($options['atomic'] && $this->_exists) { $data = array_intersect_key($data, $this->_modified + $nested); } if ($model = $this->_model) { $name = null; $options = array('atomic' => false) + $options; $relations = new Collection(array('data' => $model::relations())); $find = function ($relation) use(&$name) { return $relation->fieldName === $name; }; foreach ($this->_relationships as $name => $subObject) { if (($rel = $relations->first($find)) && $rel->link == $rel::LINK_EMBEDDED) { $data[$name] = $subObject->export($dataSource, $options); } } } return $data; }
/** * Prepares, enables and executes unserialization of the object. * * Restores state of the object including pulled results. Tries * to restore `_handlers` by calling into `_init()`. * * @param string $data Serialized properties of the object. * @return void */ public function unserialize($data) { $vars = unserialize($data); parent::_init(); foreach ($vars as $key => $value) { $this->{$key} = $value; } }
public function testRecordWithCombinedPkAndLazyLoading() { $records = array(array('client_id' => 1, 'invoice_id' => 4, 'title' => 'Payment1'), array('client_id' => 2, 'invoice_id' => 5, 'title' => 'Payment2'), array('client_id' => 2, 'invoice_id' => 6, 'title' => 'Payment3'), array('client_id' => 4, 'invoice_id' => 7, 'title' => 'Payment3')); $result = new MockResult(array('records' => $records)); $payments = new MockMultiKeyRecordSet(array('result' => $result, 'model' => $this->_model2)); $this->assertCount(0, $payments->get('_data')); $result = $payments[array('client_id' => 1, 'invoice_id' => 4)]->to('array'); $this->assertEqual($records[0], $result); $result = $payments[array('client_id' => 2, 'invoice_id' => 6)]->to('array'); $this->assertEqual($records[2], $result); $this->assertCount(3, $payments->get('_data')); $result = $payments[array('client_id' => 2, 'invoice_id' => 5)]->to('array'); $this->assertEqual($records[1], $result); $this->assertCount(3, $payments->get('_data')); $this->assertNull($payments[array('client_id' => 3, 'invoice_id' => 3)]); $this->assertNull($payments[array('client_id' => 2)]); $this->assertNull($payments[array('invoice_id' => 6)]); $this->assertCount(4, $payments->get('_data')); $this->assertEqual($records, $payments->to('array')); $expected = '[{"client_id":1,"invoice_id":4,"title":"Payment1"},'; $expected .= '{"client_id":2,"invoice_id":5,"title":"Payment2"},'; $expected .= '{"client_id":2,"invoice_id":6,"title":"Payment3"},'; $expected .= '{"client_id":4,"invoice_id":7,"title":"Payment3"}]'; Collection::formats('lithium\\net\\http\\Media'); $this->assertEqual($expected, $payments->to('json')); }
/** * Applies a callback to a copy of all data in the collection * and returns the result. * * Overriden to load any data that has not yet been loaded. * * @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()) { if (!$this->closed()) { while ($this->next()) { } } return parent::map($filter, $options); }
* The `Collection` class, which serves as the base class for some of Lithium's data objects * (`RecordSet` and `Document`) provides a way to manage data collections in a very flexible and * intuitive way, using closures and SPL interfaces. The `to()` method allows a `Collection` (or * subclass) to be converted to another format, such as an array. The `Collection` class also allows * other classes to be connected as handlers to convert `Collection` objects to other formats. * * The following connects the `Media` class as a format handler, which allows `Collection`s to be * exported to any format with a handler provided by `Media`, i.e. JSON. This enables things like * the following: * {{{ * $posts = Post::find('all'); * return $posts->to('json'); * }}} */ use lithium\util\Collection; Collection::formats('lithium\\net\\http\\Media'); /** * This filter is a convenience method which allows you to automatically route requests for static * assets stored within active plugins. For example, given a JavaScript file `bar.js` inside the * `li3_foo` plugin installed in an application, requests to `http://app/path/li3_foo/js/bar.js` * will be routed to `/path/to/app/libraries/plugins/li3_foo/webroot/js/bar.js` on the filesystem. * In production, it is recommended that you disable this filter in favor of symlinking each * plugin's `webroot` directory into your main application's `webroot` directory, or adding routing * rules in your web server's configuration. */ // use lithium\action\Dispatcher; // use lithium\action\Response; // use lithium\net\http\Media; // // Dispatcher::applyFilter('_callable', function($self, $params, $chain) { // $url = ltrim($params['request']->url, '/');
public function testRespondsToMagic() { $collection = new Collection(array('data' => array(new Entity(array('model' => 'lithium\\tests\\mocks\\data\\MockPost', 'data' => array('stats' => array('foo' => 'bar'))))))); $this->assertTrue($collection->respondsTo('instances')); $this->assertTrue($collection->respondsTo('foobar')); $this->assertFalse($collection->respondsTo('foobarbaz')); }