clue / ami-react
基于 ReactPHP 的 Asterisk 管理接口 (AMI) 的流式、事件驱动访问。
Requires
- php: >=5.3
- evenement/evenement: ^3.0 || ^2.0 || ^1.0
- react/event-loop: ^1.2
- react/promise: ^3.0 || ^2.9 || ^1.1
- react/socket: ^1.14
Requires (Dev)
- clue/block-react: ^1.5
- phpunit/phpunit: ^9.6 || ^5.7 || ^4.8.36
README
基于 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。
消息
Message
是 Response
、Action
和 Event
值对象的抽象基类。它为这三种消息类型提供了一个共同的接口。
每个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”字段值的快捷方式。
安装
安装此库的推荐方法是通过Composer。 Composer新手?
此项目遵循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)获取详细信息。