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

Patchwork Laravel Package

antecedent/patchwork

Patchwork is a PHP library for monkey patching: redefine functions, methods, and classes at runtime to ease testing and legacy refactors. Works with Composer and popular test tools, enabling stubs/mocks without changing existing code.

Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require antecedent/patchwork
    

    No additional configuration is required—Patchwork works out-of-the-box.

  2. First Use Case: Override a global function (e.g., str_replace) for testing:

    use Patchwork\Patchwork;
    
    $patchwork = new Patchwork();
    
    // Patch str_replace to return a hardcoded value
    $patchwork->getFactory()->patch(
        function ($subject) {
            return 'PATCHED';
        },
        'str_replace'
    );
    
    // Now str_replace behaves differently
    echo str_replace('foo', 'bar', 'hello'); // Outputs: "PATCHED"
    
  3. Where to Look First:

    • Documentation (if available; check the repo for a README or docs folder).
    • Focus on the Patchwork class and its Factory methods (patch, patchMethod, patchStaticMethod).
    • Explore the Patchwork\Patchwork namespace for core functionality.

Implementation Patterns

Core Workflows

  1. Testing Legacy Code:

    • Use patch() to stub or mock global functions (e.g., file_get_contents, date).
    • Example: Stub file_get_contents to return a fake file for unit tests:
      $patchwork->getFactory()->patch(
          function () { return 'fake content'; },
          'file_get_contents'
      );
      
  2. Intercepting Static Methods:

    • Patch static calls (e.g., SomeClass::staticMethod()) to inject behavior:
      $patchwork->getFactory()->patchStaticMethod(
          function ($args) { return 'intercepted'; },
          'SomeClass',
          'staticMethod'
      );
      
  3. Method Interception:

    • Override instance methods dynamically:
      $patchwork->getFactory()->patchMethod(
          function ($obj, $args) { return 'patched'; },
          'SomeClass',
          'publicMethod'
      );
      
  4. Conditional Patching:

    • Use closures to conditionally apply patches (e.g., only in tests):
      if (app()->environment('testing')) {
          $patchwork->getFactory()->patch(
              function () { return 'test value'; },
              'some_function'
          );
      }
      

Integration Tips

  • Laravel Service Provider: Bind Patchwork to the container for easy access:

    public function register()
    {
        $this->app->singleton(Patchwork::class, function () {
            return new Patchwork();
        });
    }
    

    Then inject Patchwork into tests or services:

    use Illuminate\Support\ServiceProvider;
    
    class TestServiceProvider extends ServiceProvider
    {
        public function boot(Patchwork $patchwork)
        {
            $patchwork->getFactory()->patch(...);
        }
    }
    
  • Test Setup/Teardown: Apply patches in setUp() and restore original functions in tearDown():

    public function setUp(): void
    {
        $this->patchwork->getFactory()->patch(...);
        parent::setUp();
    }
    
    public function tearDown(): void
    {
        $this->patchwork->getFactory()->unpatchAll();
        parent::tearDown();
    }
    
  • Patchwork with Laravel Facades: Patch facade methods (e.g., Hash::make()) by targeting the underlying class:

    $patchwork->getFactory()->patchMethod(
        function ($obj, $args) { return 'hashed'; },
        'Illuminate\Support\Facades\Hash',
        'make'
    );
    

Gotchas and Tips

Pitfalls

  1. Scope Leaks:

    • Patches persist until explicitly unpatched. Forgetting to call unpatchAll() or unpatch() can cause tests to interfere with each other.
    • Fix: Always clean up patches in tearDown() or use context managers (e.g., Patchwork\Context).
  2. Static Method Ambiguity:

    • Patching static methods requires exact class and method names. Typos or namespace issues will silently fail.
    • Fix: Use fully qualified names (e.g., \Some\Namespace\Class::method).
  3. Performance Overhead:

    • Runtime patching adds minimal overhead, but excessive patches (e.g., patching every function in a loop) can slow tests.
    • Fix: Limit patches to only what’s necessary for the test.
  4. Global State Pollution:

    • Patching global functions (e.g., date, time) can affect unrelated parts of the application.
    • Fix: Restrict patches to test environments or use namespaced alternatives.
  5. Closure Captures:

    • Patches are closures, so captured variables (e.g., $this) may not behave as expected in static contexts.
    • Fix: Use arrow functions or bind the closure to avoid scope issues:
      $patchwork->getFactory()->patch(
          fn() => 'value',
          'some_function'
      );
      

Debugging Tips

  1. Verify Patches: Use Patchwork\Patchwork::getPatches() to inspect active patches:

    print_r($patchwork->getPatches());
    
  2. Patch Order Matters: Later patches override earlier ones. Use unpatch() to revert selectively:

    $patchwork->getFactory()->unpatch('some_function');
    
  3. Logging Patches: Wrap patch logic in try-catch blocks to log failures:

    try {
        $patchwork->getFactory()->patch(...);
    } catch (\Exception $e) {
        \Log::error("Patch failed: " . $e->getMessage());
    }
    

Extension Points

  1. Custom Patchwork Factory: Extend Patchwork\Factory to add domain-specific patching logic:

    class CustomFactory extends \Patchwork\Factory
    {
        public function patchLaravelFacade($callback, $facade, $method)
        {
            $this->patchMethod($callback, $facade, $method);
        }
    }
    
  2. Patchwork Middleware: Create middleware to apply patches per request (e.g., for debugging):

    class PatchMiddleware
    {
        public function handle($request, Closure $next)
        {
            $patchwork = app(Patchwork::class);
            $patchwork->getFactory()->patch(...);
    
            return $next($request);
        }
    }
    
  3. Patchwork with PHPUnit: Use Patchwork\Context to scope patches to specific test methods:

    use Patchwork\Context;
    
    public function testSomething()
    {
        Context::patch($patchwork, function () {
            // Patches active only here
            $this->assertTrue(true);
        });
    }
    
  4. Patchwork for Debugging: Temporarily patch dd() or dump() to log variables:

    $patchwork->getFactory()->patch(
        function (...$args) {
            \Log::info('Dumped: ' . print_r($args, true));
            return $args[0];
        },
        'dd'
    );
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4
php-http/client-implementation
phpcr/phpcr-implementation
cucumber/gherkin-monorepo
haydenpierce/class-finder
psr/simple-cache-implementation
uri-template/tests