vendor/twig/twig/src/ExtensionSet.php line 154

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  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 Twig;
  11. use Twig\Error\RuntimeError;
  12. use Twig\Extension\ExtensionInterface;
  13. use Twig\Extension\GlobalsInterface;
  14. use Twig\Extension\StagingExtension;
  15. use Twig\NodeVisitor\NodeVisitorInterface;
  16. use Twig\TokenParser\TokenParserInterface;
  17. /**
  18.  * @author Fabien Potencier <fabien@symfony.com>
  19.  *
  20.  * @internal
  21.  */
  22. final class ExtensionSet
  23. {
  24.     private $extensions;
  25.     private $initialized false;
  26.     private $runtimeInitialized false;
  27.     private $staging;
  28.     private $parsers;
  29.     private $visitors;
  30.     private $filters;
  31.     private $tests;
  32.     private $functions;
  33.     private $unaryOperators;
  34.     private $binaryOperators;
  35.     private $globals;
  36.     private $functionCallbacks = [];
  37.     private $filterCallbacks = [];
  38.     private $parserCallbacks = [];
  39.     private $lastModified 0;
  40.     public function __construct()
  41.     {
  42.         $this->staging = new StagingExtension();
  43.     }
  44.     public function initRuntime()
  45.     {
  46.         $this->runtimeInitialized true;
  47.     }
  48.     public function hasExtension(string $class): bool
  49.     {
  50.         return isset($this->extensions[ltrim($class'\\')]);
  51.     }
  52.     public function getExtension(string $class): ExtensionInterface
  53.     {
  54.         $class ltrim($class'\\');
  55.         if (!isset($this->extensions[$class])) {
  56.             throw new RuntimeError(sprintf('The "%s" extension is not enabled.'$class));
  57.         }
  58.         return $this->extensions[$class];
  59.     }
  60.     /**
  61.      * @param ExtensionInterface[] $extensions
  62.      */
  63.     public function setExtensions(array $extensions): void
  64.     {
  65.         foreach ($extensions as $extension) {
  66.             $this->addExtension($extension);
  67.         }
  68.     }
  69.     /**
  70.      * @return ExtensionInterface[]
  71.      */
  72.     public function getExtensions(): array
  73.     {
  74.         return $this->extensions;
  75.     }
  76.     public function getSignature(): string
  77.     {
  78.         return json_encode(array_keys($this->extensions));
  79.     }
  80.     public function isInitialized(): bool
  81.     {
  82.         return $this->initialized || $this->runtimeInitialized;
  83.     }
  84.     public function getLastModified(): int
  85.     {
  86.         if (!== $this->lastModified) {
  87.             return $this->lastModified;
  88.         }
  89.         foreach ($this->extensions as $extension) {
  90.             $r = new \ReflectionObject($extension);
  91.             if (is_file($r->getFileName()) && ($extensionTime filemtime($r->getFileName())) > $this->lastModified) {
  92.                 $this->lastModified $extensionTime;
  93.             }
  94.         }
  95.         return $this->lastModified;
  96.     }
  97.     public function addExtension(ExtensionInterface $extension): void
  98.     {
  99.         $class \get_class($extension);
  100.         if ($this->initialized) {
  101.             throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.'$class));
  102.         }
  103.         if (isset($this->extensions[$class])) {
  104.             throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.'$class));
  105.         }
  106.         $this->extensions[$class] = $extension;
  107.     }
  108.     public function addFunction(TwigFunction $function): void
  109.     {
  110.         if ($this->initialized) {
  111.             throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.'$function->getName()));
  112.         }
  113.         $this->staging->addFunction($function);
  114.     }
  115.     /**
  116.      * @return TwigFunction[]
  117.      */
  118.     public function getFunctions(): array
  119.     {
  120.         if (!$this->initialized) {
  121.             $this->initExtensions();
  122.         }
  123.         return $this->functions;
  124.     }
  125.     public function getFunction(string $name): ?TwigFunction
  126.     {
  127.         if (!$this->initialized) {
  128.             $this->initExtensions();
  129.         }
  130.         if (isset($this->functions[$name])) {
  131.             return $this->functions[$name];
  132.         }
  133.         foreach ($this->functions as $pattern => $function) {
  134.             $pattern str_replace('\\*''(.*?)'preg_quote($pattern'#'), $count);
  135.             if ($count && preg_match('#^'.$pattern.'$#'$name$matches)) {
  136.                 array_shift($matches);
  137.                 $function->setArguments($matches);
  138.                 return $function;
  139.             }
  140.         }
  141.         foreach ($this->functionCallbacks as $callback) {
  142.             if (false !== $function $callback($name)) {
  143.                 return $function;
  144.             }
  145.         }
  146.         return null;
  147.     }
  148.     public function registerUndefinedFunctionCallback(callable $callable): void
  149.     {
  150.         $this->functionCallbacks[] = $callable;
  151.     }
  152.     public function addFilter(TwigFilter $filter): void
  153.     {
  154.         if ($this->initialized) {
  155.             throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.'$filter->getName()));
  156.         }
  157.         $this->staging->addFilter($filter);
  158.     }
  159.     /**
  160.      * @return TwigFilter[]
  161.      */
  162.     public function getFilters(): array
  163.     {
  164.         if (!$this->initialized) {
  165.             $this->initExtensions();
  166.         }
  167.         return $this->filters;
  168.     }
  169.     public function getFilter(string $name): ?TwigFilter
  170.     {
  171.         if (!$this->initialized) {
  172.             $this->initExtensions();
  173.         }
  174.         if (isset($this->filters[$name])) {
  175.             return $this->filters[$name];
  176.         }
  177.         foreach ($this->filters as $pattern => $filter) {
  178.             $pattern str_replace('\\*''(.*?)'preg_quote($pattern'#'), $count);
  179.             if ($count && preg_match('#^'.$pattern.'$#'$name$matches)) {
  180.                 array_shift($matches);
  181.                 $filter->setArguments($matches);
  182.                 return $filter;
  183.             }
  184.         }
  185.         foreach ($this->filterCallbacks as $callback) {
  186.             if (false !== $filter $callback($name)) {
  187.                 return $filter;
  188.             }
  189.         }
  190.         return null;
  191.     }
  192.     public function registerUndefinedFilterCallback(callable $callable): void
  193.     {
  194.         $this->filterCallbacks[] = $callable;
  195.     }
  196.     public function addNodeVisitor(NodeVisitorInterface $visitor): void
  197.     {
  198.         if ($this->initialized) {
  199.             throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.');
  200.         }
  201.         $this->staging->addNodeVisitor($visitor);
  202.     }
  203.     /**
  204.      * @return NodeVisitorInterface[]
  205.      */
  206.     public function getNodeVisitors(): array
  207.     {
  208.         if (!$this->initialized) {
  209.             $this->initExtensions();
  210.         }
  211.         return $this->visitors;
  212.     }
  213.     public function addTokenParser(TokenParserInterface $parser): void
  214.     {
  215.         if ($this->initialized) {
  216.             throw new \LogicException('Unable to add a token parser as extensions have already been initialized.');
  217.         }
  218.         $this->staging->addTokenParser($parser);
  219.     }
  220.     /**
  221.      * @return TokenParserInterface[]
  222.      */
  223.     public function getTokenParsers(): array
  224.     {
  225.         if (!$this->initialized) {
  226.             $this->initExtensions();
  227.         }
  228.         return $this->parsers;
  229.     }
  230.     public function getTokenParser(string $name): ?TokenParserInterface
  231.     {
  232.         if (!$this->initialized) {
  233.             $this->initExtensions();
  234.         }
  235.         if (isset($this->parsers[$name])) {
  236.             return $this->parsers[$name];
  237.         }
  238.         foreach ($this->parserCallbacks as $callback) {
  239.             if (false !== $parser $callback($name)) {
  240.                 return $parser;
  241.             }
  242.         }
  243.         return null;
  244.     }
  245.     public function registerUndefinedTokenParserCallback(callable $callable): void
  246.     {
  247.         $this->parserCallbacks[] = $callable;
  248.     }
  249.     public function getGlobals(): array
  250.     {
  251.         if (null !== $this->globals) {
  252.             return $this->globals;
  253.         }
  254.         $globals = [];
  255.         foreach ($this->extensions as $extension) {
  256.             if (!$extension instanceof GlobalsInterface) {
  257.                 continue;
  258.             }
  259.             $extGlobals $extension->getGlobals();
  260.             if (!\is_array($extGlobals)) {
  261.                 throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.'\get_class($extension)));
  262.             }
  263.             $globals array_merge($globals$extGlobals);
  264.         }
  265.         if ($this->initialized) {
  266.             $this->globals $globals;
  267.         }
  268.         return $globals;
  269.     }
  270.     public function addTest(TwigTest $test): void
  271.     {
  272.         if ($this->initialized) {
  273.             throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.'$test->getName()));
  274.         }
  275.         $this->staging->addTest($test);
  276.     }
  277.     /**
  278.      * @return TwigTest[]
  279.      */
  280.     public function getTests(): array
  281.     {
  282.         if (!$this->initialized) {
  283.             $this->initExtensions();
  284.         }
  285.         return $this->tests;
  286.     }
  287.     public function getTest(string $name): ?TwigTest
  288.     {
  289.         if (!$this->initialized) {
  290.             $this->initExtensions();
  291.         }
  292.         if (isset($this->tests[$name])) {
  293.             return $this->tests[$name];
  294.         }
  295.         foreach ($this->tests as $pattern => $test) {
  296.             $pattern str_replace('\\*''(.*?)'preg_quote($pattern'#'), $count);
  297.             if ($count) {
  298.                 if (preg_match('#^'.$pattern.'$#'$name$matches)) {
  299.                     array_shift($matches);
  300.                     $test->setArguments($matches);
  301.                     return $test;
  302.                 }
  303.             }
  304.         }
  305.         return null;
  306.     }
  307.     public function getUnaryOperators(): array
  308.     {
  309.         if (!$this->initialized) {
  310.             $this->initExtensions();
  311.         }
  312.         return $this->unaryOperators;
  313.     }
  314.     public function getBinaryOperators(): array
  315.     {
  316.         if (!$this->initialized) {
  317.             $this->initExtensions();
  318.         }
  319.         return $this->binaryOperators;
  320.     }
  321.     private function initExtensions(): void
  322.     {
  323.         $this->parsers = [];
  324.         $this->filters = [];
  325.         $this->functions = [];
  326.         $this->tests = [];
  327.         $this->visitors = [];
  328.         $this->unaryOperators = [];
  329.         $this->binaryOperators = [];
  330.         foreach ($this->extensions as $extension) {
  331.             $this->initExtension($extension);
  332.         }
  333.         $this->initExtension($this->staging);
  334.         // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
  335.         $this->initialized true;
  336.     }
  337.     private function initExtension(ExtensionInterface $extension): void
  338.     {
  339.         // filters
  340.         foreach ($extension->getFilters() as $filter) {
  341.             $this->filters[$filter->getName()] = $filter;
  342.         }
  343.         // functions
  344.         foreach ($extension->getFunctions() as $function) {
  345.             $this->functions[$function->getName()] = $function;
  346.         }
  347.         // tests
  348.         foreach ($extension->getTests() as $test) {
  349.             $this->tests[$test->getName()] = $test;
  350.         }
  351.         // token parsers
  352.         foreach ($extension->getTokenParsers() as $parser) {
  353.             if (!$parser instanceof TokenParserInterface) {
  354.                 throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.');
  355.             }
  356.             $this->parsers[$parser->getTag()] = $parser;
  357.         }
  358.         // node visitors
  359.         foreach ($extension->getNodeVisitors() as $visitor) {
  360.             $this->visitors[] = $visitor;
  361.         }
  362.         // operators
  363.         if ($operators $extension->getOperators()) {
  364.             if (!\is_array($operators)) {
  365.                 throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".'\get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' '#'.$operators)));
  366.             }
  367.             if (!== \count($operators)) {
  368.                 throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.'\get_class($extension), \count($operators)));
  369.             }
  370.             $this->unaryOperators array_merge($this->unaryOperators$operators[0]);
  371.             $this->binaryOperators array_merge($this->binaryOperators$operators[1]);
  372.         }
  373.     }
  374. }