adriansuter/php-autoload-override
Override fully qualified global function calls inside class methods so you can mock them in tests. Works with PHP 8.2+ and Composer PSR-4 autoloading; integrates via a PHPUnit bootstrap using OverrideFactory to map functions (e.g., rand) to real implementations.
Installation:
composer require --dev adriansuter/php-autoload-override ^2.0
Ensure your project uses PSR-4 autoloading (PSR-0 is unsupported).
Bootstrap Setup:
In your tests/bootstrap.php, initialize overrides using OverrideFactory:
use AdrianSuter\Autoload\Override\OverrideFactory;
$classLoader = require __DIR__ . '/../vendor/autoload.php';
OverrideFactory::create()
->forClass(\My\App\Probability::class, ['rand' => \rand(...)])
->apply($classLoader);
First Test Case:
Override \rand() in ProbabilityTest:
use AdrianSuter\Autoload\Override\MockRegistry;
MockRegistry::set(\My\App\Probability::class, 'rand', 35);
$this->assertSame('blue', $probability->pick(34, 'red', 'blue'));
OverrideFactory, MockRegistry, and Override classes in src/.tests/ for real-world usage patterns.Define Overrides:
Use OverrideFactory to chain overrides for multiple classes:
OverrideFactory::create()
->forClass(\Clock::class, ['time' => \time(...)])
->forClass(\Probability::class, ['rand' => \rand(...)])
->apply($classLoader);
Test Execution:
MockRegistry::set(\My\App\Class::class, 'function', $mockValue);
tearDown() to avoid test pollution:
protected function tearDown(): void {
MockRegistry::reset(\My\App\Class::class);
}
Global Overrides:
Use MockRegistry::setGlobal() for namespace-wide mocks (e.g., \time()):
MockRegistry::setGlobal('time', 1234567890);
Laravel-Specific:
Place bootstrap logic in tests/TestCase.php or a dedicated TestServiceProvider:
public function setUp(): void {
parent::setUp();
OverrideFactory::create()
->forClass(\App\Services\Payment::class, ['log' => fn($msg) => null])
->apply($this->app->get('autoload'));
}
Dynamic Overrides:
Build declarations dynamically in AbstractTestCase:
protected function getOverrideDeclarations(): array {
return OverrideFactory::create()
->forClass(\App\Models\User::class, ['str_random' => fn($len) => 'test'])
->build();
}
Namespace-Level Overrides:
Target entire namespaces (e.g., \App\Services\):
Override::apply($classLoader, [
'App\Services\' => ['log' => fn($msg) => null]
]);
PSR-4 Requirement: Fails silently with PSR-0 autoloading. Verify with:
composer dump-autoload --optimize
Closure Evaluation:
Override closures are evaluated on every call unless guarded with MockRegistry::has():
'rand' => function ($min, $max) {
return MockRegistry::has(\Class::class, 'rand')
? MockRegistry::get(\Class::class, 'rand')
: \rand($min, $max);
}
Test Isolation:
Forgetting MockRegistry::reset() causes bleeding mocks between tests. Use:
MockRegistry::reset(); // Clears all overrides
Namespace Collisions:
Overriding functions in the same namespace as your overrides (e.g., \PHPAutoloadOverride) may cause conflicts. Use explicit namespaces:
Override::apply($classLoader, [
'App\' => ['time' => 'App\Testing\Overrides']
]);
Verify Overrides:
Check if overrides are applied by inspecting the modified class source (use var_dump(file_get_contents()) on the loaded file).
Logging:
Enable debug mode in OverrideFactory:
OverrideFactory::create()->setDebug(true)->apply($classLoader);
Coverage Tools: The library preserves coverage reports (tested with Xdebug). Ensure no side effects in CI pipelines.
Custom Override Namespaces:
Extend Override::apply() to support custom namespaces:
Override::apply($classLoader, [
'App\Services\' => ['log' => 'App\Testing\Mocks']
]);
Pre/Post-Processing:
Hook into the parser via OverrideFactory::getParser() to modify AST nodes before overriding:
$factory->setParserCallback(function ($parser) {
$parser->addNodeVisitor(new CustomVisitor());
return $parser;
});
Performance Optimization: Cache override declarations for repeated test runs:
static $overrides = null;
if (null === $overrides) {
$overrides = OverrideFactory::create()->build();
}
Override::apply($classLoader, $overrides);
How can I help you explore Laravel packages today?