ExpressionLanguage.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. /**
  14. * Allows to compile and evaluate expressions written in your own DSL.
  15. *
  16. * @author Fabien Potencier <fabien@symfony.com>
  17. */
  18. class ExpressionLanguage
  19. {
  20. private $cache;
  21. private $lexer;
  22. private $parser;
  23. private $compiler;
  24. protected $functions = [];
  25. /**
  26. * @param ExpressionFunctionProviderInterface[] $providers
  27. */
  28. public function __construct(CacheItemPoolInterface $cache = null, array $providers = [])
  29. {
  30. $this->cache = $cache ?? new ArrayAdapter();
  31. $this->registerFunctions();
  32. foreach ($providers as $provider) {
  33. $this->registerProvider($provider);
  34. }
  35. }
  36. /**
  37. * Compiles an expression source code.
  38. *
  39. * @param Expression|string $expression The expression to compile
  40. * @param array $names An array of valid names
  41. *
  42. * @return string The compiled PHP source code
  43. */
  44. public function compile($expression, $names = [])
  45. {
  46. return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource();
  47. }
  48. /**
  49. * Evaluate an expression.
  50. *
  51. * @param Expression|string $expression The expression to compile
  52. * @param array $values An array of values
  53. *
  54. * @return mixed The result of the evaluation of the expression
  55. */
  56. public function evaluate($expression, $values = [])
  57. {
  58. return $this->parse($expression, array_keys($values))->getNodes()->evaluate($this->functions, $values);
  59. }
  60. /**
  61. * Parses an expression.
  62. *
  63. * @param Expression|string $expression The expression to parse
  64. * @param array $names An array of valid names
  65. *
  66. * @return ParsedExpression A ParsedExpression instance
  67. */
  68. public function parse($expression, $names)
  69. {
  70. if ($expression instanceof ParsedExpression) {
  71. return $expression;
  72. }
  73. asort($names);
  74. $cacheKeyItems = [];
  75. foreach ($names as $nameKey => $name) {
  76. $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name;
  77. }
  78. $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems)));
  79. if (null === $parsedExpression = $cacheItem->get()) {
  80. $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names);
  81. $parsedExpression = new ParsedExpression((string) $expression, $nodes);
  82. $cacheItem->set($parsedExpression);
  83. $this->cache->save($cacheItem);
  84. }
  85. return $parsedExpression;
  86. }
  87. /**
  88. * Registers a function.
  89. *
  90. * @param string $name The function name
  91. * @param callable $compiler A callable able to compile the function
  92. * @param callable $evaluator A callable able to evaluate the function
  93. *
  94. * @throws \LogicException when registering a function after calling evaluate(), compile() or parse()
  95. *
  96. * @see ExpressionFunction
  97. */
  98. public function register($name, callable $compiler, callable $evaluator)
  99. {
  100. if (null !== $this->parser) {
  101. throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.');
  102. }
  103. $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator];
  104. }
  105. public function addFunction(ExpressionFunction $function)
  106. {
  107. $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator());
  108. }
  109. public function registerProvider(ExpressionFunctionProviderInterface $provider)
  110. {
  111. foreach ($provider->getFunctions() as $function) {
  112. $this->addFunction($function);
  113. }
  114. }
  115. protected function registerFunctions()
  116. {
  117. $this->addFunction(ExpressionFunction::fromPhp('constant'));
  118. }
  119. private function getLexer(): Lexer
  120. {
  121. if (null === $this->lexer) {
  122. $this->lexer = new Lexer();
  123. }
  124. return $this->lexer;
  125. }
  126. private function getParser(): Parser
  127. {
  128. if (null === $this->parser) {
  129. $this->parser = new Parser($this->functions);
  130. }
  131. return $this->parser;
  132. }
  133. private function getCompiler(): Compiler
  134. {
  135. if (null === $this->compiler) {
  136. $this->compiler = new Compiler($this->functions);
  137. }
  138. return $this->compiler->reset();
  139. }
  140. }