Skip to content

Commit ef62078

Browse files
authored
Merge pull request swisnl#18 from swisnl/improve-hydrator-performance
Partially rewrite JsonApi\Hydrator to improve performance
2 parents 7061ce8 + 6462408 commit ef62078

File tree

2 files changed

+72
-92
lines changed

2 files changed

+72
-92
lines changed

src/JsonApi/Hydrator.php

Lines changed: 70 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
namespace Swis\JsonApi\Client\JsonApi;
44

5-
use Art4\JsonApiClient\AccessInterface;
5+
use Art4\JsonApiClient\ElementInterface;
66
use Art4\JsonApiClient\ResourceCollectionInterface;
7-
use Art4\JsonApiClient\ResourceCollectionInterface as JsonApiCollection;
8-
use Art4\JsonApiClient\ResourceIdentifierCollection as IdentifierCollection;
97
use Art4\JsonApiClient\ResourceIdentifierCollectionInterface;
108
use Art4\JsonApiClient\ResourceIdentifierInterface;
11-
use Art4\JsonApiClient\ResourceItemInterface as JsonApItem;
9+
use Art4\JsonApiClient\ResourceItemInterface;
1210
use Swis\JsonApi\Client\Collection;
1311
use Swis\JsonApi\Client\Interfaces\ItemInterface;
1412
use Swis\JsonApi\Client\Interfaces\TypeMapperInterface;
@@ -20,7 +18,7 @@ class Hydrator
2018
/**
2119
* @var \Swis\JsonApi\Client\Interfaces\TypeMapperInterface
2220
*/
23-
private $typeMapper;
21+
protected $typeMapper;
2422

2523
/**
2624
* @param \Swis\JsonApi\Client\Interfaces\TypeMapperInterface $typeMapper
@@ -30,62 +28,37 @@ public function __construct(TypeMapperInterface $typeMapper)
3028
$this->typeMapper = $typeMapper;
3129
}
3230

33-
/**
34-
* @param \Art4\JsonApiClient\ResourceCollectionInterface $jsonApiCollection
35-
*
36-
* @return \Swis\JsonApi\Client\Collection
37-
*/
38-
public function hydrateCollection(JsonApiCollection $jsonApiCollection)
39-
{
40-
$collection = new Collection();
41-
foreach ($jsonApiCollection->asArray() as $item) {
42-
$collection->push($this->hydrateItem($item));
43-
}
44-
45-
return $collection;
46-
}
47-
4831
/**
4932
* @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem
5033
*
5134
* @return \Swis\JsonApi\Client\Interfaces\ItemInterface
5235
*/
53-
public function hydrateItem(JsonApItem $jsonApiItem)
36+
public function hydrateItem(ResourceItemInterface $jsonApiItem): ItemInterface
5437
{
55-
$item = $this->getItemClass($jsonApiItem);
38+
$item = $this->getItemClass($jsonApiItem->get('type'));
5639

57-
$item->setType($jsonApiItem->get('type'))
58-
->setId($jsonApiItem->get('id'));
40+
$item->setId($jsonApiItem->get('id'));
5941

60-
$this->hydrateAttributes($jsonApiItem, $item);
42+
if ($jsonApiItem->has('attributes')) {
43+
$item->fill($jsonApiItem->get('attributes')->asArray(true));
44+
}
6145

6246
return $item;
6347
}
6448

