crazy-goat/workerman-bundle
Symfony bundle integrating Workerman to run a high-performance async HTTP server, scheduler and supervisor in pure PHP. Keeps the Symfony kernel/container alive between requests for faster apps. Supports SO_REUSEPORT and optional direct Request creation for speed.
Prevent middleware header mutations from persisting in Workerman's request cache and being replayed to later requests with the same raw buffer. Headers are restored after each request dispatch, preventing cross-request identity, proxy, and tenant-state leakage (#576)
Fix remote unauthenticated denial-of-service: a single control byte in
any request header value killed the worker process. The request
lifecycle in HttpRequestHandler::__invoke() is now wrapped in a
try/catch that converts throwables into 400/500 responses, and
ServerWorker::onConnect installs a TcpConnection::$errorHandler
backstop that closes the connection instead of letting Workerman call
Worker::stopAll(250). Client errors (MalformedRequestException,
FileUploadValidationException) are logged at debug level to prevent
log flooding; server faults are logged at error level. A nested
try/catch around the error-response send ensures doTerminate() and
the reboot check still run when even the send fails
(#577)
RequestConverter now throws MalformedRequestException (extends
\InvalidArgumentException, implements ClientInputExceptionInterface)
instead of bare \InvalidArgumentException for malformed client input
(control bytes in headers, invalid URI/method). This lets
HttpRequestHandler distinguish client errors (400) from server faults
(500) — a middleware throwing \InvalidArgumentException is now
correctly a 500, not a 400
(#577)CrazyGoat\WorkermanBundle\Exception\ClientInputExceptionInterface —
marker interface for exceptions caused by malformed client input,
implemented by MalformedRequestException and
FileUploadValidationException
(#577)
CrazyGoat\WorkermanBundle\Exception\MalformedRequestException —
thrown by RequestConverter for malformed client input (control
bytes, invalid URI/method)
(#577)
Optimize InotifyMonitorWatcher startup by deferring the recursive directory
walk to after the event loop starts. At boot only the top-level source
directories are watched; remaining subdirectories are watched lazily via a
single deferred pass. This eliminates a synchronous full-directory walk that
could delay worker readiness on very large source trees
(#324)
Optimize FileMonitorWatcher::checkPattern() by compiling glob patterns to a single PCRE regex at construction time, reducing per-tick matching from O(files × patterns) to O(files) (#339)
Harden PHAR-build tests against silent phar.readonly skips: add
phar.readonly=0 to composer test / composer test:coverage scripts and
CI workflow ini-values, introduce PharReadOnlyGuardTest that fails under CI
when phar.readonly is set without explicit opt-out
(WORKERMAN_ALLOW_PHAR_READONLY_SKIP=1), and refactor
testCommandFailsWhenPharReadonlyIsSet to use injected PharCapabilities
instead of depending on the runtime INI setting
(#340)
Harden UtilsTest signal-logic tests: add pcntl and posix extensions to
CI runner, introduce guard test that fails when extensions are missing without
explicit opt-out (WORKERMAN_ALLOW_PCNTL_SKIP=1). macOS contributors can skip
these tests locally by setting the env var
(#346)
Populate <coverage/> in phpunit.xml with Clover and text report output,
add composer test:coverage and composer coverage:check scripts, and
enforce a line-coverage threshold in CI using bin/check-coverage.php. CI
enables PCOV, generates var/coverage.xml, and uploads it as an artifact per
matrix job. A regression test asserts the coverage gate remains present in
.github/workflows/tests.yaml (#357)
Expand ProcessTest from a single PID-file recency check to full process lifecycle coverage: ProcessStartEvent dispatch regression check, ProcessErrorEvent dispatch on throwable (via new TestErrorProcess), no-error-event assertion during normal operation, and SIGTERM-to-worker restart verification (locates the worker PID via /proc on Linux or ps on macOS). Introduces ProcessEventRecorder test listener and ProcessMarkerPaths constants class as the shared source of truth for marker file paths (#348)
Add PHPBench benchmark suite covering the five documented hot paths: RequestConverter::toSymfonyRequest, ResponseConverter::convert, MemoryRebootStrategy::shouldReboot, PeriodicalTrigger::getNextRunDate, and HttpRequestHandler::__invoke (composed middleware chain). Run via composer bench. CI executes the suite on every PR in advisory mode (results are logged but do not block merge). Documented measurement protocol in CONTRIBUTING.md (#328)
Add a cross-platform middleware dispatch contract test (MiddlewareDispatchContractTest). A dedicated test server on port 9991 runs a counting middleware that increments a shared counter file under flock() and tags every response with X-Dispatch-Count. The contract asserts that exactly one dispatch is observed per incoming HTTP request (single + sequential request cases), so any regression of the issue #533 dispatch-count class — including the macOS-specific triple-dispatch — fails CI immediately and on every supported OS (#542)
phar.readonly INI probe and \Phar extension presence check out of PharBuilder::build() into a new PharCapabilities collaborator. PharBuilder now accepts the capability checker via constructor (defaults to a live PharCapabilities::probe()), making the runtime checks individually testable and stubbable. The DI container registers PharCapabilities and injects it into the workerman.phar_builder service. No behavioural change observable for the build:phar flow (#372)Reset the test middleware execution-order accumulator on the first middleware invocation so MiddlewareTest::testHeaders no longer sees a stale X-Test-Middleware-request-order value on subsequent keep-alive requests under macOS / Workerman. Added a regression test that performs two consecutive requests through the same HTTP client and asserts identical middleware order on both responses (#533)
Make ProcessInspector::isProcessAlive() portable across POSIX systems — on macOS and other non-Linux platforms where /proc is unavailable, the function now uses posix_kill($pid, 0) for the primary liveness check and falls back to a non-blocking pcntl_waitpid() to distinguish running processes from zombies. The Linux /proc/{pid}/status zombie check is preserved as a Linux-only refinement. getParentPid(), isMasterRunning(), and killOrphanedIntermediateFork() are likewise gated on PHP_OS_FAMILY === 'Linux' so they no longer crash on macOS. Fixes ServerManager::stop() returning false on macOS because waitForProcessToStop() never observed the process dying (#530)
ProcessTest::testProcessIsLive failed on macOS because TestProcess wrote the status timestamp only once per __invoke() invocation, then exited. Once Workerman's boot + shutdown + supervisor respawn cycle exceeded 4 seconds (common on macOS), the persisted timestamp always looked stale. TestProcess now refreshes the status file on a 1-second heartbeat inside a loop so the timestamp always stays within the test's recency window; the test's secondary budget is widened from 4 to 10 seconds as a safety net (#534)
Fix race condition in ServerManager::getStatus() / getConnections() where PHP's stat cache and stale status files from interrupted runs could cause waitForFile() to read an empty or incomplete file written by Workerman's SIGIOT/SIGIO handler. StatusFileReader::waitForFile() now calls clearstatcache() before each poll and rejects 0-byte files. ServerManager deletes the stale status/connections file before signaling, ensuring waitForFile() always waits for fresh output from the current signal. Fixes WorkermanCommandTest failures on macOS where slower filesystem operations widened the race window (#535)
PharBuilder's user-supplied exclude_patterns against accidental ReDoS at build time. ExcludePattern now performs defense-in-depth: (1) a structural lint at construction time that rejects patterns containing nested unbounded quantifiers ((a+)+, (.+)*, (a+){2,}, ...) before the build ever traverses the source tree, (2) a PCRE compile-check against a probe string that surfaces patterns PHP itself rejects with a clear error message, (3) a per-call pcre.backtrack_limit/pcre.recursion_limit guard inside matches() that returns false rather than hanging if the limit trips. The temporary ini values are restored on every exit path. Negative regression test (testBuildRefusesNestedUnboundedQuantifierPattern) and a behavioural test for the per-call guard prevent future regressions. Documentation in docs/build-packaging.md recommends atomic groups ((?>...)) as the PCRE-native alternative for matching power that would otherwise require nested quantifiers (#334)Request::setHeader() and Request::withHeader() flagging that re-injecting X-Forwarded-* or Forwarded headers from untrusted input re-creates the trusted-proxy bypass class of bugs (#344)docs/security.md section — covers the risk, recommended ordering (run trusted-proxy filtering after middleware that mutates headers), scope-limiting forwarding-header writes, and the canonical Symfony setTrustedProxies() / setTrustedHosts() alternative (#344)str_contains('/proc/$pid/cmdline', 'WorkerMan') check in ProcessInspector with a fingerprint-based verification. ServerManager now writes a sidecar fingerprint file (<pid_file>.fingerprint) at start time, recording the master PID, start time (clock ticks since boot, Linux only), and UID. ProcessInspector verifies all three fields before signaling, preventing misidentification of unrelated co-located processes whose command line happens to contain "WorkerMan". The legacy cmdline-based check is retained as a fallback when no fingerprint file is present (backward compatibility and daemon mode). The fingerprint file is created with 0600 permissions and removed on stop() (#327)docs/security.md. The audit.ignore list is kept empty — no Composer security advisory is suppressed globally. Dev-only advisories are handled via composer audit --no-dev (the CI/production audit mode), so production dependencies are never shielded by a global suppression. Add testAuditIgnoreListIsEmpty and testComposerAuditNoDevIsClean tests to enforce the policy and prevent accidental re-introduction of suppressed advisories (#337)$_SERVER['WORKERMAN_CACHE_WARMUP_TIMEOUT'] superglobal mutation with a typed CacheWarmupTimeoutConfig static holder that bridges the bundle extension loader (runs during kernel boot) and Runner construction (runs later, outside the DI container via Runtime::getRunner() or ServerManager::start()/restart()). The env-var override path is preserved — WorkermanBundle::loadExtension() still reads WORKERMAN_CACHE_WARMUP_TIMEOUT from $_SERVER/$_ENV and applies it before storing the resolved value in the holder. Runner now accepts the timeout as a constructor argument with a default of 30 seconds, and the validation rule (>= 1) lives in one place on the holder (#368, #367)Update docs/security.md Static Files Protection examples to use the StaticFilesMiddleware service approach instead of the deprecated serve_files and root_dir server options. All YAML examples now show the recommended service registration pattern (#345)
Add explicit [@api](https://github.com/api) annotation to Utils::reload() to clarify that it is the canonical public API for programmatic worker reload, resolving the remaining ambiguity from the [@internal](https://github.com/internal) removal in 0.21.0 (#352)
Delegate issue triage and code review steps in docs/workflow.md to subagents with their own context, protecting the main session's token budget for implementation and fixes (#531)
testRunnerUsesCorrectForkErrorHandling (which read Runner.php as a string) with testForkFailureThrowsRuntimeException — a behavioral test that stubs the fork() method via a readonly subclass and asserts RuntimeException is thrown when pcntl_fork() returns -1. Removes the dead fork_failure case from the isolated test fixture (#313)testBootstrapClosesProcOpenPipes and testWorkermanCommandClosesProcOpenPipes (which read source-code files as strings and asserted on substrings) with behavioral tests that exercise the proc_open pipe cleanup pattern on actual subprocesses and assert all pipe resources are closed after fclose() (#319, #326)testSourceFileNoLongerContainsGetFileInfo (which read PollingMonitorWatcher.php as a string and asserted on a substring) with testPollUsesSingleStatPerFile — a behavioral test that instruments the iterator with CountingSplFileInfo and asserts exactly one stat() call per file. The new test catches any redundant stat-touching call (getFileInfo(), getSize(), isFile(), duplicate getMTime(), etc.) under any name, not just getFileInfo() (#330)StreamedBinaryFileResponseTest with comprehensive test coverage: content type detection, Content-Length verification, Content-Disposition, offset/maxlen behavior, deleteFileAfterSend cleanup, output correctness for small and large files, chunk size validation, auto ETag/Last-Modified headers, and edge cases (empty file, non-readable file, private responses) (#353)testSchedulerWorkerLogsExceptionsInChildProcess (which read SchedulerWorker.php as a string and asserted on substrings) with a behavioral test that forks a child, invokes SchedulerWorker::handleChild via reflection with a TaskHandler that throws, and asserts the child exits with code 1 and the exception is logged via Worker::log() (#306)ConfigLoader::loadFromCache() before requiring the generated PHP cache file. Cache files with world-writable permissions are now rejected with a clear error message, preventing arbitrary code execution if the cache directory is misconfigured (#323)umask(0077) while writing the config cache file in ConfigLoader::warmUp() so the generated PHP file is always created with restrictive 0600 permissions, regardless of the surrounding umask (#323)docs/security.md — the cache directory must not be writable by untrusted users (#323)[@unlink](https://github.com/unlink) error suppression in BinaryFileResponseStrategy cleanup callback; unlink failures are now checked and logged through the injected PSR-3 logger (#314)onBufferDrain as the primary cleanup hook in BinaryFileResponseStrategy instead of onClose, so file deletion runs at the correct lifecycle point (after the send buffer is flushed) and does not persist across keep-alive requests; onClose is retained as a fallback for early disconnects; both callbacks self-remove after firing and chain to any previously-set handlers (#308)ServerManager::consumeFile() for status and connections files to prevent symlink-swap redirection of the unlink. A failure to unlink the renamed temp file is now logged through the PSR-3 logger instead of being silently suppressed (#304)Connection: close header check case-insensitive in HttpRequestHandler — RFC 7230 treats the token case-insensitively, so Close, CLOSE, etc. now correctly trigger connection close, preventing wasted file descriptors and unexpected request reuse in long-running workers (#336)memory_reset_peak_usage() behind a boot-time flag so the per-request syscall is skipped when no reboot strategy needs memory_get_peak_usage() — currently no bundled strategy uses peak memory, so the call is eliminated entirely on the hot path (#317)ExceptionRebootStrategy's full Throwable storage with a boolean flag to eliminate a memory leak in long-running workers — the previous implementation retained the exception's entire stack trace (including referenced Request, controller, and service object graphs) until shouldReboot() was consumed (#307)method_exists() results per (class, method) pair in ServiceHandlerTrait to avoid redundant reflection lookups on every tick/invocation in TaskHandler and ProcessHandler (#315)Util\Wait::until() to unify the polling strategy in StatusFileReader::waitForFile() (previously a fixed 50ms cadence) and ProcessInspector::waitForProcessToStop() (previously an inline exponential-backoff loop with time()-based deadlines). The shared helper polls a condition with exponential backoff from 10ms up to 250ms and uses microtime(true) deadlines, so the total wall time stays at or below the configured upper bound (the old time()-based path could overshoot by up to one second) (#362)PollingMonitorWatcher: relax final on the class and on FileMonitorWatcher::createRecursiveIterator() so a test-only subclass can inject a counting RecursiveDirectoryIterator. The behavioral test in PollingMonitorWatcherTest requires this extension point to verify the watcher makes exactly one stat() call per file; without it, the test would be limited to flaky wall-time heuristics (#330)CronExpressionTrigger: remove redundant class_exists(Cron\CronExpression::class) gate from the constructor — the check is already performed by TriggerFactory::create() before instantiation, making the duplicate guard unreachable and misleading (#355)TriggerFactory: replace falsy object check (if ($dateTime)) with explicit instanceof \DateTimeImmutable check to clarify that the branch is taken only when ISO-8601 datetime parsing succeeds, and to avoid relying on object truthiness (#361)WorkermanCommand: rename $allowedActions local variable to $invalidActionMessage so the name accurately reflects that it holds an error message, not a list of allowed actions (#373)StaticFilesMiddleware: replace repeated DIRECTORY_SEPARATOR . ltrim($path, '/') with a named joinPaths() helper that normalises both root and request path separators explicitly, eliminating implicit coupling that would silently produce wrong paths if a future change stripped the leading slash (#365)WorkermanCompilerPass: standardise tag set ordering — $responseConverterStrategies remain sorted by priority (descending) for correct dispatch order in ResponseConverter::convert(), while $tasks, $processes, and $rebootStrategies are now sorted by service ID via ksort for deterministic ServiceLocator registration and reproducible container builds (#371)BinaryComposer: reduce MAGIC_BYTES visibility from public to private — the constant is only used internally and was accidentally exposed as part of the public API (#363)PeriodicalTrigger: remove fragile (array) cast on \DateInterval to read the private from_string property; replace with a flat 'DateInterval' description for directly-passed DateInterval objects (#360)ServicesConfigurator: use === true consistently for all boolean active config flags in configureRebootStrategies() — previously only memory.active used strict comparison, while always, max_requests, and exception used a truthy check (#370)DateTimeTrigger: move assignment out of if condition to eliminate assignment-in-condition smell and avoid potential =/== confusion (#359)Http\Request: add runtime deprecation notice to withHeader() warning that the PSR-7-named alias is misleading — it mutates the request in place rather than returning a new instance; users should migrate to setHeader() (#364)Resolver: rename inner closure variadic parameter to ...$kernelArgs so it no longer shadows the outer $args variable destructured from resolver->resolve() (#366)Add comprehensive interface-level and per-method PHPDoc to MiddlewareInterface, RebootStrategyInterface, and TriggerInterface — every interface now documents its purpose, lifecycle, consumption site, and parameter/return semantics so third-party implementers have a complete contract reference (#322)
Update "What's new in this fork" section in README.md with a comprehensive comparison against upstream luzrain/workerman-bundle, covering 20+ feature additions, dependency differences, and architectural changes (#491)
Document composer test port binding, troubleshooting steps, and workarounds in CONTRIBUTING.md to help contributors avoid "Address already in use" errors (#358)
Update README main configuration example to demonstrate StaticFilesMiddleware instead of relying on the deprecated serve_files option; the replacement was previously only shown in a dedicated subsection (#342)
Document --include-tests and --kernel-class CLI options in docs/build-packaging.md — these options were already supported by workerman:build:phar but omitted from the documentation (#331)
Resolve contradiction between CONTRIBUTING.md and CHANGELOG.md on approval policy: CONTRIBUTING.md now accurately reflects the current "no approval count required (solo dev project)" policy, matching the historical CHANGELOG 0.15.0 entry (#333)
Document runtime_dir in the README.md configuration reference, with full semantics (writable, must live outside the PHAR in PHAR/BIN mode, restrictive 0700 permissions on subdirectories) and a cross-link to docs/build-packaging.md; align the ConfigurationTreeBuilder info string with the README so config:dump-reference matches (#343)
Replace [@param](https://github.com/param) mixed[] with typed array{...} shapes on ServerWorker::__construct()/configureHandler()/createSslContext(), PharBuilder::build()/buildExcludePatterns()/buildExcludeFiles()/generateStub(), BuildPathResolver::resolveBuildDir()/resolvePharPath()/resolveBinPath()/resolveFilename(), and WorkermanBundle::loadExtension() — the shapes mirror the ConfigurationTreeBuilder definitions so PHPStan can verify config access and IDEs can autocomplete keys (#332)
HttpRequestHandler through the injected PSR-3 logger instead of error_log() to prevent sensitive data leaking to stderr; error_log() retained as fallback when no logger is available (#296)follow_symlinks option (default: false) to prevent symlink following under static root (#292)connection_timeout, keepalive_timeout and per-server body_size_cap for slowloris protection (#279)SchedulerWorker to avoid fopen/fclose blocking syscalls in the event loop on every scheduled task fire — handles are opened once per PID file and reused across the worker's lifetime (#297)SchedulerWorker with first-class callable ($this->onTickTimer(...)) and pass task parameters via the timer args array (#293)normalizeHeaderName results and fix irregular header acronyms (ETag, Content-MD5) (#287)FileUploadValidator::validate when no uploaded files are present (#281)ConfigLoader::getConfig: split into named methods, replace silent empty-fallback with exception (#325)ConfigLoader: move setBuildConfig into setters block (#329)TaskErrorEvent immutable by removing unused setError mutator (#338)function_exists checks in InotifyMonitorWatcher (#341)InotifyMonitorWatcher::$pathByWd PHPDoc type from string[] to array<int, string> (#347)Utils::reboot() is deprecated since 0.17.0 and remains deprecated; Utils::reload() is the replacement. reboot() is scheduled for removal in the next major release. No internal call sites remain in the bundle (#318)__invoke fallback tests to TaskHandlerTest and ProcessHandlerTest (#276)onWorkerStart invocation tests to ServerWorkerTest (#284)MiddlewareTest (#288)processFiles non-array drop branch in RequestConverterTest (#294)SchedulerWorkerSigchldTest with behavioral test using reflection (#302)AsTask and AsProcess attributes (#309)HttpRequestHandler explaining the request lifecycle (#320)Request class (#321)** list/emphasis markers to * / blockquote format across the README for consistent rendering (#310)kernel_class in PHAR stub generation — reject invalid PHP class names to prevent code injection (#263)0700 mode — prevents other users on multi-user systems from reading PID/status files (#270, #274, #453)Timer::add(0, ...) for terminate scheduling — reduces event-loop timer churn (#273)RequestConverter when no files are present in the request (#277)PharHelper::getProjectDir — thin wrapper that duplicates rtrim() with no added value (#316)WorkermanCompilerPass final — leaf class with no subclasses (#312)buildServerBag() and detectFormData() from RequestConverter::toSymfonyRequest() — reduces a 180-line method to coordinated delegates (#301)HttpRequestHandler::__invoke() — eliminates duplicate terminate try/catch (#291)ServerManager — replaces opaque formula comment (#295)RecursiveDirectoryIterator setup into a single method — removes duplicated boilerplate in Polling/Inotify watchers (#285)AbstractErrorListener and AbstractHandler base classes — eliminates near-identical code in Task/Process error listeners and handlers (#278, #275)configureHandler() from ServerWorker::onWorkerStart() — reduces closure complexityStaticFilesMiddleware to work with phar:// stream wrappers — realpath() returns false for phar:// paths, making the middleware unusable when running as PHAR/standalone binary (#447)README.md RebootStrategyInterface example — wrong FQCN caused copy-paste to fail (#289)ext-zip and ext-inotify to CI, fix test assertions for missing extensionsRunner::run() covering all decomposed entry points and process lifecycle (#260)ServerManager public surface with integration tests (#264)HttpRequestHandler in test instead of only testing construction and inheritance (#253)AsProcess and AsTask attributes covering all configuration options (#247)gc_collect_cycles() is actually invoked in MemoryRebootStrategy (#271)[@internal](https://github.com/internal) vs public-API contradiction in Utils class — Utils::reload() is now explicitly documented as a public API for programmatic graceful worker reload. Removed [@internal](https://github.com/internal) annotation, added PHPDoc, and documented usage in README (#290)bin/console in README — clarify it refers to the application's console, not the bundle's bin/ directory; add bin/README.md documenting the bundle's development scripts (#282)docs/superpowers/ planning artifacts from Composer package export (#298)composer.json keywords and description for Packagist discoverability (#299)StaticFilesMiddleware (#235)SchedulerWorker PID file handling — uses exclusive flock with strict permissions (#240)SfxDownloader::extractZip — validates entry paths against destination (#252)If-Modified-Since / If-None-Match support to StaticFilesMiddleware — reduces redundant file reads and 304 responses (#254)PollingMonitorWatcher directory scan across ticks with MAX_FILES_PER_TICK — prevents event-loop starvation on large source trees (#246)gc_collect_cycles and call memory_get_usage() once instead of twice in MemoryRebootStrategy (#250, #272)DatePeriod construction with O(1) DateTime::add() in PeriodicalTrigger::getNextRunDate (#239)ListenScheme enum for type-safe listen scheme configuration, replacing stringly-typed switch (#305)BuildPathResolver class consolidating duplicated resolveXxxPath helpers across build commands (#242)ServiceMethod value object replacing stringly-typed "service::method" concatenation (#258)SfxSourceResolver class extracted from BuildBinCommand::resolveSfx (#238)e2e/README.md explaining e2e directory purpose and contributor guidanceresources/phar-stub.tpl template file (#234)ServicesConfigurator::configure() into per-domain private methods (#249)SfxDownloader::extractZip into staged methods with typed exception (#251)ListenScheme enum (#305)SchedulerWorker::$handler is now readonly on a final class — prevents latent read-before-init bug (#262)SupervisorWorker is now final (consistent with other workers) (#265)^8.0 to match composer.json constraint (#257)KernelFactoryTest covering factory creation, boot, and shutdown (#224)RuntimeTest covering runtime path resolution and environment handling (#228)ResolverTest covering resolve() tuple shape, closure invocation, and error propagation (#230)ByteFormatterTest covering all unit boundaries and fractional values (#241)TaskErrorListenerTest and ProcessErrorListenerTest covering error dispatch and logging (#237)UPGRADE.md covering breaking changes from 0.12 through 0.17 (#256)build.sfx.sha256 and build.sfx.allow_insecure configuration options (#267)workerman:server connections output columns (#269)serve_files (#268)e2e/README.md explaining e2e directory purpose and contributor guidanceRequestConverter before propagation to Symfony — prevents header injection via crafted requests (#220)StaticFilesMiddleware path traversal — replaced naive concatenation with explicit path-join helper (#226)trusted_hosts configuration option for Host header enforcement — non-matching hosts return 400 before kernel boot (#213)SymfonyController on exception path — prevents request-scope memory leak across requests (#303)DefaultResponseStrategy sends large responses in chunks with configurable chunk size, eliminating full-body buffering (#236)StreamedResponseStrategy streams body in chunks via ob_start callback instead of buffering entire body — reduces peak RSS from response size to chunk_size * 2 (#229)BinaryFileResponseStrategy chains onClose callbacks instead of overwriting, enabling multiple cleanup handlers (#225)ReflectionProperty instances in a single ReflectionClass call on the hot path (#222)BinaryFileResponseReflector — consolidates reflection helpers and caches them by file class (#223)ProcessInspector and StatusFileReader classes extracted from ServerManager god class (#211)PharFileFilter and ExcludePattern named classes extracted from PharBuilder inline filter (#227)BinaryFileResponseReflector helper class for cached reflection access (#223)FileUploadValidator focused validation methods replacing monolithic validateFileEntry (#208)PharHelper::resolveRuntimePath() as the single shared method for PHAR path resolution (#211)ConfigLoader is now injected directly into ServerManager instead of probing the DI container via service locator (#214)Runner::run() refactored into focused helper methods with clear single responsibilities (#210)SchedulerWorker::runCallback() refactored into extracted parent/child/error branches with shared cleanup (#219)ConfigurationTreeBuilder::configure() split into per-section builders reducing a 260-line method to coordinated delegates (#216)RequestConverter::processFiles() refactored to handle non-array file entries with proper logging (#207)SchedulerWorker behavioral tests covering fork, flock, and PID lifecycle (#209)InotifyMonitorWatcherTest covering isFlagSet, start, watchDir, onNotify (#212)FileMonitorWorkerTest covering file monitor worker lifecycle (#218)SupervisorWorkerTest covering process lifecycle, signal handling, and error dispatch (#215)FileMonitorWatcherTest for create() factory and checkPattern() (#221)PharHelper unit tests for resolveRuntimePath() (#211)docs/README.md index page for user-facing documentation (#244)reload_strategy.memory in README (#233)StaticFilesMiddleware example (#231)servers.listen is effectively required (#232)CHANGELOG.md 0.16.0 to correct reverse-chronological position (#255)workerman:build:phar command to build PHAR archives with dynamic stubworkerman:build:bin command to create standalone binaries (SFX + custom php.ini + PHAR)PharHelper utility for detecting PHAR mode and resolving runtime paths outside the archivebuild configuration section for PHAR exclusions, custom php.ini, and SFX sources--kernel-class CLI option for overriding kernel class in PHAR stubConfigLoader fallback when cache is missing for PHAR scenariosRunner source path is now configurable instead of hardcoded to tests/App (#130)proc_open pipes in test bootstrap to prevent file descriptor leaks (#170)boolval() with (bool) cast for consistent style across the codebase (#159)final keyword to MiddlewareTest and StaticFilesMiddlewareTest (#168)getFileInfo() call on SplFileInfo object in PollingMonitorWatcher (#166)LevelSetList::UP_TO_PHP_82 with withPhpSets() in rector.php (#164)composer audit block-insecure to report insecure packages instead of silently ignoring them (#43)autoload-dev mapping (#167)phpunit.xml schema version to match installed PHPUnit 10.5 (#162)Added Utils::reload() method as the canonical name for graceful worker restart (#32)
Utils::reboot() is preserved as a deprecated alias with deprecation noticeAdded SymfonyController injection via DI into HttpRequestHandler (#158)
HttpRequestHandler now accepts SymfonyController $controller via constructor injectionWorkermanCompilerPass registers workerman.symfony_controller service with autowiring aliasKernelInterface and ResponseConverter dependenciesReplaced require pattern in WorkermanBundle with proper injectable classes (#145)
ConfigurationTreeBuilderServicesConfiguratorsrc/config/configuration.php and src/config/services.phpRemoved unnecessary array_map calls and simplified data flow in WorkermanCompilerPass (#24)
composer.json audit.abandoned config from "ignore" to "report" so abandoned package warnings are no longer silently suppressed (#163)
Removed FPM-specific no-op calls (ignore_user_abort(), connection_aborted()) from StreamedBinaryFileResponse — these have no effect in Workerman's event-driven architecture (#160)
Extracted magic string '+10 year' to class constant MAX_SCHEDULE_HORIZON in PeriodicalTrigger (#156)
Removed dead StreamResponseInterface and streamContent() method from StreamedBinaryFileResponse — the generator-based streaming was never called by the response pipeline; BinaryFileResponseStrategy handles it via withFile() (#165)
Removed 8 permanently skipped tests from HttpRequestHandlerTest that were never executed (#154)
cache_warmup_timeout configuration node with min(1) validationWORKERMAN_CACHE_WARMUP_TIMEOUT environment variable supportCACHE_WARMUP_TIMEOUT from RunnerX-Forwarded-Proto header
unconditionally. HTTPS is now detected only from the actual SSL transport
layer. Users behind reverse proxies must configure Symfony's trusted
proxies. (#152)
Fixed Runner::run() — mkdir() return value now checked to prevent silent failures (#151)
\RuntimeException with clear message when directory creation failsis_dir() check handles race condition between check and mkdir() callFixed Runner::run() — added timeout for cache warmup, use posix_kill instead of exit (#141)
CACHE_WARMUP_TIMEOUT constant (30 seconds)posix_kill() to avoid deadlock with extensions that register shutdown handlersFixed ProcessHandler and TaskHandler — validate dynamic method calls to prevent worker crashes (#153, #174)
ServiceMethodHelperFixed Utils::cpuCount() — handle null from shell_exec('nproc') (#150)
is_string() check for nproc outputFixed PeriodicalTrigger — removed useless assert() (#178)
Fixed SchedulerWorker — log exceptions in child process instead of swallowing (#178)
Fixed ServerManager — replaced hardcoded sleep(1) with polling loop (#155, #179)
getServerStatus() and getConnections()Fixed WorkermanCompilerPass::referenceMap() — improved PHPDoc (#171)
[@param](https://github.com/param) type to match findTaggedServiceIds() return shapeFixed CI: upgraded actions/checkout from v2 to v6.0.2 with SHA pinning (#172)
Fixed CI: pinned shivammathur/setup-php to commit SHA in tests workflow (#149, #175)
master branch (#132)
Tests/lint and Tests/testsAdded ServerAction enum for type-safe command actions (#135)
WorkermanCommand and ServerManager for improved type safetyAdded validation in ConfigLoader::warmUp() to ensure all config sections are set before caching (#35)
ConfigSection enum with cases: WORKERMAN, PROCESS, SCHEDULERLogicException with descriptive message when any section is missingAdded pre-push git hook to run composer lint before pushing (#137)
Fixed TriggerFactory fragile cron expression detection heuristic (#34, #138)
CronExpression::isValidExpression() for robust detection instead of exception-based heuristicclass_exists check for graceful handling when package is not installedFixed SupervisorWorker — removed sleep(1) hack and added proper logging (#36, #143)
Fixed WorkermanCompilerPass — replaced anonymous class with proper named class (#37, #144)
config/compilerpass.php to src/DependencyInjection/WorkermanCompilerPass.php[0 => ..., 1 => ..., 2 => ...]['workerman' => ..., 'process' => ..., 'scheduler' => ...]ConfigSection enum values as keys for clarity and type safetyrm -rf var/cache/*Request::withHeader() is deprecated (#38)
setHeader() insteadwithHeader() is kept as alias for backward compatibilityServerWorker SSL certificate validation for HTTPS/WSS servers (#18)
local_cert and local_pk are provided for SSL transport\InvalidArgumentException messages instead of cryptic SSL errorsCritical: Fixed Middleware Pipeline — closure capturing wrong request in middleware chain (#21)
$input parameter instead of outer scope $requestFixed KernelFactory — singleton kernel state reset between requests (#22)
services_resetter to reset services tagged with kernel.resetFixed RequestConverter — missing nested file handling (#26)
files[0], files[avatar], or <input name="documents[]" multiple> now work correctlyFixed ResponseConverter — generic HTTP header normalization (#25)
Fixed Runner — proper error handling for fork and cache warmup (#23)
pcntl_fork() error (-1) now throws exception instead of falling into indefinite waitBreaking: ResponseConverterStrategyInterface::convert() now requires a TcpConnection parameter
TcpConnection $connection parameter to your custom strategy's convert() methodBinaryFileResponseStrategy now deletes temp files immediately after connection closes
onClose callback instead of loading file into memoryTimer::add)RequestConverter::toSymfonyRequest() now returns empty content for multipart/form-data requests
php://input is not available for multipartgetContent() returned full raw body including file contents$request->files as beforegetContent(), you'll need to adapt itStreamedBinaryFileResponse::streamContent() simplified chunking logic
strlen($data)) instead of requested bytes ($read)Added test helper methods in RequestConverterTest for temp file cleanup and request creation (#88)
tearDown() for automatic temp file cleanupcreateTempFile() helper methodcreateRequestWithFiles() helper methodAdded QUERY_STRING to server bag in RequestConverter (#66)
$request->server->get('QUERY_STRING') to return query stringgetQueryString() to work correctlyAdded REQUEST_TIME and REQUEST_TIME_FLOAT to server bag in RequestConverter (#67)
time() and microtime(true) at request conversion timeAdded SERVER_PORT and SERVER_NAME to server bag in RequestConverter (#65)
$request->getPort() for non-standard ports (8080, 8443, etc.)getPort() to return correct value when Host header has no portX-Forwarded-Proto: https headerHTTPS=on for HTTPS requests, enabling proper getScheme() behaviorAdded E2E tests for StreamedResponse in SymfonyControllerTest (#69)
Added E2E tests for HTTPS detection in SymfonyControllerTest (#64)
isSecure(), getScheme() behaviorSERVER_PROTOCOL includes HTTP/ prefix (#60)
'HTTP/' . $rawRequest->protocolVersion()Extracted ResponseConverter from SymfonyController using Strategy Pattern (#72)
ResponseConverterStrategyInterface for pluggable response conversion strategiesResponseConverter orchestrator that selects and executes appropriate strategy based on response typeDefaultResponseStrategy for handling standard Symfony responsesNoResponseStrategyException for when no matching strategy is foundworkerman.response_converter.strategy)Added BinaryFileResponseStrategy for proper file download support (#70)
BinaryFileResponse using Workerman's native withFile() for efficient streamingSplTempFileObject (in-memory temp files) by reading content directly to bodydeleteFileAfterSend by reading file into memory and deleting immediatelyAdded StreamedResponseStrategy for StreamedResponse and EventStreamResponse (SSE) support (#71)
Typed exception hierarchy for better error handling and monitoring (#93)
WorkermanExceptionInterface marker interface to catch all bundle exceptionsWorkermanException abstract base class extending \RuntimeExceptionServerException with ServerAlreadyRunningException, ServerNotRunningException, ServerStopFailedExceptionValidationException (extends \InvalidArgumentException) with FileUploadValidationException, ConfigurationValidationExceptionSchedulerException (extends \InvalidArgumentException) with InvalidTriggerException, InvalidCronExpressionExceptionMiddlewareException (extends \InvalidArgumentException) with InvalidMiddlewareException, StaticFileMiddlewareExceptionKernelException with KernelCreationException, InvalidCacheDirectoryExceptionfinal with single-responsibility names\InvalidArgumentException throw sites replaced with domain-specific validation/scheduler/middleware exceptions\RuntimeException throw sites replaced with KernelCreationException and InvalidCacheDirectoryException\LogicException throw site replaced with InvalidCronExpressionExceptionException namespace)
CrazyGoat\WorkermanBundle\ServerAlreadyRunningException → CrazyGoat\WorkermanBundle\Exception\ServerAlreadyRunningExceptionCrazyGoat\WorkermanBundle\ServerNotRunningException → CrazyGoat\WorkermanBundle\Exception\ServerNotRunningExceptionCrazyGoat\WorkermanBundle\ServerStopFailedException → CrazyGoat\WorkermanBundle\Exception\ServerStopFailedExceptionCritical: Fixed RequestConverter missing REMOTE_ADDR, breaking getClientIp() and trusted proxies (#61)
TcpConnection object$request->getClientIp() now returns actual client IP instead of nullisFromTrustedProxy(), X-Forwarded-* headers) now works correctly127.0.0.1:0) provided for unit test scenariosCritical: Fixed BinaryFileResponse returning empty body for file downloads (#70)
BinaryFileResponse::getContent() returns false, causing empty responsesBinaryFileResponseStrategy uses Workerman's withFile() for proper streaming$this->file() or BinaryFileResponse now work correctlyCritical: Fixed RequestConverter bypassing ServerBag, breaking HTTP authentication and server bag reads (#59)
HTTP_* format in server bag (CGI convention)Authorization header is correctly parsed into PHP_AUTH_USER/PHP_AUTH_PW for Basic/Digest authContent-Type, Content-Length, Content-MD5 use CGI convention (no HTTP_ prefix)SERVER_PROTOCOL now has correct HTTP/1.1 format instead of 1.1$request->getUser() and $request->getPassword() now work correctly$request->server->get('HTTP_HOST') and other server bag reads now return expected valuesFor detailed migration instructions for all breaking changes, see UPGRADE.md.
How can I help you explore Laravel packages today?