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.)
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:We'd like to factor that into a small domain-specific attribute:
This is not possible:
Linkis declaredfinal(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 whilenewhas 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
Linkis one of three subclasses ofabstract class Parameter, and it is the only final one:Parameter(abstract)QueryParameterHeaderParameterLinkAPI Platform already lets users extend
ApiResourceandHttpOperation, which carry far more behavior thanLink— 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
finalfromLink, aligning it with itsParametersiblings.Happy to send a PR if you are open to it.
(Observed on api-platform/core v4.3.17.)