fluffy / connector
提供 '信号和槽' 模式
Requires
- php: >=5.5.0
- symfony/yaml: ^3.2
Requires (Dev)
- phpunit/phpunit: ~4
- symfony/config: ^3.2
- symfony/dependency-injection: ^3.2
README
Connector - PHP的"信号和槽"机制。
这个库受Qt的"信号和槽"机制启发,因此非常相似。
信号和槽
信号是用来告诉外部世界一个对象内部状态的变化的东西。例如:电话可以响,猫可以喵喵叫等等。响声和喵喵叫是信号。它们告诉我们它们的内部状态发生了变化。
你可以以某种方式对这些信号做出反应。例如:接电话或喂猫。你的反应就是槽。
在编程中也有类似的情况:有时我们需要对对象状态的变化做出反应。这正是这个库的目的:它使对象之间的通信更加容易。甚至比使用"观察者"模式还要容易。
安装
运行
$ composer require fluffy/connector
或将依赖项添加到你的composer.json文件中
"require": { ... "fluffy/connector": "^1.3" }
用法
1. 信号
如果你想让你的对象能够发出信号,你需要实现SignalInterface
并使用SignalTrait
。例如,你有一个日志类,你希望当日志完成工作后发出信号somethingIsLogged
<?php /** * @file * Contains definition of Logger class. */ use Fluffy\Connector\Signal\SignalInterface; use Fluffy\Connector\Signal\SignalTrait; /** * Class Logger. */ class Logger implements SignalInterface { use SignalTrait; public function log() { // Do logging stuff. ... // Emit signal about successfull logging. $this->emit('somethingIsLogged', 'Some useful data'); } }
要发出信号,你需要调用emit
方法并传递信号名称和数据。你可以传递任何你想要的:数组、字符串、对象或数字。就是这样。现在你的日志向外界发出了信号。但还没有人连接到这个信号。让我们来做这件事。
2. 槽
槽是一个普通的类方法。让我们定义一个带有槽的类。
<?php /** * @file * Contains definition of Receiver class. */ /** * Class Receiver. */ class Receiver { public function slotReactOnSignal($dataFromSignal) { echo "Received data: $dataFromSignal"; } }
3. 连接
到目前为止,我们有一个发出信号的Logger
类和一个带有槽的Receiver
类。要对接收信号进行槽操作,你需要将它们连接起来。让我们这样做。
use Fluffy\Connector\ConnectionManager; $logger = new Logger(); $receiver = new Receiver(); ConnectionManager::connect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal'); $logger->log();
由于你调用了ConnectionManager::connect(SignalInterface $sender, $signalName, $receiver, $slotName);
方法,信号和槽已经连接。这意味着在调用$logger->log()
之后,将发出somethingIsLogged
信号,并调用slotReactOnSignal
槽。结果将是"Received data: Some useful data"
。你可以将任意多个槽连接到信号。实际上,你可以创建以下类型的连接
- 一个信号到一个槽
- 一个信号到多个槽
- 多个信号到多个槽
- 多个信号到一个槽
你还可以通过调用ConnectionManager::initConnections(array $connections);
方法来建立多个连接
ConnectionManager::initConnections([ ... [ 'sender' => new Logger(), 'signal' => 'somethingIsLogged', 'receiver' => new Receiver(), 'slot' => 'slotReactOnSignal', 'type' => ConnectionManager::CONNECTION_PERMANENT, ], ... ]);
3.1. 连接类型
默认情况下,ConnectionManager::connect()
方法创建永久连接。这意味着槽在第一次发射后不会从信号中断开。但你可以创建一次性的连接。只需将第五个参数传递给ConnectionManager::connect()
方法作为ConnectionManager::CONNECTION_ONE_TIME
。例如
use Fluffy\Connector\ConnectionManager; $logger = new Logger(); $receiver = new Receiver(); ConnectionManager::connect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal', ConnectionManager::CONNECTION_ONE_TIME); $logger->log(); // Log once again. $logger->log();
在调用Logger::log()
的第二次调用后,将不会发生任何事情,因为槽将在第一次发射后从信号中断开。
3.2. 连接权重
由于你可以将不同的接收器和不同的槽连接到同一个发送器和信号,因此ConnectionManager
按照连接权重调用连接的槽。权重越低,优先级越高。连接权重影响所有接收器中槽的顺序,但不影响单个接收器中槽的顺序。
默认情况下,ConnectionManager::connect()
方法创建权重为零的永久连接。但你可以通过传递第六个参数到ConnectionManager::connect()
方法来更改它。例如
use Fluffy\Connector\ConnectionManager; $logger = new Logger(); $receiver = new Receiver(); // This connection has weight 0 by default. ConnectionManager::connect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal'); // And this has weight -10. ConnectionManager::connect($logger, 'somethingIsLogged', $receiver, 'anotherSlotReactOnSignal', ConnectionManager::CONNECTION_ONE_TIME, -10); // Signal 'somethingIsLogged' is emitted here. Since we've explicitly defined // connections weights `ConnectionManager` will call slots in the next order: // 1. Slot 'anotherSlotReactOnSignal'. // 2. Slot 'slotReactOnSignal'. $logger->log();
4. 断开连接
如果您不再想监听信号,只需从它断开连接。
ConnectionManager::disconnect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal');
您也可以通过条件断开连接。
- 从所有信号中断开给定发送者的所有接收者。
ConnectionManager::disconnect($logger);
- 从给定发送者的给定信号中断开所有接收者。
ConnectionManager::disconnect($logger, 'somethingIsLogged');
- 从给定接收者和给定发送者的给定信号中断开所有槽。
ConnectionManager::disconnect($logger, 'somethingIsLogged', $receiver);
如果您想重置所有现有连接,请调用
ConnectionManager::resetAllConnections()
5. 服务连接
如果您使用的是 Symfony 依赖注入
组件,您可能不想手动创建对象,而是从服务容器中检索它们。对于这种情况,您可以在不进行任何手动对象创建的情况下连接定义在 services.yml
文件中的服务。就是这样您可以实现这一点
- 假设您有一个包含以下服务的
services.yml
文件
services: service.logger: class: \Logger arguments: [...] service.receiver: class: \Receiver arguments: [...]
- 为了将
\Receiver
槽slotReactOnSignal
连接到\Logger
信号somethingIsLogged
,在项目中的某个位置创建一个 yaml 文件(例如services.connections.yml
),内容如下
# Connection name. Can be any string. test_connection_one: # Sender service id from "services.yml" file. sender: service.logger # Sender's signal. signal: somethingIsLogged # Receiver service id from "services.yml" file. receiver: service.receiver # Receiver's slot. slot: slotReactOnSignal # Connection type. 0 - "permanent". 1 - "one time". # You can omit "type" parameter and it will be # "permanent" by default. type: 0 # Connection weight. You can omit "weight" parameter and it will be # "0" by default. weight: 1 # You can define as many connections as you want. ...
- 初始化服务连接
// Here you need to pass a yaml string from file and a service container. // This should be done once somewhere in a front controller of your // application. $serviceConnections = ConnectionManager::parseServicesConnections(file_get_contents('services.connections.yml'), $container); ConnectionManager::initConnections($serviceConnections);
- 现在您的
\Logger
服务可以发出信号,而\Receiver
服务可以准备对信号做出反应
// Receiver will respond to signal "somethingIsLogged" with a slot defined in "services.connections.yml". $container->get('service.logger')->emit('somethingIsLogged', 'Signal data');
测试
请查看测试以获取更多信息和使用场景。
为了运行测试,请输入
$ composer install
$ ./vendor/bin/phpunit
这是为什么?
我只是喜欢 Qt 的信号和槽系统,并想将其引入 PHP 世界。
有什么优势吗?
- 它很轻量。
- 仅依赖于一个第三方库:
symfony/yaml
许可证
GPLv3。请参阅 LICENSE 文件。