Skip to content

Commit 2758830

Browse files
authored
Merge pull request #6536 from pmattmann/feature/related-collection-link-normalizer-performance
RelatedCollectionLinkNormalizer performance improved
2 parents 14a06d9 + 70a7155 commit 2758830

File tree

1 file changed

+46
-15
lines changed

1 file changed

+46
-15
lines changed

api/src/Serializer/Normalizer/RelatedCollectionLinkNormalizer.php

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use ApiPlatform\Exception\ResourceClassNotFoundException;
99
use ApiPlatform\Metadata\GetCollection;
1010
use ApiPlatform\Metadata\IriConverterInterface;
11+
use ApiPlatform\Metadata\Operation;
1112
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
1213
use ApiPlatform\Metadata\UrlGeneratorInterface;
1314
use App\Entity\BaseEntity;
@@ -81,6 +82,11 @@ class RelatedCollectionLinkNormalizer implements NormalizerInterface, Serializer
8182
use PropertyHelperTrait;
8283
use ClassInfoTrait;
8384

85+
/**
86+
* @var (Operation|string)[]
87+
*/
88+
private array $exactSearchFilterExistsOperationCache = [];
89+
8490
public function __construct(
8591
private NormalizerInterface $decorated,
8692
private ServiceLocator $filterLocator,
@@ -111,12 +117,17 @@ public function normalize($data, $format = null, array $context = []): null|arra
111117
continue;
112118
}
113119

114-
try {
115-
$normalized_data['_links'][$rel] = ['href' => $this->getRelatedCollectionHref($data, $rel, $context)];
116-
} catch (UnsupportedRelationException $e) {
117-
// The relation is not supported, or there is no matching filter defined on the related entity
120+
// If relation is a public property, this property can be checked to be a non-null value
121+
$values = get_object_vars($data);
122+
if (array_key_exists($rel, $values) && null == $values[$rel]) {
123+
// target-value is NULL
124+
continue;
125+
}
126+
127+
if (!$this->getRelatedCollectionHref($data, $rel, $context, $result)) {
118128
continue;
119129
}
130+
$normalized_data['_links'][$rel] = ['href' => $result];
120131
}
121132

122133
return $normalized_data;
@@ -136,7 +147,7 @@ public function setSerializer(SerializerInterface $serializer): void {
136147
}
137148
}
138149

139-
public function getRelatedCollectionHref($object, $rel, array $context = []): string {
150+
protected function getRelatedCollectionHref($object, $rel, array $context, &$href): bool {
140151
$resourceClass = $this->getObjectClass($object);
141152

142153
if ($this->nameConverter instanceof NameConverterInterface) {
@@ -149,7 +160,9 @@ public function getRelatedCollectionHref($object, $rel, array $context = []): st
149160
$params = $this->extractUriParams($object, $annotation->getParams());
150161
[$uriTemplate] = $this->uriTemplateFactory->createFromResourceClass($annotation->getRelatedEntity());
151162

152-
return $this->uriTemplate->expand($uriTemplate, $params);
163+
$href = $this->uriTemplate->expand($uriTemplate, $params);
164+
165+
return true;
153166
}
154167

155168
try {
@@ -161,7 +174,8 @@ public function getRelatedCollectionHref($object, $rel, array $context = []): st
161174

162175
$relationMetadata = $classMetadata->getAssociationMapping($rel);
163176
} catch (MappingException) {
164-
throw new UnsupportedRelationException($resourceClass.'#'.$rel.' is not a Doctrine association. Embedding non-Doctrine collections is currently not implemented.');
177+
// $resourceClass # $rel is not a Doctrine association. Embedding non-Doctrine collections is currently not implemented
178+
return false;
165179
}
166180

167181
$relatedResourceClass = $relationMetadata['targetEntity'];
@@ -170,21 +184,38 @@ public function getRelatedCollectionHref($object, $rel, array $context = []): st
170184
$relatedFilterName ??= $relationMetadata['inversedBy'];
171185

172186
if (empty($relatedResourceClass) || empty($relatedFilterName)) {
173-
throw new UnsupportedRelationException('The '.$resourceClass.'#'.$rel.' relation does not have both a targetEntity and a mappedBy or inversedBy property');
187+
// The $resourceClass # $rel relation does not have both a targetEntity and a mappedBy or inversedBy property
188+
return false;
174189
}
175190

176-
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($relatedResourceClass);
177-
$operation = OperationHelper::findOneByType($resourceMetadataCollection, GetCollection::class);
191+
$lookupKey = $relatedResourceClass.':'.$relatedFilterName;
192+
if (isset($this->exactSearchFilterExistsOperationCache[$lookupKey])) {
193+
$result = $this->exactSearchFilterExistsOperationCache[$lookupKey];
194+
} else {
195+
$result = 'No Operation';
196+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($relatedResourceClass);
197+
$operation = OperationHelper::findOneByType($resourceMetadataCollection, GetCollection::class);
178198

179-
if (!$operation) {
180-
throw new UnsupportedRelationException('The resource '.$relatedResourceClass.' does not implement GetCollection() operation.');
199+
if (!$operation) {
200+
// The resource $relatedResourceClass does not implement GetCollection() operation
201+
} else {
202+
$filterExists = $this->exactSearchFilterExists($relatedResourceClass, $relatedFilterName);
203+
if (!$filterExists) {
204+
// The resource $relatedResourceClass does not have a search filter for the relation $relatedFilterName
205+
} else {
206+
$result = $operation;
207+
}
208+
}
209+
$this->exactSearchFilterExistsOperationCache[$lookupKey] = $result;
181210
}
182211

183-
if (!$this->exactSearchFilterExists($relatedResourceClass, $relatedFilterName)) {
184-
throw new UnsupportedRelationException('The resource '.$relatedResourceClass.' does not have a search filter for the relation '.$relatedFilterName.'.');
212+
if ($result instanceof Operation) {
213+
$href = $this->router->generate($result->getName(), [$relatedFilterName => urlencode($this->iriConverter->getIriFromResource($object))], UrlGeneratorInterface::ABS_PATH);
214+
215+
return true;
185216
}
186217

187-
return $this->router->generate($operation->getName(), [$relatedFilterName => urlencode($this->iriConverter->getIriFromResource($object))], UrlGeneratorInterface::ABS_PATH);
218+
return false;
188219
}
189220

190221
protected function getRelatedCollectionLinkAnnotation(string $className, string $propertyName): ?RelatedCollectionLink {

0 commit comments

Comments
 (0)