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

Environment Laravel Package

sebastian/environment

A small PHP utility for writing code with runtime-specific execution paths. Detect the PHP or HHVM version, check whether code is running in the console versus the web, and branch accordingly. Handy for test tooling and library internals.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

sebastian/environment is a low-level runtime introspection utility, not an application-layer component. That defines where it fits and where it does not.

Fits well:

  • Health/diagnostics surface — Runtime::getCurrentSettings(), Runtime::isOpcacheActive(), Runtime::canCollectCodeCoverage(), and Console::hasColorSupport() are genuinely useful for a /health/runtime or artisan about-style command, and the 9.3.x line fixed real round-trip bugs (php -d quoting, empty-string overrides for php.ini-only extensions) that make those outputs trustworthy.
  • Test-suite runtime guards — skipping or gating tests on runtime capabilities (hasXdebug(), hasPCOV(), isOpcacheActive(), non-TTY console behavior) without hand-rolled PHP_VERSION parsing.
  • CI/build assertions — a cheap precondition check in a pipeline step or a pre-deploy script.
  • Xdebug/PCOV coverage policy — canCollectCodeCoverage() is the canonical answer and already encodes Xdebug mode handling (fixed in 7.2.1/8.0.1), which teams routinely get wrong by hand.

Does not fit:

  • Request-path feature flags. The package has no Runtime::isPhp81(), no isLinux(), no hasExtension(). Those appear in earlier draft assessments in this record and are not part of the shipped API. Building product behaviour on a facade like Environment::isPhp80() means we write and maintain that adapter ourselves — the value proposition shifts from "adopt a package" to "adopt a package plus a thin internal abstraction."
  • Environment tiering. This is orthogonal to Laravel's app()->environment(). Do not let the naming collision confuse the team; enforce that distinction in code review.
  • HHVM detection. isHHVM() and the HHVM branches are legacy surface. HHVM is not a realistic deployment target for Laravel 10+. Any migration effort justified by "HHVM support" should be declined.

Integration Feasibility

  • Zero-ceremony install. composer require --dev sebastian/environment. No service provider, no config publish, no migrations. It is almost certainly already in our vendor/ tree transitively via PHPUnit, so the real decision is whether to promote it to a declared direct dependency (yes, for anything we import in our own code) and which constraint to pin.
  • The "dependents: 0" figure is misleading. In practice PHPUnit is the dependent, which means this package is on virtually every Laravel project's disk. Treat it as near-universal, not as an obscure pick.
  • Facade/adapter work is real but small. A ~40-line RuntimeInfo wrapper binding Runtime and Console as singletons, exposing the four or five methods we standardize on, is a half-day of engineering plus tests. That is the actual delivery item, not the Composer line.
  • No framework coupling. BSD-3-Clause, no Laravel version sensitivity, no container conflicts.

Technical Risk

Primary risk — transitive version lock with PHPUnit. This is the only risk that can break a build:

  • The maintainer's majors drop trailing PHP versions: v8.0.0 dropped PHP 8.2 support, v9.0.0 dropped PHP 8.3 support. So the 9.x line targets the newest PHP only, and our PHP runtime version determines which major we can use, which in turn is coupled to the PHPUnit major we run.
  • If we declare ^9 directly while PHPUnit pins ^8, Composer resolves to the intersection or fails. The safe default is to match the constraint PHPUnit already imposes rather than to introduce an independent one.
  • Action: read composer.lock for the resolved version before proposing any constraint.

Secondary risks:

  • API drift within a major. Runtime::getBinary() and getRawBinary() are deprecated in favour of escapeshellarg(PHP_BINARY) and the PHP_BINARY constant. Any adapter must be written against the resolved version's actual method set, not against a blog post.
  • Specification drift in our own record. Prior drafts in this repo assert methods and behaviours that don't exist (Environment::detect(), Runtime::isLinux(), Runtime::isPhpVersion(), "zero-cost in production" for a package that shells out in getCurrentSettings()). Any plan derived from those drafts must be re-validated against the installed source before estimation.
  • Data hygiene. The package metadata shows a last release dated 2026-10-01 (future-dated) and 0 dependents. Both are artifacts, not facts. Do not let either drive prioritisation — neither the "actively released" signal nor the "no adoption" signal is reliable here.
  • Blast radius is genuinely low. Dev-only import, no runtime hooks, permissive licence, no autoloaded global side effects. Worst case is a require-dev removal.

