Conditions | 75 |
Paths | 236 |
Total Lines | 311 |
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 |
||
320 | protected function sequenceInlineNodes(bool $allowArray = true): NodeInterface |
||
321 | { |
||
322 | $text = '{'; |
||
323 | $node = null; |
||
324 | $key = null; |
||
325 | $namespace = null; |
||
326 | $method = null; |
||
327 | $potentialAccessor = null; |
||
328 | $callDetected = false; |
||
329 | $hasPass = false; |
||
330 | $hasColon = null; |
||
331 | $hasWhitespace = false; |
||
332 | $isArray = false; |
||
333 | $array = []; |
||
334 | $arguments = []; |
||
335 | $ignoredEndingBraces = 0; |
||
336 | $countedEscapes = 0; |
||
337 | |||
338 | $this->splitter->switch($this->contexts->inline); |
||
339 | $this->splitter->sequence->next(); |
||
340 | foreach ($this->splitter->sequence as $symbol => $captured) { |
||
341 | $text .= $captured; |
||
342 | switch ($symbol) { |
||
343 | case Splitter::BYTE_BACKSLASH: |
||
344 | // Increase the number of counted escapes (is passed to sequenceNode() in the "QUOTE" cases and reset |
||
345 | // after the quoted string is extracted). |
||
346 | ++$countedEscapes; |
||
347 | break; |
||
348 | |||
349 | case Splitter::BYTE_ARRAY_START: |
||
350 | |||
351 | $text .= chr($symbol); |
||
352 | $isArray = $allowArray; |
||
353 | |||
354 | #ArrayStart: |
||
355 | // Sequence the node. Pass the "use numeric keys?" boolean based on the current byte. Only array |
||
356 | // start creates numeric keys. Inline start with keyless values creates ECMA style {foo:foo, bar:bar} |
||
357 | // from {foo, bar}. |
||
358 | $array[$key ?? $captured ?? 0] = $node = $this->sequenceArrayNode(null, $symbol === Splitter::BYTE_ARRAY_START); |
||
359 | $this->splitter->switch($this->contexts->inline); |
||
360 | unset($key); |
||
361 | break; |
||
362 | |||
363 | case Splitter::BYTE_INLINE: |
||
364 | // Encountering this case can mean different things: sub-syntax like {foo.{index}} or array, depending |
||
365 | // on presence of either a colon or comma before the inline. In protected mode it is simply added. |
||
366 | $text .= '{'; |
||
367 | if (!$hasWhitespace && $text !== '{{') { |
||
368 | // Most likely, a nested object accessor syntax e.g. {foo.{bar}} - enter protected context since |
||
369 | // these accessors do not allow anything other than additional nested accessors. |
||
370 | $this->splitter->switch($this->contexts->accessor); |
||
371 | ++$ignoredEndingBraces; |
||
372 | } elseif ($this->splitter->context->context === Context::CONTEXT_PROTECTED) { |
||
373 | // Ignore one ending additional curly brace. Subtracted in the BYTE_INLINE_END case below. |
||
374 | // The expression in this case looks like {{inline}.....} and we capture the curlies. |
||
375 | $potentialAccessor .= $captured; |
||
376 | ++$ignoredEndingBraces; |
||
377 | } elseif ($allowArray || $isArray) { |
||
378 | $isArray = true; |
||
379 | $captured = $key ?? $captured ?? $potentialAccessor; |
||
380 | // This is a sub-syntax following a colon - meaning it is an array. |
||
381 | if ($captured !== null) { |
||
382 | #goto ArrayStart; |
||
383 | $array[$key ?? $captured ?? 0] = $node = $this->sequenceArrayNode(null, $symbol === Splitter::BYTE_ARRAY_START); |
||
384 | $this->splitter->switch($this->contexts->inline); |
||
385 | } |
||
386 | } else { |
||
387 | $childNodeToAdd = $this->sequenceInlineNodes($allowArray); |
||
388 | $node = isset($node) ? $node->addChildNode($childNodeToAdd) : (new RootNode())->addChildNode($childNodeToAdd); |
||
389 | } |
||
390 | break; |
||
391 | |||
392 | case Splitter::BYTE_MINUS: |
||
393 | $text .= '-'; |
||
394 | break; |
||
395 | |||
396 | // Backtick may be encountered in two different contexts: normal inline context, in which case it has |
||
397 | // the same meaning as any quote and causes sequencing of a quoted string. Or protected context, in |
||
398 | // which case it also sequences a quoted node but appends the result instead of assigning to array. |
||
399 | // Note that backticks do not support escapes (they are a new feature that does not require escaping). |
||
400 | case Splitter::BYTE_BACKTICK: |
||
401 | if ($this->splitter->context->context === Context::CONTEXT_PROTECTED) { |
||
402 | $node->addChildNode(new TextNode($text)); |
||
403 | $node->addChildNode($this->sequenceQuotedNode()->flatten()); |
||
404 | $text = ''; |
||
405 | break; |
||
406 | } |
||
407 | // Fallthrough is intentional: if not in protected context, consider the backtick a normal quote. |
||
408 | |||
409 | // Case not normally countered in straight up "inline" context, but when encountered, means we have |
||
410 | // explicitly found a quoted array key - and we extract it. |
||
411 | case Splitter::BYTE_QUOTE_SINGLE: |
||
412 | case Splitter::BYTE_QUOTE_DOUBLE: |
||
413 | if (!$allowArray) { |
||
414 | $text .= chr($symbol); |
||
415 | break; |
||
416 | } |
||
417 | if (isset($key)) { |
||
418 | $array[$key] = $this->sequenceQuotedNode($countedEscapes)->flatten(true); |
||
419 | $key = null; |
||
420 | } else { |
||
421 | $key = $this->sequenceQuotedNode($countedEscapes)->flatten(true); |
||
422 | } |
||
423 | $countedEscapes = 0; |
||
424 | $isArray = $allowArray; |
||
425 | break; |
||
426 | |||
427 | case Splitter::BYTE_SEPARATOR_COMMA: |
||
428 | if (!$allowArray) { |
||
429 | $text .= ','; |
||
430 | break; |
||
431 | } |
||
432 | if (isset($captured)) { |
||
433 | $array[$key ?? $captured] = is_numeric($captured) ? $captured + 0 : new ObjectAccessorNode($captured); |
||
434 | } |
||
435 | $key = null; |
||
436 | $isArray = $allowArray; |
||
437 | break; |
||
438 | |||
439 | case Splitter::BYTE_SEPARATOR_EQUALS: |
||
440 | $text .= '='; |
||
441 | if (!$allowArray) { |
||
442 | $node = new RootNode(); |
||
443 | $this->splitter->switch($this->contexts->protected); |
||
444 | break; |
||
445 | } |
||
446 | $key = $captured; |
||
447 | $isArray = $allowArray; |
||
448 | break; |
||
449 | |||
450 | case Splitter::BYTE_SEPARATOR_COLON: |
||
451 | $text .= ':'; |
||
452 | $hasColon = true; |
||
453 | $namespace = $captured; |
||
454 | $key = $key ?? $captured; |
||
455 | $isArray = $isArray || ($allowArray && is_numeric($key)); |
||
456 | break; |
||
457 | |||
458 | case Splitter::BYTE_WHITESPACE_SPACE: |
||
459 | case Splitter::BYTE_WHITESPACE_EOL: |
||
460 | case Splitter::BYTE_WHITESPACE_RETURN: |
||
461 | case Splitter::BYTE_WHITESPACE_TAB: |
||
462 | // If we already collected some whitespace we must enter protected context. |
||
463 | $text .= $this->source->source[$this->splitter->index - 1]; |
||
464 | if ($hasWhitespace && !$hasPass && !$allowArray) { |
||
465 | // Protection mode: this very limited context does not allow tags or inline syntax, and will |
||
466 | // protect things like CSS and JS - and will only enter a more reactive context if encountering |
||
467 | // the backtick character, meaning a quoted string will be sequenced. This backtick-quoted |
||
468 | // string can then contain inline syntax like variable accessors. |
||
469 | $node = $node ?? new RootNode(); |
||
470 | $this->splitter->switch($this->contexts->protected); |
||
471 | break; |
||
472 | } |
||
473 | $key = $key ?? $captured; |
||
474 | $hasWhitespace = true; |
||
475 | $isArray = $allowArray && ($hasColon ?? $isArray ?? is_numeric($captured)); |
||
476 | $potentialAccessor = ($potentialAccessor ?? $captured); |
||
477 | break; |
||
478 | |||
479 | case Splitter::BYTE_TAG_END: |
||
480 | case Splitter::BYTE_PIPE: |
||
481 | // If there is an accessor on the left side of the pipe and $node is not defined, we create $node |
||
482 | // as an object accessor. If $node already exists we do nothing (and expect the VH trigger, the |
||
483 | // parenthesis start case below, to add $node as childnode and create a new $node). |
||
484 | $hasPass = true; |
||
485 | $isArray = $allowArray; |
||
486 | $callDetected = false; |
||
487 | $potentialAccessor = $potentialAccessor ?? $captured; |
||
488 | $text .= $this->source->source[$this->splitter->index - 1]; |
||
489 | if ($node instanceof ViewHelperInterface) { |
||
490 | $node->postParse($arguments, $this->state, $this->renderingContext); |
||
491 | } |
||
492 | if (isset($potentialAccessor)) { |
||
493 | $childNodeToAdd = new ObjectAccessorNode($potentialAccessor); |
||
494 | $node = isset($node) ? $node->addChildNode($childNodeToAdd) : $childNodeToAdd; //$node ?? (is_numeric($potentialAccessor) ? $potentialAccessor + 0 : new ObjectAccessorNode($potentialAccessor)); |
||
495 | } |
||
496 | unset($namespace, $method, $potentialAccessor, $key); |
||
497 | break; |
||
498 | |||
499 | case Splitter::BYTE_PARENTHESIS_START: |
||
500 | $isArray = false; |
||
501 | // Special case: if a parenthesis start was preceded by whitespace but had no pass operator we are |
||
502 | // not dealing with a ViewHelper call and will continue the sequencing, grabbing the parenthesis as |
||
503 | // part of the expression. |
||
504 | $text .= '('; |
||
505 | if (!$hasColon || ($hasWhitespace && !$hasPass)) { |
||
506 | $this->splitter->switch($this->contexts->protected); |
||
507 | unset($namespace, $method); |
||
508 | break; |
||
509 | } |
||
510 | |||
511 | $callDetected = true; |
||
512 | $method = $captured; |
||
513 | $childNodeToAdd = $node; |
||
514 | try { |
||
515 | $node = $this->resolver->createViewHelperInstance($namespace, $method); |
||
516 | $definitions = $node->prepareArguments(); |
||
517 | } catch (\TYPO3Fluid\Fluid\Core\Exception $exception) { |
||
518 | throw $this->splitter->createErrorAtPosition($exception->getMessage(), $exception->getCode()); |
||
519 | } |
||
520 | $this->splitter->switch($this->contexts->array); |
||
521 | $arguments = $this->sequenceArrayNode($definitions)->getInternalArray(); |
||
522 | $this->splitter->switch($this->contexts->inline); |
||
523 | if ($childNodeToAdd) { |
||
524 | $escapingEnabledBackup = $this->escapingEnabled; |
||
525 | $this->escapingEnabled = (bool)$node->isChildrenEscapingEnabled(); |
||
526 | if ($childNodeToAdd instanceof ObjectAccessorNode) { |
||
527 | $this->callInterceptor($childNodeToAdd, InterceptorInterface::INTERCEPT_OBJECTACCESSOR); |
||
528 | } |
||
529 | $this->escapingEnabled = $escapingEnabledBackup; |
||
530 | $node->addChildNode($childNodeToAdd); |
||
531 | } |
||
532 | $text .= ')'; |
||
533 | unset($potentialAccessor); |
||
534 | break; |
||
535 | |||
536 | case Splitter::BYTE_INLINE_END: |
||
537 | $text .= '}'; |
||
538 | if (--$ignoredEndingBraces >= 0) { |
||
539 | break; |
||
540 | } |
||
541 | $isArray = $allowArray && ($isArray ?: ($hasColon && !$hasPass && !$callDetected)); |
||
542 | $potentialAccessor = $potentialAccessor ?? $captured; |
||
543 | |||
544 | // Decision: if we did not detect a ViewHelper we match the *entire* expression, from the cached |
||
545 | // starting index, to see if it matches a known type of expression. If it does, we must return the |
||
546 | // appropriate type of ExpressionNode. |
||
547 | if ($isArray) { |
||
548 | if ($captured !== null) { |
||
549 | $array[$key ?? $captured] = is_numeric($captured) ? $captured + 0 : new ObjectAccessorNode($captured); |
||
550 | } |
||
551 | return new ArrayNode($array); |
||
552 | } elseif ($callDetected) { |
||
553 | // The first-priority check is for a ViewHelper used right before the inline expression ends, |
||
554 | // in which case there is no further syntax to come. |
||
555 | $node = $node->postParse($arguments, $this->state, $this->renderingContext); |
||
556 | $interceptionPoint = InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER; |
||
557 | } elseif ($this->splitter->context->context === Context::CONTEXT_ACCESSOR) { |
||
558 | // If we are currently in "accessor" context we can now add the accessor by stripping the collected text. |
||
559 | $node = new ObjectAccessorNode(substr($text, 1, -1)); |
||
560 | $interceptionPoint = InterceptorInterface::INTERCEPT_OBJECTACCESSOR; |
||
561 | } elseif ($this->splitter->context->context === Context::CONTEXT_PROTECTED || ($hasWhitespace && !$callDetected && !$hasPass)) { |
||
562 | // In order to qualify for potentially being an expression, the entire inline node must contain |
||
563 | // whitespace, must not contain parenthesis, must not contain a colon and must not contain an |
||
564 | // inline pass operand. This significantly limits the number of times this (expensive) routine |
||
565 | // has to be executed. |
||
566 | $interceptionPoint = InterceptorInterface::INTERCEPT_TEXT; |
||
567 | $childNodeToAdd = new TextNode($text); |
||
568 | foreach ($this->renderingContext->getExpressionNodeTypes() as $expressionNodeTypeClassName) { |
||
569 | $matchedVariables = []; |
||
570 | // TODO: rewrite expression nodes to receive a sub-Splitter that lets the expression node |
||
571 | // consume a symbol+capture sequence and either match or ignore it; then use the already |
||
572 | // consumed (possibly halted mid-way through iterator!) sequence to achieve desired behavior. |
||
573 | preg_match_all($expressionNodeTypeClassName::$detectionExpression, $text, $matchedVariables, PREG_SET_ORDER); |
||
574 | foreach ($matchedVariables as $matchedVariableSet) { |
||
575 | try { |
||
576 | $childNodeToAdd = new $expressionNodeTypeClassName($matchedVariableSet[0], $matchedVariableSet, $this->state); |
||
577 | $interceptionPoint = InterceptorInterface::INTERCEPT_EXPRESSION; |
||
578 | } catch (ExpressionException $error) { |
||
579 | $childNodeToAdd = new TextNode($this->renderingContext->getErrorHandler()->handleExpressionError($error)); |
||
580 | } |
||
581 | break; |
||
582 | } |
||
583 | } |
||
584 | $node = isset($node) ? $node->addChildNode($childNodeToAdd) : $childNodeToAdd; |
||
585 | } elseif (!$hasPass && !$callDetected) { |
||
586 | // Third priority check is if there was no pass syntax and no ViewHelper, in which case we |
||
587 | // create a standard ObjectAccessorNode; alternatively, if nothing was captured (expression |
||
588 | // was empty, e.g. {} was used) we create a TextNode with the captured text to output "{}". |
||
589 | if (isset($potentialAccessor)) { |
||
590 | // If the accessor is set we can trust it is not a numeric value, since this will have |
||
591 | // set $isArray to TRUE if nothing else already did so. |
||
592 | $node = is_numeric($potentialAccessor) ? $potentialAccessor + 0 : new ObjectAccessorNode($potentialAccessor); |
||
593 | $interceptionPoint = InterceptorInterface::INTERCEPT_OBJECTACCESSOR; |
||
594 | } else { |
||
595 | $node = new TextNode($text); |
||
596 | $interceptionPoint = InterceptorInterface::INTERCEPT_TEXT; |
||
597 | } |
||
598 | } elseif ($hasPass && $this->resolver->isAliasRegistered((string)$potentialAccessor)) { |
||
599 | // Fourth priority check is for a pass to a ViewHelper alias, e.g. "{value | raw}" in which case |
||
600 | // we look for the alias used and create a ViewHelperNode with no arguments. |
||
601 | $childNodeToAdd = $node; |
||
602 | $node = $this->resolver->createViewHelperInstance(null, $potentialAccessor); |
||
603 | $node->addChildNode($childNodeToAdd); |
||
604 | $node = $node->postParse($arguments, $this->state, $this->renderingContext); |
||
605 | $interceptionPoint = InterceptorInterface::INTERCEPT_CLOSING_VIEWHELPER; |
||
606 | } else { |
||
607 | # TODO: should this be an error case, or should it result in a TextNode? |
||
608 | throw $this->splitter->createErrorAtPosition( |
||
609 | 'Invalid inline syntax - not accessor, not expression, not array, not ViewHelper, but ' . |
||
610 | 'contains the tokens used by these in a sequence that is not valid Fluid syntax. You can ' . |
||
611 | 'most likely avoid this by adding whitespace inside the curly braces before the first ' . |
||
612 | 'Fluid-like symbol in the expression. Symbols recognized as Fluid are: "' . |
||
613 | addslashes(implode('","', array_map('chr', $this->contexts->inline->bytes))) . '"', |
||
614 | 1558782228 |
||
615 | ); |
||
616 | } |
||
617 | |||
618 | $escapingEnabledBackup = $this->escapingEnabled; |
||
619 | $this->escapingEnabled = (bool)((isset($viewHelper) && $node->isOutputEscapingEnabled()) || $escapingEnabledBackup); |
||
620 | $this->callInterceptor($node, $interceptionPoint); |
||
621 | $this->escapingEnabled = $escapingEnabledBackup; |
||
622 | return $node; |
||
623 | } |
||
624 | } |
||
625 | |||
626 | // See note in sequenceTagNode() end of method body. TL;DR: this is intentionally here instead of as "default" |
||
627 | // case in the switch above for a very specific reason: the case is only encountered if seeing EOF before the |
||
628 | // inline expression was closed. |
||
629 | throw $this->splitter->createErrorAtPosition('Unterminated inline syntax', 1557838506); |
||
630 | } |
||
631 | |||
884 | } |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.