Skip to content

Commit 320b268

Browse files
authored
Merge pull request api-platform#5748 from soyuka/main
Merge 3.1
2 parents 92a81f0 + 5f3721d commit 320b268

File tree

19 files changed

+146
-382
lines changed

19 files changed

+146
-382
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v3.1.14
4+
5+
### Bug fixes
6+
7+
* [146f55330](https://github.com/api-platform/core/commit/146f55330e3df8301ac84345b69a25cdfb908b27) fix(metadata): operation NotExposed status to 404 (#5717)
8+
* [4dcfc16c3](https://github.com/api-platform/core/commit/4dcfc16c38ab4c371a37a7d92d2f2f205de31f89) fix(symfony): perf regression with Symfony 6.3 (#5721)
9+
* [4f9626f42](https://github.com/api-platform/core/commit/4f9626f42b75a5fd1f9d681c80ad6c4ee56318fe) fix(serializer): use data if no uri_variables provided (#5743)
10+
* [7bb92a52f](https://github.com/api-platform/core/commit/7bb92a52f5c6e02705547408281eba93f73b588e) fix(doctrine): use stateOptions only within doctrine context (#5726)
11+
* [83dbfbff1](https://github.com/api-platform/core/commit/83dbfbff1717dabba7ce9e814d0bdb556b49fcb8) fix(metadata): generated NotExposed operation should inherit resource options (#5722)
12+
* [ccad63683](https://github.com/api-platform/core/commit/ccad6368303d341f37eff0317cc8e433504c460f) Revert "fix: search on nested sub-entity that doesn't use "id" as its ORM identifier (#5623)" (#5744)
13+
* [e2745855b](https://github.com/api-platform/core/commit/e2745855be4986d361626d1b853e45cde229d3d8) fix(openapi): model Example, Header and Reference (#5716)
14+
* [ebf03104f](https://github.com/api-platform/core/commit/ebf03104fcbffc5af74d78c3e9b14d02d7527214) fix(jsonld): skolem uri template may have a _format (#5729)
15+
316
## v3.1.13
417

518
### Bug fixes

features/doctrine/search_filter.feature

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,15 +1025,6 @@ Feature: Search filter on collections
10251025
And the response should be in JSON
10261026
And the JSON node "hydra:totalItems" should be equal to 1
10271027

1028-
@!mongodb
1029-
@createSchema
1030-
Scenario: Search on nested sub-entity that doesn't use "id" as its ORM identifier
1031-
Given there is a dummy entity with a sub entity with id "stringId" and name "someName"
1032-
When I send a "GET" request to "/dummy_with_subresource?subEntity=/dummy_subresource/stringId"
1033-
Then the response status code should be 200
1034-
And the response should be in JSON
1035-
And the JSON node "hydra:totalItems" should be equal to 1
1036-
10371028
@!mongodb
10381029
@createSchema
10391030
Scenario: Custom search filters can use Doctrine Expressions as join conditions

features/main/patch.feature

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,25 @@ Feature: Sending PATCH requets
5858
}
5959
}
6060
"""
61+
62+
Scenario: Patch a relation with uri variables that are not `id`
63+
When I add "Content-Type" header equal to "application/merge-patch+json"
64+
And I send a "PATCH" request to "/betas/1" with body:
65+
"""
66+
{
67+
"alpha": "/alphas/2"
68+
}
69+
"""
70+
Then the response should be in JSON
71+
And the response status code should be 200
72+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
73+
And the JSON should be equal to:
74+
"""
75+
{
76+
"@context": "/contexts/Beta",
77+
"@id": "/betas/1",
78+
"@type": "Beta",
79+
"betaId": 1,
80+
"alpha": "/alphas/2"
81+
}
82+
"""

src/Doctrine/Common/Filter/SearchFilterTrait.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,7 @@ protected function getIdFromValue(string $value): mixed
124124
$iriConverter = $this->getIriConverter();
125125
$item = $iriConverter->getResourceFromIri($value, ['fetch_data' => false]);
126126

127-
if (null === $this->identifiersExtractor) {
128-
return $this->getPropertyAccessor()->getValue($item, 'id');
129-
}
130-
131-
$identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item);
132-
133-
return 1 === \count($identifiers) ? array_pop($identifiers) : $identifiers;
127+
return $this->getPropertyAccessor()->getValue($item, 'id');
134128
} catch (InvalidArgumentException) {
135129
// Do nothing, return the raw value
136130
}

src/Doctrine/Orm/Filter/SearchFilter.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
196196
if ($metadata->hasField($field)) {
197197
if ('id' === $field) {
198198
$values = array_map($this->getIdFromValue(...), $values);
199-
// todo: handle composite IDs
200199
}
201200

202201
if (!$this->hasValidValues($values, $this->getDoctrineFieldType($property, $resourceClass))) {
@@ -218,11 +217,9 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
218217
}
219218

220219
$values = array_map($this->getIdFromValue(...), $values);
221-
// todo: handle composite IDs
222220

223221
$associationResourceClass = $metadata->getAssociationTargetClass($field);
224-
$associationMetadata = $this->getClassMetadata($associationResourceClass);
225-
$associationFieldIdentifier = $associationMetadata->getIdentifierFieldNames()[0];
222+
$associationFieldIdentifier = $metadata->getIdentifierFieldNames()[0];
226223
$doctrineTypeField = $this->getDoctrineFieldType($associationFieldIdentifier, $associationResourceClass);
227224

228225
if (!$this->hasValidValues($values, $doctrineTypeField)) {

src/Serializer/AbstractItemNormalizer.php

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use ApiPlatform\Exception\ItemNotFoundException;
2121
use ApiPlatform\Metadata\ApiProperty;
2222
use ApiPlatform\Metadata\CollectionOperationInterface;
23+
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
2324
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2425
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2526
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
@@ -402,8 +403,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con
402403

403404
if (
404405
$this->isAllowedAttribute($classOrObject, $propertyName, null, $context)
405-
&& (
406-
isset($context['api_normalize']) && $propertyMetadata->isReadable()
406+
&& (isset($context['api_normalize']) && $propertyMetadata->isReadable()
407407
|| isset($context['api_denormalize']) && ($propertyMetadata->isWritable() || !\is_object($classOrObject) && $propertyMetadata->isInitializable())
408408
)
409409
) {
@@ -512,12 +512,7 @@ protected function denormalizeCollection(string $attribute, ApiProperty $propert
512512

513513
$collectionKeyType = $type->getCollectionKeyTypes()[0] ?? null;
514514
$collectionKeyBuiltinType = $collectionKeyType?->getBuiltinType();
515-
$childContext = $this->createChildContext(['resource_class' => $className] + $context, $attribute, $format);
516-
unset($childContext['uri_variables']);
517-
if ($this->resourceMetadataCollectionFactory) {
518-
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($className)->getOperation();
519-
}
520-
515+
$childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
521516
$values = [];
522517
foreach ($value as $index => $obj) {
523518
if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) {
@@ -637,8 +632,7 @@ protected function getAttributeValue(object $object, string $attribute, string $
637632
}
638633

639634
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
640-
$childContext = $this->createChildContext($context, $attribute, $format);
641-
unset($childContext['iri'], $childContext['uri_variables'], $childContext['resource_class'], $childContext['operation']);
635+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
642636

643637
return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
644638
}
@@ -652,12 +646,7 @@ protected function getAttributeValue(object $object, string $attribute, string $
652646
}
653647

654648
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
655-
$childContext = $this->createChildContext($context, $attribute, $format);
656-
$childContext['resource_class'] = $resourceClass;
657-
if ($this->resourceMetadataCollectionFactory) {
658-
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
659-
}
660-
unset($childContext['iri'], $childContext['uri_variables']);
649+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
661650

662651
return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
663652
}
@@ -671,17 +660,16 @@ protected function getAttributeValue(object $object, string $attribute, string $
671660
$context['force_resource_class'],
672661
);
673662

663+
// Anonymous resources
674664
if ($type->getClassName()) {
675-
$childContext = $this->createChildContext($context, $attribute, $format);
676-
unset($childContext['iri'], $childContext['uri_variables'], $childContext['resource_class'], $childContext['force_resource_class']);
665+
$childContext = $this->createChildContext($this->createOperationContext($context, null), $attribute, $format);
677666
$childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
678667

679668
return $this->serializer->normalize($attributeValue, $format, $childContext);
680669
}
681670

682671
if ('array' === $type->getBuiltinType()) {
683-
$childContext = $this->createChildContext($context, $attribute, $format);
684-
unset($childContext['iri'], $childContext['uri_variables']);
672+
$childContext = $this->createChildContext($this->createOperationContext($context, null), $attribute, $format);
685673

686674
return $this->serializer->normalize($attributeValue, $format, $childContext);
687675
}
@@ -811,11 +799,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
811799
&& $this->resourceClassResolver->isResourceClass($className)
812800
) {
813801
$resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
814-
$childContext = $this->createChildContext($context, $attribute, $format);
815-
$childContext['resource_class'] = $resourceClass;
816-
if ($this->resourceMetadataCollectionFactory) {
817-
$childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
818-
}
802+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
819803

820804
return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext);
821805
}
@@ -932,4 +916,29 @@ private function setValue(object $object, string $attributeName, mixed $value):
932916
// Properties not found are ignored
933917
}
934918
}
919+
920+
private function createOperationContext(array $context, string $resourceClass = null): array
921+
{
922+
if (isset($context['operation']) && !isset($context['root_operation'])) {
923+
$context['root_operation'] = $context['operation'];
924+
$context['root_operation_name'] = $context['operation_name'];
925+
}
926+
927+
unset($context['iri'], $context['uri_variables']);
928+
if (!$resourceClass) {
929+
return $context;
930+
}
931+
932+
unset($context['operation'], $context['operation_name']);
933+
$context['resource_class'] = $resourceClass;
934+
if ($this->resourceMetadataCollectionFactory) {
935+
try {
936+
$context['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
937+
$context['operation_name'] = $context['operation']->getName();
938+
} catch (OperationNotFoundException) {
939+
}
940+
}
941+
942+
return $context;
943+
}
935944
}

src/Serializer/ItemNormalizer.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,7 @@ private function updateObjectToPopulate(array $data, array &$context): void
8686

8787
private function getContextUriVariables(array $data, $operation, array $context): array
8888
{
89-
if (!isset($context['uri_variables'])) {
90-
return ['id' => $data['id']];
91-
}
92-
93-
$uriVariables = $context['uri_variables'];
89+
$uriVariables = $context['uri_variables'] ?? $data;
9490

9591
/** @var Link $uriVariable */
9692
foreach ($operation->getUriVariables() as $uriVariable) {

src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
<service id="api_platform.doctrine_mongodb.odm.search_filter" class="ApiPlatform\Doctrine\Odm\Filter\SearchFilter" public="false" abstract="true">
117117
<argument type="service" id="doctrine_mongodb" />
118118
<argument type="service" id="api_platform.iri_converter" />
119-
<argument type="service" id="api_platform.identifiers_extractor" on-invalid="ignore" />
119+
<argument type="service" id="api_platform.identifiers_extractor.cached" on-invalid="ignore" />
120120
<argument type="service" id="api_platform.property_accessor" />
121121
<argument type="service" id="logger" on-invalid="ignore" />
122122
<argument key="$nameConverter" type="service" id="api_platform.name_converter" on-invalid="ignore"/>

src/Symfony/Bundle/Resources/config/doctrine_orm.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
<argument type="service" id="api_platform.iri_converter" />
153153
<argument type="service" id="api_platform.property_accessor" />
154154
<argument type="service" id="logger" on-invalid="ignore" />
155-
<argument key="$identifiersExtractor" type="service" id="api_platform.identifiers_extractor" on-invalid="ignore" />
155+
<argument key="$identifiersExtractor" type="service" id="api_platform.identifiers_extractor.cached" on-invalid="ignore" />
156156
<argument key="$nameConverter" type="service" id="api_platform.name_converter" on-invalid="ignore" />
157157
</service>
158158
<service id="ApiPlatform\Doctrine\Orm\Filter\SearchFilter" alias="api_platform.doctrine.orm.search_filter" />

tests/Behat/DoctrineContext.php

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,8 @@
128128
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPassenger;
129129
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyProduct;
130130
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyProperty;
131-
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummySubEntity;
132131
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceNotApiResourceChild;
133132
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTravel;
134-
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyWithSubEntity;
135133
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\EmbeddableDummy;
136134
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\EmbeddedDummy;
137135
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\EntityClassWithDateTime;
@@ -2145,20 +2143,6 @@ public function thereIsAResourceUsingEntityClassAndDateTime(): void
21452143
$this->manager->flush();
21462144
}
21472145

2148-
/**
2149-
* @Given there is a dummy entity with a sub entity with id :strId and name :name
2150-
*/
2151-
public function thereIsADummyWithSubEntity(string $strId, string $name): void
2152-
{
2153-
$subEntity = new DummySubEntity($strId, $name);
2154-
$mainEntity = new DummyWithSubEntity();
2155-
$mainEntity->setSubEntity($subEntity);
2156-
$mainEntity->setName('main');
2157-
$this->manager->persist($subEntity);
2158-
$this->manager->persist($mainEntity);
2159-
$this->manager->flush();
2160-
}
2161-
21622146
private function isOrm(): bool
21632147
{
21642148
return null !== $this->schemaTool;

0 commit comments

Comments
 (0)