Complex classes like Twig_ExpressionParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Twig_ExpressionParser, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 23 | class Twig_ExpressionParser |
||
| 24 | { |
||
| 25 | const OPERATOR_LEFT = 1; |
||
| 26 | const OPERATOR_RIGHT = 2; |
||
| 27 | |||
| 28 | protected $parser; |
||
| 29 | protected $unaryOperators; |
||
| 30 | protected $binaryOperators; |
||
| 31 | |||
| 32 | public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) |
||
| 33 | { |
||
| 34 | $this->parser = $parser; |
||
| 35 | $this->unaryOperators = $unaryOperators; |
||
| 36 | $this->binaryOperators = $binaryOperators; |
||
| 37 | } |
||
| 38 | |||
| 39 | public function parseExpression($precedence = 0) |
||
| 40 | { |
||
| 41 | $expr = $this->getPrimary(); |
||
| 42 | $token = $this->parser->getCurrentToken(); |
||
| 43 | while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { |
||
| 44 | $op = $this->binaryOperators[$token->getValue()]; |
||
| 45 | $this->parser->getStream()->next(); |
||
| 46 | |||
| 47 | if (isset($op['callable'])) { |
||
| 48 | $expr = call_user_func($op['callable'], $this->parser, $expr); |
||
| 49 | } else { |
||
| 50 | $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); |
||
| 51 | $class = $op['class']; |
||
| 52 | $expr = new $class($expr, $expr1, $token->getLine()); |
||
| 53 | } |
||
| 54 | |||
| 55 | $token = $this->parser->getCurrentToken(); |
||
| 56 | } |
||
| 57 | |||
| 58 | if (0 === $precedence) { |
||
| 59 | return $this->parseConditionalExpression($expr); |
||
| 60 | } |
||
| 61 | |||
| 62 | return $expr; |
||
| 63 | } |
||
| 64 | |||
| 65 | protected function getPrimary() |
||
|
|
|||
| 66 | { |
||
| 67 | $token = $this->parser->getCurrentToken(); |
||
| 68 | |||
| 69 | if ($this->isUnary($token)) { |
||
| 70 | $operator = $this->unaryOperators[$token->getValue()]; |
||
| 71 | $this->parser->getStream()->next(); |
||
| 72 | $expr = $this->parseExpression($operator['precedence']); |
||
| 73 | $class = $operator['class']; |
||
| 74 | |||
| 75 | return $this->parsePostfixExpression(new $class($expr, $token->getLine())); |
||
| 76 | } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
||
| 77 | $this->parser->getStream()->next(); |
||
| 78 | $expr = $this->parseExpression(); |
||
| 79 | $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); |
||
| 80 | |||
| 81 | return $this->parsePostfixExpression($expr); |
||
| 82 | } |
||
| 83 | |||
| 84 | return $this->parsePrimaryExpression(); |
||
| 85 | } |
||
| 86 | |||
| 87 | protected function parseConditionalExpression($expr) |
||
| 107 | |||
| 108 | protected function isUnary(Twig_Token $token) |
||
| 112 | |||
| 113 | protected function isBinary(Twig_Token $token) |
||
| 117 | |||
| 118 | public function parsePrimaryExpression() |
||
| 119 | { |
||
| 120 | $token = $this->parser->getCurrentToken(); |
||
| 121 | switch ($token->getType()) { |
||
| 122 | case Twig_Token::NAME_TYPE: |
||
| 123 | $this->parser->getStream()->next(); |
||
| 124 | switch ($token->getValue()) { |
||
| 125 | case 'true': |
||
| 126 | case 'TRUE': |
||
| 127 | $node = new Twig_Node_Expression_Constant(true, $token->getLine()); |
||
| 128 | break; |
||
| 129 | |||
| 130 | case 'false': |
||
| 131 | case 'FALSE': |
||
| 132 | $node = new Twig_Node_Expression_Constant(false, $token->getLine()); |
||
| 133 | break; |
||
| 134 | |||
| 135 | case 'none': |
||
| 136 | case 'NONE': |
||
| 137 | case 'null': |
||
| 138 | case 'NULL': |
||
| 139 | $node = new Twig_Node_Expression_Constant(null, $token->getLine()); |
||
| 140 | break; |
||
| 141 | |||
| 142 | default: |
||
| 143 | if ('(' === $this->parser->getCurrentToken()->getValue()) { |
||
| 144 | $node = $this->getFunctionNode($token->getValue(), $token->getLine()); |
||
| 145 | } else { |
||
| 146 | $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); |
||
| 147 | } |
||
| 148 | } |
||
| 149 | break; |
||
| 150 | |||
| 151 | case Twig_Token::NUMBER_TYPE: |
||
| 152 | $this->parser->getStream()->next(); |
||
| 153 | $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
||
| 154 | break; |
||
| 155 | |||
| 156 | case Twig_Token::STRING_TYPE: |
||
| 157 | case Twig_Token::INTERPOLATION_START_TYPE: |
||
| 158 | $node = $this->parseStringExpression(); |
||
| 159 | break; |
||
| 160 | |||
| 161 | case Twig_Token::OPERATOR_TYPE: |
||
| 162 | if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { |
||
| 163 | // in this context, string operators are variable names |
||
| 164 | $this->parser->getStream()->next(); |
||
| 165 | $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); |
||
| 166 | break; |
||
| 167 | } elseif (isset($this->unaryOperators[$token->getValue()])) { |
||
| 168 | $class = $this->unaryOperators[$token->getValue()]['class']; |
||
| 169 | |||
| 170 | $ref = new ReflectionClass($class); |
||
| 171 | $negClass = 'Twig_Node_Expression_Unary_Neg'; |
||
| 172 | $posClass = 'Twig_Node_Expression_Unary_Pos'; |
||
| 173 | if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) { |
||
| 174 | throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename()); |
||
| 175 | } |
||
| 176 | |||
| 177 | $this->parser->getStream()->next(); |
||
| 178 | $expr = $this->parsePrimaryExpression(); |
||
| 179 | |||
| 180 | $node = new $class($expr, $token->getLine()); |
||
| 181 | break; |
||
| 182 | } |
||
| 183 | |||
| 184 | default: |
||
| 185 | if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { |
||
| 186 | $node = $this->parseArrayExpression(); |
||
| 187 | } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { |
||
| 188 | $node = $this->parseHashExpression(); |
||
| 189 | } else { |
||
| 190 | throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getFilename()); |
||
| 191 | } |
||
| 192 | } |
||
| 193 | |||
| 194 | return $this->parsePostfixExpression($node); |
||
| 195 | } |
||
| 196 | |||
| 197 | public function parseStringExpression() |
||
| 198 | { |
||
| 199 | $stream = $this->parser->getStream(); |
||
| 200 | |||
| 201 | $nodes = array(); |
||
| 202 | // a string cannot be followed by another string in a single expression |
||
| 203 | $nextCanBeString = true; |
||
| 204 | while (true) { |
||
| 205 | if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) { |
||
| 206 | $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
||
| 207 | $nextCanBeString = false; |
||
| 208 | } elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) { |
||
| 209 | $nodes[] = $this->parseExpression(); |
||
| 210 | $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); |
||
| 211 | $nextCanBeString = true; |
||
| 212 | } else { |
||
| 213 | break; |
||
| 214 | } |
||
| 215 | } |
||
| 216 | |||
| 217 | $expr = array_shift($nodes); |
||
| 218 | foreach ($nodes as $node) { |
||
| 219 | $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine()); |
||
| 220 | } |
||
| 221 | |||
| 222 | return $expr; |
||
| 223 | } |
||
| 224 | |||
| 225 | public function parseArrayExpression() |
||
| 226 | { |
||
| 227 | $stream = $this->parser->getStream(); |
||
| 228 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); |
||
| 229 | |||
| 230 | $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine()); |
||
| 231 | $first = true; |
||
| 232 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
||
| 233 | if (!$first) { |
||
| 234 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); |
||
| 235 | |||
| 236 | // trailing ,? |
||
| 237 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
||
| 238 | break; |
||
| 239 | } |
||
| 240 | } |
||
| 241 | $first = false; |
||
| 242 | |||
| 243 | $node->addElement($this->parseExpression()); |
||
| 244 | } |
||
| 245 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); |
||
| 246 | |||
| 247 | return $node; |
||
| 248 | } |
||
| 249 | |||
| 250 | public function parseHashExpression() |
||
| 251 | { |
||
| 252 | $stream = $this->parser->getStream(); |
||
| 253 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); |
||
| 254 | |||
| 255 | $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine()); |
||
| 256 | $first = true; |
||
| 257 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { |
||
| 258 | if (!$first) { |
||
| 259 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); |
||
| 260 | |||
| 261 | // trailing ,? |
||
| 262 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { |
||
| 263 | break; |
||
| 264 | } |
||
| 265 | } |
||
| 266 | $first = false; |
||
| 267 | |||
| 268 | // a hash key can be: |
||
| 269 | // |
||
| 270 | // * a number -- 12 |
||
| 271 | // * a string -- 'a' |
||
| 272 | // * a name, which is equivalent to a string -- a |
||
| 273 | // * an expression, which must be enclosed in parentheses -- (1 + 2) |
||
| 274 | if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) { |
||
| 275 | $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
||
| 276 | } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
||
| 277 | $key = $this->parseExpression(); |
||
| 278 | } else { |
||
| 279 | $current = $stream->getCurrent(); |
||
| 280 | |||
| 281 | throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $this->parser->getFilename()); |
||
| 282 | } |
||
| 283 | |||
| 284 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); |
||
| 285 | $value = $this->parseExpression(); |
||
| 286 | |||
| 287 | $node->addElement($value, $key); |
||
| 288 | } |
||
| 289 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); |
||
| 290 | |||
| 291 | return $node; |
||
| 292 | } |
||
| 293 | |||
| 294 | public function parsePostfixExpression($node) |
||
| 295 | { |
||
| 296 | while (true) { |
||
| 297 | $token = $this->parser->getCurrentToken(); |
||
| 298 | if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { |
||
| 299 | if ('.' == $token->getValue() || '[' == $token->getValue()) { |
||
| 300 | $node = $this->parseSubscriptExpression($node); |
||
| 301 | } elseif ('|' == $token->getValue()) { |
||
| 302 | $node = $this->parseFilterExpression($node); |
||
| 303 | } else { |
||
| 304 | break; |
||
| 305 | } |
||
| 306 | } else { |
||
| 307 | break; |
||
| 308 | } |
||
| 309 | } |
||
| 310 | |||
| 311 | return $node; |
||
| 312 | } |
||
| 313 | |||
| 314 | public function getFunctionNode($name, $line) |
||
| 315 | { |
||
| 316 | switch ($name) { |
||
| 317 | case 'parent': |
||
| 318 | $args = $this->parseArguments(); |
||
| 319 | if (!count($this->parser->getBlockStack())) { |
||
| 320 | throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename()); |
||
| 321 | } |
||
| 322 | |||
| 323 | if (!$this->parser->getParent() && !$this->parser->hasTraits()) { |
||
| 324 | throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename()); |
||
| 325 | } |
||
| 326 | |||
| 327 | return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); |
||
| 328 | case 'block': |
||
| 329 | return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line); |
||
| 330 | case 'attribute': |
||
| 331 | $args = $this->parseArguments(); |
||
| 332 | if (count($args) < 2) { |
||
| 333 | throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename()); |
||
| 334 | } |
||
| 335 | |||
| 336 | return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line); |
||
| 337 | default: |
||
| 338 | if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { |
||
| 339 | $arguments = new Twig_Node_Expression_Array(array(), $line); |
||
| 340 | foreach ($this->parseArguments() as $n) { |
||
| 341 | $arguments->addElement($n); |
||
| 342 | } |
||
| 343 | |||
| 344 | $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line); |
||
| 345 | $node->setAttribute('safe', true); |
||
| 346 | |||
| 347 | return $node; |
||
| 348 | } |
||
| 349 | |||
| 350 | $args = $this->parseArguments(true); |
||
| 351 | $class = $this->getFunctionNodeClass($name, $line); |
||
| 352 | |||
| 353 | return new $class($name, $args, $line); |
||
| 354 | } |
||
| 355 | } |
||
| 356 | |||
| 357 | public function parseSubscriptExpression($node) |
||
| 358 | { |
||
| 359 | $stream = $this->parser->getStream(); |
||
| 360 | $token = $stream->next(); |
||
| 361 | $lineno = $token->getLine(); |
||
| 362 | $arguments = new Twig_Node_Expression_Array(array(), $lineno); |
||
| 363 | $type = Twig_Template::ANY_CALL; |
||
| 364 | if ($token->getValue() == '.') { |
||
| 365 | $token = $stream->next(); |
||
| 366 | if ( |
||
| 367 | $token->getType() == Twig_Token::NAME_TYPE |
||
| 368 | || |
||
| 369 | $token->getType() == Twig_Token::NUMBER_TYPE |
||
| 370 | || |
||
| 371 | ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) |
||
| 372 | ) { |
||
| 373 | $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); |
||
| 374 | |||
| 375 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
||
| 376 | $type = Twig_TemplateInterface::METHOD_CALL; |
||
| 377 | foreach ($this->parseArguments() as $n) { |
||
| 378 | $arguments->addElement($n); |
||
| 379 | } |
||
| 380 | } |
||
| 381 | } else { |
||
| 382 | throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename()); |
||
| 383 | } |
||
| 384 | |||
| 385 | if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { |
||
| 386 | if (!$arg instanceof Twig_Node_Expression_Constant) { |
||
| 387 | throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename()); |
||
| 388 | } |
||
| 389 | |||
| 390 | $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno); |
||
| 391 | $node->setAttribute('safe', true); |
||
| 392 | |||
| 393 | return $node; |
||
| 394 | } |
||
| 395 | } else { |
||
| 396 | $type = Twig_Template::ARRAY_CALL; |
||
| 397 | |||
| 398 | // slice? |
||
| 399 | $slice = false; |
||
| 400 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) { |
||
| 401 | $slice = true; |
||
| 402 | $arg = new Twig_Node_Expression_Constant(0, $token->getLine()); |
||
| 403 | } else { |
||
| 404 | $arg = $this->parseExpression(); |
||
| 405 | } |
||
| 406 | |||
| 407 | if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { |
||
| 408 | $slice = true; |
||
| 409 | } |
||
| 410 | |||
| 411 | if ($slice) { |
||
| 412 | if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
||
| 413 | $length = new Twig_Node_Expression_Constant(null, $token->getLine()); |
||
| 414 | } else { |
||
| 415 | $length = $this->parseExpression(); |
||
| 416 | } |
||
| 417 | |||
| 418 | $class = $this->getFilterNodeClass('slice', $token->getLine()); |
||
| 419 | $arguments = new Twig_Node(array($arg, $length)); |
||
| 420 | $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine()); |
||
| 421 | |||
| 422 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); |
||
| 423 | |||
| 424 | return $filter; |
||
| 425 | } |
||
| 426 | |||
| 427 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']'); |
||
| 428 | } |
||
| 429 | |||
| 430 | return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); |
||
| 431 | } |
||
| 432 | |||
| 433 | public function parseFilterExpression($node) |
||
| 434 | { |
||
| 435 | $this->parser->getStream()->next(); |
||
| 436 | |||
| 437 | return $this->parseFilterExpressionRaw($node); |
||
| 438 | } |
||
| 439 | |||
| 440 | public function parseFilterExpressionRaw($node, $tag = null) |
||
| 441 | { |
||
| 442 | while (true) { |
||
| 443 | $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); |
||
| 444 | |||
| 445 | $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
||
| 446 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
||
| 447 | $arguments = new Twig_Node(); |
||
| 448 | } else { |
||
| 449 | $arguments = $this->parseArguments(true); |
||
| 450 | } |
||
| 451 | |||
| 452 | $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); |
||
| 453 | |||
| 454 | $node = new $class($node, $name, $arguments, $token->getLine(), $tag); |
||
| 455 | |||
| 456 | if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { |
||
| 457 | break; |
||
| 458 | } |
||
| 459 | |||
| 460 | $this->parser->getStream()->next(); |
||
| 461 | } |
||
| 462 | |||
| 463 | return $node; |
||
| 464 | } |
||
| 465 | |||
| 466 | /** |
||
| 467 | * Parses arguments. |
||
| 468 | * |
||
| 469 | * @param bool $namedArguments Whether to allow named arguments or not |
||
| 470 | * @param bool $definition Whether we are parsing arguments for a function definition |
||
| 471 | */ |
||
| 472 | public function parseArguments($namedArguments = false, $definition = false) |
||
| 473 | { |
||
| 474 | $args = array(); |
||
| 475 | $stream = $this->parser->getStream(); |
||
| 476 | |||
| 477 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); |
||
| 478 | while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { |
||
| 479 | if (!empty($args)) { |
||
| 480 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); |
||
| 481 | } |
||
| 482 | |||
| 483 | if ($definition) { |
||
| 484 | $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name'); |
||
| 485 | $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine()); |
||
| 486 | } else { |
||
| 487 | $value = $this->parseExpression(); |
||
| 488 | } |
||
| 489 | |||
| 490 | $name = null; |
||
| 491 | if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { |
||
| 492 | if (!$value instanceof Twig_Node_Expression_Name) { |
||
| 493 | throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename()); |
||
| 494 | } |
||
| 495 | $name = $value->getAttribute('name'); |
||
| 496 | |||
| 497 | if ($definition) { |
||
| 498 | $value = $this->parsePrimaryExpression(); |
||
| 499 | |||
| 500 | if (!$this->checkConstantExpression($value)) { |
||
| 501 | throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename()); |
||
| 502 | } |
||
| 503 | } else { |
||
| 504 | $value = $this->parseExpression(); |
||
| 505 | } |
||
| 506 | } |
||
| 507 | |||
| 508 | if ($definition) { |
||
| 509 | if (null === $name) { |
||
| 510 | $name = $value->getAttribute('name'); |
||
| 511 | $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine()); |
||
| 512 | } |
||
| 513 | $args[$name] = $value; |
||
| 514 | } else { |
||
| 515 | if (null === $name) { |
||
| 516 | $args[] = $value; |
||
| 517 | } else { |
||
| 518 | $args[$name] = $value; |
||
| 519 | } |
||
| 520 | } |
||
| 521 | } |
||
| 522 | $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); |
||
| 523 | |||
| 524 | return new Twig_Node($args); |
||
| 525 | } |
||
| 526 | |||
| 527 | public function parseAssignmentExpression() |
||
| 544 | |||
| 545 | public function parseMultitargetExpression() |
||
| 546 | { |
||
| 547 | $targets = array(); |
||
| 548 | while (true) { |
||
| 549 | $targets[] = $this->parseExpression(); |
||
| 550 | if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { |
||
| 551 | break; |
||
| 552 | } |
||
| 553 | } |
||
| 554 | |||
| 555 | return new Twig_Node($targets); |
||
| 556 | } |
||
| 557 | |||
| 558 | protected function getFunctionNodeClass($name, $line) |
||
| 577 | |||
| 578 | protected function getFilterNodeClass($name, $line) |
||
| 597 | |||
| 598 | // checks that the node only contains "constant" elements |
||
| 599 | protected function checkConstantExpression(Twig_NodeInterface $node) |
||
| 615 | } |
||
| 616 |
Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a
@returnannotation as described here.