zalas/phpunit-globals
A lightweight PHPUnit extension that backs up and restores PHP globals between tests so leaked global state can't break your suite. Register the listener, add the trait, and annotate which globals to preserve for clean, predictable test runs.
What it is: A dev-only PHPUnit extension that lets test classes and methods declare global variables via PHP attributes (e.g. #[GlobalVar('key', 'value')]) instead of mutating $GLOBALS by hand in setUp()/tearDown(). It plugs into PHPUnit's supported extension API, so it does not monkey-patch the framework.
Change in v4.2.0: A single, additive change — the PHPUnit version constraint is widened to allow PHPUnit 13.0. No new features, no deprecations, no API changes, no change to attribute semantics, no change to the PHP minimum. It is a compatibility-broadening minor release, not a behavioral one.
Architecture fit: Unchanged. Narrow, well-bounded concern in the test infrastructure layer. No runtime coupling to application code, no production surface, and it does not alter Laravel's container, config, or bootstrapping. Conceptually it is a declarative wrapper over a pattern teams already use ad hoc — the appeal is that it makes global-state dependencies in tests explicit and greppable rather than hidden inside lifecycle hooks.
Integration feasibility: Remains high for projects on PHPUnit 10+ and PHP 8.1+. The v4.2.0 release materially improves the forward path: previously a PHPUnit major bump would have forced a fork or a version pin, and now the same extension carries from PHPUnit 10 through 13. Integration is still composer require --dev, one <extensions> entry in phpunit.xml, and per-test attribute usage; realistic effort is under a day for a pilot. Projects on PHPUnit 9 remain blocked — the extension API it depends on does not exist there.
Technical risk: Still low blast radius, but the release slightly changes the risk shape. Supporting four PHPUnit majors in one package means more conditional compatibility code and a wider untested-by-you matrix; "supports 13" means "declares support for 13," added on release day with limited production soak. The genuine risks remain behavioral: cleanup semantics (restored vs. merely unset), ordering interactions with other extensions and with setUp/tearDown, incompatibility with process isolation and parallel runners (values set in the parent do not exist in forked processes), and Pest closures where class-level attributes may not apply. The "extension not registered" failure mode is still the sharpest: attributes are inert, tests still pass, and the global is silently unset — false confidence rather than a red build. Widening the constraint increases the chance that a routine composer update moves you onto a major the extension nominally supports but that your team has not validated.
Key questions:
^10.5 || ^11 || ^12 || ^13) and whether 13.0 support is CI-tested or declared-only. Check the repo's test matrix, not just the changelog line.composer update float us into a new major?@runInSeparateProcess, and Pest v2/v3.bootstrap/app.php)? Attribute timing may be too late for that class of dependency.Stack fit: Best suited to Laravel codebases carrying legacy or third-party code that reads $GLOBALS or superglobals — configurations that are painful to refactor but easy to pin in a test. It is a complement to, not a replacement for, proper dependency injection; teams should not treat it as license to add global state to new code. Where a test can instead inject config through Laravel's container or Config::set(), that remains the better answer.
Migration path: Start read-only. Inventory existing $GLOBALS[...] = writes and superglobal mutations across the test suite to size the opportunity. Pilot on one or two test classes with the highest density of manual global setup, keep the change behind a branch, and compare behavior with the existing setUp() approach before touching anything broadly. No production code, config, or deployment pipeline changes are required, which keeps the change reversible with a single revert commit.
Compatibility: The v4.2.0 release removes the previous hard ceiling at PHPUnit 12 — the package now spans PHPUnit 10 through 13. This is good news for sequencing future PHPUnit upgrades, but it does not change the floor: still PHPUnit ≥ 10 and PHP ≥ 8.1. Note that PHPUnit 13 itself will carry a higher PHP minimum; confirm the extension's declared support does not implicitly raise your required PHP version in your lockfile. Verify the PHPUnit version in composer.json and the Laravel skeleton's phpunit.xml schema — older skeletons ship a PHPUnit 9 schema without <extensions> and need the XML upgraded first. Check that the CI matrix does not pin an older PHPUnit for coverage runs. If the suite runs under Paratest or with process isolation anywhere, scope adoption to non-isolated tests until behavior is confirmed.
Sequencing:
composer.json (do not accept an unbounded constraint) so that the widened extension constraint cannot silently drag the suite onto PHPUnit 13 before the team has validated it.Maintenance: Minimal ongoing cost — pin the version, let Dependabot propose bumps, and review each release. v4.2.0 is evidence of active maintenance: a same-day PHPUnit 13 compatibility update is exactly the signal you want from a single-maintainer package. That materially lowers, though does not eliminate, the abandoned-tooling risk. The small contingency for a fork still stands — the code surface is a handful of classes, so an internal copy is roughly a half-day of work.
Support: No commercial support and no meaningful community forum. Ownership must be assigned explicitly to the platform/QA engineering group, documented in the testing standards page, so it does not become orphaned tooling that nobody understands when it breaks during a PHPUnit upgrade. Because the package's release cadence is now tied to PHPUnit's, the owning team should subscribe to PHPUnit release announcements as the earliest indicator an extension update is coming.
Scaling: Negligible performance impact — attribute reflection per test class is microseconds against a suite that already boots an application per test. It scales with test count, not with production traffic, so there is no capacity planning implication. The scaling concern is organizational: the more widely the attribute convention spreads, the more the team depends on a single-maintainer package, which argues for the fork contingency being prepared rather than improvised. The widened version support reduces upgrade friction but does not change the dependency concentration.
Failure modes:
composer update, which is why the PHPUnit major should be pinned.Ramp-up: Very low. One attribute and one XML block is a same-day skill for a mid-level PHP developer; a short README excerpt in the team wiki plus one worked example is sufficient onboarding. The training burden is not in the tool but in the discipline around it: knowing when not to reach for it.
Recommendation: Adopt as a narrowly scoped dev dependency where legacy global reads already exist, contingent on the PHPUnit version check and the silent-failure guard. v4.2.0 improves the durability case — take the bump, pin your PHPUnit major deliberately, and confirm in CI whether PHPUnit 13 support is tested or merely declared. Do not treat the pattern as endorsement for new code, and keep the fork path pre-approved given the single-maintainer profile.
How can I help you explore Laravel packages today?