PHP项目Symfony property-info与类型

wen PHP项目 2

Symfony PropertyInfo 组件详解

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

PHP项目Symfony property-info与类型

基本安装

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;
    }
}

最佳实践

  1. 组合多个提取器:使用多个提取器可以获得更完整的类型信息
  2. 缓存类型信息:对于频繁使用的实体,缓存 type 信息提高性能
  3. 结合验证器:与 Symfony Validator 配合使用,实现更强大的数据验证
  4. 使用静态分析:结合 PHPStan 或 Psalm 进行代码分析

PropertyInfo 组件在表单构建、API 序列化、数据验证等场景都非常有用,能够显著提升代码的类型安全性。

抱歉,评论功能暂时关闭!