Skip to content

Commit cc9f6a5

Browse files
authored
feat(symfony): request and view kernel listeners (#6102)
1 parent b488366 commit cc9f6a5

File tree

82 files changed

+2512
-406
lines changed

Some content is hidden

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

82 files changed

+2512
-406
lines changed

.github/workflows/ci.yml

+71
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,8 @@ jobs:
11271127
run: vendor/bin/simple-phpunit --version
11281128
- name: Clear test app cache
11291129
run: tests/Fixtures/app/console cache:clear --ansi
1130+
- name: Use legacy ignored deprecations
1131+
run: cp tests/.ignored-deprecations-legacy-events tests/.ignored-deprecations
11301132
- name: Run PHPUnit tests
11311133
run: |
11321134
mkdir -p build/logs/phpunit
@@ -1229,3 +1231,72 @@ jobs:
12291231
name: openapi-docs-php${{ matrix.php }}
12301232
path: build/out/openapi
12311233
continue-on-error: true
1234+
1235+
behat_listeners:
1236+
name: Behat event listeners (PHP ${{ matrix.php }})
1237+
env:
1238+
USE_SYMFONY_LISTENERS: 1
1239+
runs-on: ubuntu-latest
1240+
timeout-minutes: 20
1241+
strategy:
1242+
matrix:
1243+
php:
1244+
- '8.3'
1245+
fail-fast: false
1246+
steps:
1247+
- name: Checkout
1248+
uses: actions/checkout@v3
1249+
- name: Setup PHP
1250+
uses: shivammathur/setup-php@v2
1251+
with:
1252+
php-version: ${{ matrix.php }}
1253+
tools: pecl, composer
1254+
extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite
1255+
coverage: pcov
1256+
ini-values: memory_limit=-1
1257+
- name: Get composer cache directory
1258+
id: composercache
1259+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
1260+
- name: Cache dependencies
1261+
uses: actions/cache@v3
1262+
with:
1263+
path: ${{ steps.composercache.outputs.dir }}
1264+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
1265+
restore-keys: ${{ runner.os }}-composer-
1266+
- name: Update project dependencies
1267+
run: composer update --no-interaction --no-progress --ansi
1268+
- name: Install PHPUnit
1269+
run: vendor/bin/simple-phpunit --version
1270+
- name: Clear test app cache
1271+
run: tests/Fixtures/app/console cache:clear --ansi
1272+
- name: Run Behat tests (PHP 8)
1273+
run: |
1274+
mkdir -p build/logs/behat
1275+
vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=symfony_listeners --no-interaction
1276+
- name: Upload test artifacts
1277+
if: always()
1278+
uses: actions/upload-artifact@v3
1279+
with:
1280+
name: behat-logs-php${{ matrix.php }}
1281+
path: build/logs/behat
1282+
continue-on-error: true
1283+
- name: Export OpenAPI documents
1284+
run: |
1285+
mkdir -p build/out/openapi
1286+
tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json
1287+
tests/Fixtures/app/console api:openapi:export --yaml -o build/out/openapi/openapi_v3.yaml
1288+
- name: Setup node
1289+
uses: actions/setup-node@v3
1290+
with:
1291+
node-version: '14'
1292+
- name: Validate OpenAPI documents
1293+
run: |
1294+
npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.json
1295+
npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.yaml
1296+
- name: Upload OpenAPI artifacts
1297+
if: always()
1298+
uses: actions/upload-artifact@v3
1299+
with:
1300+
name: openapi-docs-php${{ matrix.php }}
1301+
path: build/out/openapi
1302+
continue-on-error: true

behat.yml.dist

+35-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ mongodb:
7373
- 'Behat\MinkExtension\Context\MinkContext'
7474
- 'behatch:context:rest'
7575
filters:
76-
tags: '~@sqlite&&~@elasticsearch&&~@!mongodb&&~@mercure'
76+
tags: '~@sqlite&&~@elasticsearch&&~@!mongodb&&~@mercure&&~@controller'
7777

7878
mercure:
7979
suites:
@@ -220,3 +220,37 @@ legacy:
220220
symfony: ~
221221
'Behatch\Extension': ~
222222

223+
symfony_listeners:
224+
suites:
225+
default:
226+
contexts:
227+
- 'ApiPlatform\Tests\Behat\CommandContext'
228+
- 'ApiPlatform\Tests\Behat\DoctrineContext'
229+
- 'ApiPlatform\Tests\Behat\GraphqlContext'
230+
- 'ApiPlatform\Tests\Behat\JsonContext'
231+
- 'ApiPlatform\Tests\Behat\HydraContext'
232+
- 'ApiPlatform\Tests\Behat\OpenApiContext'
233+
- 'ApiPlatform\Tests\Behat\HttpCacheContext'
234+
- 'ApiPlatform\Tests\Behat\JsonApiContext'
235+
- 'ApiPlatform\Tests\Behat\JsonHalContext'
236+
- 'ApiPlatform\Tests\Behat\MercureContext'
237+
- 'ApiPlatform\Tests\Behat\XmlContext'
238+
- 'Behat\MinkExtension\Context\MinkContext'
239+
- 'behatch:context:rest'
240+
filters:
241+
tags: '~@postgres&&~@mongodb&&~@elasticsearch&&~@mercure'
242+
extensions:
243+
'FriendsOfBehat\SymfonyExtension':
244+
bootstrap: 'tests/Fixtures/app/bootstrap.php'
245+
kernel:
246+
environment: 'test'
247+
debug: true
248+
class: AppKernel
249+
path: 'tests/Fixtures/app/AppKernel.php'
250+
'Behat\MinkExtension':
251+
base_url: 'http://example.com/'
252+
files_path: 'features/files'
253+
sessions:
254+
default:
255+
symfony: ~
256+
'Behatch\Extension': ~

features/jsonld/input_output.feature

+2
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ Feature: JSON-LD DTO input and output
186186
"""
187187

188188
@createSchema
189+
@controller
189190
Scenario: Create a resource with no input
190191
When I send a "POST" request to "/dummy_dto_no_inputs"
191192
Then the response status code should be 201
@@ -210,6 +211,7 @@ Feature: JSON-LD DTO input and output
210211
}
211212
"""
212213

214+
@controller
213215
Scenario: Update a resource with no input
214216
When I send a "POST" request to "/dummy_dto_no_inputs/1/double_bat"
215217
Then the response status code should be 200

features/main/custom_operation.feature renamed to features/main/custom_controller.feature

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@controller
12
Feature: Custom operation
23
As a client software developer
34
I need to be able to create custom operations

phpstan.neon.dist

+1
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,4 @@ parameters:
100100
- '#Call to method hasCacheableSupportsMethod\(\) on an unknown class Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface\.#'
101101
- '#Class Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface not found\.#'
102102
- '#Access to undefined constant Symfony\\Component\\HttpKernel\\HttpKernelInterface::MASTER_REQUEST\.#'
103+
- '#Attribute class PHPUnit\\Framework\\Attributes\\DataProvider does not exist.#'

src/Documentation/Action/EntrypointAction.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ public function __invoke(Request $request)
4747
'spec_version' => (string) $request->query->get(LegacyOpenApiNormalizer::SPEC_VERSION),
4848
];
4949
$request->attributes->set('_api_platform_disable_listeners', true);
50-
$operation = new Get(outputFormats: $this->documentationFormats, read: true, serialize: true, class: Entrypoint::class, provider: [self::class, 'provide']);
50+
$operation = new Get(
51+
outputFormats: $this->documentationFormats,
52+
read: true,
53+
serialize: true,
54+
class: Entrypoint::class,
55+
provider: [self::class, 'provide']
56+
);
5157
$request->attributes->set('_api_operation', $operation);
5258
$body = $this->provider->provide($operation, [], $context);
5359
$operation = $request->attributes->get('_api_operation');

