Using Yii with Swoole
Swoole is a PHP extension for event-driven, coroutine-based network applications. It includes an HTTP server, coroutine APIs, and runtime hooks for common blocking I/O functions.
Swoole allows Yii request handlers to run as workers. Each worker may handle multiple requests. Such an operation mode is often called event loop and allows not re-initializing a framework for each request that improves performance significantly.
Installation
Swoole supports Linux, macOS, Cygwin, and WSL. Install the extension through PECL:
bash
pecl install swooleIf PECL doesn't enable the extension automatically, add it to your php.ini:
ini
extension=swoole.soCheck that PHP loads the extension:
bash
php --ri swooleThe Swoole project also provides a Docker image and documents source builds with optional features such as OpenSSL, sockets, MySQL, PostgreSQL, and curl hooks.
Putting up a server
Swoole passes Swoole\Http\Request and Swoole\Http\Response objects to request callbacks. Yii handles PSR-7 requests and responses, so add a converter package:
shell
composer require ilexn/swoole-convert-psr7Create an entry script, server.php:
php
<?php
declare(strict_types=1);
use App\Environment;
use Ilex\SwoolePsr7\SwooleResponseConverter;
use Ilex\SwoolePsr7\SwooleServerRequestConverter;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Log\LogLevel;
use Swoole\Http\Server;
use Swoole\HTTP\Request;
use Swoole\HTTP\Response;
use Yiisoft\Di\StateResetter;
use Yiisoft\ErrorHandler\ErrorHandler;
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
use Yiisoft\Log\Logger;
use Yiisoft\Log\StreamTarget;
use Yiisoft\Yii\Http\Application;
use Yiisoft\Yii\Http\Handler\ThrowableHandler;
use Yiisoft\Yii\Runner\Http\HttpApplicationRunner;
ini_set('display_errors', 'stderr');
require_once __DIR__ . '/src/bootstrap.php';
$runner = new HttpApplicationRunner(
rootPath: __DIR__,
debug: Environment::appDebug(),
checkEvents: Environment::appDebug(),
environment: Environment::appEnv(),
temporaryErrorHandler: new ErrorHandler(
new Logger(
[
(new StreamTarget())->setLevels([
LogLevel::EMERGENCY,
LogLevel::ERROR,
LogLevel::WARNING,
]),
],
),
new HtmlRenderer(),
),
);
$container = $runner->getContainer();
$application = $container->get(Application::class);
$errorCatcher = $container->get(ErrorCatcher::class);
$stateResetter = $container->get(StateResetter::class);
$serverRequestFactory = new SwooleServerRequestConverter(
$container->get(ServerRequestFactoryInterface::class),
$container->get(UriFactoryInterface::class),
$container->get(UploadedFileFactoryInterface::class),
$container->get(StreamFactoryInterface::class)
);
$server = new Server('0.0.0.0', 9501);
$server->on('start', static function (Server $server) use ($application) {
$application->start();
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$server->on('request', static function (
Request $request,
Response $response
) use ($serverRequestFactory, $application, $errorCatcher, $stateResetter) {
try {
$psr7Request = $serverRequestFactory->createFromSwoole($request);
$psr7Response = $application->handle($psr7Request);
(new SwooleResponseConverter($response))->send($psr7Response);
} catch (Throwable $throwable) {
$handler = new ThrowableHandler($throwable);
$psr7Response = $errorCatcher->process($request, $handler);
(new SwooleResponseConverter($response))->send($psr7Response);
}
$application->afterEmit($psr7Response);
$stateResetter->reset();
});
$server->on('shutdown', static function (Server $server) use ($application) {
$application->shutdown();
});
$server->start();Starting a server
To start a server, execute the following command:
php server.phpOn scope
A scope is shared, so at each iteration of the event loop every service that depends on state should be reset.