From 3f1b9f79c5652c6ff6ad44d9c5cbdddbe07876cb Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sun, 4 Mar 2018 12:28:40 -0500 Subject: [PATCH] Adding count() and countKeys() to Collection Not having these is honestly surprising to many. Instead of trying to be always correct by not having the method, provide it as an "advanced" feature with strings attached. I've added a long section of warnings on when `count()` will be maybe not be what you need. --- src/Collection/Collection.php | 28 +++++++---- src/Collection/CollectionInterface.php | 49 ++++++++++++++++++++ src/Collection/CollectionTrait.php | 26 +++++++++++ tests/TestCase/Collection/CollectionTest.php | 25 +++++++--- 4 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/Collection/Collection.php b/src/Collection/Collection.php index fc78c7da0d3..ab7f725f67e 100644 --- a/src/Collection/Collection.php +++ b/src/Collection/Collection.php @@ -73,17 +73,29 @@ public function unserialize($collection) } /** - * Throws an exception. + * {@inheritDoc} * - * Issuing a count on a Collection can have many side effects, some making the - * Collection unusable after the count operation. - * - * @return void - * @throws \LogicException + * @return int */ public function count() { - throw new LogicException('You cannot issue a count on a Collection.'); + $traversable = $this->optimizeUnwrap(); + + if (is_array($traversable)) { + return count($traversable); + } + + return iterator_count($traversable); + } + + /** + * {@inheritDoc} + * + * @return int + */ + public function countKeys() + { + return count($this->toArray()); } /** @@ -95,7 +107,7 @@ public function count() public function __debugInfo() { return [ - 'count' => iterator_count($this), + 'count' => $this->count(), ]; } } diff --git a/src/Collection/CollectionInterface.php b/src/Collection/CollectionInterface.php index 52d87e0e0e1..ef2723e14d2 100644 --- a/src/Collection/CollectionInterface.php +++ b/src/Collection/CollectionInterface.php @@ -1049,4 +1049,53 @@ public function unwrap(); * @return \Cake\Collection\CollectionInterface */ public function transpose(); + + /** + * Returns the amount of elements in the collection. + * + * ## WARNINGS: + * + * ### Consumes all elements for NoRewindIterator collections: + * + * On certain type of collections, calling this method may render unusable afterwards. + * That is, you may not be able to get elements out of it, or to iterate on it anymore. + * + * Specifically any collection wrapping a Generator (a function with a yield statement) + * or a unbuffered database cursor will not accept any other function calls after calling + * `count()` on it. + * + * Create a new collection with `buffered()` method to overcome this problem. + * + * ### Can report more elements than unique keys: + * + * Any collection constructed by appending collections together, or by having internal iterators + * returning duplicate keys, will report a larger amount of elements using this functions than + * the final amount of elements when converting the collections to a keyed array. This is because + * duplicate keys will be collapsed into a single one in the final array, whereas this count method + * is only concerned by the amount of elements after converting it to a plain list. + * + * If you need the count of elements after taking the keys in consideration + * (the count of unique keys), you can call `countKeys()` + * + * ### Will change the current position of the iterator: + * + * Calling this method at the same time that you are iterating this collections, for example in + * a foreach, will result in undefined behavior. Avoid doing this. + * + * + * @return int + */ + public function count(); + + /** + * Returns the number of unique keys in this iterator. This is, the number of + * elements the collection will contain after calling `toArray()` + * + * This method comes with a number of caveats. Please refer to `CollectionInterface::count()` + * for details. + * + * @see \Cake\Collection\CollectionInterface::count() + * @return int + */ + public function countKeys(); } diff --git a/src/Collection/CollectionTrait.php b/src/Collection/CollectionTrait.php index 49fd986b973..82454cda17f 100644 --- a/src/Collection/CollectionTrait.php +++ b/src/Collection/CollectionTrait.php @@ -811,6 +811,32 @@ public function transpose() return new Collection($result); } + /** + * {@inheritDoc} + * + * @return int + */ + public function count() + { + $traversable = $this->optimizeUnwrap(); + + if (is_array($traversable)) { + return count($traversable); + } + + return iterator_count($traversable); + } + + /** + * {@inheritDoc} + * + * @return int + */ + public function countKeys() + { + return count($this->toArray()); + } + /** * Unwraps this iterator and returns the simplest * traversable that can be used for getting the data out diff --git a/tests/TestCase/Collection/CollectionTest.php b/tests/TestCase/Collection/CollectionTest.php index e47629f3c20..47dcf610b7c 100644 --- a/tests/TestCase/Collection/CollectionTest.php +++ b/tests/TestCase/Collection/CollectionTest.php @@ -1007,16 +1007,29 @@ public function testInvalidConstructorArgument() } /** - * Tests that issuing a count will throw an exception + * Tests that Count returns the number of elements * + * @dataProvider simpleProvider * @return void */ - public function testCollectionCount() + public function testCollectionCount($list) { - $this->expectException(\LogicException::class); - $data = [1, 2, 3, 4]; - $collection = new Collection($data); - $collection->count(); + $list = (new Collection($list))->buffered(); + $collection = new Collection($list); + $this->assertEquals(8, $collection->append($list)->count()); + } + + /** + * Tests that countKeys returns the number of unique keys + * + * @dataProvider simpleProvider + * @return void + */ + public function testCollectionCountKeys($list) + { + $list = (new Collection($list))->buffered(); + $collection = new Collection($list); + $this->assertEquals(4, $collection->append($list)->countKeys()); } /**