PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Classes/TYPO3/FLOW3/Aop/Builder/ProxyClassBuilder.php

https://github.com/christianjul/FLOW3-Composer
PHP | 774 lines | 601 code | 33 blank | 140 comment | 7 complexity | 353b7efd24fd78ebcce2fdd4a9a0f103 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Aop\Builder;
  3. /* *
  4. * This script belongs to the FLOW3 framework. *
  5. * *
  6. * It is free software; you can redistribute it and/or modify it under *
  7. * the terms of the GNU Lesser General Public License, either version 3 *
  8. * of the License, or (at your option) any later version. *
  9. * *
  10. * The TYPO3 project - inspiring people to share! *
  11. * */
  12. use TYPO3\FLOW3\Cache\CacheManager;
  13. use TYPO3\FLOW3\Annotations as FLOW3;
  14. /**
  15. * The main class of the AOP (Aspect Oriented Programming) framework.
  16. *
  17. * @FLOW3\Proxy(false)
  18. * @FLOW3\Scope("singleton")
  19. */
  20. class ProxyClassBuilder {
  21. /**
  22. * @var \TYPO3\FLOW3\Object\Proxy\Compiler
  23. */
  24. protected $compiler;
  25. /**
  26. * The FLOW3 settings
  27. * @var array
  28. */
  29. protected $settings;
  30. /**
  31. * @var \TYPO3\FLOW3\Reflection\ReflectionService
  32. */
  33. protected $reflectionService;
  34. /**
  35. * @var \TYPO3\FLOW3\Log\SystemLoggerInterface
  36. */
  37. protected $systemLogger;
  38. /**
  39. * An instance of the pointcut expression parser
  40. * @var \TYPO3\FLOW3\Aop\Pointcut\PointcutExpressionParser
  41. */
  42. protected $pointcutExpressionParser;
  43. /**
  44. * @var \TYPO3\FLOW3\Aop\Builder\ProxyClassBuilder
  45. */
  46. protected $proxyClassBuilder;
  47. /**
  48. * @var \TYPO3\FLOW3\Cache\Frontend\VariableFrontend
  49. */
  50. protected $objectConfigurationCache;
  51. /**
  52. * @var \TYPO3\FLOW3\Object\CompileTimeObjectManager
  53. */
  54. protected $objectManager;
  55. /**
  56. * Hardcoded list of FLOW3 sub packages (first 15 characters) which must be immune to AOP proxying for security, technical or conceptual reasons.
  57. * @var array
  58. */
  59. protected $blacklistedSubPackages = array('TYPO3\FLOW3\Aop', 'TYPO3\FLOW3\Cac', 'TYPO3\FLOW3\Con', 'TYPO3\FLOW3\Err', 'TYPO3\FLOW3\Log', 'TYPO3\FLOW3\Mon', 'TYPO3\FLOW3\Obj', 'TYPO3\FLOW3\Pac', 'TYPO3\FLOW3\Pro', 'TYPO3\FLOW3\Ref', 'TYPO3\FLOW3\Uti', 'TYPO3\FLOW3\Val');
  60. /**
  61. * A registry of all known aspects
  62. * @var array
  63. */
  64. protected $aspectContainers = array();
  65. /**
  66. * @var array
  67. */
  68. protected $methodInterceptorBuilders = array();
  69. /**
  70. * @param \TYPO3\FLOW3\Object\Proxy\Compiler $compiler
  71. * @return void
  72. */
  73. public function injectCompiler(\TYPO3\FLOW3\Object\Proxy\Compiler $compiler) {
  74. $this->compiler = $compiler;
  75. }
  76. /**
  77. * Injects the reflection service
  78. *
  79. * @param \TYPO3\FLOW3\Reflection\ReflectionService $reflectionService
  80. * @return void
  81. */
  82. public function injectReflectionService(\TYPO3\FLOW3\Reflection\ReflectionService $reflectionService) {
  83. $this->reflectionService = $reflectionService;
  84. }
  85. /**
  86. * @param \TYPO3\FLOW3\Log\SystemLoggerInterface $systemLogger
  87. * @return void
  88. */
  89. public function injectSystemLogger(\TYPO3\FLOW3\Log\SystemLoggerInterface $systemLogger) {
  90. $this->systemLogger = $systemLogger;
  91. }
  92. /**
  93. * Injects an instance of the pointcut expression parser
  94. *
  95. * @param \TYPO3\FLOW3\Aop\Pointcut\PointcutExpressionParser $pointcutExpressionParser
  96. * @return void
  97. */
  98. public function injectPointcutExpressionParser(\TYPO3\FLOW3\Aop\Pointcut\PointcutExpressionParser $pointcutExpressionParser) {
  99. $this->pointcutExpressionParser = $pointcutExpressionParser;
  100. }
  101. /**
  102. * Injects the cache for storing information about objects
  103. *
  104. * @param \TYPO3\FLOW3\Cache\Frontend\VariableFrontend $objectConfigurationCache
  105. * @return void
  106. * @FLOW3\Autowiring(false)
  107. */
  108. public function injectObjectConfigurationCache(\TYPO3\FLOW3\Cache\Frontend\VariableFrontend $objectConfigurationCache) {
  109. $this->objectConfigurationCache = $objectConfigurationCache;
  110. }
  111. /**
  112. * Injects the Adviced Constructor Interceptor Builder
  113. *
  114. * @param \TYPO3\FLOW3\Aop\Builder\AdvicedConstructorInterceptorBuilder $builder
  115. * @return void
  116. */
  117. public function injectAdvicedConstructorInterceptorBuilder(\TYPO3\FLOW3\Aop\Builder\AdvicedConstructorInterceptorBuilder $builder) {
  118. $this->methodInterceptorBuilders['AdvicedConstructor'] = $builder;
  119. }
  120. /**
  121. * Injects the Adviced Method Interceptor Builder
  122. *
  123. * @param \TYPO3\FLOW3\Aop\Builder\AdvicedMethodInterceptorBuilder $builder
  124. * @return void
  125. */
  126. public function injectAdvicedMethodInterceptorBuilder(\TYPO3\FLOW3\Aop\Builder\AdvicedMethodInterceptorBuilder $builder) {
  127. $this->methodInterceptorBuilders['AdvicedMethod'] = $builder;
  128. }
  129. /**
  130. * @param \TYPO3\FLOW3\Object\CompileTimeObjectManager $objectManager
  131. * @return void
  132. */
  133. public function injectObjectManager(\TYPO3\FLOW3\Object\CompileTimeObjectManager $objectManager) {
  134. $this->objectManager = $objectManager;
  135. }
  136. /**
  137. * Injects the FLOW3 settings
  138. *
  139. * @param array $settings The settings
  140. * @return void
  141. */
  142. public function injectSettings(array $settings) {
  143. $this->settings = $settings;
  144. }
  145. /**
  146. * Builds proxy class code which weaves advices into the respective target classes.
  147. *
  148. * The object configurations provided by the Compiler are searched for possible aspect
  149. * annotations. If an aspect class is found, the poincut expressions are parsed and
  150. * a new aspect with one or more advisors is added to the aspect registry of the AOP framework.
  151. * Finally all advices are woven into their target classes by generating proxy classes.
  152. *
  153. * In general, the command typo3.flow3:core:compile is responsible for compilation
  154. * and calls this method to do so.
  155. *
  156. * In order to distinguish between an emerged / changed possible target class and
  157. * a class which has been matched previously but just didn't have to be proxied,
  158. * the latter are kept track of by an "unproxiedClass-*" cache entry.
  159. *
  160. * @return void
  161. */
  162. public function build() {
  163. $allAvailableClassNamesByPackage = $this->objectManager->getRegisteredClassNames();
  164. $possibleTargetClassNames = $this->getProxyableClasses($allAvailableClassNamesByPackage);
  165. $actualAspectClassNames = $this->reflectionService->getClassNamesByAnnotation('TYPO3\FLOW3\Annotations\Aspect');
  166. sort($possibleTargetClassNames);
  167. sort($actualAspectClassNames);
  168. $this->aspectContainers = $this->buildAspectContainers($actualAspectClassNames);
  169. $rebuildEverything = FALSE;
  170. if ($this->objectConfigurationCache->has('allAspectClassesUpToDate') === FALSE) {
  171. $rebuildEverything = TRUE;
  172. $this->systemLogger->log('Aspects have been modified, therefore rebuilding all target classes.', LOG_INFO);
  173. $this->objectConfigurationCache->set('allAspectClassesUpToDate', TRUE);
  174. }
  175. $possibleTargetClassNameIndex = new ClassNameIndex();
  176. $possibleTargetClassNameIndex->setClassNames($possibleTargetClassNames);
  177. $targetClassNameCandidates = new ClassNameIndex();
  178. foreach ($this->aspectContainers as $aspectContainer) {
  179. $targetClassNameCandidates->applyUnion($aspectContainer->reduceTargetClassNames($possibleTargetClassNameIndex));
  180. }
  181. $targetClassNameCandidates->sort();
  182. foreach ($targetClassNameCandidates->getClassNames() as $targetClassName) {
  183. $isUnproxied = $this->objectConfigurationCache->has('unproxiedClass-' . str_replace('\\', '_', $targetClassName));
  184. $hasCacheEntry = $this->compiler->hasCacheEntryForClass($targetClassName) || $isUnproxied;
  185. if ($rebuildEverything === TRUE || $hasCacheEntry === FALSE) {
  186. $proxyBuildResult = $this->buildProxyClass($targetClassName, $this->aspectContainers);
  187. if ($proxyBuildResult !== FALSE) {
  188. if ($isUnproxied) {
  189. $this->objectConfigurationCache->remove('unproxiedClass-' . str_replace('\\', '_', $targetClassName));
  190. }
  191. $this->systemLogger->log(sprintf('Built AOP proxy for class "%s".', $targetClassName), LOG_DEBUG);
  192. } else {
  193. $this->objectConfigurationCache->set('unproxiedClass-' . str_replace('\\', '_', $targetClassName), TRUE);
  194. }
  195. }
  196. }
  197. }
  198. /**
  199. * Traverses the aspect containers to find a pointcut from the aspect class name
  200. * and pointcut method name
  201. *
  202. * @param string $aspectClassName Name of the aspect class where the pointcut has been declared
  203. * @param string $pointcutMethodName Method name of the pointcut
  204. * @return mixed The \TYPO3\FLOW3\Aop\Pointcut\Pointcut or FALSE if none was found
  205. */
  206. public function findPointcut($aspectClassName, $pointcutMethodName) {
  207. if (!isset($this->aspectContainers[$aspectClassName])) return FALSE;
  208. foreach ($this->aspectContainers[$aspectClassName]->getPointcuts() as $pointcut) {
  209. if ($pointcut->getPointcutMethodName() === $pointcutMethodName) {
  210. return $pointcut;
  211. }
  212. }
  213. return FALSE;
  214. }
  215. /**
  216. * Returns an array of method names and advices which were applied to the specified class. If the
  217. * target class has no adviced methods, an empty array is returned.
  218. *
  219. * @param string $targetClassName Name of the target class
  220. * @return mixed An array of method names and their advices as array of \TYPO3\FLOW3\Aop\Advice\AdviceInterface
  221. * @throws \TYPO3\FLOW3\Aop\Exception
  222. */
  223. public function getAdvicedMethodsInformationByTargetClass($targetClassName) {
  224. throw new \TYPO3\FLOW3\Aop\Exception('This method is currently not supported.');
  225. if (!isset($this->advicedMethodsInformationByTargetClass[$targetClassName])) {
  226. return array();
  227. } else {
  228. return $this->advicedMethodsInformationByTargetClass[$targetClassName];
  229. }
  230. }
  231. /**
  232. * Determines which of the given classes are potentially proxyable
  233. * and returns their names in an array.
  234. *
  235. * @param array $classNamesByPackage Names of the classes to check
  236. * @return array Names of classes which can be proxied
  237. */
  238. protected function getProxyableClasses(array $classNamesByPackage) {
  239. $proxyableClasses = array();
  240. foreach ($classNamesByPackage as $classNames) {
  241. foreach ($classNames as $className) {
  242. if (!in_array(substr($className, 0, 15), $this->blacklistedSubPackages)) {
  243. if (!$this->reflectionService->isClassAnnotatedWith($className, 'TYPO3\FLOW3\Annotations\Aspect') &&
  244. !$this->reflectionService->isClassFinal($className)) {
  245. $proxyableClasses[] = $className;
  246. }
  247. }
  248. }
  249. }
  250. return $proxyableClasses;
  251. }
  252. /**
  253. * Checks the annotations of the specified classes for aspect tags
  254. * and creates an aspect with advisors accordingly.
  255. *
  256. * @param array &$classNames Classes to check for aspect tags.
  257. * @return array An array of \TYPO3\FLOW3\Aop\AspectContainer for all aspects which were found.
  258. */
  259. protected function buildAspectContainers(array &$classNames) {
  260. $aspectContainers = array();
  261. foreach ($classNames as $aspectClassName) {
  262. $aspectContainers[$aspectClassName] = $this->buildAspectContainer($aspectClassName);
  263. }
  264. return $aspectContainers;
  265. }
  266. /**
  267. * Creates and returns an aspect from the annotations found in a class which
  268. * is tagged as an aspect. The object acting as an advice will already be
  269. * fetched (and therefore instantiated if necessary).
  270. *
  271. * @param string $aspectClassName Name of the class which forms the aspect, contains advices etc.
  272. * @return mixed The aspect container containing one or more advisors or FALSE if no container could be built
  273. * @throws \TYPO3\FLOW3\Aop\Exception
  274. */
  275. protected function buildAspectContainer($aspectClassName) {
  276. $aspectContainer = new \TYPO3\FLOW3\Aop\AspectContainer($aspectClassName);
  277. $methodNames = get_class_methods($aspectClassName);
  278. foreach ($methodNames as $methodName) {
  279. foreach ($this->reflectionService->getMethodAnnotations($aspectClassName, $methodName) as $annotation) {
  280. $annotationClass = get_class($annotation);
  281. switch ($annotationClass) {
  282. case 'TYPO3\FLOW3\Annotations\Around' :
  283. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass));
  284. $advice = new \TYPO3\FLOW3\Aop\Advice\AroundAdvice($aspectClassName, $methodName);
  285. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  286. $advisor = new \TYPO3\FLOW3\Aop\Advisor($advice, $pointcut);
  287. $aspectContainer->addAdvisor($advisor);
  288. break;
  289. case 'TYPO3\FLOW3\Annotations\Before' :
  290. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass));
  291. $advice = new \TYPO3\FLOW3\Aop\Advice\BeforeAdvice($aspectClassName, $methodName);
  292. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  293. $advisor = new \TYPO3\FLOW3\Aop\Advisor($advice, $pointcut);
  294. $aspectContainer->addAdvisor($advisor);
  295. break;
  296. case 'TYPO3\FLOW3\Annotations\AfterReturning' :
  297. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass));
  298. $advice = new \TYPO3\FLOW3\Aop\Advice\AfterReturningAdvice($aspectClassName, $methodName);
  299. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  300. $advisor = new \TYPO3\FLOW3\Aop\Advisor($advice, $pointcut);
  301. $aspectContainer->addAdvisor($advisor);
  302. break;
  303. case 'TYPO3\FLOW3\Annotations\AfterThrowing' :
  304. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass));
  305. $advice = new \TYPO3\FLOW3\Aop\Advice\AfterThrowingAdvice($aspectClassName, $methodName);
  306. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  307. $advisor = new \TYPO3\FLOW3\Aop\Advisor($advice, $pointcut);
  308. $aspectContainer->addAdvisor($advisor);
  309. break;
  310. case 'TYPO3\FLOW3\Annotations\After' :
  311. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass));
  312. $advice = new \TYPO3\FLOW3\Aop\Advice\AfterAdvice($aspectClassName, $methodName);
  313. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  314. $advisor = new \TYPO3\FLOW3\Aop\Advisor($advice, $pointcut);
  315. $aspectContainer->addAdvisor($advisor);
  316. break;
  317. case 'TYPO3\FLOW3\Annotations\Pointcut' :
  318. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->expression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass));
  319. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($annotation->expression, $pointcutFilterComposite, $aspectClassName, $methodName);
  320. $aspectContainer->addPointcut($pointcut);
  321. break;
  322. }
  323. }
  324. }
  325. $introduceAnnotation = $this->reflectionService->getClassAnnotation($aspectClassName, 'TYPO3\FLOW3\Annotations\Introduce');
  326. if ($introduceAnnotation !== NULL) {
  327. if ($introduceAnnotation->interfaceName === NULL) {
  328. throw new \TYPO3\FLOW3\Aop\Exception('The interface introduction in class "' . $aspectClassName . '" does not contain the required interface name).', 1172694761);
  329. }
  330. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($introduceAnnotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, 'TYPO3\FLOW3\Annotations\Introduce'));
  331. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($introduceAnnotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  332. $introduction = new \TYPO3\FLOW3\Aop\InterfaceIntroduction($aspectClassName, $introduceAnnotation->interfaceName, $pointcut);
  333. $aspectContainer->addInterfaceIntroduction($introduction);
  334. }
  335. foreach ($this->reflectionService->getClassPropertyNames($aspectClassName) as $propertyName) {
  336. $introduceAnnotation = $this->reflectionService->getPropertyAnnotation($aspectClassName, $propertyName, 'TYPO3\FLOW3\Annotations\Introduce');
  337. if ($introduceAnnotation !== NULL) {
  338. $pointcutFilterComposite = $this->pointcutExpressionParser->parse($introduceAnnotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $propertyName, 'TYPO3\FLOW3\Annotations\Introduce'));
  339. $pointcut = new \TYPO3\FLOW3\Aop\Pointcut\Pointcut($introduceAnnotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName);
  340. $introduction = new \TYPO3\FLOW3\Aop\PropertyIntroduction($aspectClassName, $propertyName, $pointcut);
  341. $aspectContainer->addPropertyIntroduction($introduction);
  342. }
  343. }
  344. if (count($aspectContainer->getAdvisors()) < 1 && count($aspectContainer->getPointcuts()) < 1 && count($aspectContainer->getInterfaceIntroductions()) < 1) throw new \TYPO3\FLOW3\Aop\Exception('The class "' . $aspectClassName . '" is tagged to be an aspect but doesn\'t contain advices nor pointcut or introduction declarations.', 1169124534);
  345. return $aspectContainer;
  346. }
  347. /**
  348. * Builds methods for a single AOP proxy class for the specified class.
  349. *
  350. * @param string $targetClassName Name of the class to create a proxy class file for
  351. * @param array &$aspectContainers The array of aspect containers from the AOP Framework
  352. * @return boolean TRUE if the proxy class could be built, FALSE otherwise.
  353. */
  354. public function buildProxyClass($targetClassName, array &$aspectContainers) {
  355. $interfaceIntroductions = $this->getMatchingInterfaceIntroductions($aspectContainers, $targetClassName);
  356. $introducedInterfaces = $this->getInterfaceNamesFromIntroductions($interfaceIntroductions);
  357. $propertyIntroductions = $this->getMatchingPropertyIntroductions($aspectContainers, $targetClassName);
  358. $methodsFromTargetClass = $this->getMethodsFromTargetClass($targetClassName);
  359. $methodsFromIntroducedInterfaces = $this->getIntroducedMethodsFromInterfaceIntroductions($interfaceIntroductions, $targetClassName);
  360. $interceptedMethods = array();
  361. $this->addAdvicedMethodsToInterceptedMethods($interceptedMethods, array_merge($methodsFromTargetClass, $methodsFromIntroducedInterfaces), $targetClassName, $aspectContainers);
  362. $this->addIntroducedMethodsToInterceptedMethods($interceptedMethods, $methodsFromIntroducedInterfaces);
  363. if (count($interceptedMethods) < 1 && count($introducedInterfaces) < 1) return FALSE;
  364. $proxyClass = $this->compiler->getProxyClass($targetClassName);
  365. if ($proxyClass === FALSE) {
  366. return FALSE;
  367. }
  368. $proxyClass->addInterfaces($introducedInterfaces);
  369. foreach ($propertyIntroductions as $propertyIntroduction) {
  370. $proxyClass->addProperty($propertyIntroduction->getPropertyName(), 'NULL', $propertyIntroduction->getPropertyVisibility(), $propertyIntroduction->getPropertyDocComment());
  371. }
  372. $proxyClass->getMethod('FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode("\t\tif (method_exists(get_parent_class(\$this), 'FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray') && is_callable('parent::FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray')) parent::FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray();\n");
  373. $proxyClass->getMethod('FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray')->addPreParentCallCode($this->buildMethodsAndAdvicesArrayCode($interceptedMethods));
  374. $proxyClass->getMethod('FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray')->overrideMethodVisibility('protected');
  375. $callBuildMethodsAndAdvicesArrayCode = "\n\t\t\$this->FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray();\n";
  376. $proxyClass->getConstructor()->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode);
  377. $proxyClass->getMethod('__wakeup')->addPreParentCallCode($callBuildMethodsAndAdvicesArrayCode);
  378. if (!$this->reflectionService->hasMethod($targetClassName, '__wakeup')) {
  379. $proxyClass->getMethod('__wakeup')->addPostParentCallCode("\t\tif (method_exists(get_parent_class(\$this), '__wakeup') && is_callable('parent::__wakeup')) parent::__wakeup();\n");
  380. }
  381. // FIXME this can be removed again once Doctrine is fixed (see fixMethodsAndAdvicesArrayForDoctrineProxiesCode())
  382. $proxyClass->getMethod('FLOW3_Aop_Proxy_fixMethodsAndAdvicesArrayForDoctrineProxies')->addPreParentCallCode($this->fixMethodsAndAdvicesArrayForDoctrineProxiesCode());
  383. // FIXME this can be removed again once Doctrine is fixed (see fixInjectedPropertiesForDoctrineProxiesCode())
  384. $proxyClass->getMethod('FLOW3_Aop_Proxy_fixInjectedPropertiesForDoctrineProxies')->addPreParentCallCode($this->fixInjectedPropertiesForDoctrineProxiesCode());
  385. $this->buildGetAdviceChainsMethodCode($targetClassName);
  386. $this->buildInvokeJoinPointMethodCode($targetClassName);
  387. $this->buildMethodsInterceptorCode($targetClassName, $interceptedMethods);
  388. $proxyClass->addProperty('FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices', 'array()');
  389. $proxyClass->addProperty('FLOW3_Aop_Proxy_groupedAdviceChains', 'array()');
  390. $proxyClass->addProperty('FLOW3_Aop_Proxy_methodIsInAdviceMode', 'array()');
  391. return TRUE;
  392. }
  393. /**
  394. * Returns the methods of the target class.
  395. *
  396. * @param string $targetClassName Name of the target class
  397. * @return array Method information with declaring class and method name pairs
  398. */
  399. protected function getMethodsFromTargetClass($targetClassName) {
  400. $methods = array();
  401. $class = new \ReflectionClass($targetClassName);
  402. foreach (array('__construct', '__clone') as $builtInMethodName) {
  403. if (!$class->hasMethod($builtInMethodName)) {
  404. $methods[] = array($targetClassName, $builtInMethodName);
  405. }
  406. }
  407. foreach ($class->getMethods() as $method) {
  408. $methods[] = array($targetClassName, $method->getName());
  409. }
  410. return $methods;
  411. }
  412. /**
  413. * Creates code for an array of target methods and their advices.
  414. *
  415. * Example:
  416. *
  417. * $this->FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices = array(
  418. * 'getSomeProperty' => array(
  419. * 'TYPO3\FLOW3\Aop\Advice\AroundAdvice' => array(
  420. * new \TYPO3\FLOW3\Aop\Advice\AroundAdvice('TYPO3\Foo\SomeAspect', 'aroundAdvice', \\TYPO3\\FLOW3\\Core\\Bootstrap::$staticObjectManager, function() { ... }),
  421. * ),
  422. * ),
  423. * );
  424. *
  425. *
  426. * @param array $methodsAndGroupedAdvices An array of method names and grouped advice objects
  427. * @return string PHP code for the content of an array of target method names and advice objects
  428. * @see buildProxyClass()
  429. */
  430. protected function buildMethodsAndAdvicesArrayCode(array $methodsAndGroupedAdvices) {
  431. if (count($methodsAndGroupedAdvices) < 1) return '';
  432. $methodsAndAdvicesArrayCode = "\n\t\t\$objectManager = \\TYPO3\\FLOW3\\Core\\Bootstrap::\$staticObjectManager;\n";
  433. $methodsAndAdvicesArrayCode .= "\t\t\$this->FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices = array(\n";
  434. foreach ($methodsAndGroupedAdvices as $methodName => $advicesAndDeclaringClass) {
  435. $methodsAndAdvicesArrayCode .= "\t\t\t'" . $methodName . "' => array(\n";
  436. foreach ($advicesAndDeclaringClass['groupedAdvices'] as $adviceType => $adviceConfigurations) {
  437. $methodsAndAdvicesArrayCode .= "\t\t\t\t'" . $adviceType . "' => array(\n";
  438. foreach ($adviceConfigurations as $adviceConfiguration) {
  439. $advice = $adviceConfiguration['advice'];
  440. $methodsAndAdvicesArrayCode .= "\t\t\t\t\tnew \\" . get_class($advice) . "('" . $advice->getAspectObjectName() . "', '" . $advice->getAdviceMethodName() . "', \$objectManager, " . $adviceConfiguration['runtimeEvaluationsClosureCode'] . "),\n";
  441. }
  442. $methodsAndAdvicesArrayCode .= "\t\t\t\t),\n";
  443. }
  444. $methodsAndAdvicesArrayCode .= "\t\t\t),\n";
  445. }
  446. $methodsAndAdvicesArrayCode .= "\t\t);\n";
  447. return $methodsAndAdvicesArrayCode;
  448. }
  449. /**
  450. * Creates code that builds the targetMethodsAndGroupedAdvices array if it does not exist. This happens when a Doctrine
  451. * lazy loading proxy for an object is created for some specific purpose, but filled afterwards "on the fly" if this object
  452. * is part of a wide range "findBy" query.
  453. *
  454. * @todo Remove once doctrine is fixed
  455. * @return string
  456. */
  457. protected function fixMethodsAndAdvicesArrayForDoctrineProxiesCode() {
  458. $code = <<<EOT
  459. if (!isset(\$this->FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices) || empty(\$this->FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices)) {
  460. \$this->FLOW3_Aop_Proxy_buildMethodsAndAdvicesArray();
  461. if (is_callable('parent::FLOW3_Aop_Proxy_fixMethodsAndAdvicesArrayForDoctrineProxies')) parent::FLOW3_Aop_Proxy_fixMethodsAndAdvicesArrayForDoctrineProxies();
  462. }
  463. EOT;
  464. return $code;
  465. }
  466. /**
  467. * Creates code that reinjects dependencies if they do not exist. This is necessary because in certain circumstances
  468. * Doctrine loads a proxy in UnitOfWork->createEntity() without calling __wakeup and thus does not initialize DI.
  469. * This happens when a Doctrine lazy loading proxy for an object is created for some specific purpose, but filled
  470. * afterwards "on the fly" if this object is part of a wide range "findBy" query.
  471. *
  472. * @todo Remove once doctrine is fixed
  473. * @return string
  474. */
  475. protected function fixInjectedPropertiesForDoctrineProxiesCode() {
  476. $code = <<<EOT
  477. if (!\$this instanceof \Doctrine\ORM\Proxy\Proxy || isset(\$this->FLOW3_Proxy_injectProperties_fixInjectedPropertiesForDoctrineProxies)) {
  478. return;
  479. }
  480. \$this->FLOW3_Proxy_injectProperties_fixInjectedPropertiesForDoctrineProxies = TRUE;
  481. if (is_callable(array(\$this, 'FLOW3_Proxy_injectProperties'))) {
  482. \$this->FLOW3_Proxy_injectProperties();
  483. }
  484. EOT;
  485. return $code;
  486. }
  487. /**
  488. * Traverses all intercepted methods and their advices and builds PHP code to intercept
  489. * methods if necessary.
  490. *
  491. * The generated code is added directly to the proxy class by calling the respective
  492. * methods of the Compiler API.
  493. *
  494. * @param string $targetClassName The target class the pointcut should match with
  495. * @param array $interceptedMethods An array of method names which need to be intercepted
  496. * @return void
  497. * @throws \TYPO3\FLOW3\Aop\Exception\VoidImplementationException
  498. */
  499. protected function buildMethodsInterceptorCode($targetClassName, array $interceptedMethods) {
  500. foreach ($interceptedMethods as $methodName => $methodMetaInformation) {
  501. if (count($methodMetaInformation['groupedAdvices']) === 0) {
  502. throw new \TYPO3\FLOW3\Aop\Exception\VoidImplementationException(sprintf('Refuse to introduce method %s into target class %s because it has no implementation code. You might want to create an around advice which implements this method.', $methodName, $targetClassName), 1303224472);
  503. }
  504. $builderType = 'Adviced' . ($methodName === '__construct' ? 'Constructor' : 'Method');
  505. $this->methodInterceptorBuilders[$builderType]->build($methodName, $interceptedMethods, $targetClassName);
  506. }
  507. }
  508. /**
  509. * Traverses all aspect containers, their aspects and their advisors and adds the
  510. * methods and their advices to the (usually empty) array of intercepted methods.
  511. *
  512. * @param array &$interceptedMethods An array (empty or not) which contains the names of the intercepted methods and additional information
  513. * @param array $methods An array of class and method names which are matched against the pointcut (class name = name of the class or interface the method was declared)
  514. * @param string $targetClassName Name of the class the pointcut should match with
  515. * @param array &$aspectContainers All aspects to take into consideration
  516. * @return void
  517. */
  518. protected function addAdvicedMethodsToInterceptedMethods(array &$interceptedMethods, array $methods, $targetClassName, array &$aspectContainers) {
  519. $pointcutQueryIdentifier = 0;
  520. foreach ($aspectContainers as $aspectContainer) {
  521. if (!$aspectContainer->getCachedTargetClassNameCandidates()->hasClassName($targetClassName)) {
  522. continue;
  523. }
  524. foreach ($aspectContainer->getAdvisors() as $advisor) {
  525. $pointcut = $advisor->getPointcut();
  526. foreach ($methods as $method) {
  527. list($methodDeclaringClassName, $methodName) = $method;
  528. if ($this->reflectionService->isMethodFinal($targetClassName, $methodName)) continue;
  529. if ($pointcut->matches($targetClassName, $methodName, $methodDeclaringClassName, $pointcutQueryIdentifier)) {
  530. $advice = $advisor->getAdvice();
  531. $interceptedMethods[$methodName]['groupedAdvices'][get_class($advice)][] = array(
  532. 'advice' => $advice,
  533. 'runtimeEvaluationsClosureCode' => $pointcut->getRuntimeEvaluationsClosureCode()
  534. );
  535. $interceptedMethods[$methodName]['declaringClassName'] = $methodDeclaringClassName;
  536. }
  537. $pointcutQueryIdentifier ++;
  538. }
  539. }
  540. }
  541. }
  542. /**
  543. * Traverses all methods which were introduced by interfaces and adds them to the
  544. * intercepted methods array if they didn't exist already.
  545. *
  546. * @param array &$interceptedMethods An array (empty or not) which contains the names of the intercepted methods and additional information
  547. * @param array $methodsFromIntroducedInterfaces An array of class and method names from introduced interfaces
  548. * @return void
  549. */
  550. protected function addIntroducedMethodsToInterceptedMethods(array &$interceptedMethods, array $methodsFromIntroducedInterfaces) {
  551. foreach ($methodsFromIntroducedInterfaces as $interfaceAndMethodName) {
  552. list($interfaceName, $methodName) = $interfaceAndMethodName;
  553. if (!isset($interceptedMethods[$methodName])) {
  554. $interceptedMethods[$methodName]['groupedAdvices'] = array();
  555. $interceptedMethods[$methodName]['declaringClassName'] = $interfaceName;
  556. }
  557. }
  558. }
  559. /**
  560. * Traverses all aspect containers and returns an array of interface
  561. * introductions which match the target class.
  562. *
  563. * @param array &$aspectContainers All aspects to take into consideration
  564. * @param string $targetClassName Name of the class the pointcut should match with
  565. * @return array array of interface names
  566. */
  567. protected function getMatchingInterfaceIntroductions(array &$aspectContainers, $targetClassName) {
  568. $introductions = array();
  569. foreach ($aspectContainers as $aspectContainer) {
  570. if (!$aspectContainer->getCachedTargetClassNameCandidates()->hasClassName($targetClassName)) {
  571. continue;
  572. }
  573. foreach ($aspectContainer->getInterfaceIntroductions() as $introduction) {
  574. $pointcut = $introduction->getPointcut();
  575. if ($pointcut->matches($targetClassName, NULL, NULL, uniqid())) {
  576. $introductions[] = $introduction;
  577. }
  578. }
  579. }
  580. return $introductions;
  581. }
  582. /**
  583. * Traverses all aspect containers and returns an array of property
  584. * introductions which match the target class.
  585. *
  586. * @param array &$aspectContainers All aspects to take into consideration
  587. * @param string $targetClassName Name of the class the pointcut should match with
  588. * @return array array of property introductions
  589. */
  590. protected function getMatchingPropertyIntroductions(array &$aspectContainers, $targetClassName) {
  591. $introductions = array();
  592. foreach ($aspectContainers as $aspectContainer) {
  593. if (!$aspectContainer->getCachedTargetClassNameCandidates()->hasClassName($targetClassName)) {
  594. continue;
  595. }
  596. foreach ($aspectContainer->getPropertyIntroductions() as $introduction) {
  597. $pointcut = $introduction->getPointcut();
  598. if ($pointcut->matches($targetClassName, NULL, NULL, uniqid())) {
  599. $introductions[] = $introduction;
  600. }
  601. }
  602. }
  603. return $introductions;
  604. }
  605. /**
  606. * Returns an array of interface names introduced by the given introductions
  607. *
  608. * @param array $interfaceIntroductions An array of interface introductions
  609. * @return array Array of interface names
  610. */
  611. protected function getInterfaceNamesFromIntroductions(array $interfaceIntroductions) {
  612. $interfaceNames = array();
  613. foreach ($interfaceIntroductions as $introduction) {
  614. $interfaceNames[] = '\\' . $introduction->getInterfaceName();
  615. }
  616. return $interfaceNames;
  617. }
  618. /**
  619. * Returns all methods declared by the introduced interfaces
  620. *
  621. * @param array $interfaceIntroductions An array of \TYPO3\FLOW3\Aop\InterfaceIntroduction
  622. * @return array An array of method information (interface, method name)
  623. * @throws \TYPO3\FLOW3\Aop\Exception
  624. */
  625. protected function getIntroducedMethodsFromInterfaceIntroductions(array $interfaceIntroductions) {
  626. $methods = array();
  627. $methodsAndIntroductions = array();
  628. foreach ($interfaceIntroductions as $introduction) {
  629. $interfaceName = $introduction->getInterfaceName();
  630. $methodNames = get_class_methods($interfaceName);
  631. if (is_array($methodNames)) {
  632. foreach ($methodNames as $newMethodName) {
  633. if (isset($methodsAndIntroductions[$newMethodName])) throw new \TYPO3\FLOW3\Aop\Exception('Method name conflict! Method "' . $newMethodName . '" introduced by "' . $introduction->getInterfaceName() . '" declared in aspect "' . $introduction->getDeclaringAspectClassName() . '" has already been introduced by "' . $methodsAndIntroductions[$newMethodName]->getInterfaceName() . '" declared in aspect "' . $methodsAndIntroductions[$newMethodName]->getDeclaringAspectClassName() . '".', 1173020942);
  634. $methods[] = array($interfaceName, $newMethodName);
  635. $methodsAndIntroductions[$newMethodName] = $introduction;
  636. }
  637. }
  638. }
  639. return $methods;
  640. }
  641. /**
  642. * Adds a "getAdviceChains()" method to the current proxy class.
  643. *
  644. * @param string $targetClassName
  645. * @return void
  646. */
  647. protected function buildGetAdviceChainsMethodCode($targetClassName) {
  648. $proxyMethod = $this->compiler->getProxyClass($targetClassName)->getMethod('FLOW3_Aop_Proxy_getAdviceChains');
  649. $proxyMethod->setMethodParametersCode('$methodName');
  650. $proxyMethod->overrideMethodVisibility('private');
  651. $code = <<<'EOT'
  652. $adviceChains = array();
  653. if (isset($this->FLOW3_Aop_Proxy_groupedAdviceChains[$methodName])) {
  654. $adviceChains = $this->FLOW3_Aop_Proxy_groupedAdviceChains[$methodName];
  655. } else {
  656. if (isset($this->FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices[$methodName])) {
  657. $groupedAdvices = $this->FLOW3_Aop_Proxy_targetMethodsAndGroupedAdvices[$methodName];
  658. if (isset($groupedAdvices['TYPO3\FLOW3\Aop\Advice\AroundAdvice'])) {
  659. $this->FLOW3_Aop_Proxy_groupedAdviceChains[$methodName]['TYPO3\FLOW3\Aop\Advice\AroundAdvice'] = new \TYPO3\FLOW3\Aop\Advice\AdviceChain($groupedAdvices['TYPO3\FLOW3\Aop\Advice\AroundAdvice']);
  660. $adviceChains = $this->FLOW3_Aop_Proxy_groupedAdviceChains[$methodName];
  661. }
  662. }
  663. }
  664. return $adviceChains;
  665. EOT;
  666. $proxyMethod->addPreParentCallCode($code);
  667. }
  668. /**
  669. * Adds a "invokeJoinPoint()" method to the current proxy class.
  670. *
  671. * @param string $targetClassName
  672. * @return void
  673. */
  674. protected function buildInvokeJoinPointMethodCode($targetClassName) {
  675. $proxyMethod = $this->compiler->getProxyClass($targetClassName)->getMethod('FLOW3_Aop_Proxy_invokeJoinPoint');
  676. $proxyMethod->setMethodParametersCode('\TYPO3\FLOW3\Aop\JoinPointInterface $joinPoint');
  677. $code = <<<'EOT'
  678. if (__CLASS__ !== $joinPoint->getClassName()) return parent::FLOW3_Aop_Proxy_invokeJoinPoint($joinPoint);
  679. if (isset($this->FLOW3_Aop_Proxy_methodIsInAdviceMode[$joinPoint->getMethodName()])) {
  680. return call_user_func_array(array('self', $joinPoint->getMethodName()), $joinPoint->getMethodArguments());
  681. }
  682. EOT;
  683. $proxyMethod->addPreParentCallCode($code);
  684. }
  685. /**
  686. * Renders a short message which gives a hint on where the currently parsed pointcut expression was defined.
  687. *
  688. * @param string $aspectClassName
  689. * @param string $methodName
  690. * @param string $tagName
  691. * @return string
  692. */
  693. protected function renderSourceHint($aspectClassName, $methodName, $tagName) {
  694. return sprintf('%s::%s (%s advice)', $aspectClassName, $methodName, $tagName);
  695. }
  696. }
  697. ?>