From 400de524637af39527d70c4e363d07325c016855 Mon Sep 17 00:00:00 2001 From: Mike Petrovich Date: Sun, 19 Aug 2018 15:40:42 -0400 Subject: [PATCH] Add initial support for generators --- README.md | 20 +++- composer.json | 6 +- docs/Operations.md | 12 +- src/Generator/filter.php | 32 +++++ src/Generator/take.php | 18 +++ src/_.php | 10 +- src/filter.php | 8 +- src/take.php | 11 +- tests/GeneratorTest.php | 60 ++++++++++ tests/_Test.php | 32 +++-- tests/filterTest.php | 248 ++++++++++++++++++++++++++++++++++++++- tests/takeTest.php | 235 ++++++++++++++++++++++++++++++++++++- tests/toArrayTest.php | 13 ++ 13 files changed, 675 insertions(+), 30 deletions(-) create mode 100644 src/Generator/filter.php create mode 100644 src/Generator/take.php create mode 100644 tests/GeneratorTest.php diff --git a/README.md b/README.md index 7b9b54f..0ee7c7c 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ At a glance Highlights --- -- [Many data types supported](#supported-data-types): arrays, objects, [`Traversable`](http://php.net/manual/en/class.traversable.php), [`DirectoryIterator`](http://php.net/manual/en/class.directoryiterator.php), and more +- [Many data types supported](#supported-data-types): arrays, objects, [generators](http://php.net/manual/en/language.generators.overview.php), [`Traversable`](http://php.net/manual/en/class.traversable.php), [`DirectoryIterator`](http://php.net/manual/en/class.directoryiterator.php), and more - [Chaining](#chaining) - [Currying](#currying) - [Lazy evaluation](#lazy-evaluation) @@ -254,6 +254,7 @@ $chain->run(); Dash can work with a wide variety of data types, including: - arrays - objects (eg. `stdClass`) +- [generators](http://php.net/manual/en/language.generators.overview.php) - anything that implements the [`Traversable`](http://php.net/manual/en/class.traversable.php) interface - [`DirectoryIterator`](http://php.net/manual/en/class.directoryiterator.php), which is also a `Traversable` but cannot normally be used with `iterator_to_array()` [due to a PHP bug](https://bugs.php.net/bug.php?id=49755). Dash works around this transparently. @@ -292,6 +293,23 @@ Dash\chain(new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4])) // === 5 ``` +With a generator: + +```php +$integers = function () { + for ($int = 1; true; $int++) { + yield $int; + } +}; + +Dash\chain($integers()) + ->filter('Dash\isOdd') + ->take(3) + ->reverse() + ->value(); +// === [5, 3, 1] +``` + With a `DirectoryIterator`: ```php diff --git a/composer.json b/composer.json index 26cd1aa..b4bed5e 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=5.4.0" + "php": ">=5.5.0" }, "autoload": { "psr-4": { @@ -167,7 +167,9 @@ "src/Curry/toArray.php", "src/Curry/toObject.php", "src/Curry/unary.php", - "src/Curry/values.php" + "src/Curry/values.php", + "src/Generator/filter.php", + "src/Generator/take.php" ] }, "require-dev": { diff --git a/docs/Operations.md b/docs/Operations.md index cb9277a..438954e 100644 --- a/docs/Operations.md +++ b/docs/Operations.md @@ -14,7 +14,7 @@ Operation | Signature [deltas](#deltas) | `deltas($iterable): array` [difference](#difference) | `difference($iterable /*, ...iterables */): array` [each](#each) | `each($iterable, $iteratee): mixed` -[filter](#filter) | `filter($iterable, $predicate = 'Dash\identity'): array` +[filter](#filter) | `filter($iterable, $predicate = 'Dash\identity'): array\|iterable` [find](#find) | `find($iterable, $predicate = 'Dash\identity'): array\|null` [findKey](#findkey) | `findKey($iterable, $predicate = 'Dash\identity'): string\|null` [findLast](#findlast) | `findLast($iterable, $predicate = 'Dash\identity'): array\|null` @@ -45,7 +45,7 @@ Operation | Signature [rotate](#rotate) | `rotate($iterable, $count = 1): array` [sort](#sort) | `sort($iterable, $comparator = 'Dash\compare'): array` [sum](#sum) | `sum($iterable): numeric` -[take](#take) | `take($iterable, $count = 1): array` +[take](#take) | `take($iterable, $count = 1): array\|iterable` [takeRight](#takeright) | `takeRight($iterable, $count = 1): array` [toArray](#toarray) | `toArray($value): array` [toObject](#toobject) | `toObject($value): object` @@ -434,7 +434,7 @@ filter [Operations](#operations) › [Iterable](#iterable) ```php -filter($iterable, $predicate = 'Dash\identity'): array +filter($iterable, $predicate = 'Dash\identity'): array|iterable # Curried: (all parameters required) Curry\filter($predicate, $iterable) @@ -450,7 +450,7 @@ Parameter | Type | Description --- | --- | :--- `$iterable` | `iterable\|stdClass\|null` | `$predicate` | `callable\|string\|array` | (optional) If a callable, invoked with `($value, $key, $iterable)` for each element in `$iterable`; if a string, will get elements with truthy values at `$field`; if an array of form `[$field, $value]`, will get elements whose `$field` loosely equals `$value` -**Returns** | `array` | List of elements in `$iterable` that satisfy `$predicate` +**Returns** | `array\|iterable` | List of elements in `$iterable` that satisfy `$predicate` **Example:** ```php @@ -1840,7 +1840,7 @@ take [Operations](#operations) › [Iterable](#iterable) ```php -take($iterable, $count = 1): array +take($iterable, $count = 1): array|iterable # Curried: (all parameters required) Curry\take($count, $iterable) @@ -1856,7 +1856,7 @@ Parameter | Type | Description --- | --- | :--- `$iterable` | `iterable\|stdClass\|null` | `$count` | `integer` | If negative, gets all but the last `$count` elements of `$iterable` -**Returns** | `array` | New array of `$count` elements +**Returns** | `array\|iterable` | New array of `$count` elements **Example:** ```php diff --git a/src/Generator/filter.php b/src/Generator/filter.php new file mode 100644 index 0000000..2039b90 --- /dev/null +++ b/src/Generator/filter.php @@ -0,0 +1,32 @@ + $value) { + $isIndexedArray = $isIndexedArray && ($key === $index); + + if (call_user_func($predicate, $value, $key, $iterable)) { + if ($isIndexedArray) { + yield $value; + } + else { + yield $key => $value; + } + } + + $index++; + } +} diff --git a/src/Generator/take.php b/src/Generator/take.php new file mode 100644 index 0000000..d7b6fdb --- /dev/null +++ b/src/Generator/take.php @@ -0,0 +1,18 @@ + $value) { + if ($count < 1) { + break; + } + yield $key => $value; + $count--; + } +} diff --git a/src/_.php b/src/_.php index 8b1c548..bfe4eab 100644 --- a/src/_.php +++ b/src/_.php @@ -158,8 +158,7 @@ public static function unsetCustom($name) */ public static function __callStatic($method, $args) { - $callable = self::toCallable($method); - return call_user_func_array($callable, $args); + return call_user_func_array(self::toCallable($method), $args); } /** @@ -352,12 +351,10 @@ private static function toCallable($method) if (is_callable("\\Dash\\$method")) { return "\\Dash\\$method"; } - elseif (isset(self::$customFunctions[$method])) { + if (isset(self::$customFunctions[$method])) { return self::$customFunctions[$method]; } - else { - throw new \BadMethodCallException("No operation named '$method' found"); - } + throw new \BadMethodCallException("No operation named '$method' found"); } /** @@ -381,7 +378,6 @@ private function __construct($input = null) private function toOperation($method, $args) { $callable = self::toCallable($method); - $operation = function ($input) use ($callable, $args) { array_unshift($args, $input); return call_user_func_array($callable, $args); diff --git a/src/filter.php b/src/filter.php index bf15604..b7b7c01 100644 --- a/src/filter.php +++ b/src/filter.php @@ -17,7 +17,7 @@ * if a string, will get elements with truthy values at `$field`; * if an array of form `[$field, $value]`, will get elements * whose `$field` loosely equals `$value` - * @return array List of elements in `$iterable` that satisfy `$predicate` + * @return array|iterable List of elements in `$iterable` that satisfy `$predicate` * * @example Dash\filter([1, 2, 3, 4], 'Dash\isEven'); @@ -53,7 +53,11 @@ function ($value, $key) { return $key > 1; } */ function filter($iterable, $predicate = 'Dash\identity') { - assertType($iterable, ['iterable', 'stdClass', 'null'], __FUNCTION__); + assertType($iterable, ['Generator', 'iterable', 'stdClass', 'null'], __FUNCTION__); + + if ($iterable instanceof \Generator) { + return Generator\filter($iterable, $predicate); + } if (is_null($iterable)) { return []; diff --git a/src/take.php b/src/take.php index 9038ddd..b469618 100644 --- a/src/take.php +++ b/src/take.php @@ -13,7 +13,7 @@ * @category Iterable * @param iterable|stdClass|null $iterable * @param integer $count If negative, gets all but the last `$count` elements of `$iterable` - * @return array New array of `$count` elements + * @return array|iterable New array of `$count` elements * * @example Dash\take([2, 3, 5, 8, 13], 3); @@ -27,7 +27,14 @@ */ function take($iterable, $count = 1) { - assertType($iterable, ['iterable', 'stdClass', 'null'], __FUNCTION__); + assertType($iterable, ['Generator', 'iterable', 'stdClass', 'null'], __FUNCTION__); + + if ($iterable instanceof \Generator) { + if ($count < 0) { + throw new \InvalidArgumentException('Count cannot be negative when using a generator'); + } + return Generator\take($iterable, $count); + } $array = toArray($iterable); $preserveKeys = !isIndexedArray($array); diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php new file mode 100644 index 0000000..0588408 --- /dev/null +++ b/tests/GeneratorTest.php @@ -0,0 +1,60 @@ +assertSame('2, 4, 6', $result); + } + + /** + * @dataProvider cases + */ + public function testChaining($generator) + { + $result = Dash\chain($generator()) + ->filter('Dash\isEven') + ->take(3) + ->join(', ') + ->value(); + + $this->assertSame('2, 4, 6', $result); + } + + public function cases() + { + return [ + 'With a finite generator' => [ + function () { + foreach (range(1, 10) as $key => $value) { + yield $key => $value; + } + }, + ], + 'With an infinite generator' => [ + function () { + $current = 1; + while (true) { + yield $current; + $current++; + } + }, + ], + ]; + } +} diff --git a/tests/_Test.php b/tests/_Test.php index 11b2d84..8641e90 100644 --- a/tests/_Test.php +++ b/tests/_Test.php @@ -28,13 +28,11 @@ public function testReadmeExamples() ->value(); $this->assertSame(18, $avgMaleAge); - if (function_exists('array_column')) { // array_column() requires PHP 5.5+ - $males = array_filter($people, function ($person) { - return $person['gender'] === 'male'; - }); - $avgMaleAge = array_sum(array_column($males, 'age')) / count($males); - $this->assertSame(18, $avgMaleAge); - } + $males = array_filter($people, function ($person) { + return $person['gender'] === 'male'; + }); + $avgMaleAge = array_sum(array_column($males, 'age')) / count($males); + $this->assertSame(18, $avgMaleAge); /* Ad-hoc operation @@ -53,6 +51,7 @@ public function testReadmeExamples() Data types */ + // Array $this->assertSame( [4, 8], Dash\chain([1, 2, 3, 4]) @@ -63,6 +62,7 @@ public function testReadmeExamples() ->value() ); + // Object $this->assertSame( 'a, c', Dash\chain((object) ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]) @@ -72,6 +72,7 @@ public function testReadmeExamples() ->value() ); + // Traversable $this->assertSame( 5, Dash\chain(new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4])) @@ -81,6 +82,22 @@ public function testReadmeExamples() ->value() ); + // Generator + $integers = function () { + for ($int = 0; true; $int++) { + yield $int; + } + }; + $this->assertSame( + [5, 3, 1], + Dash\chain($integers()) + ->filter('Dash\isOdd') + ->take(3) + ->reverse() + ->value() + ); + + // DirectoryIterator $iterator = new \FilesystemIterator(__DIR__, \FilesystemIterator::SKIP_DOTS); $filenames = Dash\chain($iterator) ->reject(function ($fileinfo) { @@ -90,7 +107,6 @@ public function testReadmeExamples() return pathinfo($fileinfo)['filename']; }) ->value(); - $this->assertGreaterThan(10, count($filenames)); /* diff --git a/tests/filterTest.php b/tests/filterTest.php index 0a79929..ff73614 100644 --- a/tests/filterTest.php +++ b/tests/filterTest.php @@ -3,6 +3,7 @@ /** * @covers Dash\filter * @covers Dash\Curry\filter + * @covers Dash\Generator\filter */ class filterTest extends PHPUnit_Framework_TestCase { @@ -253,6 +254,251 @@ public function cases() ]; } + /** + * @dataProvider casesGenerator + */ + public function testGenerator($iterable, $predicate, $expected) + { + $result = iterator_to_array(Dash\filter($iterable, $predicate)); + $this->assertEquals($expected, $result); + } + + public function casesGenerator() + { + $generator = function ($iterable) { + foreach ((array) $iterable as $key => $value) { + yield $key => $value; + } + }; + + return [ + 'With null' => [ + 'iterable' => $generator(null), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an empty array' => [ + 'iterable' => $generator([]), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + + /* + With indexed array + */ + + 'With an indexed array with no elements that satisfy the predicate' => [ + 'iterable' => $generator([2, 4, 6, 8]), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an indexed array with one element that satisfies the predicate' => [ + 'iterable' => $generator([2, 4, 5, 6]), + 'predicate' => 'Dash\isOdd', + 'expected' => [5], + ], + 'With an indexed array with several elements that satisfy the predicate' => [ + 'iterable' => $generator([1, 3, 4, 7]), + 'predicate' => 'Dash\isOdd', + 'expected' => [1, 3, 7], + ], + 'With an indexed array with all elements that satisfy the predicate' => [ + 'iterable' => $generator([1, 3, 5, 7]), + 'predicate' => 'Dash\isOdd', + 'expected' => [1, 3, 5, 7], + ], + 'With an indexed array and matchesProperty($field) shorthand' => [ + 'iterable' => $generator([ + ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ]), + 'predicate' => 'active', + 'expected' => [ + ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ], + ], + 'With an indexed array and matchesProperty($field, $value) shorthand' => [ + 'iterable' => $generator([ + ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ]), + 'predicate' => ['active', false], + 'expected' => [ + ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + ], + ], + + /* + With associative array + */ + + 'With an associative array with no elements that satisfy the predicate' => [ + 'iterable' => $generator(['a' => 2, 'b' => 4, 'c' => 6, 'd' => 8]), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an associative array with one element that satisfies the predicate' => [ + 'iterable' => $generator(['a' => 2, 'b' => 4, 'c' => 5, 'd' => 6]), + 'predicate' => 'Dash\isOdd', + 'expected' => ['c' => 5], + ], + 'With an associative array with several elements that satisfy the predicate' => [ + 'iterable' => $generator(['a' => 1, 'b' => 3, 'c' => 4, 'd' => 7]), + 'predicate' => 'Dash\isOdd', + 'expected' => ['a' => 1, 'b' => 3, 'd' => 7], + ], + 'With an associative array with all elements that satisfy the predicate' => [ + 'iterable' => $generator(['a' => 1, 'b' => 3, 'c' => 5, 'd' => 7]), + 'predicate' => 'Dash\isOdd', + 'expected' => ['a' => 1, 'b' => 3, 'c' => 5, 'd' => 7], + ], + 'With an associative array and matchesProperty($field) shorthand' => [ + 'iterable' => $generator([ + 'a' => ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + 'b' => ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + 'c' => ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + 'd' => ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ]), + 'predicate' => 'active', + 'expected' => [ + 'b' => ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + 'd' => ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ], + ], + 'With an associative array and matchesProperty($field, $value) shorthand' => [ + 'iterable' => $generator([ + 'a' => ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + 'b' => ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + 'c' => ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + 'd' => ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ]), + 'predicate' => ['active', false], + 'expected' => [ + 'a' => ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + 'c' => ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + ], + ], + + /* + With stdClass + */ + + 'With an empty stdClass' => [ + 'iterable' => $generator((object) []), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an stdClass with no elements that satisfy the predicate' => [ + 'iterable' => $generator((object) ['a' => 2, 'b' => 4, 'c' => 6]), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an stdClass with one element that satisfies the predicate' => [ + 'iterable' => $generator((object) ['a' => 2, 'b' => 3, 'c' => 6]), + 'predicate' => 'Dash\isOdd', + 'expected' => ['b' => 3] + ], + 'With an stdClass with several elements that satisfy the predicate' => [ + 'iterable' => $generator((object) ['a' => 1, 'b' => 4, 'c' => 5]), + 'predicate' => 'Dash\isOdd', + 'expected' => ['a' => 1, 'c' => 5], + ], + 'With an stdClass with all elements that satisfy the predicate' => [ + 'iterable' => $generator((object) ['a' => 1, 'b' => 3, 'c' => 5]), + 'predicate' => 'Dash\isOdd', + 'expected' => ['a' => 1, 'b' => 3, 'c' => 5], + ], + 'With an stdClass and matchesProperty($field) shorthand' => [ + 'iterable' => $generator((object) [ + 'a' => (object) ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + 'b' => (object) ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + 'c' => (object) ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + 'd' => (object) ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ]), + 'predicate' => 'active', + 'expected' => [ + 'b' => (object) ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + 'd' => (object) ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ], + ], + 'With an stdClass and matchesProperty($field, $value) shorthand' => [ + 'iterable' => $generator((object) [ + 'a' => (object) ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + 'b' => (object) ['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true], + 'c' => (object) ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + 'd' => (object) ['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true], + ]), + 'predicate' => ['active', false], + 'expected' => [ + 'a' => (object) ['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false], + 'c' => (object) ['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false], + ], + ], + + /* + With ArrayObject + */ + + 'With an empty ArrayObject' => [ + 'iterable' => $generator(new ArrayObject([])), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an ArrayObject with no elements that satisfy the predicate' => [ + 'iterable' => $generator(new ArrayObject(['a' => 2, 'b' => 4, 'c' => 6])), + 'predicate' => 'Dash\isOdd', + 'expected' => [], + ], + 'With an ArrayObject with one element that satisfies the predicate' => [ + 'iterable' => $generator(new ArrayObject(['a' => 2, 'b' => 3, 'c' => 6])), + 'predicate' => 'Dash\isOdd', + 'expected' => ['b' => 3] + ], + 'With an ArrayObject with several elements that satisfy the predicate' => [ + 'iterable' => $generator(new ArrayObject(['a' => 1, 'b' => 4, 'c' => 5])), + 'predicate' => 'Dash\isOdd', + 'expected' => ['a' => 1, 'c' => 5], + ], + 'With an ArrayObject with all elements that satisfy the predicate' => [ + 'iterable' => $generator(new ArrayObject(['a' => 1, 'b' => 3, 'c' => 5])), + 'predicate' => 'Dash\isOdd', + 'expected' => ['a' => 1, 'b' => 3, 'c' => 5], + ], + 'With an ArrayObject and matchesProperty($field) shorthand' => [ + 'iterable' => $generator(new ArrayObject([ + 'a' => new ArrayObject(['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false]), + 'b' => new ArrayObject(['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true]), + 'c' => new ArrayObject(['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false]), + 'd' => new ArrayObject(['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true]), + ])), + 'predicate' => 'active', + 'expected' => [ + 'b' => new ArrayObject(['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true]), + 'd' => new ArrayObject(['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true]), + ], + ], + 'With an ArrayObject and matchesProperty($field, $value) shorthand' => [ + 'iterable' => $generator(new ArrayObject([ + 'a' => new ArrayObject(['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false]), + 'b' => new ArrayObject(['name' => 'Jane', 'age' => 27, 'gender' => 'female', 'active' => true]), + 'c' => new ArrayObject(['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false]), + 'd' => new ArrayObject(['name' => 'Pete', 'age' => 35, 'gender' => 'male', 'active' => true]), + ])), + 'predicate' => ['active', false], + 'expected' => [ + 'a' => new ArrayObject(['name' => 'John', 'age' => 30, 'gender' => 'male', 'active' => false]), + 'c' => new ArrayObject(['name' => 'Kane', 'age' => 33, 'gender' => 'male', 'active' => false]), + ], + ], + ]; + } + public function testPredicateArgs() { $iterable = ['a' => 1, 'b' => 2, 'c' => 3]; @@ -312,7 +558,7 @@ public function testTypeAssertions($iterable, $type) } catch (Exception $e) { $this->assertSame( - "Dash\\filter expects iterable or stdClass or null but was given $type", + "Dash\\filter expects Generator or iterable or stdClass or null but was given $type", $e->getMessage() ); throw $e; diff --git a/tests/takeTest.php b/tests/takeTest.php index 7523f52..d6a1a3f 100644 --- a/tests/takeTest.php +++ b/tests/takeTest.php @@ -3,6 +3,7 @@ /** * @covers Dash\take * @covers Dash\Curry\take + * @covers Dash\Generator\take */ class takeTest extends PHPUnit_Framework_TestCase { @@ -289,6 +290,238 @@ public function cases() ]; } + /** + * @dataProvider casesGenerator + */ + public function testGenerator($iterable, $count, $expected) + { + $result = Dash\mapValues(Dash\take($iterable, $count)); + $this->assertEquals($expected, $result); + } + + public function casesGenerator() + { + $generator = function ($iterable) { + foreach ((array) $iterable as $key => $value) { + yield $key => $value; + } + }; + + return [ + 'With null' => [ + 'iterable' => $generator(null), + 'count' => 1, + 'expected' => [], + ], + + /* + With array + */ + + 'With an empty array' => [ + 'iterable' => $generator([]), + 'count' => 1, + 'expected' => [], + ], + 'With an indexed array with one element' => [ + 'iterable' => $generator(['a']), + 'count' => 2, + 'expected' => ['a'], + ], + 'With an indexed array' => [ + 'iterable' => $generator(['a', 'b', 'c', 'd']), + 'count' => 2, + 'expected' => ['a', 'b'], + ], + 'With an associative array with one element' => [ + 'iterable' => $generator(['a' => 3]), + 'count' => 2, + 'expected' => ['a' => 3], + ], + 'With an associative array' => [ + 'iterable' => $generator(['a' => 3, 'b' => 8, 'c' => 2, 'd' => 5]), + 'count' => 2, + 'expected' => ['a' => 3, 'b' => 8], + ], + [ + 'iterable' => $generator([1, 2, 3, 4]), + 'count' => 0, + 'expected' => [], + ], + [ + 'iterable' => $generator([1, 2, 3, 4]), + 'count' => 1, + 'expected' => [1], + ], + [ + 'iterable' => $generator([1, 2, 3, 4]), + 'count' => 2, + 'expected' => [1, 2], + ], + [ + 'iterable' => $generator([1, 2, 3, 4]), + 'count' => 3, + 'expected' => [1, 2, 3], + ], + [ + 'iterable' => $generator([1, 2, 3, 4]), + 'count' => 4, + 'expected' => [1, 2, 3, 4], + ], + [ + 'iterable' => $generator([1, 2, 3, 4]), + 'count' => 5, + 'expected' => [1, 2, 3, 4], + ], + + /* + With stdClass + */ + + 'With an empty stdClass' => [ + 'iterable' => $generator((object) []), + 'count' => 1, + 'expected' => [], + ], + 'With an stdClass of an indexed array with one element' => [ + 'iterable' => $generator((object) ['a']), + 'count' => 2, + 'expected' => ['a'], + ], + 'With an stdClass of an indexed array' => [ + 'iterable' => $generator((object) ['a', 'b', 'c', 'd']), + 'count' => 2, + 'expected' => ['a', 'b'], + ], + 'With an stdClass of an associative array with one element' => [ + 'iterable' => $generator((object) ['a' => 3]), + 'count' => 2, + 'expected' => ['a' => 3], + ], + 'With an stdClass of an associative array' => [ + 'iterable' => $generator((object) ['a' => 3, 'b' => 8, 'c' => 2, 'd' => 5]), + 'count' => 2, + 'expected' => ['a' => 3, 'b' => 8], + ], + [ + 'iterable' => $generator((object) [1, 2, 3, 4]), + 'count' => 0, + 'expected' => [], + ], + [ + 'iterable' => $generator((object) [1, 2, 3, 4]), + 'count' => 1, + 'expected' => [1], + ], + [ + 'iterable' => $generator((object) [1, 2, 3, 4]), + 'count' => 2, + 'expected' => [1, 2], + ], + [ + 'iterable' => $generator((object) [1, 2, 3, 4]), + 'count' => 3, + 'expected' => [1, 2, 3], + ], + [ + 'iterable' => $generator((object) [1, 2, 3, 4]), + 'count' => 4, + 'expected' => [1, 2, 3, 4], + ], + [ + 'iterable' => $generator((object) [1, 2, 3, 4]), + 'count' => 5, + 'expected' => [1, 2, 3, 4], + ], + + /* + With ArrayObject + */ + + 'With an empty ArrayObject' => [ + 'iterable' => $generator(new ArrayObject([])), + 'count' => 1, + 'expected' => [], + ], + 'With an ArrayObject of an indexed array with one element' => [ + 'iterable' => $generator(new ArrayObject(['a'])), + 'count' => 2, + 'expected' => ['a'], + ], + 'With an ArrayObject of an indexed array' => [ + 'iterable' => $generator(new ArrayObject(['a', 'b', 'c', 'd'])), + 'count' => 2, + 'expected' => ['a', 'b'], + ], + 'With an ArrayObject of an associative array with one element' => [ + 'iterable' => $generator(new ArrayObject(['a' => 3])), + 'count' => 2, + 'expected' => ['a' => 3], + ], + 'With an ArrayObject of an associative array' => [ + 'iterable' => $generator(new ArrayObject(['a' => 3, 'b' => 8, 'c' => 2, 'd' => 5])), + 'count' => 2, + 'expected' => ['a' => 3, 'b' => 8], + ], + [ + 'iterable' => $generator(new ArrayObject([1, 2, 3, 4])), + 'count' => 0, + 'expected' => [], + ], + [ + 'iterable' => $generator(new ArrayObject([1, 2, 3, 4])), + 'count' => 1, + 'expected' => [1], + ], + [ + 'iterable' => $generator(new ArrayObject([1, 2, 3, 4])), + 'count' => 2, + 'expected' => [1, 2], + ], + [ + 'iterable' => $generator(new ArrayObject([1, 2, 3, 4])), + 'count' => 3, + 'expected' => [1, 2, 3], + ], + [ + 'iterable' => $generator(new ArrayObject([1, 2, 3, 4])), + 'count' => 4, + 'expected' => [1, 2, 3, 4], + ], + [ + 'iterable' => $generator(new ArrayObject([1, 2, 3, 4])), + 'count' => 5, + 'expected' => [1, 2, 3, 4], + ], + ]; + } + + public function testInfiniteGenerator() + { + $generator = function () { + $value = 1; + while (true) { + yield $value; + $value++; + } + }; + $result = $generator(); + $this->assertEquals([1, 2, 3], iterator_to_array(Dash\take($result, 3))); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGeneratorCountRestriction() + { + $generator = function () { + foreach ([1, 2, 3] as $key => $value) { + yield $value; + } + }; + Dash\take($generator(), -1); + } + /** * @dataProvider casesTypeAssertions * @expectedException InvalidArgumentException @@ -300,7 +533,7 @@ public function testTypeAssertions($iterable, $type) } catch (Exception $e) { $this->assertSame( - "Dash\\take expects iterable or stdClass or null but was given $type", + "Dash\\take expects Generator or iterable or stdClass or null but was given $type", $e->getMessage() ); throw $e; diff --git a/tests/toArrayTest.php b/tests/toArrayTest.php index a058c3b..91529d1 100644 --- a/tests/toArrayTest.php +++ b/tests/toArrayTest.php @@ -109,6 +109,19 @@ public function cases() 'value' => new ArrayObject(['a' => 3, 'b' => 8, 'c' => 2, 'd' => 5]), 'expected' => ['a' => 3, 'b' => 8, 'c' => 2, 'd' => 5], ], + + /* + With Generator + */ + + 'With a generator' => [ + 'value' => call_user_func(function() { + foreach (['a' => 1, 'b' => 2, 'c' => 3] as $key => $value) { + yield $key => $value; + } + }), + 'expected' => ['a' => 1, 'b' => 2, 'c' => 3], + ], ]; }