Skip to content

Commit 3b639b0

Browse files
committed
Merge 3.2
2 parents e895b69 + db50a46 commit 3b639b0

File tree

21 files changed

+153
-22
lines changed

21 files changed

+153
-22
lines changed

.github/workflows/guides.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ jobs:
4242
restore-keys: ${{ runner.os }}-composer-
4343
- name: Install project dependencies
4444
working-directory: docs
45-
run: composer install --no-interaction --no-progress --ansi && composer require webonyx/graphql-php
45+
run: |
46+
composer update --no-interaction --no-progress --ansi
47+
cp -r ../src ./vendor/api-platform/core/
4648
- name: Test guides
4749
working-directory: docs
4850
env:

docs/composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"zenstruck/foundry": "^1.31",
3535
"symfony/http-client": "^7.0",
3636
"symfony/browser-kit": "^7.0",
37-
"justinrainbow/json-schema": "^5.2"
37+
"justinrainbow/json-schema": "^5.2",
38+
"webonyx/graphql-php": "^15.11"
3839
},
3940
"config": {
4041
"allow-plugins": {
@@ -43,6 +44,5 @@
4344
},
4445
"require-dev": {
4546
"phpunit/phpunit": "^10"
46-
},
47-
"minimum-stability": "dev"
47+
}
4848
}

docs/config/packages/framework.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ api_platform:
77
json: ['application/json']
88
docs_formats:
99
jsonopenapi: ['application/vnd.openapi+json']
10-
event_listeners_backward_compatibility_layer: false
1110
keep_legacy_inflector: false
1211
defaults:
1312
extra_properties:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Feature: Test entity class option on collections
2+
In order to retrieve a collections of resources mapped to a DTO automatically
3+
As a client software developer
4+
5+
@createSchema
6+
@!mongodb
7+
Scenario: Get collection
8+
Given there are issue6039 users
9+
And I add "Accept" header equal to "application/ld+json"
10+
When I send a "GET" request to "/issue6039_user_apis"
11+
Then the response status code should be 200
12+
And the JSON node "hydra:member[0].bar" should not exist

