Skip to content

Commit ef4812a

Browse files
committed
Merge 3.4
2 parents e29b9f6 + f40d9a1 commit ef4812a

File tree

21 files changed

+268
-63
lines changed

21 files changed

+268
-63
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,46 @@ jobs:
8383
php-version: ${{ matrix.php }}
8484
extensions: intl, bcmath, curl, openssl, mbstring, mongodb
8585
ini-values: memory_limit=-1
86-
tools: pecl, composer, php-cs-fixer
86+
tools: php-cs-fixer
8787
coverage: none
8888
- name: Run PHP-CS-Fixer fix
8989
run: php-cs-fixer fix --dry-run --diff --ansi
9090

91+
lint-container:
92+
name: Lint Container
93+
runs-on: ubuntu-latest
94+
timeout-minutes: 20
95+
strategy:
96+
matrix:
97+
php:
98+
- '8.3'
99+
fail-fast: false
100+
steps:
101+
- name: Checkout
102+
uses: actions/checkout@v4
103+
- name: Setup PHP
104+
uses: shivammathur/setup-php@v2
105+
with:
106+
php-version: ${{ matrix.php }}
107+
extensions: intl, bcmath, curl, openssl, mbstring, mongodb
108+
ini-values: memory_limit=-1
109+
tools: composer
110+
coverage: none
111+
- name: Get composer cache directory
112+
id: composercache
113+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
114+
- name: Cache dependencies
115+
uses: actions/cache@v4
116+
with:
117+
path: ${{ steps.composercache.outputs.dir }}
118+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
119+
restore-keys: ${{ runner.os }}-composer-
120+
- name: Update project dependencies
121+
run: |
122+
composer update --no-interaction --no-progress --ansi
123+
- name: Run container lint
124+
run: tests/Fixtures/app/console lint:container
125+
91126
phpstan:
92127
name: PHPStan (PHP ${{ matrix.php }})
93128
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Changelog
22

