diff --git a/src/IterableObject.php b/src/IterableObject.php index b8eec16..c4608ee 100644 --- a/src/IterableObject.php +++ b/src/IterableObject.php @@ -2,12 +2,13 @@ namespace BenTools\IterableFunctions; -use Closure; use EmptyIterator; -use InvalidArgumentException; use IteratorAggregate; use Traversable; +/** + * @internal + */ final class IterableObject implements IteratorAggregate { /** @@ -18,93 +19,50 @@ final class IterableObject implements IteratorAggregate /** * @var callable */ - private $filter; + private $filterFn; /** * @var callable */ - private $map; + private $mapFn; - /** - * IterableObject constructor. - * @param iterable|array|Traversable $iterable - * @param callable|null $filter - * @param callable|null $map - * @throws InvalidArgumentException - */ - public function __construct($iterable, $filter = null, $map = null) + public function __construct(?iterable $iterable = null, ?callable $filter = null, ?callable $map = null) { - if (null === $iterable) { - $iterable = new EmptyIterator(); - } - if (!is_iterable($iterable)) { - throw new InvalidArgumentException( - sprintf('Expected array or Traversable, got %s', is_object($iterable) ? get_class($iterable) : gettype($iterable)) - ); - } - - // Cannot rely on callable type-hint on PHP 5.3 - if (null !== $filter && !is_callable($filter) && !$filter instanceof Closure) { - throw new InvalidArgumentException( - sprintf('Expected callable, got %s', is_object($filter) ? get_class($filter) : gettype($filter)) - ); - } - - if (null !== $map && !is_callable($map) && !$map instanceof Closure) { - throw new InvalidArgumentException( - sprintf('Expected callable, got %s', is_object($map) ? get_class($map) : gettype($map)) - ); - } - - $this->iterable = $iterable; - $this->filter = $filter; - $this->map = $map; + $this->iterable = $iterable ?? new EmptyIterator(); + $this->filterFn = $filter; + $this->mapFn = $map; } - /** - * @param callable $filter - * @return self - */ - public function filter($filter) + public function filter(callable $filter): self { - return new self($this->iterable, $filter, $this->map); + return new self($this->iterable, $filter, $this->mapFn); } - /** - * @param callable $map - * @return self - */ - public function map($map) + public function map(callable $map): self { - return new self($this->iterable, $this->filter, $map); + return new self($this->iterable, $this->filterFn, $map); } - /** - * @inheritdoc - */ - public function getIterator() + public function getIterator(): Traversable { $iterable = $this->iterable; - if (null !== $this->filter) { - $iterable = iterable_filter($iterable, $this->filter); + if (null !== $this->filterFn) { + $iterable = iterable_filter($iterable, $this->filterFn); } - if (null !== $this->map) { - $iterable = iterable_map($iterable, $this->map); + if (null !== $this->mapFn) { + $iterable = iterable_map($iterable, $this->mapFn); } return iterable_to_traversable($iterable); } - /** - * @return array - */ - public function asArray() + public function asArray(): array { $iterable = $this->iterable; - if (null !== $this->filter) { - $iterable = iterable_filter($iterable, $this->filter); + if (null !== $this->filterFn) { + $iterable = iterable_filter($iterable, $this->filterFn); } - if (null !== $this->map) { - $iterable = iterable_map($iterable, $this->map); + if (null !== $this->mapFn) { + $iterable = iterable_map($iterable, $this->mapFn); } return iterable_to_array($iterable); } diff --git a/src/iterable-functions.php b/src/iterable-functions.php index 6fbdc9e..b92de9f 100644 --- a/src/iterable-functions.php +++ b/src/iterable-functions.php @@ -1,174 +1,108 @@ convert it to an ArrayIterator. + * + * @param array|Traversable $iterable + */ +function iterable_to_traversable(iterable $iterable): Traversable +{ + if ($iterable instanceof Traversable) { + return $iterable; } + + return new ArrayIterator($iterable); } -if (!function_exists('iterable_to_traversable')) { - - /** - * If the iterable is not intance of \Traversable, it is an array => convert it to an ArrayIterator. - * - * @param iterable|array|\Traversable $iterable - * @return \Traversable - */ - function iterable_to_traversable($iterable) - { - if ($iterable instanceof Traversable) { - return $iterable; - } elseif (is_array($iterable)) { - return new ArrayIterator($iterable); - } else { - throw new \InvalidArgumentException( - sprintf( - 'Expected array or \\Traversable, got %s', - is_object($iterable) ? get_class($iterable) : gettype($iterable) - ) - ); - } + +/** + * Filters an iterable. + * + * @param array|Traversable $iterable + * @return array|CallbackFilterIterator + */ +function iterable_filter(iterable $iterable, ?callable $filter = null) +{ + if (null === $filter) { + $filter = static function ($value) { + return (bool) $value; + }; } -} -if (!function_exists('iterable_filter')) { - - /** - * Filters an iterable. - * - * @param iterable|array|\Traversable $iterable - * @param callable $filter - * @return array|CallbackFilterIterator - * @throws InvalidArgumentException - */ - function iterable_filter($iterable, $filter = null) - { - if (!is_iterable($iterable)) { - throw new \InvalidArgumentException( - sprintf('Expected array or Traversable, got %s', is_object($iterable) ? get_class($iterable) : gettype($iterable)) - ); - } - - // Cannot rely on callable type-hint on PHP 5.3 - if (null !== $filter && !is_callable($filter) && !$filter instanceof Closure) { - throw new InvalidArgumentException( - sprintf('Expected callable, got %s', is_object($filter) ? get_class($filter) : gettype($filter)) - ); - } - - if (null === $filter) { - $filter = function ($value) { - return (bool) $value; - }; - } - - if ($iterable instanceof Traversable) { - if (!class_exists('CallbackFilterIterator')) { - throw new \RuntimeException('Class CallbackFilterIterator not found. Try using a polyfill, like symfony/polyfill-php54'); - } - return new CallbackFilterIterator(new IteratorIterator($iterable), $filter); - } - - return array_filter($iterable, $filter); + if ($iterable instanceof Traversable) { + return new CallbackFilterIterator(new IteratorIterator($iterable), $filter); } + return array_filter($iterable, $filter); } -if (!function_exists('iterable_reduce')) { - /** - * Reduces an iterable. - * - * @param iterable $iterable - * @param callable(mixed, mixed) $reduce - * @return mixed - * - * @psalm-template TValue - * @psalm-template TResult - * - * @psalm-param iterable $iterable - * @psalm-param callable(TResult|null, TValue) $reduce - * @psalm-param TResult|null $initial - * - * @psalm-return TResult|null - */ - function iterable_reduce($iterable, $reduce, $initial = null) - { - foreach ($iterable as $item) { - $initial = $reduce($initial, $item); - } - - return $initial; - } -} /** - * @param iterable|array|\Traversable $iterable - * @param callable|null $filter - * @param callable|null $map - * @return Traversable|IterableObject - * @throws InvalidArgumentException + * Reduces an iterable. + * + * @param iterable $iterable + * @param callable(mixed, mixed) $reduce + * @return mixed + * + * @psalm-template TValue + * @psalm-template TResult + * + * @psalm-param iterable $iterable + * @psalm-param callable(TResult|null, TValue) $reduce + * @psalm-param TResult|null $initial + * + * @psalm-return TResult|null */ -function iterable($iterable, $filter = null, $map = null) +function iterable_reduce(iterable $iterable, callable $reduce, $initial = null) +{ + foreach ($iterable as $item) { + $initial = $reduce($initial, $item); + } + + return $initial; +} + +function iterable(iterable $iterable, ?callable $filter = null, ?callable $map = null): IterableObject { return new IterableObject($iterable, $filter, $map); } diff --git a/tests/IsIterableTest.php b/tests/IsIterableTest.php deleted file mode 100644 index 7449010..0000000 --- a/tests/IsIterableTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertTrue(function_exists('is_iterable')); - } - - public function testArrayIsIterable() - { - $array = array('foo', 'bar'); - $this->assertTrue(is_iterable($array)); - } - - public function testIteratorIsIterable() - { - $iterator = new DirectoryIterator(__DIR__); - $this->assertTrue(is_iterable($iterator)); - } - - public function testScalarIsNotIterable() - { - $scalar = 'foobar'; - $this->assertFalse(is_iterable($scalar)); - } - - public function testObjectIsNotIterable() - { - $object = new \stdClass(); - $this->assertFalse(is_iterable($object)); - } - - public function testResourceIsNotIterable() - { - $resource = fopen('php://temp', 'rb'); - $this->assertFalse(is_iterable($resource)); - } - -} diff --git a/tests/IterableFilterTest.php b/tests/IterableFilterTest.php index fdc2539..36b7d38 100644 --- a/tests/IterableFilterTest.php +++ b/tests/IterableFilterTest.php @@ -1,35 +1,26 @@ assertEquals(array(1 => 'bar'), iterable_to_array(iterable_filter($iterable, $filter))); + $this->assertEquals([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter))); } - public function testTraversableFilter() + public function testTraversableFilter(): void { - $iterable = SplFixedArray::fromArray(array('foo', 'bar')); - $filter = function ($input) { + $iterable = SplFixedArray::fromArray(['foo', 'bar']); + $filter = static function ($input) { return 'bar' === $input; }; - $this->assertEquals(array(1 => 'bar'), iterable_to_array(iterable_filter($iterable, $filter))); - } - - public function testInvalidIterable() - { - $this->expectException(InvalidArgumentException::class); - - $filter = function () { - return true; - }; - iterable_filter('foo', $filter); + $this->assertEquals([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter))); } } diff --git a/tests/IterableMapTest.php b/tests/IterableMapTest.php index 0746827..0a0cda9 100644 --- a/tests/IterableMapTest.php +++ b/tests/IterableMapTest.php @@ -1,31 +1,22 @@ assertEquals(array('FOO', 'BAR'), iterable_to_array(iterable_map($iterable, $map))); + $this->assertEquals(['FOO', 'BAR'], iterable_to_array(iterable_map($iterable, $map))); } - public function testTraversableMap() + public function testTraversableMap(): void { - $iterable = SplFixedArray::fromArray(array('foo', 'bar')); + $iterable = SplFixedArray::fromArray(['foo', 'bar']); $map = 'strtoupper'; - $this->assertEquals(array('FOO', 'BAR'), iterable_to_array(iterable_map($iterable, $map))); - } - - public function testInvalidIterable() - { - $this->expectException(InvalidArgumentException::class); - - $filter = function () { - return true; - }; - iterable_map('foo', $filter); + $this->assertEquals(['FOO', 'BAR'], iterable_to_array(iterable_map($iterable, $map))); } } diff --git a/tests/IterableObjectTest.php b/tests/IterableObjectTest.php index 5c09d8c..0547cd6 100644 --- a/tests/IterableObjectTest.php +++ b/tests/IterableObjectTest.php @@ -1,84 +1,87 @@ assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject); + $this->assertInstanceOf(IterableObject::class, $iterableObject); $this->assertEquals($expectedResult, iterator_to_array($iterableObject)); } + /** * @dataProvider dataProvider */ - public function testFromArrayToArray($data, $filter = null, $map = null, $expectedResult) + public function testFromArrayToArray($data, $filter = null, $map = null, $expectedResult): void { $iterableObject = iterable($data, $filter, $map); - $this->assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject); + $this->assertInstanceOf(IterableObject::class, $iterableObject); $this->assertEquals($expectedResult, $iterableObject->asArray()); } /** * @dataProvider dataProvider */ - public function testFromTraversableToIterator($data, $filter = null, $map = null, $expectedResult) + public function testFromTraversableToIterator($data, $filter = null, $map = null, $expectedResult): void { $data = SplFixedArray::fromArray($data); $iterableObject = iterable($data, $filter, $map); - $this->assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject); + $this->assertInstanceOf(IterableObject::class, $iterableObject); $this->assertEquals($expectedResult, iterator_to_array($iterableObject)); } + /** * @dataProvider dataProvider */ - public function testFromTraversableToArray($data, $filter = null, $map = null, $expectedResult) + public function testFromTraversableToArray($data, $filter = null, $map = null, $expectedResult): void { $data = SplFixedArray::fromArray($data); $iterableObject = iterable($data, $filter, $map); - $this->assertInstanceOf('BenTools\IterableFunctions\IterableObject', $iterableObject); + $this->assertInstanceOf(IterableObject::class, $iterableObject); $this->assertEquals($expectedResult, $iterableObject->asArray()); } public function dataProvider() { - $data = array('foo', 'bar'); - $filter = function ($value) { + $data = ['foo', 'bar']; + $filter = static function ($value) { return 'bar' === $value; }; $map = 'strtoupper'; - return array( - array( + return [ + [ $data, null, null, - array('foo', 'bar') - ), - array( + ['foo', 'bar'], + ], + [ $data, $filter, null, - array(1 => 'bar') - ), - array( + [1 => 'bar'], + ], + [ $data, null, $map, - array('FOO', 'BAR') - ), - array( + ['FOO', 'BAR'], + ], + [ $data, $filter, $map, - array(1 => 'BAR') - ), - ); + [1 => 'BAR'], + ], + ]; } } diff --git a/tests/IterableReduceTest.php b/tests/IterableReduceTest.php index c503629..78345d6 100644 --- a/tests/IterableReduceTest.php +++ b/tests/IterableReduceTest.php @@ -1,10 +1,11 @@ assertTrue(function_exists('iterable_to_array')); + $iterator = new ArrayIterator(['foo', 'bar']); + $this->assertEquals(['foo', 'bar'], iterable_to_array($iterator)); } - public function testIteratorToArray() + public function testIteratorWithoutKeysToArray(): void { - $iterator = new ArrayIterator(array('foo', 'bar')); - $this->assertEquals(array('foo', 'bar'), iterable_to_array($iterator)); + $iterator = new ArrayIterator([1 => 'foo', 2 => 'bar']); + $this->assertEquals([0 => 'foo', 1 => 'bar'], iterable_to_array($iterator, false)); } - public function testIteratorWithoutKeysToArray() + public function testArrayToArray(): void { - $iterator = new ArrayIterator(array(1 => 'foo', 2 => 'bar')); - $this->assertEquals(array(0 => 'foo', 1 => 'bar'), iterable_to_array($iterator, false)); + $array = ['foo', 'bar']; + $this->assertEquals(['foo', 'bar'], iterable_to_array($array)); } - public function testArrayToArray() + public function testArrayWithoutKeysToArray(): void { - $array = array('foo', 'bar'); - $this->assertEquals(array('foo', 'bar'), iterable_to_array($array)); + $array = [1 => 'foo', 2 => 'bar']; + $this->assertEquals([0 => 'foo', 1 => 'bar'], iterable_to_array($array, false)); } - public function testArrayWithoutKeysToArray() - { - $array = array(1 => 'foo', 2 => 'bar'); - $this->assertEquals(array(0 => 'foo', 1 => 'bar'), iterable_to_array($array, false)); - } - - public function testScalarToArray() + public function testScalarToArray(): void { $scalar = 'foobar'; $this->assertTrue($this->triggersError($scalar)); } - public function testObjectToArray() + public function testObjectToArray(): void { $object = new stdClass(); $this->assertTrue($this->triggersError($object)); } - public function testResourceToArray() + public function testResourceToArray(): void { $resource = fopen('php://temp', 'rb'); $this->assertTrue($this->triggersError($resource)); } - private function triggersError($input) - { - return version_compare(PHP_VERSION, '7.0.0') >= 0 ? $this->triggersErrorPHP7($input) : $this->triggersErrorPHP5($input); - } - - private function triggersErrorPHP7($input) + private function triggersError($input): bool { $errorOccured = false; try { iterable_to_array($input); - } - catch (\TypeError $e) { + } catch (\TypeError $e) { $errorOccured = true; } return $errorOccured; } - private function triggersErrorPHP5($input) - { - $errorOccured = false; - - set_error_handler(function ($errno) { - return E_RECOVERABLE_ERROR === $errno; - }); - - if (false === @iterable_to_array($input)) { - $errorOccured = true; - } - - restore_error_handler(); - - return $errorOccured; - } - } diff --git a/tests/IterableToTraversableTest.php b/tests/IterableToTraversableTest.php index b39c9cc..94be524 100644 --- a/tests/IterableToTraversableTest.php +++ b/tests/IterableToTraversableTest.php @@ -1,34 +1,27 @@ assertTrue(function_exists('iterable_to_traversable')); - } - - public function testIteratorToTraversable() + public function testIteratorToTraversable(): void { - $iterator = new ArrayIterator(array('foo' => 'bar')); + $iterator = new ArrayIterator(['foo' => 'bar']); $traversable = iterable_to_traversable($iterator); $this->assertSame($iterator, $traversable); - $this->assertInstanceOf('Traversable', $iterator); } - public function testArrayToTraversable() + public function testArrayToTraversable(): void { - $array = array('foo' => 'bar'); + $array = ['foo' => 'bar']; $traversable = iterable_to_traversable($array); - $this->assertEquals(new ArrayIterator(array('foo' => 'bar')), $traversable); - $this->assertInstanceOf('Traversable', $traversable); + $this->assertEquals(new ArrayIterator(['foo' => 'bar']), $traversable); } - public function testInvalidArgument() + public function testInvalidArgument(): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(TypeError::class); $string = 'foo'; iterable_to_traversable($string);