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.