sebastian/global-state
Snapshot and restore PHP global state with sebastian/global-state, the standalone component extracted from PHPUnit. Capture globals, superglobals, static properties, and constants for reliable test isolation.
Architecture fit: Program-level test tooling, not a product dependency. sebastian/global-state is a stand-alone extraction of PHPUnit's own test-isolation machinery — it snapshots $GLOBALS, superglobals, and static properties, then compares/restores them so tests don't leak state into one another. That places it squarely in the development-dependency tier, never on the production autoload path. For a Laravel program it has no architectural seam: RefreshDatabase, per-test application instances, container rebinding, Facade::clearResolvedInstances(), and the fake() helpers already cover the leakage categories most feature tests produce. The defensible fits are narrow and specific — a shared base TestCase, an internal test-harness package, or a non-PHPUnit runner (custom CLI harness, worker loop needing deterministic resets between iterations). Record it in the dependency register as inherited dev tooling; do not treat it as an architecture decision requiring sign-off.
Integration feasibility: Mechanically trivial, organizationally a governance question. A composer require --dev plus a Snapshot::snapshot() / Restorer pair behind a trait is roughly half a day for one engineer. The cost that matters at program level is that declaring the dependency directly converts a free transitive pin into an explicitly owned one: your team then fields the upgrade tickets when the PHP support window shifts, rather than riding PHPUnit's. Feasibility is high; discretionary value is low; the correct posture is "adopt explicitly only with a measured problem and a named owner," not "adopt because it's cheap."
Technical risk: Low for correct test-only use, meaningful for drift and misuse. Four items belong in the risk register:
$_ENV/$_SERVER/$_SESSION outside test teardown is a stability and security concern, not a testing convenience. Any usage outside tests/ is a stop-work signal.The adoption signal is a flag rather than reassurance: ~6.6k stars with effectively zero direct dependents means the install base is almost entirely transitive via PHPUnit, and README-only documentation means the API contract is whatever the source says it is.
Key questions:
phpunit/phpunit?tests/?Stack fit: Development dependency only, contained behind a thin wrapper. Wrap it in a small GlobalStateIsolation trait or base-class hook (~50 lines) so the package stays an implementation detail and engineers never call it directly. Use try/finally semantics rather than relying on tearDown ordering — teardown order is exactly what breaks when another team inserts a base class between you and the framework. Default to selective snapshots (['globals' => ['$_ENV']]) rather than full captures, so the fast path is also the easy path.
Migration path: Do not schedule speculative adoption. The defensible sequence:
--order-by=random) and classify flaky failures by root cause. Publish the baseline flake rate and suite duration.Compatibility: The binding constraint is the PHP matrix, not Laravel. Validate the resolved version against the target Laravel release's minimum PHP before writing code, and re-validate at every Laravel, PHP, or PHPUnit major — treat it as a gate item in upgrade checklists, not a footnote. Patch-level behaviour has shifted too (superglobal handling changed in 8.0.1/8.0.2; constant-name escaping fixed in 8.0.3/9.0.1), so selective-snapshot arrays need per-version verification rather than copy-paste from a blog post.
Sequencing: Spike in one high-flake suite and record before/after numbers → wrap usage → measure again → keep only if flake rate drops without a meaningful runtime regression → roll to the shared base case. Keep the whole effort inside a single sprint and a single squad; this does not warrant multi-team coordination. The one piece of cross-team work worth funding is the inventory and classification step in item 2 — it produces durable value (a flake taxonomy and a CI gate) even if you never adopt the package directly.
Milestone gates for the program plan:
Maintenance: Near-zero direct cost — actively released (latest 2026-06-01) and maintained alongside PHPUnit by the same author. The indirect cost is the real line item: every PHPUnit major forces a compatible-bump review, and this package's breaking changes ship on that cadence rather than ours. Budget a recurring entry in every test-infrastructure and PHP-upgrade cycle, and track its support window on the same roadmap lane as the platform PHP version.
Support: Community and upstream only — no commercial backing, no LTS commitment beyond the maintainer's own policy. Knowledge will concentrate in whoever writes the wrapper. Mitigate structurally: keep the wrapper small enough to rewrite in an afternoon, document its contract and default exclusions (notably $_SESSION and $_COOKIE) in the repo, and record the transitive pin in the dependency register so a future maintainer can answer "why is this version locked?" without archaeology.
Scaling: Snapshot cost scales with suite size × $GLOBALS size × object-graph complexity. Selective snapshots are the primary lever — enforce them as the wrapper default. Parallel execution is the scaling hazard: restore touches process-wide state, so correctness under Paratest must be proven with a repeat-run soak, not inferred from a green local build. Watch long-running suite memory, as snapshotted object graphs retain references between runs.
Failure modes: Silent under-capture (dynamic properties and internal object state escape the snapshot, producing tests that pass locally and leak in CI) · session/cookie races under concurrency · Composer resolution breaking on a PHP upgrade because the transitive pin moved underneath us · memory retention from large snapshotted graphs inflating long-running suite memory · and the organizational failure mode — over-adoption becoming cover for leaving global-state design problems unfixed in production code. That last one is the TPM-relevant risk: a test-suite workaround is not a remediation plan, and it resurfaces later as an incident or an unplanned refactor.
Ramp-up: Low. Any engineer fluent in PHPUnit can use the snapshot/restore pair within an hour; the non-obvious learning is what is excluded by default and why selective snapshots matter, which is a one-page internal doc plus a code-review convention, not a training program. Keep the wrapper opinionated so correct usage is the path of least resistance, and ramp-up stays a non-issue at program scale.
How can I help you explore Laravel packages today?