PHP 中的特性(Attributes)是一种为类、属性、函数、常量和参数添加元数据的方式。这些元数据可用于提供关于元素的附加信息,或在特定情况下更改元素的行为。
spiral/attributes
组件提供了一种在 PHP 中使用特性的简单且一致的方式。
在 PHP 应用程序中使用特性的好处:
该组件还服务于两个非常重要的目的:
该组件提供了一个元数据读取器桥接器,允许在同一个项目中使用现代的 PHP 特性 和 Doctrine 注解。
注意: 本文档使用术语“元数据”来同时指代“特性”和“注解”。
要安装该组件:
composer require spiral/attributes
要启用该组件,你只需要将 Spiral\Bootloader\Attributes\AttributesBootloader
类添加到引导程序列表中:
public function defineBootloaders(): array
{
return [
// ...
\Spiral\Bootloader\Attributes\AttributesBootloader::class,
// ...
];
}
在 框架 — 引导程序 部分阅读关于引导程序的更多信息。
该组件提供了一组用于处理特性的接口和类。
Spiral\Attributes\ReaderInterface
提供了从类、属性、函数、常量和参数中读取特性的一组方法。
interface ReaderInterface
{
public function getClassMetadata(\ReflectionClass $class, string $name = null): iterable;
public function getPropertyMetadata(\ReflectionProperty $property, string $name = null): iterable;
public function getFunctionMetadata(\ReflectionFunctionAbstract $function, string $name = null): iterable;
public function getConstantMetadata(\ReflectionClassConstant $constant, string $name = null): iterable;
public function getParameterMetadata(\ReflectionParameter $parameter, string $name = null): iterable;
public function firstClassMetadata(\ReflectionClass $class, string $name): ?object;
public function firstPropertyMetadata(\ReflectionProperty $property, string $name): ?object;
public function firstFunctionMetadata(\ReflectionFunctionAbstract $function, string $name): ?object;
public function firstConstantMetadata(\ReflectionClassConstant $constant, string $name): ?object;
public function firstParameterMetadata(\ReflectionParameter $parameter, string $name): ?object;
}
要读取类元数据,请使用 $reader->getClassMetadata()
方法。它接收作为输入的所需类的 ReflectionClass
,并返回可用元数据对象的列表。
$reflection = new ReflectionClass(User::class);
$attributes = $reader->getClassMetadata($reflection);
// returns iterable<object>
该方法的第二个可选参数 $name
允许你指定要检索哪些特定的元数据对象。
$reflection = new ReflectionClass(User::class);
$attributes = $reader->getClassMetadata($reflection, Entity::class);
// returns iterable<Entity>
要获取一个元数据对象,你可以使用方法 $reader->firstClassMetadata()
。
$reflection = new ReflectionClass(User::class);
$attribute = $reader->firstClassMetadata($reflection, Entity::class);
// returns Entity|null
从 v2.10.0 版本开始,支持从类中使用的 traits 读取特性。
#[Cycle\Entity]
class Entity {
use TsTrait;
}
#[Behavior\CreatedAt]
#[Behavior\UpdatedAt]
trait TsTrait
{
#[Cycle\Column(type: 'datetime')]
private DateTimeImmutable $createdAt;
#[Cycle\Column(type: 'datetime', nullable: true)]
private ?DateTimeImmutable $updatedAt = null;
}
要读取属性元数据,请使用 $reader->getPropertyMetadata()
方法。它接收作为输入的所需属性的 ReflectionProperty
,并返回可用元数据对象的列表。
$reflection = new ReflectionProperty(User::class, 'name');
$attributes = $reader->getPropertyMetadata($reflection);
// returns iterable<object>
该方法的第二个可选参数 $name
允许你指定要检索哪些特定的元数据对象。
$reflection = new ReflectionProperty(User::class, 'name');
$attributes = $reader->getPropertyMetadata($reflection, Column::class);
// returns iterable<Column>
要获取一个元数据对象,你可以使用方法 $reader->firstPropertyMetadata()
。
$reflection = new ReflectionProperty(User::class, 'name');
$column = $reader->firstPropertyMetadata($reflection, Column::class);
// returns Column|null
要读取函数元数据,请使用 $reader->getFunctionMetadata()
方法。它接收一个 ReflectionFunction
或 ReflectionMethod
类型的参数,代表所需的函数,并返回可用元数据对象的列表。
$reflection = new ReflectionMethod(RequestData::class, 'getEmail');
$attributes = $reader->getFunctionMetadata($reflection);
// returns iterable<object>
该方法的第二个可选参数 $name
允许你指定要检索哪些特定的元数据对象。
$reflection = new ReflectionMethod(RequestData::class, 'getEmail');
$attributes = $reader->getPropertyMetadata($reflection, DTOGetter::class);
// returns iterable<DTOGetter>
要获取一个元数据对象,你可以使用方法 $reader->firstFunctionMetadata()
。
$reflection = new ReflectionMethod(RequestData::class, 'getEmail');
$getter = $reader->firstFunctionMetadata($reflection, DTOGetter::class);
// returns DTOGetter|null
要读取类常量元数据,请使用 $reader->getConstantMetadata()
方法。它接收一个 ReflectionClassConstant
类型的参数,代表所需的函数,并返回可用元数据对象的列表。
$reflection = new ReflectionClassConstant(Example::class, 'CONSTANT_NAME');
$attributes = $reader->getConstantMetadata($reflection);
// returns iterable<object>
该方法的第二个可选参数 $name
允许你指定要检索哪些特定的元数据对象。
$reflection = new ReflectionClassConstant(Example::class, 'CONSTANT_NAME');
$attributes = $reader->getConstantMetadata($reflection, Deprecated::class);
// returns iterable<Deprecated>
要获取一个元数据对象,你可以使用方法 $reader->firstConstantMetadata()
。
$reflection = new ReflectionClassConstant(Example::class, 'CONSTANT_NAME');
$getter = $reader->firstConstantMetadata($reflection, Deprecated::class);
// returns Deprecated|null
要读取函数/方法参数元数据,请使用 $reader->getParameterMetadata()
方法。它接收一个 ReflectionParameter
类型的参数,代表所需的函数,并返回可用元数据对象的列表。
$reflection = new ReflectionParameter('send_email', 'email');
$attributes = $reader->getParameterMetadata($reflection);
// returns iterable<object>
该方法的第二个可选参数 $name
允许你指定要检索哪些特定的元数据对象。
$reflection = new ReflectionParameter('send_email', 'email');
$attributes = $reader->getParameterMetadata($reflection, PreCondition::class);
// returns iterable<PreCondition>
要获取一个元数据对象,你可以使用方法 $reader->firstParameterMetadata()
。
$reflection = new ReflectionParameter('send_email', 'email');
$getter = $reader->firstConstantMetadata($reflection, PreCondition::class);
// returns PreCondition|null
注意 关于使用 doctrine 注解的详细信息,请参阅 doctrine 文档。
你应该使用“混合”语法来创建元数据类,以便在任何版本的 PHP 上都能使用。
/**
* @Annotation
* @Target({ "CLASS" })
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class MyEntityMetadata
{
public string $table;
}
在这种情况下,你可以在任何 PHP 版本上使用这个元数据类。
#[MyEntityMetadata(table: 'users')]
class User {}
该包支持不同的实例化特性方式,但默认情况下它使用 Doctrine 逻辑来实现兼容性。
假设你像这样使用你的元数据类,传递一个字段“property
”,其字符串值为“value
”。
#[CustomMetadataClass(property: "value")]
class AnnotatedClass
{
}
在这种情况下,注解类本身可能如下所示:
在这种情况下,当声明一个元数据类时,特性/注解属性将被填充。
注意 另请参阅 Doctrine 自定义注解
/** @Annotation */
#[\Attribute]
class CustomMetadataClass
{
public $property;
}
在构造函数声明的情况下,使用元数据类的所有数据都将作为数组传递给此构造函数。
注意 另请参阅 Doctrine 自定义注解
/** @Annotation */
#[\Attribute]
class CustomMetadataClass
{
public function __construct(array $properties)
{
// $properties = [ "property" => "value" ]
}
}
如果你想使用命名构造函数参数(另请参阅
PHP 手册 — 命名参数),
那么你必须将接口 Spiral\Attributes\NamedArgumentConstructorAttribute
添加到元数据类中,该接口将标记所需的元数据类为一个接受命名参数的类。
/** @Annotation */
#[\Attribute]
class CustomMetadataClass implements \Spiral\Attributes\NamedArgumentConstructorAttribute
{
public function __construct($property)
{
// $property = "value"
}
}
请注意,使用先前的方法将需要你拥有一个 spiral/attributes
包,并且你将无法在缺少此包的其他项目中使用这些类。
为了解决这个问题,你可以使用元数据类,这将意味着相同的事情(元数据类使用命名参数),但不会直接实现接口,因此不需要项目中的 spiral/attributes
包。
/**
* @Annotation
* @Spiral\Attributes\NamedArgumentConstructor
*/
#[\Attribute]
#[\Spiral\Attributes\NamedArgumentConstructor]
class CustomMetadataClass
{
public function __construct($property)
{
// $property = "value"
}
}
Spiral\Attributes\Factory
封装了几个实现,并且默认情况下返回一个 选择性读取器 实现,该实现适用于大多数情况。但是,如果你的平台和/或应用程序上提供,你可以要求一个特定的实现。
use Spiral\Attributes\Factory;
$reader = (new Factory())->create();
注意 请注意,为了使此读取器在应用程序中可用,你需要引入 "doctrine/annotations" 组件。
此读取器实现允许读取 doctrine 注解。
/** @ExampleAnnotation */
class Example {}
$reader = new \Spiral\Attributes\AnnotationReader();
$annotations = $reader->getClassMetadata(new ReflectionClass(Example::class));
// returns iterable<ExampleAnnotation>
此读取器实现允许在任何 PHP 版本上读取原生 PHP 特性。
#[ExampleAttribute]
class Example {}
$reader = new \Spiral\Attributes\AttributeReader();
$attributes = $reader->getClassMetadata(new ReflectionClass(Example::class));
// returns iterable<ExampleAttribute>
该实现会自动根据应用程序中使用的语法选择正确的读取器。如果同时在同一个项目中使用两种语法,则需要此行为。例如,在已经运行的项目中,将注解语法转换为现代的特性语法。
#[ExampleAttribute]
class ClassWithAttributes {}
$reader = new \Spiral\Attributes\Composite\SelectiveReader([
new \Spiral\Attributes\AnnotationReader(),
new \Spiral\Attributes\AttributeReader(),
]);
$annotations = $reader->getClassMetadata(new ReflectionClass(ClassWithAnnotations::class));
// returns iterable<ExampleAnnotation>
$attributes = $reader->getClassMetadata(new ReflectionClass(ClassWithAttributes::class));
// returns iterable<ExampleAttribute>
注意 当在同一位置使用注解和特性时,此读取器的行为是不确定的。
该读取器的实现将几种语法合并为一个。如果你同时使用多个库,并且这些库只支持旧语法或新语法,则需要此行为。
/** @DoctrineAnnotation */
#[NativeAttribute]
class ExampleClass {}
$reader = new \Spiral\Attributes\Composite\MergeReader([
new \Spiral\Attributes\AnnotationReader(),
new \Spiral\Attributes\AttributeReader(),
]);
$metadata = $reader->getClassMetadata(new ReflectionClass(ExampleClass::class));
// returns iterable { DoctrineAnnotation, NativeAttribute }
某些实现可能会在某种程度上减慢速度,因为它们会从头开始读取元数据。当有大量此类数据时,尤其如此。
为了优化和加速读取器的工作,建议使用缓存。特性包支持 PSR-6 和 PSR-16 规范。要创建它们,你需要使用相应的类。
use Spiral\Attributes\Psr6CachedReader;
use Spiral\Attributes\Psr16CachedReader;
use Spiral\Attributes\AttributeReader;
$psr6reader = new Psr6CachedReader(
new AttributeReader(),
new SomePsr6CacheImplementation() // Any PSR-6 cache implementation
);
$psr16reader = new Psr16CachedReader(
new AttributeReader(),
new SomePsr6CacheImplementation() // Any PSR-16 cache implementation
);
你还可以将缓存实现实例传递给工厂类。
use Spiral\Attributes\Factory;
$reader = (new Factory)
->withCache($cacheDriver)
->create();
// Where $cacheDriver is PSR-6 or PSR-16 cache driver implementation