Introduction to Asynchronous Programming in PHP
Traditionally, PHP is considered a synchronous language: each request is executed sequentially, blocking execution until an I/O operation (e.g., reading a file or a database query) completes. However, with the growing popularity of high-load web applications and microservices, the need arose to handle tens of thousands of concurrent connections without scaling the number of processes. Asynchronous programming solves this problem by allowing operations to run concurrently within a single thread.
In this guide, we will break down how asynchrony works in PHP, what tools are available, and how to properly build non-blocking applications. You will learn about generators, coroutines, the ReactPHP, Amp, and Swoole libraries, as well as the new Fibers extension.
Fundamentals of Asynchrony: Event Loop and Non-blocking I/O
Asynchronous programming is based on the concept of the Event Loop. This is an infinite loop that waits for and processes events: network requests, completion of I/O operations, timers. Instead of waiting for an operation to finish, the code registers a callback that is executed when the data is ready. This prevents wasting CPU time on waiting.
PHP traditionally uses blocking functions (e.g., file_get_contents() or sleep()). For asynchronous work, you need to replace them with non-blocking alternatives. Here is a simple example using the ReactPHP library:
<?phprequire 'vendor/autoload.php';
use React\\EventLoop\\Factory;use React\\Promise\\Promise;
$loop = Factory::create();
// Asynchronous file reading$promise = new Promise(function ($resolve, $reject) use ($loop) { $loop->addTimer(0.001, function () use ($resolve) { $content = file_get_contents('/etc/hosts'); $resolve($content); });});
$promise->then(function ($data) { echo "Read: " . strlen($data) . " bytes";});
echo "This code will execute immediately, without waiting for the file to be read";$loop->run();?>In this example, addTimer registers a task that will be executed in the next tick of the loop. The Event Loop is not blocked, and we see the message immediately, while the reading result appears later.
Generators and Coroutines: Flow Control
Generators (yield) appeared in PHP 5.5 and allow pausing a function's execution, returning intermediate values. They form the basis for coroutines — lightweight threads that can voluntarily yield control. Combined with the Event Loop, generators allow writing code that looks synchronous but executes asynchronously.
Let's look at an example with the Amp library, which uses coroutines:
<?phprequire 'vendor/autoload.php';
use Amp\\Loop;use Amp\\Delayed;
// Asynchronous coroutine$coroutine = function () { echo "Coroutine start"; // Pause for 1 second without blocking yield new Delayed(1000); echo "1 second passed"; yield new Delayed(500); echo "Another 0.5 seconds passed";};
Loop::run(function () use ($coroutine) { // Run the coroutine $result = yield from $coroutine(); echo "Coroutine completed";});?>In this code, yield new Delayed(1000) pauses the coroutine's execution, returning control to the Event Loop. After 1 second, execution resumes from the same point. This allows handling thousands of such coroutines simultaneously without creating new processes.
Libraries and Tools for Asynchronous PHP
Several mature libraries exist for working with asynchrony in PHP. Let's look at the three most popular ones.
ReactPHP
ReactPHP is the oldest and most well-known library. It provides an Event Loop, Promise (analogous to JavaS