tourze/symfony-aop-pool-bundle
Symfony bundle that brings aspect-oriented pooling to your services: wrap expensive resources in configurable pools via AOP interceptors, with acquire and release handled automatically.
tourze/symfony-aop-pool-bundle is a Symfony bundle, not a Laravel package — that distinction drives everything below. There is no ServiceProvider, no extra.laravel block, and no package discovery. Read the release as "an AOP engine you can borrow from," not "a package you composer require and forget."
composer require tourze/symfony-aop-pool-bundle and immediately read composer.json — the require block tells you which Symfony components you are dragging in (framework-bundle, dependency-injection, the underlying AOP/proxy library) and the minimum PHP version. This is the single most important file before you write a line of code.*Bundle.php) — shows what compiler passes and config it registers;DependencyInjection/Configuration.php — the authoritative list of config keys, since docs are typically thin on tourze/* packages;DependencyInjection/*Extension.php — how services are wired and which parameters/tags matter;Resources/config/*.yaml — the actual service definitions, and usually the clearest documentation of intent;Attribute/ or Annotation/ directory — this is where the marker attributes that make a method "advised" live.First realistic decision point: if you cannot articulate the cross-cutting concern in one sentence (audit logging, timing/metrics, retry, tenant scoping), stop. Laravel already solves most of these more cheaply.
Pattern A — Micro-kernel bridge (the only sane way to use the bundle itself in Laravel).
The bundle needs Symfony's DI container, compiler passes, and config tree. Laravel will not provide them. Boot a minimal Symfony ContainerBuilder (or a tiny Kernel) inside a Laravel service provider, register the bundle, compile, and resolve the AOP-managed services from that container:
// App\Providers\AopServiceProvider
public function register(): void
{
$container = new ContainerBuilder();
$container->setParameter('kernel.debug', config('app.debug'));
$container->setParameter('kernel.environment', app()->environment());
$container->setParameter('kernel.build_dir', storage_path('framework/aop'));
(new TourzeAopPoolBundle())->build($container);
$container->compile();
$this->app->instance('aop.container', $container);
$this->app->bind(BillingCalculator::class, fn () => $container->get(BillingCalculator::class));
}
You are now maintaining two containers. Budget for that. Resolve only the handful of classes that actually need advice through this container; leave the rest on Laravel's.
Pattern B — Use the underlying AOP library directly. In practice the bundle is a thin Symfony wrapper over a proxy-generating AOP engine. If you only need interception, depend on the engine, write your own ServiceProvider, and generate proxies into storage_path(). You lose the bundle's pool/warmup wiring but gain something you can debug.
Pattern C — Translate the intent into Laravel-native constructs. For most teams this is the correct outcome:
| AOP aspect you'd write | Laravel equivalent |
|---|---|
| Transactional advice | DB::transaction() closure, or DB::transactionLevel() guards |
| Timing/metrics advice | Middleware + Telescope/Clockwork, or a decorator binding |
| Retry advice | Retry::times(3)->sleep(100)->run(...) |
| Audit logging | Model observers / events |
| Tenant or scope enforcement | Global scope, middleware, or a container decorator |
Aspect hygiene, if you go with A or B. One aspect class per concern; pointcuts narrowed by attribute, never by naming convention across the whole namespace. A wildcard pointcut in a pooled/proxied container is how you get a 3-second boot time and an unreadable stack trace.
Pool semantics. The "pool" in the name implies reuse of interceptor/aspect instances across advised calls. That makes aspects stateful by accident. Keep aspect properties immutable; anything request-scoped (tenant id, correlation id, authenticated user) must be injected per-invocation, not stored on the aspect.
composer require will silently do nothing until you register something. A fresh composer require followed by "it does nothing" is the most common first-hour complaint.final classes and private/static methods cannot be advised. Proxy-based AOP requires subclassing. If your service is final, you get no advice and usually no error — just silence. Remove final, or extract an interface and type-hint the interface everywhere.BillingCalculator and the container returns a proxy, instanceof checks, get_class() comparisons, and strict constructor hints against the concrete name will fail or bypass the proxy entirely. $object::class returns the proxy class name — never persist or switch on it.storage_path('framework/aop') (or equivalent), ensure it's gitignored, writable by the web user, and cleared during deploy alongside php artisan optimize:clear. Stale proxies after a deploy are the #1 debugging sink: the code is right, the proxy is old. Add a cache-busting step keyed on composer.lock.final? (2) was the instance resolved from the AOP container or from Laravel's? (3) does the proxy file exist on disk and is its mtime newer than the class? (4) debug:container (Symfony) to confirm the service is decorated. Do not start by reading the aspect code.RequestTerminated listener or guarantee aspects are stateless. Symptom: user A's tenant id appearing in user B's logs, intermittently, only in production.unserialize() on a worker that hasn't generated proxies fails. Keep advised classes out of job payloads; pass scalars.tourze/* packages commonly ship a Configuration.php whose keys don't match examples in the README. Treat Configuration.php as the spec. Undefined keys are usually silently ignored rather than throwing — verify your config actually took effect by dumping the resolved parameters.compare link — no changelog prose. Treat the 1.0.x line as pre-stable: pin "tourze/symfony-aop-pool-bundle": "1.0.1" exactly, and read the GitHub diff (1.0.0...1.0.1) before bumping. For a project of this size, assume semantic versioning is aspirational and every bump carries a small breaking-change risk.How can I help you explore Laravel packages today?