symfony/type-info
Symfony TypeInfo extracts and normalizes PHP type information. Resolve types from reflections or strings, build complex types via factories (nullable, list, generic, enum), cast to readable strings, and query identifiers/conditions for safer tooling and analysis.
Installation:
composer require symfony/type-info phpstan/phpdoc-parser
phpstan/phpdoc-parser is required for resolving raw string types (e.g., @var annotations).First Use Case: Resolve a property type from a reflection object:
use Symfony\Component\TypeInfo\TypeResolver;
$resolver = TypeResolver::create();
$type = $resolver->resolve(new \ReflectionProperty(User::class, 'email'));
echo (string) $type; // Outputs: "string|null"
Key Entry Points:
TypeResolver: Core class for resolving types from reflections or strings.Type factories: Static methods like Type::object(), Type::list(), or Type::nullable() to build types programmatically.isIdentifiedBy(), isSatisfiedBy(), or getClassName() to validate or extract type details.// Resolve a property type
$property = new \ReflectionProperty(User::class, 'createdAt');
$type = $resolver->resolve($property); // Returns `DateTimeInterface` Type
// Resolve a method parameter
$parameter = new \ReflectionParameter([UserRepository::class, 'find'], 0);
$type = $resolver->resolve($parameter); // Returns `int` Type
// Raw type strings (e.g., from PHPDoc)
$type = $resolver->resolve('array<int, string>'); // Returns `array<int, string>` Type
// PHPDoc annotations
$type = $resolver->resolve('@var \Illuminate\Support\Collection<int>'); // Returns `Collection<int>` Type
use Symfony\Component\TypeInfo\TypeContextFactory;
// Create a cached factory (persists resolved types)
$contextFactory = TypeContextFactory::create();
$context = $contextFactory->create();
$resolver = TypeResolver::create($context);
// Subsequent resolutions reuse cached results
$type1 = $resolver->resolve(new \ReflectionProperty(User::class, 'name'));
$type2 = $resolver->resolve(new \ReflectionProperty(User::class, 'name'));
// $type1 === $type2 (same instance due to caching)
// Nullable list of objects
$type = Type::list(Type::nullable(Type::object(User::class)));
// Generic collections
$type = Type::generic(Type::object(Collection::class), Type::int());
// Union types
$type = Type::union(Type::string(), Type::null());
use Symfony\Component\TypeInfo\TypeFactoryTrait;
// Define a sealed array shape
$shape = Type::arrayShape([
'id' => Type::int(),
'name' => Type::string(),
], sealed: true);
// Check if a type matches the shape
$shape->isSatisfiedBy(fn (Type $type) => $type->isArrayShape());
use Illuminate\Validation\Rule;
use Symfony\Component\TypeInfo\TypeResolver;
class CustomRule extends Rule
{
public function __construct(private TypeResolver $resolver)
{
}
public function passes($attribute, $value)
{
$type = $this->resolver->resolve(new \ReflectionProperty($this->getTargetClass(), $attribute));
return $type->isIdentifiedBy(TypeIdentifier::STRING);
}
}
use Symfony\Component\TypeInfo\Type;
use Zircote\Swagger\Scan\ClassAnalyzer;
class OpenApiGenerator
{
public function generateSchema(string $class): array
{
$reflection = new \ReflectionClass($class);
$properties = [];
foreach ($reflection->getProperties() as $property) {
$type = TypeResolver::create()->resolve($property);
$properties[$property->getName()] = (string) $type;
}
return ['properties' => $properties];
}
}
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\TypeInfo\TypeResolver;
class TypeAwareNormalizer implements ContextAwareNormalizerInterface
{
public function __construct(private TypeResolver $resolver)
{
}
public function normalize($object, string $format = null, array $context = [])
{
$reflection = new \ReflectionClass($object);
$data = [];
foreach ($reflection->getProperties() as $property) {
$type = $this->resolver->resolve($property);
$data[$property->getName()] = $this->normalizeValue($property->getValue($object), $type);
}
return $data;
}
}
// Resolve a PHPDoc type (e.g., from a method return)
$type = $resolver->resolve('/**
* @return \Illuminate\Database\Eloquent\Collection<int, \App\Models\User>
*/');
// Check if a type satisfies a condition (e.g., for validation)
$type->isSatisfiedBy(fn (Type $t) =>
$t->isIdentifiedBy(Collection::class) &&
$t->getGenericTypes()[0]->isIdentifiedBy(TypeIdentifier::INT)
);
Namespace Resolution Issues:
TypeContext includes all relevant autoload paths.TypeContextFactory::create() with custom paths:
$context = TypeContextFactory::create()
->addPaths([base_path('app')])
->create();
BackedEnum vs. Interface Confusion:
BackedEnum as enums.Type::enum() explicitly or update to v7.3.3+ (includes a fix for this).Union Type Merging:
Type::union(Type::int(), Type::null())).TypeFactoryTrait::union() carefully or normalize types first:
$union = Type::union(Type::int(), Type::null());
$normalized = $union->normalize(); // Ensures consistent structure
PHPDoc Parser Dependencies:
@var annotations, ensure phpstan/phpdoc-parser is installed and compatible with your PHP version.composer.json:
"require": {
"phpstan/phpdoc-parser": "^1.15"
}
Caching Overhead:
TypeContextFactory) improves performance but may consume memory for large codebases.$context = TypeContextFactory::create()->disableCache()->create();
Inspect Type Structures:
Use (string) $type to debug complex types:
$type = Type::generic(Type::object(Collection::class), Type::int());
echo (string) $type; // Outputs: "Collection<int>"
Check Type Identifiers:
Use isIdentifiedBy() to verify type matches:
$type->isIdentifiedBy(TypeIdentifier::OBJECT, User::class);
// or
$type->isIdentifiedBy('array');
Validate with isSatisfiedBy:
Custom predicates for complex conditions:
$type->isSatisfiedBy(fn (Type $t) =>
$t->isList() && $t->getInnerType()->isIdentifiedBy(TypeIdentifier::STRING)
);
Handle Generic Type Errors: If generics fail to resolve (e.g., missing type arguments), wrap in a fallback:
try {
$type = $resolver->resolve(new \ReflectionProperty($class, 'genericProp'));
} catch (\InvalidArgumentException $e) {
$type = Type::mixed(); // Fallback
}
TypeResolver to support domain-specific types (e.g., Laravel collections):
use Symfony\Component\TypeInfo\TypeResolverInterface;
class LaravelTypeResolver implements TypeResolverInterface
{
public function resolve($subject): Type
{
if ($subject instanceof \ReflectionProperty && $subject->getType()?->getName() === 'Illuminate\Support\Collection') {
return Type::generic(Type::object
How can I help you explore Laravel packages today?