1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Twig\ExpressionParser; 13 14/** 15 * @template-implements \IteratorAggregate<ExpressionParserInterface> 16 * 17 * @internal 18 */ 19final class ExpressionParsers implements \IteratorAggregate 20{ 21 /** 22 * @var array<class-string<ExpressionParserInterface>, array<string, ExpressionParserInterface>> 23 */ 24 private array $parsersByName = []; 25 26 /** 27 * @var array<class-string<ExpressionParserInterface>, ExpressionParserInterface> 28 */ 29 private array $parsersByClass = []; 30 31 /** 32 * @var \WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>>|null 33 */ 34 private ?\WeakMap $precedenceChanges = null; 35 36 /** 37 * @param array<ExpressionParserInterface> $parsers 38 */ 39 public function __construct(array $parsers = []) 40 { 41 $this->add($parsers); 42 } 43 44 /** 45 * @param array<ExpressionParserInterface> $parsers 46 * 47 * @return $this 48 */ 49 public function add(array $parsers): static 50 { 51 foreach ($parsers as $parser) { 52 if ($parser->getPrecedence() > 512 || $parser->getPrecedence() < 0) { 53 trigger_deprecation('twig/twig', '3.21', 'Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence()); 54 // throw new \InvalidArgumentException(\sprintf('Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence())); 55 } 56 $interface = $parser instanceof PrefixExpressionParserInterface ? PrefixExpressionParserInterface::class : InfixExpressionParserInterface::class; 57 $this->parsersByClass[$parser::class] = $parser; 58 foreach (self::getOperatorTokensFor($parser) as $token) { 59 $this->parsersByName[$interface][$token] = $parser; 60 } 61 } 62 63 return $this; 64 } 65 66 /** 67 * @template T of ExpressionParserInterface 68 * 69 * @param class-string<T> $class 70 * 71 * @return T|null 72 */ 73 public function getByClass(string $class): ?ExpressionParserInterface 74 { 75 return $this->parsersByClass[$class] ?? null; 76 } 77 78 /** 79 * @template T of ExpressionParserInterface 80 * 81 * @param class-string<T> $interface 82 * 83 * @return T|null 84 */ 85 public function getByName(string $interface, string $name): ?ExpressionParserInterface 86 { 87 return $this->parsersByName[$interface][$name] ?? null; 88 } 89 90 public function getIterator(): \Traversable 91 { 92 $seen = []; 93 foreach ($this->parsersByName as $parsers) { 94 foreach ($parsers as $parser) { 95 $id = spl_object_id($parser); 96 if (!isset($seen[$id])) { 97 $seen[$id] = true; 98 yield $parser; 99 } 100 } 101 } 102 foreach ($this->parsersByClass as $parser) { 103 $id = spl_object_id($parser); 104 if (!isset($seen[$id])) { 105 $seen[$id] = true; 106 yield $parser; 107 } 108 } 109 } 110 111 /** 112 * @internal 113 * 114 * @return \WeakMap<ExpressionParserInterface, array<ExpressionParserInterface>> 115 */ 116 public function getPrecedenceChanges(): \WeakMap 117 { 118 if (null === $this->precedenceChanges) { 119 $this->precedenceChanges = new \WeakMap(); 120 foreach ($this as $ep) { 121 if (!$ep->getPrecedenceChange()) { 122 continue; 123 } 124 $min = min($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence()); 125 $max = max($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence()); 126 foreach ($this as $e) { 127 if ($e->getPrecedence() > $min && $e->getPrecedence() < $max) { 128 if (!isset($this->precedenceChanges[$e])) { 129 $this->precedenceChanges[$e] = []; 130 } 131 $this->precedenceChanges[$e][] = $ep; 132 } 133 } 134 } 135 } 136 137 return $this->precedenceChanges; 138 } 139 140 /** 141 * @internal 142 * 143 * @return array<string> 144 */ 145 public static function getOperatorTokensFor(ExpressionParserInterface $parser): array 146 { 147 if (method_exists($parser, 'getOperatorTokens')) { 148 return $parser->getOperatorTokens(); 149 } 150 151 trigger_deprecation('twig/twig', '3.24', 'Not implementing the "getOperatorTokens()" method in "%s" is deprecated. This method will be part of the "%s" interface in 4.0.', $parser::class, ExpressionParserInterface::class); 152 153 return [$parser->getName(), ...$parser->getAliases()]; 154 } 155} 156