Symfony PropertyInfo 组件详解
PropertyInfo 是 Symfony 中用于获取类属性信息的强大组件,特别在类型推断方面非常实用。

基本安装
composer require symfony/property-info
核心功能
获取属性类型
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
// 创建提取器
$reflectionExtractor = new ReflectionExtractor();
$propertyInfo = new PropertyInfoExtractor(
[$reflectionExtractor], // 属性列表提取器
[$reflectionExtractor], // 类型提取器
[], // 可读性提取器
[], // 可写性提取器
[$reflectionExtractor] // 访问器提取器
);
// 获取属性类型
$types = $propertyInfo->getTypes(User::class, 'email');
foreach ($types as $type) {
echo $type->getBuiltinType(); // string
echo $type->isNullable(); // false
echo $type->getClassName(); // null for built-in types
}
结合 Doctrine 注解
// 安装 Doctrine 注解支持
composer require doctrine/annotations
// 获取 Doctrine 注解的实体信息
use Symfony\Component\PropertyInfo\Extractor\DoctrineExtractor;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
class UserService
{
private PropertyInfoExtractor $propertyInfo;
public function __construct(EntityManagerInterface $entityManager)
{
// Doctrine 提取器可以获取更多类型信息
$doctrineExtractor = new DoctrineExtractor($entityManager);
$this->propertyInfo = new PropertyInfoExtractor(
[$doctrineExtractor],
[$doctrineExtractor, new ReflectionExtractor()],
[$doctrineExtractor],
[$doctrineExtractor],
[$doctrineExtractor]
);
}
public function analyzeEntity(string $class): array
{
$properties = $this->propertyInfo->getProperties($class);
$info = [];
foreach ($properties as $property) {
$types = $this->propertyInfo->getTypes($class, $property);
$info[$property] = [
'types' => $types,
'readable' => $this->propertyInfo->isReadable($class, $property),
'writable' => $this->propertyInfo->isWritable($class, $property),
];
}
return $info;
}
}
高级用法示例
实体类型分析
// 示例实体
#[ORM\Entity]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column(type: 'json')]
private array $roles = [];
#[ORM\ManyToOne(targetEntity: Company::class)]
private ?Company $company = null;
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
// getters/setters...
}
// 类型分析服务
class TypeAnalyzer
{
private PropertyInfoExtractor $propertyInfo;
public function __construct(PropertyInfoExtractor $propertyInfo)
{
$this->propertyInfo = $propertyInfo;
}
public function getPropertyTypes(string $class, string $property): array
{
$types = $this->propertyInfo->getTypes($class, $property);
$result = [];
foreach ($types as $type) {
$typeInfo = [
'builtin_type' => $type->getBuiltinType(),
'nullable' => $type->isNullable(),
'class_name' => $type->getClassName(),
'collection' => $type->isCollection(),
];
// 处理集合类型
if ($type->isCollection() && $collectionKeyType = $type->getCollectionKeyType()) {
$typeInfo['collection_key_type'] = $collectionKeyType->getBuiltinType();
}
if ($type->isCollection() && $collectionValueType = $type->getCollectionValueType()) {
$typeInfo['collection_value_type'] = [
'builtin_type' => $collectionValueType->getBuiltinType(),
'class_name' => $collectionValueType->getClassName(),
];
}
$result[] = $typeInfo;
}
return $result;
}
public function getTypeCompatibility(string $class, string $property, mixed $value): bool
{
$types = $this->propertyInfo->getTypes($class, $property);
foreach ($types as $type) {
if ($type->isNullable() && $value === null) {
return true;
}
if ($this->isValueCompatible($type, $value)) {
return true;
}
}
return false;
}
private function isValueCompatible(Type $type, mixed $value): bool
{
$builtinType = $type->getBuiltinType();
return match ($builtinType) {
'string' => is_string($value),
'int' => is_int($value),
'float' => is_float($value),
'bool' => is_bool($value),
'array' => is_array($value),
'object' => $type->getClassName() ? is_a($value, $type->getClassName()) : is_object($value),
'resource' => is_resource($value),
default => false,
};
}
}
// 使用示例
$analyzer = new TypeAnalyzer($propertyInfo);
$types = $analyzer->getPropertyTypes(User::class, 'email');
// 输出: [['builtin_type' => 'string', 'nullable' => true, ...]]
$types = $analyzer->getPropertyTypes(User::class, 'company');
// 输出: [['builtin_type' => 'object', 'class_name' => 'App\Entity\Company', 'nullable' => true]]
在 API 平台中使用
// config/packages/api_platform.yaml
api_platform:
# 自动启用 property-info
defaults:
# ...
// 自定义序列化组
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']]
)]
class User
{
#[Groups(['user:read', 'user:write'])]
private string $email;
// ...
}
// 利用 property-info 进行字段验证
class UserValidator
{
private PropertyInfoExtractor $propertyInfo;
public function validateData(string $class, array $data): array
{
$errors = [];
foreach ($data as $property => $value) {
$types = $this->propertyInfo->getTypes($class, $property);
if (empty($types)) {
$errors[$property] = 'Unknown property';
continue;
}
if (!$this->isTypeValid($types, $value)) {
$errors[$property] = 'Invalid type for property';
}
}
return $errors;
}
private function isTypeValid(array $types, mixed $value): bool
{
foreach ($types as $type) {
// 检查 nullable
if ($type->isNullable() && $value === null) {
return true;
}
// 检查标量类型
if ($type->getBuiltinType() === gettype($value)) {
return true;
}
// 检查对象类型
if ($type->getBuiltinType() === 'object' && $type->getClassName()) {
if ($value instanceof $type->getClassName()) {
return true;
}
}
}
return false;
}
}
最佳实践
- 组合多个提取器:使用多个提取器可以获得更完整的类型信息
- 缓存类型信息:对于频繁使用的实体,缓存 type 信息提高性能
- 结合验证器:与 Symfony Validator 配合使用,实现更强大的数据验证
- 使用静态分析:结合 PHPStan 或 Psalm 进行代码分析
PropertyInfo 组件在表单构建、API 序列化、数据验证等场景都非常有用,能够显著提升代码的类型安全性。