6549
/**
66-
* @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem
50+
* @param \Art4\JsonApiClient\ResourceCollectionInterface $jsonApiCollection
6751
*
68-
* @return \Swis\JsonApi\Client\Interfaces\ItemInterface
52+
* @return \Swis\JsonApi\Client\Collection
6953
*/
70-
protected function getItemClass(JsonApItem $jsonApiItem): ItemInterface
54+
public function hydrateCollection(ResourceCollectionInterface $jsonApiCollection): Collection
7155
{
72-
$type = $jsonApiItem->get('type');
73-
if ($this->typeMapper->hasMapping($type)) {
74-
return $this->typeMapper->getMapping($type);
56+
$collection = new Collection();
57+
foreach ($jsonApiCollection->asArray() as $item) {
58+
$collection->push($this->hydrateItem($item));
7559
}
7660

77-
return new JenssegersItem();
78-
}
79-
80-
/**
81-
* @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem
82-
* @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item
83-
*/
84-
protected function hydrateAttributes(JsonApItem $jsonApiItem, ItemInterface $item)
85-
{
86-
if ($jsonApiItem->has('attributes')) {
87-
$item->fill($jsonApiItem->get('attributes')->asArray(true));
88-
}
61+
return $collection;
8962
}
9063

9164
/**
@@ -94,35 +67,39 @@ protected function hydrateAttributes(JsonApItem $jsonApiItem, ItemInterface $ite
9467
*/
9568
public function hydrateRelationships(Collection $jsonApiItems, Collection $items)
9669
{
70+
$keyedItems = $items->keyBy(
71+
function (ItemInterface $item) {
72+
return $this->getItemKey($item);
73+
}
74+
);
75+
9776
$jsonApiItems->each(
98-
function (JsonApItem $jsonApiItem) use ($items) {
77+
function (ResourceItemInterface $jsonApiItem) use ($keyedItems) {
9978
if (!$jsonApiItem->has('relationships')) {
10079
return;
10180
}
10281

103-
$item = $this->getIncludedItem($items, $jsonApiItem);
82+
$item = $this->getItem($keyedItems, $jsonApiItem);
10483

10584
if ($item instanceof NullItem) {
10685
return;
10786
}
10887

109-
$relationships = $this->getJsonApiDocumentRelationships($jsonApiItem);
110-
111-
foreach ($relationships as $name => $relationship) {
112-
/** @var \Art4\JsonApiClient\ResourceItemInterface $data */
88+
foreach ($jsonApiItem->get('relationships')->asArray() as $name => $relationship) {
89+
/** @var \Art4\JsonApiClient\ElementInterface $data */
11390
$data = $relationship->get('data');
11491
$method = camel_case($name);
11592

11693
if ($data instanceof ResourceIdentifierInterface) {
117-
$includedItem = $this->getIncludedItem($items, $data);
94+
$includedItem = $this->getItem($keyedItems, $data);
11895

11996
if ($includedItem instanceof NullItem) {
12097
continue;
12198
}
12299

123100
$item->setRelation($method, $includedItem);
124-
} elseif ($data instanceof ResourceCollectionInterface || $data instanceof ResourceIdentifierCollectionInterface) {
125-
$collection = $this->getIncludedItems($items, $data);
101+
} elseif ($data instanceof ResourceIdentifierCollectionInterface) {
102+
$collection = $this->getCollection($keyedItems, $data);
126103

127104
$item->setRelation($method, $collection);
128105
}
@@ -132,72 +109,75 @@ function (JsonApItem $jsonApiItem) use ($items) {
132109
}
133110

134111
/**
135-
* @param \Art4\JsonApiClient\ResourceItemInterface $jsonApiItem
112+
* @param string $type
136113
*
137-
* @return \Art4\JsonApiClient\Relationship[]
114+
* @return \Swis\JsonApi\Client\Interfaces\ItemInterface
138115
*/
139-
protected function getJsonApiDocumentRelationships(JsonApItem $jsonApiItem): array
116+
protected function getItemClass(string $type): ItemInterface
140117
{
141-
return $jsonApiItem->get('relationships')->asArray(false);
118+
if ($this->typeMapper->hasMapping($type)) {
119+
return $this->typeMapper->getMapping($type);
120+
}
121+
122+
return (new JenssegersItem())->setType($type);
142123
}
143124

144125
/**
145-
* @param \Swis\JsonApi\Client\Collection $included
146-
* @param \Art4\JsonApiClient\AccessInterface $accessor
126+
* @param \Swis\JsonApi\Client\Collection $included
127+
* @param \Art4\JsonApiClient\ResourceIdentifierInterface|\Art4\JsonApiClient\ResourceItemInterface $identifier
147128
*
148129
* @return \Swis\JsonApi\Client\Interfaces\ItemInterface
149130
*/
150-
protected function getIncludedItem(Collection $included, AccessInterface $accessor): ItemInterface
131+
protected function getItem(Collection $included, $identifier): ItemInterface
151132
{
152-
return $included->first(
153-
function (ItemInterface $item) use ($accessor) {
154-
return $this->accessorBelongsToItem($accessor, $item);
155-
},
156-
new NullItem()
133+
return $included->get(
134+
$this->getElementKey($identifier),
135+
function () {
136+
return new NullItem();
137+
}
157138
);
158139
}
159140

160141
/**
161-
* @param \Art4\JsonApiClient\AccessInterface $accessor
162-
* @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item
142+
* @param \Swis\JsonApi\Client\Collection $included
143+
* @param \Art4\JsonApiClient\ResourceIdentifierCollectionInterface|\Art4\JsonApiClient\ResourceCollectionInterface $identifierCollection
163144
*
164-
* @return bool
145+
* @return \Swis\JsonApi\Client\Collection
165146
*/
166-
protected function accessorBelongsToItem(AccessInterface $accessor, ItemInterface $item): bool
147+
protected function getCollection(Collection $included, $identifierCollection): Collection
167148
{
168-
return $item->getType() === $accessor->get('type')
169-
&& (string)$item->getId() === $accessor->get('id');
149+
$items = new Collection();
150+
151+
foreach ($identifierCollection->asArray() as $identifier) {
152+
$item = $this->getItem($included, $identifier);
153+
154+
if ($item instanceof NullItem) {
155+
continue;
156+
}
157+
158+
$items->push($item);
159+
}
160+
161+
return $items;
170162
}
171163

172164
/**
173-
* @param \Swis\JsonApi\Client\Collection $included
174-
* @param \Art4\JsonApiClient\ResourceIdentifierCollection $collection
165+
* @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item
175166
*
176-
* @return \Swis\JsonApi\Client\Collection
167+
* @return string
177168
*/
178-
protected function getIncludedItems(Collection $included, IdentifierCollection $collection): Collection
169+
protected function getItemKey(ItemInterface $item): string
179170
{
180-
return $included->filter(
181-
function (ItemInterface $item) use ($collection) {
182-
return $this->itemExistsInRelatedIdentifiers($collection->asArray(false), $item);
183-
}
184-
)->values();
171+
return sprintf('%s:%s', $item->getType(), $item->getId());
185172
}
186173

187174
/**
188-
* @param \Art4\JsonApiClient\ResourceIdentifier[] $relatedIdentifiers
189-
* @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item
175+
* @param \Art4\JsonApiClient\ElementInterface $accessor
190176
*
191-
* @return bool
177+
* @return string
192178
*/
193-
protected function itemExistsInRelatedIdentifiers(array $relatedIdentifiers, ItemInterface $item): bool
179+
protected function getElementKey(ElementInterface $accessor): string
194180
{
195-
foreach ($relatedIdentifiers as $relatedIdentifier) {
196-
if ($this->accessorBelongsToItem($relatedIdentifier, $item)) {
197-
return true;
198-
}
199-
}
200-
201-
return false;
181+
return sprintf('%s:%s', $accessor->get('type'), $accessor->get('id'));
202182
}
203183
}

tests/JsonApi/HydratorTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ protected function getTypeMapperMock()
5454
$typeMapper->method('hasMapping')->willReturn(true);
5555
$typeMapper->method('getMapping')->will(
5656
$this->returnCallback(
57-
function () {
58-
return new JenssegersItem();
57+
function (string $type) {
58+
return (new JenssegersItem())->setType($type);
5959
}
6060
)
6161
);

0 commit comments

Comments
 (0)