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\Compiler; 15use Twig\Error\SyntaxError; 16use Twig\Node\CoercesChildrenToStringInterface; 17use Twig\Node\Expression\Unary\SpreadUnary; 18use Twig\Node\Expression\Unary\StringCastUnary; 19 20class ArrayExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnArrayInterface, CoercesChildrenToStringInterface 21{ 22 use SupportDefinedTestTrait; 23 24 private $index; 25 26 public function __construct(array $elements, int $lineno) 27 { 28 parent::__construct($elements, [], $lineno); 29 30 $this->index = -1; 31 foreach ($this->getKeyValuePairs() as $pair) { 32 if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { 33 $this->index = $pair['key']->getAttribute('value'); 34 } 35 } 36 } 37 38 public function getKeyValuePairs(): array 39 { 40 $pairs = []; 41 foreach (array_chunk($this->nodes, 2) as $pair) { 42 $pairs[] = [ 43 'key' => $pair[0], 44 'value' => $pair[1], 45 ]; 46 } 47 48 return $pairs; 49 } 50 51 public function hasElement(AbstractExpression $key): bool 52 { 53 foreach ($this->getKeyValuePairs() as $pair) { 54 // we compare the string representation of the keys 55 // to avoid comparing the line numbers which are not relevant here. 56 if ((string) $key === (string) $pair['key']) { 57 return true; 58 } 59 } 60 61 return false; 62 } 63 64 /** 65 * Checks if the array is a sequence (keys are sequential integers starting from 0). 66 * 67 * @internal 68 */ 69 public function isSequence(): bool 70 { 71 foreach ($this->getKeyValuePairs() as $i => $pair) { 72 $key = $pair['key']; 73 if ($key instanceof TempNameExpression) { 74 $keyValue = $key->getAttribute('name'); 75 } elseif ($key instanceof ConstantExpression) { 76 $keyValue = $key->getAttribute('value'); 77 } else { 78 return false; 79 } 80 81 if ($keyValue !== $i) { 82 return false; 83 } 84 } 85 86 return true; 87 } 88 89 public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void 90 { 91 if (null === $key) { 92 $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); 93 } 94 95 array_push($this->nodes, $key, $value); 96 } 97 98 public function getStringCoercedChildNames(): array 99 { 100 // dynamic mapping keys (computed at runtime) are coerced to string; 101 // static keys (constants or sequence indexes) are emitted as PHP 102 // literals by compile() and never trigger a __toString() call 103 $names = []; 104 foreach (array_chunk($this->nodes, 2) as $i => $pair) { 105 $key = $pair[0]; 106 if ($key instanceof ConstantExpression || $key instanceof TempNameExpression) { 107 continue; 108 } 109 110 $names[] = (string) ($i * 2); 111 } 112 113 return $names; 114 } 115 116 public function compile(Compiler $compiler): void 117 { 118 if ($this->definedTest) { 119 $compiler->repr(true); 120 121 return; 122 } 123 124 // Check for empty expressions which are only allowed in destructuring 125 foreach ($this->getKeyValuePairs() as $pair) { 126 if ($pair['value'] instanceof EmptyExpression) { 127 throw new SyntaxError('Empty array elements are only allowed in destructuring assignments.', $pair['value']->getTemplateLine(), $this->getSourceContext()); 128 } 129 } 130 131 $compiler->raw('['); 132 $isSequence = true; 133 foreach ($this->getKeyValuePairs() as $i => $pair) { 134 if (0 !== $i) { 135 $compiler->raw(', '); 136 } 137 138 $key = null; 139 if ($pair['key'] instanceof TempNameExpression) { 140 $key = $pair['key']->getAttribute('name'); 141 $pair['key'] = new ConstantExpression($key, $pair['key']->getTemplateLine()); 142 } elseif ($pair['key'] instanceof ConstantExpression) { 143 $key = $pair['key']->getAttribute('value'); 144 } else { 145 // dynamic key: cast to string so PHP accepts it as an array offset 146 // (the sandbox visitor has already wrapped it with a __toString policy check) 147 $pair['key'] = new StringCastUnary($pair['key'], $pair['key']->getTemplateLine()); 148 } 149 150 if ($key !== $i) { 151 $isSequence = false; 152 } 153 154 if (!$isSequence && !$pair['value'] instanceof SpreadUnary) { 155 $compiler 156 ->subcompile($pair['key']) 157 ->raw(' => ') 158 ; 159 } 160 161 $compiler->subcompile($pair['value']); 162 } 163 $compiler->raw(']'); 164 } 165} 166