symfony/type-info
Symfony TypeInfo extracts and normalizes PHP type information from reflections and type strings, with support for generics, nullables, enums, and collections. Resolve types via TypeResolver and work with a rich Type API for inspection and string casting.
## Getting Started
### Minimal Setup
1. **Installation** (unchanged):
```bash
composer require symfony/type-info
composer require phpstan/phpdoc-parser # Required for raw string resolution
First Use Case (updated for ObjectShapeType hardening):
Resolve a property type from a class with stricter shape validation:
use Symfony\Component\TypeInfo\TypeResolver;
$resolver = TypeResolver::create();
$type = $resolver->resolve(new \ReflectionProperty(User::class, 'address'));
echo (string) $type; // Outputs: "array{street?: string, city?: string, zip?: string|null}"
Key Entry Points (updated):
TypeResolver::create(): Now includes hardened ObjectShapeType validation.Type::arrayShape(): Explicitly define strict object-like shapes (e.g., array{key?: type}).(string) $type: Improved readability for nested shapes (e.g., array{items: list<int>}).$resolver = TypeResolver::create();
$shapeType = $resolver->resolve(new \ReflectionProperty(User::class, 'metadata'));
// Outputs: "array{theme?: string, preferences?: array{notifications?: bool}}"
$type = $resolver->resolve('array{id: int, name: string|null}');
$type->isArrayShape(); // true
$type->getShape()->get('name')->isNullable(); // true
Extend TypeResolver or chain resolvers:
use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface;
class CustomResolver implements TypeResolverInterface {
public function resolve($subject): Type {
if ($subject instanceof \ReflectionClass) {
return Type::object($subject->getName());
}
return TypeResolver::create()->resolve($subject);
}
}
// Strict shape definition
$shape = Type::arrayShape([
'id' => Type::int(),
'name' => Type::string(),
'metadata' => Type::nullable(Type::arrayShape([
'created_at' => Type::string(),
])),
]);
// Runtime resolution
$resolver = TypeResolver::create();
$type = $resolver->resolve($shape);
echo (string) $type;
// Outputs: "array{id: int, name: string, metadata?: array{created_at: string}}"
// Nullable types
$nullableInt = Type::nullable(Type::int());
// Lists/Arrays
$listOfStrings = Type::list(Type::string());
$arrayShape = Type::arrayShape(['id' => Type::int()]);
// Generics
$collectionType = Type::generic(Type::object(Collection::class), Type::int());
// Unions
$unionType = Type::union(Type::int(), Type::string());
use Illuminate\Validation\Rule;
use Symfony\Component\TypeInfo\TypeResolver;
class ShapeValidationRule extends Rule {
protected $expectedShape;
public function __construct(string $expectedShape) {
$this->expectedShape = $expectedShape;
$this->resolver = TypeResolver::create();
}
public function passes($attribute, $value) {
$actualType = $this->resolver->resolve($value);
$expectedType = $this->resolver->resolve($this->expectedShape);
return $expectedType->isSatisfiedBy(fn(Type $t) =>
$t->isArrayShape() &&
$expectedType->getShape()->equals($t->getShape())
);
}
}
use Illuminate\Foundation\Http\FormRequest;
class StoreUserRequest extends FormRequest {
public function rules() {
$resolver = TypeResolver::create();
$shapeType = $resolver->resolve('array{name: string, age?: int}');
return [
'user' => ['required', new ShapeValidationRule((string) $shapeType)],
];
}
}
use Symfony\Component\HttpFoundation\JsonResponse;
class ApiTransformer {
public function transform($data, string $expectedShape): array {
$resolver = TypeResolver::create();
$type = $resolver->resolve($expectedShape);
if (!$type->isArrayShape()) {
throw new \InvalidArgumentException("Expected array shape type");
}
return array_map(function($item) use ($type) {
return $this->castToShape($item, $type->getShape());
}, $data);
}
protected function castToShape($item, Shape $shape): array {
$result = [];
foreach ($shape->getKeys() as $key) {
$valueType = $shape->get($key);
$result[$key] = $valueType->isNullable()
? ($item[$key] ?? null)
: $item[$key];
}
return $result;
}
}
Leverage Symfony’s caching component for performance:
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\TypeInfo\TypeContextFactory;
$cache = new ArrayAdapter();
$contextFactory = new TypeContextFactory($cache);
$resolver = TypeResolver::create($contextFactory);
PHPDoc Parser Dependency (unchanged):
phpstan/phpdoc-parser. Install explicitly:
composer require phpstan/phpdoc-parser
Object Shape Validation Hardening (new):
ObjectShapeType now enforces exact key presence/absence. Optional keys must use ? syntax:
// Valid
$shape = Type::arrayShape(['name?' => Type::string()]);
// Invalid (throws error in v8.1.0+)
$shape = Type::arrayShape(['name' => Type::nullable(Type::string())]);
? syntax:
// Before (may fail)
$shape = Type::arrayShape(['age' => Type::nullable(Type::int())]);
// After (explicit)
$shape = Type::arrayShape(['age?' => Type::int()]);
Union Type Quirks (unchanged):
Type::nullable() for optional values.Template Type Resolution (unchanged):
$type = Type::generic(Type::object(Collection::class), Type::int());
Array vs. List (unchanged):
Type::list() for PHP 8.1+ typed arrays.Inspect Shape Structure:
Use (string) $type for detailed shape output:
$shape = Type::arrayShape(['user?' => Type::object(User::class)]);
echo (string) $shape; // Outputs: "array{user?: App\Models\User}"
Check Shape Satisfaction: Validate shapes against runtime data:
$type->isSatisfiedBy(fn(Type $t) =>
$t->isArrayShape() &&
$t->getShape()->hasKey('id') &&
$t->getShape()->get('id')->isIdentifiedBy(TypeIdentifier::INT)
);
Handle Edge Cases (updated):
hasKey() and get() with null checks:
if ($shape->hasKey('metadata') && $shape->get('metadata')->isNullable()) {
$metadataType = $shape->get('metadata')->getInnerType();
}
if ($type->isIdentifiedBy(TypeIdentifier::ARRAY) && !$type->isArrayShape()) {
// Handle non-shape arrays
}
Custom Shape Resolvers:
Implement TypeResolverInterface for domain-specific shape resolution:
class DatabaseShapeResolver implements TypeResolverInterface {
public function resolve($subject): Type {
if ($subject instanceof \ReflectionProperty) {
$shape = $this->fetchShapeFromDatabase($subject->getName());
return Type::arrayShape($shape);
}
return Type::mixed();
}
}
Type Aliases (unchanged): Register custom aliases via `TypeContextFactory
How can I help you explore Laravel packages today?