PHP Development

A Comprehensive Guide to Event-Driven Programming in PHP

By the Domain India teamPublished 8 min read
Knowledge base article
Contents (7 sections)

"Event-driven" means two different things in PHP. Inside a normal web request, it means your code announces that something happened and separate listeners react to it. In a long-running PHP process, it means an event loop that waits for timers, network connections and messages and runs a callback when each one arrives. This guide covers both, with current code for PHP 8.

Key takeaways

For ordinary PHP websites, use an event dispatcher (PSR-14, Symfony EventDispatcher or Laravel events) to decouple what happened from what reacts to it. For real-time work such as WebSockets, many concurrent HTTP calls or background workers, use an event loop: ReactPHP or AMPHP (both built on PHP 8.1 Fibers today), or the Swoole or OpenSwoole extensions. Event loops run as long-lived processes, so they need a VPS or a container platform, not shared hosting.

1. The core idea: events and listeners

An event is a record that something happened: an order was placed, a user signed up, a timer fired, a socket received data. A listener (or handler) is a function that runs in response. The code that raises the event doesn't know or care which listeners exist.

Separation of concerns
The checkout code places the order; the email, the invoice and the stock update are separate listeners you can add or remove without touching checkout.
Concurrency
An event loop waits on many sockets and timers at once, so one process can serve thousands of idle connections.
Extensibility
Plugins and modules hook into your events without editing your code, which is how WordPress actions and filters work.

The trade-off is that the flow is harder to follow: a single "order placed" event may trigger five listeners in different files. Name events clearly and keep a list of who listens to what.

2. Events inside a request: the PSR-14 dispatcher

Most PHP applications run once per HTTP request and exit. They can still be event-driven. PSR-14 is the standard interface for this, and Symfony, Laravel and many other frameworks implement it or something close to it.

A minimal example with the Symfony EventDispatcher component:

php
<?php
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Contracts\EventDispatcher\Event;

final class OrderPlaced extends Event
{
    public function __construct(public readonly int $orderId, public readonly string $email) {}
}

$dispatcher = new EventDispatcher();

$dispatcher->addListener(OrderPlaced::class, function (OrderPlaced $e) {
    // send the confirmation email
    error_log("Email for order {$e->orderId} to {$e->email}");
});

$dispatcher->addListener(OrderPlaced::class, function (OrderPlaced $e) {
    // reduce stock
    error_log("Stock updated for order {$e->orderId}");
});

// In your checkout code:
$dispatcher->dispatch(new OrderPlaced(1042, '[email protected]'));

Listeners run one after another, in the same request, in the order you registered them (or by priority). If a listener does slow work, such as calling an external API, move that work to a queue so the visitor isn't kept waiting.

3. Event loops: long-running PHP

An event loop is a process that never exits on its own. It keeps a list of timers and open sockets, waits until one of them is ready, runs the matching callback, and goes back to waiting. Because nothing blocks while waiting for the network, one process can handle many connections at once.

ToolTypeGood for
ReactPHPPure-PHP libraries (Composer)Event loop, streams, HTTP client and server, sockets
AMPHPPure-PHP libraries on the Revolt event loopFiber-based async code that reads like normal sequential code
Swoole or OpenSwooleC extensions installed on the serverHigh-performance HTTP and WebSocket servers, coroutines, worker processes
Fibers (PHP 8.1 and later)Built into PHPThe low-level building block ReactPHP and AMPHP now use for async and await

4. ReactPHP: timers and concurrent HTTP requests

Install with Composer (composer require react/event-loop react/http react/async). In current ReactPHP versions there is a single global loop, and it starts automatically at the end of your script, so you no longer create it with a factory or call run() yourself.

A timer that fires every second and stops after five ticks:

php
<?php
require __DIR__ . '/vendor/autoload.php';

use React\EventLoop\Loop;

$count = 0;
$timer = Loop::addPeriodicTimer(1.0, function () use (&$count, &$timer) {
    echo "Tick\n";
    if (++$count === 5) {
        Loop::cancelTimer($timer);
    }
});

Three HTTP requests sent at the same time, awaited together:

php
<?php
require __DIR__ . '/vendor/autoload.php';

use React\Http\Browser;
use function React\Async\await;
use function React\Promise\all;

$browser = new Browser();

try {
    $responses = await(all([
        $browser->get('https://example.com/1'),
        $browser->get('https://example.com/2'),
        $browser->get('https://example.com/3'),
    ]));
    foreach ($responses as $response) {
        echo $response->getStatusCode(), ' ', strlen((string) $response->getBody()), " bytes\n";
    }
} catch (Throwable $e) {
    echo 'Request failed: ', $e->getMessage(), "\n";
}

The total time is roughly the slowest single request, not the sum of all three. await() uses Fibers under the hood, so the code reads top to bottom while the loop keeps running.

