ExpressionLanguage.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\ExpressionLanguage;
  11. use Psr\Cache\CacheItemPoolInterface;
  12. use Symfony\Component\Cache\Adapter\ArrayAdapter;
  13. use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheAdapter;
  14. use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface;
  15. /**
  16. * Allows to compile and evaluate expressions written in your own DSL.
  17. *
  18. * @author Fabien Potencier <fabien@symfony.com>
  19. */
  20. class ExpressionLanguage
  21. {
  22. private $cache;
  23. private $lexer;
  24. private $parser;
  25. private $compiler;
  26. protected $functions = [];
  27. /**
  28. * @param CacheItemPoolInterface $cache
  29. * @param ExpressionFunctionProviderInterface[] $providers
  30. */
  31. public function __construct($cache = null, array $providers = [])
  32. {
  33. if (null !== $cache) {
  34. if ($cache instanceof ParserCacheInterface) {
  35. @trigger_error(sprintf('Passing an instance of %s as constructor argument for %s is deprecated as of 3.2 and will be removed in 4.0. Pass an instance of %s instead.', ParserCacheInterface::class, self::class, CacheItemPoolInterface::class), E_USER_DEPRECATED);
  36. $cache = new ParserCacheAdapter($cache);
  37. } elseif (!$cache instanceof CacheItemPoolInterface) {
  38. throw new \InvalidArgumentException(sprintf('Cache argument has to implement %s.', CacheItemPoolInterface::class));
  39. }
  40. }
  41. $this->cache = $cache ?: new ArrayAdapter();
  42. $this->registerFunctions();
  43. foreach ($providers as $provider) {
  44. $this->registerProvider($provider);
  45. }
  46. }
  47. /**
  48. * Compiles an expression source code.
  49. *
  50. * @param Expression|string $expression The expression to compile
  51. * @param array $names An array of valid names
  52. *
  53. * @return string The compiled PHP source code
  54. */
  55. public function compile($expression, $names = [])
  56. {
  57. return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource();
  58. }
  59. /**
  60. * Evaluate an expression.
  61. *
  62. * @param Expression|string $expression The expression to compile
  63. * @param array $values An array of values
  64. *
  65. * @return mixed The result of the evaluation of the expression
  66. */
  67. public function evaluate($expression, $values = [])
  68. {
  69. return $this->parse($expression, array_keys($values))->getNodes()->evaluate($this->functions, $values);
  70. }
  71. /**
  72. * Parses an expression.
  73. *
  74. * @param Expression|string $expression The expression to parse
  75. * @param array $names An array of valid names
  76. *
  77. * @return ParsedExpression A ParsedExpression instance
  78. */
  79. public function parse($expression, $names)
  80. {
  81. if ($expression instanceof ParsedExpression) {
  82. return $expression;
  83. }
  84. asort($names);
  85. $cacheKeyItems = [];
  86. foreach ($names as $nameKey => $name) {
  87. $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name;
  88. }
  89. $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems)));
  90. if (null === $parsedExpression = $cacheItem->get()) {
  91. $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names);
  92. $parsedExpression = new ParsedExpression((string) $expression, $nodes);
  93. $cacheItem->set($parsedExpression);
  94. $this->cache->save($cacheItem);
  95. }
  96. return $parsedExpression;
  97. }
  98. /**
  99. * Registers a function.
  100. *
  101. * @param string $name The function name
  102. * @param callable $compiler A callable able to compile the function
  103. * @param callable $evaluator A callable able to evaluate the function
  104. *
  105. * @throws \LogicException when registering a function after calling evaluate(), compile() or parse()
  106. *
  107. * @see ExpressionFunction
  108. */
  109. public function register($name, callable $compiler, callable $evaluator)
  110. {
  111. if (null !== $this->parser) {
  112. throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.');
  113. }
  114. $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator];
  115. }
  116. public function addFunction(ExpressionFunction $function)
  117. {
  118. $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator());
  119. }
  120. public function registerProvider(ExpressionFunctionProviderInterface $provider)
  121. {
  122. foreach ($provider->getFunctions() as $function) {
  123. $this->addFunction($function);
  124. }
  125. }
  126. protected function registerFunctions()
  127. {
  128. $this->addFunction(ExpressionFunction::fromPhp('constant'));
  129. }
  130. private function getLexer()
  131. {
  132. if (null === $this->lexer) {
  133. $this->lexer = new Lexer();
  134. }
  135. return $this->lexer;
  136. }
  137. private function getParser()
  138. {
  139. if (null === $this->parser) {
  140. $this->parser = new Parser($this->functions);
  141. }
  142. return $this->parser;
  143. }
  144. private function getCompiler()
  145. {
  146. if (null === $this->compiler) {
  147. $this->compiler = new Compiler($this->functions);
  148. }
  149. return $this->compiler->reset();
  150. }
  151. }