Skip to content

Commit c14b695

Browse files
committed
feat(symfony): request and view kernel listeners
1 parent a5d6003 commit c14b695

File tree

84 files changed

+2895
-782
lines changed

Some content is hidden

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

84 files changed

+2895
-782
lines changed

.github/workflows/ci.yml

+72-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ jobs:
266266
working-directory: src/${{ matrix.component }}
267267
run: |
268268
composer update
269-
vendor/bin/simple-phpunit --log-junit "build/logs/phpunit/junit.xml" ${{ matrix.coverage && '--coverage-clover build/logs/phpunit/clover.xml' || '' }}
269+
vendor/bin/phpunit --log-junit "build/logs/phpunit/junit.xml" ${{ matrix.coverage && '--coverage-clover build/logs/phpunit/clover.xml' || '' }}
270270
- name: Upload test artifacts
271271
if: always()
272272
uses: actions/upload-artifact@v3
@@ -1111,6 +1111,8 @@ jobs:
11111111
run: vendor/bin/simple-phpunit --version
11121112
- name: Clear test app cache
11131113
run: tests/Fixtures/app/console cache:clear --ansi
1114+
- name: Use legacy ignored deprecations
1115+
run: cp tests/.ignored-deprecations-legacy-events tests/.ignored-deprecations
11141116
- name: Run PHPUnit tests
11151117
run: |
11161118
mkdir -p build/logs/phpunit
@@ -1213,3 +1215,72 @@ jobs:
12131215
name: openapi-docs-php${{ matrix.php }}
12141216
path: build/out/openapi
12151217
continue-on-error: true
1218+
1219+
behat_listeners:
1220+
name: Behat event listeners (PHP ${{ matrix.php }})
1221+
env:
1222+
USE_SYMFONY_LISTENERS: 1
1223+
runs-on: ubuntu-latest
1224+
timeout-minutes: 20
1225+
strategy:
1226+
matrix:
1227+
php:
1228+
- '8.3'
1229+
fail-fast: false
1230+
steps:
1231+
- name: Checkout
1232+
uses: actions/checkout@v3
1233+
- name: Setup PHP
1234+
uses: shivammathur/setup-php@v2
1235+
with:
1236+
php-version: ${{ matrix.php }}
1237+
tools: pecl, composer
1238+
extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite
1239+
coverage: pcov
1240+
ini-values: memory_limit=-1
1241+
- name: Get composer cache directory
1242+
id: composercache
1243+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
1244+
- name: Cache dependencies
1245+
uses: actions/cache@v3
1246+
with:
1247+
path: ${{ steps.composercache.outputs.dir }}
1248+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
1249+
restore-keys: ${{ runner.os }}-composer-
1250+
- name: Update project dependencies
1251+
run: composer update --no-interaction --no-progress --ansi
1252+
- name: Install PHPUnit
1253+
run: vendor/bin/simple-phpunit --version
1254+
- name: Clear test app cache
1255+
run: tests/Fixtures/app/console cache:clear --ansi
1256+
- name: Run Behat tests (PHP 8)
1257+
run: |
1258+
mkdir -p build/logs/behat
1259+
vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=symfony_listeners --no-interaction
1260+
- name: Upload test artifacts
1261+
if: always()
1262+
uses: actions/upload-artifact@v3
1263+
with:
1264+
name: behat-logs-php${{ matrix.php }}
1265+
path: build/logs/behat
1266+
continue-on-error: true
1267+
- name: Export OpenAPI documents
1268+
run: |
1269+
mkdir -p build/out/openapi
1270+
tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json
1271+
tests/Fixtures/app/console api:openapi:export --yaml -o build/out/openapi/openapi_v3.yaml
1272+
- name: Setup node
1273+
uses: actions/setup-node@v3
1274+
with:
1275+
node-version: '14'
1276+
- name: Validate OpenAPI documents
1277+
run: |
1278+
npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.json
1279+
npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.yaml
1280+
- name: Upload OpenAPI artifacts
1281+
if: always()
1282+
uses: actions/upload-artifact@v3
1283+
with:
1284+
name: openapi-docs-php${{ matrix.php }}
1285+
path: build/out/openapi
1286+
continue-on-error: true

behat.yml.dist

+35-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ legacy:
203203
- 'Behat\MinkExtension\Context\MinkContext'
204204
- 'behatch:context:rest'
205205
filters:
206-
tags: '~@postgres&&~@mongodb&&~@elasticsearch'
206+
tags: '~@postgres&&~@mongodb&&~@elasticsearch&&~@mercure'
207207
extensions:
208208
'FriendsOfBehat\SymfonyExtension':
209209
bootstrap: 'tests/Fixtures/app/bootstrap.php'
@@ -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/DocumentationAction.php

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ private function getOpenApiDocumentation(array $context, string $format, Request
8585
$context['request'] = $request;
8686
$operation = new Get(
8787
class: OpenApi::class,
88+
validate: false,
8889
read: true,
8990
serialize: true,
9091
provider: fn () => $this->openApiFactory->__invoke($context),

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/Metadata/phpunit.xml.dist

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
</testsuite>
1919
</testsuites>
2020

21+
<listeners>
22+
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
23+
</listeners>
24+
2125
<coverage>
2226
<include>
2327
<directory>./</directory>

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)