siriusphp/stratum

此包已被废弃,不再维护。未建议替代包。

通用库,允许通过类似装饰器的机制扩展对象的功能

dev-master 2015-02-19 10:15 UTC

This package is auto-updated.

Last update: 2024-03-24 03:03:21 UTC


README

#Sirius Stratum

Build Status Code Coverage Scrutinizer Code Quality

Sirius\Stratum 是一个库,允许您在不使用

  1. 深度继承 的情况下创建可扩展的系统。继承在创建可扩展类时并不能走得很远。
  2. 特性。特性克服了继承的限制,但需要预先定义结构(您无法在运行时添加特性)
  3. 事件系统。事件系统需要您编写大量代码来处理与事件系统的交互。
  4. 命令总线。命令总线允许您通过不同的函数响应命令来更改系统的行为,但这些函数的组合非常简单。例如,考虑一个 getLatestPosts 命令,它应该从数据库中检索帖子;您将回调附加到该命令,但后来您想实现缓存机制(例如:首先查询缓存,如果缓存中没有项目,则委托给先前的回调)
  5. AOP(面向方面编程)。AOP 非常困难,因为术语和实现“重量级”。 http://go.aopphp.com/ 是其中之一实现

话虽如此,我必须警告您,Sirius\Stratum 并非完美无瑕,也有一些权衡(非常小)。

它是如何工作的?

该项目始于一个问题:如何在保持继承的同时,使类的某个方法在运行时改变其行为?明显的答案是使用类似洋葱的结构将对象包装起来。从这个角度来看,Sirius\Stratum 类似于装饰器模式。想象您有一个 ORM,您想

  1. 记录调用(用于基准测试目的)
  2. 拦截异常,向开发者发送通知
  3. 缓存结果
$orm = new CacheBehaviour(new LogBehaviour( new ExceptionNotifier(new ORM($dbConn))));
$orm->getLatestArticles(); 

这就是使用装饰器模式实现的方式。这种方法有一些限制/问题

  1. 如果在调用堆栈底部,ORM 对象调用其另一个方法(例如:getLatestArticles() 调用 $this->executeQuery()),则对该方法的调用将不会通过上面的层
  2. 您的装饰器必须实现被装饰类相同的接口。当然,这可以自动化(即:有一个机制可以在磁盘上自动创建装饰器类)

现在介绍一下 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. 有哪些权衡?

  1. 它不是一个“纯”模式的实现。 有人抱怨 它要么是中介模式,要么是责任链模式,或者是一个隐蔽的事件模式。
  2. 您有全局状态(例如:每个类的“strata”的单一管理器)。我认为这种实现类似于特性(在全局级别上定义特性)并且我不是一个纯粹主义者。
  3. 由于层(或“装饰器”)不需要实现被装饰对象的接口,因此库依赖于__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. 由于我非常喜欢装饰器,我能否装饰一个装饰器?

尚未测试,但我看不出为什么不行。