From e13d5e7f806bd91bab5c93010b51f28765a63f34 Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 2 Jun 2026 13:50:07 +0200 Subject: [PATCH] fix(metadata): apply YAML/XML attrs to virtual properties ExtractorPropertyMetadataFactory short-circuited to handleNotFound() when the resource class did not declare a PHP property matching the requested name. YAML and XML mappings are free to declare virtual properties backed by methods (e.g. `admin` mapped to isAdmin()), and ExtractorPropertyNameCollection- Factory already exposes them, so the guard silently dropped configured metadata (security expressions, descriptions, etc.). Remove the property_exists/interface_exists clause: the remaining `extractor->getProperties()[$resourceClass][$property] ?? null` already returns null when the property is not in the extracted mapping, falling through to handleNotFound(). Closes #8178 --- .../ExtractorPropertyMetadataFactory.php | 5 +-- .../ExtractorPropertyMetadataFactoryTest.php | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/Metadata/Tests/Property/Factory/ExtractorPropertyMetadataFactoryTest.php diff --git a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php index e8e13f2256a..dad89450030 100644 --- a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php @@ -50,10 +50,7 @@ public function create(string $resourceClass, string $property, array $options = } } - if ( - !property_exists($resourceClass, $property) && !interface_exists($resourceClass) - || null === ($propertyMetadata = $this->extractor->getProperties()[$resourceClass][$property] ?? null) - ) { + if (null === ($propertyMetadata = $this->extractor->getProperties()[$resourceClass][$property] ?? null)) { return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property); } diff --git a/src/Metadata/Tests/Property/Factory/ExtractorPropertyMetadataFactoryTest.php b/src/Metadata/Tests/Property/Factory/ExtractorPropertyMetadataFactoryTest.php new file mode 100644 index 00000000000..ce35b84c66b --- /dev/null +++ b/src/Metadata/Tests/Property/Factory/ExtractorPropertyMetadataFactoryTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata\Tests\Property\Factory; + +use ApiPlatform\Metadata\Extractor\PropertyExtractorInterface; +use ApiPlatform\Metadata\Property\Factory\ExtractorPropertyMetadataFactory; +use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Comment; +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; + +final class ExtractorPropertyMetadataFactoryTest extends TestCase +{ + use ProphecyTrait; + + public function testCreateVirtualPropertyFromExtractor(): void + { + $extractorProphecy = $this->prophesize(PropertyExtractorInterface::class); + $extractorProphecy->getProperties()->willReturn([ + Comment::class => [ + 'admin' => [ + 'security' => 'user.isAdmin() === true', + ], + ], + ]); + + $factory = new ExtractorPropertyMetadataFactory($extractorProphecy->reveal()); + $metadata = $factory->create(Comment::class, 'admin'); + + self::assertSame('user.isAdmin() === true', $metadata->getSecurity()); + } +}