Key Questions

  1. What is the forcing function? Diagnostics endpoint, CI gate, coverage-policy fix, or "clean up scattered version_compare calls"? Without one, this is a nice-to-have and should not consume a sprint.
  2. Who owns the adapter? If we ship a RuntimeInfo facade, it becomes internal API with its own versioning and docs. Is that accepted, or do we mandate direct Runtime usage and skip the abstraction?
  3. What constraint does composer.lock already resolve to, and does promoting this to a direct dependency change the resolution graph at all? Answer this before committing to a major.
  4. Is the PHP-version gate supposed to be a Composer platform constraint instead? For "require PHP 8.2+", Composer's config.platform and require.php already fail the install, which is earlier and cheaper than a runtime exception in CI. We should be clear that this package is for introspection, not for enforcement.
  5. Which PHP versions must CI keep green, and does that map to multiple sebastian/environment majors in one matrix? If yes, we cannot declare a single wide constraint.
  6. Do we accept a dev-only scope? If any non-test code path needs runtime introspection (health endpoint, about command), this must be a require, not require-dev — and that changes the deployment story.

Integration Approach

Stack Fit

  • Composer-native, Laravel-agnostic. Works with any Laravel 10/11/12 app; no service provider registration required, and none is shipped.
  • Lives in the dev/tooling tier. Default placement is require-dev, consumed by PHPUnit bootstrap, tests/, and CI scripts. The exception is a runtime diagnostics endpoint, which pulls it into require.
  • Complements, not replaces, existing tooling. composer.json platform constraints remain the enforcement mechanism; Laravel's app()->environment() remains the tier mechanism; sebastian/environment is the introspection layer underneath both.
  • Coexists with the PHPUnit toolchain we already run — same maintainer, same release cadence, shared version alignment. This is a reason to use it rather than hand-roll version parsing.

