tonybogdanov/mockable-annotations

Mockable Doctrine 注解

v1.2 2020-05-12 13:32 UTC

This package is auto-updated.

Last update: 2024-09-03 21:07:40 UTC


README

build coverage

介绍

Doctrine Annotations 是一个强大的工具,允许以干净、语义化和可预测的方式为类、方法和属性指定所有类型的元信息。

默认注解读取器中缺少的一个特性是可模拟性,或是在运行时注入注解的能力。
当被注解的类是第三方库的一部分,而你无法控制它时,这特别有用。在这种情况下,默认的策略可能是扩展或重写整个类,但这并不总是可行,也肯定不是可维护的。

此包引入了一个可模拟的注解读取器,支持多种方式来“指导”它如何读取注解。它支持重写和合并类、方法和属性级别的注解,并且还公开了大量接口,以便你引入自己的功能。

它作为一个包装器来包装你的选择注解读取器实例,因此你仍然可以以最小的努力从中受益(例如缓存)。

安装

composer require tonybogdanov/mockable-annotations

* 默认情况下,读取器仅依赖于 doctrine/annotations 包,所以如果你打算使用 Doctrine 的缓存读取器,请确保也将 doctrine/cache 添加到你的依赖项中。

提供者

包含的 MockableAnnotationReader 使用注解提供者模拟类、方法级别和属性级别的注解。使用任何 [clear|get|set|add][Class|Method|Property]MockProvider(s) 方法来注册你的提供者。这些方法期望实例化相应的接口,因此如果你需要,你甚至可以构建自己的提供者。

该包提供了 3 个默认提供者:ClassMockProviderMethodMockProviderPropertyMockProvider,支持覆盖和合并策略,可选过滤和优先级(优先级较高的值将在最后执行)。

示例

以下类定义

/**
 * @TestAnnotation("hello") 
 */
class TestClass {}

可以模拟其类注解,因此读取器实际上看到的是以下内容

/**
 * @AnotherTestAnnotation("world") 
 */
class TestClass {}

使用以下示例代码

$newAnnotation = new AnotherTestAnnotation();
$newAnnotation->value = "world";

$reader = new MockableAnnotationReader( new AnnotationReader() );
$reader->addClassMockProvider( new ClassMockProvider(
                               
   [ $newAnnotation ],
   new OverrideStrategy(),
   new ClassNameFilter( TestClass::class )

) );

$reader->getClassAnnotations( TestClass::class );
// ...

这里我们使用的是 OverrideStrategy,它忽略原始注解并使用传递给 ClassMockProvider 的自定义注解替换它们。您也可以使用 MergeStrategy,它将合并原始注解与新的注解。当然,您也可以实现并传递自己的策略。

此外,我们使用了一个 ClassNameFilter,这样提供者只有在读取的类是 TestClass 的实例时才会应用。

* 如果您需要过滤器检查被读取的类是否是 TestClass精确实例 而不是继承自 TestClass 的类的实例,可以将 true 作为第二个参数传递给 ClassNameFilter 过滤器。

模拟方法和属性遵循相同的原理,具有非常相似的 API。
请参阅源代码以获取参考。

便利方法

注册提供者可能是一个相当冗长的任务,因此对于大多数常见场景,读取器公开了一些便利方法。

  • [覆盖|合并][类|方法|属性]注解 - 接受一个类名、(一个方法或属性名)、一个注解数组和一个可选的优先级,注册一个简单的类/方法/属性注解提供者,仅当正在读取的类/方法/属性与指定的相匹配时适用。

  • [覆盖|合并]别名[类|方法|属性]注解 - 接受两个类名对(以及方法或属性名),其中第一对目标为正在读取的类/方法/属性,第二对目标为相应类/方法/属性的注解将覆盖/合并到源注解中。

    这在您不想以编程方式指定注解并希望从“别名”或“哑”类/方法/属性中获取注解时非常有用。

  • [覆盖|合并]别名注解 - 可能是最方便的,此方法接受源类名和目标类名,然后将目标类中所有类/方法 & 属性注解覆盖/合并到源类中。

    请注意,只有源类中的方法和属性将被检查并与目标类匹配。目标(别名)类中的注解(源类中不存在),将被忽略。

模拟与继承

设计上,Doctrine 注解是不可继承的。这意味着如果您有类 A 继承自类 B,并在 B 上定义了一些类级注解,那么检索 A 的注解时将不会包括它们。

* 请注意,上述内容仅适用于注解。父类的特性和属性仍然可见,并且其注解将在子类中进行检查。

上述内容反映到模拟中,意味着您理论上需要单独模拟子类和父类,因为读取器会分别遍历它们。

MockableReader 可以帮助在一定程度上缓解这个问题,因为它尊重继承。当您模拟类 B 时,当读取器检查从 B 继承的任何类时,所有类级注解都将应用(除非在 ClassNameFilter 上将 strict 标志设置为 TRUE),即使您没有明确模拟它。

如果您确实明确模拟了它,那么继承的注解将被忽略。