在异步模式下使用选定的 PHP 框架

还有许多其他 PHP 框架也实现了异步编程模型。在本节中,我们将介绍最流行的 PHP 异步框架 ReactPHP 以及另一种流行的 PHP 异步框架 Amp。此外,我们还将向您展示如何在异步模式下使用某些 PHP 框架。

需要注意的是,许多能以异步模式运行的 PHP 框架都依赖 Swoole 扩展。不依赖 Swoole 扩展的 PHP 框架是 ReactPHP。

使用 ReactPHP

ReactPHP (https://reactphp.org/) 是 Reactor 软件设计模式的一种实现,其灵感来自非阻塞异步 Node.js 框架 (https://nodejs.org/en/) 等。

虽然 ReactPHP 无法像 Swoole 扩展那样自动提高性能,但它有一个很大的优势,那就是不依赖 UNIX 或 Linux 的功能,因此可以在 Windows 服务器上运行。ReactPHP 的另一个优势是,除了那些已经作为标准扩展的 PHP 扩展之外,它对其他 PHP 扩展没有特定的依赖性。

任何 ReactPHP 应用程序的核心都是 React\EventLoop\Loop 类。顾名思义,Loop 实例启动时实际上是一个 无限循环。大多数 PHP 无限循环都会给应用程序带来灾难!但在本例中,循环是与服务器实例一起使用的,该实例会持续监听指定端口上的请求。

ReactPHP 的另一个关键组件是 React/Socket/Server。该类会在给定端口上打开一个套接字,使 ReactPHP 应用程序能够直接监听 HTTP 请求,而无需涉及网络服务器。

ReactPHP 的其他功能还包括监听 UDP 请求、非阻塞缓存和异步承诺的实现。ReactPHP 还有一个 Stream 组件,它允许您延迟文件系统的读取和写入,从而大大提高了性能,因为您的应用程序无需再等待此类文件 I/O 请求的完成。

使用 ReactPHP 的最后一个优势是它完全符合 PSR-7(HTTP 消息传递)。现在我们来看看使用 ReactPHP 重写的运行前面描述的聊天 API 的示例程序。以下是重写程序的步骤:

  1. 从命令提示符进入 Docker PHP 8 容器,使用 Composer 安装必要的 ReactPHP 组件:

    cd /repo/ch12
    composer require --ignore-platform-reqs react/event-loop
    composer require --ignore-platform-reqs react/http
    composer require --ignore-platform-reqs react/socket
  2. 然后我们重写 /repo/ch12/php8_chat_swoole.php,并将其重命名为 /repo/ch12/php8_chat_react.php。我们首先需要修改的是使用语句:

    // /repo/ch12/php8_chat_react.php
    include __DIR__ . '/vendor/autoload.php';
    use Chat\Message\Pipe;
    use React\EventLoop\Factory;
    use React\Http\Server;
    use React\Http\Message\Response as ReactResponse;
    use Psr\Http\Message\ServerRequestInterface;
  3. 然后,我们启动一个会话并创建一个 React\EventLoop\Loop 实例,如下所示:

    session_start();
    $loop = Factory::create();
  4. 我们现在定义一个处理程序,它接受 PSR-7 ServerRequestInterface 实例作为参数,并返回一个 React\Http\Message\Response 实例:

    $server = new Server($loop,
        function (ServerRequestInterface $request) {
            return new ReactResponse(200,
                    ['Content-Type' => 'text/plain'],
                    <8 SPACES>Pipe::exec($request)
            );
    });
  5. 然后,我们建立一个 React\Socker\Server 实例来监听 9501 端口,并执行一个循环,就像这样:

    $socket = new React\Socket\Server(9501, $loop);
    $server->listen($socket);
    echo "Server running at http://locahost:9501\n";
    $loop->run();

然后,我们在 PHP 8.1 容器中打开一个单独的命令 shell,启动 ReactPHP 服务器,如下所示:

root@php8_tips_php8_1 [ /repo/ch12 ]# php php8_chat_react.php

然后,我们可以从另一个命令 shell 进入 PHP 8.1 容器,运行测试程序如下:

root@php8_tips_php8_1 [ /repo/ch12 ]# php php8_chat_test.php \
http://localhost:9501

输出结果(未显示)与使用 Swoole 扩展时显示的结果类似。

接下来,我们看看另一个流行的 PHP 异步框架: Amp。

使用 Amp 实现 PHP 异步

Amp 框架( https://amphp.org/ )与 ReactPHP 非常相似,提供定时器、承诺和流的实现。Amp 还提供了例程支持以及异步迭代组件。后者非常吸引人,因为迭代对于大多数 PHP 应用程序来说都是必不可少的。如果能将迭代移入异步处理模式,虽然可能会涉及大量的重构工作,但却能极大地提升应用程序的性能。另一个有趣的变化是,Amp 可以直接使用任何 ReactPHP 组件!

要安装 Amp,请使用 Composer。Amp 的各种组件在不同的软件源中提供,因此您不必安装整个框架,只需安装您需要的组件即可。PHP Amp 服务器的实际实现与 ReactPHP 的示例非常相似。

现在让我们看看另一个可以在 PHP 异步模式下运行的框架:Mezzio,以前叫 Zend Expressive

将 Mezzio 与 Swoole 结合使用

Mezzio 框架 ( https://docs.mezzio.dev/ ) 是 Matthew Weier O’Phinney (https://mwop.net/) 的心血结晶,是老框架 Zend Framework 和新框架 Zend Expressive 的延续。Mezzio 属于微型框架这一相对较新的类别。微框架不依赖于老旧的模型-视图-控制器(MVC)软件设计模式,主要面向 RESTful API 开发。在实际应用中,微框架支持 PHP 中间件原理,运行时开销更少,速度也相应更快。

要在 Swoole 中使用 Mezzio 应用程序,只需具备以下三点:

  1. 安装 Swoole 扩展(本章前面已有介绍)。

  2. 像这样安装 mezzio-swoole 组件:

    composer require mezzio/mezzio-swoole
  3. 然后,您需要使用 Swoole 服务器实例运行 Mezzio。这可以通过以下命令实现:

    /path/to/project/vendor/bin/laminas mezzio:swoole:start
  4. 在 Mezzio 应用程序的配置文件中,您需要添加以下键:

    return [
        'mezzio-swoole' => [
            'swoole-http-server' => [
                'host' => '0.0.0.0', // all IP addresses
                'port' => 9501,
            ]
        ],
    ];

为了进一步提高性能,当然还应该重写代码的相应部分,以利用 PHP 的异步功能。接下来,我们来看看超越 async 的 PHP 扩展。

使用并行扩展

并行扩展 (https://www.php.net/parallel) 在 PHP 7.2 及以上版本中引入。其目的是在 PHP async 之外更进一步,进入全面并行处理的世界。并行扩展提供了五个关键的底层类,它们可以构成并行处理应用程序的基础。使用该扩展,PHP 开发人员可以像 Go 语言一样编写并行代码。让我们从 parallel\Runtime 开始。

parallel\Runtime 类

每个 parallel\Runtime 实例都会产生一个新的 PHP 线程。然后你可以使用 parallel\Runtime::run() 来安排任务。run() 的第一个参数是 Closure(匿名函数)。第二个可选参数是 $argv,代表运行时传递给任务的输入参数。 parallel\Runtime::close() 用于优雅地关闭线程。当出现错误时,可以使用 parallel\Runtime::kill() 立即退出线程。

parallel\Future 类

parallel\Future 实例是作为 parallel\ Runtime::run() 的返回值创建的。它的行为很像 PHP 异步承诺(本章前面有描述)。该类有三个方法,执行以下操作:

  • parallel\Future::value() 返回任务已完成的值

  • parallel\Future::cancel() 取消代表承诺失败状态的任务

  • parallel\Future::cancelled()|done() 如果任务仍未完成,则返回任务状态

parallel\Channel 类

parallel\Channel 类允许开发者在任务间共享信息。使用 __construct() 方法或 make() 方法创建通道。如果没有为 __construct() 方法提供参数,或没有为 make() 方法提供第二个参数,通道将被视为未缓冲通道。如果向 __construct() 或作为 make() 的第二个参数提供了一个整数,该值就代表通道的容量。然后,你可以使用 parallel\Channel::send()parallel\Channel::recv() 方法通过通道发送和接收数据。

非缓冲通道会阻塞对 send() 的调用,直到有接收者为止,反之亦然。另一方面,缓冲通道在容量达到之前不会阻塞。

parallel\Events 类

parallel\Events 类与本章第一节描述的事件循环类似。该类有 addChannel()addFuture() 方法,用于添加要监控的通道和/或未来实例。setBlocking() 方法允许事件循环以阻塞或非阻塞模式监控事件。使用 setTimeout() 方法可设置允许循环持续多长时间的总体控制周期(以毫秒为单位)。最后,poll() 方法会使事件循环轮询下一个事件。

安装 parallel 扩展

并行扩展可以使用 pecl 命令或预编译的二进制文件安装,就像其他非标准的 PHP 扩展一样。但需要注意的是,该扩展仅适用于 Zend Thread Safety (ZTS) PHP 安装。因此,如果使用 Docker,则需要获取 PHP ZTS 映像;如果自定义编译 PHP,则需要使用 --enable-zts (Windows)或 --enable-maintainer-zts (非 Windows)配置工具标志。

在了解了如何在异步模式下使用一些选定的 PHP 扩展和框架之后,我们将展望未来并讨论 PHP 8.1 fibers。