Skip to content

Commit

Permalink
Merge pull request #18 from swisnl/improve-hydrator-performance
Browse files Browse the repository at this point in the history
Partially rewrite JsonApi\Hydrator to improve performance
  • Loading branch information
JaZo authored Jun 1, 2018
2 parents 7061ce8 + 6462408 commit ef62078
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 92 deletions.
160 changes: 70 additions & 90 deletions src/JsonApi/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +18,7 @@ class Hydrator
/**
* @var \Swis\JsonApi\Client\Interfaces\TypeMapperInterface
*/
private $typeMapper;
protected $typeMapper;

/**
* @param \Swis\JsonApi\Client\Interfaces\TypeMapperInterface $typeMapper
Expand All @@ -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;
}

/**
Expand All @@ -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);
}
Expand All @@ -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'));
}
}
4 changes: 2 additions & 2 deletions tests/JsonApi/HydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
)
);
Expand Down

0 comments on commit ef62078

Please sign in to comment.