GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Processor   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 254
Duplicated Lines 9.45 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 9
dl 24
loc 254
ccs 105
cts 105
cp 1
rs 9.6
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A processHandler() 0 16 2
A processRecursion() 0 14 3
A withEventContainer() 0 7 1
A withRecursionDepth() 12 13 4
A withMaxIterations() 12 13 4
A withAutoProcessContent() 0 13 2
A process() 0 18 5
A dispatchEvent() 0 13 3
B processIteration() 0 38 8
A applyReplaces() 0 12 2

How to fix   Duplicated Code   

Duplicated Code

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
2
namespace Thunder\Shortcode\Processor;
3
4
use Thunder\Shortcode\Event\ReplaceShortcodesEvent;
5
use Thunder\Shortcode\Event\FilterShortcodesEvent;
6
use Thunder\Shortcode\EventContainer\EventContainerInterface;
7
use Thunder\Shortcode\Events;
8
use Thunder\Shortcode\HandlerContainer\HandlerContainerInterface as Handlers;
9
use Thunder\Shortcode\Parser\ParserInterface;
10
use Thunder\Shortcode\Shortcode\ReplacedShortcode;
11
use Thunder\Shortcode\Shortcode\ParsedShortcodeInterface;
12
use Thunder\Shortcode\Shortcode\ProcessedShortcode;
13
use Thunder\Shortcode\Shortcode\ShortcodeInterface;
14
15
/**
16
 * @author Tomasz Kowalczyk <[email protected]>
17
 */
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)
35
    {
36 64
        $this->parser = $parser;
37 64
        $this->handlers = $handlers;
38 64
    }
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
0 ignored issues
show
Bug Best Practice introduced by
The expression $shortcodes of type Thunder\Shortcode\Shortc...sedShortcodeInterface[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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)
155
    {
156 57
        $processed = ProcessedShortcode::createFromContext(clone $context);
157 57
        $content = $this->processRecursion($processed, $context);
158 57
        $processed = $processed->withContent($content);
159
160 57
        if($handler) {
161 53
            return $handler($processed);
162
        }
163
164 10
        $state = $parsed->getText();
165 10
        $length = (int)mb_strlen($processed->getTextContent(), 'utf-8');
166 10
        $offset = (int)mb_strrpos($state, $processed->getTextContent(), 0, 'utf-8');
167
168 10
        return mb_substr($state, 0, $offset, 'utf-8').(string)$processed->getContent().mb_substr($state, $offset + $length, mb_strlen($state, 'utf-8'), 'utf-8');
169
    }
170
171
    /** @return string|null */
172 57
    private function processRecursion(ProcessedShortcode $shortcode, ProcessorContext $context)
173
    {
174 57
        $content = $shortcode->getContent();
175 57
        if ($this->autoProcessContent && null !== $content) {
176 46
            $context->recursionLevel++;
177
            // this is safe from using max iterations value because it's manipulated in process() method
178 46
            $content = $this->processIteration($content, clone $context, $shortcode);
179 46
            $context->recursionLevel--;
180
181 46
            return $content;
182
        }
183
184 28
        return $content;
185
    }
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)
195
    {
196 8
        $self = clone $this;
197 8
        $self->eventContainer = $eventContainer;
198
199 8
        return $self;
200
    }
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
    {
213
        /** @psalm-suppress DocblockTypeContradiction */
214 3
        if (null !== $depth && !(is_int($depth) && $depth >= 0)) {
215 1
            $msg = 'Recursion depth must be null (infinite) or integer >= 0!';
216 1
            throw new \InvalidArgumentException($msg);
217
        }
218
219 2
        $self = clone $this;
220 2
        $self->recursionDepth = $depth;
221
222 2
        return $self;
223
    }
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
    {
237
        /** @psalm-suppress DocblockTypeContradiction */
238 3
        if (null !== $iterations && !(is_int($iterations) && $iterations > 0)) {
239 1
            $msg = 'Maximum number of iterations must be null (infinite) or integer > 0!';
240 1
            throw new \InvalidArgumentException($msg);
241
        }
242
243 2
        $self = clone $this;
244 2
        $self->maxIterations = $iterations;
245
246 2
        return $self;
247
    }
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)
259
    {
260
        /** @psalm-suppress DocblockTypeContradiction */
261 3
        if (!is_bool($flag)) {
262 1
            $msg = 'Auto processing flag must be a boolean value!';
263 1
            throw new \InvalidArgumentException($msg);
264
        }
265
266 2
        $self = clone $this;
267 2
        $self->autoProcessContent = (bool)$flag;
268
269 2
        return $self;
270
    }
271
}
272