m6web/php-process-manager-bundle
A Symfony bundle for managing long-running PHP background processes and workers. Configure commands, start and stop them, monitor status, and handle graceful restarts with logging, so daemons and queue workers stay supervised without custom glue code.
First, try to install it — then stop. The honest first step with this package in a Laravel app is the dependency-resolution attempt:
composer require m6web/php-process-manager-bundle
Expect a wall of conflicts. The last release is from 2016-06-23, it requires the monolithic symfony/symfony (2.x/3.x era) rather than split components, and modern Laravel pins symfony/process ^6/^7. --ignore-platform-reqs will not save you: the removed ProcessBuilder and changed Process signatures mean the code fatals at runtime even if it resolves. The repo is archived, :no_entry: DEPRECATED is in the description, and it has 0 dependents — no one is maintaining a compatibility shim.
So the realistic "getting started" is read it, don't run it. Look at, in order:
README.md — short, and the only real documentation.DependencyInjection/Configuration.php — this is the entire config surface (worker name, command, max_process, timeout/sleep, start/stop behaviour).ProcessManager/ — the interface plus the concrete manager that wraps Symfony\Component\Process\Process and manages PID files.Command/ — the console commands, roughly pm:list / pm:start / pm:stop / pm:status / pm:restart (verify exact names against the README; the bundle predates console naming conventions).Your first use case is not "install this bundle" — it's the problem it solves: declare N long-running workers in config, then start/stop/status/restart them from one CLI command with PID-file supervision. Copy that shape into an Artisan command and you're done in an afternoon.
A. Config-declared worker registry. The bundle's core idea is a YAML list of process managers, each with a command and a max instance count. In Laravel that becomes config/workers.php:
return [
'mailer' => ['command' => 'artisan queue:work --queue=mail', 'max_process' => 2],
'sync' => ['command' => 'artisan app:sync', 'max_process' => 1, 'timeout' => null],
];
B. The supervisor command. One Artisan command with actions, mirroring the bundle's commands:
class ProcessManagerCommand extends Command
{
protected $signature = 'pm:{action} {name?}';
public function handle(ProcessManager $pm): int
{
return match ($this->argument('action')) {
'start' => $pm->start($this->argument('name')),
'stop' => $pm->stop($this->argument('name')),
'status' => $pm->status($this->argument('name')),
'restart' => $pm->restart($this->argument('name')),
};
}
}
Bind ProcessManager as a singleton in a service provider. It is CLI-only — never resolve it from a controller or a queued job (a job that spawns a manager that runs a job is a fork bomb with extra steps).
C. PID-file supervision loop. start → Process::fromShellCommandline(...) with setTimeout(null) and output redirected to a file → write storage/app/pm/{name}.pid → status uses posix_kill($pid, 0) → stop sends SIGTERM, waits, then SIGKILL → restart composes the two.
D. Health checks via the scheduler. The bundle's status check belongs on a schedule, not in a cron line: $schedule->command('pm:status --restart-dead')->everyMinute()->withoutOverlapping().
E. Event hooks. The bundle dispatches events on start/stop; the Laravel equivalent is PMStarted / PMStopped events plus listeners for logging, metrics, or a Slack ping. That's the cleanest extension point to port.
F. Don't reinvent the queue. If all you need is reliable workers with retries and autoscaling, use queue:work + Supervisor or Horizon. Use this on-demand-spawn pattern only when the number of workers must be decided at runtime by application logic — that's the niche the bundle occupied.
Kernel, symfony/framework-bundle, and YAML DI with bundle-extension semantics. Porting means rewriting the container config by hand.symfony/symfony requirement is a hard blocker on any modern Laravel; there is no partial workaround.posix_kill, pcntl_signal, pcntl_async_signals are missing on Windows, some shared hosts, and slim Docker images. Guard with function_exists() and document the requirement.posix_kill($pid, 0) returning true is not proof of liveness. Store the command line and verify with ps -p $pid -o args=, or keep a start timestamp.setsid and kill the whole process group, or you'll get duplicates on the next start.Symfony\Process pipes fill at ~64 KB and the child blocks forever. This bites every long-running worker. Redirect stdout/stderr to storage/logs/workers/*.log instead of reading pipes.setTimeout(null) explicitly on every spawn.pcntl_async_signals(true) and a SIGTERM handler in the worker command, otherwise stop is just SIGKILL and you lose in-flight work.list/status. Your own toolbox is ps aux | grep artisan, the PID directory, lsof -p, and worker log tails — build a pm:status --json early.ProcessManager interface, the Process wrapper (spawn/pid/isRunning/kill), and the event dispatcher hooks. That's roughly 200 LOC and removes the dependency entirely.How can I help you explore Laravel packages today?