Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Supervision Laravel Package

phppro/supervision

A lightweight supervision layer for Laravel and PHP applications, helping monitor long-running workers and scheduled jobs, detect failures, and keep background processes running with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Verify the package before writing any code against it. phppro/supervision has zero stars, zero dependents, and a description that mostly restates the vendor name. "Supervision" plausibly means process health checks, restart handling, and failure hooks for long-running workers — the description.md says exactly that — but confirm against source, not the tagline.

composer show phppro/supervision --all      # versions, PHP/Laravel constraints, requires
git clone https://github.com/phppro/supervision && cd supervision
cat composer.json                           # PSR-4 namespace, extra.laravel.providers, license
find src -type f -name '*.php' | sort       # what's actually in the box
ls config/ tests/ .github/workflows/ 2>/dev/null

Read in this order:

  1. composer.json — the extra.laravel.providers key tells you whether the service provider auto-discovers (normal Laravel) or must be registered manually (framework-agnostic wrapper). The require block tells you whether it pins Laravel to the current major, which becomes a platform-wide upgrade blocker later.
  2. The service provider — this is where the package binds its supervisor, merges config, and publishes anything. Every extension point you'll use is registered here.
  3. config/supervision.php (or equivalent) — the shape of a "supervised process" definition: name, command/callable, interval, retries, backoff, failure callback. That schema is the day-to-day API.
  4. The contracts directory — look for a HealthCheck, Probe, or Supervised interface. That's where you plug in custom logic without forking.

Install and publish:

composer require phppro/supervision
php artisan vendor:publish --provider="Phppro\Supervision\SupervisionServiceProvider" --tag=config

If vendor:publish produces nothing, the package is config-by-code (constructor arguments / a static registry) and you'll wire it in a service provider instead.

First use case — the one that actually pays for itself. Don't start with an HTTP health endpoint. Start by supervising the thing most likely to silently die: a queue worker or a scheduled daemon that stalls without crashing.

// config/supervision.php (illustrative — confirm keys against source)
return [
    'processes' => [
        'queue:default' => [
            'command'  => 'queue:work --queue=default --sleep=3 --tries=3',
            'interval' => 10,        // seconds between health evaluations
            'timeout'  => 60,        // kill and restart if no heartbeat
            'retries'  => 5,
            'backoff'  => [5, 15, 60, 300, 900],
        ],
    ],
];

Then confirm it works in isolation before wiring it into production:

php artisan supervision:list        # or whatever the command is named — check src/Console
php artisan supervision:run --once  # single evaluation pass, inspect output

Do not proceed past this point until you can answer: what does a "health check" concretely evaluate, where does its result go (log, DB, Redis, external endpoint), and does the package require an account or API key to function. A monitoring package that phones home is a supply-chain decision, not a dependency decision.

Implementation Patterns

Pattern 1: Config-driven process registry

The idiomatic usage is declarative — processes live in config, and the supervisor reads them at boot. This is what makes the package usable day-to-day: adding a supervised worker is a config change, not a code change.

// config/supervision.php
'processes' => [
    'horizon' => [
        'command'  => 'horizon',
        'strategy' => 'heartbeat',   // vs 'pid' or 'exit-code'
        'interval' => 15,
    ],
    'reverb' => [
        'command'  => 'reverb:start',
        'strategy' => 'heartbeat',
        'interval' => 15,
    ],
],

Watch for the distinction between liveness ("is the process alive?") and readiness ("is it doing useful work?"). A queue worker can be alive and wedged on a poisoned job. If the package's strategies are liveness-only, you need a custom heartbeat hook (Pattern 3).

Pattern 2: Heartbeat from inside the long-running process

A supervisor polling ps or a PID only catches hard crashes. To catch stalls, the supervised process itself must tick a heartbeat, and the supervisor must compare the timestamp. In a queue worker, that means a job middleware or a Looping callback:

// app/Providers/AppServiceProvider.php
use Phppro\Supervision\Facades\Supervisor; // name must be confirmed from source

public function boot(): void
{
    Queue::looping(function () {
        Supervisor::heartbeat('queue:default');
    });
}

The interval between looping() callbacks is governed by --sleep and by how long the current job takes. Set the supervisor's timeout comfortably above your p99 job duration, or you'll restart workers mid-job and create duplicate processing.

Pattern 3: Custom checks via the extension hook

The description promises "extensible hooks for custom monitoring logic." In practice this is where you encode domain health: is the payment gateway reachable, is the cache warm, is the last import still running. Implement the package's check interface and register it in config.

namespace App\Supervision\Checks;

class QueueDepthCheck implements HealthCheckInterface
{
    public function __construct(private Redis $redis) {}

    public function name(): string { return 'queue-depth'; }

    public function check(): HealthResult
    {
        $depth = $this->redis->llen('queues:default');

        return $depth > 5000
            ? HealthResult::unhealthy("queue depth {$depth} exceeds threshold")
            : HealthResult::healthy();
    }
}

Keep checks cheap and non-blocking. A check that makes an outbound HTTP call with a 30s timeout will stall the supervisor loop and cause every other process to appear dead. Wrap outbound probes in a short timeout and cache the result for the evaluation interval.

Pattern 4: Failure handling without restart storms

Naive restart-on-failure turns a transient dependency outage into a fork bomb. Use the backoff array if the package supports it; otherwise implement the guard in your failure hook.

