Event-driven архитектура на PHP: пишем асинхронный код без боли

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Когда ваш 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()->                

Блоги

Рекомендаций по книгам