crazy-goat/workerman-bundle
Run Symfony on Workerman: a pure-PHP event loop HTTP server, scheduler, and supervisor replacing php-fpm, nginx, cron, and supervisord. The kernel and DI container persist between requests for speed, with a composable middleware pipeline, console commands, and no external binaries.
Architecture fit. The package is a Symfony bundle (CrazyGoat\WorkermanBundle), not a Laravel package. It registers via config/bundles.php, ships Symfony console commands, a Symfony Configuration tree, compiler passes, and relies on Symfony's services_resetter and kernel lifecycle. For a Laravel estate there is no integration path without a rewrite: Laravel has no bundle/kernel-terminate equivalent, no services_resetter, and no Symfony DI tree to hook. The realistic TPM framing is therefore two-track:
^6.4 | ^7.0 | ^8.0, PHP ^8.2).The architecture itself is coherent: a middleware pipeline (MiddlewareInterface, StaticFilesMiddleware, SymfonyController) built once and cached, response conversion via a strategy pattern (BinaryFileResponse, StreamedResponse, chunked default), a SchedulerWorker and SupervisorWorker, plus console lifecycle management (workerman:server start/stop/restart/reload/status/connections) and PHAR/bin packaging. That is a genuine four-component consolidation, not a thin wrapper.
Integration feasibility. High for a Symfony service, low for anything else. Concrete gates:
| Gate | Status |
|---|---|
| PHP | ^8.2 only |
| Symfony | ^6.4 / ^7.0 / ^8.0 |
| Extensions | pcntl, posix required → Unix only, no Windows, restricted PaaS |
| PSR-7 | Deliberately removed (smaller surface, fewer deps) |
| TLS | local_cert/local_pk required for https:///wss://; symlinks rejected |
| Ports <1024 | root or CAP_NET_BIND_SERVICE |
The symlink rejection is a real deployment blocker: Kubernetes secret mounts are symlinks, so in-cluster TLS termination with this bundle will fail — TLS must sit at ingress/LB.
Technical risk. Ranked:
luzrain/workerman-bundle. No commercial support, no production references, no evidence of third-party adoption. The release metadata (2026-10-06) should be verified against the actual tag/artifact before any decision.UPGRADE.md + changelog discipline exists but is self-imposed.services_resetter firing after kernel termination. Any service holding request state (identity, entity manager, buffers, static caches) will leak across requests. This is the classic Octane-class failure and it is not fully solved — it is delegated to Symfony's resetter coverage.sleep() serializes an entire worker. There is no documented blocking-I/O audit tooling.static_files.allowed_extensions is silently ignored when StaticFilesMiddleware is registered as a service — which is exactly the recommended setup. Silent security-relevant config no-ops are a red flag for review rigor.listen produces a worker that accepts no connections with no error. Same class of problem.Key questions.
reuse_port, graceful workerman:server reload, LB connection draining?trusted_hosts, blocked extensions, dot-file blocking, traversal protection, and symlink policy against our existing nginx ruleset?Stack fit. For Symfony: strong fit, drop-in via Composer + bundles.php + a workerman.yaml, minimal code change. For Laravel: not a fit as a dependency — treat it as a design reference and cost the equivalent build against Octane, or a thin Laravel adapter over Workerman (middleware pipeline, kernel handling, per-request flush, Artisan commands for lifecycle). Budget that adapter as net-new work with no upstream support.
Migration path. Phase it, one service at a time, behind the existing load balancer:
pcntl/posix, and that TLS is terminated upstream.8080) behind the LB, with StaticFilesMiddleware and trusted_hosts configured, reload_strategy.exception and file_monitor enabled in staging only.CronExpression::isValidExpression() validation is a genuine improvement over upstream's heuristic), and supervisord-managed daemons to SupervisorWorker.workerman:build:phar / workerman:build:bin with runtime_dir and WORKERMAN_RUNTIME_DIR for air-gapped or on-prem targets, verifying 0700 directory creation and writable cache/log paths.Compatibility. Pin an exact version; keep composer audit in CI (note the repo itself skips audit locally via LINT_SKIP_AUDIT=1 — do not replicate that). Confirm no reliance on PSR-7 middleware (removed in this fork) and no reliance on the deprecated serve_files/root_dir path. Windows and shared-hosting targets are out. Validate that every https:///wss:// listener has non-symlinked cert paths.
Sequencing. (a) Decide track: Symfony-adopt vs Laravel-reference. (b) If adopt, secure internal ownership of the fork or a hard pin with a documented upgrade cadence. (c) Pilot behind feature flag / LB weight 5%. (d) Canary ramp with latency and memory-reload counters as promotion gates. (e) Only then expand to scheduler/supervisor consolidation, which carries the largest blast radius because a supervisor bug takes down background processing rather than one HTTP worker.
Exit criteria for the pilot: no silent worker states, no unreset stateful services after a 24h soak, memory-reload rate below an agreed threshold, and p95 no worse than baseline.
Maintenance. Ownership is the dominant cost. Expect: upstream merge triage against luzrain/workerman-bundle, tracking walkor/workerman releases, PHP minor upgrades (the CI matrix already covers 8.2–8.5), Symfony 8 alignment, and internal maintenance of the Laravel adapter if that route is taken. The repo's discipline (UPGRADE.md for breaking changes, changelog gate via bin/check-changelog.php, exception-usage lint) lowers this materially — but it lowers our review cost only if we keep the same gates.
Support. No vendor, no SLA, no dependents to lean on. Support model is self-support: internal runbooks, a named owner, and a fork-or-fall-back decision recorded up front. Fallback must be real — the fpm/nginx config should be kept in version control until the bake period ends.
Scaling. processes defaults to CPU×2 per server; reuse_port enables multiple binds for rollout. Horizontal scale stays LB-driven. Watch the memory reload strategy: default limit 128 MB, gc_limit 96 MB, gc_cooldown 60s. Under leaky workloads this produces worker churn — acceptable as a safety valve, unacceptable as steady state. keepalive_timeout 30s and connection_timeout 120s shape connection-pool behavior at the LB and should be tuned per service, not left at defaults. max_package_size 10 MB global with per-server body_size_cap overrides.
Failure modes to instrument and runbook:
listen → silent zero traffic.static_files.allowed_extensions silently ignored under the recommended middleware setup → unintended file exposure.stop_timeout default 2s is tight./proc-based detection and parent-PID tracking exist, but this is now our problem, not systemd's.workerman:server status depends on SIGIOT plus a status file with status_timeout 5s → health-check design must not assume it is fast or reliable.WORKERMAN_CACHE_WARMUP_TIMEOUT) and signal-based success detection — treat warmup failure as a deploy gate, not a warning.Ramp-up. The team needs a new mental model: event loop instead of process-per-request, signal handling, /proc process inspection, Workerman APIs, PHAR builds, and a rewritten on-call runbook (no nginx access log, no fpm pool status page). Estimate 2–4 weeks for a small platform team to reach competence and produce runbooks; add chaos drills for memory reload, orphan cleanup, and scheduler child-failure reaping before production. The absence of php-fpm's per-request isolation is the conceptual jump most engineers underestimate — the reload strategy is a mitigation, not a substitute.
Recommendation. Do not adopt on the strength of 2 stars and 0 dependents. Adopt only where there is a Symfony service, a named internal owner for the fork, a measured win over fpm+nginx, and LB-terminated TLS. Elsewhere, harvest the architecture — middleware pipeline, strategy-based response conversion, memory-reload watchdog, signal-based process supervision, PHAR packaging — and map each to the Octane/RoadRunner equivalent. Re-score once there is a production reference or a maintained upstream.
How can I help you explore Laravel packages today?