diff --git a/phpstan.neon b/phpstan.neon index e346066..bc9ccbe 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,3 +11,7 @@ parameters: message: '#, PHPUnit\Framework\MockObject\MockObject|[a-zA-Z0-9\\_] given.#' paths: - tests/UnitTests + - + message: '#Unable to resolve the template type ExpectedType in call to method static method.#' + paths: + - tests/UnitTests/Contract/CollectionInterfaceTest.php diff --git a/src/Collection/ElementCollection.php b/src/Collection/ElementCollection.php new file mode 100644 index 0000000..00ce66f --- /dev/null +++ b/src/Collection/ElementCollection.php @@ -0,0 +1,95 @@ + + * @template-implements CollectionInterface + */ +class ElementCollection implements CollectionInterface +{ + /** @var array */ + private array $elements = []; + + public function add(ElementInterface $element): void + { + $this->elements[] = $element; + } + + /** + * @inheritDoc + */ + public function offsetExists($offset): bool + { + return isset($this->elements[$offset]); + } + + /** + * @inheritDoc + */ + public function offsetGet($offset) + { + return $this->elements[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value): void + { + if ($offset === null) { + $this->elements[] = $value; + return; + } + + $this->elements[$offset] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset): void + { + unset($this->elements[$offset]); + } + + /** + * @inheritDoc + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->elements); + } + + /** + * @inheritDoc + */ + public function count(): int + { + return count($this->elements); + } +} diff --git a/src/Contract/CollectionInterface.php b/src/Contract/CollectionInterface.php new file mode 100644 index 0000000..cf0accd --- /dev/null +++ b/src/Contract/CollectionInterface.php @@ -0,0 +1,35 @@ + + * @template TKey + * @template T + * @template-extends \IteratorAggregate + * @template-extends \ArrayAccess + */ +interface CollectionInterface extends \ArrayAccess, \Countable, \IteratorAggregate +{ +} diff --git a/src/Contract/ElementInterface.php b/src/Contract/ElementInterface.php new file mode 100644 index 0000000..0980f6d --- /dev/null +++ b/src/Contract/ElementInterface.php @@ -0,0 +1,31 @@ + + */ +interface ElementInterface +{ +} diff --git a/tests/UnitTests/Collection/ElementCollectionTest.php b/tests/UnitTests/Collection/ElementCollectionTest.php new file mode 100644 index 0000000..8a49cf9 --- /dev/null +++ b/tests/UnitTests/Collection/ElementCollectionTest.php @@ -0,0 +1,154 @@ + + */ +class ElementCollectionTest extends TestCase +{ + protected ?MockObject $mockElementInterface; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->mockElementInterface = $this->createMock(ElementInterface::class); + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $this->mockElementInterface = null; + } + + public function testElementCollectionImplementsCollectionInterface(): void + { + self::assertInstanceOf(CollectionInterface::class, new ElementCollection()); + } + + public function testElementCollectionHasElementProperty(): void + { + self::assertClassHasAttribute('elements', ElementCollection::class); + } + + public function interfaceMethodDataProvider(): \Generator + { + yield ['offsetExists']; + yield ['offsetGet']; + yield ['offsetSet']; + yield ['offsetUnset']; + yield ['count']; + yield ['getIterator']; + } + + /** + * @dataProvider interfaceMethodDataProvider + */ + public function testElementCollectionImplementsInterfaceMethods(string $methodName): void + { + self::assertTrue(method_exists(ElementCollection::class, $methodName)); + } + + public function testAddMethodAddsElementInterfaceToCollection(): void + { + $collection = new ElementCollection(); + $collection->add($this->mockElementInterface); + + self::assertSame($this->mockElementInterface, $collection[0]); + } + + public function testCountReturnsCountOfElementsProperty(): void + { + $collection = new ElementCollection(); + $collection->add($this->mockElementInterface); + $collection->add($this->mockElementInterface); + + self::assertSame(2, $collection->count()); + } + + public function testGetIteratorProvidesArrayIteratorForElementProperty(): void + { + $collection = new ElementCollection(); + $collection->add($this->mockElementInterface); + + $result = $collection->getIterator(); + + $this->assertInstanceOf(\ArrayIterator::class, $result); + self::assertCount(1, $result); + } + + public function testOffsetExistsReturnsBoolIfElementsPropertyKeyExists(): void + { + $collection = new ElementCollection(); + + self::assertFalse($collection->offsetExists(0)); + $collection->add($this->mockElementInterface); + self::assertTrue($collection->offsetExists(0)); + } + + public function testOffsetGetReturnsElementFromElementsPropertyAtGivenKey(): void + { + $collection = new ElementCollection(); + $collection->add($this->mockElementInterface); + + self::assertSame($this->mockElementInterface, $collection->offsetGet(0)); + } + + public function testOffsetSetAddsElementToElementsPropertyWithNullKey(): void + { + $collection = new ElementCollection(); + $collection->offsetSet(null, $this->mockElementInterface); + + self::assertArrayHasKey(0, $collection); + } + + public function testOffsetSetAddsElementToElementsPropertyWithKey(): void + { + $collection = new ElementCollection(); + $collection->offsetSet('test', $this->mockElementInterface); + + self::assertArrayHasKey('test', $collection); + } + + public function testOffsetUnsetRemovesElementFromElementsPropertyWithProvidedKey(): void + { + $collection = new ElementCollection(); + $collection[5] = $this->mockElementInterface; + + self::assertCount(1, $collection); + + $collection->offsetUnset(5); + self::assertCount(0, $collection); + } +} diff --git a/tests/UnitTests/Contract/CollectionInterfaceTest.php b/tests/UnitTests/Contract/CollectionInterfaceTest.php new file mode 100644 index 0000000..7c6e4bf --- /dev/null +++ b/tests/UnitTests/Contract/CollectionInterfaceTest.php @@ -0,0 +1,52 @@ + + */ +class CollectionInterfaceTest extends TestCase +{ + public function interfaceDateProvider(): array + { + return [ + [\ArrayAccess::class], + [\Countable::class], + [\IteratorAggregate::class] + ]; + } + + /** + * @dataProvider interfaceDateProvider + */ + public function testCollectionInterfaceExtendsInterfaces(string $interfaceSignature): void + { + $collection = $this->createMock(CollectionInterface::class); + + self::assertInstanceOf($interfaceSignature, $collection); + } +}