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 StringPrimitive 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 StringPrimitive, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class StringPrimitive implements PrimitiveInterface, StringableInterface |
||
10 | { |
||
11 | const PAD_RIGHT = STR_PAD_RIGHT; |
||
12 | const PAD_LEFT = STR_PAD_LEFT; |
||
13 | const PAD_BOTH = STR_PAD_BOTH; |
||
14 | const PREG_NO_FLAGS = 0; |
||
15 | const PREG_SPLIT_NO_EMPTY = PREG_SPLIT_NO_EMPTY; |
||
16 | const PREG_SPLIT_DELIM_CAPTURE = PREG_SPLIT_DELIM_CAPTURE; |
||
17 | const PREG_SPLIT_OFFSET_CAPTURE = PREG_SPLIT_OFFSET_CAPTURE; |
||
18 | const PREG_OFFSET_CAPTURE = PREG_OFFSET_CAPTURE; |
||
19 | |||
20 | private $value; |
||
21 | |||
22 | 119 | public function __construct(string $value) |
|
26 | |||
27 | /** |
||
28 | * {@inheritdoc} |
||
29 | */ |
||
30 | 1 | public function toPrimitive() |
|
34 | |||
35 | /** |
||
36 | * {@inheritdoc} |
||
37 | */ |
||
38 | 59 | public function __toString(): string |
|
42 | |||
43 | /** |
||
44 | * Split the string into a collection of ones |
||
45 | * |
||
46 | * @param string $delimiter |
||
47 | * |
||
48 | * @return TypedCollectionInterface |
||
49 | */ |
||
50 | 2 | public function split(string $delimiter = null): TypedCollectionInterface |
|
65 | |||
66 | /** |
||
67 | * Returns a collection of the string splitted by the given chunk size |
||
68 | * |
||
69 | * @param int $size |
||
70 | * |
||
71 | * @return TypedCollectionInterface |
||
72 | */ |
||
73 | 1 | public function chunk(int $size = 1): TypedCollectionInterface |
|
83 | |||
84 | /** |
||
85 | * Returns the position of the first occurence of the string |
||
86 | * |
||
87 | * @param string $needle |
||
88 | * @param int $offset |
||
89 | * |
||
90 | * @throws SubstringException If the string is not found |
||
91 | * |
||
92 | * @return int |
||
93 | */ |
||
94 | 3 | public function pos(string $needle, int $offset = 0): int |
|
107 | |||
108 | /** |
||
109 | * Replace all occurences of the search string with the replacement one |
||
110 | * |
||
111 | * @param string $search |
||
112 | * @param string $replacement |
||
113 | * |
||
114 | * @return self |
||
115 | */ |
||
116 | 1 | public function replace(string $search, string $replacement): self |
|
124 | |||
125 | /** |
||
126 | * Returns the string following the given delimiter |
||
127 | * |
||
128 | * @param string $delimiter |
||
129 | * |
||
130 | * @throws SubstringException If the string is not found |
||
131 | * |
||
132 | * @return self |
||
133 | */ |
||
134 | 2 | public function str(string $delimiter): self |
|
147 | |||
148 | /** |
||
149 | * Return the string in upper case |
||
150 | * |
||
151 | * @return self |
||
152 | */ |
||
153 | 1 | public function toUpper(): self |
|
157 | |||
158 | /** |
||
159 | * Return the string in lower case |
||
160 | * |
||
161 | * @return self |
||
162 | */ |
||
163 | 1 | public function toLower(): self |
|
167 | |||
168 | /** |
||
169 | * Return the string length |
||
170 | * |
||
171 | * @return int |
||
172 | */ |
||
173 | 2 | public function length(): int |
|
177 | |||
178 | /** |
||
179 | * Reverse the string |
||
180 | * |
||
181 | * @return self |
||
182 | */ |
||
183 | 1 | public function reverse(): self |
|
187 | |||
188 | /** |
||
189 | * Pad the string |
||
190 | * |
||
191 | * @param int $length |
||
192 | * @param string $character |
||
193 | * @param int $direction |
||
194 | * |
||
195 | * @return self |
||
196 | */ |
||
197 | 1 | public function pad(int $length, string $character = ' ', int $direction = self::PAD_RIGHT): self |
|
206 | |||
207 | /** |
||
208 | * Pad to the right |
||
209 | * |
||
210 | * @param int $length |
||
211 | * @param string $character |
||
212 | * |
||
213 | * @return self |
||
214 | */ |
||
215 | 1 | public function rightPad(int $length, string $character = ' '): self |
|
219 | |||
220 | /** |
||
221 | * Pad to the left |
||
222 | * |
||
223 | * @param int $length |
||
224 | * @param string $character |
||
225 | * |
||
226 | * @return self |
||
227 | */ |
||
228 | 1 | public function leftPad(int $length, string $character = ' '): self |
|
232 | |||
233 | /** |
||
234 | * Pad both sides |
||
235 | * |
||
236 | * @param int $length |
||
237 | * @param string $character |
||
238 | * |
||
239 | * @return self |
||
240 | */ |
||
241 | 1 | public function uniPad(int $length, string $character = ' '): self |
|
245 | |||
246 | /** |
||
247 | * Find length of initial segment not matching mask |
||
248 | * |
||
249 | * @param string $mask |
||
250 | * @param int $start |
||
251 | * @param int $length |
||
252 | * |
||
253 | * @return int |
||
254 | */ |
||
255 | 1 | public function cspn(string $mask, int $start = 0, int $length = null): int |
|
270 | |||
271 | /** |
||
272 | * Repeat the string n times |
||
273 | * |
||
274 | * @param int $repeat |
||
275 | * |
||
276 | * @return self |
||
277 | */ |
||
278 | 1 | public function repeat(int $repeat): self |
|
282 | |||
283 | /** |
||
284 | * Shuffle the string |
||
285 | * |
||
286 | * @return self |
||
287 | */ |
||
288 | 1 | public function shuffle(): self |
|
292 | |||
293 | /** |
||
294 | * Strip slashes |
||
295 | * |
||
296 | * @return self |
||
297 | */ |
||
298 | 1 | public function stripSlashes(): self |
|
302 | |||
303 | /** |
||
304 | * Strip C-like slashes |
||
305 | * |
||
306 | * @return self |
||
307 | */ |
||
308 | 1 | public function stripCSlashes(): self |
|
312 | |||
313 | /** |
||
314 | * Return the word count |
||
315 | * |
||
316 | * @param string $charlist |
||
317 | * |
||
318 | * @return int |
||
319 | */ |
||
320 | 1 | public function wordCount(string $charlist = ''): int |
|
328 | |||
329 | /** |
||
330 | * Return the collection of words |
||
331 | * |
||
332 | * @param string $charlist |
||
333 | * |
||
334 | * @return TypedCollectionInterface |
||
335 | */ |
||
336 | 1 | public function words(string $charlist = ''): TypedCollectionInterface |
|
349 | |||
350 | /** |
||
351 | * Split the string using a regular expression |
||
352 | * |
||
353 | * @param string $regex |
||
354 | * @param int $limit |
||
355 | * @param int $flags |
||
356 | * |
||
357 | * @return TypedCollectionInterface |
||
358 | */ |
||
359 | 2 | public function pregSplit(string $regex, int $limit = -1, int $flags = self::PREG_NO_FLAGS): TypedCollectionInterface |
|
372 | |||
373 | /** |
||
374 | * Check if the string match the given regular expression |
||
375 | * |
||
376 | * @param string $regex |
||
377 | * @param int $offset |
||
378 | * |
||
379 | * @throws Exception If the regex failed |
||
380 | * |
||
381 | * @return bool |
||
382 | */ |
||
383 | 2 | public function match(string $regex, int $offset = 0): bool |
|
394 | |||
395 | /** |
||
396 | * Return a collection of the elements matching the regex |
||
397 | * |
||
398 | * @param string $regex |
||
399 | * @param int $offset |
||
400 | * @param int $flags |
||
401 | * |
||
402 | * @throws Exception If the regex failed |
||
403 | * |
||
404 | * @return TypedCollectionInterface |
||
405 | */ |
||
406 | 2 | public function getMatches(string $regex, int $offset = 0, int $flags = self::PREG_NO_FLAGS): TypedCollectionInterface |
|
427 | |||
428 | /** |
||
429 | * Replace part of the string by using a regular expression |
||
430 | * |
||
431 | * @param string $regex |
||
432 | * @param string $replacement |
||
433 | * @param int $limit |
||
434 | * |
||
435 | * @throws Exception If the regex failed |
||
436 | * |
||
437 | * @return self |
||
438 | */ |
||
439 | 1 | public function pregReplace(string $regex, string $replacement, int $limit = -1): self |
|
454 | |||
455 | /** |
||
456 | * Return part of the string |
||
457 | * |
||
458 | * @param int $start |
||
459 | * @param int $length |
||
460 | * |
||
461 | * @return self |
||
462 | */ |
||
463 | 1 | public function substring(int $start, int $length = null): self |
|
473 | |||
474 | /** |
||
475 | * Return a formatted string |
||
476 | * |
||
477 | * @return self |
||
478 | */ |
||
479 | 1 | public function sprintf(): self |
|
487 | |||
488 | /** |
||
489 | * Return the string with the first letter as uppercase |
||
490 | * |
||
491 | * @return self |
||
492 | */ |
||
493 | 2 | public function ucfirst(): self |
|
497 | |||
498 | /** |
||
499 | * Return the string with the first letter as lowercase |
||
500 | * |
||
501 | * @return self |
||
502 | */ |
||
503 | 1 | public function lcfirst(): self |
|
507 | |||
508 | /** |
||
509 | * Return a CamelCase representation of the string |
||
510 | * |
||
511 | * @return self |
||
512 | */ |
||
513 | 1 | public function camelize(): self |
|
524 | |||
525 | /** |
||
526 | * Append a string at the end of the current one |
||
527 | * |
||
528 | * @param string $string |
||
529 | * |
||
530 | * @return self |
||
531 | */ |
||
532 | 2 | public function append(string $string): self |
|
536 | |||
537 | /** |
||
538 | * Prepend a string at the beginning of the current one |
||
539 | * |
||
540 | * @param string $string |
||
541 | * |
||
542 | * @return self |
||
543 | */ |
||
544 | 1 | public function prepend(string $string): self |
|
548 | |||
549 | /** |
||
550 | * Check if the 2 strings are equal |
||
551 | * |
||
552 | * @param self $string |
||
553 | * |
||
554 | * @return bool |
||
555 | */ |
||
556 | 11 | public function equals(self $string): bool |
|
560 | |||
561 | /** |
||
562 | * Trim the string |
||
563 | * |
||
564 | * @param string $mask |
||
565 | * |
||
566 | * @return self |
||
567 | */ |
||
568 | 1 | public function trim(string $mask = null): self |
|
572 | |||
573 | /** |
||
574 | * Trim the right side of the string |
||
575 | * |
||
576 | * @param string $mask |
||
577 | * |
||
578 | * @return self |
||
579 | */ |
||
580 | 1 | public function rightTrim(string $mask = null): self |
|
584 | |||
585 | /** |
||
586 | * Trim the left side of the string |
||
587 | * |
||
588 | * @param string $mask |
||
589 | * |
||
590 | * @return self |
||
591 | */ |
||
592 | 1 | public function leftTrim(string $mask = null): self |
|
596 | } |
||
597 |
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.