Когда ваш PHP-код перестаёт быть просто скриптом
Вы когда-нибудь запускали скрипт на PHP, который обрабатывал тысячу писем, и ждали минуту, пока он выполнится? Или пытались сделать чат на «голом» PHP и понимали, что без постоянного обновления страницы это невозможно? Если да — вы уже сталкивались с ограничениями традиционного синхронного подхода.
Event-driven архитектура на PHP — это не просто модный термин из мира Node.js. Это реальный способ сделать ваш код быстрее, отзывчивее и масштабируемее. В этой статье мы разберём, как работает событийно-ориентированное программирование в PHP, напишем несколько живых примеров и поймём, когда оно действительно нужно.
Что такое Event-driven архитектура простыми словами
Представьте себе офис, где каждый сотрудник работает строго по инструкции: «Сделай А, потом Б, потом В». Это — синхронный код. А теперь представьте офис, где сотрудники реагируют на события: «Когда приходит письмо — ответь на него», «Когда заказ оформлен — отправь уведомление». Это и есть событийно-ориентированная архитектура.
В event-driven архитектуре на PHP программа не выполняется линейно. Вместо этого она ждёт событий (клик, запрос, таймер, завершение задачи) и реагирует на них. Главный компонент здесь — event loop (цикл событий), который постоянно проверяет, не произошло ли что-то, и запускает соответствующие обработчики.
Почему PHP-разработчикам стоит обратить на это внимание
Долгое время PHP ассоциировался исключительно с синхронным выполнением: пришёл запрос — отработал скрипт — отдал ответ. Но с появлением таких библиотек, как ReactPHP и Amp, всё изменилось. Теперь вы можете:
- Обрабатывать тысячи WebSocket-соединений одновременно
- Выполнять фоновые задачи без очередей типа RabbitMQ
- Строить реальные микросервисы на чистом PHP
- Писать асинхронные HTTP-клиенты и серверы
И всё это — без смены языка и переписывания всей кодовой базы.
Основные компоненты Event-driven архитектуры
Прежде чем писать код, давайте разберёмся с ключевыми понятиями. В любой событийно-ориентированной системе на PHP есть три главных элемента:
- Event loop — бесконечный цикл, который слушает события (таймеры, сокеты, сигналы) и диспатчит их обработчикам.
- Emitter/Dispatcher — объект, который генерирует и отправляет события.
- Listener/Handler — функция или метод, который реагирует на конкретное событие.
В классическом PHP вы бы написали: file_get_contents('https://api.example.com') — и ждали бы ответа. В event-driven подходе вы подписываетесь на событие «данные получены» и продолжаете выполнять другой код.
Пример 1: Простой Event Dispatcher на чистом PHP
Давайте реализуем базовый диспетчер событий без внешних библиотек. Это поможет понять суть event-driven программирования в PHP.
<?php
class EventDispatcher {
private array $listeners = [];
public function addListener(string $eventName, callable $listener): void {
$this->listeners[$eventName][] = $listener;
}
public function dispatch(string $eventName, mixed $data = null): void {
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $listener) {
call_user_func($listener, $data);
}
}
}
// Использование
$dispatcher = new EventDispatcher();
$dispatcher->addListener('user.registered', function ($userData) {
echo "Отправляем приветственное письмо для {$userData['email']}\n";
});
$dispatcher->addListener('user.registered', function ($userData) {
echo "Логируем регистрацию пользователя {$userData['name']}\n";
});
// Где-то в коде
$dispatcher->dispatch('user.registered', [
'name' => 'Иван',
'email' => 'ivan@example.com'
]);
// Вывод:
// Отправляем приветственное письмо для ivan@example.com
// Логируем регистрацию пользователя Иван
Этот код — основа любого event-driven фреймворка PHP. Обратите внимание: мы не меняли логику основного приложения, а просто добавили обработчики. Если потребуется добавить ещё одно действие — мы просто подписываем новый listener.
Пример 2: Асинхронность с ReactPHP
Теперь перейдём к реальной асинхронности. ReactPHP — это самая популярная библиотека для event-driven архитектуры на PHP. Она предоставляет event loop, работу с сокетами, HTTP-клиент и многое другое.
Установите её через Composer:
composer require react/event-loop react/http
Вот пример асинхронного HTTP-сервера с таймером:
<?php
require 'vendor/autoload.php';
use React\EventLoop\Factory;
use React\Http\Server;
use React\Http\Message\Response;
use Psr\Http\Message\ServerRequestInterface;
$loop = Factory::create();
// Таймер, который срабатывает каждые 2 секунды
$loop->addPeriodicTimer(2, function () {
echo "Тик: " . date('H:i:s') . "\n";
});
// HTTP-сервер
$server = new Server($loop, function (ServerRequestInterface $request) {
return new Response(
200,
['Content-Type' => 'text/plain'],
"Привет из event-driven PHP!\n"
);
});
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$server->listen($socket);
echo "Сервер запущен на http://127.0.0.1:8080\n";
$loop->run();
Запустите этот скрипт. Вы увидите, что сервер работает, таймер тикает, а в браузере по адресу http://127.0.0.1:8080 отдаётся ответ. При этом всё выполняется в одном процессе, без Apache или Nginx. Это и есть магия event loop в PHP.
Пример 3: Асинхронный HTTP-клиент
Одна из самых частых задач — сделать несколько HTTP-запросов и не ждать каждый по очереди. С ReactPHP это делается в несколько строк:
<?php
require 'vendor/autoload.php';
use React\EventLoop\Factory;
use React\Http\Browser;
$loop = Factory::create();
$client = new Browser($loop);
$urls = [
'https://jsonplaceholder.typicode.com/posts/1',
'https://jsonplaceholder.typicode.com/posts/2',
'https://jsonplaceholder.typicode.com/posts/3',
];
foreach ($urls as $url) {
$client->get($url)->then(
function (Psr\Http\Message\ResponseInterface $response) use ($url) {
echo "Получен ответ от {$url}: " . $response->getBody()->