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 JsonEncoder 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 JsonEncoder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class JsonEncoder |
||
22 | { |
||
23 | /** |
||
24 | * Encode a value as JSON array. |
||
25 | */ |
||
26 | const JSON_ARRAY = 1; |
||
27 | |||
28 | /** |
||
29 | * Encode a value as JSON object. |
||
30 | */ |
||
31 | const JSON_OBJECT = 2; |
||
32 | |||
33 | /** |
||
34 | * Encode a value as JSON string. |
||
35 | */ |
||
36 | const JSON_STRING = 3; |
||
37 | |||
38 | /** |
||
39 | * Encode a value as JSON integer or float. |
||
40 | */ |
||
41 | const JSON_NUMBER = 4; |
||
42 | |||
43 | /** |
||
44 | * @var JsonValidator |
||
45 | */ |
||
46 | private $validator; |
||
47 | |||
48 | /** |
||
49 | * @var int |
||
50 | */ |
||
51 | private $arrayEncoding = self::JSON_ARRAY; |
||
52 | |||
53 | /** |
||
54 | * @var int |
||
55 | */ |
||
56 | private $numericEncoding = self::JSON_STRING; |
||
57 | |||
58 | /** |
||
59 | * @var bool |
||
60 | */ |
||
61 | private $gtLtEscaped = false; |
||
62 | |||
63 | /** |
||
64 | * @var bool |
||
65 | */ |
||
66 | private $ampersandEscaped = false; |
||
67 | |||
68 | /** |
||
69 | * @var bool |
||
70 | */ |
||
71 | private $singleQuoteEscaped = false; |
||
72 | |||
73 | /** |
||
74 | * @var bool |
||
75 | */ |
||
76 | private $doubleQuoteEscaped = false; |
||
77 | |||
78 | /** |
||
79 | * @var bool |
||
80 | */ |
||
81 | private $slashEscaped = true; |
||
82 | |||
83 | /** |
||
84 | * @var bool |
||
85 | */ |
||
86 | private $unicodeEscaped = true; |
||
87 | |||
88 | /** |
||
89 | * @var bool |
||
90 | */ |
||
91 | private $prettyPrinting = false; |
||
92 | |||
93 | /** |
||
94 | * @var bool |
||
95 | */ |
||
96 | private $terminatedWithLineFeed = false; |
||
97 | |||
98 | /** |
||
99 | * @var int |
||
100 | */ |
||
101 | private $maxDepth = 512; |
||
102 | |||
103 | /** |
||
104 | * Creates a new encoder. |
||
105 | * |
||
106 | * @param null|JsonValidator $validator |
||
107 | */ |
||
108 | 61 | public function __construct(JsonValidator $validator = null) |
|
112 | |||
113 | /** |
||
114 | * Encodes data as JSON. |
||
115 | * |
||
116 | * If a schema is passed, the value is validated against that schema before |
||
117 | * encoding. The schema may be passed as file path or as object returned |
||
118 | * from `JsonDecoder::decodeFile($schemaFile)`. |
||
119 | * |
||
120 | * You can adjust the decoding with the various setters in this class. |
||
121 | * |
||
122 | * @param mixed $data The data to encode |
||
123 | * @param string|object $schema The schema file or object |
||
|
|||
124 | * |
||
125 | * @return string The JSON string |
||
126 | * |
||
127 | * @throws EncodingFailedException If the data could not be encoded |
||
128 | * @throws ValidationFailedException If the data fails schema validation |
||
129 | * @throws InvalidSchemaException If the schema is invalid |
||
130 | */ |
||
131 | 53 | public function encode($data, $schema = null) |
|
232 | |||
233 | /** |
||
234 | * Encodes data into a JSON file. |
||
235 | * |
||
236 | * @param mixed $data The data to encode |
||
237 | * @param string $path The path where the JSON file will be stored |
||
238 | * @param string|object $schema The schema file or object |
||
239 | * |
||
240 | * @throws EncodingFailedException If the data could not be encoded |
||
241 | * @throws ValidationFailedException If the data fails schema validation |
||
242 | * @throws InvalidSchemaException If the schema is invalid |
||
243 | * |
||
244 | * @see encode |
||
245 | */ |
||
246 | 6 | public function encodeFile($data, $path, $schema = null) |
|
306 | |||
307 | /** |
||
308 | * Returns the encoding of non-associative arrays. |
||
309 | * |
||
310 | * @return int One of the constants {@link JSON_OBJECT} and {@link JSON_ARRAY} |
||
311 | */ |
||
312 | public function getArrayEncoding() |
||
316 | |||
317 | /** |
||
318 | * Sets the encoding of non-associative arrays. |
||
319 | * |
||
320 | * By default, non-associative arrays are decoded as JSON arrays. |
||
321 | * |
||
322 | * @param int $encoding One of the constants {@link JSON_OBJECT} and {@link JSON_ARRAY} |
||
323 | * |
||
324 | * @throws \InvalidArgumentException If the passed encoding is invalid |
||
325 | */ |
||
326 | 5 | View Code Duplication | public function setArrayEncoding($encoding) |
338 | |||
339 | /** |
||
340 | * Returns the encoding of numeric strings. |
||
341 | * |
||
342 | * @return int One of the constants {@link JSON_STRING} and {@link JSON_NUMBER} |
||
343 | */ |
||
344 | public function getNumericEncoding() |
||
348 | |||
349 | /** |
||
350 | * Sets the encoding of numeric strings. |
||
351 | * |
||
352 | * By default, non-associative arrays are decoded as JSON strings. |
||
353 | * |
||
354 | * @param int $encoding One of the constants {@link JSON_STRING} and {@link JSON_NUMBER} |
||
355 | * |
||
356 | * @throws \InvalidArgumentException If the passed encoding is invalid |
||
357 | */ |
||
358 | 6 | View Code Duplication | public function setNumericEncoding($encoding) |
370 | |||
371 | /** |
||
372 | * Returns whether ampersands (&) are escaped. |
||
373 | * |
||
374 | * If `true`, ampersands will be escaped as "\u0026". |
||
375 | * |
||
376 | * By default, ampersands are not escaped. |
||
377 | * |
||
378 | * @return bool Whether ampersands are escaped |
||
379 | */ |
||
380 | public function isAmpersandEscaped() |
||
384 | |||
385 | /** |
||
386 | * Sets whether ampersands (&) should be escaped. |
||
387 | * |
||
388 | * If `true`, ampersands will be escaped as "\u0026". |
||
389 | * |
||
390 | * By default, ampersands are not escaped. |
||
391 | * |
||
392 | * @param bool $enabled Whether ampersands should be escaped |
||
393 | */ |
||
394 | 2 | public function setEscapeAmpersand($enabled) |
|
398 | |||
399 | /** |
||
400 | * Returns whether double quotes (") are escaped. |
||
401 | * |
||
402 | * If `true`, double quotes will be escaped as "\u0022". |
||
403 | * |
||
404 | * By default, double quotes are not escaped. |
||
405 | * |
||
406 | * @return bool Whether double quotes are escaped |
||
407 | */ |
||
408 | public function isDoubleQuoteEscaped() |
||
412 | |||
413 | /** |
||
414 | * Sets whether double quotes (") should be escaped. |
||
415 | * |
||
416 | * If `true`, double quotes will be escaped as "\u0022". |
||
417 | * |
||
418 | * By default, double quotes are not escaped. |
||
419 | * |
||
420 | * @param bool $enabled Whether double quotes should be escaped |
||
421 | */ |
||
422 | 2 | public function setEscapeDoubleQuote($enabled) |
|
426 | |||
427 | /** |
||
428 | * Returns whether single quotes (') are escaped. |
||
429 | * |
||
430 | * If `true`, single quotes will be escaped as "\u0027". |
||
431 | * |
||
432 | * By default, single quotes are not escaped. |
||
433 | * |
||
434 | * @return bool Whether single quotes are escaped |
||
435 | */ |
||
436 | public function isSingleQuoteEscaped() |
||
440 | |||
441 | /** |
||
442 | * Sets whether single quotes (") should be escaped. |
||
443 | * |
||
444 | * If `true`, single quotes will be escaped as "\u0027". |
||
445 | * |
||
446 | * By default, single quotes are not escaped. |
||
447 | * |
||
448 | * @param bool $enabled Whether single quotes should be escaped |
||
449 | */ |
||
450 | 2 | public function setEscapeSingleQuote($enabled) |
|
454 | |||
455 | /** |
||
456 | * Returns whether forward slashes (/) are escaped. |
||
457 | * |
||
458 | * If `true`, forward slashes will be escaped as "\/". |
||
459 | * |
||
460 | * By default, forward slashes are not escaped. |
||
461 | * |
||
462 | * @return bool Whether forward slashes are escaped |
||
463 | */ |
||
464 | public function isSlashEscaped() |
||
468 | |||
469 | /** |
||
470 | * Sets whether forward slashes (") should be escaped. |
||
471 | * |
||
472 | * If `true`, forward slashes will be escaped as "\/". |
||
473 | * |
||
474 | * By default, forward slashes are not escaped. |
||
475 | * |
||
476 | * @param bool $enabled Whether forward slashes should be escaped |
||
477 | */ |
||
478 | 2 | public function setEscapeSlash($enabled) |
|
482 | |||
483 | /** |
||
484 | * Returns whether greater than/less than symbols (>, <) are escaped. |
||
485 | * |
||
486 | * If `true`, greater than will be escaped as "\u003E" and less than as |
||
487 | * "\u003C". |
||
488 | * |
||
489 | * By default, greater than/less than symbols are not escaped. |
||
490 | * |
||
491 | * @return bool Whether greater than/less than symbols are escaped |
||
492 | */ |
||
493 | public function isGtLtEscaped() |
||
497 | |||
498 | /** |
||
499 | * Sets whether greater than/less than symbols (>, <) should be escaped. |
||
500 | * |
||
501 | * If `true`, greater than will be escaped as "\u003E" and less than as |
||
502 | * "\u003C". |
||
503 | * |
||
504 | * By default, greater than/less than symbols are not escaped. |
||
505 | * |
||
506 | * @param bool $enabled Whether greater than/less than should be escaped |
||
507 | */ |
||
508 | 2 | public function setEscapeGtLt($enabled) |
|
512 | |||
513 | /** |
||
514 | * Returns whether unicode characters are escaped. |
||
515 | * |
||
516 | * If `true`, unicode characters will be escaped as hexadecimals strings. |
||
517 | * For example, "ü" will be escaped as "\u00fc". |
||
518 | * |
||
519 | * By default, unicode characters are escaped. |
||
520 | * |
||
521 | * @return bool Whether unicode characters are escaped |
||
522 | */ |
||
523 | public function isUnicodeEscaped() |
||
527 | |||
528 | /** |
||
529 | * Sets whether unicode characters should be escaped. |
||
530 | * |
||
531 | * If `true`, unicode characters will be escaped as hexadecimals strings. |
||
532 | * For example, "ü" will be escaped as "\u00fc". |
||
533 | * |
||
534 | * By default, unicode characters are escaped. |
||
535 | * |
||
536 | * @param bool $enabled Whether unicode characters should be escaped |
||
537 | */ |
||
538 | 2 | public function setEscapeUnicode($enabled) |
|
542 | |||
543 | /** |
||
544 | * Returns whether JSON strings are formatted for better readability. |
||
545 | * |
||
546 | * If `true`, line breaks will be added after object properties and array |
||
547 | * entries. Each new nesting level will be indented by four spaces. |
||
548 | * |
||
549 | * By default, pretty printing is not enabled. |
||
550 | * |
||
551 | * @return bool Whether JSON strings are formatted |
||
552 | */ |
||
553 | public function isPrettyPrinting() |
||
557 | |||
558 | /** |
||
559 | * Sets whether JSON strings should be formatted for better readability. |
||
560 | * |
||
561 | * If `true`, line breaks will be added after object properties and array |
||
562 | * entries. Each new nesting level will be indented by four spaces. |
||
563 | * |
||
564 | * By default, pretty printing is not enabled. |
||
565 | * |
||
566 | * @param bool $prettyPrinting Whether JSON strings should be formatted |
||
567 | */ |
||
568 | 2 | public function setPrettyPrinting($prettyPrinting) |
|
572 | |||
573 | /** |
||
574 | * Returns whether JSON strings are terminated with a line feed. |
||
575 | * |
||
576 | * By default, JSON strings are not terminated with a line feed. |
||
577 | * |
||
578 | * @return bool Whether JSON strings are terminated with a line feed |
||
579 | */ |
||
580 | public function isTerminatedWithLineFeed() |
||
584 | |||
585 | /** |
||
586 | * Sets whether JSON strings should be terminated with a line feed. |
||
587 | * |
||
588 | * By default, JSON strings are not terminated with a line feed. |
||
589 | * |
||
590 | * @param bool $enabled Whether JSON strings should be terminated with a |
||
591 | * line feed |
||
592 | */ |
||
593 | 2 | public function setTerminateWithLineFeed($enabled) |
|
597 | |||
598 | /** |
||
599 | * Returns the maximum recursion depth. |
||
600 | * |
||
601 | * A depth of zero means that objects are not allowed. A depth of one means |
||
602 | * only one level of objects or arrays is allowed. |
||
603 | * |
||
604 | * @return int The maximum recursion depth |
||
605 | */ |
||
606 | public function getMaxDepth() |
||
610 | |||
611 | /** |
||
612 | * Sets the maximum recursion depth. |
||
613 | * |
||
614 | * If the depth is exceeded during encoding, an {@link EncodingFailedException} |
||
615 | * will be thrown. |
||
616 | * |
||
617 | * A depth of zero means that objects are not allowed. A depth of one means |
||
618 | * only one level of objects or arrays is allowed. |
||
619 | * |
||
620 | * @param int $maxDepth The maximum recursion depth |
||
621 | * |
||
622 | * @throws \InvalidArgumentException If the depth is not an integer greater |
||
623 | * than or equal to zero |
||
624 | */ |
||
625 | 6 | View Code Duplication | public function setMaxDepth($maxDepth) |
643 | } |
||
644 |
This check looks for
@param
annotations where the type inferred by our type inference engine differs from the declared type.It makes a suggestion as to what type it considers more descriptive.
Most often this is a case of a parameter that can be null in addition to its declared types.