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

Phpunit Globals Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (dev only):

composer require --dev zalas/phpunit-globals

Register the extension. On PHPUnit 10–13 add it to phpunit.xml (the <extensions> block must sit in the schema-correct position — after <source>/<coverage>, before <logging>):

<extensions>
    <bootstrap class="Zalas\PHPUnit\Globals\GlobalsExtension"/>
</extensions>

The same registration works across PHPUnit 10, 11, 12 and 13 as of v4.2.0. On PHPUnit 9 the registration syntax differs (listener/extension API); check the README for that version before upgrading a legacy suite.

First use case — a unit test for a legacy bridge that reads a global, without polluting every other test:

use PHPUnit\Framework\TestCase;
use Zalas\PHPUnit\Globals\Attribute\GlobalVariable;

final class LegacyFlagTest extends TestCase
{
    #[GlobalVariable('legacy_flag', 'on')]
    public function test_flag_is_read(): void
    {
        $this->assertSame('on', $GLOBALS['legacy_flag']);
    }
}

Where to look first: the package is tiny, so vendor/zalas/phpunit-globals/src/Attribute/ is the authoritative list of attributes and their constructor signatures, and src/ shows exactly when the extension writes the globals (before setUp, around the test, etc.). Read the GlobalVariable and extension classes before designing around it — the README examples are the whole API surface.


Implementation Patterns

1. Class-level defaults, method-level overrides. Put shared globals on the test class and override per test. Verify precedence once with a throwaway assertion — the extension reads both scopes, and the merge order matters if you rely on it.

#[GlobalVariable('app_mode', 'test')]
final class BillingTest extends TestCase
{
    #[GlobalVariable('app_mode', 'sandbox')]
    public function test_sandbox_path(): void { /* ... */ }
}

2. Pair with backup/restore for suites that share a process. Globals are process-wide, so a value set for one test leaks into the next unless it is restored. Use PHPUnit's native #[BackupGlobals(true)] (or the package's backup attribute if present) on classes that touch $GLOBALS, and confirm the snapshot is taken before the extension writes your value — otherwise the restore can either wipe your attribute value mid-test or leave it behind for the next test. A quick two-test probe (set in one, assertArrayNotHasKey in the next) settles it.

3. Laravel $_ENV / $_SERVER bridging. env() and many legacy integrations read $_SERVER/$_ENV, not $GLOBALS keys you invent. Because attribute arguments are constant expressions, you cannot do array_merge($_SERVER, [...]) inside the attribute. Two workable patterns:

  • Set a single whole array (careful: it replaces the superglobal, so include the keys the framework needs):
#[GlobalVariable('_SERVER', ['REQUEST_METHOD' => 'POST', 'REMOTE_ADDR' => '127.0.0.1'])]
  • For anything dynamic or additive, skip the attribute and do it in setUp()/a trait, where you can merge and also call putenv():
protected function setUp(): void
{
    parent::setUp();
    $_SERVER['HTTP_X_TENANT'] = 'acme';
    putenv('HTTP_X_TENANT=acme');
}

The package shines for static, per-test scalars; merge-style superglobal work is usually clearer in setUp().

4. A base test case / trait for a family of tests. If several tests need the same legacy globals, define a small abstract LegacyBridgeTestCase that carries the class-level #[GlobalVariable] attributes, and extend it. Keeps the attribute noise out of individual tests and gives one place to delete when the legacy code is removed.

5. Pest. Pest test functions can carry PHP attributes in recent versions, but confirm the extension actually fires for your Pest version with one smoke test before converting a suite.

6. Data providers are not a fit. You cannot interpolate provider values into attribute arguments. If a global must vary per data set, set it at the top of the test body or in a setUp() keyed off the current data set.


Gotchas and Tips

  • Unknown attributes fail silently. PHP does not error on a misspelled or unimported attribute class until reflection instantiates it. If you forget use Zalas\PHPUnit\Globals\Attribute\GlobalVariable;, the test simply doesn't see the global. Always add a smoke assertion (assertArrayHasKey('my_global', $GLOBALS)) the first time you wire it up.
  • Forgetting the extension registration is also silent. Attributes are inert without GlobalsExtension in phpunit.xml. Check this first when "the attribute does nothing."
  • Constant expressions only. Attribute arguments accept scalars, arrays, constants, and class constants — no env(), config(), function calls, or closures. Objects are risky: new in attribute arguments is PHP 8.1+ and the extension may not support object values anyway.
  • Setting a superglobal replaces it. #[GlobalVariable('_SERVER', [...])] overwrites the entire $_SERVER for that test, which can break framework code expecting argv, SCRIPT_NAME, etc. Prefer app-specific globals or setUp() merging.
  • $_ENV is often empty in CLI. PHP's variables_order typically omits E, so writing $_ENV through the attribute may not influence libraries that read it. Prefer $_SERVER and putenv() for env()-style lookups.
  • Laravel's env() and config cache. env() reads $_SERVER/$_ENV/getenv() dynamically, so test-time changes are usually visible — but never run php artisan config:cache in the test lifecycle, and remember config() values are already frozen at boot. If production code reads config(), setting $_SERVER won't help.
  • phpunit.xml <env> vs. attributes. <env name="FOO" value="bar"/> is applied at bootstrap; the attribute is applied at test time. They can disagree. Pick one mechanism per key, or you'll debug a "why is it bar?" that isn't the attribute's fault.
  • Parallel testing. Paratest/--parallel gives each worker its own process, so globals don't cross workers — but within a worker, leakage across tests still happens. Backup/restore is not optional in parallel suites.
  • Ordering with setUp(). Determine whether the extension writes before or after your setUp(); if it writes before, your setUp() can read the value, but any setUp() assignment overrides the attribute. Don't set the same key in both places.
  • Version pinning. This is a small, low-dependency package: pin the version in composer.json, and keep a one-line smoke test so a future PHPUnit upgrade that breaks the extension fails loudly rather than silently. v4.2.0 adds PHPUnit 13 support (PR #57) but otherwise changes nothing, so a suite on PHPUnit 10–12 needs no code changes to take it; a PHPUnit 13 upgrade is now covered by the same extension registration. The source is small enough to vendor-inspect when debugging.
  • Extension point. If you need computed globals (e.g., a timestamp or a value from a fixture), don't fight the attribute API — write a small PHPUnit extension or a setUp() helper that sets $GLOBALS and registers cleanup. The package is best treated as sugar for the static case, not a general global-state manager.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels