objectiphy / annotations
独立的注释阅读器 - 与Doctrine兼容,但稍微简单一些。
Requires
- php: >=7.4
Requires (Dev)
- phpunit/phpunit: ^9.0
README
描述
一个独立的属性和注释阅读器,它可以读取属性并解析PHP文档注释中的注释。与Doctrine兼容,但不需要它,即它可以替代Doctrine注释阅读器,前提是你不需要在类上使用嵌套注释(属性和方法上的嵌套注释在文档块注释中是OK的,但不是属性)。不支持嵌套类注释,因为Objectiphy不需要它们,并且原生PHP 8属性不支持嵌套。
为什么不直接使用Doctrine呢?
没有理由!尽可以使用Doctrine - 它很棒。我写这个部分是为了学术练习,但也是为了让我有更多的自由去做我想做的事。在写作的时候,Doctrine让你跳过几个障碍,并且对未告知它的随机非标准注释不太宽容。我认为这更易于使用,并且应该与Doctrine一样表现良好。你可以用这个阅读器读取任何属性或注释(除了类上的嵌套注释)。
要求
Objectiphy Annotations需要PHP 7.4或更高版本。它没有其他依赖项。我选择PHP 7.4是因为那是初始写作时的最新版本,并且允许我使用属性上的类型提示,这是PHP的早期版本所不支持。它已被更新为读取PHP 8及更高版本的属性。
安装
您可以使用composer安装Objectiphy Annotations
composer require objectiphy/annotations
...或者直接git克隆或下载项目,并直接包含或使用PSR-4自动加载器。
基本用法
以下文档描述了文档块注释,但等效的PHP 8属性将以相同的方式工作。例如,一个注释可能看起来像这样
/** * @Mapping\Relationship( * childClassName="TestUser", * sourceJoinColumn="user_id", * relationshipType="one_to_one", * cascadeDeletes=true, * orphanRemoval=true * ) */
...等效的属性可能看起来像这样
#[Mapping\Relationship( childClassName: TestUser::class, sourceJoinColumn: 'user_id', relationshipType: 'one_to_one', cascadeDeletes: true, orphanRemoval: true )]
...以上两种都会被注释阅读器以完全相同的方式读取和返回。
假设你有一个带有属性注释的实体,如下所示
namespace MyNamespace; class MyEntity { /** @var MyEntity $childObject A child object of the same type as the parent. */ private MyEntity $childObject; }
你可以创建一个注释阅读器并像这样读取@var
注释(请注意,在大多数情况下,你应该使用依赖注入容器来创建阅读器而不是直接实例化它)
use Objectiphy\Annotations\AnnotationReader; use MyNamespace\MyEntity; $annotationReader = new AnnotationReader(); $annotation = $annotationReader->getAnnotationFromProperty(MyEntity::class, 'childObject', 'var'); echo "Name: " . $annotation->name . "\n"; echo "Type: " . $annotation->type . "\n"; echo "Variable: " . $annotation->variable . "\n"; echo "Comment: " . $annotation->comment;
上面的代码会输出
Name: var
Type: MyNamespace\MyEntity
Variable: $childObject
Comment: A child object of the same type as the parent.
请注意,类型已被解析为完全限定的类名。如果注释名称后面只有一个单词并且没有其他内容,或者如果注释名称后面跟一个单词,该单词以美元符号开头(这被认为是变量),阅读器将尝试解析此类通用的注释中的类名。
使用自定义注释类
你还可以使用自定义注释类,注释阅读器会尝试返回你的类的实例。你不需要告诉阅读器关于你的类,或注册任何命名空间,或在其上使用任何注释。
例如,如果你有一个具有强制构造函数参数、公共属性和具有getter和setter的受保护属性的类,如下所示
namespace MyNamespace\Annotations; class MyAnnotation { public string $childClassName; protected int $value = 100; private string $name; public function __construct(string $name) { $this->name = $name; } public function setValue(int $value): void { $this->value = $value; } public function getValue(): int { return $this->value; } public function setName(string $name): void { $this->name = $name; } public function getName(): string { return $this->name; } }
...你可以将其用作类、属性或方法的注释,如下所示
namespace MyNamespace\Entities; //You don't have to use an alias, this is just to demonstrate that you can: use MyNamespace\Annotations\MyAnnotation as AnnotationAlias; use MyNamespace\ValueObjects\OtherClass; class MyEntity2 { /** * @var OtherClass * @AnnotationAlias(name="nameValue", childClassNameName="OtherClass", value=200) */ public $childClassName; }
...然后使用注释阅读器将注释解析为你的自定义注释类的一个实例,如下所示
use Objectiphy\Annotations\AnnotationReader; use MyNamespace\Annotations\MyAnnotation; use MyNamespace\Entities\MyEntity2; $annotationReader = new AnnotationReader(); $annotationReader->setClassNameAttributes(['childClassName']); $annotation = $annotationReader->getAnnotationFromProperty(MyEntity2::class, 'childClassName', MyAnnotation::class); echo "Name: " . $annotation->getName() . "\n"; echo "Child Class Name: " . $annotation->childClassName . "\n"; echo "Value: " . $annotation->getValue();
...这将输出以下内容(请注意,因为我们告诉它childClassNameName
是一个类名属性,所以它将其解析为完全限定的类名)
Name: nameValue
Child Class Name: MyNamespace\ValueObjects\OtherClass
Value: 200
当填充对象时,注解读取器会检查是否存在必需的构造函数参数,并将任何匹配的值传递到构造函数中。然后,它会遍历所有定义的属性,如果存在匹配的属性名称,则会将该属性设置为属性的值(如果属性不是公共的并且存在以“set”为前缀的匹配名称的方法,则使用设置器)。
使用接口
注解读取器实现了AnnotationReaderInterface接口,如果存在Doctrine Reader接口,则扩展了Doctrine Reader接口。因此,您可以向任何需要Doctrine Reader接口的服务传递AnnotationReader的实例。
在您的代码中对注解读取器进行类型提示时,您应该始终提示AnnotationReaderInterface
(或Doctrine的Reader
)-不要提示AnnotationReader
本身。这允许您(例如)稍后替换具体的实现为缓存读取器(见下文的缓存部分)。
静默操作
由于没有规定如何将注解反序列化为对象,可能存在读取器无法创建预期对象的情况。默认情况下,这将以静默方式失败,除非它与Objectiphy注解相关(在这种情况下,我们知道规则是什么,所以异常是异常)。如果在静默模式下发生任何错误,则$lastErrorMessage
属性将被填充,并将返回值为null
,但不会抛出异常。
要为非Objectiphy注解抛出异常,只需在创建AnnotationReader实例时,将$throwExceptions
参数设置为true。要抑制Objectiphy注解的异常,将$throwExceptionsObjectiphy
标志设置为false。
缓存
您可以使用任何PSR-16兼容的缓存机制来缓存注解。使用缓存可以减少读取注解所需的处理量,这在AWS等可扩展环境中可能是一个重要的考虑因素,尽管读取和写入缓存仍涉及一些开销,这可能会抵消简单用例中的任何性能优势。
要使用缓存,只需实例化一个CachedAnnotationReader,并向它传递您的PSR-16缓存实例和一个标准的AnnotationReader实例。CachedAnnotationReader是标准AnnotationReader类的装饰器,并实现了相同的AnnotationReaderInterface。
致谢
由Russell Walker开发(rwalker.php@gmail.com)
许可协议
Objectiphy Annotations在MIT许可下发布 - 请参阅附带的许可文件。