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()); } /**