siriusphp / stratum
此包已被废弃,不再维护。未建议替代包。
通用库,允许通过类似装饰器的机制扩展对象的功能
dev-master
2015-02-19 10:15 UTC
Requires
- php: >=5.3
Requires (Dev)
- mockery/mockery: 0.9
- phpunit/phpunit: 3.7
- satooshi/php-coveralls: dev-master
This package is auto-updated.
Last update: 2024-03-24 03:03:21 UTC
README
#Sirius Stratum
Sirius\Stratum
是一个库,允许您在不使用
- 深度继承 的情况下创建可扩展的系统。继承在创建可扩展类时并不能走得很远。
- 特性。特性克服了继承的限制,但需要预先定义结构(您无法在运行时添加特性)
- 事件系统。事件系统需要您编写大量代码来处理与事件系统的交互。
- 命令总线。命令总线允许您通过不同的函数响应命令来更改系统的行为,但这些函数的组合非常简单。例如,考虑一个
getLatestPosts
命令,它应该从数据库中检索帖子;您将回调附加到该命令,但后来您想实现缓存机制(例如:首先查询缓存,如果缓存中没有项目,则委托给先前的回调) - AOP(面向方面编程)。AOP 非常困难,因为术语和实现“重量级”。 http://go.aopphp.com/ 是其中之一实现
话虽如此,我必须警告您,Sirius\Stratum
并非完美无瑕,也有一些权衡(非常小)。
它是如何工作的?
该项目始于一个问题:如何在保持继承的同时,使类的某个方法在运行时改变其行为?明显的答案是使用类似洋葱的结构将对象包装起来。从这个角度来看,Sirius\Stratum
类似于装饰器模式。想象您有一个 ORM,您想
- 记录调用(用于基准测试目的)
- 拦截异常,向开发者发送通知
- 缓存结果
$orm = new CacheBehaviour(new LogBehaviour( new ExceptionNotifier(new ORM($dbConn)))); $orm->getLatestArticles();
这就是使用装饰器模式实现的方式。这种方法有一些限制/问题
- 如果在调用堆栈底部,
ORM
对象调用其另一个方法(例如:getLatestArticles()
调用$this->executeQuery()
),则对该方法的调用将不会通过上面的层 - 您的装饰器必须实现被装饰类相同的接口。当然,这可以自动化(即:有一个机制可以在磁盘上自动创建装饰器类)
现在介绍一下 Sirius\Stratum!
1. 将您的代码移动到“基类”中
class ORMBase { function __construct($dbConn) { // whatever... } function getLatestArticles() { // query the database, map the results, return a collection } } class ORM extends ORMBase { use \Sirius\Stratum\LayerableTrait; }
注意! 对于 PHP5.3,您需要自己复制并粘贴 \Sirius\Stratum\LayerableTrait
特性的代码。抱歉!
2. 确定您想要扩展/装饰哪些方法
class ORM extends ORMBase { use \Sirius\Stratum\LayerableTrait; function getLatestArticles() { return $this->executeLayeredMethod(__FUNCTION__, func_get_args()); } }
3. 指示 Stratum 管理器将哪些层添加到目标类
$manager = new Sirius\Stratum\Manager(); $manager->add('CacheBehaviour', 'ORM', -1000); // -1000 is the priority (not mandatory though) $manager->add('LogBehaviour', 'ORM', 999); $manager->add('ExceptionNotifier', 'ORM', 998); // add decorators by TRAIT $manager->add('LogBehaviour', 'uses:Vendor\Package\LoggableTrait'); // add decorators by INTERFACE $manager->add('LogBehaviour', 'implements:Vendor\Package\LoggableInterface'); // add decorator by PARENT CLASS $manager->add('LogBehaviour', 'extends:Vendor\Package\SomeBaseClass'); // attach the layers on the target method $ormInstance->setTopLayer($manager->createLayerStack($ormInstance));
层类必须扩展 Sirius\Stratum\Layer
类。
就这样!
常见问题解答?
1. 有哪些权衡?
- 它不是一个“纯”模式的实现。 有人抱怨 它要么是中介模式,要么是责任链模式,或者是一个隐蔽的事件模式。
- 您有全局状态(例如:每个类的“strata”的单一管理器)。我认为这种实现类似于特性(在全局级别上定义特性)并且我不是一个纯粹主义者。
- 由于层(或“装饰器”)不需要实现被装饰对象的接口,因此库依赖于
__call()
将调用传递给下一层,这会带来性能损失。尽管如此,我会在将来尝试解决这个问题。
2. 我的行为限制在类中吗?
不是。您可以将对象作为装饰器添加(请注意,该对象将需要由该类克隆,所以请记住这一点)或者是一个返回装饰器的回调/函数。
$manager->add($someAlreadyInstanciatedLayer, 'ORM'); $manager->add($someCallableThatReturnsALayer, 'ORM');
1. 如果装饰器具有相同的优先级会发生什么?
它们将以添加的相反顺序被调用(即:最后的将围绕第一层)。
$manager->add('LayerA', 'LayerableClass', 100); $manager->add('LayerB', 'LayerableClass', 100); $decoratedClassObject->foo();
假设这些是唯一的装饰器,DecoratorB::foo()
将被首先调用,它可能调用LayerA::foo()
,而LayerA::foo()
可能调用LayerableClass::foo()
。
4. 我可以多次添加装饰器吗?
是的。管理器不会检查装饰器是否已附加到类,所以请小心。
5. 我还能使用事件吗?
是的。您可以有一个会发出事件的装饰器。它甚至可能使您的生活变得更简单(在需要该装饰器的所有类中使用相同的装饰器)。
class EventsLayer extends Sirius\Stratum\Layer { function foo() { $this->emit('before_foo', func_get_args()); return $this->callNext(__FUNCTION__, func_get_args()); } }
6. 由于我非常喜欢装饰器,我能否装饰一个装饰器?
尚未测试,但我看不出为什么不行。