3+
## v3.3.6
4+
5+
### Bug fixes
6+
7+
* [8e253d4d7](https://github.com/api-platform/core/commit/8e253d4d76c25c27c233b3cf4fd2314b13c1e193) fix(graphql): validate after resolver (#6426)
8+
* [d66769069](https://github.com/api-platform/core/commit/d66769069fda051e4ddc7be50764c3f34d055ad0) fix(symfony): load swagger_ui when enabled (#6424)
9+
* [e06c88b71](https://github.com/api-platform/core/commit/e06c88b7149da4e5b691d432f11ec3d27e41750a) fix(metadata): add some phpdoc annotations to ORM (#6387)
10+
* [fb7c4658c](https://github.com/api-platform/core/commit/fb7c4658c327c9628bcc86d42e85c3546a74d993) fix(test): canonicalizing json arrays (#6386)
11+
* [ff533565d](https://github.com/api-platform/core/commit/ff533565d9b976fcda818ab4f79a5a2642f14a32) fix(doctrine): use null-safe operator when retrieving parameters (#6423)
12+
13+
Notes:
14+
15+
The patch at #6426 introduces a new `validateAfterResolver` option to mitigate the BC-break introduced in 3.3 that does the validation before calling the resolver:
16+
17+
```php
18+
new Mutation(
19+
resolver: 'app.graphql.mutation_resolver.activity_log',
20+
name: 'create',
21+
validateAfterResolver: true,
22+
validate: false
23+
)
24+
```
25+
326
## v3.3.5
427

528
### Bug fixes
@@ -35,7 +58,7 @@
3558

3659
### Notes
3760

38-
You can remove the `event_listeners_backward_compatibility_layer` flag and set `use_symfony_listeners` instead. The `use_symfony_listeners` should be `true` if you use controllers or if you rely on Symfony event listeners. Note that now flags like `read` can be forced to `true` if you want to call a Provider even on `POST` operations. These are the rules we set up on runtime if no value has been set:
61+
You can remove the `event_listeners_backward_compatibility_layer` flag and set `use_symfony_listeners` instead. The `use_symfony_listeners` should be `true` if you use controllers or if you rely on Symfony event listeners. Note that now flags like `read` can be forced to `true` if you want to call a Provider even on `POST` operations. These are the rules we set up on runtime if no value has been set:
3962

4063
```php
4164
if (null === $operation->canValidate()) {
@@ -51,7 +74,7 @@ if (null === $operation->canDeserialize()) {
5174
}
5275
```
5376

54-
Previously listeners did the checks before reading our flags and you could not force the values.
77+
Previously listeners did the checks before reading our flags and you could not force the values.
5578

5679
When using GraphQl, with `event_listeners_backward_compatibility_layer: true`, mutation resolver gets called before validation, when using `false` (the future default) validation occurs on the user's input.
5780

@@ -155,7 +178,7 @@ The v3.3.0-beta.1 introduces a new `QueryParameter` attribute to improve [the fi
155178
* [cc9f6a518](https://github.com/api-platform/core/commit/cc9f6a518222598d20556fc1ec62b7c4be52bf52) feat(symfony): request and view kernel listeners (#6102)
156179
* [ce9ab8226](https://github.com/api-platform/core/commit/ce9ab8226934bfac45e3408e9468bf32a02aa2e9) feat(metadata): headers configuration (#6074)
157180

158-
Components:
181+
Components:
159182
- `api-platform/parametervalidator`
160183
- `api-platform/doctrine-common`
161184
- `api-platform/doctrine-orm`
@@ -164,7 +187,7 @@ Components:
164187
A new interface `ApiPlatform\Serializer\TagCollectorInterface` allows to collect cache tags (IRIs) during serialization instead of using API Platform defaults.
165188
An experimental feature (#5290) gives the ability to use `security` on sub resource links.
166189

167-
If you use controllers you should use:
190+
If you use controllers you should use:
168191

169192
```yaml
170193
api_platform:
@@ -181,7 +204,7 @@ The default is `false` you can get rid of the `event_listeners_backward_compatib
181204
class Book {}
182205
```
183206

184-
These namespaces are deprecated:
207+
These namespaces are deprecated:
185208

186209
- `ApiPlatform\Api`
187210
- `ApiPlatform\Exception`
@@ -200,6 +223,14 @@ api_platform:
200223
form: ['multipart/form-data']
201224
```
202225

226+
## v3.2.24
227+
228+
### Bug fixes
229+
230+
* [451d50e53](https://github.com/api-platform/core/commit/451d50e538a4f7aac67d47601fee5474af99c870) fix(symfony): deprecations 7.1
231+
* [93e71eb82](https://github.com/api-platform/core/commit/93e71eb822aa06db2b6de303039f9b8c65cad7a8) fix(graphql): name converter with class (#6396)
232+
* [99314bf80](https://github.com/api-platform/core/commit/99314bf80e7c61147fb311da6403b37be5eab5b2) fix(state): handle empty request in read provider (#6403)
233+
203234
## v3.2.23
204235

205236
### Bug fixes
@@ -361,7 +392,7 @@ Symfony 7 support.
361392

362393
### Bug fixes
363394

364-
To have errors backward compatible with 3.1, use:
395+
To have errors backward compatible with 3.1, use:
365396

366397
```yaml
367398
api_platform:
@@ -2316,4 +2347,4 @@ Please read #2825 if you have issues with the behavior of Readable/Writable Link
23162347
## 1.0.0 beta 2
23172348

23182349
* Preserve indexes when normalizing and denormalizing associative arrays
2319-
* Allow setting default order for property when registering a `Doctrine\Orm\Filter\OrderFilter` instance
2350+
* Allow setting default order for property when registering a `Doctrine\Orm\Filter\OrderFilter` instance

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"behat/mink": "^1.9",
4343
"doctrine/cache": "^1.11 || ^2.1",
4444
"doctrine/common": "^3.2.2",
45-
"doctrine/dbal": "^3.4.0",
45+
"doctrine/dbal": "^3.4.0 || ^4.0",
4646
"doctrine/doctrine-bundle": "^1.12 || ^2.0",
4747
"doctrine/mongodb-odm": "^2.2",
4848
"doctrine/mongodb-odm-bundle": "^4.0 || ^5.0",
@@ -64,7 +64,7 @@
6464
"phpunit/phpunit": "^9.6",
6565
"psr/log": "^1.0 || ^2.0 || ^3.0",
6666
"ramsey/uuid": "^3.9.7 || ^4.0",
67-
"ramsey/uuid-doctrine": "^1.4 || ^2.0",
67+
"ramsey/uuid-doctrine": "^1.4 || ^2.0 || ^3.0",
6868
"sebastian/comparator": "<5.0",
6969
"soyuka/contexts": "v3.3.9",
7070
"soyuka/pmu": "^0.0.2",

docs/guides/subresource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class Company
7575
function request(): Request
7676
{
7777
// Persistence is automatic, you can try to create or read data:
78-
return Request::create('/company/1/employees', 'GET');
78+
return Request::create('/companies/1/employees', 'GET');
7979
}
8080
}
8181

features/graphql/mutation.feature

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,3 +1052,20 @@ Feature: GraphQL mutation support
10521052
And the header "Content-Type" should be equal to "application/json"
10531053
And the JSON node "errors" should not exist
10541054
And the JSON node "data.deleteActivityLog.activityLog" should exist
1055+
1056+
@!mongodb
1057+
Scenario: Mutation should run before validation
1058+
When I send the following GraphQL request:
1059+
"""
1060+
mutation {
1061+
createActivityLog(input: {name: ""}) {
1062+
activityLog {
1063+
name
1064+
}
1065+
}
1066+
}
1067+
"""
1068+
Then the response status code should be 200
1069+
And the response should be in JSON
1070+
And the header "Content-Type" should be equal to "application/json"
1071+
And the JSON node "data.createActivityLog.activityLog.name" should be equal to "hi"

src/Doctrine/Orm/Extension/ParameterExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function __construct(private readonly ContainerInterface $filterLocator)
3535
*/
3636
private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
3737
{
38-
foreach ($operation->getParameters() ?? [] as $parameter) {
38+
foreach ($operation?->getParameters() ?? [] as $parameter) {
3939
$values = $parameter->getExtraProperties()['_api_values'] ?? [];
4040
if (!$values) {
4141
continue;

src/Metadata/GraphQl/Operation.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public function __construct(
4040
protected ?array $args = null,
4141
protected ?array $extraArgs = null,
4242
protected ?array $links = null,
43+
protected ?bool $validateAfterResolver = null,
4344

4445
?string $shortName = null,
4546
?string $class = null,
@@ -195,4 +196,17 @@ public function withLinks(array $links): self
195196

196197
return $self;
197198
}
199+
200+
public function canValidateAfterResolver(): ?bool
201+
{
202+
return $this->validateAfterResolver;
203+
}
204+
205+
public function withValidateAfterResolver(bool $validateAfterResolver = true): self
206+
{
207+
$self = clone $this;
208+
$self->validateAfterResolver = $validateAfterResolver;
209+
210+
return $self;
211+
}
198212
}

src/OpenApi/Serializer/SerializerContextBuilder.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313

1414
namespace ApiPlatform\OpenApi\Serializer;
1515

16+
use ApiPlatform\Serializer\SerializerContextBuilderInterface as LegacySerializerContextBuilderInterface;
1617
use ApiPlatform\State\SerializerContextBuilderInterface;
1718
use Symfony\Component\HttpFoundation\Request;
1819

1920
/**
2021
* @internal
2122
*/
22-
final class SerializerContextBuilder implements SerializerContextBuilderInterface
23+
final class SerializerContextBuilder implements SerializerContextBuilderInterface, LegacySerializerContextBuilderInterface
2324
{
2425
public function __construct(private readonly SerializerContextBuilderInterface $decorated)
2526
{

src/Serializer/SerializerContextBuilderInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
/**
1919
* Builds the context used by the Symfony Serializer.
2020
*
21+
* @deprecated use ApiPlatform\State\SerializerContextBuilderInterface instead
22+
*
2123
* @author Kévin Dunglas <dunglas@gmail.com>
2224
*/
2325
interface SerializerContextBuilderInterface extends StateSerializerContextBuilderInterface

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,9 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
536536
$loader->load('symfony/swagger_ui.xml');
537537
}
538538

539-
$loader->load('state/swagger_ui.xml');
539+
if ($config['enable_swagger_ui']) {
540+
$loader->load('state/swagger_ui.xml');
541+
}
540542

541543
if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
542544
// Remove the listener but keep the controller to allow customizing the path of the UI

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,11 @@
3434
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
3535
<argument key="$debug">%kernel.debug%</argument>
3636
</service>
37-
<service id="ApiPlatform\Serializer\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />
3837

3938
<service id="api_platform.serializer.filter_parameter_provider" class="ApiPlatform\Serializer\Parameter\SerializerFilterParameterProvider" public="false">
4039
<argument type="service" id="api_platform.filter_locator" />
4140
<tag name="api_platform.parameter_provider" key="api_platform.serializer.filter_parameter_provider" priority="-895" />
4241
</service>
43-
4442
<service id="ApiPlatform\Serializer\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />
4543

4644
<service id="api_platform.serializer.context_builder.filter" class="ApiPlatform\Serializer\SerializerFilterContextBuilder" decorates="api_platform.serializer.context_builder" public="false">

src/Symfony/Bundle/Resources/config/graphql/validator.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,12 @@
66
<argument type="service" id="api_platform.graphql.state_provider.validate.inner" />
77
<argument type="service" id="api_platform.validator" />
88
</service>
9+
10+
<!-- see api_platform.graphql.state_provider.resolver and discussion at https://github.com/api-platform/core/issues/6354 this validates after resolver has been called -->
11+
<service id="api_platform.graphql.state_provider.validate_after_resolver" class="ApiPlatform\Symfony\Validator\State\ValidateProvider" decorates="api_platform.graphql.state_provider" decoration-priority="180">
12+
<argument type="service" id="api_platform.graphql.state_provider.validate_after_resolver.inner" />
13+
<argument type="service" id="api_platform.validator" />
14+
<argument>canValidateAfterResolver</argument>
15+
</service>
916
</services>
1017
</container>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,7 @@
5959
<argument type="service" id="api_platform.state_provider.parameter.inner" />
6060
<argument type="tagged_locator" tag="api_platform.parameter_provider" index-by="key" />
6161
</service>
62+
63+
<service id="ApiPlatform\State\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />
6264
</services>
6365
</container>

src/Symfony/Validator/State/ValidateProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424
final class ValidateProvider implements ProviderInterface
2525
{
26-
public function __construct(private readonly ?ProviderInterface $decorated, private readonly ValidatorInterface $validator)
26+
public function __construct(private readonly ?ProviderInterface $decorated, private readonly ValidatorInterface $validator, private readonly string $canValidateAccessor = 'canValidate')
2727
{
2828
}
2929

@@ -35,7 +35,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
3535
return $body;
3636
}
3737

38-
if (!($operation->canValidate() ?? true)) {
38+
if (method_exists($operation, $this->canValidateAccessor) && !($operation->{$this->canValidateAccessor}() ?? ('canValidate' === $this->canValidateAccessor))) {
3939
return $body;
4040
}
4141

tests/Behat/MercureContext.php

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

1414
namespace ApiPlatform\Tests\Behat;
1515

16+
use ApiPlatform\Tests\Fixtures\TestBundle\Mercure\TestHub;
1617
use Behat\Behat\Context\Context;
1718
use Behat\Gherkin\Node\PyStringNode;
1819
use Behat\Gherkin\Node\TableNode;
@@ -37,7 +38,7 @@ public function __construct(private readonly ContainerInterface $driverContainer
3738
*/
3839
public function mercureUpdatesShouldHaveBeenSent(int $number): void
3940
{
40-
$updateHandler = $this->driverContainer->get('mercure.hub.default.message_handler');
41+
$updateHandler = $this->getMercureTestHub();
4142
$total = \count($updateHandler->getUpdates());
4243

4344
if (0 === $total) {
@@ -70,7 +71,7 @@ public function firstMercureUpdateShouldHaveData(PyStringNode $data): void
7071
*/
7172
public function mercureUpdateShouldHaveTopics(int $index, TableNode $table): void
7273
{
73-
$updateHandler = $this->driverContainer->get('mercure.hub.default.message_handler');
74+
$updateHandler = $this->getMercureTestHub();
7475
$updates = $updateHandler->getUpdates();
7576

7677
if (0 === \count($updates)) {
@@ -90,7 +91,7 @@ public function mercureUpdateShouldHaveTopics(int $index, TableNode $table): voi
9091
*/
9192
public function mercureUpdateShouldHaveData(int $index, PyStringNode $data): void
9293
{
93-
$updateHandler = $this->driverContainer->get('mercure.hub.default.message_handler');
94+
$updateHandler = $this->getMercureTestHub();
9495
$updates = $updateHandler->getUpdates();
9596

9697
if (0 === \count($updates)) {
@@ -113,8 +114,7 @@ public function theFollowingMercureUpdateShouldHaveBeenSent(string $topics, PySt
113114
$topics = explode(',', $topics);
114115
$update = json_decode($update->getRaw(), true, 512, \JSON_THROW_ON_ERROR);
115116

116-
$updateHandler = $this->driverContainer->get('mercure.hub.default.message_handler');
117-
117+
$updateHandler = $this->getMercureTestHub();
118118
foreach ($updateHandler->getUpdates() as $sentUpdate) {
119119
$toMatchTopics = \count($topics);
120120
foreach ($sentUpdate->getTopics() as $sentTopic) {
@@ -136,4 +136,9 @@ public function theFollowingMercureUpdateShouldHaveBeenSent(string $topics, PySt
136136

137137
throw new \RuntimeException('Mercure update has not been sent.');
138138
}
139+
140+
private function getMercureTestHub(): TestHub
141+
{
142+
return $this->driverContainer->get('mercure.hub.default.test_hub');
143+
}
139144
}

0 commit comments

Comments
 (0)