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 Proxy Builder Laravel Package

ejsmont-artur/php-proxy-builder

Runtime proxy builder for PHP that wraps any object with Advice (AOP-style) to add cross-cutting behavior like caching. Proxies transparently delegate method calls to the target while keeping proxy, advice, and client code fully decoupled.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation Add the package via Composer:

    composer require ejsmont-artur/php-proxy-builder
    

    Register the service provider in config/app.php:

    'providers' => [
        // ...
        ArturEjsmont\ProxyBuilder\ProxyBuilderServiceProvider::class,
    ],
    
  2. Basic Usage Create a simple proxy for a class (e.g., UserRepository):

    use ArturEjsmont\ProxyBuilder\ProxyBuilder;
    
    $proxyBuilder = app(ProxyBuilder::class);
    $proxy = $proxyBuilder->build(UserRepository::class, [
        'method' => 'findById',
        'interceptor' => function ($method, $args) {
            // Pre-processing logic (e.g., logging)
            $result = $method(...$args);
            // Post-processing logic
            return $result;
        }
    ]);
    
  3. First Use Case: Logging Wrap a service method to log inputs/outputs:

    $loggerProxy = $proxyBuilder->build(UserService::class, [
        'method' => 'createUser',
        'interceptor' => function ($method, $args) use ($logger) {
            $logger->info('Creating user with args:', $args);
            $result = $method(...$args);
            $logger->info('User created:', $result);
            return $result;
        }
    ]);
    

Implementation Patterns

1. Dynamic Proxy Generation

  • Workflow: Use build() to generate proxies dynamically in service containers or middleware.
  • Example: Proxy Laravel’s Cache facade for analytics:
    $cacheProxy = $proxyBuilder->build(\Illuminate\Cache\CacheManager::class, [
        'method' => 'get',
        'interceptor' => function ($method, $args) {
            $key = $args[0];
            $hit = $method(...$args);
            app('analytics')->track('cache_hit', ['key' => $key]);
            return $hit;
        }
    ]);
    

2. AOP-Style Cross-Cutting Concerns

  • Pattern: Apply interceptors to multiple methods/classes via method patterns (e.g., *ById).
  • Example: Add validation to all save* methods:
    $proxy = $proxyBuilder->build(UserRepository::class, [
        'pattern' => '/^save/',
        'interceptor' => function ($method, $args) {
            if (empty($args[0]->email)) {
                throw new \InvalidArgumentException('Email required!');
            }
            return $method(...$args);
        }
    ]);
    

3. Integration with Laravel Bindings

  • Tip: Bind proxies to the container for seamless dependency injection:
    $app->bind(UserRepository::class, function ($app) {
        return $app->make(ProxyBuilder::class)
            ->build(UserRepository::class, [
                'method' => 'delete',
                'interceptor' => function ($method, $args) {
                    // Soft delete logic
                    $method(...$args);
                    app('log')->info('Soft-deleted user ID: ' . $args[0]);
                }
            ]);
    });
    

4. Chaining Interceptors

  • Pattern: Layer interceptors for complex workflows (e.g., auth + logging):
    $proxy = $proxyBuilder->build(OrderService::class);
    $proxy->addInterceptor('placeOrder', function ($method, $args) {
        if (!auth()->check()) throw new \UnauthorizedHttpException;
        return $method(...$args);
    });
    $proxy->addInterceptor('placeOrder', function ($method, $args) {
        app('log')->info('Order placed:', $args);
        return $method(...$args);
    });
    

5. Proxying External Services

  • Use Case: Wrap HTTP clients (e.g., Guzzle) to retry failed requests:
    $clientProxy = $proxyBuilder->build(\GuzzleHttp\Client::class, [
        'method' => '__call',
        'interceptor' => function ($method, $args) {
            try {
                return $method(...$args);
            } catch (\GuzzleHttp\Exception\RequestException $e) {
                return $method(...$args); // Retry
            }
        }
    ]);
    

Gotchas and Tips

Pitfalls

  1. Method Signature Mismatches

    • Issue: Interceptors must match the exact method signature (including &$ for references).
    • Fix: Use var_dump(func_get_args()) to debug argument structures.
  2. Proxy Lifetime

    • Issue: Proxies are stateless; shared state (e.g., counters) must be managed externally (e.g., via closures or app bindings).
    • Example:
      $callCount = 0;
      $proxy = $proxyBuilder->build(Service::class, [
          'method' => 'doWork',
          'interceptor' => function () use (&$callCount) {
              $callCount++;
              // ...
          }
      ]);
      
  3. Performance Overhead

    • Tip: Avoid overusing proxies for trivial methods. Benchmark critical paths.
  4. Circular Dependencies

    • Issue: Proxies can break circular references (e.g., A proxies B, which proxies A).
    • Fix: Use instance() in bindings or lazy-load proxies.

Debugging Tips

  • Log Interceptors: Add var_dump or dd() to interceptors to inspect calls:
    'interceptor' => function ($method, $args) {
        error_log("Method: {$method}, Args: " . print_r($args, true));
        return $method(...$args);
    }
    
  • Check Proxy Class: Use get_class($proxy) to verify the generated proxy class name (e.g., ProxyBuilder_Proxy_123).

Extension Points

  1. Custom Proxy Classes

    • Extend ArturEjsmont\ProxyBuilder\ProxyBuilder to add default interceptors:
      class CustomProxyBuilder extends ProxyBuilder {
          public function __construct() {
              $this->addDefaultInterceptor('*', function ($method, $args) {
                  // Global logging
              });
          }
      }
      
  2. Interceptor Priorities

    • Workaround: Use named interceptors to control order:
      $proxy->addInterceptor('method', $interceptor, 'auth'); // Lower priority
      $proxy->addInterceptor('method', $interceptor, 'log');  // Higher priority
      
  3. Proxy Caching

    • Optimization: Cache proxies in Laravel’s cache layer to avoid regeneration:
      $cacheKey = 'proxy_' . md5(UserRepository::class);
      return Cache::remember($cacheKey, 3600, function () use ($proxyBuilder) {
          return $proxyBuilder->build(UserRepository::class, [...]);
      });
      

Config Quirks

  • No Built-in Config: The package is lightweight; configure via code (e.g., default interceptors in the service provider’s boot()).
  • Namespace Collisions: Ensure proxy class names don’t clash with existing classes by customizing the ProxyBuilder constructor or using unique prefixes.
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.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle