Skip to content

Instantly share code, notes, and snippets.

@schmunk42
Created June 16, 2026 14:02
Show Gist options
  • Select an option

  • Save schmunk42/3357f3d0fdffe11e977812900355cefd to your computer and use it in GitHub Desktop.

Select an option

Save schmunk42/3357f3d0fdffe11e977812900355cefd to your computer and use it in GitHub Desktop.
api-platform/core 4.3: object|<Class> docblocks (DispatchTrait) break under symfony/type-info 7.4 — Cannot create union with both object and class type

api-platform/core 4.3 — object|<Class> docblocks break under symfony/type-info 7.4

api-platform/core 4.3 ships redundant union docblocks object|<Class> in its own source:

  • src/Symfony/Messenger/DispatchTrait.php:28@param object|Envelope $message
  • src/Doctrine/Common/Messenger/DispatchTrait.php:28@param object|Envelope $message

With symfony/type-info 7.4 installed, the PhpStanExtractor that API Platform wires as api_platform.property_info.php_stan_extractor throws while building the type graph when it parses such a docblock during cache:clear / metadata warmup:

Symfony\Component\TypeInfo\Exception\InvalidArgumentException:
Cannot create union with both "object" and class type.
  at symfony/type-info/Type/UnionType.php:75

object|Envelope is redundant — object already subsumes every class type. Legacy symfony/property-info Type tolerated it; symfony/type-info 7.4 rejects it hard.

PR #8206 (4.3.10) isolated the framework property_info service from API Platform's extractors, but API Platform's own extractor still parses these docblocks → same crash.

Reproduce

composer install
php repro.php

Expected output (bug reproduced, exit code 1):

PHP            : 8.4.22
api-platform/core: v4.3.13
symfony/type-info: v7.4.9
symfony/property-info: v7.4.8
------------------------------------------------------------
[KO]  Symfony\Component\TypeInfo\Exception\InvalidArgumentException: Cannot create union with both "object" and class type.
      thrown from: .../symfony/type-info/Type/UnionType.php:75

No app kernel required — repro.php invokes the failing extractor directly on a property whose docblock mirrors the DispatchTrait shape.

Suggested fix

Drop the redundant |Envelope from both DispatchTrait docblocks (the signature already declares object $message/: Envelope):

-    /**
-     * @param object|Envelope $message
-     */
     private function dispatch(object $message): Envelope
{
"require": {
"php": ">=8.4",
"api-platform/core": "4.3.13",
"symfony/property-info": "7.4.*",
"symfony/type-info": "7.4.9",
"phpstan/phpdoc-parser": "^2.0",
"phpdocumentor/type-resolver": "^1.8"
},
"config": {
"allow-plugins": {
"php-http/discovery": false
}
},
"minimum-stability": "stable"
}
<?php
declare(strict_types=1);
// Minimal reproduction for api-platform/core 4.3:
// `object|<Class>` docblocks (e.g. DispatchTrait's `@param object|Envelope`)
// break under symfony/type-info 7.4 with:
// "Cannot create union with both \"object\" and class type."
//
// Run:
// composer install
// php repro.php
// Exit code 1 + [KO] = bug reproduced.
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
final class Envelope
{
}
final class Subject
{
// Mirrors api-platform/core 4.3 DispatchTrait::dispatch():
// src/Symfony/Messenger/DispatchTrait.php:28 -> @param object|Envelope $message
// src/Doctrine/Common/Messenger/DispatchTrait.php:28
// `object|<Class>` is a redundant union — `object` already subsumes any class.
/** @var object|Envelope */
public mixed $message;
}
echo 'PHP : '.PHP_VERSION."\n";
foreach (['api-platform/core', 'symfony/type-info', 'symfony/property-info'] as $pkg) {
echo str_pad($pkg, 15).': '.\Composer\InstalledVersions::getPrettyVersion($pkg)."\n";
}
echo str_repeat('-', 60)."\n";
// Exactly the component api-platform wires as
// `api_platform.property_info.php_stan_extractor` and runs over resource
// classes during cache:clear / metadata warmup.
$extractor = new PhpStanExtractor();
try {
$type = $extractor->getType(Subject::class, 'message');
echo '[OK] extracted type: '.($type ?? 'null')."\n";
exit(0);
} catch (\Throwable $e) {
echo '[KO] '.$e::class.': '.$e->getMessage()."\n";
echo ' thrown from: '.$e->getFile().':'.$e->getLine()."\n";
exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment