Skip to main content

Why Nexus

PHP was built for requests.
Nexus extends it to processes.

Modern PHP applications need daemons, workers, event streams, and real-time connections — not just request-response handlers. Nexus brings Erlang/OTP supervision and Akka-style actor semantics to the PHP you already know, without rewriting your stack.

The production gap no framework fills.

PHP-FPM handles HTTP superbly. But the moment you need a background worker, a WebSocket handler, a rate-limit counter, or a pub/sub fan-out, you reach for a message queue, a Redis key, a cron job, and maybe a supervisor daemon. Four separate systems. Four separate failure modes. Four separate ops surfaces.

That patchwork is not a design choice — it's the absence of a model for concurrent, stateful PHP processes. Every team reinvents it differently. Nexus is the model.

Actors replace the queue, the counter, the daemon loop, and the cron coordinator — each as a typed, supervised, restartable unit of work with its own mailbox and lifecycle. The supervision tree monitors all of them. One failure doesn't cascade.

Without Nexus

  • Cron + queue + daemon + Redis = four failure surfaces
  • Shared state via database rows — locking, deadlocks, retries
  • Worker crash unnoticed until the next health check
  • No type safety across process boundaries

With Nexus

  • Supervised actor tree replaces all four
  • Each actor owns its state — no shared memory
  • Supervisor restarts crashed actors automatically
  • Psalm generics enforce message types at compile time
bootstrap.php — before + after
<?php
// Before Nexus: three moving parts that break independently.
// ✗  cron fires duplicate jobs when the previous run overruns
// ✗  queue workers hold DB connections even when idle
// ✗  daemon crashes at 3 AM; cron never notices

// After Nexus: one supervised actor tree.
$system->spawn(
    Props::fromBehavior(
        Behavior::setup(function ($ctx) {
            $ctx->spawn(
                Props::fromFactory(fn() => new OrderProcessorActor())
                    ->withSupervision(SupervisionStrategy::exponentialBackoff(
                        initialBackoff: Duration::millis(100),
                        maxBackoff:     Duration::seconds(30),
                        maxRetries:     10,
                    )),
                'order-processor',
            );
            return Behavior::same();
        })
    ),
    'worker-supervisor',
);

Rip out the worker farm.

A typical "worker farm" — cron + queue + worker daemon + supervisor conf — requires four components to agree on the same shared state. When the queue backpressures, cron fires again. When the daemon crashes, nothing notices until the next health check interval.

An actor replaces all of it. It receives messages from its mailbox, processes them one at a time, and persists its own state. The supervision strategy defines exactly what happens on failure — restart with backoff, stop, escalate to the parent — and the actor tree enforces it automatically.

You get the reliability of a supervised daemon without writing a single process-management script.

Actor system guides →

Type safety from handler to actor and back.

ActorRef<T> carries the message type as a Psalm generic. Send the wrong type to tell() and Psalm reports an error before the code ships — not after a production incident.

The nexus-psalm plugin adds seven actor-specific rules on top of Psalm level 1: message immutability, mutable closure capture detection, blocking I/O inside handlers, and generic type inference for Props::from*() methods.

Every actor in the system carries its type contract in the source. No runtime surprises from mismatched message shapes.

Isolation and supervision are security properties as much as reliability properties — actor boundaries prevent TOCTOU races and shared-state corruption by construction, not by convention. See the full security model →

Psalm plugin rules →
Psalm catches this at analysis time
<?php
// Psalm catches actor mismatches at analysis time — not in production.

/** @var ActorRef<DepositRequest> $walletRef */
$walletRef->tell(new WithdrawalRequest(...)); // ← Psalm ERROR: wrong message type

// nexus-psalm rules enforce:
// • Messages to tell() must be readonly classes
// • #[FromActor] parameter types must match actor reply types
// • Props::fromFactory() closures must not capture by reference
// • Blocking I/O calls (sleep, file_get_contents) are banned inside handlers

Supervision: failure as a first-class concern.

In a traditional PHP daemon, a crashed worker takes the whole process down — or at best, a supervisor script notices after a health check interval. In Nexus, every actor has a parent. When a child crashes, the parent's supervision strategy runs immediately: restart with backoff, stop permanently, or escalate to the grandparent.

Supervision strategies are declared at spawn time with a typed decider closure. The decider receives the actual Throwable and returns a Directive. Different exception types produce different outcomes — Psalm ensures the decider is exhaustive.

"Let it crash" is not recklessness — it is the deliberate separation of the error path from the happy path. The actor that crashes is isolated. The supervision tree handles recovery. Your business logic stays clean.

Supervision model →
Props with supervision strategy
<?php
// Supervision: define failure policy at spawn time, not at call time.
// The parent actor declares what happens when a child crashes.

Props::fromFactory(fn() => new PaymentGatewayActor($client))
    ->withSupervision(
        SupervisionStrategy::exponentialBackoff(
            initialBackoff: Duration::millis(200),
            maxBackoff:     Duration::seconds(60),
            maxRetries:     5,
            decider: static fn(\Throwable $e): Directive => match(true) {
                $e instanceof NetworkException   => Directive::Restart,
                $e instanceof InvalidPaymentData => Directive::Stop,
                default                          => Directive::Escalate,
            },
        ),
    );
// Network blip? Restart with backoff.
// Bad data? Stop immediately, let the parent decide.
// Unknown? Escalate — bubble to the grandparent supervisor.
ActorSystem::create — runtime swap
<?php
// Same actor code — swap one line to change the runtime.

// Development / testing: FiberRuntime — no extensions required
$system = ActorSystem::create('app', new FiberRuntime());

// Production: SwooleRuntime — true async I/O, coroutine concurrency
$system = ActorSystem::create('app', new SwooleRuntime());

// Deterministic tests: StepRuntime — full control over message ordering
$runtime = new StepRuntime();
$system  = ActorSystem::create('app', $runtime);
$runtime->step(); // process exactly one message
$runtime->drain(); // process all queued messages

Three runtimes, one codebase.

FiberRuntime uses PHP 8.5 Fibers — no extensions required. It is the right choice for local development, CI pipelines, and unit testing. Each actor runs in its own Fiber; the scheduler yields cooperatively.

SwooleRuntime uses Swoole coroutines and true async I/O. It supports multi-worker scaling via the worker pool, thousands of concurrent WebSocket connections, and non-blocking database operations. This is the production runtime for latency-sensitive services.

StepRuntime is deterministic. You control exactly how many messages are processed and in what order. It makes complex actor interaction tests straightforward to write and deterministic to run on any CI machine.

Runtime comparison →

When not to use Nexus.

Nexus is not a universal PHP framework. The actor model adds structural overhead: message passing instead of direct calls, mailboxes, supervision configuration, and a new mental model for your team. That overhead pays off when you have long-lived state, concurrent workflows, or failure isolation requirements. It does not pay off for a straightforward CRUD service with ten endpoints.

  • Simple CRUD APIs — Symfony or Laravel with Doctrine ORM is faster to build and easier to staff.
  • Simple background jobs — if Symfony Messenger or Laravel Queues already meet your needs, they are simpler to operate.
  • Raw async I/O only — if you need a fast event loop without actor semantics, ReactPHP or bare Swoole are lighter options.
  • Teams new to PHP 8.x generics — Nexus relies on Psalm level-1 generics. If your team has not used Psalm before, budget time for the learning curve.

If you land in the "not sure" category, the when-to-use-actors decision guide walks through a concrete checklist.

How does Nexus compare?

Amphp, Spiral RoadRunner, raw Swoole, or queue+cron+workers — the comparison page lays out the tradeoffs row by row. No marketing language; each cell either has a link to the code or marks a planned feature.

View the comparison →

See it in five minutes.

The quick-start installs Nexus, spawns a counter actor, and runs it on FiberRuntime.

composer require nexus-actors/nexus