'processes' => [
    'importer' => [
        'command'  => 'import:run',
        'retries'  => 3,
        'backoff'  => [10, 60, 300],
        'on_failure' => fn ($process, $exception) => Log::channel('supervision')
            ->critical('importer failed', ['attempt' => $process->attempts(), 'error' => $exception->getMessage()]),
    ],
],

Route the failure callback to a dedicated log channel. The worst outcome of a supervision layer is that its own alerts are buried in application noise.

Pattern 5: Framework-agnostic usage alongside the Laravel provider

If the package truly is framework-agnostic at its core (description claims this), you can reuse the same supervision objects in a standalone worker script or a non-Laravel microservice. The pattern is: instantiate the supervisor directly, register the same check classes, run its loop.

// bin/supervise.php — outside Laravel
require __DIR__ . '/../vendor/autoload.php';

$supervisor = new \Phppro\Supervision\Supervisor(config('supervision.processes'));
$supervisor->onFailure(fn ($e) => error_log($e->getMessage()));
$supervisor->run(); // blocking loop

This is the cleanest escape hatch: the Laravel service provider is a convenience wrapper, and the underlying supervisor is portable. Verify this before relying on it — if the framework-agnostic claim is aspirational, the provider is a hard dependency.

Pattern 6: Scheduling the supervisor itself

Don't run the supervisor from Kernel::schedule() with a one-shot check and expect continuous supervision — the scheduler fires and exits. Two viable shapes:

  • A long-running supervisor daemon managed by your OS (systemd, Supervisor) — the classic setup, and the one this package most likely assumes.
  • A scheduled one-shot evaluation (supervision:run --once every minute) that checks heartbeats written by other processes and triggers restarts externally.

Pick one and be consistent. Mixing both produces double restarts and confusing logs.

Gotchas and Tips

Treat this as unvetted until proven otherwise. Zero stars and zero dependents means you are likely the first production user. Pin the exact version, and consider forking into a private registry with a composer.json repositories entry — because a single-maintainer package with no release history is one abandoned commit away from a broken composer update across your estate.

"repositories": [{ "type": "vcs", "url": "[email protected]:yourorg/supervision.git" }],
"require": { "phppro/supervision": "dev-main#a1b2c3d" }

The memory trap in long-lived supervisors. A supervisor running for weeks that appends every check result to an in-memory array will eventually OOM — and it will do so at 3am, taking the supervision down silently. Confirm the package bounds its history buffer or flushes to a sink. If it doesn't, cap retention in your failure hook or run the supervisor under a memory limit with a scheduled restart.

Silent supervisor death. Nothing is watching the watcher. Whatever you do with this package, add one external check — a systemd Restart=always, a ping to Healthchecks.io, or a simple cron that writes a timestamp — that alerts when the supervisor stops reporting. The failure mode where supervision quietly ceases and everyone assumes monitoring is live is worse than no monitoring.

Blocking checks block everything. The supervisor loop is almost certainly serial. One slow health check delays evaluation of every subsequent process, and the loop's own timeout may then restart a perfectly healthy worker. Keep each check under a second; measure the total evaluation pass time when you add your tenth process.

Signals and in-flight work. SIGTERM handling for queue workers is subtle — a worker must finish its current job before exiting, or you get duplicate processing and lost jobs. If the package sends SIGKILL on timeout, set timeouts above your longest job. Check whether it sends SIGTERM first and waits; if it doesn't, that's a fork-worthy patch.

Config caching. php artisan config:cache serializes closures — they can't survive it. If your process definitions or failure handlers use closures ('on_failure' => fn (...) => ...), deployment will break with a cryptic serialization error. Use invokable classes or class-string references in any config that must survive config:cache, and test explicitly with config:cache and config:clear in your deploy pipeline.

The check interface is your testability seam. The most valuable thing you can do early is write a fake HealthCheckInterface implementation in tests — a FakeHealthCheck returning healthy/unhealthy on demand. Then you can test failure handling and backoff behavior without spawning real processes. If the package doesn't make the interface swappable, that's a design smell to note before adoption.

Where to extend vs. where to wrap. Prefer wrapping the package behind your own small interface (App\Supervision\SupervisionManager) rather than calling Supervisor:: or the underlying classes directly across the codebase. When (not if) you replace or fork it, that's one file to change. This is the single highest-leverage decision in the whole integration.

Overlap with existing tooling. Likely overlaps: Laravel Horizon (queue supervision, auto-restarts, failed job handling), Laravel Pulse (production metrics), Supervisor/systemd (process management), Sentry (exception alerting). Adding phppro/supervision on top of these duplicates signal and creates two sources of truth during an incident. If Horizon is already running, the honest question is what this adds — process supervision for non-queue daemons (Reverb, custom consumers) is the plausible niche.

Debugging a stuck supervisor. Check in this order: (1) is the supervisor process itself alive (ps aux | grep supervision); (2) is the heartbeat file/row/Redis key updating; (3) is the health check returning unhealthy for an environmental reason (network ACL, credential expiry) rather than a code reason; (4) is the failure callback itself throwing, which can cascade. Add a --verbose or debug log level early — you'll want per-evaluation output showing every process and its result.

Confirm the emission target before production. The single most important question: where does supervision state live, and is it required to be reachable? If the package writes synchronously to a downed backend, monitoring becomes an outage amplifier — a network partition takes down process supervision, which takes down the workers. Ensure writes are fire-and-forget or locally buffered.

License and provenance. Read LICENSE and the commit history before committing engineering time. If the package is effectively a client for a commercial backend, adopting it is a vendor decision, not a library decision — route it through whoever owns procurement.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console