src/Hydra/EventListener/AddLinkHeaderListener.php

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
/**
2626
* Adds the HTTP Link header pointing to the Hydra documentation.
2727
*
28+
* @deprecated use ApiPlatform\Hydra\State\HydraLinkProcessor instead
29+
*
2830
* @author Kévin Dunglas <[email protected]>
2931
*/
3032
final class AddLinkHeaderListener

src/State/Processor/SerializeProcessor.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@
3636
final class SerializeProcessor implements ProcessorInterface
3737
{
3838
/**
39-
* @param ProcessorInterface<T1, T2> $processor
39+
* @param ProcessorInterface<mixed, mixed>|null $processor
4040
*/
41-
public function __construct(private readonly ProcessorInterface $processor, private readonly SerializerInterface $serializer, private readonly SerializerContextBuilderInterface $serializerContextBuilder)
41+
public function __construct(private readonly ?ProcessorInterface $processor, private readonly SerializerInterface $serializer, private readonly SerializerContextBuilderInterface $serializerContextBuilder)
4242
{
4343
}
4444

4545
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
4646
{
4747
if ($data instanceof Response || !$operation->canSerialize() || !($request = $context['request'] ?? null)) {
48-
return $this->processor->process($data, $operation, $uriVariables, $context);
48+
return $this->processor ? $this->processor->process($data, $operation, $uriVariables, $context) : $data;
4949
}
5050

5151
// @see ApiPlatform\State\Processor\RespondProcessor
@@ -59,7 +59,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
5959
$serializerContext['uri_variables'] = $uriVariables;
6060

6161
if (isset($serializerContext['output']) && \array_key_exists('class', $serializerContext['output']) && null === $serializerContext['output']['class']) {
62-
return $this->processor->process(null, $operation, $uriVariables, $context);
62+
return $this->processor ? $this->processor->process(null, $operation, $uriVariables, $context) : null;
6363
}
6464

6565
$resources = new ResourceList();
@@ -80,6 +80,6 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
8080
$request->attributes->set('_api_platform_links', $linkProvider);
8181
}
8282

83-
return $this->processor->process($serialized, $operation, $uriVariables, $context);
83+
return $this->processor ? $this->processor->process($serialized, $operation, $uriVariables, $context) : $serialized;
8484
}
8585
}

