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

Isolator Laravel Package

icecave/isolator

Icecave Isolator isolates and mocks PHP global state for testing. It provides PHPUnit integration to snapshot and restore global variables, superglobals, constants, and functions, so each test runs in a clean, predictable environment free of hidden state leakage and side effects.

Deep Wiki
Context7

Getting Started

Install and orient. The package is a small, single-purpose library (composer require icecave/isolator). It predates modern PHP typing (last release 2015), so expect PHP 5-era code that still runs on newer runtimes but has no return types or scalar type hints.

The problem it solves. PHP global functions (time(), rand(), microtime(), file_get_contents(), uniqid(), getenv(), etc.) are called directly by name, so there is no seam to substitute them in tests. Isolator turns them into an injectable object: instead of time(), you call $this->isolator->time(). In production the default implementation forwards to the real global function; in tests you inject a double that returns fixed values.

Where to look first.

  • Icecave\Isolator\Isolator — the core class. Its __call() is the forwarding mechanism; it is the only interesting code.
  • Isolator::get() — returns the shared/default instance, or the instance you pass in. This is the helper you will use in every constructor.

First use case. Take a class with a nondeterministic call and give it a seam:

use Icecave\Isolator\Isolator;

class TokenGenerator
{
    private $isolator;

    public function __construct(Isolator $isolator = null)
    {
        $this->isolator = Isolator::get($isolator);
    }

    public function generate()
    {
        return sha1((string) $this->isolator->microtime(true));
    }
}

In production, new TokenGenerator() works unchanged and calls the real microtime(). In a test, inject a double and the output becomes deterministic.

Implementation Patterns

Constructor injection with a null default. This is the canonical pattern and the one you should copy everywhere. The nullable Isolator $isolator = null argument plus Isolator::get($isolator) keeps existing call sites (new Foo()) working while allowing tests to pass a double. Never call Isolator::get() inside methods; resolve it once in the constructor.

Wrap only the impure calls. You do not need to route every global function through the isolator. Reserve it for anything nondeterministic or side-effecting — clocks, randomness, filesystem, network, environment, process functions. Pure helpers (strlen, array_map, sprintf) add noise and overhead for no benefit.

Testing with a mocking library. Phake and Mockery handle magic methods cleanly, which is why the original docs gravitate toward them:

$isolator = Phake::mock(Isolator::class);
Phake::when($isolator)->time()->thenReturn(1234567890);

$obj = new TokenGenerator($isolator);

With PHPUnit, time() is not a declared method, so you must mock __call itself:

$isolator = $this->getMockBuilder(Isolator::class)
    ->setMethods(['__call'])
    ->getMock();

$isolator->method('__call')->willReturnCallback(
    function ($name, array $args) {
        return $name === 'time' ? 1234567890 : null;
    }
);

A hand-rolled subclass overriding __call() is equally valid and easier to reason about when you need per-test behaviour.

Legacy adoption workflow. In an existing codebase, add an Isolator property to the class under test, mechanically rewrite time( → $this->isolator->time(, and leave everything else alone. The nullable default means you do not have to touch every instantiation site at once. Repeat class by class.

Wrapping awkward global functions. Functions with by-reference parameters (preg_match's $matches, sort, settype) cannot go through __call (see Gotchas). For those, add a small named method on your own class that delegates once, and call that — this preserves a testable seam without fighting the magic method.

Thread the isolator like a port. Treat it as a clock/IO dependency and pass it through factories and service containers rather than reaching for the static singleton inside arbitrary code. For new code, prefer a narrow interface (a clock, a filesystem abstraction) over the generic isolator; the isolator is best as a migration tool.

Gotchas and Tips

By-reference arguments break. __call cannot pass arguments by reference, so functions that mutate an out-parameter (preg_match($pattern, $subject, $matches), sort(), settype(), array_push() with references) will not behave correctly through the isolator. Wrap those in explicit methods instead of routing them through __call.

Namespace resolution is not what Isolator fixes. Inside a namespace, an unqualified time() call still resolves to the global function; Isolator only helps once you have converted the call into $this->isolator->time(). Do not expect the package to intercept bare function calls — it is a method-call facade, not a runtime hook.

Static/singleton state leaks between tests. Isolator::get() hands out a shared instance. If you rely on it rather than explicit injection, be deliberate about resetting state between tests, and be cautious in long-running processes where the singleton persists.

PHPUnit's magic-method mocking is fiddly. You must include __call in setMethods()/onlyMethods(); stubbing time() directly does nothing. The name string your code passes to the undefined method is what your stub matches on, so keep naming consistent.

No IDE support or static analysis. Because calls like $this->isolator->time() are undefined methods, autocompletion and analysis tools will flag them. Add @method annotations to your property docblock or a typed wrapper for sanity.

Performance overhead in hot paths. Every call goes through __call, which is measurably slower than a direct function call. Acceptable for I/O and clock calls; avoid in tight loops or high-frequency numeric code.

Unmaintained; know the alternatives. A 2015 package with 35 stars is effectively frozen. Before adopting, verify it works on your PHP version. For new projects consider php-mock/php-mock (namespace-level function mocking), PSR-20 clocks for time, Flysystem for filesystem access, and vfsStream for filesystem tests. Isolator remains a pragmatic choice for legacy code where a full refactor is not on the table.

Debugging unexpected global calls. Temporarily subclass Isolator and log or var_dump inside __call to discover which globals your code actually reaches for; this is a fast way to map a legacy class before refactoring.

Extension points. Subclass Isolator and override __call to add call recording, caching, or remapping specific functions, delegating to parent::__call() for everything you do not want to special-case. This is the cleanest way to add test-time or diagnostic behaviour without touching production classes.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings