PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Classes/TYPO3/FLOW3/Aop/Pointcut/PointcutExpressionParser.php

https://github.com/christianjul/FLOW3-Composer
PHP | 474 lines | 256 code | 46 blank | 172 comment | 31 complexity | 46866bdf9c4ff0205daff8b6e99c8a64 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Aop\Pointcut;
  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\Annotations as FLOW3;
  13. /**
  14. * The pointcut expression parser parses the definition of the place and circumstances
  15. * where advices can be inserted later on. The input of the parse() function is a string
  16. * from a pointcut- or advice annotation and returns a pointcut filter composite.
  17. *
  18. * @see \TYPO3\FLOW3\Aop\Pointcut, PointcutFilterComposite
  19. * @FLOW3\Scope("singleton")
  20. * @FLOW3\Proxy(false)
  21. */
  22. class PointcutExpressionParser {
  23. const PATTERN_SPLITBYOPERATOR = '/\s*(\&\&|\|\|)\s*/';
  24. const PATTERN_MATCHPOINTCUTDESIGNATOR = '/^\s*(classAnnotatedWith|class|methodAnnotatedWith|methodTaggedWith|method|within|filter|setting|evaluate)/';
  25. const PATTERN_MATCHVISIBILITYMODIFIER = '/^(public|protected) +/';
  26. const PATTERN_MATCHRUNTIMEEVALUATIONSDEFINITION = '/(?:
  27. (?:
  28. \s*( "(?:\\\"|[^"])*"
  29. |\(.*?\)
  30. |\'(?:\\\\\'|[^\'])*\'
  31. |[a-zA-Z0-9\-_.]+
  32. )
  33. \s*(===?|!==?|<=|>=|<|>|in|contains|matches)\s*
  34. ( "(?:\\\"|[^"])*"
  35. |\(.*?\)
  36. |\'(?:\\\\\'|[^\'])*\'
  37. |[a-zA-Z0-9\-_.]+
  38. )
  39. )
  40. \s*,{0,1}?
  41. )+
  42. /x';
  43. const PATTERN_MATCHRUNTIMEEVALUATIONSVALUELIST = '/(?:
  44. \s*(
  45. "(?:\\\"|[^"])*"
  46. |\'(?:\\\\\'|[^\'])*\'
  47. |(?:[a-zA-Z0-9\-_.])+
  48. )
  49. \s*,{0,1}?
  50. )+
  51. /x';
  52. const PATTERN_MATCHMETHODNAMEANDARGUMENTS = '/^(?P<MethodName>.*)\((?P<MethodArguments>.*)\)$/';
  53. /**
  54. * @var \TYPO3\FLOW3\Aop\Builder\ProxyClassBuilder
  55. */
  56. protected $proxyClassBuilder;
  57. /**
  58. * @var \TYPO3\FLOW3\Reflection\ReflectionService
  59. */
  60. protected $reflectionService;
  61. /**
  62. * @var \TYPO3\FLOW3\Object\ObjectManagerInterface
  63. */
  64. protected $objectManager;
  65. /**
  66. * @var string
  67. */
  68. protected $sourceHint = '';
  69. /**
  70. * @param \TYPO3\FLOW3\Aop\Builder\ProxyClassBuilder $proxyClassBuilder
  71. * @return void
  72. */
  73. public function injectProxyClassBuilder(\TYPO3\FLOW3\Aop\Builder\ProxyClassBuilder $proxyClassBuilder) {
  74. $this->proxyClassBuilder = $proxyClassBuilder;
  75. }
  76. /**
  77. * @param \TYPO3\FLOW3\Reflection\ReflectionService $reflectionService
  78. * @return void
  79. */
  80. public function injectReflectionService(\TYPO3\FLOW3\Reflection\ReflectionService $reflectionService) {
  81. $this->reflectionService = $reflectionService;
  82. }
  83. /**
  84. * @param \TYPO3\FLOW3\Object\ObjectManagerInterface $objectManager
  85. * @return void
  86. */
  87. public function injectObjectManager(\TYPO3\FLOW3\Object\ObjectManagerInterface $objectManager) {
  88. $this->objectManager = $objectManager;
  89. }
  90. /**
  91. * Parses a string pointcut expression and returns the pointcut
  92. * objects accordingly
  93. *
  94. * @param string $pointcutExpression The expression defining the pointcut
  95. * @param string $sourceHint A message giving a hint on where the expression was defined. This is used in error messages.
  96. * @return PointcutFilterComposite A composite of class-filters, method-filters and pointcuts
  97. * @throws \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException
  98. * @throws \TYPO3\FLOW3\Aop\Exception
  99. */
  100. public function parse($pointcutExpression, $sourceHint) {
  101. $this->sourceHint = $sourceHint;
  102. if (!is_string($pointcutExpression) || strlen($pointcutExpression) === 0) {
  103. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Pointcut expression must be a valid string, ' . gettype($pointcutExpression) . ' given, defined in ' . $this->sourceHint, 1168874738);
  104. }
  105. $pointcutFilterComposite = new PointcutFilterComposite();
  106. $pointcutExpressionParts = preg_split(self::PATTERN_SPLITBYOPERATOR, $pointcutExpression, -1, PREG_SPLIT_DELIM_CAPTURE);
  107. for ($partIndex = 0; $partIndex < count($pointcutExpressionParts); $partIndex += 2) {
  108. $operator = ($partIndex > 0) ? trim($pointcutExpressionParts[$partIndex - 1]) : '&&';
  109. $expression = trim($pointcutExpressionParts[$partIndex]);
  110. if ($expression[0] === '!') {
  111. $expression = trim(substr($expression, 1));
  112. $operator .= '!';
  113. }
  114. if (strpos($expression, '(') === FALSE) {
  115. $this->parseDesignatorPointcut($operator, $expression, $pointcutFilterComposite);
  116. } else {
  117. $matches = array();
  118. $numberOfMatches = preg_match(self::PATTERN_MATCHPOINTCUTDESIGNATOR, $expression, $matches);
  119. if ($numberOfMatches !== 1) {
  120. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Syntax error: Pointcut designator expected near "' . $expression . '", defined in ' . $this->sourceHint, 1168874739);
  121. }
  122. $pointcutDesignator = $matches[0];
  123. $signaturePattern = $this->getSubstringBetweenParentheses($expression);
  124. switch ($pointcutDesignator) {
  125. case 'classAnnotatedWith':
  126. case 'class' :
  127. case 'methodAnnotatedWith':
  128. case 'methodTaggedWith' :
  129. case 'method' :
  130. case 'within' :
  131. case 'filter' :
  132. case 'setting' :
  133. $parseMethodName = 'parseDesignator' . ucfirst($pointcutDesignator);
  134. $this->$parseMethodName($operator, $signaturePattern, $pointcutFilterComposite);
  135. break;
  136. case 'evaluate' :
  137. $this->parseRuntimeEvaluations($operator, $signaturePattern, $pointcutFilterComposite);
  138. break;
  139. default :
  140. throw new \TYPO3\FLOW3\Aop\Exception('Support for pointcut designator "' . $pointcutDesignator . '" has not been implemented (yet), defined in ' . $this->sourceHint, 1168874740);
  141. }
  142. }
  143. }
  144. return $pointcutFilterComposite;
  145. }
  146. /**
  147. * Takes a class annotation filter pattern and adds a so configured class annotation filter to the
  148. * filter composite object.
  149. *
  150. * @param string $operator The operator
  151. * @param string $classAnnotationPattern The pattern expression as configuration for the class annotation filter
  152. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the class annotation filter) will be added to this composite object.
  153. * @return void
  154. */
  155. protected function parseDesignatorClassAnnotatedWith($operator, $classAnnotationPattern, PointcutFilterComposite $pointcutFilterComposite) {
  156. $filter = new PointcutClassAnnotatedWithFilter($classAnnotationPattern);
  157. $filter->injectReflectionService($this->reflectionService);
  158. $pointcutFilterComposite->addFilter($operator, $filter);
  159. }
  160. /**
  161. * Takes a class filter pattern and adds a so configured class filter to the
  162. * filter composite object.
  163. *
  164. * @param string $operator The operator
  165. * @param string $classPattern The pattern expression as configuration for the class filter
  166. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the class filter) will be added to this composite object.
  167. * @return void
  168. */
  169. protected function parseDesignatorClass($operator, $classPattern, PointcutFilterComposite $pointcutFilterComposite) {
  170. $filter = new PointcutClassNameFilter($classPattern);
  171. $filter->injectReflectionService($this->reflectionService);
  172. $pointcutFilterComposite->addFilter($operator, $filter);
  173. }
  174. /**
  175. * Takes a method annotation filter pattern and adds a so configured method annotation filter to the
  176. * filter composite object.
  177. *
  178. * @param string $operator The operator
  179. * @param string $methodAnnotationPattern The pattern expression as configuration for the method annotation filter
  180. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the method annotation filter) will be added to this composite object.
  181. * @return void
  182. * @deprecated since 1.0
  183. */
  184. protected function parseDesignatorMethodAnnotatedWith($operator, $methodAnnotationPattern, PointcutFilterComposite $pointcutFilterComposite) {
  185. $filter = new PointcutMethodAnnotatedWithFilter($methodAnnotationPattern);
  186. $filter->injectReflectionService($this->reflectionService);
  187. $pointcutFilterComposite->addFilter($operator, $filter);
  188. }
  189. /**
  190. * Takes a method tag filter pattern and adds a so configured method tag filter to the
  191. * filter composite object.
  192. *
  193. * @param string $operator The operator
  194. * @param string $methodTagPattern The pattern expression as configuration for the method tag filter
  195. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the method tag filter) will be added to this composite object.
  196. * @return void
  197. */
  198. protected function parseDesignatorMethodTaggedWith($operator, $methodTagPattern, PointcutFilterComposite $pointcutFilterComposite) {
  199. $filter = new PointcutMethodTaggedWithFilter($methodTagPattern);
  200. $filter->injectReflectionService($this->reflectionService);
  201. $pointcutFilterComposite->addFilter($operator, $filter);
  202. }
  203. /**
  204. * Splits the parameters of the pointcut designator "method" into a class
  205. * and a method part and adds the appropriately configured filters to the
  206. * filter composite object.
  207. *
  208. * @param string $operator The operator
  209. * @param string $signaturePattern The pattern expression defining the class and method - the "signature"
  210. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the class and method filter) will be added to this composite object.
  211. * @return void
  212. * @throws \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException if there's an error in the pointcut expression
  213. */
  214. protected function parseDesignatorMethod($operator, $signaturePattern, PointcutFilterComposite $pointcutFilterComposite) {
  215. if (strpos($signaturePattern, '->') === FALSE) {
  216. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Syntax error: "->" expected in "' . $signaturePattern . '", defined in ' . $this->sourceHint, 1169027339);
  217. }
  218. $methodVisibility = $this->getVisibilityFromSignaturePattern($signaturePattern);
  219. list($classPattern, $methodPattern) = explode ('->', $signaturePattern, 2);
  220. if (strpos($methodPattern, '(') === FALSE ) {
  221. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Syntax error: "(" expected in "' . $methodPattern . '", defined in ' . $this->sourceHint, 1169144299);
  222. }
  223. $matches = array();
  224. preg_match(self::PATTERN_MATCHMETHODNAMEANDARGUMENTS, $methodPattern, $matches);
  225. $methodNamePattern = $matches['MethodName'];
  226. $methodArgumentPattern = $matches['MethodArguments'];
  227. $methodArgumentConstraints = $this->getArgumentConstraintsFromMethodArgumentsPattern($methodArgumentPattern);
  228. $classNameFilter = new PointcutClassNameFilter($classPattern);
  229. $classNameFilter->injectReflectionService($this->reflectionService);
  230. $methodNameFilter = new PointcutMethodNameFilter($methodNamePattern, $methodVisibility, $methodArgumentConstraints);
  231. $methodNameFilter->injectSystemLogger($this->objectManager->get('TYPO3\FLOW3\Log\SystemLoggerInterface'));
  232. $methodNameFilter->injectReflectionService($this->reflectionService);
  233. if ($operator !== '&&') {
  234. $subComposite = new PointcutFilterComposite();
  235. $subComposite->addFilter('&&', $classNameFilter);
  236. $subComposite->addFilter('&&', $methodNameFilter);
  237. $pointcutFilterComposite->addFilter($operator, $subComposite);
  238. } else {
  239. $pointcutFilterComposite->addFilter('&&', $classNameFilter);
  240. $pointcutFilterComposite->addFilter('&&', $methodNameFilter);
  241. }
  242. }
  243. /**
  244. * Adds a class type filter to the poincut filter composite
  245. *
  246. * @param string $operator
  247. * @param string $signaturePattern The pattern expression defining the class type
  248. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the class type filter) will be added to this composite object.
  249. * @return void
  250. */
  251. protected function parseDesignatorWithin($operator, $signaturePattern, PointcutFilterComposite $pointcutFilterComposite) {
  252. $filter = new PointcutClassTypeFilter($signaturePattern);
  253. $filter->injectReflectionService($this->reflectionService);
  254. $pointcutFilterComposite->addFilter($operator, $filter);
  255. }
  256. /**
  257. * Splits the value of the pointcut designator "pointcut" into an aspect
  258. * class- and a pointcut method part and adds the appropriately configured
  259. * filter to the composite object.
  260. *
  261. * @param string $operator The operator
  262. * @param string $pointcutExpression The pointcut expression (value of the designator)
  263. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the pointcut filter) will be added to this composite object.
  264. * @return void
  265. * @throws \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException
  266. */
  267. protected function parseDesignatorPointcut($operator, $pointcutExpression, PointcutFilterComposite $pointcutFilterComposite) {
  268. if (strpos($pointcutExpression, '->') === FALSE) {
  269. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Syntax error: "->" expected in "' . $pointcutExpression . '", defined in ' . $this->sourceHint, 1172219205);
  270. }
  271. list($aspectClassName, $pointcutMethodName) = explode ('->', $pointcutExpression, 2);
  272. $pointcutFilter = new PointcutFilter($aspectClassName, $pointcutMethodName);
  273. $pointcutFilter->injectProxyClassBuilder($this->proxyClassBuilder);
  274. $pointcutFilterComposite->addFilter($operator, $pointcutFilter);
  275. }
  276. /**
  277. * Adds a custom filter to the poincut filter composite
  278. *
  279. * @param string $operator The operator
  280. * @param string $filterObjectName Object Name of the custom filter (value of the designator)
  281. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the custom filter) will be added to this composite object.
  282. * @return void
  283. * @throws \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException
  284. */
  285. protected function parseDesignatorFilter($operator, $filterObjectName, PointcutFilterComposite $pointcutFilterComposite) {
  286. $customFilter = $this->objectManager->get($filterObjectName);
  287. if (!$customFilter instanceof PointcutFilterInterface) {
  288. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Invalid custom filter: "' . $filterObjectName . '" does not implement the required PointcutFilterInterface, defined in ' . $this->sourceHint, 1231871755);
  289. }
  290. $pointcutFilterComposite->addFilter($operator, $customFilter);
  291. }
  292. /**
  293. * Adds a setting filter to the pointcut filter composite
  294. *
  295. * @param string $operator The operator
  296. * @param string $configurationPath The path to the settings option, that should be used
  297. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the custom filter) will be added to this composite object.
  298. * @return void
  299. */
  300. protected function parseDesignatorSetting($operator, $configurationPath, PointcutFilterComposite $pointcutFilterComposite) {
  301. $filter = new PointcutSettingFilter($configurationPath);
  302. $filter->injectConfigurationManager($this->objectManager->get('TYPO3\FLOW3\Configuration\ConfigurationManager'));
  303. $pointcutFilterComposite->addFilter($operator, $filter);
  304. }
  305. /**
  306. * Adds runtime evaluations to the pointcut filter composite
  307. *
  308. * @param string $operator The operator
  309. * @param string $runtimeEvaluations The runtime evaluations string
  310. * @param PointcutFilterComposite $pointcutFilterComposite An instance of the pointcut filter composite. The result (ie. the custom filter) will be added to this composite object.
  311. * @return void
  312. */
  313. protected function parseRuntimeEvaluations($operator, $runtimeEvaluations, PointcutFilterComposite $pointcutFilterComposite) {
  314. $runtimeEvaluationsDefinition = array(
  315. $operator => array(
  316. 'evaluateConditions' => $this->getRuntimeEvaluationConditionsFromEvaluateString($runtimeEvaluations)
  317. )
  318. );
  319. $pointcutFilterComposite->setGlobalRuntimeEvaluationsDefinition($runtimeEvaluationsDefinition);
  320. }
  321. /**
  322. * Returns the substring of $string which is enclosed by parentheses
  323. * of the first level.
  324. *
  325. * @param string $string The string to parse
  326. * @return string The inner part between the first level of parentheses
  327. * @throws \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException
  328. */
  329. protected function getSubstringBetweenParentheses($string) {
  330. $startingPosition = 0;
  331. $openParentheses = 0;
  332. $substring = '';
  333. for ($i = $startingPosition; $i < strlen($string); $i++) {
  334. if ($string[$i] === ')') {
  335. $openParentheses--;
  336. }
  337. if ($openParentheses > 0) {
  338. $substring .= $string{$i};
  339. }
  340. if ($string[$i] === '(') {
  341. $openParentheses++;
  342. }
  343. }
  344. if ($openParentheses < 0) {
  345. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Pointcut expression is in excess of ' . abs($openParentheses) . ' closing parenthesis/es, defined in ' . $this->sourceHint, 1168966689);
  346. }
  347. if ($openParentheses > 0) {
  348. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Pointcut expression lacks of ' . $openParentheses . ' closing parenthesis/es, defined in ' . $this->sourceHint, 1168966690);
  349. }
  350. return $substring;
  351. }
  352. /**
  353. * Parses the signature pattern and returns the visibility modifier if any. If a modifier
  354. * was found, it will be removed from the $signaturePattern.
  355. *
  356. * @param string &$signaturePattern The regular expression for matching the method() signature
  357. * @return string Visibility modifier or NULL of none was found
  358. * @throws \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException
  359. */
  360. protected function getVisibilityFromSignaturePattern(&$signaturePattern) {
  361. $visibility = NULL;
  362. $matches = array();
  363. $numberOfMatches = preg_match_all(self::PATTERN_MATCHVISIBILITYMODIFIER, $signaturePattern, $matches, PREG_SET_ORDER);
  364. if ($numberOfMatches > 1) {
  365. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Syntax error: method name expected after visibility modifier in "' . $signaturePattern . '", defined in ' . $this->sourceHint, 1172492754);
  366. }
  367. if ($numberOfMatches === FALSE) {
  368. throw new \TYPO3\FLOW3\Aop\Exception\InvalidPointcutExpressionException('Error while matching visibility modifier in "' . $signaturePattern . '", defined in ' . $this->sourceHint, 1172492967);
  369. }
  370. if ($numberOfMatches === 1) {
  371. $visibility = $matches[0][1];
  372. $signaturePattern = trim(substr($signaturePattern, strlen($visibility)));
  373. }
  374. return $visibility;
  375. }
  376. /**
  377. * Parses the method arguments pattern and returns the corresponding constraints array
  378. *
  379. * @param string $methodArgumentsPattern The arguments pattern defined in the pointcut expression
  380. * @return array The corresponding constraints array
  381. */
  382. protected function getArgumentConstraintsFromMethodArgumentsPattern($methodArgumentsPattern) {
  383. $matches = array();
  384. $argumentConstraints = array();
  385. preg_match_all(self::PATTERN_MATCHRUNTIMEEVALUATIONSDEFINITION, $methodArgumentsPattern, $matches);
  386. for ($i = 0; $i < count($matches[0]); $i++) {
  387. if ($matches[2][$i] === 'in' || $matches[2][$i] === 'matches') {
  388. $list = array();
  389. $listEntries = array();
  390. if (preg_match('/^\s*\(.*\)\s*$/', $matches[3][$i], $list) > 0) {
  391. preg_match_all(self::PATTERN_MATCHRUNTIMEEVALUATIONSVALUELIST, $list[0], $listEntries);
  392. $matches[3][$i] = $listEntries[1];
  393. }
  394. }
  395. $argumentConstraints[$matches[1][$i]]['operator'][] = $matches[2][$i];
  396. $argumentConstraints[$matches[1][$i]]['value'][] = $matches[3][$i];
  397. }
  398. return $argumentConstraints;
  399. }
  400. /**
  401. * Parses the evaluate string for runtime evaluations and returns the corresponding conditions array
  402. *
  403. * @param string $evaluateString The evaluate string defined in the pointcut expression
  404. * @return array The corresponding constraints array
  405. */
  406. protected function getRuntimeEvaluationConditionsFromEvaluateString($evaluateString) {
  407. $matches = array();
  408. $runtimeEvaluationConditions = array();
  409. preg_match_all(self::PATTERN_MATCHRUNTIMEEVALUATIONSDEFINITION, $evaluateString, $matches);
  410. for ($i = 0; $i < count($matches[0]); $i++) {
  411. if ($matches[2][$i] === 'in' || $matches[2][$i] === 'matches') {
  412. $list = array();
  413. $listEntries = array();
  414. if (preg_match('/^\s*\(.*\)\s*$/', $matches[3][$i], $list) > 0) {
  415. preg_match_all(self::PATTERN_MATCHRUNTIMEEVALUATIONSVALUELIST, $list[0], $listEntries);
  416. $matches[3][$i] = $listEntries[1];
  417. }
  418. }
  419. $runtimeEvaluationConditions[] = array(
  420. 'operator' => $matches[2][$i],
  421. 'leftValue' => $matches[1][$i],
  422. 'rightValue' => $matches[3][$i],
  423. );
  424. }
  425. return $runtimeEvaluationConditions;
  426. }
  427. }
  428. ?>