Building the OpenSwoole Runtime for AssegaiPHP

How AssegaiPHP grew from a short-lived PHP request model into a framework with an experimental OpenSwoole runtime path, why that matters, and what comes next.

Building the OpenSwoole Runtime for AssegaiPHP

AssegaiPHP started with the execution model most PHP developers already know well.

A request comes in. The framework boots the application. The request is handled. The process finishes the work and the request cycle ends.

That model is simple, reliable, and a good default.

But it also places a ceiling on what the framework can do efficiently once the application starts growing into heavier workloads, longer-lived processes, streaming use cases, and more advanced concurrency.

That is why we started work on an OpenSwoole runtime.

This post explains what changed, why it matters, what is already useful in 0.8.0, and what is still clearly out of scope for now.

Why OpenSwoole matters

OpenSwoole gives PHP applications a very different operating model.

Instead of treating each request like a fresh short-lived process, an OpenSwoole server can keep workers alive and let the application handle many requests over time.

That matters for a few reasons:

  • it reduces repeated bootstrap work
  • it opens the door to coroutine-based I/O
  • it makes long-lived runtime features possible
  • it gives the framework a path toward WebSockets, streaming, and richer service-to-service communication

For AssegaiPHP, the value is not just raw speed.

The bigger value is that OpenSwoole gives the framework a path to become a stronger runtime for modern backend applications without losing the developer workflow that already makes AssegaiPHP productive.

The challenge was not starting a server

The hardest part of this work was never going to be "make OpenSwoole start."

The real challenge was correctness.

A framework built around short-lived PHP requests can accidentally depend on assumptions like:

  • static request objects being safe enough
  • response state never outliving the current request
  • providers being effectively rebuilt all the time
  • shutdown work being tied to the end of a normal PHP request cycle
  • errors being rendered through PHP-global behavior

Those assumptions are easy to miss because they work fine in the default runtime.

A long-lived worker changes the rules.

Once the same app instance handles multiple requests, even small leaks in request state, responder state, or provider scope become real bugs.

So the OpenSwoole work started by treating runtime correctness as the foundation.

What changed inside the framework

The OpenSwoole work pushed changes through several layers of AssegaiPHP.

1. A real runtime abstraction

The first step was teaching the framework that "how HTTP runs" is a replaceable concern.

That gave us a default PHP runtime and an OpenSwoole runtime instead of one hardcoded request model.

This matters because it keeps the application model stable while letting the transport and server behavior evolve.

2. Request-scoped runtime state

We separated application-scoped state from request-scoped state.

That meant moving request, response, responder, and emitter handling onto a request lifecycle that can be refreshed safely for every request instead of assuming there is only ever one active request in the process.

This was one of the most important changes because it protects the framework from stale request leakage when workers stay alive.

3. Reusable application graphs

Long-lived runtimes only become valuable if the framework can safely reuse the expensive parts of the application graph.

So we taught the app to prepare modules, providers, controllers, and middleware once, then reuse that graph across later requests.

That reduces repeated work while still rebuilding the request scope cleanly for each incoming request.

4. Lifecycle hooks that make sense in long-lived workers

As part of the runtime work, AssegaiPHP gained clearer lifecycle hooks like:

  • module initialization
  • application bootstrap
  • application shutdown

That was important not only for OpenSwoole, but for package integration too. Features like event listener registration benefit from a clear application bootstrap point.

5. Runtime-aware request and response bridges

The OpenSwoole runtime now builds a request context from the server callback and emits the response through a dedicated OpenSwoole emitter instead of pretending the PHP global request model is always the source of truth.

That lets the framework keep one application model while adapting cleanly to a different runtime environment.

What we hardened along the way

A runtime like this is only useful if it behaves correctly under pressure.

So a lot of the work was not glamorous. It was test-driven hardening.

We added coverage around things like:

  • invalid runtime configuration failing early
  • request scope hydration from runtime request data
  • lifecycle hooks running once where they should
  • shutdown hooks working for both OpenSwoole and the default PHP runtime
  • sequential requests inside one worker staying isolated from each other
  • a failed request not poisoning a later request in the same worker
  • runtime-level failures still going through the framework error pipeline

That last point matters more than it sounds.

If an exception escapes the wrong layer in a long-lived runtime, you do not just want a raw crash. You want the framework to render the error in a controlled way and leave the worker in a sane state for the next request.

What this means for developers today

If you are using AssegaiPHP today, the OpenSwoole work gives you an experimental runtime path you can already explore.

If you are not interested in OpenSwoole yet, 0.8.0 does not force a runtime switch on you. The default PHP runtime still remains the normal path.

The current direction looks like this:

{
  "development": {
    "server": {
      "runtime": "openswoole",
      "host": "127.0.0.1",
      "port": 9501,
      "openswoole": {
        "workerNum": 2,
        "taskWorkerNum": 1,
        "maxRequest": 250,
        "enableCoroutine": true,
        "hookFlags": ["file", "sleep"]
      }
    }
  }
}

And then:

assegai serve --runtime=openswoole

That gives you a real runtime target to begin validating against.

But it is important to say this clearly:

0.8.0 ships runtime foundations and an experimental OpenSwoole path. It does not ship the full async story.

The framework now has the right seams, lifecycle model, request isolation, and worker-safety improvements for OpenSwoole to make sense.

That is real progress.

It also means we can talk about the runtime honestly instead of treating it as a vague future idea.

Just as importantly, the release does not force a new execution model onto existing projects. If you are happy on the default PHP runtime, you can stay there and let 0.8.0 read as framework cleanup plus a new runtime option rather than a mandatory runtime shift.

Why this work matters beyond OpenSwoole

One of the nice side effects of this project is that it improved AssegaiPHP even for the default runtime.

Work like this forced the framework to become cleaner about:

  • lifecycle boundaries
  • request scope
  • response emission
  • package bootstrapping
  • application reuse

That kind of cleanup pays off everywhere.

A framework becomes stronger when its assumptions are made explicit.

OpenSwoole gave us the pressure needed to make some of those assumptions visible and fix them properly.

Where we go from here

There is still work to do.

The next major steps are:

  • keep expanding runtime integration coverage
  • keep reducing the remaining static and process-global assumptions in the stack
  • improve the developer story around long-lived runtimes
  • use this foundation to support richer async-friendly features later

That future can include things like:

  • stronger queue and background processing flows
  • streaming responses and real-time transport features
  • WebSockets
  • better long-lived worker tooling
  • eventually, cleaner paths toward microservice-style transports

Those are future steps, not hidden promises inside 0.8.0.

But the order matters.

The goal is not to pile features onto an unstable base.

The goal is to make the base solid enough that those features feel natural when they arrive.

Final thought

The OpenSwoole runtime work is significant because it changes what AssegaiPHP can grow into.

It moves the framework from a single execution model toward a proper runtime architecture.

That is a deeper shift than adding another server option.

It is the kind of work that gives future capabilities somewhere stable to stand.

And that is exactly why it is worth doing carefully.