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:
Complex classes like JsonElementReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use JsonElementReader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | final class JsonElementReader implements JsonReadable |
||
26 | { |
||
27 | /** |
||
28 | * A stack representing the next element to be consumed |
||
29 | * |
||
30 | * @var array |
||
31 | */ |
||
32 | private $stack = []; |
||
33 | |||
34 | /** |
||
35 | * An array of types that map to the position in the stack |
||
36 | * |
||
37 | * @var array |
||
38 | */ |
||
39 | private $stackTypes = []; |
||
40 | |||
41 | /** |
||
42 | * The current size of the stack |
||
43 | * |
||
44 | * @var int |
||
45 | */ |
||
46 | private $stackSize = 0; |
||
47 | |||
48 | /** |
||
49 | * A cache of the current [@see JsonToken]. This should get nulled out |
||
50 | * whenever a new token should be returned with the subsequent call |
||
51 | * to [@see JsonDecodeReader::peek] |
||
52 | * |
||
53 | * @var |
||
54 | */ |
||
55 | private $currentToken; |
||
56 | |||
57 | /** |
||
58 | * An array of path names that correspond to the current stack |
||
59 | * |
||
60 | * @var array |
||
61 | */ |
||
62 | private $pathNames = []; |
||
63 | |||
64 | /** |
||
65 | * An array of path indicies that correspond to the current stack. This array could contain invalid |
||
66 | * values at indexes outside the current stack. It could also contain incorrect values at indexes |
||
67 | * where a path name is used. Data should only be fetched by referencing the current position in the stack. |
||
68 | * |
||
69 | * @var array |
||
70 | */ |
||
71 | private $pathIndices = []; |
||
72 | |||
73 | /** |
||
74 | * Constructor |
||
75 | * |
||
76 | * @param JsonElement $jsonElement |
||
77 | */ |
||
78 | 69 | public function __construct(JsonElement $jsonElement) |
|
82 | |||
83 | /** |
||
84 | * Consumes the next token and asserts it's the beginning of a new array |
||
85 | * |
||
86 | * @return void |
||
87 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BEGIN_ARRAY |
||
88 | */ |
||
89 | 15 | View Code Duplication | public function beginArray(): void |
98 | |||
99 | /** |
||
100 | * Consumes the next token and asserts it's the end of an array |
||
101 | * |
||
102 | * @return void |
||
103 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not END_ARRAY |
||
104 | */ |
||
105 | 5 | public function endArray(): void |
|
112 | |||
113 | /** |
||
114 | * Consumes the next token and asserts it's the beginning of a new object |
||
115 | * |
||
116 | * @return void |
||
117 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BEGIN_OBJECT |
||
118 | */ |
||
119 | 17 | public function beginObject(): void |
|
128 | |||
129 | /** |
||
130 | * Consumes the next token and asserts it's the end of an object |
||
131 | * |
||
132 | * @return void |
||
133 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not END_OBJECT |
||
134 | */ |
||
135 | 4 | public function endObject(): void |
|
142 | |||
143 | /** |
||
144 | * Returns true if the array or object has another element |
||
145 | * |
||
146 | * If the current scope is not an array or object, this returns false |
||
147 | * |
||
148 | * @return bool |
||
149 | */ |
||
150 | 4 | public function hasNext(): bool |
|
156 | |||
157 | /** |
||
158 | * Consumes the value of the next token, asserts it's a boolean and returns it |
||
159 | * |
||
160 | * @return bool |
||
161 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not BOOLEAN |
||
162 | */ |
||
163 | 6 | public function nextBoolean(): bool |
|
174 | |||
175 | /** |
||
176 | * Consumes the value of the next token, asserts it's a double and returns it |
||
177 | * |
||
178 | * @return double |
||
179 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NUMBER |
||
180 | */ |
||
181 | 3 | public function nextDouble(): float |
|
192 | |||
193 | /** |
||
194 | * Consumes the value of the next token, asserts it's an int and returns it |
||
195 | * |
||
196 | * @return int |
||
197 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NUMBER |
||
198 | */ |
||
199 | 6 | public function nextInteger(): int |
|
210 | |||
211 | /** |
||
212 | * Consumes the value of the next token, asserts it's a string and returns it |
||
213 | * |
||
214 | * @return string |
||
215 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME or STRING |
||
216 | */ |
||
217 | 14 | View Code Duplication | public function nextString(): string |
233 | |||
234 | /** |
||
235 | * Consumes the value of the next token and asserts it's null |
||
236 | * |
||
237 | * @return null |
||
238 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME or NULL |
||
239 | */ |
||
240 | 2 | public function nextNull() |
|
250 | |||
251 | /** |
||
252 | * Consumes the next name and returns it |
||
253 | * |
||
254 | * @return string |
||
255 | * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME |
||
256 | */ |
||
257 | 9 | View Code Duplication | public function nextName(): string |
273 | |||
274 | /** |
||
275 | * Returns an enum representing the type of the next token without consuming it |
||
276 | * |
||
277 | * @return string |
||
278 | */ |
||
279 | 69 | public function peek(): string |
|
335 | |||
336 | /** |
||
337 | * Skip the next value. If the next value is an object or array, all children will |
||
338 | * also be skipped. |
||
339 | * |
||
340 | * @return void |
||
341 | */ |
||
342 | 1 | public function skipValue(): void |
|
346 | |||
347 | /** |
||
348 | * Get the current read path in json xpath format |
||
349 | * |
||
350 | * @return string |
||
351 | */ |
||
352 | 10 | View Code Duplication | public function getPath(): string |
367 | |||
368 | /** |
||
369 | * Push an element onto the stack |
||
370 | * |
||
371 | * @param JsonElement|Iterator $element |
||
372 | * @param string $type |
||
373 | */ |
||
374 | 69 | View Code Duplication | private function push($element, $type = null): void |
385 | |||
386 | /** |
||
387 | * Pop the last element off the stack and return it |
||
388 | * |
||
389 | * @return JsonElement|Iterator |
||
390 | */ |
||
391 | 45 | View Code Duplication | private function pop() |
399 | |||
400 | /** |
||
401 | * Check that the next token equals the expectation |
||
402 | * |
||
403 | * @param string $expectedToken |
||
404 | * @throws UnexpectedJsonTokenException |
||
405 | */ |
||
406 | 52 | View Code Duplication | private function expect(string $expectedToken) |
420 | |||
421 | /** |
||
422 | * Increment the path index. This should be called any time a new value is parsed. |
||
423 | */ |
||
424 | 36 | View Code Duplication | private function incrementPathIndex(): void |
434 | } |
||
435 |
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.