clue/ami-react

基于 ReactPHP 的 Asterisk 管理接口 (AMI) 的流式、事件驱动访问。

v1.2.0 2024-02-23 09:56 UTC

This package is auto-updated.

Last update: 2024-08-23 11:13:06 UTC


README

CI status installs on Packagist

基于 ReactPHP 的 Asterisk 管理接口 (AMI) 的流式、事件驱动访问。

Asterisk PBX 是一个流行的开源电话解决方案,提供了一系列电话功能。它允许您控制和管理 PBX,包括发起新呼叫、执行 Asterisk 命令或监视订阅者、通道或队列的状态。

  • 异步执行操作 - 并行向 Asterisk 服务发送任何数量的操作(命令),并在结果返回时立即处理它们。基于 Promise 的设计提供了一种处理无序响应的合理接口。
  • 事件驱动核心 - 注册您的事件处理回调以响应传入的事件,例如来电或订阅者状态的更改。
  • 轻量级、SOLID 设计 - 提供了“足够好”的抽象,不会妨碍您。未来的或自定义的操作和事件不需要更改即可得到支持。
  • 良好的测试覆盖率 - 随附自动化测试套件,并定期在实际环境中针对当前 Asterisk 版本以及到 Asterisk 1.8 版本的版本进行测试。

目录

支持我们

我们在开发、维护和更新我们出色的开源项目上投入了大量时间。您可以通过 在 GitHub 上成为赞助者 来帮助我们保持高质量的工作。赞助者将获得许多回报,请参阅我们的 赞助页面 了解详情。

让我们一起将这些项目提升到新的水平!🚀

快速入门示例

安装后,您可以使用以下代码访问您的本地 Asterisk 实例并通过 AMI 执行一些简单命令:

<?php

require __DIR__ . '/vendor/autoload.php';

$factory = new Clue\React\Ami\Factory();

$factory->createClient('user:secret@localhost')->then(function (Clue\React\Ami\Client $client) {
    echo 'Client connected' . PHP_EOL;

    $sender = new Clue\React\Ami\ActionSender($client);
    $sender->listCommands()->then(function (Clue\React\Ami\Protocol\Response $response) {
        echo 'Available commands:' . PHP_EOL;
        var_dump($response);
    });
});

有关示例,请参阅 示例

用法

工厂

Factory 负责创建您的 Client 实例。

$factory = new Clue\React\Ami\Factory();

该类接受一个可选的 LoopInterface|null $loop 参数,可以用来将事件循环实例传递给该对象使用。您可以使用 null 值来使用默认的事件循环。除非您确信要显式使用给定的事件循环实例,否则不应该提供此值。

如果您需要自定义连接器设置(DNS解析、TLS参数、超时、代理服务器等),您可以显式传递一个自定义的 ConnectorInterface 实例。

$connector = new React\Socket\Connector(array(
    'dns' => '127.0.0.1',
    'tcp' => array(
        'bindto' => '192.168.10.1:0'
    ),
    'tls' => array(
        'verify_peer' => false,
        'verify_peer_name' => false
    )
));

$factory = new Clue\React\Ami\Factory(null, $connector);

createClient()

使用 createClient(string $url): PromiseInterface<Client,Exception> 方法可以创建一个新的 Client

它有助于建立到 AMI 的纯 TCP/IP 或安全 TLS 连接,并可选择执行初始 login 操作。

$factory->createClient($url)->then(
    function (Clue\React\Ami\Client $client) {
        // client connected (and authenticated)
    },
    function (Exception $e) {
        // an error occurred while trying to connect or authorize client
    }
);

此方法返回一个 Promise,在成功时解析为 Client 实例,如果 URL 无效或连接或身份验证失败,则拒绝并抛出 Exception

$url 参数包含要连接的主机以及可选的端口号(对于纯 TCP/IP 连接,默认为 5038)。

$factory->createClient('localhost:5038');

上述示例没有传递任何身份验证信息,因此您可能需要在连接后调用 ActionSender::login() 或使用推荐的快捷方式传递用户名和密码以供 AMI 登录,如下所示

$factory->createClient('user:secret@localhost');

请注意,如果用户名和密码包含特殊字符,它们必须进行 URL 编码(百分号编码)

$user = 'he:llo';
$pass = 'p@ss';

$promise = $factory->createClient(
    rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost'
);

