diff --git a/composer.json b/composer.json index 7cfc0defc..052409800 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ ], "require": { "php": ">= 7.4", - "loophp/iterators": "^1.5.6" + "loophp/iterators": "^1.5.8" }, "require-dev": { "amphp/parallel-functions": "^1", diff --git a/src/Collection.php b/src/Collection.php index 08cb869b1..7eb4c65cf 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -16,7 +16,6 @@ use IteratorAggregate; use loophp\collection\Contract\Collection as CollectionInterface; use loophp\collection\Contract\Operation; -use loophp\collection\Iterator\ResourceIterator; use loophp\collection\Operation\All; use loophp\collection\Operation\Append; use loophp\collection\Operation\Apply; @@ -135,6 +134,7 @@ use loophp\iterators\CachingIteratorAggregate; use loophp\iterators\ClosureIterator; use loophp\iterators\IterableIterator; +use loophp\iterators\ResourceIteratorAggregate; use loophp\iterators\StringIteratorAggregate; use NoRewindIterator; use Psr\Cache\CacheItemPoolInterface; @@ -470,7 +470,7 @@ public static function fromCallable(callable $callable, iterable $parameters = [ public static function fromFile(string $filepath): self { return new self( - static fn (): Iterator => new ResourceIterator(fopen($filepath, 'rb'), true), + static fn (): IteratorAggregate => new ResourceIteratorAggregate(fopen($filepath, 'rb'), true), ); } @@ -515,7 +515,7 @@ public static function fromIterable(iterable $iterable): self */ public static function fromResource($resource): self { - return new self(static fn (): Iterator => new ResourceIterator($resource)); + return new self(static fn (): IteratorAggregate => new ResourceIteratorAggregate($resource)); } /** diff --git a/src/Iterator/ResourceIterator.php b/src/Iterator/ResourceIterator.php deleted file mode 100644 index 4b3ad8725..000000000 --- a/src/Iterator/ResourceIterator.php +++ /dev/null @@ -1,91 +0,0 @@ - - */ -final class ResourceIterator implements Iterator -{ - /** - * @var Iterator - */ - private Iterator $iterator; - - /** - * @param false|resource $resource - */ - public function __construct($resource, bool $closeResource = false) - { - if (!is_resource($resource) || 'stream' !== get_resource_type($resource)) { - throw new InvalidArgumentException('Invalid resource type.'); - } - - $callback = - /** - * @param resource $resource - * - * @return Generator - */ - static function ($resource) use ($closeResource): Generator { - try { - while (false !== $chunk = fgetc($resource)) { - yield $chunk; - } - } finally { - if ($closeResource) { - fclose($resource); - } - } - }; - - $this->iterator = new ClosureIterator($callback, [$resource]); - } - - #[ReturnTypeWillChange] - public function current(): string - { - return $this->iterator->current(); - } - - /** - * @return int - */ - #[ReturnTypeWillChange] - public function key() - { - return $this->iterator->key(); - } - - public function next(): void - { - $this->iterator->next(); - } - - public function rewind(): void - { - $this->iterator->rewind(); - } - - public function valid(): bool - { - return $this->iterator->valid(); - } -} diff --git a/src/Iterator/TypedIterator.php b/src/Iterator/TypedIterator.php deleted file mode 100644 index b705f5bb8..000000000 --- a/src/Iterator/TypedIterator.php +++ /dev/null @@ -1,131 +0,0 @@ - - */ -final class TypedIterator implements Iterator -{ - /** - * @var Iterator - */ - private Iterator $iterator; - - /** - * @param Iterator $iterator - * @param null|callable(mixed): string $getType - */ - public function __construct(Iterator $iterator, ?callable $getType = null) - { - $getType ??= - /** - * @param mixed $variable - */ - static function ($variable): string { - if (!is_object($variable)) { - return gettype($variable); - } - - $interfaces = class_implements($variable); - - if ([] === $interfaces || false === $interfaces) { - return get_class($variable); - } - - sort($interfaces); - - return implode(',', $interfaces); - }; - - $this->iterator = new ClosureIterator( - /** - * @param Iterator $iterator - * - * @return Generator - */ - static function (Iterator $iterator) use ($getType): Generator { - $previousType = null; - - foreach ($iterator as $key => $value) { - if (null === $value) { - yield $key => $value; - - continue; - } - - $currentType = $getType($value); - $previousType ??= $currentType; - - if ($currentType !== $previousType) { - throw new InvalidArgumentException( - sprintf( - "Detected mixed types: '%s' and '%s' !", - $previousType, - $currentType - ) - ); - } - - yield $key => $value; - } - }, - [$iterator] - ); - } - - /** - * @return T - */ - #[ReturnTypeWillChange] - public function current() - { - return $this->iterator->current(); - } - - /** - * @return TKey - */ - #[ReturnTypeWillChange] - public function key() - { - return $this->iterator->key(); - } - - public function next(): void - { - $this->iterator->next(); - } - - public function rewind(): void - { - $this->iterator->rewind(); - } - - public function valid(): bool - { - return $this->iterator->valid(); - } -} diff --git a/src/Operation/Strict.php b/src/Operation/Strict.php index aaa39d6f9..2b2d06e24 100644 --- a/src/Operation/Strict.php +++ b/src/Operation/Strict.php @@ -11,7 +11,8 @@ use Closure; use Iterator; -use loophp\collection\Iterator\TypedIterator; +use IteratorAggregate; +use loophp\iterators\TypedIteratorAggregate; /** * @immutable @@ -24,7 +25,7 @@ final class Strict extends AbstractOperation /** * @pure * - * @return Closure(null|callable(mixed): string): Closure(Iterator): Iterator + * @return Closure(null|callable(mixed): string): Closure(Iterator): IteratorAggregate */ public function __invoke(): Closure { @@ -32,14 +33,14 @@ public function __invoke(): Closure /** * @param null|callable(mixed): string $callback * - * @return Closure(Iterator): Iterator + * @return Closure(Iterator): IteratorAggregate */ static fn (?callable $callback = null): Closure => /** * @param Iterator $iterator * - * @return Iterator + * @return IteratorAggregate */ - static fn (Iterator $iterator): Iterator => new TypedIterator($iterator, $callback); + static fn (Iterator $iterator): IteratorAggregate => new TypedIteratorAggregate($iterator, $callback); } } diff --git a/tests/unit/CollectionGenericOperationTest.php b/tests/unit/CollectionGenericOperationTest.php index f765e15e8..4f7ece8f0 100644 --- a/tests/unit/CollectionGenericOperationTest.php +++ b/tests/unit/CollectionGenericOperationTest.php @@ -21,6 +21,7 @@ final class CollectionGenericOperationTest extends TestCase { use GenericCollectionProviders; + use IterableAssertions; /** diff --git a/tests/unit/CollectionSpecificOperationTest.php b/tests/unit/CollectionSpecificOperationTest.php index 3d29988d3..a5fd4cc3c 100644 --- a/tests/unit/CollectionSpecificOperationTest.php +++ b/tests/unit/CollectionSpecificOperationTest.php @@ -33,6 +33,7 @@ final class CollectionSpecificOperationTest extends TestCase { use GenericCollectionProviders; + use IterableAssertions; public function testApplyOperation(): void diff --git a/tests/unit/Iterator/ResourceIteratorTest.php b/tests/unit/Iterator/ResourceIteratorTest.php deleted file mode 100644 index 49dc26028..000000000 --- a/tests/unit/Iterator/ResourceIteratorTest.php +++ /dev/null @@ -1,79 +0,0 @@ -expectException(InvalidArgumentException::class); - - $iterator = new ResourceIterator(false); - } - - public function testDoesNotAllowUnsupportedResource(): void - { - $this->expectException(InvalidArgumentException::class); - - $iterator = new ResourceIterator(imagecreate(100, 100)); - } - - public function testDoesNotCloseResourceByDefault(): void - { - $file = fopen(__DIR__ . '/../../fixtures/sample.txt', 'rb'); - - $iterator = new ResourceIterator($file); - - self::assertIdenticalIterable( - range('a', 'c'), - $iterator - ); - - self::assertTrue(is_resource($file)); - self::assertIsResource($file); - } -} diff --git a/tests/unit/Iterator/TypedIteratorTest.php b/tests/unit/Iterator/TypedIteratorTest.php deleted file mode 100644 index c89d9a1f4..000000000 --- a/tests/unit/Iterator/TypedIteratorTest.php +++ /dev/null @@ -1,451 +0,0 @@ - 1, 'bar' => 2]; - - public function testWithNullValues(): void - { - $data = ['a' => null, 'b' => null]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsArrayOfAnyType(): void - { - $data = [self::MAP_DATA, self::LIST_DATA]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsCustomGettypeCallback(): void - { - $callback = static fn ($variable) => gettype($variable); - - $obj1 = new class() { - public function sayHello(): string - { - return 'Hello'; - } - }; - $obj2 = new stdClass(); - - $data = [new $obj1(), new $obj2()]; - - $iterator = new TypedIterator(new ArrayIterator($data), $callback); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsDifferentClassesWithMultipleInterfaces(): void - { - $obj1 = new class() implements Countable, JsonSerializable { - public function count(): int - { - return 0; - } - - public function jsonSerialize(): string - { - return ''; - } - }; - - $obj2 = new class() implements Countable, JsonSerializable { - public function count(): int - { - return 0; - } - - public function jsonSerialize(): string - { - return ''; - } - }; - - $data = [new $obj1(), new $obj2()]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsDifferentClassesWithSameInterface(): void - { - $obj1 = new class() implements Countable { - public function count(): int - { - return 0; - } - }; - - $obj2 = new class() implements Countable { - public function count(): int - { - return 0; - } - }; - - $data = [new $obj1(), new $obj2()]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsDifferentClassesWithSameInterfaceButInDifferentOrder(): void - { - $obj1 = new class() implements IteratorAggregate, Countable { - public function count(): int - { - return 0; - } - - public function getIterator(): Traversable - { - yield 0; - } - }; - - $obj2 = new class() implements Countable, IteratorAggregate { - public function count(): int - { - return 0; - } - - public function getIterator(): Traversable - { - yield 0; - } - }; - - $data = [new $obj1(), new $obj2()]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsNullType(): void - { - $data = [1, null, 3]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsSameClassWithInterface(): void - { - $obj = new class() implements Countable { - public function count(): int - { - return 0; - } - }; - - $data = [new $obj(), new $obj()]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsSameClassWithMultipleInterfaces(): void - { - $obj = new class() implements Countable, JsonSerializable { - public function count(): int - { - return 0; - } - - public function jsonSerialize(): string - { - return ''; - } - }; - - $data = [new $obj(), new $obj()]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testAllowsSameClassWithoutInterface(): void - { - $obj1 = new stdClass(); - $obj1->id = 1; - - $obj2 = new stdClass(); - $obj2->id = 2; - - $data = [$obj1, $obj2]; - - $iterator = new TypedIterator(new ArrayIterator($data)); - self::assertIdenticalIterable( - $data, - $iterator - ); - } - - public function testDisallowsBoolMixed(): void - { - $iterator = new TypedIterator(new ArrayIterator([true, false, 'bar'])); - $iterator->next(); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsDifferentClasses(): void - { - $obj1 = new class() { - public function sayHello(): string - { - return 'Hello'; - } - }; - - $obj2 = new stdClass(); - - $iterator = new TypedIterator(new ArrayIterator([new $obj1(), new $obj2()])); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsFloatMixed(): void - { - $iterator = new TypedIterator(new ArrayIterator([2.3, 5.6, 1])); - $iterator->next(); - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsIntMixed(): void - { - $iterator = new TypedIterator(new ArrayIterator([1, 2, 'foo'])); - - $iterator->next(); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsMixedAtBeginning(): void - { - $iterator = new TypedIterator(new ArrayIterator([1, 'bar', 'foo'])); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsMixedInMiddle(): void - { - $iterator = new TypedIterator(new ArrayIterator([1, 'bar', 2])); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsMixOfClassesWithAndWithoutInterfaces(): void - { - $obj1 = new class() implements Countable { - public function count(): int - { - return 0; - } - }; - - $obj2 = new class() { - public function count(): int - { - return 0; - } - }; - - $iterator = new TypedIterator(new ArrayIterator([new $obj1(), new $obj2()])); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsMixOfClassesWithDifferentInterfaces(): void - { - $obj1 = new class() implements Countable { - public function count(): int - { - return 0; - } - }; - - $obj2 = new class() implements JsonSerializable { - public function jsonSerialize(): string - { - return ''; - } - }; - - $iterator = new TypedIterator(new ArrayIterator([new $obj1(), new $obj2()])); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsResourceMixedOpenClosed(): void - { - $openResource = fopen('data://text/plain,ABCD', 'rb'); - $closedResource = fopen('data://text/plain,XYZ', 'rb'); - fclose($closedResource); - - $iterator = new TypedIterator(new ArrayIterator([$openResource, $closedResource])); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testDisallowsStringMixed(): void - { - $iterator = new TypedIterator(new ArrayIterator(['foo', 'bar', 3])); - - $iterator->next(); - - $this->expectException(InvalidArgumentException::class); - - $iterator->next(); - } - - public function testIsInitializableFromArray(): void - { - $iterator = new TypedIterator(new ArrayIterator(self::LIST_DATA)); - - self::assertTrue($iterator->valid()); - - self::assertIdenticalIterable( - self::LIST_DATA, - $iterator - ); - } - - public function testIsInitializableFromGenerator(): void - { - $gen = static fn (): Generator => yield from self::MAP_DATA; - - $iterator = new TypedIterator($gen()); - - self::assertTrue($iterator->valid()); - self::assertIdenticalIterable( - self::MAP_DATA, - $iterator - ); - } - - public function testIsInitializableFromIterator(): void - { - $iterator = new TypedIterator(new ArrayIterator(self::LIST_DATA)); - - self::assertTrue($iterator->valid()); - self::assertIdenticalIterable( - self::LIST_DATA, - $iterator - ); - } - - public function testReturnAnIntKey(): void - { - $iterator = new TypedIterator(new ArrayIterator(self::LIST_DATA)); - - self::assertSame(0, $iterator->key()); - $iterator->next(); - self::assertSame(1, $iterator->key()); - } - - public function testReturnAStringKey(): void - { - $iterator = new TypedIterator(new ArrayIterator(self::MAP_DATA)); - - self::assertSame('foo', $iterator->key()); - $iterator->next(); - self::assertSame('bar', $iterator->key()); - } - - public function testRewind(): void - { - $iterator = new TypedIterator(new ArrayIterator(['foo'])); - - self::assertSame('foo', $iterator->current()); - - $iterator->next(); - self::assertFalse($iterator->valid()); - self::assertNull($iterator->current()); - - $iterator->rewind(); - self::assertTrue($iterator->valid()); - self::assertSame('foo', $iterator->current()); - } -}