5. Swoole and OpenSwoole: WebSocket servers

Swoole, and its community fork OpenSwoole, are PHP extensions written in C. You install them with PECL or your distribution's packages, not with Composer. They give you coroutine-based servers with many worker processes.

A small WebSocket echo server:

php
<?php
use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;
use Swoole\Http\Request;

$server = new Server('0.0.0.0', 9502);

$server->on('open', function (Server $server, Request $request) {
    echo "Connection opened: {$request->fd}\n";
});

$server->on('message', function (Server $server, Frame $frame) {
    $server->push($frame->fd, json_encode(['received' => $frame->data]));
});

$server->on('close', function (Server $server, int $fd) {
    echo "Connection closed: {$fd}\n";
});

$server->start();

The events here are open, message and close. With OpenSwoole the class names start with OpenSwoole\ instead of Swoole\; check the documentation of the extension you install. In production, put the server behind Nginx or another reverse proxy that terminates TLS, so browsers connect with wss://.

6. Best practices

Do
  • Keep handlers short and non-blocking; send heavy work to a queue or a worker process
  • Handle every rejected promise and catch exceptions inside callbacks
  • Run the process under a supervisor (systemd or Supervisor) so it restarts after a crash
  • Watch memory over days, since long-running PHP keeps everything it doesn't release
Avoid
  • Blocking calls inside the loop, such as PDO queries, sleep() or file_get_contents() on a URL
  • Global state that grows with every request in a long-running server
  • Assuming a library is async-safe because it works in normal PHP
  • Rewriting a simple request-response site as an event loop without a real need

Blocking I/O is the most common mistake. One slow database query inside a ReactPHP handler freezes every other connection until it finishes. Use async drivers built for your loop, or move the work to a separate process.

7. Running this on Domain India

Shared hosting (cPanel, DirectAdmin, Webuzo) runs PHP once per request. In-request event dispatchers from section 2 work normally there. Long-running event loops and socket servers do not fit shared hosting:

  • On cPanel and DirectAdmin, proc_open and popen are disabled, and on cPanel the socket functions fsockopen and stream_socket_client are disabled too, which ReactPHP and AMPHP networking rely on. See PHP disabled functions on shared hosting.
  • Composer cannot run on shared hosting. Run composer install on your own computer or in CI, then upload the project with its vendor/ folder.
  • You can't install PHP extensions such as Swoole on a shared server.

App Platform. Only Node.js is detected automatically; a PHP event-loop app runs from a Dockerfile you provide, and it must listen on the PORT environment variable. See getting started with the App Platform.

VPS. A Domain India VPS is self-managed with full root access, so you can install PHP, Composer, Swoole or OpenSwoole, run the process under systemd and put Nginx in front for TLS. You are responsible for updates and security on your own server.

VPS Starter
₹552.65/mo + GST
  • 1 vCPU
  • 2 GB DDR4 RAM
  • 64 GB NVMe SSD Storage
  • 2 TB Monthly Bandwidth
See plan details
App Starter
₹100/mo + GST
  • 512 MB RAM per app
  • 1 vCPU
  • 5 GB NVMe SSD
  • PostgreSQL Database
See plan details

Prices on the cards are live and exclude 18% GST.

Is PHP an event-driven language?

Not by default. PHP normally runs one script per request and exits. You can structure that code with events and listeners using a PSR-14 dispatcher, or run a long-lived event loop with ReactPHP, AMPHP, Swoole or OpenSwoole.

What is the difference between ReactPHP and Swoole?

ReactPHP is a set of pure-PHP libraries installed with Composer that provide an event loop, streams and HTTP. Swoole is a C extension installed on the server that provides coroutine-based servers and worker processes, and usually runs faster.

What are PHP Fibers?

Fibers, added in PHP 8.1, let a function pause and resume later. ReactPHP's async package and AMPHP use them so that asynchronous code can be written in a normal top-to-bottom style with await.

Can I run a ReactPHP or WebSocket server on shared hosting?

No. Shared hosting runs PHP per request, the socket and process functions such servers need are disabled on our cPanel servers, and you cannot install extensions such as Swoole. Use a VPS, or the App Platform with a Dockerfile.

Why does my event loop freeze?

Almost always because a handler makes a blocking call, such as a normal PDO query, sleep() or a synchronous HTTP request. Use async libraries made for your loop, or hand the slow work to a separate worker process.

How do I keep a PHP event loop running after a crash or reboot?

Run it as a systemd service or under Supervisor with automatic restart enabled, and write logs to a file or the journal so you can see why it stopped.

Ready to build? For an in-request event dispatcher, any PHP host works; for long-running servers, compare the VPS plans or the App Platform. Questions about your setup? Open a support ticket.

Run long-lived PHP on your own VPS

Full root access, KVM virtualization and NVMe storage for event loops, WebSocket servers and background workers.

See VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app