默认情况下,Factory 使用明文 TCP 连接。如果您想创建安全的 TLS 连接,可以使用 tls 方案(默认端口号为 5039

$factory->createClient('tls://user:secret@localhost:5039');

客户端

Client 负责与 Asterisk 管理器接口交换消息,并跟踪挂起的操作。

如果您想发送出站操作,请参阅下面的 ActionSender 类。

除了定义一些方法外,此接口还实现了 EventEmitterInterface,允许您根据以下说明对某些事件做出反应。

close()

可以使用 close(): void 方法强制关闭 AMI 连接并拒绝所有挂起的操作。

end()

可以使用 end(): void 方法在所有挂起的操作完成后软关闭 AMI 连接。

createAction()

可以使用 createAction(string $name, array $fields): Action 方法构建自定义的 AMI 操作。

此方法被视为高级用法,通常仅用于内部。创建 Action 对象、通过 AMI 发送它们并等待传入的 Response 对象通常隐藏在 ActionSender 接口后面。

如果您需要自定义或不受支持的操作,也可以手动执行,如下所示。请考虑为 ActionSender 添加新操作的 PR。

将自动添加一个唯一的值到 "ActionID" 字段(用于匹配传入的响应)。

$action = $client->createAction('Originate', array('Channel' => …));
$promise = $client->request($action);

request()

可以使用 request(Action $action): PromiseInterface<Response,Exception> 方法将给定的消息排队通过 AMI 发送,并等待一个与 "ActionID" 字段值匹配的 Response 对象。

此方法被视为高级用法,通常仅用于内部。创建 Action 对象、通过 AMI 发送它们并等待传入的 Response 对象通常隐藏在 ActionSender 接口后面。

如果您需要自定义或不受支持的操作,也可以手动执行,如下所示。请考虑为 ActionSender 添加新操作的 PR。

$action = $client->createAction('Originate', array('Channel' => …));
$promise = $client->request($action);

event 事件

每当AMI发送事件时,如电话开始或结束等,都会触发 event 事件(多么美妙的名字)。该事件接收一个描述事件实例的单一 Event 参数。

$client->on('event', function (Clue\React\Ami\Protocol\Event $event) {
    // process an incoming AMI event (see below)
    var_dump($event->getName(), $event);
});

可以通过AMI配置和 events() 动作 来开启/关闭事件报告。也可以使用 events() 动作 来启用 "EventMask",仅报告特定事件,详见 AMI 文档

有关事件类型及其相应字段的更多详细信息,请参阅 AMI 事件文档

error 事件

当发生致命错误时,如客户端连接丢失或无效时,将触发 error 事件。该事件接收一个描述错误实例的单一 Exception 参数。

$client->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

此事件仅在发生致命错误时触发,并将关闭客户端连接。不要与由无效命令引起的“软”错误混淆。

close 事件

一旦客户端连接关闭(终止),就会触发 close 事件。

$client->on('close', function () {
    echo 'Connection closed' . PHP_EOL;
});

另请参阅 close() 方法。

ActionSender

ActionSender 包装了一个给定的 Client 实例,以提供一个简单的方式来执行常见操作。此类代表执行操作和等待相应响应的主要接口。

$sender = new Clue\React\Ami\ActionSender($client);

操作

所有公共方法都类似于它们各自的AMI操作。

$sender->login($name, $pass);
$sender->logoff();
$sender->ping();
$sender->command($command);
$sender->events($eventMask);

$sender->coreShowChannels();
$sender->sipPeers();
$sender->agents();

// many more…

在此不列出所有可用的操作,请参阅 类概述

请注意,使用 ActionSender 并非必需,但推荐用于执行常见操作。

如果您需要自定义或不受支持的命令,也可以手动执行。有关详细信息,请参阅上面高级的 createAction() 使用。考虑提交PR以将新操作添加到 ActionSender 中。

Promises

发送操作是异步的(非阻塞的),因此您实际上可以并行发送多个操作请求。AMI将针对每个操作响应一个 Response 对象。顺序没有保证。发送操作使用基于 Promise 的接口,这使得在操作完成时进行响应变得容易(即成功完成或因错误被拒绝)。

$sender->ping()->then(
    function (Clue\React\Ami\Protocol\Response $response) {
        // response received for ping action
    },
    function (Exception $e) {
        // an error occurred while executing the action
        
        if ($e instanceof Clue\React\Ami\Protocol\ErrorException) {
            // we received a valid error response (such as authorization error)
            $response = $e->getResponse();
        } else {
            // we did not receive a valid response (likely a transport issue)
        }
    }
});

所有操作在成功时都解析为一个 Response 对象,一些操作记录了返回特殊化的 Collection 对象以包含条目列表。

阻塞

如上所述,此库默认提供强大的异步API。

但是,如果您想将其集成到传统的阻塞环境中,您还应该考虑使用 clue/reactphp-block

生成的阻塞代码可能看起来像这样

use Clue\React\Block;
use React\EventLoop\Loop;

