Weave Code
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

Php Autoload Override Laravel Package

adriansuter/php-autoload-override

Override fully qualified function calls inside PHP class methods so you can mock them in tests. Works with Composer PSR-4 autoloading (PHP 8.2+). Use OverrideFactory to map functions (e.g., rand) to custom implementations for deterministic PHPUnit tests.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup for Laravel

  1. Install the package in your Laravel project:
    composer require --dev adriansuter/php-autoload-override
    
  2. Register overrides in bootstrap/app.php (or a TestingServiceProvider):
    use AdrianSuter\Autoload\Override\OverrideFactory;
    
    $app->booting(function () {
        OverrideFactory::create()
            ->forClass(\App\Services\RandomService::class, ['rand' => \rand(...)])
            ->apply($app->get('composer.autoload.classloader'));
    });
    
  3. Use MockRegistry in tests to override functions:
    use AdrianSuter\Autoload\Override\MockRegistry;
    
    public function testRandomService()
    {
        MockRegistry::set(\App\Services\RandomService::class, 'rand', 42);
        $result = (new \App\Services\RandomService())->generate();
        $this->assertEquals(42, $result);
    }
    

First Use Case: Mocking \rand() in a Service Class

// app/Services/RandomService.php
class RandomService {
    public function generate(): int
    {
        return \rand(1, 100);
    }
}

Test:

public function testGenerateReturnsMockedValue()
{
    MockRegistry::set(RandomService::class, 'rand', 99);
    $this->assertEquals(99, (new RandomService())->generate());
}

Implementation Patterns

1. Centralized Override Management

Use a TestingServiceProvider to manage overrides globally:

// app/Providers/TestingServiceProvider.php
public function boot()
{
    $this->app->afterResolving('composer.autoload.classloader', function ($loader) {
        OverrideFactory::create()
            ->forClass(\App\Models\User::class, ['now' => \Carbon\Carbon::now()])
            ->forClass(\App\Services\PaymentService::class, ['time' => \time()])
            ->apply($loader);
    });
}

2. Per-Test Overrides with Traits

Create a reusable trait for test classes:

// tests/Traits/OverrideTrait.php
trait OverrideTrait {
    protected function setOverride(string $class, string $function, $value): void
    {
        MockRegistry::set($class, $function, $value);
    }

    protected function resetOverride(string $class): void
    {
        MockRegistry::reset($class);
    }
}

Usage:

use Tests\Traits\OverrideTrait;

class PaymentServiceTest extends TestCase {
    use OverrideTrait;

    public function testPaymentProcessing()
    {
        $this->setOverride(PaymentService::class, 'time', 1625097600);
        // Test logic...
    }

    protected function tearDown(): void
    {
        $this->resetOverride(PaymentService::class);
        parent::tearDown();
    }
}

3. Global Overrides for Shared Dependencies

Override functions globally (e.g., for all tests):

// tests/bootstrap.php
MockRegistry::setGlobal('time', \time());
MockRegistry::setGlobal('rand', function () { return 42; });

4. Integration with Laravel Facades

Override facade methods (e.g., Str::random()):

// bootstrap/app.php
OverrideFactory::create()
    ->forClass(\Illuminate\Support\Str::class, ['random' => fn () => 'mocked-random-string'])
    ->apply($loader);

5. Dynamic Overrides in Factories

Use factories to generate test data with mocked functions:

// tests/CreatesMockedUsers.php
use AdrianSuter\Autoload\Override\MockRegistry;

trait CreatesMockedUsers {
    public function createMockedUser(): User
    {
        MockRegistry::set(User::class, 'str_random', 'user-123');
        return User::factory()->create();
    }
}

Gotchas and Tips

