Installation
composer require goaop/framework
Create an Aspect Kernel
Extend Go\Core\AspectKernel in app/ApplicationAspectKernel.php:
use Go\Core\AspectKernel;
class ApplicationAspectKernel extends AspectKernel {
protected function configureAop(AspectContainer $container): void {
// Register aspects here
}
}
Initialize the Kernel
In your front controller (e.g., public/index.php):
$kernel = ApplicationAspectKernel::getInstance();
$kernel->init([
'debug' => true,
'appDir' => __DIR__ . '/..',
'cacheDir' => __DIR__ . '/runtime/aop_cache',
'includePaths' => [__DIR__ . '/../src']
]);
Define an Aspect
Create a class implementing Go\Aop\Aspect with advice methods:
use Go\Aop\Aspect;
use Go\Lang\Attribute\Before;
class LoggingAspect implements Aspect {
#[Before("execution(public *->save(..))")]
public function logSave(MethodInvocation $invocation) {
echo "Saving: " . $invocation->getMethod()->getName() . "\n";
}
}
Register the Aspect
In configureAop():
$container->registerAspect(new LoggingAspect());
Log all method calls in a service layer:
// Aspect/LoggerAspect.php
#[Before("execution(*->*(..))")]
public function logMethod(MethodInvocation $invocation) {
$class = $invocation->getThis()->getClass()->getName();
$method = $invocation->getMethod()->getName();
file_put_contents('method_log.txt', "$class::$method\n", FILE_APPEND);
}
configureAop():
$container->registerAspect(new SecurityAspect());
$container->registerAspect(new CachingAspect());
// Target all public methods in UserService
#[Before("execution(public UserService->*(..))")]
// Target methods annotated with @Cacheable
#[Before("@annotation(Cacheable)")]
#[Before("execution(*->validate(..))")]
public function validateInput(MethodInvocation $invocation) {
$args = $invocation->getArguments();
if (empty($args[0])) throw new InvalidArgumentException();
}
#[Around("execution(*->fetch(..))")]
public function cacheFetch(ProceedingJoinPoint $pjp) {
$cacheKey = md5($pjp->getMethod()->getName() . serialize($pjp->getArguments()));
if (cache()->has($cacheKey)) {
return cache()->get($cacheKey);
}
$result = $pjp->proceed();
cache()->put($cacheKey, $result, 3600);
return $result;
}
#[After("execution(*->*(..))")]
public function logExecutionTime(MethodInvocation $invocation) {
$time = microtime(true) - $invocation->getStartTime();
Logger::info("Method {$invocation->getMethod()} took {$time}s");
}
use Go\Aop\Intercept\FieldAccess;
#[Before("fieldAccess(*->userName)")]
public function logPropertyAccess(FieldAccess $access) {
echo "Accessing userName: " . $access->getField()->getName() . "\n";
}
#[Introduce("interface Serializable")]
public function serialize($object): string {
return serialize($object->getData());
}
#[Introduce("interface Serializable")]
public function unserialize(string $serialized): void {
$object->setData(unserialize($serialized));
}
Service Provider Setup:
// app/Providers/AopServiceProvider.php
use Go\Core\AspectKernel;
class AopServiceProvider extends ServiceProvider {
public function register() {
$kernel = ApplicationAspectKernel::getInstance();
$kernel->init([
'debug' => config('app.debug'),
'appDir' => base_path(),
'cacheDir' => storage_path('framework/aop_cache'),
'includePaths' => [app_path(), database_path()]
]);
}
}
Register in config/app.php:
'providers' => [
// ...
App\Providers\AopServiceProvider::class,
],
Mock JoinPoints:
$invocation = $this->createMock(MethodInvocation::class);
$invocation->method('getMethod')->willReturn(new ReflectionMethod(User::class, 'save'));
$invocation->method('getArguments')->willReturn([$user]);
$aspect = new LoggingAspect();
$aspect->logSave($invocation);
Test Pointcut Matching:
$pointcut = new Pointcut("execution(public *->save(..))");
$this->assertTrue($pointcut->matches(new MethodJoinPoint(User::class, 'save')));
$kernel->init([
'cacheDir' => storage_path('framework/aop_cache'),
'cacheTTL' => 3600, // Cache weaved classes for 1 hour
]);
'excludePaths' => [
storage_path('framework/views'),
storage_path('framework/cache'),
],
Private Method Interception
#[Before("execution(private *->*(..))")]
Final Classes/Methods
final classes/methods, but be cautious:
final methods may cause unexpected behavior in child classes.@annotation or @within pointcuts for safer targeting:
#[Before("@annotation(Loggable)")]
Property Interception Limitations (PHP 8.4+)
static, readonly, or inherited private/final properties.get hooks (no set).Around advice for full control:
#[Around("fieldAccess(*->items)")]
public function interceptArrayAccess(ProceedingFieldJoinPoint $pjp) {
$value = $pjp->proceed();
if ($pjp->getAccessType() === FieldAccessType::WRITE) {
// Custom logic for writes
}
return $value;
}
Circular Dependencies
Aspect A depends on Aspect B, which depends on Aspect A → Refactor to avoid mutual initialization.Debugging Weaved Code
rm -rf storage/framework/aop_cache/*) if aspects aren’t applying:
php artisan cache:clear
Enable Debug Mode
$kernel->init(['debug' => true]);
cacheDir/weaved/.stderr.Inspect Weaved Classes
cacheDir/weaved/.php -r 'var_dump(class_uses(\App\Models\User::class));' to see applied traits.Pointcut Debugging
$pointcut = new Pointcut("execution(*->*(..))");
var_dump($pointcut->matches(new MethodJoinPoint(User::class, '
How can I help you explore Laravel packages today?