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\Infix;
13
14use Twig\Attribute\FirstClassTwigCallableReady;
15use Twig\ExpressionParser\AbstractExpressionParser;
16use Twig\ExpressionParser\ExpressionParserDescriptionInterface;
17use Twig\ExpressionParser\InfixAssociativity;
18use Twig\ExpressionParser\InfixExpressionParserInterface;
19use Twig\Node\Expression\AbstractExpression;
20use Twig\Node\Expression\ArrayExpression;
21use Twig\Node\Expression\MacroReferenceExpression;
22use Twig\Node\Expression\NameExpression;
23use Twig\Node\Nodes;
24use Twig\Parser;
25use Twig\Token;
26use Twig\TwigTest;
27
28/**
29 * @internal
30 */
31class IsExpressionParser extends AbstractExpressionParser implements InfixExpressionParserInterface, ExpressionParserDescriptionInterface
32{
33    use ArgumentsTrait;
34
35    private $readyNodes = [];
36
37    public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
38    {
39        $stream = $parser->getStream();
40        $test = $parser->getTest($token->getLine());
41
42        $arguments = null;
43        if ($stream->test(Token::OPERATOR_TYPE, '(')) {
44            $arguments = $this->parseNamedArguments($parser);
45        } elseif ($test->hasOneMandatoryArgument()) {
46            $arguments = new Nodes([0 => $parser->parseExpression($this->getPrecedence())]);
47        }
48
49        if ('defined' === $test->getName() && $expr instanceof NameExpression && null !== $alias = $parser->getImportedSymbol('function', $expr->getAttribute('name'))) {
50            $expr = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], new ArrayExpression([], $expr->getTemplateLine()), $expr->getTemplateLine());
51        }
52
53        $ready = $test instanceof TwigTest;
54        if (!isset($this->readyNodes[$class = $test->getNodeClass()])) {
55            $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class);
56        }
57
58        if (!$ready = $this->readyNodes[$class]) {
59            trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class);
60        }
61
62        return new $class($expr, $ready ? $test : $test->getName(), $arguments, $stream->getCurrent()->getLine());
63    }
64
65    public function getPrecedence(): int
66    {
67        return 100;
68    }
69
70    public function getName(): string
71    {
72        return 'is';
73    }
74
75    public function getDescription(): string
76    {
77        return 'Twig tests';
78    }
79
80    public function getAssociativity(): InfixAssociativity
81    {
82        return InfixAssociativity::Left;
83    }
84}
85