Pitfalls

  1. Class Loading Timing:

    • Overrides must be applied before the target class is loaded. Place them in bootstrap/app.php or a service provider’s boot() method.
    • Symptom: Overrides fail silently if the class is already loaded (e.g., due to eager loading in Laravel).
    • Fix: Use app()->booted() to delay override application:
      $app->booted(function () {
          OverrideFactory::create()->forClass(...)->apply($loader);
      });
      
  2. OPcache Invalidation:

    • Laravel’s OPcache may cache class definitions before overrides are applied.
    • Symptom: Overrides don’t work in CI or production-like environments.
    • Fix: Add this to phpunit.xml.dist:
      <php>
          <ini name="opcache.enable" value="0"/>
      </php>
      
    • Or clear OPcache in bootstrap/app.php:
      if (function_exists('opcache_reset')) {
          opcache_reset();
      }
      
  3. Namespace Collisions:

    • Overriding functions in shared namespaces (e.g., App\) can affect unrelated classes.
    • Example: Overriding \App\Helpers\str() may break Str::random() if not scoped correctly.
    • Fix: Use fully qualified class names and avoid overlapping namespaces.
  4. Static Analysis Tools:

    • PHPStan/Psalm may flag override closures as "unresolvable" or "dynamic".
    • Fix: Suppress warnings with @phpstan-ignore-next-line or configure your static analyzer to ignore the adriansuter/php-autoload-override namespace.
  5. Performance in Non-Test Environments:

    • Overrides are only applied during tests if registered in the bootstrap. However, the autoloader modification persists for the entire request.
    • Fix: Use a test-specific bootstrap (e.g., tests/bootstrap.php) and ensure overrides are not applied in production.
  6. MockRegistry Scope Leaks:

    • Forgetting to call MockRegistry::reset() can cause test pollution.
    • Fix: Use tearDown() or a trait to auto-reset:
      protected function tearDown(): void
      {
          MockRegistry::reset(static::class);
          parent::tearDown();
      }
      

Debugging Tips

  1. Verify Overrides Are Applied:

    // Check if an override exists
    if (MockRegistry::has(\App\Services\RandomService::class, 'rand')) {
        echo "Override is set!";
    }
    
  2. Log Override Declarations:

    $declarations = OverrideFactory::create()
        ->forClass(...)
        ->build();
    \Log::debug('Override declarations:', $declarations);
    
  3. Test with MockRegistry::get():

    $mockedValue = MockRegistry::get(\App\Services\RandomService::class, 'rand');
    $this->assertEquals(42, $mockedValue);
    
  4. Check Autoloader Modifications:

    $loader = $app->get('composer.autoload.classloader');
    \Log::debug('Autoloader prefixes:', $loader->getPrefixes());
    

Extension Points

  1. Custom Override Logic: Override the MockRegistry to add logging or validation:

    MockRegistry::extend(function ($class, $function, $value) {
        \Log::debug("Setting override for {$class}::{$function} = {$value}");
        return $value;
    });
    
  2. Dynamic Overrides via Config: Load overrides from config/testing.php:

    // config/testing.php
    'overrides' => [
        \App\Services\RandomService::class => ['rand' => 42],
    ],
    
    // TestingServiceProvider
    public function boot()
    {
        collect(config('testing.overrides'))
            ->each(fn ($functions, $class) =>
                OverrideFactory::create()->forClass($class, $functions)
            )
            ->first()
            ->apply($loader);
    }
    
  3. Integration with Laravel’s Testing Helper: Extend Laravel’s Testing trait to auto-apply overrides:

    // app/Traits/WithMockedFunctions.php
    use AdrianSuter\Autoload\Override\{OverrideFactory, MockRegistry};
    
    trait WithMockedFunctions {
        public function withMockedFunctions(array $overrides): static
        {
            OverrideFactory::create()
                ->forClass(static::class, $overrides)
                ->apply($this->app->get('composer.autoload.classloader'));
            return $this;
        }
    }
    

    Usage:

    $this->withMockedFunctions(['rand' => 42])->get('/random');
    
  4. **Support for Non-PSR

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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata