goaop / goaop-laravel-bridge
Integration bridge for Go! AOP framework
Fund package maintenance!
Requires
- php: ^8.4
- goaop/framework: ^4.0@dev
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-02 13:50:01 UTC
README
Integration bridge for the Go! AOP framework and Laravel. It boots the AOP engine early in the application lifecycle so aspects are woven into your classes transparently — no code generation steps, no manual proxies.
Why Aspect-Oriented Programming in Laravel?
Some logic doesn't belong to any single class, yet ends up copied into hundreds of them: logging, caching, metrics, transactions, authorization checks, rate limiting, retry policies. Laravel's middleware solves this for HTTP requests — AOP solves it for any method in your codebase. Define the behavior once as an aspect, declare where it applies with a pointcut expression, and the engine weaves it in automatically. Your services stay pure business logic.
✨ Highlights
- 🪡 Transparent weaving — aspects are applied by transforming classes at load time. No base classes to extend, no interfaces to implement, no
Proxywrappers to maintain, no changes to how younewor inject your services. - 🏷️ Modern PHP 8 attributes — advices are declared with
#[Before],#[After],#[Around]and#[AfterThrowing]right on your aspect methods, with a powerful regexp-like pointcut syntax (execution(public App\Services\**->*(*))). - 🧩 Laravel-native integration — package auto-discovery, publishable config, aspects resolved through the container (constructor DI works), registration via a simple config list or the
goaop.aspectservice tag,php artisan aboutsupport. - 🚀 Production-friendly — pre-generate all proxies at deploy time with
php artisan aop:warmup; withdebugoff the woven code is served straight from cache and plays nicely with opcache. - 🔬 Pure PHP, no magic runtime — built on Go! AOP 4.x: no PECL extensions, no
eval(), all transformations produce reviewable static PHP files. - ✅ Proven by tests — the bridge ships with end-to-end weaving tests (advice execution,
Aroundreturn rewriting, cache generation) running against Laravel 12 and 13 on PHP 8.4.
Requirements
- PHP >= 8.4
- Laravel 12 or 13
- goaop/framework 4.x (attribute-based aspects)
Installation
composer require goaop/goaop-laravel-bridge
The service provider is registered automatically via package discovery.
Note Until goaop/framework 4.0 is tagged, the bridge depends on the unreleased
4.0-devline, so your application'scomposer.jsonneeds:"minimum-stability": "dev", "prefer-stable": true
Publish the configuration if you want to tweak it:
php artisan vendor:publish --tag=goaop-config
Defining an aspect
Aspects are plain classes implementing Go\Aop\Aspect whose advice methods are declared with PHP 8 attributes (#[Before], #[After], #[Around], #[AfterThrowing]):
<?php namespace App\Aspects; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Attribute\Around; use Go\Lang\Attribute\Before; use Illuminate\Support\Facades\Log; class LoggingAspect implements Aspect { #[Before('execution(public App\Services\**->*(*))')] public function logMethodCall(MethodInvocation $invocation): void { Log::debug('Calling ' . $invocation->getMethod()->getName()); } #[Around('execution(public App\Services\PaymentService->charge(*))')] public function measureCharge(MethodInvocation $invocation): mixed { $start = hrtime(true); try { return $invocation->proceed(); } finally { Log::info('charge() took ' . (hrtime(true) - $start) / 1e6 . 'ms'); } } }
See the Go! AOP documentation on core concepts and creating aspects for the full pointcut expression syntax.
Auto-discovery of aspects
You never register aspects with the AOP engine manually. During the application's boot phase — after all service providers have registered, before your application code runs — the bridge collects aspects from two sources and hands each of them to the engine's aspect container:
-
The
go_aop.aspectsconfig list. Every class listed here is registered with the engine as soon as the AOP kernel starts, before any service provider boots, and resolved through the Laravel service container on the first call of one of its advices. Constructor dependency injection works out of the box, and an aspect may depend on services that only become available once the application has fully booted:// config/go_aop.php 'aspects' => [ App\Aspects\LoggingAspect::class, ],
-
The
goaop.aspectcontainer tag. Any service tagged withgoaop.aspectin one of your service providers is picked up automatically — useful when an aspect needs non-trivial construction logic, or when a package wants to contribute aspects without touching your config:// app/Providers/AppServiceProvider.php — in register() $this->app->singleton(LoggingAspect::class, function ($app) { return new LoggingAspect($app->make(LoggerInterface::class)); }); $this->app->tag([LoggingAspect::class], 'goaop.aspect');
Tagged services are resolved when the bridge's provider boots (a Laravel tag can only be enumerated by resolving its services), so prefer the config list for aspects with heavy or boot-dependent constructors.
Both sources can be combined; duplicates are registered only once. Every discovered class must implement Go\Aop\Aspect, otherwise the bridge fails fast with a descriptive exception. The Laravel container is attached to the engine's aspect container as its PSR-11 delegate, so advices and aspect factories can look up application services through it. Once registered, the engine reads the #[Before]/#[After]/#[Around]/#[AfterThrowing] attributes from the aspect's methods and weaves the advices into every class matched by their pointcut expressions as it is loaded (or ahead of time via aop:warmup).
Configuration
Key options in config/go_aop.php (all overridable via env):
| Option | Default | Purpose |
|---|---|---|
debug |
GOAOP_DEBUG → APP_DEBUG |
Re-weave when sources change; keep off in production |
appDir |
base_path() |
Root directory the weaver may touch |
cacheDir |
storage_path('framework/aop') (GOAOP_CACHE_DIR) |
Where woven sources and proxies are cached |
cacheFileMode |
0770 (GOAOP_CACHE_PERMISSIONS, octal digits, e.g. "770") |
chmod for cache files |
includePaths |
[app_path()] |
Only these directories are woven |
excludePaths |
[] |
Never weave these |
features |
0 (GOAOP_FEATURES) |
Bitmask of Go\Aop\Features engine features (INTERCEPT_INCLUDES = 4, PREBUILT_CACHE = 64; function and new interception is driven by pointcuts and needs no flag) |
aspects |
[] |
Aspect classes to auto-register |
Deployment
Weaving happens lazily on first load of each class. To pre-generate all proxies during deployment (recommended with debug => false):
php artisan aop:warmup
The command exits with a non-zero status when a file cannot be woven, so a deploy script stops instead of shipping an incomplete cache. Weaving errors are written to the error output; pass --fail-fast to stop at the first one.
Large applications are woven faster in parallel worker processes:
php artisan aop:warmup --workers=8
Every worker is the application's own artisan running the hidden aop:warmup-worker command, so it boots with the same environment, configuration and aspects as the command itself (an --env option is passed on). The command hands the files out, merges the cache records of all workers and writes the cache metadata once at the end. Each weaving process keeps the last 100 parsed files in memory; raise --cached-files to re-parse less, lower it to use less memory.
To prepare the cache of a new release next to the running one, build it in another directory:
php artisan aop:warmup --cache-dir=/srv/app/releases/next/storage/framework/aop
The kernel (and the workers) keep serving from their own directory while the other one is built from scratch, and a cache built this way is complete and location independent.
php artisan about shows the current AOP cache location and debug mode.
Caveats
- Weaving starts at the beginning of the application's boot phase. Classes that are already loaded before that (very early bootstrap code, other packages'
register()internals) cannot be woven. - Only classes under
appDir+includePathsare considered — vendor code is not woven. - Exception handlers are best excluded via
excludePaths: when a fatal error occurs, a woven handler may not be loadable from a cold cache.
License
MIT — see LICENSE.