Migration Path

  1. Inventory (½ day). Grep for PHP_VERSION, version_compare, phpversion(), defined('HHVM_VERSION'), extension_loaded(, ini_get('opcache, getenv('FORCE_COLOR'), posix_isatty. Classify each hit as test, CI script, diagnostics, or request-path. The request-path bucket is the one that determines scope.
  2. Resolve the constraint (½ day). Read composer.lock; confirm which major is present; confirm whether promoting to a direct dependency is a no-op. Record the PHP↔PHPUnit↔environment major matrix in the ADR.
  3. Spike the adapter, don't build it yet (1 day). Prototype the RuntimeInfo wrapper against the resolved version. This is where fabricated API assumptions surface cheaply.
  4. Replace one bucket at a time. Order: CI scripts → test guards → diagnostics → (only if approved) request-path. Each bucket is a separate PR, each revertable.
  5. Codify the convention. Lint rule or PHPStan custom rule flagging new version_compare(PHP_VERSION, ...) in tests/ and app/, with the approved replacement referenced in the message.
  6. Document the boundary. One paragraph in the engineering handbook: "Composer constraints enforce versions; app()->environment() selects config; RuntimeInfo only describes the runtime."

Compatibility

  • PHP matrix is the hard constraint. The 8.x line requires a newer PHP than 8.2; the 9.x line requires a newer PHP than 8.3. Our supported-PHP range must be mapped per branch, and a multi-PHP CI matrix may legitimately resolve different majors per job — acceptable, but it must be explicit rather than emergent.
  • PHPUnit version alignment is non-negotiable. We adopt the constraint Composer already resolved for PHPUnit; we do not impose our own.
  • Laravel compatibility: none in question — no framework coupling, no container assumptions, no global state.
  • Deprecated surface: exclude getBinary() and getRawBinary() from the adapter. Use PHP_BINARY and escapeshellarg(PHP_BINARY) directly.
  • Not portable: Runtime::getCurrentSettings() spawns a child process on older PHP (9.3.3 removed that on PHP 8.6+). Never call it on a request path.

Sequencing

Phase Scope Risk Est.
0 ADR: scope decision, version matrix, adapter-vs-direct call none 1 day
1 Promote to declared require-dev with the lock-resolved constraint low 2 hrs
2 CI precondition step (fail fast, clear message) low ½ day
3 Test guards: coverage policy via canCollectCodeCoverage(), TTY/colour via Console low 1 day
4 RuntimeInfo adapter + artisan runtime:inspect diagnostics command medium 2–3 days
5 (Optional, gated) request-path replacement of manual version checks medium 2–5 days
6 Lint rule + docs low 1 day

Phase 0 is the gate. If Phase 0 concludes "no diagnostics need, CI gate can be a Composer platform constraint," the correct outcome is to stop after Phase 2 and record the decision — not to proceed for consistency's sake.


Operational Impact

Maintenance

  • Near-zero passive cost. Single maintainer, high-discipline release notes (every fix is issue-linked), PHPStan-analyzed, CI + Codecov badges, semantic versioning respected. This is a well-run package.
  • Active cost is our adapter, not the dependency. The RuntimeInfo wrapper, its tests, and its docs are ours to maintain forever. Budget for it explicitly or don't build it.
  • Upgrade cadence is coupled, not independent. Majors land alongside PHPUnit majors and drop old PHP. Treat upgrades as part of the PHPUnit upgrade runbook, not as standalone chores.
  • Version-pinning discipline required. Because we share the constraint with PHPUnit, never widen the constraint to "fix" an unrelated Composer conflict — that is a signal of a deeper dependency problem.

Support

  • Single-maintainer concentration risk is real but bounded. Worst realistic outcome is an abandoned package; mitigation is trivial — the surface we use is ~5 methods we can vendor or replace with PHP_BINARY, extension_loaded, and ini_get in an afternoon.
  • No vendor SLA, no paid support. Acceptable for dev-tooling. Not acceptable as the sole dependency behind a customer-facing SLA claim — so keep it out of anything that gates production traffic.
  • Escalation path is upstream GitHub issues. We should carry no fork. If we need a patch, pin to a commit and open the upstream PR.
  • Deprecation absorption: getBinary()/getRawBinary() are already deprecated. Our adapter must not use them, or we inherit a future breaking change for no benefit.

Scaling

  • Cost scales with nothing. No I/O, no reflection-heavy bootstrap, no configuration. Ten requests or ten million, the cost is identical — provided it stays off the request path.
  • The one method that does not scale is getCurrentSettings(). It can spawn a child process (pre-8.6) and shells out. At 10 RPS that is 10 subprocesses per second. Hard rule: diagnostics-only, invoked from a CLI command or a rate-limited health endpoint, never from middleware or a controller on a hot route.
  • CI scaling is fine. A single runtime assertion step adds milliseconds; it does not multiply across matrix jobs.
  • Multi-tenant/multi-PHP is the only scaling dimension that matters — if we serve multiple PHP versions concurrently, the adapter must not assume a single runtime profile.

Failure Modes

Failure Likelihood Impact Mitigation
Composer constraint conflict with PHPUnit Medium Build-blocking, blocks all deploys Match the lock-resolved constraint; never invent a wider one; validate in a throwaway branch first
Subprocess spawn in a hot path (getCurrentSettings() misused) Medium Latency spike, process-table exhaustion under load Ban via middleware allowlist; lint rule; code review checklist
Adapter built against non-existent methods (per earlier drafts) Medium-High if drafts are trusted Wasted sprint, late discovery, credibility damage Phase 0 prototype against installed source; delete fabricated API from the record
PHP major upgrade breaks the constraint silently Medium CI green locally, red in prod image Pin config.platform.php; include the package in the PHP-upgrade runbook
Upstream abandonment Low Dev-only inconvenience Documented 1-day replacement path in the ADR
Non-TTY warnings on colour detection Low Log noise in CI/containers Fixed upstream (9.0.1/8.0.4); stay on a patched version
Env-var-driven behaviour surprises (FORCE_COLOR) Low Cosmetic, hard to reproduce Document that FORCE_COLOR now affects output; set explicitly in CI

Ramp-Up

  • Engineering onboarding: ~30 minutes. Small, predictable API. The learning curve is not the package — it is our convention.
  • The real ramp-up is the convention, not the code. Engineers must internalise three separate questions: Is PHP new enough? → Composer platform. Which config tier? → app()->environment(). What is the runtime actually doing? → RuntimeInfo. Name collision between app()->environment() and this package is the single most likely source of misuse — address it with naming (RuntimeInfo, not Environment) and one handbook paragraph.
  • Reviewer checklist (one line each): no version_compare(PHP_VERSION in tests/ or app/; no getCurrentSettings() in a request path; no getBinary()/getRawBinary(); adapter methods exist in the locked version.
  • First-week deliverable to prove the value: the artisan runtime:inspect command. It makes the adoption tangible to the whole team in one review, rather than an invisible composer.json diff.

TPM recommendation: approve Phases 0–3 (declared dev dependency, CI gate, test guards) as a small, low-risk hygiene item; treat Phases 4–5 as a separate, evidence-gated decision. Do not approve any plan that depends on the facade or detection methods described in the earlier draft assessments in this record — those must be re-validated against the installed source before estimation, and the HHVM angle should be dropped entirely.

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.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support