Skip to content

feat(metadata): allow extending the Link attribute - #8536

Merged
soyuka merged 1 commit into
api-platform:4.4from
soyuka:feat/8449-extendable-link
Sep 16, 2026
Merged

soyuka merged 1 commit into
api-platform:4.4from
soyuka:feat/8449-extendable-link

Conversation

@soyuka

@soyuka soyuka commented Sep 16, 2026

Copy link
Copy Markdown
Member
Q A
Branch? 4.4
Tickets Closes #8449
License MIT
Doc PR n/a

Link was the only final subclass of the abstract ParameterQueryParameter and HeaderParameter are both extendable. That prevents users from giving a recurring link shape a domain name:

#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)]
final class ProjectLink extends Link
{
    public function __construct(string $permission)
    {
        parent::__construct(
            fromClass: Project::class,
            toProperty: 'project',
            security: \sprintf('is_granted("%s", project)', $permission),
            securityObjectName: 'project',
        );
    }
}

// call site
uriVariables: ['projectId' => new ProjectLink('RULE_VIEW')]

A static factory is not a workaround: attribute arguments must be constant expressions, and while new has been allowed there since PHP 8.1, static calls are not.

Dropping final is safe — every wither on Link uses clone $this, so a subclass survives them, and consumers (LinksHandlerTrait, IdentifiersExtractor, UriVariablesConverter) only read it through its getters.

LinkFactory::createLinksFromAttributes() needed the matching half of the change: it looked up property attributes with getAttributes(Link::class), which filters on the exact class name, so a subclassed attribute placed on a property would have been silently ignored. It now passes \ReflectionAttribute::IS_INSTANCEOF. No class in the tree extends Link, so this is a no-op for existing code.

Covered by LinkFactoryTest::testCreateLinksFromAttributesWithExtendedLink, which asserts the subclass instance survives the factory and keeps the values its constructor preset.

Link was the only final subclass of Parameter, preventing users from
naming a recurring link shape as a domain attribute. Attribute arguments
must be constant expressions, so a static factory is not a workaround.

All Link withers use `clone $this`, so subclassing is safe. LinkFactory
now matches Link attributes with IS_INSTANCEOF, otherwise a subclassed
attribute on a property would be silently ignored.

Closes api-platform#8449
@soyuka
soyuka merged commit a1579ea into api-platform:4.4 Sep 16, 2026
117 checks passed
@soyuka
soyuka deleted the feat/8449-extendable-link branch September 16, 2026 08:53
soyuka added a commit that referenced this pull request Sep 16, 2026
The 4.4 branch reverts #8211 (constraint-aware 422 for denormalization
errors) so that 4.4.0 ships the 4.3 behaviour. That revert is deliberately
not propagated here: 5.0 keeps the feature.

Every file touched by the revert, by the ^4.4 constraint bump and by the
deprecation-message fix was resolved to main's version. Only #8536 and the
consolidated v4.4.0 changelog section come from 4.4.
@lyrixx

lyrixx commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants