Skip to content

Commit 4ddff9b

Browse files
committed
fix(symfony): isolate api_platform.property_info tags
Rename extractor tags from `property_info.*` to `api_platform.property_info.*` so registrations do not leak into Symfony's framework `property_info` service. The shared tag caused `PhpStanExtractor` to be injected into the validator's property_info chain, crashing on `@template T of object` Doctrine annotations: Cannot create union with both "object" and class type. Restores the design intent of #7969 (two independent services). Fixes #8201
1 parent 8c95af6 commit 4ddff9b

2 files changed

Lines changed: 44 additions & 12 deletions

File tree

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,29 +77,29 @@
7777
$services->alias('api_platform.property_accessor', 'property_accessor');
7878

7979
$services->set('api_platform.property_info.reflection_extractor', ReflectionExtractor::class)
80-
->tag('property_info.list_extractor', ['priority' => -1000])
81-
->tag('property_info.type_extractor', ['priority' => -1002])
82-
->tag('property_info.access_extractor', ['priority' => -1000])
83-
->tag('property_info.initializable_extractor', ['priority' => -1000]);
80+
->tag('api_platform.property_info.list_extractor', ['priority' => -1000])
81+
->tag('api_platform.property_info.type_extractor', ['priority' => -1002])
82+
->tag('api_platform.property_info.access_extractor', ['priority' => -1000])
83+
->tag('api_platform.property_info.initializable_extractor', ['priority' => -1000]);
8484

8585
if (class_exists(DocBlockFactory::class)) {
8686
$services->set('api_platform.property_info.php_doc_extractor', PhpDocExtractor::class)
87-
->tag('property_info.description_extractor', ['priority' => -1000])
88-
->tag('property_info.type_extractor', ['priority' => -1001]);
87+
->tag('api_platform.property_info.description_extractor', ['priority' => -1000])
88+
->tag('api_platform.property_info.type_extractor', ['priority' => -1001]);
8989
}
9090

9191
if (class_exists(PhpDocParser::class) && class_exists(ContextFactory::class)) {
9292
$services->set('api_platform.property_info.phpstan_extractor', PhpStanExtractor::class)
93-
->tag('property_info.type_extractor', ['priority' => -1000]);
93+
->tag('api_platform.property_info.type_extractor', ['priority' => -1000]);
9494
}
9595

9696
$services->set('api_platform.property_info', PropertyInfoExtractor::class)
9797
->args([
98-
tagged_iterator('property_info.list_extractor'),
99-
tagged_iterator('property_info.type_extractor'),
100-
tagged_iterator('property_info.description_extractor'),
101-
tagged_iterator('property_info.access_extractor'),
102-
tagged_iterator('property_info.initializable_extractor'),
98+
tagged_iterator('api_platform.property_info.list_extractor'),
99+
tagged_iterator('api_platform.property_info.type_extractor'),
100+
tagged_iterator('api_platform.property_info.description_extractor'),
101+
tagged_iterator('api_platform.property_info.access_extractor'),
102+
tagged_iterator('api_platform.property_info.initializable_extractor'),
103103
]);
104104

105105
$services->set('api_platform.property_info.cache', PropertyInfoCacheExtractor::class)

src/Symfony/Tests/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,36 @@ public function testPaginationMaximumItemsPerPageWhenDefaultsKeyIsMissing(): voi
412412
$this->assertTrue($this->container->hasParameter('api_platform.collection.pagination.maximum_items_per_page'));
413413
$this->assertSame(30, $this->container->getParameter('api_platform.collection.pagination.maximum_items_per_page'));
414414
}
415+
416+
/**
417+
* @see https://github.com/api-platform/core/issues/8201
418+
*/
419+
public function testPropertyInfoExtractorsDoNotLeakIntoFrameworkPropertyInfo(): void
420+
{
421+
$config = self::DEFAULT_CONFIG;
422+
(new ApiPlatformExtension())->load($config, $this->container);
423+
424+
$services = ['api_platform.property_info.reflection_extractor'];
425+
if (class_exists(\phpDocumentor\Reflection\DocBlockFactory::class)) {
426+
$services[] = 'api_platform.property_info.php_doc_extractor';
427+
}
428+
if (class_exists(\PHPStan\PhpDocParser\Parser\PhpDocParser::class) && class_exists(\phpDocumentor\Reflection\Types\ContextFactory::class)) {
429+
$services[] = 'api_platform.property_info.phpstan_extractor';
430+
}
431+
432+
foreach ($services as $service) {
433+
$this->assertContainerHasService($service);
434+
$tags = $this->container->getDefinition($service)->getTags();
435+
foreach ($tags as $name => $_) {
436+
$this->assertStringStartsNotWith('property_info.', $name, \sprintf('Service "%s" must not use the global "property_info.*" tag namespace (leaks into Symfony\'s property_info and breaks the validator chain — issue #8201). Found tag "%s".', $service, $name));
437+
}
438+
}
439+
440+
$apiPlatformPropertyInfo = $this->container->getDefinition('api_platform.property_info');
441+
foreach ($apiPlatformPropertyInfo->getArguments() as $arg) {
442+
if ($arg instanceof \Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument) {
443+
$this->assertStringStartsWith('api_platform.property_info.', $arg->getTag(), \sprintf('api_platform.property_info must consume only "api_platform.property_info.*" private tags; found "%s".', $arg->getTag()));
444+
}
445+
}
446+
}
415447
}

0 commit comments

Comments
 (0)