index.rst 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. ========
  2. Overview
  3. ========
  4. This bundle adds AOP capabilities to Symfony2.
  5. If you haven't heard of AOP yet, it basically allows you to separate a
  6. cross-cutting concern (for example, security checks) into a dedicated class,
  7. and not having to repeat that code in all places where it is needed.
  8. In other words, this allows you to execute custom code before, and after the
  9. invocation of certain methods in your service layer, or your controllers. You
  10. can also choose to skip the invocation of the original method, or throw exceptions.
  11. Installation
  12. ------------
  13. Checkout a copy of the code::
  14. git submodule add https://github.com/schmittjoh/JMSAopBundle.git src/JMS/AopBundle
  15. Then register the bundle with your kernel::
  16. // in AppKernel::registerBundles()
  17. $bundles = array(
  18. // ...
  19. new JMS\AopBundle\JMSAopBundle(),
  20. // ...
  21. );
  22. This bundle also requires the CG library for code generation::
  23. git submodule add https://github.com/schmittjoh/cg-library.git vendor/cg-library
  24. Make sure that you also register the namespaces with the autoloader::
  25. // app/autoload.php
  26. $loader->registerNamespaces(array(
  27. // ...
  28. 'JMS' => __DIR__.'/../vendor/bundles',
  29. 'CG' => __DIR__.'/../vendor/cg-library/src',
  30. // ...
  31. ));
  32. Configuration
  33. -------------
  34. ::
  35. jms_aop:
  36. cache_dir: %kernel.cache_dir%/jms_aop
  37. Usage
  38. -----
  39. In order to execute custom code, you need two classes. First, you need a so-called
  40. pointcut. The purpose of this class is to make a decision whether a method call
  41. should be intercepted by a certain interceptor. This decision has to be made
  42. statically only on the basis of the method signature itself.
  43. The second class is the interceptor. This class is being called instead
  44. of the original method. It contains the custom code that you would like to
  45. execute. At this point, you have access to the object on which the method is
  46. called, and all the arguments which were passed to that method.
  47. Examples
  48. --------
  49. 1. Logging
  50. ~~~~~~~~~~
  51. In this example, we will be implementing logging for all methods that contain
  52. "delete".
  53. Pointcut
  54. ^^^^^^^^
  55. ::
  56. <?php
  57. use JMS\AopBundle\Aop\PointcutInterface;
  58. class LoggingPointcut implements PointcutInterface
  59. {
  60. public function matchesClass(\ReflectionClass $class)
  61. {
  62. return true;
  63. }
  64. public function matchesMethod(\ReflectionMethod $method)
  65. {
  66. return false !== strpos($method->name, 'delete');
  67. }
  68. }
  69. ::
  70. # services.yml
  71. services:
  72. my_logging_pointcut:
  73. class: LoggingPointcut
  74. tags:
  75. - { name: jms_aop.pointcut, interceptor: logging_interceptor }
  76. LoggingInterceptor
  77. ^^^^^^^^^^^^^^^^^^
  78. ::
  79. <?php
  80. use CG\Proxy\MethodInterceptorInterface;
  81. use CG\Proxy\MethodInvocation;
  82. use Symfony\Component\HttpKernel\Log\LoggerInterface;
  83. use Symfony\Component\Security\Core\SecurityContextInterface;
  84. class LoggingInterceptor implements MethodInterceptorInterface
  85. {
  86. private $context;
  87. private $logger;
  88. public function __construct(SecurityContextInterface $context,
  89. LoggerInterface $logger)
  90. {
  91. $this->context = $context;
  92. $this->logger = $logger;
  93. }
  94. public function intercept(MethodInvocation $invocation)
  95. {
  96. $user = $this->context->getToken()->getUsername();
  97. $this->logger->info(sprintf('User "%s" invoked method "%s".', $user, $invocation->reflection->name));
  98. // make sure to proceed with the invocation otherwise the original
  99. // method will never be called
  100. return $invocation->proceed();
  101. }
  102. }
  103. ::
  104. # services.yml
  105. services:
  106. logging_interceptor:
  107. class: LoggingInterceptor
  108. arguments: [@security.context, @logger]
  109. 2. Transaction Management
  110. ~~~~~~~~~~~~~~~~~~~~~~~~~
  111. In this example, we add a @Transactional annotation, and we automatically wrap all methods
  112. where this annotation is declared in a transaction.
  113. Pointcut
  114. ^^^^^^^^
  115. ::
  116. use Doctrine\Common\Annotations\Reader;
  117. use JMS\AopBundle\Aop\PointcutInterface;
  118. use JMS\DiExtraBundle\Annotation as DI;
  119. /**
  120. * @DI\Service
  121. * @DI\Tag("jms_aop.pointcut", attributes = {"interceptor" = "aop.transactional_interceptor"})
  122. *
  123. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  124. */
  125. class TransactionalPointcut implements PointcutInterface
  126. {
  127. private $reader;
  128. /**
  129. * @DI\InjectParams({
  130. * "reader" = @DI\Inject("annotation_reader"),
  131. * })
  132. * @param Reader $reader
  133. */
  134. public function __construct(Reader $reader)
  135. {
  136. $this->reader = $reader;
  137. }
  138. public function matchesClass(\ReflectionClass $class)
  139. {
  140. return true;
  141. }
  142. public function matchesMethod(\ReflectionMethod $method)
  143. {
  144. return null !== $this->reader->getMethodAnnotation($method, 'Annotation\Transactional');
  145. }
  146. }
  147. Interceptor
  148. ^^^^^^^^^^^
  149. ::
  150. use Symfony\Component\HttpKernel\Log\LoggerInterface;
  151. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  152. use CG\Proxy\MethodInvocation;
  153. use CG\Proxy\MethodInterceptorInterface;
  154. use Doctrine\ORM\EntityManager;
  155. use JMS\DiExtraBundle\Annotation as DI;
  156. /**
  157. * @DI\Service("aop.transactional_interceptor")
  158. *
  159. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  160. */
  161. class TransactionalInterceptor implements MethodInterceptorInterface
  162. {
  163. private $em;
  164. private $logger;
  165. /**
  166. * @DI\InjectParams
  167. * @param EntityManager $em
  168. */
  169. public function __construct(EntityManager $em, LoggerInterface $logger)
  170. {
  171. $this->em = $em;
  172. $this->logger = $logger;
  173. }
  174. public function intercept(MethodInvocation $invocation)
  175. {
  176. $this->logger->info('Beginning transaction for method "'.$invocation.'")');
  177. $this->em->getConnection()->beginTransaction();
  178. try {
  179. $rs = $invocation->proceed();
  180. $this->logger->info(sprintf('Comitting transaction for method "%s" (method invocation successful)', $invocation));
  181. $this->em->getConnection()->commit();
  182. return $rs;
  183. } catch (\Exception $ex) {
  184. if ($ex instanceof NotFoundHttpException) {
  185. $this->logger->info(sprintf('Committing transaction for method "%s" (exception thrown, but no rollback)', $invocation));
  186. $this->em->getConnection()->commit();
  187. } else {
  188. $this->logger->info(sprintf('Rolling back transaction for method "%s" (exception thrown)', $invocation));
  189. $this->em->getConnection()->rollBack();
  190. }
  191. throw $ex;
  192. }
  193. }
  194. }