Skip to content

Commit 5ced1c6

Browse files
committed
Merge 3.0 onto main
2 parents d565f95 + b1351c6 commit 5ced1c6

File tree

79 files changed

+4074
-3714
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4074
-3714
lines changed

.commitlintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"defaultIgnores": true,
3-
"parserPreset": "conventional-changelog-conventionalcommits",
3+
"extends": "@commitlint/config-conventional",
44
"rules": {
55
"scope-enum": [
66
2,

.github/workflows/ci.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,24 @@ env:
1111

1212
jobs:
1313
commitlint:
14+
if: github.event_name == 'pull_request'
15+
env:
16+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1417
runs-on: ubuntu-latest
1518
steps:
1619
- uses: actions/checkout@v3
1720
with:
1821
fetch-depth: 0
19-
configFile: .commitlintrc
20-
- uses: wagoid/commitlint-github-action@v5
22+
- name: Run commitlint
23+
run: |
24+
commit=$(gh api \
25+
-H "Accept: application/vnd.github+json" \
26+
/repos/${{ github.repository }}/pulls/${{github.event.number}}/commits \
27+
| jq -r '.[0].commit.message')
28+
# we can't use npx see https://github.com/conventional-changelog/commitlint/issues/613
29+
echo '{}' > package.json
30+
npm install --no-fund --no-audit @commitlint/config-conventional @commitlint/cli
31+
echo $commit | ./node_modules/.bin/commitlint -g .commitlintrc
2132
php-cs-fixer:
2233
name: PHP-cs-fixer (PHP ${{ matrix.php }})
2334
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
2-
=======
2+
3+
## 3.0.1
4+
5+
* Symfony: don't use ArrayAdapter cache in production #5027
6+
* Symfony: remove `_api_exception_to_status` leftovers (#4992)
7+
* Serializer: support empty array as object (#4999)
8+
* Chore: compatibility with PHP 8.2 (#5024)
9+
* Symfony: resource class directories bc break (#4982)
10+
* Symfony: exception_status bad merge (#4981)
11+
* Graphql: remove unused service for ItemResolverFactory (#4976)
12+
* Chore: document missing breaking changes on the 3.0.0-beta.1
313

414
## 3.0.0
515

@@ -13,19 +23,6 @@
1323

1424
Various cleanup in services and removal of backward compatibility layer.
1525

16-
## 2.7.0
17-
18-
* chore: remove @experimental phpdoc (#4933)
19-
* Metadata: do not set id when identifier is `false` (#4880)
20-
* Metadata: automatic GET operation when none is declared (#4881)
21-
* Metadata: exception to status on operations (#4861)
22-
* Serializer: adds the JSON_INVALID_UTF8_IGNORE flag to JsonEncode (#4741)
23-
* Symfony: autoconfigure legacy Doctrine extensions (#4909)
24-
* Elasticsearch: skip metadata without ES nodes (#4913)
25-
* Symfony: deprecated the `$exceptionOnNoToken` parameter in `ResourceAccessChecker::__construct()` (#4900)
26-
27-
Various cs fixes and PHPDoc to help upgrading to 3.0.
28-
2926
## 3.0.0-rc.2
3027

3128
* JsonLd: correct the `api_jsonld_context` route format (#4844)
@@ -49,6 +46,41 @@ Breaking changes:
4946
* Doctrine: `ContextAware` interfaces were merged with their child interfaces you can safely remove them (#4779)
5047
* Metadata: the `Core` namespace got removed (#4805)
5148
* Mercure: deprecation removed (#4805)
49+
* Identifiers: using an object as identifier is supported only when this object is `Stringable`
50+
* Serializer: `skip_null_values` now defaults to `true`
51+
* Metadata: `Patch` is added to the automatic CRUD
52+
53+
## 2.7.1
54+
55+
* Chore: update swagger ui and javascript libraries (#5028)
56+
* Symfony: don't use ArrayAdapter cache in production #4975 (#5025)
57+
* Doctrine: check fetch joined queries based on all aliases (#4974)
58+
* Metadata: fix missing `array` cast for RDF types in `ApiResource` & `ApiProperty` constructors (#5000)
59+
* Symfony: replace FQCN service names by snake ones (#5019)
60+
* Symfony: add missing dependency on symfony/deprecation-contracts (#5015)
61+
* Chore: add conflict on elasticsearch >= 8.0 (#5018)
62+
* Symfony: bc layer broken for symfony/console lower than 5.3 (#4990)
63+
* Symfony: missing deprecations related to Ulid and Uuid normalize… (#4963)
64+
* Metadata: do not auto-generate NotExposed operation when using custom operation classes
65+
* Symfony: upgrade command requires phpunit (#4968)
66+
* Symfony: upgrade command removes filters (#4970)
67+
* Symfony: missing Elasticsearch DocumentMetadataFactoryInterface alias definition (#4962)
68+
* Chore: drop dependency on fig/link-util (#4945)
69+
* Metadata: resource name collection missing deprecation (#4953)
70+
* Doctrine: ability to use ORM and ODM (#5032)
71+
72+
## 2.7.0
73+
74+
* chore: remove @experimental phpdoc (#4933)
75+
* Metadata: do not set id when identifier is `false` (#4880)
76+
* Metadata: automatic GET operation when none is declared (#4881)
77+
* Metadata: exception to status on operations (#4861)
78+
* Serializer: adds the JSON_INVALID_UTF8_IGNORE flag to JsonEncode (#4741)
79+
* Symfony: autoconfigure legacy Doctrine extensions (#4909)
80+
* Elasticsearch: skip metadata without ES nodes (#4913)
81+
* Symfony: deprecated the `$exceptionOnNoToken` parameter in `ResourceAccessChecker::__construct()` (#4900)
82+
83+
Various cs fixes and PHPDoc to help upgrading to 3.0.
5284

5385
## 2.7.0-rc.2
5486

@@ -141,7 +173,7 @@ Doctrine: new interfaces for Filters and Extensions ready, switch to the `ApiPla
141173

142174
* Swagger UI: Add `usePkceWithAuthorizationCodeGrant` to Swagger UI initOAuth (#4649)
143175
* **BC**: `mapping.paths` in configuration should override bundles configuration (#4465)
144-
* GraphQL: Add ability to use different pagination types for the queries of a resource (#4453)
176+
* GraphQL: Add the ability to use different pagination types for the queries of a resource (#4453)
145177
* Security: **BC** Fix `ApiProperty` `security` attribute expression being passed a class string for the `object` variable on updates/creates - null is now passed instead if the object is not available (#4184)
146178
* Security: `ApiProperty` now supports a `security_post_denormalize` attribute, which provides access to the `object` variable for the object being updated/created and `previous_object` for the object before it was updated (#4184)
147179
* Maker: Add `make:data-provider` and `make :data-persister` commands to generate a data provider / persister (#3850)
@@ -175,7 +207,7 @@ Doctrine: new interfaces for Filters and Extensions ready, switch to the `ApiPla
175207
* Metadata: deprecation of `ApiPlatform\Core\Annotation` (#4351)
176208
* Metadata: `ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface` is deprecated in favor of `ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface` (#4351)
177209
* Metadata: item and collection prefixes for operations are deprecated, as well as the `ApiPlatform\Core\Api\OperationType` class (#4351)
178-
* Graphql: `ApiPlatform\Metadata\GraphQl` follow the same metadata conventions (a Subscription operation is available and isn't hidden behind an update Mutation anymore), interfaces got simplified (beeing @experimental) (#4351)
210+
* Graphql: `ApiPlatform\Metadata\GraphQl` follow the same metadata conventions (a Subscription operation is available and isn't hidden behind an update Mutation anymore), interfaces got simplified (being @experimental) (#4351)
179211
* IriConverter: new interface for `ApiPlatform\Bridge\Symfony\Routing\IriConverter` that adds an operationName, same for `ApiPlatform\Api\IdentifiersExtractor` (#4351)
180212
* DataProvider: new `ApiPlatform\State\ProviderInterface` that replaces DataProviders (#4351)
181213
* DataPersister: new `ApiPlatform\State\ProcessorInterface` that replaces DataPersisters (#4351)

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
"require": {
2222
"php": ">=8.1",
2323
"doctrine/inflector": "^1.0 || ^2.0",
24-
"fig/link-util": "^1.0",
2524
"psr/cache": "^1.0 || ^2.0 || ^3.0",
2625
"psr/container": "^1.0 || ^2.0",
2726
"symfony/http-foundation": "^6.1",
@@ -100,7 +99,8 @@
10099
"doctrine/common": "<2.7",
101100
"doctrine/dbal": "<2.10",
102101
"doctrine/mongodb-odm": "<2.2",
103-
"doctrine/persistence": "<1.3"
102+
"doctrine/persistence": "<1.3",
103+
"elasticsearch/elasticsearch": ">=8.0"
104104
},
105105
"suggest": {
106106
"doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Feature: Using exception_to_status config
2+
As an API developer
3+
I can customize the status code returned if the application throws an exception
4+
5+
@createSchema
6+
@!mongodb
7+
Scenario: Configure status code via the operation exceptionToStatus to map custom NotFound error to 404
8+
When I add "Content-Type" header equal to "application/ld+json"
9+
And I send a "GET" request to "/dummy_exception_to_statuses/123"
10+
Then the response status code should be 404
11+
And the response should be in JSON
12+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
13+
14+
@!mongodb
15+
Scenario: Configure status code via the resource exceptionToStatus to map custom NotFound error to 400
16+
When I add "Content-Type" header equal to "application/ld+json"
17+
And I send a "PUT" request to "/dummy_exception_to_statuses/123" with body:
18+
"""
19+
{
20+
"name": "black"
21+
}
22+
"""
23+
Then the response status code should be 400
24+
And the response should be in JSON
25+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
26+
27+
@!mongodb
28+
Scenario: Configure status code via the config file to map FilterValidationException to 400
29+
When I add "Content-Type" header equal to "application/ld+json"
30+
And I send a "GET" request to "/dummy_exception_to_statuses"
31+
Then the response status code should be 400
32+
And the response should be in JSON
33+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Feature: Serialize empty array as object
2+
In order to have a coherent JSON representation
3+
As a developer
4+
I should be able to serialize some empty array properties as objects
5+
6+
@createSchema
7+
Scenario: Get a resource with empty array properties as objects
8+
When I add "Content-Type" header equal to "application/ld+json"
9+
And I send a "GET" request to "/empty_array_as_objects/5"
10+
Then the response status code should be 200
11+
And the response should be in JSON
12+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
13+
And the JSON should be equal to:
14+
"""
15+
{
16+
"@context": "/contexts/EmptyArrayAsObject",
17+
"@id": "/empty_array_as_objects/6",
18+
"@type": "EmptyArrayAsObject",
19+
"id": 6,
20+
"emptyArray": [],
21+
"emptyArrayAsObject": {},
22+
"arrayObjectAsArray": [],
23+
"arrayObject": {},
24+
"stringArray": [
25+
"foo",
26+
"bar"
27+
],
28+
"objectArray": {
29+
"foo": 67,
30+
"bar": "baz"
31+
}
32+
}
33+
"""

src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php

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

1414
namespace ApiPlatform\Doctrine\Odm\Metadata\Resource;
1515

16-
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
17-
use ApiPlatform\Doctrine\Common\State\RemoveProcessor;
1816
use ApiPlatform\Doctrine\Odm\State\CollectionProvider;
1917
use ApiPlatform\Doctrine\Odm\State\ItemProvider;
2018
use ApiPlatform\Metadata\ApiResource;
@@ -101,9 +99,9 @@ private function getProvider(Operation $operation): string
10199
private function getProcessor(Operation $operation): string
102100
{
103101
if ($operation instanceof DeleteOperationInterface) {
104-
return RemoveProcessor::class;
102+
return 'api_platform.doctrine_mongodb.odm.state.remove_processor';
105103
}
106104

107-
return PersistProcessor::class;
105+
return 'api_platform.doctrine_mongodb.odm.state.persist_processor';
108106
}
109107
}

src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php

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

1414
namespace ApiPlatform\Doctrine\Orm\Metadata\Resource;
1515

16-
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
17-
use ApiPlatform\Doctrine\Common\State\RemoveProcessor;
1816
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
1917
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
2018
use ApiPlatform\Metadata\ApiResource;
@@ -101,9 +99,9 @@ private function getProvider(Operation $operation): string
10199
private function getProcessor(Operation $operation): string
102100
{
103101
if ($operation instanceof DeleteOperationInterface) {
104-
return RemoveProcessor::class;
102+
return 'api_platform.doctrine.orm.state.remove_processor';
105103
}
106104

107-
return PersistProcessor::class;
105+
return 'api_platform.doctrine.orm.state.persist_processor';
108106
}
109107
}

src/Doctrine/Orm/Util/QueryChecker.php

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -92,30 +92,14 @@ public static function hasMaxResults(QueryBuilder $queryBuilder): bool
9292
public static function hasOrderByOnFetchJoinedToManyAssociation(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool
9393
{
9494
if (
95-
0 === (is_countable($selectParts = $queryBuilder->getDQLPart('select')) ? \count($selectParts = $queryBuilder->getDQLPart('select')) : 0) ||
96-
0 === (is_countable($queryBuilder->getDQLPart('join')) ? \count($queryBuilder->getDQLPart('join')) : 0) ||
97-
0 === (is_countable($orderByParts = $queryBuilder->getDQLPart('orderBy')) ? \count($orderByParts = $queryBuilder->getDQLPart('orderBy')) : 0)
95+
0 === \count($queryBuilder->getDQLPart('join')) ||
96+
0 === \count($orderByParts = $queryBuilder->getDQLPart('orderBy'))
9897
) {
9998
return false;
10099
}
101100

102101
$rootAliases = $queryBuilder->getRootAliases();
103102

104-
$selectAliases = [];
105-
106-
foreach ($selectParts as $select) {
107-
foreach ($select->getParts() as $part) {
108-
[$alias] = explode('.', (string) $part);
109-
110-
$selectAliases[] = $alias;
111-
}
112-
}
113-
114-
$selectAliases = array_diff($selectAliases, $rootAliases);
115-
if (0 === \count($selectAliases)) {
116-
return false;
117-
}
118-
119103
$orderByAliases = [];
120104

121105
foreach ($orderByParts as $orderBy) {
@@ -133,11 +117,13 @@ public static function hasOrderByOnFetchJoinedToManyAssociation(QueryBuilder $qu
133117
return false;
134118
}
135119

120+
$allAliases = $queryBuilder->getAllAliases();
121+
136122
foreach ($orderByAliases as $orderByAlias) {
137123
$inToManyContext = false;
138124

139125
foreach (QueryBuilderHelper::traverseJoins($orderByAlias, $queryBuilder, $managerRegistry) as $alias => [$metadata, $association]) {
140-
if ($inToManyContext && \in_array($alias, $selectAliases, true)) {
126+
if ($inToManyContext && \in_array($alias, $allAliases, true)) {
141127
return true;
142128
}
143129

src/GraphQl/Resolver/Stage/ReadStage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function __construct(private readonly IriConverterInterface $iriConverter
4343
/**
4444
* {@inheritdoc}
4545
*/
46-
public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context): iterable|object|null
46+
public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context): object|array|null
4747
{
4848
if (!($operation->canRead() ?? true)) {
4949
return $context['is_collection'] ? [] : null;

src/GraphQl/Resolver/Stage/ReadStageInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@
2222
*/
2323
interface ReadStageInterface
2424
{
25-
public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context): iterable|object|null;
25+
public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context): object|array|null;
2626
}

src/GraphQl/Resolver/Stage/SerializeStage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(private readonly NormalizerInterface $normalizer, pr
3838
{
3939
}
4040

41-
public function __invoke(iterable|object|null $itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array
41+
public function __invoke(object|array|null $itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array
4242
{
4343
$isCollection = $operation instanceof CollectionOperationInterface;
4444
$isMutation = $operation instanceof Mutation;

src/GraphQl/Resolver/Stage/SerializeStageInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@
2222
*/
2323
interface SerializeStageInterface
2424
{
25-
public function __invoke(iterable|object|null $itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array;
25+
public function __invoke(object|array|null $itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array;
2626
}

src/Hydra/EventListener/AddLinkHeaderListener.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
use ApiPlatform\Api\UrlGeneratorInterface;
1717
use ApiPlatform\JsonLd\ContextBuilder;
1818
use ApiPlatform\Util\CorsTrait;
19-
use Fig\Link\GenericLinkProvider;
20-
use Fig\Link\Link;
2119
use Symfony\Component\HttpKernel\Event\ResponseEvent;
20+
use Symfony\Component\WebLink\GenericLinkProvider;
21+
use Symfony\Component\WebLink\Link;
2222

2323
/**
2424
* Adds the HTTP Link header pointing to the Hydra documentation.

src/Hydra/Serializer/CollectionNormalizer.php

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function __construct(private readonly ContextBuilderInterface $contextBui
5656
*/
5757
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
5858
{
59-
return self::FORMAT === $format && is_iterable($data);
59+
return self::FORMAT === $format && is_iterable($data) && isset($context['resource_class']) && !isset($context['api_sub_level']);
6060
}
6161

6262
/**
@@ -66,10 +66,6 @@ public function supportsNormalization(mixed $data, string $format = null, array
6666
*/
6767
public function normalize(mixed $object, string $format = null, array $context = []): array
6868
{
69-
if (!isset($context['resource_class']) || isset($context['api_sub_level'])) {
70-
return $this->normalizeRawCollection($object, $format, $context);
71-
}
72-
7369
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
7470
$context = $this->initContext($resourceClass, $context);
7571
$data = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context);
@@ -106,19 +102,6 @@ public function normalize(mixed $object, string $format = null, array $context =
106102

107103
public function hasCacheableSupportsMethod(): bool
108104
{
109-
return true;
110-
}
111-
112-
/**
113-
* Normalizes a raw collection (not API resources).
114-
*/
115-
private function normalizeRawCollection(iterable $object, ?string $format, array $context): array
116-
{
117-
$data = [];
118-
foreach ($object as $index => $obj) {
119-
$data[$index] = $this->normalizer->normalize($obj, $format, $context);
120-
}
121-
122-
return $data;
105+
return false;
123106
}
124107
}

0 commit comments

Comments
 (0)