function getSipPeers()
{
    $factory = new Clue\React\Ami\Factory();

    $target = 'name:password@localhost';
    $promise = $factory->createClient($target)->then(function (Clue\React\Ami\Client $client) {
        $sender = new Clue\React\Ami\ActionSender($client);
        $ret = $sender->sipPeers()->then(function (Clue\React\Ami\Collection $collection) {
            return $collection->getEntryEvents();
        });
        $client->end();
        return $ret;
    });

    return Block\await($promise, Loop::get(), 5.0);
}

有关详细信息,请参阅 clue/reactphp-block

消息

MessageResponseActionEvent 值对象的抽象基类。它为这三种消息类型提供了一个共同的接口。

每个Message由任意数量的字段组成,每个字段都有一个名称和一个或多个值。字段名称不区分大小写。值的解释是特定于应用程序的。

getFieldValue()

可以使用getFieldValue(string $key): ?string方法来获取给定字段键的第一个值。

如果没有找到值,则返回null

getFieldValues()

可以使用getFieldValues(string $key): string[]方法来获取给定字段键的所有值的列表。

如果没有找到值,则返回一个空的array()

getFieldVariables()

可以使用getFieldVariables(string $key): array方法来获取给定键中所有变量赋值的哈希表。

如果没有找到值,则返回一个空的array()

getFields()

可以使用getFields(): array方法来获取所有字段的数组。

getActionId()

可以使用getActionId(): string方法来获取该消息的唯一动作ID。

这是获取“ActionID”字段值的快捷方式。

响应

值对象Response表示从AMI接收到的传入响应。它共享父类Message的所有属性。

getCommandOutput()

可以使用getCommandOutput(): ?string方法来获取“命令”动作的结果输出。只有当这是一个对“命令”动作的响应时,此值才可用,否则默认为null

$sender->command('help')->then(function (Clue\React\Ami\Protocol\Response $response) {
    echo $response->getCommandOutput();
});

集合

值对象Collection表示从AMI接收到的特定动作的传入响应,这些动作返回一个条目列表。它共享父类Response的所有属性。

您可以像访问正常的Response一样访问Collection,以访问此集合的领先Response,或者可以使用以下方法来访问列表条目和完成事件。

Action: CoreShowChannels

Response: Success
EventList: start
Message: Channels will follow

Event: CoreShowChannel
Channel: SIP / 123
ChannelState: 6
ChannelStateDesc: Up
…

Event: CoreShowChannel
Channel: SIP / 456
ChannelState: 6
ChannelStateDesc: Up
…

Event: CoreShowChannel
Channel: SIP / 789
ChannelState: 6
ChannelStateDesc: Up
…

Event: CoreShowChannelsComplete
EventList: Complete
ListItems: 3

getEntryEvents()

可以使用getEntryEvents(): Event[]方法来获取所有中间Event对象的列表,其中每个条目代表集合中的单个条目。

foreach ($collection->getEntryEvents() as $entry) {
    assert($entry instanceof Clue\React\Ami\Protocol\Event);
    echo $entry->getFieldValue('Channel') . PHP_EOL;
}

getCompleteEvent()

可以使用getCompleteEvent(): Event方法来获取完成此集合的后续Event

echo $collection->getCompleteEvent()->getFieldValue('ListItems') . PHP_EOL;

操作

值对象Action表示要发送到AMI的出站动作消息。它共享父类Message的所有属性。

getMessageSerialized()

可以使用getMessageSerialized(): string方法来获取发送到Asterisk的此出站动作的序列化版本。

此方法被认为是高级用法,通常仅在内部使用。

事件

值对象Event表示从AMI接收到的传入事件。它共享父类Message的所有属性。

getName()

可以使用getName(): ?string方法来获取事件的名称。

这是获取“Event”字段值的快捷方式。

安装

安装此库的推荐方法是通过ComposerComposer新手?

此项目遵循SemVer。这将安装最新支持的版本

composer require clue/ami-react:^1.2

有关版本升级的详细信息,请参阅变更日志

此项目旨在在任何平台上运行,因此不需要任何PHP扩展,并支持在从PHP 5.3到当前PHP 8+的旧版PHP上运行。强烈建议使用此项目的最新支持版本

测试

要运行测试套件,您首先需要克隆此存储库,然后通过Composer安装所有依赖项通过Composer

composer install

要运行测试套件,请转到项目根目录并运行

vendor/bin/phpunit

测试套件包含单元测试和功能集成测试。功能测试需要访问一个运行中的Asterisk服务器实例,默认情况下将跳过。如果您还想运行功能测试,需要在环境变量中提供您自己的 AMI登录详细信息,如下所示

LOGIN=username:password@localhost php vendor/bin/phpunit

许可

本项目在宽松的MIT许可证下发布。

您知道吗?我提供定制开发服务,并为发布赞助和贡献开具发票。请与我联系(@clue)获取详细信息。