From 5e0058bd0b775ac8462c5fd9ca2204f9dc4a1647 Mon Sep 17 00:00:00 2001 From: Richard van Laak Date: Tue, 15 Jun 2021 12:19:24 +0200 Subject: [PATCH] encapsulation over inheritance: hide ItemList API on Item behind a facade in a BC way --- src/Micrometa/Ports/Item/Collection.php | 72 +++++++++++++++++++ src/Micrometa/Ports/Item/CollectionFacade.php | 46 ++++++++++++ src/Micrometa/Ports/Item/Item.php | 43 ++++++++++- src/Micrometa/Ports/Item/ItemInterface.php | 2 +- src/Micrometa/Ports/Item/ItemList.php | 58 +-------------- .../Ports/Item/ItemListInterface.php | 31 +------- .../Micrometa/Ports/Item/ItemListTest.php | 71 ++++++++++++++++++ src/Micrometa/Tests/Ports/ItemTest.php | 35 +++------ 8 files changed, 245 insertions(+), 113 deletions(-) create mode 100644 src/Micrometa/Ports/Item/Collection.php create mode 100644 src/Micrometa/Ports/Item/CollectionFacade.php create mode 100644 src/Micrometa/Tests/Micrometa/Ports/Item/ItemListTest.php diff --git a/src/Micrometa/Ports/Item/Collection.php b/src/Micrometa/Ports/Item/Collection.php new file mode 100644 index 0000000..0afb6d8 --- /dev/null +++ b/src/Micrometa/Ports/Item/Collection.php @@ -0,0 +1,72 @@ +getItemByTypeAndIndex($type, $index); + } + + /** + * Return an item by type and index + * + * @param string $type Item type + * @param int $index Item index + * + * @return ItemInterface Item + * @throws OutOfBoundsException If the item index is out of bounds + */ + private function getItemByTypeAndIndex($type, $index) + { + $typeItems = $this->getItems($type); + + // If the item index is out of bounds + if (count($typeItems) <= $index) { + throw new OutOfBoundsException( + sprintf(OutOfBoundsException::INVALID_ITEM_INDEX_STR, $index), + OutOfBoundsException::INVALID_ITEM_INDEX + ); + } + + return $typeItems[$index]; + } +} diff --git a/src/Micrometa/Ports/Item/CollectionFacade.php b/src/Micrometa/Ports/Item/CollectionFacade.php new file mode 100644 index 0000000..d917efb --- /dev/null +++ b/src/Micrometa/Ports/Item/CollectionFacade.php @@ -0,0 +1,46 @@ +item = $item; - parent::__construct(ItemFactory::createFromApplicationItems($this->item->getChildren())); + $this->itemList = new ItemList(ItemFactory::createFromApplicationItems($this->item->getChildren())); } /** @@ -328,4 +333,38 @@ public function getValue() { return $this->item->getValue(); } + + /** + * Filter the items by item type(s). + * + * @param array ...$types Item types + * + * @return ItemInterface[] Items matching the requested types + */ + public function getItems(...$types) + { + return $this->itemList->getItems(...$types); + } + + /** + * Return the first item, optionally of particular types. + * + * @param array ...$types Item types + * + * @return ItemInterface Item + */ + public function getFirstItem(...$types) + { + return $this->itemList->getFirstItem(...$types); + } + + /** + * Count the elements in the item list. + * + * @return int + */ + public function count() + { + return $this->itemList->count(); + } } diff --git a/src/Micrometa/Ports/Item/ItemInterface.php b/src/Micrometa/Ports/Item/ItemInterface.php index 0a2558d..17f91ef 100755 --- a/src/Micrometa/Ports/Item/ItemInterface.php +++ b/src/Micrometa/Ports/Item/ItemInterface.php @@ -45,7 +45,7 @@ * @package Jkphl\Micrometa * @subpackage Jkphl\Micrometa\Ports */ -interface ItemInterface extends ItemListInterface +interface ItemInterface { /** * Return whether the item is of a particular type (or contained in a list of types) diff --git a/src/Micrometa/Ports/Item/ItemList.php b/src/Micrometa/Ports/Item/ItemList.php index 814d1e2..5acfbfb 100755 --- a/src/Micrometa/Ports/Item/ItemList.php +++ b/src/Micrometa/Ports/Item/ItemList.php @@ -36,17 +36,16 @@ namespace Jkphl\Micrometa\Ports\Item; -use Jkphl\Micrometa\Ports\Exceptions\InvalidArgumentException; use Jkphl\Micrometa\Ports\Exceptions\OutOfBoundsException; use Jkphl\Micrometa\Ports\Exceptions\RuntimeException; /** - * Abstract item list + * Item list * * @package Jkphl\Micrometa * @subpackage Jkphl\Micrometa\Ports */ -class ItemList implements ItemListInterface +class ItemList extends Collection implements ItemListInterface { /** * Items @@ -255,57 +254,4 @@ public function count() { return count($this->items); } - - /** - * Generic item getter - * - * @param string $type Item type - * @param array $arguments Arguments - * - * @return ItemInterface Item - * @throws InvalidArgumentException If the item index is invalid - * @api - */ - public function __call($type, $arguments) - { - $index = 0; - if (count($arguments)) { - // If the item index is invalid - if (!is_int($arguments[0]) || ($arguments[0] < 0)) { - throw new InvalidArgumentException( - sprintf(InvalidArgumentException::INVALID_ITEM_INDEX_STR, $arguments[0]), - InvalidArgumentException::INVALID_ITEM_INDEX - ); - } - - $index = $arguments[0]; - } - - // Return the item by type and index - return $this->getItemByTypeAndIndex($type, $index); - } - - /** - * Return an item by type and index - * - * @param string $type Item type - * @param int $index Item index - * - * @return ItemInterface Item - * @throws OutOfBoundsException If the item index is out of bounds - */ - protected function getItemByTypeAndIndex($type, $index) - { - $typeItems = $this->getItems($type); - - // If the item index is out of bounds - if (count($typeItems) <= $index) { - throw new OutOfBoundsException( - sprintf(OutOfBoundsException::INVALID_ITEM_INDEX_STR, $index), - OutOfBoundsException::INVALID_ITEM_INDEX - ); - } - - return $typeItems[$index]; - } } diff --git a/src/Micrometa/Ports/Item/ItemListInterface.php b/src/Micrometa/Ports/Item/ItemListInterface.php index 6d14ed5..30b9191 100755 --- a/src/Micrometa/Ports/Item/ItemListInterface.php +++ b/src/Micrometa/Ports/Item/ItemListInterface.php @@ -39,36 +39,11 @@ /** * Item list interface * + * Prefer type-hinting against CollectionFacade over this interface to narrow-down the number of public methods. + * * @package Jkphl\Micrometa * @subpackage Jkphl\Micrometa\Ports */ -interface ItemListInterface extends \Iterator, \Countable, \ArrayAccess +interface ItemListInterface extends CollectionFacade, \Iterator, \ArrayAccess { - /** - * Return an object representation of the item list - * - * @return \stdClass Micro information items - * @api - */ - public function toObject(); - - /** - * Filter the items by item type(s) - * - * @param array ...$types Item types - * - * @return ItemInterface[] Items matching the requested types - * @api - */ - public function getItems(...$types); - - /** - * Return the first item, optionally of particular types - * - * @param array ...$types Item types - * - * @return ItemInterface Item - * @api - */ - public function getFirstItem(...$types); } diff --git a/src/Micrometa/Tests/Micrometa/Ports/Item/ItemListTest.php b/src/Micrometa/Tests/Micrometa/Ports/Item/ItemListTest.php new file mode 100644 index 0000000..b156dfd --- /dev/null +++ b/src/Micrometa/Tests/Micrometa/Ports/Item/ItemListTest.php @@ -0,0 +1,71 @@ +feedItemList = new ItemList([$this->getFeedItem(), $this->getFeedItem()]); + } + + public function testNestedItemsCounts() + { + // Test the number of nested items + self::assertCount(2, $this->feedItemList); + self::assertCount(2, $this->feedItemList->getItems()); + } + + public function testNestedItemsIteration() + { + foreach ($this->feedItemList->getItems() as $itemIndex => $entryItem) { + self::assertInstanceOf(ItemInterface::class, $entryItem); + self::assertIsInt($itemIndex); + } + } + + public function testNestedItemsRetrievalViaArrayAccess() + { + $entryItems = $this->feedItemList->getFirstItem()->getItems('h-entry'); + $entryItem = $entryItems[1]; + + self::assertInstanceOf(ItemInterface::class, $entryItem); + } + + public function testFirstNestedItemRetrieval() + { + self::assertInstanceOf(ItemInterface::class, $this->feedItemList[0]->getFirstItem('h-entry')); + self::assertInstanceOf( + ItemInterface::class, + $this->feedItemList[0]->getFirstItem('h-entry', MicroformatsFactory::MF2_PROFILE_URI) + ); + } + + public function testExistingFirstNestedItem() + { + self::assertEquals('John Doe', $this->feedItemList[0]->hEntry()->author->name); + self::assertEquals('John Doe', $this->feedItemList->getFirstItem()->hEntry()->author->name); + } + + public function testNonExistingSecondNestedItem() + { + $this->expectException(OutOfBoundsException::class); + $this->expectExceptionCode('1492418999'); + + $this->feedItemList->hEntry(2); + } +} diff --git a/src/Micrometa/Tests/Ports/ItemTest.php b/src/Micrometa/Tests/Ports/ItemTest.php index 7d1aa15..910dac2 100755 --- a/src/Micrometa/Tests/Ports/ItemTest.php +++ b/src/Micrometa/Tests/Ports/ItemTest.php @@ -44,6 +44,7 @@ use Jkphl\Micrometa\Ports\Item\ItemList; use Jkphl\Micrometa\Tests\AbstractTestBase; use Jkphl\Micrometa\Tests\MicroformatsFeedTrait; +use Jkphl\Micrometa\Ports\Exceptions\OutOfBoundsException; /** * Parser factory tests @@ -247,10 +248,8 @@ public function testItemPropertyItem() */ public function testItemNestedItems() { - $this->expectException('Jkphl\Micrometa\Ports\Exceptions\InvalidArgumentException'); - $this->expectExceptionCode('1492418709'); $feedItem = $this->getFeedItem(); - $this->assertInstanceOf(Item::class, $feedItem); + self::assertInstanceOf(Item::class, $feedItem); // Test the number of nested items $this->assertEquals(2, count($feedItem)); @@ -266,38 +265,21 @@ public function testItemNestedItems() ); // Test the second entry item - $this->runFeedNestedEntryItemTest($feedItem); - } - - /** - * Test a nested entry item - * - * @param ItemInterface $feedItem Feed item - */ - protected function runFeedNestedEntryItemTest(ItemInterface $feedItem) - { $entryItem = $feedItem->getItems('h-entry')[1]; - $this->assertInstanceOf(ItemInterface::class, $entryItem); - - // Test the magic item getter / item type aliases - /** @noinspection PhpUndefinedMethodInspection */ - $entryItem = $feedItem->hEntry(0); - $this->assertInstanceOf(ItemInterface::class, $entryItem); - /** @noinspection PhpUndefinedMethodInspection */ - $feedItem->hEntry(-1); + self::assertInstanceOf(ItemInterface::class, $entryItem); } /** * Test non-existent nested item */ - public function testItemNonExistentNestedItems() + public function testNonExistentNestedItems() { - $this->expectException('Jkphl\Micrometa\Ports\Exceptions\OutOfBoundsException'); + $this->expectException(OutOfBoundsException::class); $this->expectExceptionCode('1492418999'); + $feedItem = $this->getFeedItem(); - /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals('John Doe', $feedItem->hEntry()->author->name); - /** @noinspection PhpUndefinedMethodInspection */ $feedItem->hEntry(2); } @@ -306,8 +288,9 @@ public function testItemNonExistentNestedItems() */ public function testItemListExport() { - $this->expectException('Jkphl\Micrometa\Ports\Exceptions\OutOfBoundsException'); + $this->expectException(OutOfBoundsException::class); $this->expectExceptionCode('1492030227'); + $feedItem = $this->getFeedItem(); $itemList = new ItemList([$feedItem]); $this->assertInstanceOf(ItemList::class, $itemList);