Key Takeaways
- Event loops in PHP are a programming construct that waits for and dispatches events or messages in a program, particularly useful for handling asynchronous operations.
- Traditional PHP programming is synchronous, executing one operation at a time and waiting for each operation to complete before moving on. Event loops, on the other hand, allow for asynchronous programming where operations can be initiated and then put aside until a result is ready, allowing other operations to be executed in the meantime.
- Libraries such as ReactPHP or Amp provide the necessary interfaces and classes to create and manage an event loop in PHP, allowing for better utilization of resources and faster response times, especially in applications that need to handle a large number of simultaneous connections.
- While event loops can provide significant performance benefits, they also introduce additional complexity into the application, requiring a different programming style and potentially making the code more difficult to understand and debug. Not all tasks are suitable for asynchronous processing, and some tasks may be more difficult to implement in an event loop.
PHP developers are always waiting for something. Sometimes we’re waiting for requests to remote services. Sometimes we’re waiting for databases to return rows from a complex query. Wouldn’t it be great if we could do other things during all that waiting?
If you’ve written some JS, you’re probably familiar with callbacks and DOM events. And though we have callbacks in PHP, they don’t work in quite the same way. That’s thanks to a feature called the event loop.
We’re going to look at how the event loop works, and how we can use the event loop in PHP.
We’re going to see some interesting PHP libraries. Some would consider these not yet stable enough to use in production. Some would consider the examples presented as “better to do in more mature languages”. There are good reasons to try these things. There are also good reasons to avoid these things in production. The purpose of this post is to highlight what’s possible in PHP.
Where Things Go To Wait
To understand event loops, let’s look at how they work in the browser. Take a look at this example:
function fitToScreen(selector) {
var element = document.querySelector(selector);
var width = element.offsetWidth;
var height = element.offsetHeight;
var top = "-" + (height / 2) + "px";
var left = "-" + (width / 2) + "px";
var ratio = getRatio(width, height);
setStyles(element, {
"position": "absolute",
"left": "50%",
"top": "50%",
"margin": top + " 0 0 " + left,
"transform": "scale(" + ratio + ", " + ratio + ")"
});
}
function getRatio(width, height) {
return Math.min(
document.body.offsetWidth / width,
document.body.offsetHeight / height
);
}
function setStyles(element, styles) {
for (var key in styles) {
if (element.style.hasOwnProperty(key)) {
element.style[key] = styles[key];
}
}
}
fitToScreen(".welcome-screen");
This code requires no extra libraries. It will work in any browser that supports CSS scale transformations. A recent version of Chrome should be all you need. Just make sure the CSS selector matches an element in your document.
These few functions take a CSS selector and center and scale the element to fit the screen. What would happen if we threw an Error
inside that for
loop? We’d see something like this…
We call that list of functions a stack trace. It’s what things look like inside the stack browsers use. They’ll handle this code in steps…
This is like how PHP uses a stack to store context. Browsers go a step further and provide WebAPIs for things like DOM events and Ajax callbacks. In its natural state, JavaScript is every bit as asynchronous as PHP. That is: while both look like they can do many things at once, they are single threaded. They can only do one thing at a time.
With the browser WebAPIs (things like setTimeout and addEventListener) we can offload parallel work to different threads. When those events happen, browsers add callbacks to a callback queue. When the stack is next empty, browses pick the callbacks up from the callback queue and execute them.
This process of clearing the stack, and then the callback queue, is the event loop.
Life Without An Event Loop
In JS, we can run the following code:
setTimeout(function() {
console.log("inside the timeout");
}, 1);
console.log("outside the timeout");
When we run this code, we see outside the timeout
and then inside the timeout
in the console. The setTimeout
function is part of the WebAPIs that browsers give us to work with. When 1 millisecond has passed, they add the callback to the callback queue.
The second console.log
completes before the one from inside the setTimeout
starts. We don’t have anything like setTimeout
in standard PHP, but if we had to try and simulate it:
function setTimeout(callable $callback, $delay) {
$now = microtime(true);
while (true) {
if (microtime(true) - $now > $delay) {
$callback();
return;
}
}
}
setTimeout(function() {
print "inside the timeout";
}, 1);
print "outside the timeout";
When we run this, we see inside the timeout
and then outside the timeout
. That’s because we have to use an infinite loop inside our setTimeout
function to execute the callback after a delay.
It may be tempting to move the while
loop outside of setTimeout
and wrap all our code in it. That might make our code feel less blocking, but at some point we’re always going to be blocked by that loop. At some point we’re going to see how we can’t do more than a single thing in a single thread at a time.
While there is nothing like setTimeout
in standard PHP, there are some obscure ways to implement non-blocking code alongside event loops. We can use functions like stream_select
to create non-blocking network IO. We can use C extensions like EIO to create non-blocking filesystem code. Let’s take a look at libraries built on these obscure methods…
Icicle
Icicle is library of components built with the event loop in mind. Let’s look at a simple example:
use Icicle\Loop;
Loop\timer(0.1, function() {
print "inside timer";
});
print "outside timer";
Loop\run();
This is with icicleio/icicle
version 0.8.0
.
Icicle’s event loop implementation is great. It has many other impressive features; like A+ promises, socket, and server implementations.
Icicle also uses generators as co-routines. Generators and co-routines are a different topic, but the code they allow is beautiful:
use Icicle\Coroutine;
use Icicle\Dns\Resolver\Resolver;
use Icicle\Loop;
$coroutine = Coroutine\create(function ($query, $timeout = 1) {
$resolver = new Resolver();
$ips = (yield $resolver->resolve(
$query, ["timeout" => $timeout]
));
foreach ($ips as $ip) {
print "ip: {$ip}\n";
}
}, "sitepoint.com");
Loop\run();
This is with icicleio/dns
version 0.5.0
.
Generators make it easier to write asynchronous code in a way that resembles synchronous code. When combined with promises and an event loop, they lead to great non-blocking code like this!
ReactPHP
ReactPHP has a similar event loop implementation, but without all the interesting generator stuff:
$loop = React\EventLoop\Factory::create();
$loop->addTimer(0.1, function () {
print "inside timer";
});
print "outside timer";
$loop->run();
This is with react/event-loop
version 0.4.1
.
ReactPHP is more mature than Icicle, and it has a larger range of components. Icicle has a way to go before it can contend with all the functionality ReactPHP offers. The developers are making good progress, though!
Conclusion
It’s difficult to get out of the single-threaded mindset that we are taught to have. We just don’t know the limits of code we could write if we had access to non-blocking APIs and event loops.
The PHP community needs to become aware of this kind of architecture. We need to learn and experiment with asynchronous and parallel execution. We need to pirate these concepts and best-practices from other languages who’ve had event loops for ages, until “how can I use the most system resources, efficiently?” is an easy question to answer with PHP.
Stay tuned for a more practical implementation of Icicle, coming soon!
Frequently Asked Questions (FAQs) about Event Loops in PHP
What is the role of an event loop in PHP?
An event loop in PHP is a programming construct that waits for and dispatches events or messages in a program. It works by keeping track of each live event in response to external stimuli and dispatching them when they are complete. This is particularly useful in PHP for handling asynchronous operations, where you want to initiate an operation and then continue processing without waiting for the operation to complete.
How does an event loop differ from traditional PHP programming?
Traditional PHP programming is synchronous, meaning it executes one operation at a time, in the order they are written, and must wait for each operation to complete before moving on to the next. An event loop, on the other hand, allows for asynchronous programming. This means that operations can be initiated and then put aside until a result is ready, allowing other operations to be executed in the meantime.
How can I implement an event loop in my PHP application?
Implementing an event loop in your PHP application involves using a library that provides this functionality, such as ReactPHP or Amp. These libraries provide the necessary interfaces and classes to create and manage an event loop. You can then use this event loop to handle asynchronous tasks in your application.
What are the benefits of using an event loop in PHP?
Using an event loop in PHP can greatly improve the performance and responsiveness of your application. It allows you to handle multiple tasks concurrently, rather than sequentially, which can lead to better utilization of resources and faster response times. This is particularly beneficial in applications that need to handle a large number of simultaneous connections, such as chat servers or real-time data feeds.
Are there any drawbacks to using an event loop in PHP?
While event loops can provide significant performance benefits, they also introduce additional complexity into your application. They require a different programming style and can make code more difficult to understand and debug. In addition, not all tasks are suitable for asynchronous processing, and some tasks may be more difficult to implement in an event loop.
Can I use an event loop with PHP frameworks like Laravel or Symfony?
Yes, it is possible to use an event loop with PHP frameworks like Laravel or Symfony, although it may require some additional configuration. Both frameworks are designed to work with synchronous PHP code, but they can be adapted to work with an event loop for handling asynchronous tasks.
How does an event loop handle errors?
Error handling in an event loop can be more complex than in synchronous PHP code. Because tasks are executed asynchronously, errors may not be caught immediately. Instead, you will typically need to provide a callback function that will be called when an error occurs.
Can I use an event loop in a PHP CLI script?
Yes, you can use an event loop in a PHP CLI (Command Line Interface) script. In fact, this is a common use case for event loops, as CLI scripts often need to perform multiple tasks concurrently.
How does an event loop work with PHP’s garbage collection?
PHP’s garbage collection works independently of the event loop. However, because the event loop keeps references to all active tasks, these tasks will not be garbage collected until they are completed. This means that you need to be careful to avoid memory leaks in your event loop code.
Can I use an event loop with PHP’s built-in server?
Yes, you can use an event loop with PHP’s built-in server. However, keep in mind that the built-in server is not designed for production use and may not provide the same level of performance or reliability as a dedicated web server.
Christopher is a writer and coder, working at Over. He usually works on application architecture, though sometimes you'll find him building compilers or robots.