src/State/Processor/WriteProcessor.php

+7-5
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ final class WriteProcessor implements ProcessorInterface
3434
use ClassInfoTrait;
3535

3636
/**
37-
* @param ProcessorInterface<T1, T2> $processor
38-
* @param ProcessorInterface<T1, T2> $callableProcessor
37+
* @param ProcessorInterface<mixed, mixed> $processor
38+
* @param ProcessorInterface<mixed, mixed> $callableProcessor
3939
*/
40-
public function __construct(private readonly ProcessorInterface $processor, private readonly ProcessorInterface $callableProcessor)
40+
public function __construct(private readonly ?ProcessorInterface $processor, private readonly ProcessorInterface $callableProcessor)
4141
{
4242
}
4343

@@ -48,9 +48,11 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
4848
|| !($operation->canWrite() ?? true)
4949
|| !$operation->getProcessor()
5050
) {
51-
return $this->processor->process($data, $operation, $uriVariables, $context);
51+
return $this->processor ? $this->processor->process($data, $operation, $uriVariables, $context) : $data;
5252
}
5353

54-
return $this->processor->process($this->callableProcessor->process($data, $operation, $uriVariables, $context), $operation, $uriVariables, $context);
54+
$data = $this->callableProcessor->process($data, $operation, $uriVariables, $context);
55+
56+
return $this->processor ? $this->processor->process($data, $operation, $uriVariables, $context) : $data;
5557
}
5658
}

src/State/Provider/ContentNegotiationProvider.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ final class ContentNegotiationProvider implements ProviderInterface
3030
* @param array<string, string[]> $formats
3131
* @param array<string, string[]> $errorFormats
3232
*/
33-
public function __construct(private readonly ProviderInterface $decorated, Negotiator $negotiator = null, private readonly array $formats = [], private readonly array $errorFormats = [])
33+
public function __construct(private readonly ?ProviderInterface $decorated = null, Negotiator $negotiator = null, private readonly array $formats = [], private readonly array $errorFormats = [])
3434
{
3535
$this->negotiator = $negotiator ?? new Negotiator();
3636
}
3737

3838
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
3939
{
4040
if (!($request = $context['request'] ?? null) || !$operation instanceof HttpOperation) {
41-
return $this->decorated->provide($operation, $uriVariables, $context);
41+
return $this->decorated?->provide($operation, $uriVariables, $context);
4242
}
4343

4444
$isErrorOperation = $operation instanceof ErrorOperation;
@@ -53,7 +53,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5353
$request->setRequestFormat($this->getRequestFormat($request, $formats, false));
5454
}
5555

56-
return $this->decorated->provide($operation, $uriVariables, $context);
56+
return $this->decorated?->provide($operation, $uriVariables, $context);
5757
}
5858

5959
/**

src/State/Provider/DeserializeProvider.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
final class DeserializeProvider implements ProviderInterface
3434
{
35-
public function __construct(private readonly ProviderInterface $decorated, private readonly SerializerInterface $serializer, private readonly SerializerContextBuilderInterface $serializerContextBuilder, private ?TranslatorInterface $translator = null)
35+
public function __construct(private readonly ?ProviderInterface $decorated, private readonly SerializerInterface $serializer, private readonly SerializerContextBuilderInterface $serializerContextBuilder, private ?TranslatorInterface $translator = null)
3636
{
3737
if (null === $this->translator) {
3838
$this->translator = new class() implements TranslatorInterface, LocaleAwareInterface {
@@ -44,13 +44,13 @@ public function __construct(private readonly ProviderInterface $decorated, priva
4444

4545
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
4646
{
47-
$data = $this->decorated->provide($operation, $uriVariables, $context);
48-
4947
// We need request content
5048
if (!$operation instanceof HttpOperation || !($request = $context['request'] ?? null)) {
51-
return $data;
49+
return $this->decorated?->provide($operation, $uriVariables, $context);
5250
}
5351

52+
$data = $this->decorated ? $this->decorated->provide($operation, $uriVariables, $context) : $request->attributes->get('data');
53+
5454
if (!$operation->canDeserialize()) {
5555
return $data;
5656
}

0 commit comments

Comments
 (0)