dmp / aop-bundle
为 Symfony6 添加 AOP 功能
1.0.0
2024-08-21 13:46 UTC
Requires
- php: >=8.1
- dmp/cg-lib: ^0.1.1
Requires (Dev)
This package is auto-updated.
Last update: 2024-09-21 14:03:39 UTC
README
======== 概述
此包为 Symfony 5 添加 AOP 功能。
如果您还没有听说过 AOP,它基本上允许您将横切关注点(例如,安全检查)分离到一个专用类中,并且不必在所有需要该代码的地方重复它。
换句话说,这允许您在服务层或控制器的某些方法调用之前和之后执行自定义代码。您还可以选择跳过原始方法的调用,或抛出异常。
安装
检出代码副本:
composer require dmp/aop-bundle
配置
jms_aop:
cache_dir: %kernel.cache_dir%/jms_aop
使用
为了执行自定义代码,您需要两个类。首先,您需要一个所谓的切入点。此类的作用是决定是否应由某个拦截器拦截方法调用。这个决定必须仅在方法签名本身的基础上静态地做出。
第二个类是拦截器。该类代替原始方法被调用。它包含您想要执行的自定义代码。此时,您可以访问被调用方法的对象以及传递给该方法的全部参数。
示例
- 日志记录
In this example, we will be implementing logging for all methods that contain
"delete".
Pointcut
^^^^^^^^
::
<?php
use DMP\AopBundle\Aop\PointcutInterface;
class LoggingPointcut implements PointcutInterface
{
public function matchesClass(\ReflectionClass $class)
{
return true;
}
public function matchesMethod(\ReflectionMethod $method)
{
return false !== strpos($method->name, 'delete');
}
}
::
# services.yml
services:
my_logging_pointcut:
class: LoggingPointcut
tags:
- { name: jms_aop.pointcut, interceptor: logging_interceptor }
LoggingInterceptor
^^^^^^^^^^^^^^^^^^
::
<?php
use CG\Proxy\MethodInterceptorInterface;
use CG\Proxy\MethodInvocation;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
class LoggingInterceptor implements MethodInterceptorInterface
{
private $context;
private $logger;
public function __construct(SecurityContextInterface $context,
LoggerInterface $logger)
{
$this->context = $context;
$this->logger = $logger;
}
public function intercept(MethodInvocation $invocation)
{
$user = $this->context->getToken()->getUsername();
$this->logger->info(sprintf('User "%s" invoked method "%s".', $user, $invocation->reflection->name));
// make sure to proceed with the invocation otherwise the original
// method will never be called
return $invocation->proceed();
}
}
::
# services.yml
services:
logging_interceptor:
class: LoggingInterceptor
arguments: [@security.context, @logger]
2. Transaction Management
在这个例子中,我们添加了 @Transactional 注解,并且自动将声明此注解的所有方法包裹在事务中。
切入点 ^^^^^^^^
::
use Doctrine\Common\Annotations\Reader;
use DMP\AopBundle\Aop\PointcutInterface;
use DMP\DiExtraBundle\Annotation as DI;
/**
* @DI\Service
* @DI\Tag("jms_aop.pointcut", attributes = {"interceptor" = "aop.transactional_interceptor"})
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class TransactionalPointcut implements PointcutInterface
{
private $reader;
/**
* @DI\InjectParams({
* "reader" = @DI\Inject("annotation_reader"),
* })
* @param Reader $reader
*/
public function __construct(Reader $reader)
{
$this->reader = $reader;
}
public function matchesClass(\ReflectionClass $class)
{
return true;
}
public function matchesMethod(\ReflectionMethod $method)
{
return null !== $this->reader->getMethodAnnotation($method, 'Annotation\Transactional');
}
}
拦截器 ^^^^^^^^^^^
::
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use CG\Proxy\MethodInvocation;
use CG\Proxy\MethodInterceptorInterface;
use Doctrine\ORM\EntityManager;
use DMP\DiExtraBundle\Annotation as DI;
/**
* @DI\Service("aop.transactional_interceptor")
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class TransactionalInterceptor implements MethodInterceptorInterface
{
private $em;
private $logger;
/**
* @DI\InjectParams
* @param EntityManager $em
*/
public function __construct(EntityManager $em, LoggerInterface $logger)
{
$this->em = $em;
$this->logger = $logger;
}
public function intercept(MethodInvocation $invocation)
{
$this->logger->info('Beginning transaction for method "'.$invocation.'")');
$this->em->getConnection()->beginTransaction();
try {
$rs = $invocation->proceed();
$this->logger->info(sprintf('Comitting transaction for method "%s" (method invocation successful)', $invocation));
$this->em->getConnection()->commit();
return $rs;
} catch (\Exception $ex) {
if ($ex instanceof NotFoundHttpException) {
$this->logger->info(sprintf('Committing transaction for method "%s" (exception thrown, but no rollback)', $invocation));
$this->em->getConnection()->commit();
} else {
$this->logger->info(sprintf('Rolling back transaction for method "%s" (exception thrown)', $invocation));
$this->em->getConnection()->rollBack();
}
throw $ex;
}
}
}