symfony/property-info
Symfony PropertyInfo extracts metadata about PHP class properties (types, visibility, accessors) from multiple sources like reflection, PHPDoc, and serializers. Useful for building API docs, forms, validation, and other tooling that needs reliable property details.
Install via Composer:
composer require symfony/property-info
Extract property types from a class (e.g., for validation or serialization):
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
$extractors = [
new ReflectionExtractor(),
new PhpDocExtractor(),
];
$propertyInfoExtractor = new PropertyInfoExtractor($extractors);
// Get type for a property
$type = $propertyInfoExtractor->getTypes(User::class, 'email');
PropertyInfoExtractor (main class for combining multiple extractors).ReflectionExtractor, PhpDocExtractor, PhpStanExtractor, DoctrineExtractor).Use multiple extractors to handle different metadata sources (e.g., PHPDoc, reflection, PHPStan):
$extractors = [
new ReflectionExtractor(), // PHP 8 attributes + reflection
new PhpDocExtractor(), // DocBlock annotations
new PhpStanExtractor(), // PHPStan type hints (if installed)
new DoctrineExtractor(), // Doctrine ORM metadata
];
$propertyInfo = new PropertyInfoExtractor($extractors);
Resolve types at runtime (e.g., for form validation or API responses):
// Check if a property is writable
$isWritable = $propertyInfo->isWritable(User::class, 'email');
// Get read/write info (e.g., for serialization)
$readInfo = $propertyInfo->getReadInfo(User::class, 'email');
$writeInfo = $propertyInfo->getWriteInfo(User::class, 'email');
Use PropertyInfo to dynamically infer validation rules:
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
class StoreUserRequest extends FormRequest
{
public function rules()
{
$propertyInfo = app(PropertyInfoExtractorInterface::class);
$types = $propertyInfo->getTypes(User::class, 'email');
return [
'email' => ['required', 'email', ...$types],
];
}
}
Generate JSON:API schemas dynamically:
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
class UserResource extends JsonResource
{
public function toArray($request)
{
$propertyInfo = app(PropertyInfoExtractorInterface::class);
$types = $propertyInfo->getTypes(User::class);
return array_reduce($types, function ($carry, $type) {
$carry['attributes'][] = [
'name' => $type['property'],
'type' => $type['type'] ?? 'string',
];
return $carry;
}, ['data' => []]);
}
}
Generate form fields based on property types:
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
class UserCrudController extends Controller
{
public function edit(Request $request)
{
$propertyInfo = app(PropertyInfoExtractorInterface::class);
$types = $propertyInfo->getTypes(User::class);
$fields = collect($types)->mapWithKeys(function ($type) {
return [
$type['property'] => match ($type['type']) {
'string' => 'text',
'integer' => 'number',
'boolean' => 'checkbox',
default => 'text',
},
];
});
return view('user.edit', compact('fields'));
}
}
Extend for domain-specific metadata (e.g., custom attributes):
use Symfony\Component\PropertyInfo\Extractor\ExtractorInterface;
class CustomAttributeExtractor implements ExtractorInterface
{
public function getType(?ReflectionClass $class, ?string $property): ?Type
{
if (!$class || !$property) {
return null;
}
$reflectionProperty = $class->getProperty($property);
$attribute = $reflectionProperty->getAttribute(CustomType::class);
return $attribute ? new Type($attribute->value) : null;
}
// Implement other required methods (getTypes, isReadable, etc.)
}
Register the extractor:
$extractors[] = new CustomAttributeExtractor();
Extractor Order Matters
ReflectionExtractor) before fallbacks like PhpDocExtractor.ReflectionExtractor will override PhpDocExtractor if both return types.PHPStanExtractor Dependency
phpstan/phpstan (composer require phpstan/phpstan).try {
$propertyInfo->getTypes(User::class, 'email');
} catch (UnsupportedTypeException $e) {
// Fallback to other extractors
}
Promoted Properties in PHP 8+
PhpDocExtractor may misread promoted properties. Use ReflectionExtractor first:
$extractors = [new ReflectionExtractor(), new PhpDocExtractor()];
DocBlock Parsing Quirks
@var tags (e.g., #[param-type var="string"]) may not work. Use full DocBlock syntax:
/**
* @var string
*/
public $email;
scalar in DocBlocks (treated as object in older versions).Performance
$cache = new ArrayCache();
$propertyInfo = new PropertyInfoExtractor($extractors, $cache);
$types = $propertyInfo->getTypes(User::class, 'email');
dump($types); // Debug raw type data
phpstan analyse to catch type mismatches early.Custom Type Resolvers Override type resolution for specific classes:
$propertyInfo->addTypeResolver(new class implements TypeResolverInterface {
public function resolve(Type $type, ?ReflectionClass $class, ?string $property): ?Type
{
if ($class && $class->getName() === User::class && $property === 'email') {
return new Type('string|null');
}
return null;
}
});
Event Listeners
Extend PropertyInfoExtractor to log or modify metadata:
$propertyInfo->addListener(new class implements PropertyInfoListenerInterface {
public function onExtract(ExtractEvent $event)
{
if ($event->getProperty() === 'email') {
$event->setTypes([new Type('string')]);
}
}
});
Laravel Service Provider
Bind PropertyInfoExtractor in AppServiceProvider:
public function register()
{
$this->app->singleton(PropertyInfoExtractorInterface::class, function () {
return new PropertyInfoExtractor([
new ReflectionExtractor(),
new PhpDocExtractor(),
]);
});
}
symfony/property-info:^7.4 for PHP 8.1–8.3.composer require doctrine/orm).How can I help you explore Laravel packages today?