fluffy/connector

提供 '信号和槽' 模式

1.3.4 2017-04-09 13:06 UTC

This package is auto-updated.

Last update: 2024-09-06 13:02:48 UTC


README

Build Status

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');

您也可以通过条件断开连接。

  1. 从所有信号中断开给定发送者的所有接收者。
ConnectionManager::disconnect($logger);
  1. 从给定发送者的给定信号中断开所有接收者。
ConnectionManager::disconnect($logger, 'somethingIsLogged');
  1. 从给定接收者和给定发送者的给定信号中断开所有槽。
ConnectionManager::disconnect($logger, 'somethingIsLogged', $receiver);

如果您想重置所有现有连接,请调用

ConnectionManager::resetAllConnections()

5. 服务连接

如果您使用的是 Symfony 依赖注入 组件,您可能不想手动创建对象,而是从服务容器中检索它们。对于这种情况,您可以在不进行任何手动对象创建的情况下连接定义在 services.yml 文件中的服务。就是这样您可以实现这一点

  1. 假设您有一个包含以下服务的 services.yml 文件
services:
  service.logger:
    class: \Logger
    arguments: [...]
  service.receiver:
    class: \Receiver
    arguments: [...]
  1. 为了将 \ReceiverslotReactOnSignal 连接到 \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.
  ...
  1. 初始化服务连接
// 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);
  1. 现在您的 \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 文件。