Skip to content

Commit

Permalink
Adding count() and countKeys() to Collection
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lorenzo committed Mar 4, 2018
1 parent e0da2a6 commit 3f1b9f7
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 14 deletions.
28 changes: 20 additions & 8 deletions src/Collection/Collection.php
Expand Up @@ -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());
}

/**
Expand All @@ -95,7 +107,7 @@ public function count()
public function __debugInfo()
{
return [
'count' => iterator_count($this),
'count' => $this->count(),
];
}
}
49 changes: 49 additions & 0 deletions src/Collection/CollectionInterface.php
Expand Up @@ -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();
}
26 changes: 26 additions & 0 deletions src/Collection/CollectionTrait.php
Expand Up @@ -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
Expand Down
25 changes: 19 additions & 6 deletions tests/TestCase/Collection/CollectionTest.php
Expand Up @@ -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());
}

/**
Expand Down

0 comments on commit 3f1b9f7

Please sign in to comment.