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\Node\Expression;
13
14use Twig\Attribute\FirstClassTwigCallableReady;
15use Twig\Compiler;
16use Twig\Node\CoercesChildrenToStringInterface;
17use Twig\Node\NameDeprecation;
18use Twig\Node\Node;
19use Twig\TwigTest;
20
21class TestExpression extends CallExpression implements ReturnBoolInterface, CoercesChildrenToStringInterface
22{
23    #[FirstClassTwigCallableReady]
24    /**
25     * @param AbstractExpression $node
26     */
27    public function __construct(Node $node, string|TwigTest $test, ?Node $arguments, int $lineno)
28    {
29        if (!$node instanceof AbstractExpression) {
30            trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $node::class);
31        }
32
33        $nodes = ['node' => $node];
34        if (null !== $arguments) {
35            $nodes['arguments'] = $arguments;
36        }
37
38        if ($test instanceof TwigTest) {
39            $name = $test->getName();
40        } else {
41            $name = $test;
42            trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigTest" when creating a "%s" test of type "%s" is deprecated.', $name, static::class);
43        }
44
45        parent::__construct($nodes, ['name' => $name, 'type' => 'test'], $lineno);
46
47        if ($test instanceof TwigTest) {
48            $this->setAttribute('twig_callable', $test);
49        }
50
51        $this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));
52        $this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));
53        $this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));
54        $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));
55    }
56
57    public function compile(Compiler $compiler): void
58    {
59        $name = $this->getAttribute('name');
60        if ($this->hasAttribute('twig_callable')) {
61            $name = $this->getAttribute('twig_callable')->getName();
62            if ($name !== $this->getAttribute('name')) {
63                trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "test" node in a NodeVisitor class is not supported anymore.');
64                $this->removeAttribute('twig_callable');
65            }
66        }
67
68        if (!$this->hasAttribute('twig_callable')) {
69            $this->setAttribute('twig_callable', $compiler->getEnvironment()->getTest($this->getAttribute('name')));
70        }
71
72        $this->compileCallable($compiler);
73    }
74
75    public function getStringCoercedChildNames(): array
76    {
77        $names = [];
78
79        // the `empty` test triggers an implicit string coercion through `CoreExtension::testEmpty()`
80        if ('empty' === $this->getAttribute('name')) {
81            $names[] = 'node';
82        }
83
84        // a test may coerce its arguments to string (the host PHP code is opaque to Twig)
85        if ($this->hasNode('arguments')) {
86            $names[] = 'arguments';
87        }
88
89        return $names;
90    }
91}
92