DEV Community

Cover image for PHP: Do you need async?
spO0q
spO0q

Posted on

PHP: Do you need async?

PHP runs per-request in a web server, but you will likely have to build event-driven applications at some point, which involves non-blocking code.

In that case, all tasks must be designed to be executed asynchronously, so they don't have to wait for the completion of other tasks.

However, PHP does not have built-in async/await keywords.

The Event Loop

The Event Loop is a critical concept.

This mechanism constantly monitors and processes events in a loop.

When event happen, it dispatches them to the matching event handlers, then the event handlers executes, which may trigger or schedule other events.

In other words, the Event Loop processes events sequentially, one at a time.

The Event Loop is not built-in. You either need to implement it or use a framework, such as ReactPHP.

Simple as that:

$loop = React\EventLoop\Loop::get();
Enter fullscreen mode Exit fullscreen mode

DIY

If you want to do it yourself, be aware you'll have to monitor possible leaks, ensure everything is in sync, manage lifecycle, probably build your own event loop, and watch many other elements, like deadlocks and memory accesses, we won't cover here.

It's not impossible but quite challenging.

You may start with Fibers (introduced in PHP 8.1) that are the building blocks of various third-party libraries, like Swoole.

I/O: async vs. parallel programming

Most developers write sequential PHP (synchronous).

However, the following input/output (I/O) operations may benefit from asynchronous programming:

  • multiple HTTP requests
  • multiple database queries

The word "non-blocking" refer to operations that take time, and thus, cannot occur at the same time.

It is not parallel programming, as such approach would deal with true simultaneous execution.

Instead, we start a task and continue other work while waiting for the task to complete.

Coroutines

You can use Swoole to run concurrent database operations without blocking:

co::set(['hook_flags' => OpenSwoole\Runtime::HOOK_TCP]);

co::run(function() {
    go(function () use ($pdo, $limit, $offset) {
        $statement = $pdo->prepare("SELECT * FROM users LIMIT :limit, :offset");
        $statement->execute(['limit' => $limit, 'offset' => $offset]);
        $data = $statement->fetchAll();
        // Process $data...
    });
});
Enter fullscreen mode Exit fullscreen mode

Here, blocking PDO run in a non-blocking coroutine context.

N.B.: Swoole is third-party library that provides useful APIs, and its syntax that is very similar to Go-lang

Design Patterns vs. asynchronous

How do you structure your code for event-driven architecture?

The Observer pattern can help.

At its core, the pattern simply consists of having a list of observers and a mechanism that notifies them when events happen.

You could use a private array of observer callables. On change, the code would loop through and calls a specific method on each observer.

You may also want to pass some payload.

Implementing the following methods can do the job.

attach/detach

Observers would be added or removed from the list at runtime when they subscribe/unsubscribe

notify

Would push the notification to all observers:

public function notify() {
    foreach ($this->observers as $observer) {
        $observer->update($this);
    }
}
Enter fullscreen mode Exit fullscreen mode

dispatch

A bit more complex, as observers would not be notified of all changes but only those they care about:

public function dispatch(string $eventName, $eventPayload) {
    if (isset($this->observers[$eventName])) {
        foreach ($this->observers[$eventName] as $observer) {
            $observer->handle($eventPayload);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You could name it "listener" instead of "observer".

So, what to do?

notify() looks basic, whereas dispatch() involves registration by event name, but it's more granular.

More concrete examples can be found here.

Alternatively, popular frameworks such as Symfony or Laravel provide event/observer ecosystems.

Queues vs. asynchronous PHP

Queues/jobs run tasks asynchronously with tasks stored (e.g., in a database or any other storage) until a worker pick them up.

If you need to setup resource-intensive background work, like sending bulk emails or processing videos, then queues/jobs are best suited for that.

I would add that queues/jobs allow you to implement some retry mechanism quite conveniently, whereas async runs in-memory within a single process.

If the async process crashes or restarts, retry state is usually lost.

Wrap this up

  • use simpler implementation of the Observer pattern when you need immediate notifications with minimal infrastructure overhead
  • use queues/jobs when you need to decouple task initiation from execution, and handle longer or resource-intensive operations
  • use async programming when you want to improve concurrency within the same request (e.g., many HTTP requests in one script)

There are existing tools to ease the implementation, but the use cases can be tricky.

References

Top comments (0)