Skip to Content
Course content

158: Swoole for Async PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've lived in the world of PHP-FPM. You're used to the cycle: a request comes in, PHP boots up, executes your script, sends the response, and then promptly dies. It's a clean, shared-nothing architecture that saves us from a lot of memory leak headaches, but it's also incredibly wasteful when you're dealing with I/O-bound tasks.

The sequential bottleneck

Imagine you're building a dashboard that needs to aggregate data from three different external sources: a GitHub API for commits, a Stripe API for recent payments, and a custom internal CRM. In a traditional PHP setup, your code looks something like this:

$githubData = $httpClient->get('https://api.github.com/...');
$stripeData = $httpClient->get('https://api.stripe.com/...');
$crmData = $httpClient->get('https://crm.internal/...');

return json_encode(['github' => $githubData, 'stripe' => $stripeData, 'crm' => $crmData]);

On the surface, this is readable and straightforward. But here's the problem: it's blocking. If each of those APIs takes 300ms to respond, your user is staring at a loading spinner for nearly a full second. Your CPU isn't actually doing any work for 99% of that time; it's just sitting there, idling, waiting for a network packet to arrive. In a high-traffic environment, this is how you exhaust your PHP-FPM worker pool and start seeing 504 Gateway Timeouts, even though your server's CPU usage is barely at 5%.

Breaking the line with Swoole Coroutines

This is where Swoole changes the game. Instead of the "boot and die" model, Swoole runs PHP as a long-lived process with an event loop, similar to how Node.js or Go operate. The magic happens with coroutines. A coroutine allows PHP to "pause" the execution of a function when it hits an I/O operation and switch to another task, coming back only when the data is actually ready.

If we rewrite that aggregator using Swoole's coroutine scheduler, we can fire off all three requests concurrently. I usually wrap these in a waitGroup or use Swoole\Coroutine\go() to handle the concurrency:

use Swoole\Coroutine;
use Swoole\Coroutine\WaitGroup;

Coroutine\run(function () {
    $wg = new WaitGroup();
    $results = [];

    $urls = [
        'github' => 'https://api.github.com/...',
        'stripe' => 'https://api.stripe.com/...',
        'crm'    => 'https://crm.internal/...',
    ];

    foreach ($urls as $key => $url) {
        $wg->add();
        Coroutine\go(function () use ($wg, $url, $key, &$results) {
            $client = new Swoole\Coroutine\Http\Client($url);
            $client->get('/');
            $results[$key] = $client->body;
            $wg->done();
        });
    }

    $wg->wait();
    echo json_encode($results);
});

Now, instead of 300ms + 300ms + 300ms, your total wait time is roughly 300ms (the time of the slowest request). We've essentially collapsed the timeline. I've used this pattern to turn API endpoints that took 5 seconds to load into endpoints that take 400ms, without changing a single thing about the external APIs themselves.

The cost of staying alive

Now, I have to give you a warning. You can't just drop Swoole into a legacy Laravel or Symfony app and expect a magic speed boost. Because the PHP process doesn't die after the request, state persists. This is the "Gotcha" that trips up almost every developer moving to async PHP.

In PHP-FPM, if you accidentally add an item to a global array or a static property, it doesn't matter—it's wiped clean for the next request. In Swoole, that static array will grow and grow until you hit a memory limit and the worker crashes. You have to be incredibly disciplined about memory management and avoid using global or static variables to store request-specific data. You're no longer writing a script; you're writing a server application.




📋 Practical Task

Build a Concurrent Crypto Price Aggregator

Your task is to create a Swoole-powered script that fetches the current price of three different cryptocurrencies (e.g., Bitcoin, Ethereum, and Solana) from a public API (like CoinGecko or Binance).

  • Implement the solution using Swoole\Coroutine\go() to ensure the requests happen concurrently.
  • Use a Swoole\Coroutine\WaitGroup to ensure the script only outputs the final JSON result once all three API calls have completed.
  • Measure the execution time using microtime(true) at the start and end of the Coroutine\run block to prove that the total time is closer to a single request's duration than the sum of all three.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.