ExpressionLanguage.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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. // Help opcache.preload discover always-needed symbols
  14. class_exists(ParsedExpression::class);
  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 ExpressionFunctionProviderInterface[] $providers
  29. */
  30. public function __construct(CacheItemPoolInterface $cache = null, array $providers = [])
  31. {
  32. $this->cache = $cache ?: new ArrayAdapter();
  33. $this->registerFunctions();
  34. foreach ($providers as $provider) {
  35. $this->registerProvider($provider);
  36. }
  37. }
  38. /**
  39. * Compiles an expression source code.
  40. *
  41. * @param Expression|string $expression The expression to compile
  42. *
  43. * @return string The compiled PHP source code
  44. */
  45. public function compile($expression, array $names = [])
  46. {
  47. return $this->getCompiler()->compile($this->parse($expression, $names)->getNodes())->getSource();
  48. }
  49. /**
  50. * Evaluate an expression.
  51. *
  52. * @param Expression|string $expression The expression to compile
  53. *
  54. * @return mixed The result of the evaluation of the expression
  55. */
  56. public function evaluate($expression, array $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. *
  65. * @return ParsedExpression A ParsedExpression instance
  66. */
  67. public function parse($expression, array $names)
  68. {
  69. if ($expression instanceof ParsedExpression) {
  70. return $expression;
  71. }
  72. asort($names);
  73. $cacheKeyItems = [];
  74. foreach ($names as $nameKey => $name) {
  75. $cacheKeyItems[] = \is_int($nameKey) ? $name : $nameKey.':'.$name;
  76. }
  77. $cacheItem = $this->cache->getItem(rawurlencode($expression.'//'.implode('|', $cacheKeyItems)));
  78. if (null === $parsedExpression = $cacheItem->get()) {
  79. $nodes = $this->getParser()->parse($this->getLexer()->tokenize((string) $expression), $names);
  80. $parsedExpression = new ParsedExpression((string) $expression, $nodes);
  81. $cacheItem->set($parsedExpression);
  82. $this->cache->save($cacheItem);
  83. }
  84. return $parsedExpression;
  85. }
  86. /**
  87. * Validates the syntax of an expression.
  88. *
  89. * @param Expression|string $expression The expression to validate
  90. * @param array|null $names The list of acceptable variable names in the expression, or null to accept any names
  91. *
  92. * @throws SyntaxError When the passed expression is invalid
  93. */
  94. public function lint($expression, ?array $names): void
  95. {
  96. if ($expression instanceof ParsedExpression) {
  97. return;
  98. }
  99. $this->getParser()->lint($this->getLexer()->tokenize((string) $expression), $names);
  100. }
  101. /**
  102. * Registers a function.
  103. *
  104. * @param callable $compiler A callable able to compile the function
  105. * @param callable $evaluator A callable able to evaluate the function
  106. *
  107. * @throws \LogicException when registering a function after calling evaluate(), compile() or parse()
  108. *
  109. * @see ExpressionFunction
  110. */
  111. public function register(string $name, callable $compiler, callable $evaluator)
  112. {
  113. if (null !== $this->parser) {
  114. throw new \LogicException('Registering functions after calling evaluate(), compile() or parse() is not supported.');
  115. }
  116. $this->functions[$name] = ['compiler' => $compiler, 'evaluator' => $evaluator];
  117. }
  118. public function addFunction(ExpressionFunction $function)
  119. {
  120. $this->register($function->getName(), $function->getCompiler(), $function->getEvaluator());
  121. }
  122. public function registerProvider(ExpressionFunctionProviderInterface $provider)
  123. {
  124. foreach ($provider->getFunctions() as $function) {
  125. $this->addFunction($function);
  126. }
  127. }
  128. protected function registerFunctions()
  129. {
  130. $this->addFunction(ExpressionFunction::fromPhp('constant'));
  131. }
  132. private function getLexer(): Lexer
  133. {
  134. if (null === $this->lexer) {
  135. $this->lexer = new Lexer();
  136. }
  137. return $this->lexer;
  138. }
  139. private function getParser(): Parser
  140. {
  141. if (null === $this->parser) {
  142. $this->parser = new Parser($this->functions);
  143. }
  144. return $this->parser;
  145. }
  146. private function getCompiler(): Compiler
  147. {
  148. if (null === $this->compiler) {
  149. $this->compiler = new Compiler($this->functions);
  150. }
  151. return $this->compiler->reset();
  152. }
  153. }