Skip to content

Commit

Permalink
Merge 3.3
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Jun 14, 2024
2 parents 61af0cc + c7f25dc commit f40d9a1
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 61 deletions.
37 changes: 36 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,46 @@ jobs:
php-version: ${{ matrix.php }}
extensions: intl, bcmath, curl, openssl, mbstring, mongodb
ini-values: memory_limit=-1
tools: pecl, composer, php-cs-fixer
tools: php-cs-fixer
coverage: none
- name: Run PHP-CS-Fixer fix
run: php-cs-fixer fix --dry-run --diff --ansi

lint-container:
name: Lint Container
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
php:
- '8.3'
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: intl, bcmath, curl, openssl, mbstring, mongodb
ini-values: memory_limit=-1
tools: composer
coverage: none
- name: Get composer cache directory
id: composercache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Update project dependencies
run: |
composer update --no-interaction --no-progress --ansi
- name: Run container lint
run: tests/Fixtures/app/console lint:container

phpstan:
name: PHPStan (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
Expand Down
45 changes: 38 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## v3.3.6

### Bug fixes

* [8e253d4d7](https://github.com/api-platform/core/commit/8e253d4d76c25c27c233b3cf4fd2314b13c1e193) fix(graphql): validate after resolver (#6426)
* [d66769069](https://github.com/api-platform/core/commit/d66769069fda051e4ddc7be50764c3f34d055ad0) fix(symfony): load swagger_ui when enabled (#6424)
* [e06c88b71](https://github.com/api-platform/core/commit/e06c88b7149da4e5b691d432f11ec3d27e41750a) fix(metadata): add some phpdoc annotations to ORM (#6387)
* [fb7c4658c](https://github.com/api-platform/core/commit/fb7c4658c327c9628bcc86d42e85c3546a74d993) fix(test): canonicalizing json arrays (#6386)
* [ff533565d](https://github.com/api-platform/core/commit/ff533565d9b976fcda818ab4f79a5a2642f14a32) fix(doctrine): use null-safe operator when retrieving parameters (#6423)

Notes:

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:

```php
new Mutation(
resolver: 'app.graphql.mutation_resolver.activity_log',
name: 'create',
validateAfterResolver: true,
validate: false
)
```

## v3.3.5

### Bug fixes
Expand Down Expand Up @@ -35,7 +58,7 @@

### Notes

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:
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:

```php
if (null === $operation->canValidate()) {
Expand All @@ -51,7 +74,7 @@ if (null === $operation->canDeserialize()) {
}
```

Previously listeners did the checks before reading our flags and you could not force the values.
Previously listeners did the checks before reading our flags and you could not force the values.

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.

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

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

If you use controllers you should use:
If you use controllers you should use:

```yaml
api_platform:
Expand All @@ -181,7 +204,7 @@ The default is `false` you can get rid of the `event_listeners_backward_compatib
class Book {}
```

These namespaces are deprecated:
These namespaces are deprecated:

- `ApiPlatform\Api`
- `ApiPlatform\Exception`
Expand All @@ -200,6 +223,14 @@ api_platform:
form: ['multipart/form-data']
```

## v3.2.24

### Bug fixes

* [451d50e53](https://github.com/api-platform/core/commit/451d50e538a4f7aac67d47601fee5474af99c870) fix(symfony): deprecations 7.1
* [93e71eb82](https://github.com/api-platform/core/commit/93e71eb822aa06db2b6de303039f9b8c65cad7a8) fix(graphql): name converter with class (#6396)
* [99314bf80](https://github.com/api-platform/core/commit/99314bf80e7c61147fb311da6403b37be5eab5b2) fix(state): handle empty request in read provider (#6403)

## v3.2.23

### Bug fixes
Expand Down Expand Up @@ -361,7 +392,7 @@ Symfony 7 support.

### Bug fixes

To have errors backward compatible with 3.1, use:
To have errors backward compatible with 3.1, use:

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

* Preserve indexes when normalizing and denormalizing associative arrays
* Allow setting default order for property when registering a `Doctrine\Orm\Filter\OrderFilter` instance
* Allow setting default order for property when registering a `Doctrine\Orm\Filter\OrderFilter` instance
2 changes: 1 addition & 1 deletion docs/guides/subresource.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class Company
function request(): Request
{
// Persistence is automatic, you can try to create or read data:
return Request::create('/company/1/employees', 'GET');
return Request::create('/companies/1/employees', 'GET');
}
}

Expand Down
17 changes: 17 additions & 0 deletions features/graphql/mutation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1052,3 +1052,20 @@ Feature: GraphQL mutation support
And the header "Content-Type" should be equal to "application/json"
And the JSON node "errors" should not exist
And the JSON node "data.deleteActivityLog.activityLog" should exist

@!mongodb
Scenario: Mutation should run before validation
When I send the following GraphQL request:
"""
mutation {
createActivityLog(input: {name: ""}) {
activityLog {
name
}
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.createActivityLog.activityLog.name" should be equal to "hi"
2 changes: 1 addition & 1 deletion src/Doctrine/Orm/Extension/ParameterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function __construct(private readonly ContainerInterface $filterLocator)
*/
private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
foreach ($operation->getParameters() ?? [] as $parameter) {
foreach ($operation?->getParameters() ?? [] as $parameter) {
$values = $parameter->getExtraProperties()['_api_values'] ?? [];
if (!$values) {
continue;
Expand Down
14 changes: 14 additions & 0 deletions src/Metadata/GraphQl/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function __construct(
protected ?array $args = null,
protected ?array $extraArgs = null,
protected ?array $links = null,
protected ?bool $validateAfterResolver = null,

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

return $self;
}

public function canValidateAfterResolver(): ?bool
{
return $this->validateAfterResolver;
}

public function withValidateAfterResolver(bool $validateAfterResolver = true): self
{
$self = clone $this;
$self->validateAfterResolver = $validateAfterResolver;

return $self;
}
}
3 changes: 2 additions & 1 deletion src/OpenApi/Serializer/SerializerContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@

namespace ApiPlatform\OpenApi\Serializer;

use ApiPlatform\Serializer\SerializerContextBuilderInterface as LegacySerializerContextBuilderInterface;
use ApiPlatform\State\SerializerContextBuilderInterface;
use Symfony\Component\HttpFoundation\Request;

/**
* @internal
*/
final class SerializerContextBuilder implements SerializerContextBuilderInterface
final class SerializerContextBuilder implements SerializerContextBuilderInterface, LegacySerializerContextBuilderInterface
{
public function __construct(private readonly SerializerContextBuilderInterface $decorated)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,9 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
$loader->load('symfony/swagger_ui.xml');
}

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

if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
// Remove the listener but keep the controller to allow customizing the path of the UI
Expand Down
2 changes: 0 additions & 2 deletions src/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
<argument key="$debug">%kernel.debug%</argument>
</service>
<service id="ApiPlatform\Serializer\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />

<service id="api_platform.serializer.filter_parameter_provider" class="ApiPlatform\Serializer\Parameter\SerializerFilterParameterProvider" public="false">
<argument type="service" id="api_platform.filter_locator" />
<tag name="api_platform.parameter_provider" key="api_platform.serializer.filter_parameter_provider" priority="-895" />
</service>

<service id="ApiPlatform\Serializer\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />

<service id="api_platform.serializer.context_builder.filter" class="ApiPlatform\Serializer\SerializerFilterContextBuilder" decorates="api_platform.serializer.context_builder" public="false">
Expand Down
7 changes: 7 additions & 0 deletions src/Symfony/Bundle/Resources/config/graphql/validator.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@
<argument type="service" id="api_platform.graphql.state_provider.validate.inner" />
<argument type="service" id="api_platform.validator" />
</service>

<!-- 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 -->
<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">
<argument type="service" id="api_platform.graphql.state_provider.validate_after_resolver.inner" />
<argument type="service" id="api_platform.validator" />
<argument>canValidateAfterResolver</argument>
</service>
</services>
</container>
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/Resources/config/state/state.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,7 @@
<argument type="service" id="api_platform.state_provider.parameter.inner" />
<argument type="tagged_locator" tag="api_platform.parameter_provider" index-by="key" />
</service>

<service id="ApiPlatform\State\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />
</services>
</container>
4 changes: 2 additions & 2 deletions src/Symfony/Validator/State/ValidateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/
final class ValidateProvider implements ProviderInterface
{
public function __construct(private readonly ?ProviderInterface $decorated, private readonly ValidatorInterface $validator)
public function __construct(private readonly ?ProviderInterface $decorated, private readonly ValidatorInterface $validator, private readonly string $canValidateAccessor = 'canValidate')
{
}

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

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

Expand Down
15 changes: 10 additions & 5 deletions tests/Behat/MercureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Tests\Behat;

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

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

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

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

$updateHandler = $this->driverContainer->get('mercure.hub.default.message_handler');

$updateHandler = $this->getMercureTestHub();
foreach ($updateHandler->getUpdates() as $sentUpdate) {
$toMatchTopics = \count($topics);
foreach ($sentUpdate->getTopics() as $sentTopic) {
Expand All @@ -136,4 +136,9 @@ public function theFollowingMercureUpdateShouldHaveBeenSent(string $topics, PySt

throw new \RuntimeException('Mercure update has not been sent.');
}

private function getMercureTestHub(): TestHub
{
return $this->driverContainer->get('mercure.hub.default.test_hub');
}
}
36 changes: 0 additions & 36 deletions tests/Fixtures/DummyMercureUpdateHandler.php

This file was deleted.

Loading

0 comments on commit f40d9a1

Please sign in to comment.