diff --git a/src/Metadata/Link.php b/src/Metadata/Link.php index 478c1327b9b..2aa9b447f73 100644 --- a/src/Metadata/Link.php +++ b/src/Metadata/Link.php @@ -17,7 +17,7 @@ use Symfony\Component\TypeInfo\Type; #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)] -final class Link extends Parameter +class Link extends Parameter { public function __construct( private ?string $parameterName = null, diff --git a/src/Metadata/Resource/Factory/LinkFactory.php b/src/Metadata/Resource/Factory/LinkFactory.php index fb87e584b0a..43e111fe0b0 100644 --- a/src/Metadata/Resource/Factory/LinkFactory.php +++ b/src/Metadata/Resource/Factory/LinkFactory.php @@ -109,7 +109,7 @@ public function createLinksFromAttributes(Metadata $operation): array foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) { $reflectionProperty = $reflectionClass->getProperty($property); - foreach ($reflectionProperty->getAttributes(Link::class) as $attributeLink) { + foreach ($reflectionProperty->getAttributes(Link::class, \ReflectionAttribute::IS_INSTANCEOF) as $attributeLink) { $metadata = $this->propertyMetadataFactory->create($resourceClass, $property); $attributeLink = $attributeLink->newInstance() diff --git a/src/Metadata/Tests/Fixtures/ApiResource/ScopedLinkResource.php b/src/Metadata/Tests/Fixtures/ApiResource/ScopedLinkResource.php new file mode 100644 index 00000000000..69982db0981 --- /dev/null +++ b/src/Metadata/Tests/Fixtures/ApiResource/ScopedLinkResource.php @@ -0,0 +1,22 @@ + + * + * 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\Fixtures\ApiResource; + +use ApiPlatform\Metadata\Tests\Fixtures\Metadata\ScopedLink; + +final class ScopedLinkResource +{ + #[ScopedLink('RULE_VIEW')] + public ?Dummy $dummy = null; +} diff --git a/src/Metadata/Tests/Fixtures/Metadata/ScopedLink.php b/src/Metadata/Tests/Fixtures/Metadata/ScopedLink.php new file mode 100644 index 00000000000..e25e37e4ab3 --- /dev/null +++ b/src/Metadata/Tests/Fixtures/Metadata/ScopedLink.php @@ -0,0 +1,29 @@ + + * + * 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\Fixtures\Metadata; + +use ApiPlatform\Metadata\Link; + +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)] +final class ScopedLink extends Link +{ + public function __construct(string $permission) + { + parent::__construct( + parameterName: 'dummyId', + securityObjectName: 'dummy', + security: \sprintf('is_granted("%s", dummy)', $permission), + ); + } +} diff --git a/src/Metadata/Tests/Resource/Factory/LinkFactoryTest.php b/src/Metadata/Tests/Resource/Factory/LinkFactoryTest.php index 9c4c352139b..0097375e5f8 100644 --- a/src/Metadata/Tests/Resource/Factory/LinkFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/LinkFactoryTest.php @@ -25,6 +25,8 @@ use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\AttributeResource; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Dummy; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\RelatedDummy; +use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\ScopedLinkResource; +use ApiPlatform\Metadata\Tests\Fixtures\Metadata\ScopedLink; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -141,6 +143,24 @@ public function testCompleteLink(): void ); } + public function testCreateLinksFromAttributesWithExtendedLink(): void + { + $propertyNameCollectionFactory = new PropertyInfoPropertyNameCollectionFactory(new PropertyInfoExtractor([new ReflectionExtractor()])); + $propertyMetadataFactory = $this->createMock(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactory->method('create')->with(ScopedLinkResource::class, 'dummy')->willReturn((new ApiProperty())->withNativeType(Type::object(Dummy::class))); + $resourceClassResolver = $this->createStub(ResourceClassResolverInterface::class); + $linkFactory = new LinkFactory($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolver); + + $links = $linkFactory->createLinksFromAttributes((new Get())->withClass(ScopedLinkResource::class)); + + self::assertCount(1, $links); + self::assertInstanceOf(ScopedLink::class, $links[0]); + self::assertSame('dummy', $links[0]->getSecurityObjectName()); + self::assertSame('is_granted("RULE_VIEW", dummy)', $links[0]->getSecurity()); + self::assertSame('dummy', $links[0]->getFromProperty()); + self::assertSame(ScopedLinkResource::class, $links[0]->getFromClass()); + } + public function testCreateLinkFromProperty(): void { $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);