Installation:
composer require dmp/aop-bundle
Add the bundle to config/bundles.php:
return [
// ...
DMP\AopBundle\DmpAopBundle::class => ['all' => true],
];
Configure Cache:
Update config/packages/dmp_aop.yaml:
jms_aop:
cache_dir: '%kernel.cache_dir%/aop'
First Use Case:
Create a pointcut to match methods (e.g., logging delete methods) and an interceptor to execute logic.
Usage section for pointcut/interceptor structure.Logging example under Examples for a practical starting point.cache_dir is writable and matches your Symfony cache setup.Define Pointcuts:
Use annotations or class/method patterns to match targets (e.g., @Pointcut("execution(* delete*(..))")).
Example:
use DMP\AopBundle\Pointcut\PointcutInterface;
class DeleteMethodPointcut implements PointcutInterface {
public function matches(MethodReflection $method): bool {
return str_contains($method->getName(), 'delete');
}
}
Create Interceptors:
Implement InterceptorInterface to define pre/post logic or method replacement.
Example (logging interceptor):
use DMP\AopBundle\Interceptor\InterceptorInterface;
use Psr\Log\LoggerInterface;
class LoggingInterceptor implements InterceptorInterface {
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function invoke(MethodInvocation $invocation) {
$this->logger->info(sprintf(
'Calling %s with args: %s',
$invocation->getMethod()->getName(),
json_encode($invocation->getArguments())
));
return $invocation->proceed(); // Proceeds to original method
}
}
Bind Pointcuts to Interceptors:
Register bindings in a service configuration (e.g., services.yaml):
services:
DMP\AopBundle\Pointcut\DeleteMethodPointcut:
tags: ['dmp_aop.pointcut']
DMP\AopBundle\Interceptor\LoggingInterceptor:
tags: ['dmp_aop.interceptor']
arguments: ['@logger']
Apply to Services:
Use @Around or @Before annotations on target methods/services:
use DMP\AopBundle\Annotation\Around;
class ProductService {
/**
* @Around("execution(* delete*(..))")
*/
public function deleteProduct() { ... }
}
LoggerInterface).MethodInvocation to inspect arguments/method context before proceeding.cache_dir to avoid runtime reflection overhead.MethodInvocation in unit tests to verify interceptor behavior.Cache Invalidation:
Clear the cache (php bin/console cache:clear) after adding/removing pointcuts/interceptors.
php bin/console dmp:aop:clear-cache
Tip: Use cache_dir outside var/ for shared environments.
Method Matching Precision:
Overly broad pointcuts (e.g., execution(* *(..))) may degrade performance.
Tip: Use specific patterns like execution(* Service\* delete*(..)).
Circular Dependencies:
Interceptors depending on services that are also intercepted can cause deadlocks.
Tip: Use @Before instead of @Around for one-way dependencies.
Annotation Parsing:
Annotations like @Around must be parsed by Symfony’s annotation reader.
Tip: Ensure annotation_reader is configured in framework.yaml:
framework:
annotation_reader: 'annotation_reader'
Enable Logging: Add debug logs for interceptor invocation:
monolog:
handlers:
main:
level: debug
channels: ['dmp_aop']
Tip: Check logs for MethodInvocation details during debugging.
Verify Pointcut Matches:
Use var_dump() in PointcutInterface::matches() to confirm method signatures are matched correctly.
Interceptor Order: Interceptors are applied in registration order. Use tags with priorities:
tags: ['dmp_aop.interceptor', { priority: 100 }]
Custom Matchers:
Extend PointcutInterface to support complex logic (e.g., role-based access).
Example:
class RolePointcut implements PointcutInterface {
public function matches(MethodReflection $method): bool {
return $method->hasAnnotation(new Role('ROLE_ADMIN'));
}
}
Aspect-Oriented Transactions:
Use @Around to wrap methods in transactions:
class TransactionInterceptor implements InterceptorInterface {
public function invoke(MethodInvocation $invocation) {
$entityManager = ...;
$entityManager->beginTransaction();
try {
$result = $invocation->proceed();
$entityManager->commit();
return $result;
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
}
}
Dynamic Interception:
Register pointcuts/interceptors programmatically via DmpAopBundle events:
use DMP\AopBundle\Event\PointcutEvent;
$eventDispatcher->addListener(PointcutEvent::REGISTER, function (PointcutEvent $event) {
$event->addPointcut(new DynamicPointcut());
});
Cache Directory Permissions:
Ensure cache_dir is writable by the web server user.
Fix: Run chmod -R 775 %kernel.cache_dir%/aop.
Symfony 6+ Compatibility: The bundle targets Symfony 5 but may work with 6+. Test with:
composer require symfony/*:^6.0 --dev
Annotation Autoconfiguration:
If annotations aren’t detected, enable auto_mapping in framework.yaml:
framework:
autowire: true
auto_mapping: true
How can I help you explore Laravel packages today?