Browse Source

Merge pull request #11786 from cakephp/collection-count

Adding count() and countKeys() to Collection
José Lorenzo Rodríguez 8 years ago
parent
commit
04bf6a8a40

+ 20 - 8
src/Collection/Collection.php

@@ -73,17 +73,29 @@ class Collection extends IteratorIterator implements CollectionInterface, Serial
     }
 
     /**
-     * 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 @@ class Collection extends IteratorIterator implements CollectionInterface, Serial
     public function __debugInfo()
     {
         return [
-            'count' => iterator_count($this),
+            'count' => $this->count(),
         ];
     }
 }

+ 49 - 0
src/Collection/CollectionInterface.php

@@ -1049,4 +1049,53 @@ interface CollectionInterface extends Iterator, JsonSerializable
      * @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();
 }

+ 26 - 0
src/Collection/CollectionTrait.php

@@ -812,6 +812,32 @@ trait CollectionTrait
     }
 
     /**
+     * {@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
      *

+ 19 - 6
tests/TestCase/Collection/CollectionTest.php

@@ -1007,16 +1007,29 @@ class CollectionTest extends TestCase
     }
 
     /**
-     * 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());
     }
 
     /**