From 6462408d21754eb62800d5ac6e94e92adb033a2c Mon Sep 17 00:00:00 2001 From: Jasper Zonneveld Date: Fri, 1 Jun 2018 11:58:10 +0200 Subject: [PATCH] Partially rewrite JsonApi\Hydrator to improve performance --- src/JsonApi/Hydrator.php | 160 +++++++++++++++------------------ tests/JsonApi/HydratorTest.php | 4 +- 2 files changed, 72 insertions(+), 92 deletions(-) diff --git a/src/JsonApi/Hydrator.php b/src/JsonApi/Hydrator.php index 54956bd..420c4e4 100644 --- a/src/JsonApi/Hydrator.php +++ b/src/JsonApi/Hydrator.php @@ -2,13 +2,11 @@ namespace Swis\JsonApi\Client\JsonApi; -use Art4\JsonApiClient\AccessInterface; +use Art4\JsonApiClient\ElementInterface; use Art4\JsonApiClient\ResourceCollectionInterface; -use Art4\JsonApiClient\ResourceCollectionInterface as JsonApiCollection; -use Art4\JsonApiClient\ResourceIdentifierCollection as IdentifierCollection; use Art4\JsonApiClient\ResourceIdentifierCollectionInterface; use Art4\JsonApiClient\ResourceIdentifierInterface; -use Art4\JsonApiClient\ResourceItemInterface as JsonApItem; +use Art4\JsonApiClient\ResourceItemInterface; use Swis\JsonApi\Client\Collection; use Swis\JsonApi\Client\Interfaces\ItemInterface; use Swis\JsonApi\Client\Interfaces\TypeMapperInterface; @@ -20,7 +18,7 @@ class Hydrator /** * @var \Swis\JsonApi\Client\Interfaces\TypeMapperInterface */ - private $typeMapper; + protected $typeMapper; /** * @param \Swis\JsonApi\Client\Interfaces\TypeMapperInterface $typeMapper @@ -30,62 +28,37 @@ public function __construct(TypeMapperInterface $typeMapper) $this->typeMapper = $typeMapper; } - /** - * @param \Art4\JsonApiClient\ResourceCollectionInterface $jsonApiCollection - * - * @return \Swis\JsonApi\Client\Collection - */ - public function hydrateCollection(JsonApiCollection $jsonApiCollection) - { - $collection = new Collection(); - foreach ($jsonApiCollection->asArray() as $item) { - $collection->push($this->hydrateItem($item)); - } - - return $collection; - } - /** * @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem * * @return \Swis\JsonApi\Client\Interfaces\ItemInterface */ - public function hydrateItem(JsonApItem $jsonApiItem) + public function hydrateItem(ResourceItemInterface $jsonApiItem): ItemInterface { - $item = $this->getItemClass($jsonApiItem); + $item = $this->getItemClass($jsonApiItem->get('type')); - $item->setType($jsonApiItem->get('type')) - ->setId($jsonApiItem->get('id')); + $item->setId($jsonApiItem->get('id')); - $this->hydrateAttributes($jsonApiItem, $item); + if ($jsonApiItem->has('attributes')) { + $item->fill($jsonApiItem->get('attributes')->asArray(true)); + } return $item; } /** - * @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem + * @param \Art4\JsonApiClient\ResourceCollectionInterface $jsonApiCollection * - * @return \Swis\JsonApi\Client\Interfaces\ItemInterface + * @return \Swis\JsonApi\Client\Collection */ - protected function getItemClass(JsonApItem $jsonApiItem): ItemInterface + public function hydrateCollection(ResourceCollectionInterface $jsonApiCollection): Collection { - $type = $jsonApiItem->get('type'); - if ($this->typeMapper->hasMapping($type)) { - return $this->typeMapper->getMapping($type); + $collection = new Collection(); + foreach ($jsonApiCollection->asArray() as $item) { + $collection->push($this->hydrateItem($item)); } - return new JenssegersItem(); - } - - /** - * @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item - */ - protected function hydrateAttributes(JsonApItem $jsonApiItem, ItemInterface $item) - { - if ($jsonApiItem->has('attributes')) { - $item->fill($jsonApiItem->get('attributes')->asArray(true)); - } + return $collection; } /** @@ -94,35 +67,39 @@ protected function hydrateAttributes(JsonApItem $jsonApiItem, ItemInterface $ite */ public function hydrateRelationships(Collection $jsonApiItems, Collection $items) { + $keyedItems = $items->keyBy( + function (ItemInterface $item) { + return $this->getItemKey($item); + } + ); + $jsonApiItems->each( - function (JsonApItem $jsonApiItem) use ($items) { + function (ResourceItemInterface $jsonApiItem) use ($keyedItems) { if (!$jsonApiItem->has('relationships')) { return; } - $item = $this->getIncludedItem($items, $jsonApiItem); + $item = $this->getItem($keyedItems, $jsonApiItem); if ($item instanceof NullItem) { return; } - $relationships = $this->getJsonApiDocumentRelationships($jsonApiItem); - - foreach ($relationships as $name => $relationship) { - /** @var \Art4\JsonApiClient\ResourceItemInterface $data */ + foreach ($jsonApiItem->get('relationships')->asArray() as $name => $relationship) { + /** @var \Art4\JsonApiClient\ElementInterface $data */ $data = $relationship->get('data'); $method = camel_case($name); if ($data instanceof ResourceIdentifierInterface) { - $includedItem = $this->getIncludedItem($items, $data); + $includedItem = $this->getItem($keyedItems, $data); if ($includedItem instanceof NullItem) { continue; } $item->setRelation($method, $includedItem); - } elseif ($data instanceof ResourceCollectionInterface || $data instanceof ResourceIdentifierCollectionInterface) { - $collection = $this->getIncludedItems($items, $data); + } elseif ($data instanceof ResourceIdentifierCollectionInterface) { + $collection = $this->getCollection($keyedItems, $data); $item->setRelation($method, $collection); } @@ -132,72 +109,75 @@ function (JsonApItem $jsonApiItem) use ($items) { } /** - * @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem + * @param string $type * - * @return \Art4\JsonApiClient\Relationship[] + * @return \Swis\JsonApi\Client\Interfaces\ItemInterface */ - protected function getJsonApiDocumentRelationships(JsonApItem $jsonApiItem): array + protected function getItemClass(string $type): ItemInterface { - return $jsonApiItem->get('relationships')->asArray(false); + if ($this->typeMapper->hasMapping($type)) { + return $this->typeMapper->getMapping($type); + } + + return (new JenssegersItem())->setType($type); } /** - * @param \Swis\JsonApi\Client\Collection $included - * @param \Art4\JsonApiClient\AccessInterface $accessor + * @param \Swis\JsonApi\Client\Collection $included + * @param \Art4\JsonApiClient\ResourceIdentifierInterface|\Art4\JsonApiClient\ResourceItemInterface $identifier * * @return \Swis\JsonApi\Client\Interfaces\ItemInterface */ - protected function getIncludedItem(Collection $included, AccessInterface $accessor): ItemInterface + protected function getItem(Collection $included, $identifier): ItemInterface { - return $included->first( - function (ItemInterface $item) use ($accessor) { - return $this->accessorBelongsToItem($accessor, $item); - }, - new NullItem() + return $included->get( + $this->getElementKey($identifier), + function () { + return new NullItem(); + } ); } /** - * @param \Art4\JsonApiClient\AccessInterface $accessor - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item + * @param \Swis\JsonApi\Client\Collection $included + * @param \Art4\JsonApiClient\ResourceIdentifierCollectionInterface|\Art4\JsonApiClient\ResourceCollectionInterface $identifierCollection * - * @return bool + * @return \Swis\JsonApi\Client\Collection */ - protected function accessorBelongsToItem(AccessInterface $accessor, ItemInterface $item): bool + protected function getCollection(Collection $included, $identifierCollection): Collection { - return $item->getType() === $accessor->get('type') - && (string)$item->getId() === $accessor->get('id'); + $items = new Collection(); + + foreach ($identifierCollection->asArray() as $identifier) { + $item = $this->getItem($included, $identifier); + + if ($item instanceof NullItem) { + continue; + } + + $items->push($item); + } + + return $items; } /** - * @param \Swis\JsonApi\Client\Collection $included - * @param \Art4\JsonApiClient\ResourceIdentifierCollection $collection + * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item * - * @return \Swis\JsonApi\Client\Collection + * @return string */ - protected function getIncludedItems(Collection $included, IdentifierCollection $collection): Collection + protected function getItemKey(ItemInterface $item): string { - return $included->filter( - function (ItemInterface $item) use ($collection) { - return $this->itemExistsInRelatedIdentifiers($collection->asArray(false), $item); - } - )->values(); + return sprintf('%s:%s', $item->getType(), $item->getId()); } /** - * @param \Art4\JsonApiClient\ResourceIdentifier[] $relatedIdentifiers - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item + * @param \Art4\JsonApiClient\ElementInterface $accessor * - * @return bool + * @return string */ - protected function itemExistsInRelatedIdentifiers(array $relatedIdentifiers, ItemInterface $item): bool + protected function getElementKey(ElementInterface $accessor): string { - foreach ($relatedIdentifiers as $relatedIdentifier) { - if ($this->accessorBelongsToItem($relatedIdentifier, $item)) { - return true; - } - } - - return false; + return sprintf('%s:%s', $accessor->get('type'), $accessor->get('id')); } } diff --git a/tests/JsonApi/HydratorTest.php b/tests/JsonApi/HydratorTest.php index 1a8f0a5..8aea3a1 100644 --- a/tests/JsonApi/HydratorTest.php +++ b/tests/JsonApi/HydratorTest.php @@ -54,8 +54,8 @@ protected function getTypeMapperMock() $typeMapper->method('hasMapping')->willReturn(true); $typeMapper->method('getMapping')->will( $this->returnCallback( - function () { - return new JenssegersItem(); + function (string $type) { + return (new JenssegersItem())->setType($type); } ) );