ejsmont-artur/php-proxy-builder-bundle
Installation
Add the bundle to your composer.json:
composer require ejsmont-artur/php-proxy-builder-bundle
Enable it in config/bundles.php:
return [
// ...
Ejsmont\PhpProxyBuilderBundle\PhpProxyBuilderBundle::class => ['all' => true],
];
First Use Case: Basic Proxy Generation
Inject the ProxyBuilder service into a controller or service:
use Ejsmont\PhpProxyBuilderBundle\ProxyBuilder;
class MyService
{
public function __construct(private ProxyBuilder $proxyBuilder) {}
public function createProxy()
{
$proxy = $this->proxyBuilder->build(
'App\Entity\MyEntity',
[
'method1' => function ($proxy, $method, $args) {
// Pre-logic
$result = $method($proxy, $args);
// Post-logic
return $result;
},
]
);
return $proxy;
}
}
Key Files to Review
src/DependencyInjection/PhpProxyBuilderExtension.php (Configuration)src/ProxyBuilder.php (Core logic)tests/ (Usage examples)Dynamic Proxy Generation
Use ProxyBuilder to wrap services/entities with runtime behavior:
$proxy = $this->proxyBuilder->build(
'App\Service\PaymentService',
[
'charge' => function ($proxy, $method, $args) {
// Validate args before calling original method
if (!isset($args[0]['amount'])) {
throw new \InvalidArgumentException('Amount required');
}
return $method($proxy, $args);
},
]
);
Dependency Injection Integration
Register proxies as services in services.yaml:
services:
App\Service\PaymentService:
class: Ejsmont\PhpProxyBuilderBundle\Proxy
factory: ['@php_proxy_builder.proxy_builder', 'build']
arguments:
- 'App\Service\PaymentService'
- { 'charge': '@App\Interceptor\ChargeValidator' }
Interceptor Pattern Create reusable interceptors (e.g., logging, caching):
// src/Interceptor/LoggerInterceptor.php
class LoggerInterceptor
{
public function __invoke($proxy, $method, $args)
{
\Log::info("Calling {$method} with " . json_encode($args));
return $method($proxy, $args);
}
}
Apply globally via config:
php_proxy_builder:
default_interceptors:
- '@App\Interceptor\LoggerInterceptor'
Method-Level Granularity Combine multiple interceptors for specific methods:
$proxy = $this->proxyBuilder->build(
'App\Service\UserService',
[
'updatePassword' => [
'@App\Interceptor\LoggerInterceptor',
'@App\Interceptor\PasswordValidator',
],
]
);
Proxy Chaining Chain proxies for layered behavior:
$loggedProxy = $this->proxyBuilder->build(
'App\Service\OrderService',
['@App\Interceptor\LoggerInterceptor']
);
$validatedProxy = $this->proxyBuilder->build(
$loggedProxy,
['create': '@App\Interceptor\OrderValidator']
);
Circular Dependencies
ServiceA proxies ServiceB, which proxies ServiceA).Method Signature Mismatches
$proxy and $args parameters).var_dump($method, $args) in interceptors to debug.Configuration Overrides
php_proxy_builder) merges with defaults. Overrides may not work as expected.!important in YAML or debug with dump($this->container->getParameter('php_proxy_builder')).Performance Overhead
php_proxy_builder:
cache_proxies: true
cache_dir: '%kernel.cache_dir%/proxies'
Type Safety
// Bad: Assumes $args[0] is always an array
$args[0]['amount'] = 100;
// Good: Type-check first
if (!is_array($args[0])) {
throw new \InvalidArgumentException('Expected array');
}
Enable Proxy Logging
Add to config/packages/dev/debug.yaml:
php_proxy_builder:
debug: true
Logs proxy generation to var/log/dev.log.
Inspect Generated Proxies Dump the proxy class to verify methods:
var_dump(get_class($proxy)); // Should show "Ejsmont\PhpProxyBuilderBundle\Proxy"
Test Interceptors Isolated Use PHPUnit to test interceptors without full proxy setup:
$interceptor = new MyInterceptor();
$result = $interceptor($this->createMock('stdClass'), 'testMethod', [1, 2, 3]);
Custom Proxy Classes
Extend Ejsmont\PhpProxyBuilderBundle\Proxy to add metadata:
class CustomProxy extends Proxy
{
public function getOriginalClass(): string
{
return $this->originalClass;
}
}
Register via config:
php_proxy_builder:
proxy_class: 'App\Proxy\CustomProxy'
Dynamic Interceptor Loading
Implement Ejsmont\PhpProxyBuilderBundle\Interceptor\InterceptorInterface for custom logic:
class DynamicInterceptor implements InterceptorInterface
{
public function __invoke($proxy, $method, $args)
{
if ($method === 'sensitiveOperation') {
$this->checkPermissions();
}
return $method($proxy, $args);
}
}
Post-Proxy Initialization
Use Symfony’s container.post_build to modify proxies:
services:
_instanceof:
App\Service\ProxiedService:
tags: ['app.proxied_service']
// src/EventListener/ProxyInitializer.php
class ProxyInitializer implements ContainerAwareInterface
{
public function onKernelRequest(GetResponseEvent $event)
{
$container = $event->getContainer();
foreach ($container->findTaggedServiceIds('app.proxied_service') as $id => $tags) {
$service = $container->get($id);
// Modify proxy behavior
}
}
}
How can I help you explore Laravel packages today?