src/Doctrine/Odm/Filter/AbstractFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b
8686
protected function denormalizePropertyName(string|int $property): string
8787
{
8888
if (!$this->nameConverter instanceof NameConverterInterface) {
89-
return $property;
89+
return (string) $property;
9090
}
9191

9292
return implode('.', array_map($this->nameConverter->denormalize(...), explode('.', (string) $property)));

src/Doctrine/Orm/Filter/AbstractFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b
8080
protected function denormalizePropertyName(string|int $property): string
8181
{
8282
if (!$this->nameConverter instanceof NameConverterInterface) {
83-
return $property;
83+
return (string) $property;
8484
}
8585

8686
return implode('.', array_map($this->nameConverter->denormalize(...), explode('.', (string) $property)));

src/Hydra/Serializer/DocumentationNormalizer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use ApiPlatform\Metadata\ApiProperty;
2121
use ApiPlatform\Metadata\ApiResource;
2222
use ApiPlatform\Metadata\CollectionOperationInterface;
23+
use ApiPlatform\Metadata\ErrorResource;
2324
use ApiPlatform\Metadata\HttpOperation;
2425
use ApiPlatform\Metadata\Operation;
2526
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
@@ -29,6 +30,7 @@
2930
use ApiPlatform\Metadata\ResourceClassResolverInterface;
3031
use ApiPlatform\Metadata\UrlGeneratorInterface;
3132
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
33+
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
3234
use Symfony\Component\PropertyInfo\Type;
3335
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
3436
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
@@ -60,6 +62,10 @@ public function normalize(mixed $object, ?string $format = null, array $context
6062
$resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass);
6163

6264
$resourceMetadata = $resourceMetadataCollection[0];
65+
if ($resourceMetadata instanceof ErrorResource && ValidationException::class === $resourceMetadata->getClass()) {
66+
continue;
67+
}
68+
6369
$shortName = $resourceMetadata->getShortName();
6470
$prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName";
6571
$this->populateEntrypointProperties($resourceMetadata, $shortName, $prefixedShortName, $entrypointProperties, $resourceMetadataCollection);

src/Metadata/Extractor/XmlResourceExtractor.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313

1414
namespace ApiPlatform\Metadata\Extractor;
1515

16+
use ApiPlatform\Elasticsearch\State\Options;
1617
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
1718
use ApiPlatform\Metadata\GetCollection;
1819
use ApiPlatform\Metadata\HeaderParameter;
1920
use ApiPlatform\Metadata\Post;
2021
use ApiPlatform\Metadata\QueryParameter;
21-
use ApiPlatform\Metadata\Tests\Fixtures\StateOptions;
2222
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
2323
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
2424
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
@@ -455,10 +455,12 @@ private function buildStateOptions(\SimpleXMLElement $resource): ?OptionsInterfa
455455
}
456456
$elasticsearchOptions = $stateOptions->elasticsearchOptions ?? null;
457457
if ($elasticsearchOptions) {
458-
return new StateOptions(
459-
isset($elasticsearchOptions['index']) ? (string) $elasticsearchOptions['index'] : null,
460-
isset($elasticsearchOptions['type']) ? (string) $elasticsearchOptions['type'] : null,
461-
);
458+
if (class_exists(Options::class)) {
459+
return new Options(
460+
isset($elasticsearchOptions['index']) ? (string) $elasticsearchOptions['index'] : null,
461+
isset($elasticsearchOptions['type']) ? (string) $elasticsearchOptions['type'] : null,
462+
);
463+
}
462464
}
463465

464466
return null;

src/Metadata/Extractor/YamlResourceExtractor.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313

1414
namespace ApiPlatform\Metadata\Extractor;
1515

16+
use ApiPlatform\Elasticsearch\State\Options;
1617
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
1718
use ApiPlatform\Metadata\GetCollection;
1819
use ApiPlatform\Metadata\HeaderParameter;
1920
use ApiPlatform\Metadata\Post;
2021
use ApiPlatform\Metadata\QueryParameter;
21-
use ApiPlatform\Metadata\Tests\Fixtures\StateOptions;
2222
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
2323
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
2424
use ApiPlatform\OpenApi\Model\Parameter;
@@ -414,7 +414,9 @@ private function buildStateOptions(array $resource): ?OptionsInterface
414414
$configuration = reset($stateOptions);
415415
switch (key($stateOptions)) {
416416
case 'elasticsearchOptions':
417-
return new StateOptions($configuration['index'] ?? null, $configuration['type'] ?? null);
417+
if (class_exists(Options::class)) {
418+
return new Options($configuration['index'] ?? null, $configuration['type'] ?? null);
419+
}
418420
}
419421

420422
return null;

src/Metadata/Resource/Factory/OperationDefaultsTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ private function getOperationWithDefaults(ApiResource $resource, Operation $oper
202202
throw new RuntimeException(sprintf('Operation should be an instance of "%s"', HttpOperation::class));
203203
}
204204

205-
if ($operation->getRouteName()) {
205+
if (!$operation->getName() && $operation->getRouteName()) {
206206
/** @var HttpOperation $operation */
207207
$operation = $operation->withName($operation->getRouteName());
208208
}

src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public function create(string $resourceClass): ResourceMetadataCollection
7575
$operation = $operation->withName($routeName);
7676
}
7777

78-
$operations->add($routeName, $operation);
78+
$operations->add($operation->getName(), $operation);
7979
continue;
8080
}
8181

src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,10 @@
3737
use ApiPlatform\Metadata\Tests\Extractor\Adapter\XmlResourceAdapter;
3838
use ApiPlatform\Metadata\Tests\Extractor\Adapter\YamlResourceAdapter;
3939
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Comment;
40-
use ApiPlatform\Metadata\Tests\Fixtures\StateOptions;
4140
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
4241
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
4342
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
4443
use ApiPlatform\OpenApi\Model\RequestBody;
45-
use ApiPlatform\State\OptionsInterface;
4644
use PHPUnit\Framework\AssertionFailedError;
4745
use PHPUnit\Framework\TestCase;
4846
use Symfony\Component\WebLink\Link;
@@ -723,7 +721,7 @@ private function withGraphQlOperations(array $values, ?array $fixtures): array
723721
return $operations;
724722
}
725723

726-
private function withStateOptions(array $values): ?OptionsInterface
724+
private function withStateOptions(array $values)
727725
{
728726
if (!$values) {
729727
return null;
@@ -736,7 +734,7 @@ private function withStateOptions(array $values): ?OptionsInterface
736734
$configuration = reset($values);
737735
switch (key($values)) {
738736
case 'elasticsearchOptions':
739-
return new StateOptions($configuration['index'] ?? null, $configuration['type'] ?? null);
737+
return null;
740738
}
741739

742740
throw new \LogicException(sprintf('Unsupported "%s" state options.', key($values)));

src/Serializer/AbstractCollectionNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ public function normalize(mixed $object, ?string $format = null, array $context
103103
$paginationData = $this->getPaginationData($object, $collectionContext);
104104

105105
$childContext = $this->createOperationContext($collectionContext, $resourceClass);
106+
if (isset($collectionContext['force_resource_class'])) {
107+
$childContext['force_resource_class'] = $collectionContext['force_resource_class'];
108+
}
109+
106110
$itemsData = $this->getItemsData($object, $format, $childContext);
107111

108112
return array_merge_recursive($data, $paginationData, $itemsData);

src/State/ApiResource/Error.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
operations: [
3333
new Operation(
3434
name: '_api_errors_problem',
35+
routeName: 'api_errors',
3536
outputFormats: ['json' => ['application/problem+json']],
3637
normalizationContext: [
3738
'groups' => ['jsonproblem'],
@@ -41,6 +42,7 @@
4142
),
4243
new Operation(
4344
name: '_api_errors_hydra',
45+
routeName: 'api_errors',
4446
outputFormats: ['jsonld' => ['application/problem+json']],
4547
normalizationContext: [
4648
'groups' => ['jsonld'],
@@ -51,13 +53,18 @@
5153
),
5254
new Operation(
5355
name: '_api_errors_jsonapi',
56+
routeName: 'api_errors',
5457
outputFormats: ['jsonapi' => ['application/vnd.api+json']],
5558
normalizationContext: [
5659
'groups' => ['jsonapi'],
5760
'skip_null_values' => true,
5861
'rfc_7807_compliant_errors' => true,
5962
],
6063
),
64+
new Operation(
65+
name: '_api_errors',
66+
routeName: 'api_errors'
67+
),
6168
],
6269
provider: 'api_platform.state.error_provider',
6370
graphQlOperations: []
@@ -119,12 +126,14 @@ public static function createFromException(\Exception|\Throwable $exception, int
119126
}
120127

121128
#[Ignore]
129+
#[ApiProperty(readable: false)]
122130
public function getHeaders(): array
123131
{
124132
return $this->headers;
125133
}
126134

127135
#[Ignore]
136+
#[ApiProperty(readable: false)]
128137
public function getStatusCode(): int
129138
{
130139
return $this->status;

src/Symfony/Bundle/Resources/config/routing/api.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,15 @@
1414
<requirement key="index">index</requirement>
1515
</route>
1616

17+
<route id="api_errors" path="/errors/{status}">
18+
<default key="_controller">api_platform.action.not_exposed</default>
19+
<default key="status">500</default>
20+
21+
<requirement key="status">\d+</requirement>
22+
</route>
23+
24+
<route id="api_validation_errors" path="/validation_errors/{id}">
25+
<default key="_controller">api_platform.action.not_exposed</default>
26+
</route>
27+
1728
</routes>

src/Symfony/Routing/IriConverter.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ private function generateSymfonyRoute(object|string $resource, int $referenceTyp
186186
}
187187

188188
try {
189-
return $this->router->generate($operation->getName(), $identifiers, $operation->getUrlGenerationStrategy() ?? $referenceType);
189+
$routeName = $operation instanceof HttpOperation ? ($operation->getRouteName() ?? $operation->getName()) : $operation->getName();
190+
191+
return $this->router->generate($routeName, $identifiers, $operation->getUrlGenerationStrategy() ?? $referenceType);
190192
} catch (RoutingExceptionInterface $e) {
191193
throw new InvalidArgumentException(sprintf('Unable to generate an IRI for the item of type "%s"', $operation->getClass()), $e->getCode(), $e);
192194
}

src/Symfony/Validator/Exception/ValidationException.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
operations: [
4141
new ErrorOperation(
4242
name: '_api_validation_errors_problem',
43+
routeName: 'api_validation_errors',
4344
outputFormats: ['json' => ['application/problem+json']],
4445
normalizationContext: ['groups' => ['json'],
4546
'skip_null_values' => true,
@@ -48,6 +49,7 @@
4849
),
4950
new ErrorOperation(
5051
name: '_api_validation_errors_hydra',
52+
routeName: 'api_validation_errors',
5153
outputFormats: ['jsonld' => ['application/problem+json']],
5254
links: [new Link(rel: ContextBuilderInterface::JSONLD_NS.'error', href: 'http://www.w3.org/ns/hydra/error')],
5355
normalizationContext: [
@@ -58,9 +60,14 @@
5860
),
5961
new ErrorOperation(
6062
name: '_api_validation_errors_jsonapi',
63+
routeName: 'api_validation_errors',
6164
outputFormats: ['jsonapi' => ['application/vnd.api+json']],
6265
normalizationContext: ['groups' => ['jsonapi'], 'skip_null_values' => true, 'rfc_7807_compliant_errors' => true]
6366
),
67+
new ErrorOperation(
68+
name: '_api_validation_errors',
69+
routeName: 'api_validation_errors'
70+
),
6471
],
6572
graphQlOperations: []
6673
)]

tests/Behat/DoctrineContext.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5722\Event;
159159
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5722\ItemLog;
160160
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5735\Group;
161+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6039\Issue6039EntityUser;
161162
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\LinkHandledDummy;
162163
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
163164
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsDummy;
@@ -2296,6 +2297,22 @@ public function thereIsADummyEntityWithAMappedSuperclass(): void
22962297
$this->manager->flush();
22972298
}
22982299

2300+
/**
2301+
* @Given there are issue6039 users
2302+
*/
2303+
public function thereAreIssue6039Users(): void
2304+
{
2305+
$entity = new Issue6039EntityUser();
2306+
$entity->name = 'test';
2307+
$entity->bar = 'test';
2308+
$this->manager->persist($entity);
2309+
$entity = new Issue6039EntityUser();
2310+
$entity->name = 'test2';
2311+
$entity->bar = 'test';
2312+
$this->manager->persist($entity);
2313+
$this->manager->flush();
2314+
}
2315+
22992316
private function isOrm(): bool
23002317
{
23012318
return null !== $this->schemaTool;

tests/Fixtures/TestBundle/ApiResource/Issue5648/DummyResource.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
use ApiPlatform\Metadata\GetCollection;
2222
use ApiPlatform\Metadata\Link;
2323
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
24+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
2425

2526
#[ApiResource(
2627
operations: [
27-
new GetCollection(uriTemplate: '/dummy_resource_with_custom_filter', itemUriTemplate: '/dummy_resource_with_custom_filter/{id}'),
28+
new GetCollection(uriTemplate: '/dummy_resource_with_custom_filter{._format}', itemUriTemplate: '/dummy_resource_with_custom_filter/{id}'),
2829
new Get(uriTemplate: '/dummy_resource_with_custom_filter/{id}', uriVariables: ['id' => new Link(fromClass: Dummy::class)]),
2930
],
3031
stateOptions: new Options(entityClass: Dummy::class)
@@ -37,5 +38,8 @@ class DummyResource
3738

3839
public string $name;
3940

41+
/**
42+
* @var RelatedDummy[]
43+
*/
4044
public array $relatedDummies;
4145
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6039;
15+
16+
use ApiPlatform\Doctrine\Orm\State\Options;
17+
use ApiPlatform\Metadata\GetCollection;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6039\Issue6039EntityUser;
19+
20+
#[GetCollection(shortName: 'Issue6039UserApi', stateOptions: new Options(entityClass: Issue6039EntityUser::class))]
21+
class UserApi
22+
{
23+
public string $id;
24+
public string $name;
25+
}

0 commit comments

Comments
 (0)