php-standard-library/os
Detect the operating system in a type-safe way using PHP Standard Library's OperatingSystemFamily enum. Part opf the php-standard-library project, this small package gives you a clean, enum-based API for OS detection instead of fragile string checks.
Architecture fit. The package does exactly one thing: resolve the host OS into an OperatingSystemFamily enum case. That is a legitimate architectural primitive — it replaces string sniffing (str_contains(PHP_OS, 'WIN'), php_uname() parsing) with a typed value that PHPStan and IDEs can reason about. For a Laravel codebase that already branches on OS in deployment scripts, Artisan commands, or filesystem/permission handling, the conceptual fit is good: it belongs behind an interface in the service container, not sprinkled as static calls.
The honest framing is that this is a ~20–40 line concern, not a subsystem. My evaluation is therefore dominated by build-vs-buy and supply-chain questions rather than by capability gaps.
Integration feasibility. Low friction mechanically — PSR-4, no transitive dependencies beyond PHP 8.1+, composer require and go. The friction is architectural: a static OperatingSystemFamily::current() is not injectable and not mockable. Any serious integration must wrap it:
interface OsDetector { public function family(): OperatingSystemFamily; }
with a single adapter implementation calling the package. That wrapper is the actual deliverable; the package is an implementation detail inside it. Once you accept that, swapping the package for 30 lines of in-house code later is a one-file change — which is also the strongest argument that the dependency may not be worth taking.
Technical risk.
^1.0. Either the docs are stale or the versioning story is unstable — both warrant a pin-and-verify before adoption.current() does not model a platform we care about, our options are: fork, wrap-and-map into our own enum, or fall back to raw detection. This materially raises the cost of the "just adopt it" path.Key questions.
current() read PHP_OS_FAMILY, php_uname(), or both — and is it a pure, side-effect-free function we can unit test?Unknown case)? A thrown exception during bootstrap is an outage; a silent Unknown is a latent bug.Build-vs-buy, stated plainly:
| Criterion | Adopt package | In-house enum |
|---|---|---|
| Initial cost | ~1 day (wrapper + tests) | ~1–2 days |
| Supply-chain surface | +1 dependency, opaque changelog | none |
| Niche platform handling | fork or remap | native |
| Upgrade risk | unassessable from repo | zero |
This is close to a coin flip, and I would not sponsor it on the strength of the package alone.
Stack fit. Laravel 10/11 on PHP 8.2+ satisfies the enum requirement. Bind an OsDetector interface as a singleton in AppServiceProvider and resolve it once; do not let the static call leak into controllers, Blade, or jobs. If we want ergonomics, add a thin facade over our interface, not over the vendor class — that keeps the vendor name out of application code, which is the whole point of the wrapper.
One genuinely useful pattern: resolve the family once at boot and expose it through a config key so downstream code compares enum cases against a cached value rather than re-detecting. Guard this carefully — see the artifact-build failure mode below.
Migration path.
PHP_OS, php_uname, DIRECTORY_SEPARATOR-adjacent branching, and strtoupper(substr(PHP_OS...)) idioms. Establish the baseline count; that number is the success metric.PHP_OS comparisons. Without enforcement, the old idiom returns within two sprints.Compatibility. Additive; existing string checks keep working, so this is a strangler-fig migration with no flag day. Watch two boundaries: older Laravel/PHP 8.0 services cannot use enums at all (hold them out of scope rather than polyfilling), and any package we publish that exposes OperatingSystemFamily in its public signature inherits the vendor as a transitive dependency — keep the enum out of our public API surface and return our own type.
Sequencing. Wrapper and tests → container binding → canary conversion → enforcement rule → broad conversion. Do not begin with middleware or exception handlers; those run on every request and a detection failure there is an outage rather than a bad log line.
Maintenance. Low code volume, but non-zero process cost: we would be tracking a dependency whose per-release changelog is a link to a monorepo. Budget a recurring line item for upgrade review that cannot be satisfied by reading this repo. Mitigation: pin an exact version, and treat any bump as a change requiring a diff against the monorepo. If we find ourselves forking to add a case, we have adopted a maintenance obligation without the benefit of upstream support — at that point, fold the code in-house.
Support. Positive externality: logging an enum case name (os=windows) instead of a free-form string materially shortens triage of platform-specific bugs, and it makes support tickets self-classifying. Document the supported set explicitly, plus defined behavior for everything outside it. There is no vendor support channel — the monorepo issue tracker is the only path, and with 0 dependents our issues are unlikely to be prioritized.
Scaling. Non-issue computationally: O(1), no I/O, no external calls. Horizontal scaling is unaffected. The only scaling-adjacent concern is consistency across a heterogeneous fleet — if some instances are Linux and some are Windows (or macOS developer machines run the same queue workers), behavior will legitimately diverge. That divergence is the feature, but it must be observable, so log the resolved family at boot alongside build metadata.
Failure modes.
Unknown/fallback policy and asserting it in tests rather than discovering it in production.current() raises on an unrecognized platform and it is called during provider registration, the application cannot boot. Resolve lazily, after boot.Ramp-up. Trivial for engineers — enum comparison is a 15-minute concept, and the wrapper makes misuse harder than correct use. The onboarding text should be two rules: never compare PHP_OS strings directly, and never call the vendor static outside the adapter. Total ramp cost is well under a day. The real ramp risk is not learning the API; it is the team treating a 0-star, single-purpose dependency as infrastructure and forgetting it is there — which is why the enforcement rule in CI matters more than any documentation we write.
How can I help you explore Laravel packages today?