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

Global State Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

It is almost certainly already installed. sebastian/global-state is a hard dependency of phpunit/phpunit, so check first:

composer show sebastian/global-state   # note the resolved version

Only run composer require --dev sebastian/global-state if you intend to own the pin (i.e. you're building a wrapper other teams depend on). Otherwise you inherit it transitively and skip the upgrade-ticket liability.

Two files tell you everything. There is no real docs beyond the README, so read the source:

  • vendor/sebastian/global-state/src/Snapshot.php — the constructor booleans are the entire configuration surface (constants, functions, classes, interfaces, traits, globalVariables, superGlobalArrays, staticProperties, iniSettings, includedFiles). Toggling these off is how you scope a capture.
  • vendor/sebastian/global-state/src/Restorer.php — the restore methods map 1:1 to those booleans (restoreGlobalVariables(), restoreStaticProperties(), restoreIniSettings(), etc.). You call them explicitly; nothing is automatic.
  • vendor/sebastian/global-state/src/CodeExporter.php — turns values into PHP source. This is your debugging tool, not a runtime feature.

The canonical first use case is a leaky legacy test that passes alone and fails in a full run. Prove the leak before adopting anything:

use SebastianBergmann\GlobalState\Snapshot;
use SebastianBergmann\GlobalState\Restorer;

$before = new Snapshot(new CodeExporter, constants: false, functions: false,
    classes: false, interfaces: false, traits: false,
    globalVariables: true, superGlobalArrays: true,
    staticProperties: true, iniSettings: false, includedFiles: false);

// ... run the suspect code ...

(new Restorer($before))->restoreGlobalVariables();
(new Restorer($before))->restoreStaticProperties();

If that fixes the failure, you have a confirmed cause. If it doesn't, close the ticket — you don't have a global-state problem, you have an isolation problem that RefreshDatabase, Facade::clearResolvedInstances(), or a fresh $this->app already covers.

Implementation Patterns

Wrap it, cap it, don't sprinkle it. The package should appear in exactly one file in your repo — a trait in your shared base TestCase, roughly 40–60 lines. Engineers call $this->isolateGlobalState(), never new Snapshot(...).

Use try/finally, not tearDown(). Teardown ordering breaks the moment another team inserts a base class or a trait between you and the framework, and a restore that silently doesn't run is worse than no restore at all:

protected function isolateGlobalState(): void
{
    if (! $this->snapshotGlobals ?? true) {
        return;
    }

    $snapshot = new Snapshot(new CodeExporter, /* selective booleans */);

    try {
        // test body runs in the subclass via setUp/tearDown
    } finally {
        (new Restorer($snapshot))->restoreGlobalVariables();
        (new Restorer($snapshot))->restoreStaticProperties();
    }
}

In practice you'll pair this with Laravel's own teardown and pick an explicit order: restore globals after $this->app->flush() and Facade::clearResolvedInstances(), because the container flush itself can write to statics (event dispatcher, facade roots). Snapshot early in setUp — before the test seeds anything — otherwise you're restoring to a polluted baseline.

Default to selective snapshots. Full captures are slow (cost scales with $GLOBALS size × object-graph complexity) and mostly useless in a Laravel suite. The realistic default is globalVariables + superGlobalArrays + staticProperties, everything else off:

new Snapshot(
    new CodeExporter,
    constants: false, functions: false, classes: false,
    interfaces: false, traits: false, iniSettings: false,
    includedFiles: false, globalVariables: true,
    superGlobalArrays: true, staticProperties: true,
);

includedFiles and functions in particular are almost never what leaked and are the expensive parts.

Exclude by not capturing, not by pruning. Older versions shipped an ExcludeList/Blacklist; recent versions favour the constructor booleans plus opting individual tests out via a property flag (e.g. protected bool $snapshotGlobals = false;) on tests that genuinely need to mutate $_ENV or a static cache. Verify the exclusion mechanism against your resolved version — this is the API that has churned most across majors.

Use CodeExporter as a diagnostic, not a fixture generator. When a test fails and you don't know which variable changed, dump $exporter->export($value) for the suspects and diff it across two snapshots. Handy for a one-off investigation; do not bake exported code into tests.

Non-PHPUnit harness is the strongest fit. If you have a custom CLI harness, an artisan command that loops over jobs, or a long-running worker that needs a deterministic reset between iterations, this is where the package earns its keep — there's no framework teardown to lean on:

while ($job = $queue->pop()) {
    $baseline = new Snapshot(new CodeExporter, /* selective */);
    try {
        $handler->handle($job);
    } finally {
        (new Restorer($baseline))->restoreGlobalVariables();
        (new Restorer($baseline))->restoreStaticProperties();
    }
}

Instrument before and after. Run with --order-by=random over repeat runs, record flake rate, adopt, record again. If the delta isn't measurable, revert — this package is not free at the program level once you declare it.

Gotchas and Tips

  • Silent under-capture is the failure mode that bites. Static capture is reflection-based and sees declared properties only. Dynamic properties (#[AllowDynamicProperties], stdClass casts), closures captured in globals, internal object state, and resources all escape the snapshot. Your test goes green locally and the state leaks in CI. Treat a green run here as evidence, never as proof.
  • Never let this into runtime code. Restoring $_ENV, $_SERVER, or $_SESSION outside tests/ is a stability and security problem. Enforce with a CI grep or a PHPStan rule if you have src/ in the same repo — a single use SebastianBergmann\GlobalState\Restorer; under app/ is a stop-work signal.
  • Superglobals aren't uniformly restorable. $_SESSION, $_COOKIE, $_FILES, and $_REQUEST have semantics beyond array contents; restoring the array doesn't undo what the test did to the session handler or the response headers. Treat them as out of scope rather than assuming a clean rollback.
  • $_ENV/$_SERVER behaviour has changed at patch level. 8.0.1 reworked superglobal snapshot performance and 8.0.2 restored nullable variables in superglobal arrays — so any selective-snapshot array you copy from a blog post needs per-version verification. Same for constant-name escaping, fixed in 8.0.3 / 9.0.1; if you're exporting code for anything containing backslashes or quotes, check the version.
  • The version matrix is the real operational cost. v9 drops PHP 8.3; v8.x targets 8.0–8.2; v7.x spans 8.1–8.3 — and because it rides with PHPUnit majors, its window silently gates your Laravel/PHP upgrade train. Put the resolved version and its PHP ceiling in your dependency register and re-check at every Laravel, PHP, or PHPUnit major.
  • Memory retention. A Snapshot holds live object references. Long-running suites that snapshot per-test and never release will retain object graphs across runs; scope the snapshot's lifetime to the single test body and let it go out of scope.
  • Parallel execution needs proving, not assuming. Full-snapshot restore mutates process-wide state. Under Paratest the semantics must be validated with a repeat-run soak — a green local build tells you nothing about cross-worker correctness.
  • Readonly and typed statics. Reflection-based restore of static readonly properties or typed properties with a null default can throw. If a class has been modernised since the leaky test was written, the restore may fail rather than restore. Wrap restore in a try/catch that fails the test loudly — a swallowed restore error is the worst outcome.
  • The most important tip is a scope one. This package cleans up test leakage; it doesn't fix the global-state design in production code. If a feature is leaking statics between tests, that's usually a real architectural problem (singleton caches, mutable service locators) that will resurface in Octane, Swoole, or long-lived queue workers — and the fix there is an explicit reset contract on your own services, not a snapshot in tests/.
  • Kill criterion. If you can't attribute a measured flake to global/static leakage, don't declare the dependency, don't build the wrapper, and keep consuming it transitively through PHPUnit. That's the correct outcome, not a shelved initiative.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console