Installation:
composer require jms/aop-bundle
Add to config/bundles.php:
return [
// ...
JMS\AopBundle\JMSAopBundle::class => ['all' => true],
];
Basic Usage:
# config/services.yaml
App\Service\LoggerInterceptor:
tags: ['jms.aop.method']
@Around to apply the interceptor:
use JMS\AopBundle\Annotation\Around;
class MyService {
/**
* @Around("execution(* App\Service\MyService->*(..))")
*/
public function logExecution(ProceedingJoinPoint $joinPoint) {
echo "Before method\n";
$result = $joinPoint->proceed();
echo "After method\n";
return $result;
}
}
First Use Case:
/**
* @Around("execution(* App\Controller\ApiController->*(..))")
*/
public function logApiCalls(ProceedingJoinPoint $joinPoint) {
$method = $joinPoint->getMethod()->getName();
$args = $joinPoint->getArgs();
\Log::info("API Call: {$method} with args: " . json_encode($args));
return $joinPoint->proceed();
}
Aspect-Oriented Cross-Cutting Concerns:
/**
* @Around("execution(* App\Service\*(..)) && !execution(* App\Service\LoggerInterceptor->*(..))")
*/
public function logServiceCalls(ProceedingJoinPoint $joinPoint) { ... }
public function timeExecution(ProceedingJoinPoint $joinPoint) {
$start = microtime(true);
$result = $joinPoint->proceed();
$duration = microtime(true) - $start;
\Log::debug("Method {$joinPoint->getMethod()->getName()} took {$duration}s");
return $result;
}
/**
* @Around("execution(* App\Controller\AdminController->*(..))")
*/
public function checkAdminAccess(ProceedingJoinPoint $joinPoint) {
if (!$this->isAdmin()) {
throw new \RuntimeException("Access denied");
}
return $joinPoint->proceed();
}
Pointcut Expressions:
// Target all public methods in a class
"execution(* App\Service\UserService->*(..))"
// Target methods with specific annotations
"execution(@App\Annotation\Cacheable * *(..))"
"execution(* App\Service\*(..)) && !execution(* App\Service\LoggerInterceptor->*(..))"
Integration with Symfony Services:
services:
app.cache_interceptor:
class: App\Interceptor\CacheInterceptor
tags: ['jms.aop.method']
/**
* @Around("execution(* App\EventListener\AuthListener->handle(..))")
*/
public function extendAuthListener(ProceedingJoinPoint $joinPoint) { ... }
Performance Optimization:
/**
* @Around("execution(* App\Service\DataLoader->loadHeavyData(..))")
*/
public function lazyLoadData(ProceedingJoinPoint $joinPoint) {
if (!$this->shouldLoad()) {
return $this->getCachedData();
}
return $joinPoint->proceed();
}
Performance Overhead:
Annotation Parsing Issues:
php bin/console debug:container --tags="jms.aop.method"
php bin/console cache:clear
Circular Dependencies:
ServiceA calls ServiceB, which calls ServiceA).@Around with ProceedingJoinPoint carefully or refactor dependencies.Method Signature Mismatches:
ProceedingJoinPoint arguments:
var_dump($joinPoint->getMethod()->getParameters());
Symfony 4+ Compatibility:
jms/aop-bundle v1.6 (last stable) or migrate to Doctrine AOP or PHP-AOP.composer require php-aop/php-aop
Enable AOP Logging:
Add to config/packages/dev/jms_aop.yaml:
jms_aop:
proxy_dir: "%kernel.cache_dir%/aop"
debug: true # Logs proxy generation
Inspect Proxies: Dump proxies to verify interceptors are applied:
$reflection = new \ReflectionClass($service);
if ($reflection->isProxy()) {
var_dump($reflection->getMethod('__invoke')->getClosure()->getClosureThis());
}
Pointcut Testing: Test pointcuts with a dummy interceptor:
/**
* @Around("execution(* App\Service\*(..))")
*/
public function testPointcut(ProceedingJoinPoint $joinPoint) {
\Log::info("Pointcut matched: " . $joinPoint->getMethod()->getName());
return $joinPoint->proceed();
}
Custom JoinPoint Interfaces:
Extend JMS\AopBundle\Aspect\JoinPoint\JoinPointInterface for domain-specific logic.
Dynamic Pointcuts:
Use JMS\AopBundle\Pointcut\ExpressionPointcut to build pointcuts dynamically:
$pointcut = new ExpressionPointcut("execution(* App\Service\*(..))");
$this->getAspectInstance()->addPointcut($pointcut);
Aspect Inheritance: Extend existing aspects to avoid duplication:
class ExtendedLoggerAspect extends LoggerAspect {
/**
* @Around("execution(* App\Service\AdminService->*(..))")
*/
public function logAdminCalls(ProceedingJoinPoint $joinPoint) {
// Custom logic before/after parent
return parent::logExecution($joinPoint);
}
}
Weaving Strategies:
opcache.jit_buffer_size.
jms_aop:
weaving_strategy: "load-time" # Requires PHP 7.4+
How can I help you explore Laravel packages today?