Installation Add the package via Composer:
composer require demoniacdeath/aop-bundle
Enable the bundle in config/bundles.php:
return [
// ...
DemoniacDeath\JMSAopBundle\JMSAopBundle::class => ['all' => true],
];
Basic Configuration
Configure the bundle in config/packages/jms_aop.yaml (create if missing):
jms_aop:
proxy_dir: "%kernel.cache_dir%/aop"
proxy_namespace: "App\\Proxy"
debug: "%kernel.debug%"
First Use Case: Logging Method Calls Create a simple aspect to log method invocations:
use JMS\Aop\Aspect;
use JMS\Aop\Before;
class LoggingAspect
{
/**
* @Before("execution(public * *(..))")
*/
public function logMethodCall(JoinPoint $joinPoint)
{
$method = $joinPoint->getMethod();
$class = $joinPoint->getClass();
\Log::info("Calling {$class}::{$method->getName()}");
}
}
Register the aspect in services.yaml:
services:
App\Aspect\LoggingAspect:
tags: ['jms.aop.aspect']
Verify AOP is Working
Inject a service annotated with @Aspect and observe logs when methods are called.
@Before/@After to log method entry/exit.
/**
* @Before("execution(* App\Service\*(..))")
*/
public function logServiceCalls(JoinPoint $joinPoint) { ... }
@Around.
/**
* @Around("execution(* App\Service\Cacheable*(..))")
*/
public function cacheResult(JoinPoint $joinPoint) { ... }
@Before.
/**
* @Before("execution(* App\Service\Admin*(..)) && args(user, ..)")
*/
public function checkAdmin(JoinPoint $joinPoint, User $user) { ... }
Track method execution time:
/**
* @Around("execution(* App\Service\*(..))")
*/
public function monitorPerformance(JoinPoint $joinPoint) {
$start = microtime(true);
$result = $joinPoint->proceed();
$duration = microtime(true) - $start;
\Log::debug("Method {$joinPoint->getMethod()->getName()} took {$duration}s");
return $result;
}
Wrap database operations in transactions:
/**
* @Around("execution(* App\Service\Repository*(..))")
*/
public function manageTransaction(JoinPoint $joinPoint) {
$entityManager = $this->getEntityManager();
$entityManager->beginTransaction();
try {
$result = $joinPoint->proceed();
$entityManager->commit();
return $result;
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
}
services:
App\Aspect\SecurityAspect:
arguments: ['@security.token_storage']
tags: ['jms.aop.aspect']
$event = new MethodCalledEvent($joinPoint);
$this->eventDispatcher->dispatch($event, MethodCalledEvent::NAME);
Use expressive pointcuts to target methods:
@Before("execution(* App\Service\UserService*(..))")@Before("@annotatedWith(App\Annotation\Cacheable)")@Before("execution(* *->get*(..))")@Before("execution(* App\Service\*(..)) && !execution(* App\Service\ReadOnly*(..))")php bin/console cache:clear
jms_aop.yaml to avoid conflicts.JoinPoint in unit tests:
$joinPoint = $this->createMock(JoinPointInterface::class);
$joinPoint->method('getMethod')->willReturn(new ReflectionMethod($class, 'methodName'));
$aspect->yourMethod($joinPoint);
JMS\Aop\Test\AspectTestCase for integration tests.jms_aop.yaml to log proxy generation.var/cache/dev/aop/ for issues.@Around sparingly, as it can impact performance due to proxy overhead.php bin/console cache:clear and check proxy_dir in config.// Start broad, then narrow down
@Before("execution(* *(..))") // All methods
@Before("execution(* App\*(..))") // All App namespace
@Before("execution(* App\Service\*(..))") // Specific service
JoinPoint is a proxy and may not expose all expected data.getArgs(), getMethod(), and getThis() carefully. For complex cases, pass dependencies via constructor.$proxyClass = $joinPoint->getProxy();
var_dump(get_class($proxyClass));
var/cache/dev/aop/ for generated proxy files.LoggingAspect, CacheAspect, SecurityAspect).namespace App\Aspect\Logging;
namespace App\Aspect\Security;
abstract class BaseAspect {
protected function log(JoinPoint $joinPoint, string $message) {
\Log::info("[AOP] {$message}: {$joinPoint->getMethod()->getName()}");
}
}
@Around to dynamically decide whether to proceed:
public function dynamicProceed(JoinPoint $joinPoint) {
if ($this->shouldSkip($joinPoint)) {
return null; // Skip execution
}
return $joinPoint->proceed();
}
# config/packages/aop.yaml
app_aop:
logging:
enabled: true
log_level: debug
// In aspect
public function __construct(array $config) {
$this->enabled = $config['enabled'];
}
public function testLoggingAspect() {
$joinPoint = $this->createMock(JoinPointInterface::class);
$joinPoint->method('getMethod')->willReturn(new ReflectionMethod('App\Service\UserService', 'getUser'));
$aspect = new LoggingAspect();
$aspect->logMethodCall($joinPoint);
$this->assertLogContains("Calling App\Service\UserService
How can I help you explore Laravel packages today?