Extend DenormalizerInterface.stub gate to cover Symfony 7.4+ (LSP errors on per-type denormalizer implementations)
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
Research direction
Start in src/Stubs/Symfony/StubFilesExtensionLoader.php:125 and inspect stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub. Adjust the Symfony Serializer version condition so the existing stub is loaded for the affected versions, then run the relevant extension tests to verify the stub is applied and the reported implementation errors are resolved.
Written by the indexing model from the issue text.
Description
Summary
Implementing DenormalizerInterface with a single concrete return type now triggers method.childReturnType (and, if narrowing @param to compensate, method.childParameterType) errors that have no clean PHPDoc resolution. The errors are caused by the conditional return type that has been part of Symfony's DenormalizerInterface since 7.4. phpstan-symfony already ships a stub that resolves this — it just isn't loaded for Symfony ≥ 7.4.
Environment
phpstan/phpstan: 2.1.56phpstan/phpstan-symfony: 2.0.18symfony/serializer: 8.0.10- PHP: 8.5
Reproduction
A typical per-type denormalizer:
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class DateRangeNormalizer implements DenormalizerInterface
{
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): DateRange
{
return new DateRange(/* ... */);
}
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
{
return $type === DateRange::class;
}
public function getSupportedTypes(?string $format): array
{
return [DateRange::class => true];
}
}
PHPStan (level 8) reports:
Return type (DateRange) of method ...DateRangeNormalizer::denormalize() should be covariant
with return type (($type is class-string<object> ? object : mixed)) of method
Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
identifier: method.childReturnType
The natural attempt to fix it — narrowing the parameter to bind TObject:
/** @param class-string<DateRange> $type */
public function denormalize(mixed $data, string $type, ...): DateRange
produces a second error:
Parameter #2 $type (class-string<DateRange>) of method ...::denormalize() should be
contravariant with parameter $type (string) of method DenormalizerInterface::denormalize()
identifier: method.childParameterType
The two requirements (bind TObject to satisfy covariance, keep param as wide as string for contravariance) are mutually exclusive in PHPStan's LSP model. There is no PHPDoc that satisfies both.
Root cause
Since Symfony 7.4, DenormalizerInterface::denormalize() is annotated:
/**
* @template TObject of object
* @param class-string<TObject>|string $type
* @return ($type is class-string<TObject> ? TObject : mixed)
*/
The conditional return is great for callers of Serializer::denormalize($data, Foo::class) (return resolves to Foo), but for implementations of the interface — which are per-type and rely on supportsDenormalization() for runtime dispatch — the conditional cannot be honored in general by a single concrete return type.
phpstan-symfony already has the right workaround: a stub at stubs/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub that flattens @return to mixed. That stub is loaded at src/Stubs/Symfony/StubFilesExtensionLoader.php:125:
if ($this->isInstalledVersionBelow('symfony/serializer', '7.4.0.0')) {
$files[] = $stubsDir . '/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub';
}
The conditional return type has been present in the source since 7.4, so the gate excludes exactly the versions where the stub is needed.
Proposed fix
Either remove the version gate entirely, or bump it to cover currently-supported Symfony versions (e.g. '9.0.0.0'):
- if ($this->isInstalledVersionBelow('symfony/serializer', '7.4.0.0')) {
+ if ($this->isInstalledVersionBelow('symfony/serializer', '9.0.0.0')) {
$files[] = $stubsDir . '/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.stub';
}
The existing stub content already does the right thing; it just isn't being applied.
Workaround in the meantime
Users can suppress the diagnostic per-method:
/** @phpstan-ignore method.childReturnType */
public function denormalize(...): DateRange
but that scales poorly across codebases with many normalizers.
- Dominant language
- PHP
- Stars
- 800
- Forks
- 104
- Avg merge
- 4m
- Merged PRs (30d)
- 1
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from phpstan/phpstan-symfony
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
phpstan/phpstan-symfony#510 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
phpstan/phpstan-symfony#268 ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
phpstan/phpstan-symfony#509 · 2 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 48/100
phpstan/phpstan-symfony#466 · 4 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
phpstan/phpstan-symfony#455 · 8 comments · 1 reaction ·
All issues in phpstan/phpstan-symfony
Similar issues
-
priority: p3
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
googleapis/librarian#7636 ·
-
0. Needs triage bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
nextcloud/fulltextsearch#1011 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
phpstan/phpstan-doctrine#794 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Automattic/static-site-importer#1767 ·