Skip to content

Commit 7ca6c6d

Browse files
committed
Merge 3.1
2 parents 92a81f0 + 3e3595d commit 7ca6c6d

File tree

19 files changed

+192
-377
lines changed

19 files changed

+192
-377
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: 80 additions & 20 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;
@@ -512,12 +513,7 @@ protected function denormalizeCollection(string $attribute, ApiProperty $propert
512513

513514
$collectionKeyType = $type->getCollectionKeyTypes()[0] ?? null;
514515
$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-
516+
$childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
521517
$values = [];
522518
foreach ($value as $index => $obj) {
523519
if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) {
@@ -643,13 +639,8 @@ protected function getAttributeValue(object $object, string $attribute, string $
643639
return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
644640
}
645641

646-
if (
647-
($className = $type->getClassName())
648-
&& $this->resourceClassResolver->isResourceClass($className)
649-
) {
650-
if (!\is_object($attributeValue) && null !== $attributeValue) {
651-
throw new UnexpectedValueException('Unexpected non-object value for to-one relation.');
652-
}
642+
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
643+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
653644

654645
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
655646
$childContext = $this->createChildContext($context, $attribute, $format);
@@ -662,9 +653,8 @@ protected function getAttributeValue(object $object, string $attribute, string $
662653
return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
663654
}
664655

665-
if (!$this->serializer instanceof NormalizerInterface) {
666-
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
667-
}
656+
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
657+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
668658

669659
unset(
670660
$context['resource_class'],
@@ -694,6 +684,20 @@ protected function getAttributeValue(object $object, string $attribute, string $
694684
unset($context['resource_class']);
695685
unset($context['force_resource_class']);
696686

687+
// Anonymous resources
688+
if ($type && $type->getClassName()) {
689+
$childContext = $this->createChildContext($context, $attribute, $format);
690+
$childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
691+
692+
return $this->serializer->normalize($attributeValue, $format, $childContext);
693+
}
694+
695+
if ($type && 'array' === $type->getBuiltinType()) {
696+
$childContext = $this->createChildContext($context, $attribute, $format);
697+
698+
return $this->serializer->normalize($attributeValue, $format, $childContext);
699+
}
700+
697701
return $this->serializer->normalize($attributeValue, $format, $context);
698702
}
699703

@@ -780,10 +784,41 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value
780784
$types = $propertyMetadata->getBuiltinTypes() ?? [];
781785
$isMultipleTypes = \count($types) > 1;
782786

783-
foreach ($types as $type) {
784-
if (null === $value && ($type->isNullable() || ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false))) {
785-
return $value;
786-
}
787+
if (null === $type) {
788+
// No type provided, blindly return the value
789+
return $value;
790+
}
791+
792+
if (null === $value && $type->isNullable() || ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false)) {
793+
return $value;
794+
}
795+
796+
$collectionValueType = $type->getCollectionValueTypes()[0] ?? null;
797+
798+
/* From @see AbstractObjectNormalizer::validateAndDenormalize() */
799+
// Fix a collection that contains the only one element
800+
// This is special to xml format only
801+
if ('xml' === $format && null !== $collectionValueType && (!\is_array($value) || !\is_int(key($value)))) {
802+
$value = [$value];
803+
}
804+
805+
if (
806+
$type->isCollection()
807+
&& null !== $collectionValueType
808+
&& null !== ($className = $collectionValueType->getClassName())
809+
&& $this->resourceClassResolver->isResourceClass($className)
810+
) {
811+
$resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
812+
813+
return $this->denormalizeCollection($attribute, $propertyMetadata, $type, $resourceClass, $value, $format, $context);
814+
}
815+
816+
if (
817+
null !== ($className = $type->getClassName())
818+
&& $this->resourceClassResolver->isResourceClass($className)
819+
) {
820+
$resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
821+
$childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
787822

788823
$collectionValueType = $type->getCollectionValueTypes()[0] ?? null;
789824

@@ -932,4 +967,29 @@ private function setValue(object $object, string $attributeName, mixed $value):
932967
// Properties not found are ignored
933968
}
934969
}
970+
971+
private function createOperationContext(array $context, string $resourceClass = null): array
972+
{
973+
if (isset($context['operation']) && !isset($context['root_operation'])) {
974+
$context['root_operation'] = $context['operation'];
975+
$context['root_operation_name'] = $context['operation_name'];
976+
}
977+
978+
unset($context['iri'], $context['uri_variables']);
979+
if (!$resourceClass) {
980+
return $context;
981+
}
982+
983+
unset($context['operation'], $context['operation_name']);
984+
$context['resource_class'] = $resourceClass;
985+
if ($this->resourceMetadataCollectionFactory) {
986+
try {
987+
$context['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
988+
$context['operation_name'] = $context['operation']->getName();
989+
} catch (OperationNotFoundException) {
990+
}
991+
}
992+
993+
return $context;
994+
}
935995
}

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;

tests/Fixtures/TestBundle/ApiResource/Issue5605/MainResource.php

Lines changed: 0 additions & 41 deletions
This file was deleted.

tests/Fixtures/TestBundle/ApiResource/Issue5605/SubResource.php

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)