Skip to content

Allow extending the Link attribute (remove final) #8449

Description

@lyrixx

Context

We use subresource Links heavily to scope resources to a parent entity. In our app, ~30 operations repeat the exact same block, where the only thing that varies is the security attribute:

uriVariables: ['projectId' => new Link(
    fromClass: Project::class,
    toProperty: 'project',
    provider: ReadLinkParameterProvider::class,
    security: 'is_granted("RULE_VIEW", project)',
    securityObjectName: 'project',
)],

We'd like to factor that into a small domain-specific attribute:

final class ProjectLink extends Link
{
    public function __construct(string $permission)
    {
        parent::__construct(
            fromClass: Project::class,
            toProperty: 'project',
            provider: ReadLinkParameterProvider::class,
            security: sprintf('is_granted("%s", project)', $permission),
            securityObjectName: 'project',
        );
    }
}
// call site
uriVariables: ['projectId' => new ProjectLink('RULE_VIEW')],

This is not possible: Link is declared final (src/Metadata/Link.php:20).

Why a userland workaround does not apply

A static factory (ProjectLink::create('RULE_VIEW')) cannot help here — attribute arguments must be constant expressions, and while new has been allowed there since PHP 8.1, static method calls are not. Subclassing is the only way to give this shape a name.

Why this looks like an oversight rather than a design decision

Link is one of three subclasses of abstract class Parameter, and it is the only final one:

Class Final?
Parameter (abstract)
QueryParameter no
HeaderParameter no
Link yes

API Platform already lets users extend ApiResource and HttpOperation, which carry far more behavior than Link — a plain metadata value object with no logic beyond its constructor and withers. Making it non-final would not weaken any invariant: consumers (LinksHandlerTrait, IdentifiersExtractor, UriVariablesConverter) only read it through its getters.

Proposal

Drop final from Link, aligning it with its Parameter siblings.

Happy to send a PR if you are open to it.

(Observed on api-platform/core v4.3.17.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions