PointcutMatchingPass.php 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <?php
  2. /*
  3. * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace JMS\AopBundle\DependencyInjection\Compiler;
  18. use CG\Core\ClassUtils;
  19. use JMS\AopBundle\Exception\RuntimeException;
  20. use Symfony\Component\Config\Resource\FileResource;
  21. use Symfony\Component\DependencyInjection\Reference;
  22. use CG\Proxy\Enhancer;
  23. use CG\Proxy\InterceptionGenerator;
  24. use Symfony\Component\DependencyInjection\Definition;
  25. use Symfony\Component\DependencyInjection\ContainerBuilder;
  26. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  27. /**
  28. * Matches pointcuts against service methods.
  29. *
  30. * This pass will collect the advices that match a certain method, and then
  31. * generate proxy classes where necessary.
  32. *
  33. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  34. */
  35. class PointcutMatchingPass implements CompilerPassInterface
  36. {
  37. private $pointcuts;
  38. private $cacheDir;
  39. private $container;
  40. public function __construct(array $pointcuts = null)
  41. {
  42. $this->pointcuts = $pointcuts;
  43. }
  44. public function process(ContainerBuilder $container)
  45. {
  46. $this->container = $container;
  47. $this->cacheDir = $container->getParameter('jms_aop.cache_dir').'/proxies';
  48. $pointcuts = $this->getPointcuts();
  49. $interceptors = array();
  50. foreach ($container->getDefinitions() as $id => $definition) {
  51. $this->processDefinition($definition, $pointcuts, $interceptors);
  52. $this->processInlineDefinitions($pointcuts, $interceptors, $definition->getArguments());
  53. $this->processInlineDefinitions($pointcuts, $interceptors, $definition->getMethodCalls());
  54. $this->processInlineDefinitions($pointcuts, $interceptors, $definition->getProperties());
  55. }
  56. $container
  57. ->getDefinition('jms_aop.interceptor_loader')
  58. ->addArgument($interceptors)
  59. ;
  60. }
  61. private function processInlineDefinitions($pointcuts, &$interceptors, array $a) {
  62. foreach ($a as $k => $v) {
  63. if ($v instanceof Definition) {
  64. $this->processDefinition($v, $pointcuts, $interceptors);
  65. } else if (is_array($v)) {
  66. $this->processInlineDefinitions($pointcuts, $interceptors, $v);
  67. }
  68. }
  69. }
  70. private function processDefinition(Definition $definition, $pointcuts, &$interceptors)
  71. {
  72. if ($definition->isSynthetic()) {
  73. return;
  74. }
  75. if ($definition->getFactoryService() || $definition->getFactoryClass()) {
  76. return;
  77. }
  78. if ($file = $definition->getFile()) {
  79. require_once $file;
  80. }
  81. if (!class_exists($definition->getClass())) {
  82. return;
  83. }
  84. $class = new \ReflectionClass($definition->getClass());
  85. // check if class is matched
  86. $matchingPointcuts = array();
  87. foreach ($pointcuts as $interceptor => $pointcut) {
  88. if ($pointcut->matchesClass($class)) {
  89. $matchingPointcuts[$interceptor] = $pointcut;
  90. }
  91. }
  92. if (empty($matchingPointcuts)) {
  93. return;
  94. }
  95. $this->addResources($class, $this->container);
  96. if ($class->isFinal()) {
  97. return;
  98. }
  99. $classAdvices = array();
  100. foreach ($class->getMethods(\ReflectionMethod::IS_PROTECTED | \ReflectionMethod::IS_PUBLIC) as $method) {
  101. if ($method->isFinal()) {
  102. continue;
  103. }
  104. $advices = array();
  105. foreach ($matchingPointcuts as $interceptor => $pointcut) {
  106. if ($pointcut->matchesMethod($method)) {
  107. $advices[] = $interceptor;
  108. }
  109. }
  110. if (empty($advices)) {
  111. continue;
  112. }
  113. $classAdvices[$method->name] = $advices;
  114. }
  115. if (empty($classAdvices)) {
  116. return;
  117. }
  118. $interceptors[ClassUtils::getUserClass($class->name)] = $classAdvices;
  119. $generator = new InterceptionGenerator();
  120. $generator->setFilter(function(\ReflectionMethod $method) use ($classAdvices) {
  121. return isset($classAdvices[$method->name]);
  122. });
  123. if ($file) {
  124. $generator->setRequiredFile($file);
  125. }
  126. $enhancer = new Enhancer($class, array(), array(
  127. $generator
  128. ));
  129. $enhancer->writeClass($filename = $this->cacheDir.'/'.str_replace('\\', '-', $class->name).'.php');
  130. $definition->setFile($filename);
  131. $definition->setClass($enhancer->getClassName($class));
  132. $definition->addMethodCall('__CGInterception__setLoader', array(
  133. new Reference('jms_aop.interceptor_loader')
  134. ));
  135. }
  136. private function addResources(\ReflectionClass $class)
  137. {
  138. do {
  139. $this->container->addResource(new FileResource($class->getFilename()));
  140. } while (($class = $class->getParentClass()) && $class->getFilename());
  141. }
  142. private function getPointcuts()
  143. {
  144. if (null !== $this->pointcuts) {
  145. return $this->pointcuts;
  146. }
  147. $pointcuts = $pointcutReferences = array();
  148. foreach ($this->container->findTaggedServiceIds('jms_aop.pointcut') as $id => $attr) {
  149. if (!isset($attr[0]['interceptor'])) {
  150. throw new RuntimeException('You need to set the "interceptor" attribute for the "jms_aop.pointcut" tag of service "'.$id.'".');
  151. }
  152. $pointcutReferences[$attr[0]['interceptor']] = new Reference($id);
  153. $pointcuts[$attr[0]['interceptor']] = $this->container->get($id);
  154. }
  155. $this->container
  156. ->getDefinition('jms_aop.pointcut_container')
  157. ->addArgument($pointcutReferences)
  158. ;
  159. return $pointcuts;
  160. }
  161. }