我想创建一个类型安全的通用日志装饰器。
我有很多存储库(接口),每个存储库都需要一个decorator来捕获它们可能引发的异常,将这些异常传递给LoggerInterface的实例,然后重新抛出它们。可以创建一个专用的装饰器并为每个装饰器进行测试,尽管这很麻烦(尤其是测试做得很好),而且我宁愿避免这样做。
首先想到的方法是使用__call
创建一个更通用的装饰器。然而,这会导致对象实例无法实现它所装饰的存储库接口。在我的项目中,这是不可行的。有没有什么方法可以告诉PHP它确实实现了这个接口,比如使用一些神奇的反射?
我在stackoverflow上读过"如何在PHP中实现装饰器?"answers"在PHP中为方法结果缓存实现装饰器模式的最佳方式",这两篇文章都概述了专用方法和通用方法,但都没有提供以类型安全的方式执行通用方法的指示。这些问题发布后已经过去了一段时间,所以也许情况已经改变了。我使用的是PHP 7,如果需要,可以使用PHP 7.1。
PHPUnit_MockObject允许通过PHPUnit中熟悉的getMock
方法调用的相同代码来构造实现接口的对象。这可以是通用装饰器的基础。然而,这需要在生产代码中使用一个模拟库。此外,这个库内部使用eval来完成它的工作。这使它不适合我的项目。
另一种方法是在运行时动态创建装饰类。不幸的是,PHP不允许开箱即用。如果runkit扩展不是一个选项,你可以模仿Doctrine ORM的做法:拥有一个执行以下步骤的DecoratorFactory
:
- 对原始类使用Reflection来获取所有接口及其方法
- 使用PHP代码生成一个文件,该文件包含一个实现所有接口的装饰代理类
- 包括生成的代理类
- 用原始类实例化生成的代理类,并返回代理类
请参阅上的示例http://www.doctrine-project.org/api/orm/2.4/source-class-Doctrine.ORM.Tools.EntityGenerator.html
获得所需内容的唯一真正方法是声明一个基本的Decorator类,该类完全实现必要的接口,然后在此基础上进行扩展。例如:
interface FooInterface {
function doFoo();
function doMoreFoo();
}
class Foo implements FooInterface {
public function __construct() {}
public function doFoo() {}
public function moreFoo() {}
}
class FooDecoratorBase implements FooInterface {
protected $foo;
public function __construct(FooInterface $foo) { $this->foo = $foo; }
public function doFoo() { $this->foo->doFoo(); }
public function moreFoo() { $this->foo->moreFoo(); }
}
class ExtraFooDecorator extends FooDecoratorBase {
public function extraFoo() {}
}
虽然我自己无法列举具体细节,但我尊敬的其他PHP开发人员告诉我,在生产代码中使用反射是个坏主意,因为它们的设计考虑的是测试/调试,而不是性能。
"Generic TypeParameters"在PHP中不可用(除了Facebook的HHVM变体)。所以这是不可能的。如果你真的想走这条路,@Sammitch的答案就是路。
然而,您正试图解决另一个问题。
装饰器模式充当包装器。如果您想为现有类添加附加功能,请使用它,而不更改类本身(或其对象状态)。
请想想,你的装饰师会是什么样的:
function DoSomeOperation() {
try {
return $this->decoratedObject->DoSomeOperation(); //Object is injected in Decorators Constructor
catch ('Exception $ex) {
Logger::Log($ex);
throw;
}
}
为什么不简单地在你的存储库中没有装饰器:
function DoSomeOperation() {
try {
//Do The Logic
catch ('Exception $ex) {
Logger::Log($ex);
throw;
}
}
但也许PDO对象的装饰器/包装器是,你真正想要的是什么?(ᵔᴥᵔ)