objectiphy/annotations

独立的注释阅读器 - 与Doctrine兼容,但稍微简单一些。

1.0.20 2023-11-30 09:21 UTC

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许可下发布 - 请参阅附带的许可文件。