Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 18 | final class Processor implements ProcessorInterface |
||
| 19 | { |
||
| 20 | /** @var Handlers */ |
||
| 21 | private $handlers; |
||
| 22 | /** @var ParserInterface */ |
||
| 23 | private $parser; |
||
| 24 | /** @var EventContainerInterface|null */ |
||
| 25 | private $eventContainer; |
||
| 26 | |||
| 27 | /** @var int|null */ |
||
| 28 | private $recursionDepth; // infinite recursion |
||
| 29 | /** @var int|null */ |
||
| 30 | private $maxIterations = 1; // one iteration |
||
| 31 | /** @var bool */ |
||
| 32 | private $autoProcessContent = true; // automatically process shortcode content |
||
| 33 | |||
| 34 | 64 | public function __construct(ParserInterface $parser, Handlers $handlers) |
|
| 39 | |||
| 40 | /** |
||
| 41 | * Entry point for shortcode processing. Implements iterative algorithm for |
||
| 42 | * both limited and unlimited number of iterations. |
||
| 43 | * |
||
| 44 | * @param string $text Text to process |
||
| 45 | * |
||
| 46 | * @return string |
||
| 47 | */ |
||
| 48 | 57 | public function process($text) |
|
| 49 | { |
||
| 50 | 57 | $iterations = $this->maxIterations === null ? 1 : $this->maxIterations; |
|
| 51 | 57 | $context = new ProcessorContext(); |
|
| 52 | 57 | $context->processor = $this; |
|
| 53 | |||
| 54 | 57 | while ($iterations--) { |
|
| 55 | 57 | $context->iterationNumber++; |
|
| 56 | 57 | $newText = $this->processIteration($text, $context, null); |
|
| 57 | 57 | if ($newText === $text) { |
|
| 58 | 9 | break; |
|
| 59 | } |
||
| 60 | 52 | $text = $newText; |
|
| 61 | 52 | $iterations += $this->maxIterations === null ? 1 : 0; |
|
| 62 | 52 | } |
|
| 63 | |||
| 64 | 57 | return $text; |
|
| 65 | } |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @param string $name |
||
| 69 | * @param object $event |
||
| 70 | * |
||
| 71 | * @return object |
||
| 72 | */ |
||
| 73 | 57 | private function dispatchEvent($name, $event) |
|
| 74 | { |
||
| 75 | 57 | if(null === $this->eventContainer) { |
|
| 76 | 52 | return $event; |
|
| 77 | } |
||
| 78 | |||
| 79 | 5 | $handlers = $this->eventContainer->getListeners($name); |
|
| 80 | 5 | foreach($handlers as $handler) { |
|
| 81 | 4 | $handler($event); |
|
| 82 | 5 | } |
|
| 83 | |||
| 84 | 5 | return $event; |
|
| 85 | } |
||
| 86 | |||
| 87 | /** |
||
| 88 | * @param string $text |
||
| 89 | * |
||
| 90 | * @return string |
||
| 91 | */ |
||
| 92 | 57 | private function processIteration($text, ProcessorContext $context, ProcessedShortcode $parent = null) |
|
| 93 | { |
||
| 94 | 57 | if (null !== $this->recursionDepth && $context->recursionLevel > $this->recursionDepth) { |
|
| 95 | 2 | return $text; |
|
| 96 | } |
||
| 97 | |||
| 98 | 57 | $context->parent = $parent; |
|
| 99 | 57 | $context->text = $text; |
|
| 100 | 57 | $filterEvent = new FilterShortcodesEvent($this->parser->parse($text), $parent); |
|
| 101 | 57 | $this->dispatchEvent(Events::FILTER_SHORTCODES, $filterEvent); |
|
| 102 | 57 | $shortcodes = $filterEvent->getShortcodes(); |
|
| 103 | 57 | $replaces = array(); |
|
| 104 | 57 | $baseOffset = $parent && $shortcodes |
|
|
|
|||
| 105 | 57 | ? (int)mb_strpos($parent->getShortcodeText(), $shortcodes[0]->getText(), 0, 'utf-8') - $shortcodes[0]->getOffset() + $parent->getOffset() |
|
| 106 | 57 | : 0; |
|
| 107 | 57 | foreach ($shortcodes as $shortcode) { |
|
| 108 | 57 | $name = $shortcode->getName(); |
|
| 109 | 57 | $hasNamePosition = array_key_exists($name, $context->namePosition); |
|
| 110 | |||
| 111 | 57 | $context->baseOffset = $baseOffset + $shortcode->getOffset(); |
|
| 112 | 57 | $context->position++; |
|
| 113 | 57 | $context->namePosition[$name] = $hasNamePosition ? $context->namePosition[$name] + 1 : 1; |
|
| 114 | 57 | $context->shortcodeText = $shortcode->getText(); |
|
| 115 | 57 | $context->offset = $shortcode->getOffset(); |
|
| 116 | 57 | $context->shortcode = $shortcode; |
|
| 117 | 57 | $context->textContent = (string)$shortcode->getContent(); |
|
| 118 | |||
| 119 | 57 | $handler = $this->handlers->get($name); |
|
| 120 | 57 | $replace = $this->processHandler($shortcode, $context, $handler); |
|
| 121 | |||
| 122 | 57 | $replaces[] = new ReplacedShortcode($shortcode, $replace); |
|
| 123 | 57 | } |
|
| 124 | |||
| 125 | 57 | $applyEvent = new ReplaceShortcodesEvent($text, $replaces, $parent); |
|
| 126 | 57 | $this->dispatchEvent(Events::REPLACE_SHORTCODES, $applyEvent); |
|
| 127 | |||
| 128 | 57 | return $applyEvent->hasResult() ? (string)$applyEvent->getResult() : $this->applyReplaces($text, $replaces); |
|
| 129 | } |
||
| 130 | |||
| 131 | /** |
||
| 132 | * @param string $text |
||
| 133 | * @param ReplacedShortcode[] $replaces |
||
| 134 | * |
||
| 135 | * @return string |
||
| 136 | */ |
||
| 137 | 56 | private function applyReplaces($text, array $replaces) |
|
| 138 | { |
||
| 139 | 56 | foreach(array_reverse($replaces) as $s) { |
|
| 140 | 56 | $offset = $s->getOffset(); |
|
| 141 | 56 | $length = mb_strlen($s->getText(), 'utf-8'); |
|
| 142 | 56 | $textLength = mb_strlen($text, 'utf-8'); |
|
| 143 | |||
| 144 | 56 | $text = mb_substr($text, 0, $offset, 'utf-8').$s->getReplacement().mb_substr($text, $offset + $length, $textLength, 'utf-8'); |
|
| 145 | 56 | } |
|
| 146 | |||
| 147 | 56 | return $text; |
|
| 148 | } |
||
| 149 | |||
| 150 | /** |
||
| 151 | * @psalm-param (callable(ShortcodeInterface):string)|null $handler |
||
| 152 | * @return string |
||
| 153 | */ |
||
| 154 | 57 | private function processHandler(ParsedShortcodeInterface $parsed, ProcessorContext $context, $handler) |
|
| 170 | |||
| 171 | /** @return string|null */ |
||
| 172 | 57 | private function processRecursion(ProcessedShortcode $shortcode, ProcessorContext $context) |
|
| 186 | |||
| 187 | /** |
||
| 188 | * Container for event handlers used in this processor. |
||
| 189 | * |
||
| 190 | * @param EventContainerInterface $eventContainer |
||
| 191 | * |
||
| 192 | * @return self |
||
| 193 | */ |
||
| 194 | 8 | public function withEventContainer(EventContainerInterface $eventContainer) |
|
| 201 | |||
| 202 | /** |
||
| 203 | * Recursion depth level, null means infinite, any integer greater than or |
||
| 204 | * equal to zero sets value (number of recursion levels). Zero disables |
||
| 205 | * recursion. Defaults to null. |
||
| 206 | * |
||
| 207 | * @param int|null $depth |
||
| 208 | * |
||
| 209 | * @return self |
||
| 210 | */ |
||
| 211 | 3 | View Code Duplication | public function withRecursionDepth($depth) |
| 224 | |||
| 225 | /** |
||
| 226 | * Maximum number of iterations, null means infinite, any integer greater |
||
| 227 | * than zero sets value. Zero is invalid because there must be at least one |
||
| 228 | * iteration. Defaults to 1. Loop breaks if result of two consequent |
||
| 229 | * iterations shows no change in processed text. |
||
| 230 | * |
||
| 231 | * @param int|null $iterations |
||
| 232 | * |
||
| 233 | * @return self |
||
| 234 | */ |
||
| 235 | 3 | View Code Duplication | public function withMaxIterations($iterations) |
| 248 | |||
| 249 | /** |
||
| 250 | * Whether shortcode content will be automatically processed and handler |
||
| 251 | * already receives shortcode with processed content. If false, every |
||
| 252 | * shortcode handler needs to process content on its own. Default true. |
||
| 253 | * |
||
| 254 | * @param bool $flag True if enabled (default), false otherwise |
||
| 255 | * |
||
| 256 | * @return self |
||
| 257 | */ |
||
| 258 | 3 | public function withAutoProcessContent($flag) |
|
| 271 | } |
||
| 272 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)or! empty(...)instead.