| Conditions | 74 |
| Paths | 230 |
| Total Lines | 309 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
| 1 | <?php |
||
| 374 | protected function sequenceInlineNodes(\Iterator $sequence, bool $allowArray = true): NodeInterface |
||
| 375 | { |
||
| 376 | $text = '{'; |
||
| 377 | $node = null; |
||
| 378 | $key = null; |
||
| 379 | $namespace = null; |
||
| 380 | $method = null; |
||
| 381 | $potentialAccessor = null; |
||
| 382 | $callDetected = false; |
||
| 383 | $hasPass = false; |
||
| 384 | $hasColon = null; |
||
| 385 | $hasWhitespace = false; |
||
| 386 | $isArray = false; |
||
| 387 | $array = []; |
||
| 388 | $arguments = []; |
||
| 389 | $ignoredEndingBraces = 0; |
||
| 390 | $countedEscapes = 0; |
||
| 391 | |||
| 392 | $this->splitter->switch($this->contexts->inline); |
||
| 393 | $sequence->next(); |
||
| 394 | foreach ($sequence as $symbol => $captured) { |
||
| 395 | $text .= $captured; |
||
| 396 | switch ($symbol) { |
||
| 397 | case Splitter::BYTE_BACKSLASH: |
||
| 398 | // Increase the number of counted escapes (is passed to sequenceNode() in the "QUOTE" cases and reset |
||
| 399 | // after the quoted string is extracted). |
||
| 400 | ++$countedEscapes; |
||
| 401 | break; |
||
| 402 | |||
| 403 | case Splitter::BYTE_ARRAY_START: |
||
| 404 | |||
| 405 | $text .= chr($symbol); |
||
| 406 | $isArray = $allowArray; |
||
| 407 | |||
| 408 | #ArrayStart: |
||
| 409 | // Sequence the node. Pass the "use numeric keys?" boolean based on the current byte. Only array |
||
| 410 | // start creates numeric keys. Inline start with keyless values creates ECMA style {foo:foo, bar:bar} |
||
| 411 | // from {foo, bar}. |
||
| 412 | $array[$key ?? $captured ?? 0] = $node = $this->sequenceArrayNode($sequence, null, $symbol === Splitter::BYTE_ARRAY_START); |
||
| 413 | $this->splitter->switch($this->contexts->inline); |
||
| 414 | unset($key); |
||
| 415 | break; |
||
| 416 | |||
| 417 | case Splitter::BYTE_INLINE: |
||
| 418 | // Encountering this case can mean different things: sub-syntax like {foo.{index}} or array, depending |
||
| 419 | // on presence of either a colon or comma before the inline. In protected mode it is simply added. |
||
| 420 | $text .= '{'; |
||
| 421 | if (!$hasWhitespace && $text !== '{{') { |
||
| 422 | // Most likely, a nested object accessor syntax e.g. {foo.{bar}} - enter protected context since |
||
| 423 | // these accessors do not allow anything other than additional nested accessors. |
||
| 424 | $this->splitter->switch($this->contexts->accessor); |
||
| 425 | ++$ignoredEndingBraces; |
||
| 426 | } elseif ($this->splitter->context->context === Context::CONTEXT_PROTECTED) { |
||
| 427 | // Ignore one ending additional curly brace. Subtracted in the BYTE_INLINE_END case below. |
||
| 428 | // The expression in this case looks like {{inline}.....} and we capture the curlies. |
||
| 429 | $potentialAccessor .= $captured; |
||
| 430 | ++$ignoredEndingBraces; |
||
| 431 | } elseif ($allowArray || $isArray) { |
||
| 432 | $isArray = true; |
||
| 433 | $captured = $key ?? $captured ?? $potentialAccessor; |
||
| 434 | // This is a sub-syntax following a colon - meaning it is an array. |
||
| 435 | if ($captured !== null) { |
||
| 436 | #goto ArrayStart; |
||
| 437 | $array[$key ?? $captured ?? 0] = $node = $this->sequenceArrayNode($sequence, null, $symbol === Splitter::BYTE_ARRAY_START); |
||
| 438 | $this->splitter->switch($this->contexts->inline); |
||
| 439 | } |
||
| 440 | } else { |
||
| 441 | $childNodeToAdd = $this->sequenceInlineNodes($sequence, $allowArray); |
||
| 442 | $node = isset($node) ? $node->addChildNode($childNodeToAdd) : (new RootNode())->addChildNode($childNodeToAdd); |
||
| 443 | } |
||
| 444 | break; |
||
| 445 | |||
| 446 | case Splitter::BYTE_MINUS: |
||
| 447 | $text .= '-'; |
||
| 448 | break; |
||
| 449 | |||
| 450 | // Backtick may be encountered in two different contexts: normal inline context, in which case it has |
||
| 451 | // the same meaning as any quote and causes sequencing of a quoted string. Or protected context, in |
||
| 452 | // which case it also sequences a quoted node but appends the result instead of assigning to array. |
||
| 453 | // Note that backticks do not support escapes (they are a new feature that does not require escaping). |
||
| 454 | case Splitter::BYTE_BACKTICK: |
||
| 455 | if ($this->splitter->context->context === Context::CONTEXT_PROTECTED) { |
||
| 456 | $node->addChildNode(new TextNode($text)); |
||
| 457 | $node->addChildNode($this->sequenceQuotedNode($sequence)->flatten()); |
||
| 458 | $text = ''; |
||
| 459 | break; |
||
| 460 | } |
||
| 461 | // Fallthrough is intentional: if not in protected context, consider the backtick a normal quote. |
||
| 462 | |||
| 463 | // Case not normally countered in straight up "inline" context, but when encountered, means we have |
||
| 464 | // explicitly found a quoted array key - and we extract it. |
||
| 465 | case Splitter::BYTE_QUOTE_SINGLE: |
||
| 466 | case Splitter::BYTE_QUOTE_DOUBLE: |
||
| 467 | if (!$allowArray) { |
||
| 468 | $text .= chr($symbol); |
||
| 469 | break; |
||
| 470 | } |
||
| 471 | if (isset($key)) { |
||
| 472 | $array[$key] = $this->sequenceQuotedNode($sequence, $countedEscapes)->flatten(true); |
||
| 473 | $key = null; |
||
| 474 | } else { |
||
| 475 | $key = $this->sequenceQuotedNode($sequence, $countedEscapes)->flatten(true); |
||
| 476 | } |
||
| 477 | $countedEscapes = 0; |
||
| 478 | $isArray = $allowArray; |
||
| 479 | break; |
||
| 480 | |||
| 481 | case Splitter::BYTE_SEPARATOR_COMMA: |
||
| 482 | if (!$allowArray) { |
||
| 483 | $text .= ','; |
||
| 484 | break; |
||
| 485 | } |
||
| 486 | if (isset($captured)) { |
||
| 487 | $array[$key ?? $captured] = is_numeric($captured) ? $captured + 0 : new ObjectAccessorNode($captured); |
||
| 488 | } |
||
| 489 | $key = null; |
||
| 490 | $isArray = $allowArray; |
||
| 491 | break; |
||
| 492 | |||
| 493 | case Splitter::BYTE_SEPARATOR_EQUALS: |
||
| 494 | $text .= '='; |
||
| 495 | if (!$allowArray) { |
||
| 496 | $node = new RootNode(); |
||
| 497 | $this->splitter->switch($this->contexts->protected); |
||
| 498 | break; |
||
| 499 | } |
||
| 500 | $key = $captured; |
||
| 501 | $isArray = $allowArray; |
||
| 502 | break; |
||
| 503 | |||
| 504 | case Splitter::BYTE_SEPARATOR_COLON: |
||
| 505 | $text .= ':'; |
||
| 506 | $hasColon = true; |
||
| 507 | $namespace = $captured; |
||
| 508 | $key = $key ?? $captured; |
||
| 509 | $isArray = $isArray || ($allowArray && is_numeric($key)); |
||
| 510 | break; |
||
| 511 | |||
| 512 | case Splitter::BYTE_WHITESPACE_SPACE: |
||
| 513 | case Splitter::BYTE_WHITESPACE_EOL: |
||
| 514 | case Splitter::BYTE_WHITESPACE_RETURN: |
||
| 515 | case Splitter::BYTE_WHITESPACE_TAB: |
||
| 516 | // If we already collected some whitespace we must enter protected context. |
||
| 517 | $text .= $this->source->source[$this->splitter->index - 1]; |
||
| 518 | if ($hasWhitespace && !$hasPass && !$allowArray) { |
||
| 519 | // Protection mode: this very limited context does not allow tags or inline syntax, and will |
||
| 520 | // protect things like CSS and JS - and will only enter a more reactive context if encountering |
||
| 521 | // the backtick character, meaning a quoted string will be sequenced. This backtick-quoted |
||
| 522 | // string can then contain inline syntax like variable accessors. |
||
| 523 | $node = $node ?? new RootNode(); |
||
| 524 | $this->splitter->switch($this->contexts->protected); |
||
| 525 | break; |
||
| 526 | } |
||
| 527 | $key = $key ?? $captured; |
||
| 528 | $hasWhitespace = true; |
||
| 529 | $isArray = $allowArray && ($hasColon ?? $isArray ?? is_numeric($captured)); |
||
| 530 | $potentialAccessor = ($potentialAccessor ?? $captured); |
||
| 531 | break; |
||
| 532 | |||
| 533 | case Splitter::BYTE_TAG_END: |
||
| 534 | case Splitter::BYTE_PIPE: |
||
| 535 | // If there is an accessor on the left side of the pipe and $node is not defined, we create $node |
||
| 536 | // as an object accessor. If $node already exists we do nothing (and expect the VH trigger, the |
||
| 537 | // parenthesis start case below, to add $node as childnode and create a new $node). |
||
| 538 | $hasPass = true; |
||
| 539 | $isArray = $allowArray; |
||
| 540 | $callDetected = false; |
||
| 541 | $potentialAccessor = $potentialAccessor ?? $captured; |
||
| 542 | $text .= $this->source->source[$this->splitter->index - 1]; |
||
| 543 | if (isset($potentialAccessor)) { |
||
| 544 | $childNodeToAdd = new ObjectAccessorNode($potentialAccessor); |
||
| 545 | $node = isset($node) ? $node->addChildNode($childNodeToAdd) : $childNodeToAdd; //$node ?? (is_numeric($potentialAccessor) ? $potentialAccessor + 0 : new ObjectAccessorNode($potentialAccessor)); |
||
| 546 | } |
||
| 547 | //!isset($potentialAccessor) ?: ($node = ($node ?? $this->createObjectAccessorNodeOrRawValue($potentialAccessor))); |
||
| 548 | unset($namespace, $method, $potentialAccessor, $key); |
||
| 549 | break; |
||
| 550 | |||
| 551 | case Splitter::BYTE_PARENTHESIS_START: |
||
| 552 | $isArray = false; |
||
| 553 | // Special case: if a parenthesis start was preceded by whitespace but had no pass operator we are |
||
| 554 | // not dealing with a ViewHelper call and will continue the sequencing, grabbing the parenthesis as |
||
| 555 | // part of the expression. |
||
| 556 | $text .= '('; |
||
| 557 | if (!$hasColon || ($hasWhitespace && !$hasPass)) { |
||
| 558 | $this->splitter->switch($this->contexts->protected); |
||
| 559 | unset($namespace, $method); |
||
| 560 | break; |
||
| 561 | } |
||
| 562 | |||
| 563 | $callDetected = true; |
||
| 564 | $method = $captured; |
||
| 565 | $childNodeToAdd = $node; |
||
| 566 | try { |
||
| 567 | $node = $this->resolver->createViewHelperInstance($namespace, $method); |
||
| 568 | $definitions = $node->prepareArguments(); |
||
| 569 | } catch (\TYPO3Fluid\Fluid\Core\Exception $exception) { |
||
| 570 | throw $this->createErrorAtPosition($exception->getMessage(), $exception->getCode()); |
||
| 571 | } |
||
| 572 | $this->splitter->switch($this->contexts->array); |
||
| 573 | $arguments = $this->sequenceArrayNode($sequence, $definitions)->getInternalArray(); |
||
| 574 | $this->splitter->switch($this->contexts->inline); |
||
| 575 | if ($childNodeToAdd) { |
||
| 576 | $escapingEnabledBackup = $this->escapingEnabled; |
||
| 577 | $this->escapingEnabled = (bool)$node->isChildrenEscapingEnabled(); |
||
| 578 | if ($childNodeToAdd instanceof ObjectAccessorNode) { |
||
| 579 | $this->callInterceptor($childNodeToAdd, InterceptorInterface::INTERCEPT_OBJECTACCESSOR); |
||
| 580 | } |
||
| 581 | $this->escapingEnabled = $escapingEnabledBackup; |
||
| 582 | $node->addChildNode($childNodeToAdd); |
||
| 583 | } |
||
| 584 | $text .= ')'; |
||
| 585 | unset($potentialAccessor); |
||
| 586 | break; |
||
| 587 | |||
| 588 | case Splitter::BYTE_INLINE_END: |
||
| 589 | $text .= '}'; |
||
| 590 | if (--$ignoredEndingBraces >= 0) { |
||
| 591 | break; |
||
| 592 | } |
||
| 593 | $isArray = $allowArray && ($isArray ?: ($hasColon && !$hasPass && !$callDetected)); |
||
| 594 | $potentialAccessor = $potentialAccessor ?? $captured; |
||
| 595 | |||
| 596 | // Decision: if we did not detect a ViewHelper we match the *entire* expression, from the cached |
||
| 597 | // starting index, to see if it matches a known type of expression. If it does, we must return the |
||
| 598 | // appropriate type of ExpressionNode. |
||
| 599 | if ($isArray) { |
||
| 600 | if ($captured !== null) { |
||
| 601 | $array[$key ?? $captured] = is_numeric($captured) ? $captured + 0 : new ObjectAccessorNode($captured); |
||
| 602 | } |
||
| 603 | return new ArrayNode($array); |
||
| 604 | } elseif ($callDetected) { |
||
| 605 | // The first-priority check is for a ViewHelper used right before the inline expression ends, |
||
| 606 | // in which case there is no further syntax to come. |
||
| 607 | $node = $node->postParse($arguments, $this->state, $this->renderingContext); |
||
| 608 | $interceptionPoint = InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER; |
||
| 609 | } elseif ($this->splitter->context->context === Context::CONTEXT_ACCESSOR) { |
||
| 610 | // If we are currently in "accessor" context we can now add the accessor by stripping the collected text. |
||
| 611 | $node = new ObjectAccessorNode(substr($text, 1, -1)); |
||
| 612 | $interceptionPoint = InterceptorInterface::INTERCEPT_OBJECTACCESSOR; |
||
| 613 | } elseif ($this->splitter->context->context === Context::CONTEXT_PROTECTED || ($hasWhitespace && !$callDetected && !$hasPass)) { |
||
| 614 | // In order to qualify for potentially being an expression, the entire inline node must contain |
||
| 615 | // whitespace, must not contain parenthesis, must not contain a colon and must not contain an |
||
| 616 | // inline pass operand. This significantly limits the number of times this (expensive) routine |
||
| 617 | // has to be executed. |
||
| 618 | $interceptionPoint = InterceptorInterface::INTERCEPT_TEXT; |
||
| 619 | $childNodeToAdd = new TextNode($text); |
||
| 620 | foreach ($this->renderingContext->getExpressionNodeTypes() as $expressionNodeTypeClassName) { |
||
| 621 | $matchedVariables = []; |
||
| 622 | // TODO: rewrite expression nodes to receive a sub-Splitter that lets the expression node |
||
| 623 | // consume a symbol+capture sequence and either match or ignore it; then use the already |
||
| 624 | // consumed (possibly halted mid-way through iterator!) sequence to achieve desired behavior. |
||
| 625 | preg_match_all($expressionNodeTypeClassName::$detectionExpression, $text, $matchedVariables, PREG_SET_ORDER); |
||
| 626 | foreach ($matchedVariables as $matchedVariableSet) { |
||
| 627 | try { |
||
| 628 | $childNodeToAdd = new $expressionNodeTypeClassName($matchedVariableSet[0], $matchedVariableSet, $this->state); |
||
| 629 | $interceptionPoint = InterceptorInterface::INTERCEPT_EXPRESSION; |
||
| 630 | } catch (ExpressionException $error) { |
||
| 631 | $childNodeToAdd = new TextNode($this->renderingContext->getErrorHandler()->handleExpressionError($error)); |
||
| 632 | } |
||
| 633 | break; |
||
| 634 | } |
||
| 635 | } |
||
| 636 | $node = isset($node) ? $node->addChildNode($childNodeToAdd) : $childNodeToAdd; |
||
| 637 | } elseif (!$hasPass && !$callDetected) { |
||
| 638 | // Third priority check is if there was no pass syntax and no ViewHelper, in which case we |
||
| 639 | // create a standard ObjectAccessorNode; alternatively, if nothing was captured (expression |
||
| 640 | // was empty, e.g. {} was used) we create a TextNode with the captured text to output "{}". |
||
| 641 | if (isset($potentialAccessor)) { |
||
| 642 | // If the accessor is set we can trust it is not a numeric value, since this will have |
||
| 643 | // set $isArray to TRUE if nothing else already did so. |
||
| 644 | $node = is_numeric($potentialAccessor) ? $potentialAccessor + 0 : new ObjectAccessorNode($potentialAccessor); |
||
| 645 | $interceptionPoint = InterceptorInterface::INTERCEPT_OBJECTACCESSOR; |
||
| 646 | } else { |
||
| 647 | $node = new TextNode($text); |
||
| 648 | $interceptionPoint = InterceptorInterface::INTERCEPT_TEXT; |
||
| 649 | } |
||
| 650 | } elseif ($hasPass && $this->resolver->isAliasRegistered((string)$potentialAccessor)) { |
||
| 651 | // Fourth priority check is for a pass to a ViewHelper alias, e.g. "{value | raw}" in which case |
||
| 652 | // we look for the alias used and create a ViewHelperNode with no arguments. |
||
| 653 | $childNodeToAdd = $node; |
||
| 654 | $node = $this->resolver->createViewHelperInstance(null, $potentialAccessor); |
||
| 655 | $node->addChildNode($childNodeToAdd); |
||
| 656 | $node = $node->postParse($arguments, $this->state, $this->renderingContext); |
||
| 657 | $interceptionPoint = InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER; |
||
| 658 | } else { |
||
| 659 | # TODO: should this be an error case, or should it result in a TextNode? |
||
| 660 | throw $this->createErrorAtPosition( |
||
| 661 | 'Invalid inline syntax - not accessor, not expression, not array, not ViewHelper, but ' . |
||
| 662 | 'contains the tokens used by these in a sequence that is not valid Fluid syntax. You can ' . |
||
| 663 | 'most likely avoid this by adding whitespace inside the curly braces before the first ' . |
||
| 664 | 'Fluid-like symbol in the expression. Symbols recognized as Fluid are: "' . |
||
| 665 | addslashes(implode('","', array_map('chr', $this->contexts->inline->bytes))) . '"', |
||
| 666 | 1558782228 |
||
| 667 | ); |
||
| 668 | } |
||
| 669 | |||
| 670 | $escapingEnabledBackup = $this->escapingEnabled; |
||
| 671 | $this->escapingEnabled = (bool)((isset($viewHelper) && $node->isOutputEscapingEnabled()) || $escapingEnabledBackup); |
||
| 672 | $this->callInterceptor($node, $interceptionPoint, $this->state); |
||
| 673 | $this->escapingEnabled = $escapingEnabledBackup; |
||
| 674 | return $node; |
||
| 675 | } |
||
| 676 | } |
||
| 677 | |||
| 678 | // See note in sequenceTagNode() end of method body. TL;DR: this is intentionally here instead of as "default" |
||
| 679 | // case in the switch above for a very specific reason: the case is only encountered if seeing EOF before the |
||
| 680 | // inline expression was closed. |
||
| 681 | throw $this->createErrorAtPosition('Unterminated inline syntax', 1557838506); |
||
| 682 | } |
||
| 683 | |||
| 932 | } |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.