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 Stream 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 Stream, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class Stream |
||
18 | { |
||
19 | /** |
||
20 | * @var string[] |
||
21 | */ |
||
22 | private static $readModes = ['r', 'w+', 'r+', 'x+', 'c+', 'rb', 'w+b', 'r+b', 'x+b', 'c+b', 'rt', 'w+t', |
||
23 | 'r+t', 'x+t', 'c+t', 'a+']; |
||
24 | |||
25 | /** |
||
26 | * @var string[] |
||
27 | */ |
||
28 | private static $writeModes = ['w', 'w+', 'rw', 'r+', 'x+', 'c+', 'wb', 'w+b', 'r+b', 'x+b', 'c+b', 'w+t', |
||
29 | 'r+t', 'x+t', 'c+t', 'a', 'a+']; |
||
30 | |||
31 | /** |
||
32 | * @var resource |
||
33 | */ |
||
34 | protected $resource; |
||
35 | |||
36 | /** |
||
37 | * @var bool |
||
38 | */ |
||
39 | protected $local; |
||
40 | |||
41 | /** |
||
42 | * @var bool |
||
43 | */ |
||
44 | protected $readable; |
||
45 | |||
46 | /** |
||
47 | * @var bool |
||
48 | */ |
||
49 | protected $writable; |
||
50 | |||
51 | /** |
||
52 | * @var bool |
||
53 | */ |
||
54 | protected $seekable; |
||
55 | |||
56 | /** |
||
57 | * @var string |
||
58 | */ |
||
59 | protected $uri; |
||
60 | |||
61 | /** |
||
62 | * @var int |
||
63 | */ |
||
64 | protected $byteOrder; |
||
65 | |||
66 | /** |
||
67 | * @var int |
||
68 | */ |
||
69 | protected static $machineByteOrder; |
||
70 | |||
71 | /** |
||
72 | * Create stream object from resource. |
||
73 | * |
||
74 | * @param resource $resource |
||
75 | * |
||
76 | * @throws Exception\InvalidArgumentException An exception will be thrown for invalid resource arguments. |
||
77 | * |
||
78 | * @return static |
||
79 | */ |
||
80 | 50 | public static function fromResource($resource) |
|
90 | |||
91 | /** |
||
92 | * Bind resource to stream and gather meta data. |
||
93 | * |
||
94 | * @param resource $resource The resource to bind to the stream. |
||
95 | * |
||
96 | * @throws Exception\InvalidArgumentException An exception will be thrown for invalid resource arguments. |
||
97 | * |
||
98 | * @return $this |
||
99 | */ |
||
100 | 48 | public function bindResource($resource) |
|
117 | |||
118 | /** |
||
119 | * Return whether the stream is local. |
||
120 | * |
||
121 | * @return bool |
||
122 | */ |
||
123 | 6 | public function isLocal() |
|
127 | |||
128 | /** |
||
129 | * Return whether read access on the stream will be granted. |
||
130 | * |
||
131 | * @return bool |
||
132 | */ |
||
133 | 8 | public function isReadable() |
|
137 | |||
138 | /** |
||
139 | * Return whether write access on the stream will be granted. |
||
140 | * |
||
141 | * @return bool |
||
142 | */ |
||
143 | 12 | public function isWritable() |
|
147 | |||
148 | /** |
||
149 | * Return whether the stream can be sought. |
||
150 | * |
||
151 | * @return bool |
||
152 | */ |
||
153 | 12 | public function isSeekable() |
|
157 | |||
158 | /** |
||
159 | * Get the URI or filename associated with the stream. |
||
160 | * |
||
161 | * @return string |
||
162 | */ |
||
163 | 4 | public function getUri() |
|
167 | |||
168 | /** |
||
169 | * Get the byte order for integer handling. |
||
170 | * |
||
171 | * @return int |
||
172 | */ |
||
173 | 4 | public function getByteOrder() |
|
174 | { |
||
175 | 4 | if (null === $this->byteOrder) { |
|
176 | 2 | return ByteOrder::MACHINE_ENDIAN; |
|
177 | } |
||
178 | |||
179 | 2 | return $this->byteOrder; |
|
180 | } |
||
181 | |||
182 | /** |
||
183 | * Set the byte order for integer handling. |
||
184 | * |
||
185 | * @param int $byteOrder The byte order to set. Must be one of the constants defined by the byte order enum. |
||
186 | * |
||
187 | * @throws Exception\InvalidArgumentException An exception will be thrown for invalid byte order arguments. |
||
188 | * |
||
189 | * @return $this |
||
190 | */ |
||
191 | 4 | public function setByteOrder($byteOrder) |
|
192 | { |
||
193 | 4 | if (!in_array($byteOrder, ByteOrder::values(), true)) { |
|
194 | 2 | throw new Exception\InvalidArgumentException('Invalid byte order'); |
|
195 | } |
||
196 | |||
197 | 2 | $this->byteOrder = $byteOrder; |
|
198 | |||
199 | 2 | return $this; |
|
200 | } |
||
201 | |||
202 | /** |
||
203 | * Get the machine byte order. |
||
204 | * |
||
205 | * @return int |
||
206 | */ |
||
207 | 184 | public function getMachineByteOrder() |
|
208 | { |
||
209 | 184 | if (null === static::$machineByteOrder) { |
|
210 | 2 | static::$machineByteOrder = ByteOrder::BIG_ENDIAN; |
|
211 | |||
212 | 2 | list(, $value) = @unpack('s', "\x01\x00"); |
|
213 | 2 | if (1 === $value) { |
|
214 | 2 | static::$machineByteOrder = ByteOrder::LITTLE_ENDIAN; |
|
215 | 1 | } |
|
216 | 1 | } |
|
217 | |||
218 | 184 | return static::$machineByteOrder; |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * Get information about the stream. |
||
223 | * |
||
224 | * @param string $info The information to retrieve. |
||
225 | * |
||
226 | * @throws Exception\IOException An exception will be thrown for invalid stream resources. |
||
227 | * |
||
228 | * @return int |
||
229 | */ |
||
230 | 4 | protected function getStat($info) |
|
245 | |||
246 | /** |
||
247 | * Get size of the stream in bytes |
||
248 | * |
||
249 | * @throws Exception\BadMethodCallException An exception will be thrown for non-local streams. |
||
250 | * @throws Exception\IOException An exception will be thrown for invalid stream resources. |
||
251 | * |
||
252 | * @return int |
||
253 | */ |
||
254 | 6 | public function getSize() |
|
262 | |||
263 | /** |
||
264 | * Return whether the end of the stream was reached. |
||
265 | * |
||
266 | * @throws Exception\IOException An exception will be thrown for invalid stream resources. |
||
267 | * |
||
268 | * @return bool |
||
269 | * @link http://www.php.net/manual/en/function.feof.php |
||
270 | */ |
||
271 | 4 | public function eof() |
|
279 | |||
280 | /** |
||
281 | * Return the current position of the stream. |
||
282 | * |
||
283 | * @throws Exception\IOException An exception will be thrown for invalid stream resources. |
||
284 | * |
||
285 | * @return int |
||
286 | * @link http://www.php.net/manual/en/function.ftell.php |
||
287 | */ |
||
288 | 8 | public function tell() |
|
296 | |||
297 | /** |
||
298 | * Seek and return the position of the stream. |
||
299 | * |
||
300 | * @param int $offset The offset. |
||
301 | * @param int $whence Either SEEK_SET (which is default), SEEK_CUR or SEEK_END. |
||
302 | * |
||
303 | * @throws Exception\BadMethodCallException An exception will be thrown for non-seekable streams. |
||
304 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
305 | * position could not be set. |
||
306 | * |
||
307 | * @return int |
||
308 | * @link http://www.php.net/manual/en/function.fseek.php |
||
309 | */ |
||
310 | 12 | public function seek($offset, $whence = SEEK_SET) |
|
326 | |||
327 | /** |
||
328 | * Rewind the position of the stream. |
||
329 | * |
||
330 | * @throws Exception\BadMethodCallException An exception will be thrown for non-seekable streams. |
||
331 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
332 | * position could not be set. |
||
333 | * |
||
334 | * @return int |
||
335 | */ |
||
336 | 2 | public function rewind() |
|
340 | |||
341 | /** |
||
342 | * Align the data in relation to the byte order. |
||
343 | * |
||
344 | * @param string $data |
||
345 | * |
||
346 | * @return string |
||
347 | */ |
||
348 | 276 | protected function alignData($data) |
|
358 | |||
359 | /** |
||
360 | * Read up to $length number of bytes of data from the stream. |
||
361 | * |
||
362 | * @param int $length The maximum number of bytes to read. |
||
363 | * |
||
364 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
365 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
366 | * data could not be read. |
||
367 | * |
||
368 | * @return string |
||
369 | * @link http://www.php.net/manual/en/function.fread.php |
||
370 | */ |
||
371 | 8 | View Code Duplication | public function read($length) |
388 | |||
389 | /** |
||
390 | * Read signed 8-bit integer (char) data from the stream. |
||
391 | * |
||
392 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
393 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
394 | * data could not be read. |
||
395 | * |
||
396 | * @return int |
||
397 | */ |
||
398 | 6 | public function readInt8() |
|
399 | { |
||
400 | 6 | list(, $value) = @unpack('c', $this->read(1)); |
|
401 | 6 | return $value; |
|
402 | } |
||
403 | |||
404 | /** |
||
405 | * Read unsigned 8-bit integer (char) data from the stream. |
||
406 | * |
||
407 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
408 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
409 | * data could not be read. |
||
410 | * |
||
411 | * @return int |
||
412 | */ |
||
413 | 8 | public function readUInt8() |
|
418 | |||
419 | /** |
||
420 | * Read signed 16-bit integer (short) data from the stream. |
||
421 | * |
||
422 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
423 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
424 | * data could not be read. |
||
425 | * |
||
426 | * @return int |
||
427 | */ |
||
428 | 18 | public function readInt16() |
|
433 | |||
434 | /** |
||
435 | * Read unsigned 16-bit integer (short) data from the stream. |
||
436 | * |
||
437 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
438 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
439 | * data could not be read. |
||
440 | * |
||
441 | * @return int |
||
442 | */ |
||
443 | 30 | View Code Duplication | public function readUInt16() |
459 | |||
460 | /** |
||
461 | * Read signed 24-bit integer data from the stream. |
||
462 | * |
||
463 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
464 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
465 | * data could not be read. |
||
466 | * |
||
467 | * @return int |
||
468 | */ |
||
469 | 18 | public function readInt24() |
|
470 | { |
||
471 | 18 | $value = $this->readUInt24(); |
|
472 | |||
473 | 18 | if ($value & 0x800000) { |
|
474 | 6 | $value -= 0x1000000; |
|
475 | 3 | } |
|
476 | |||
477 | 18 | return $value; |
|
478 | } |
||
479 | |||
480 | /** |
||
481 | * Read unsigned 24-bit integer data from the stream. |
||
482 | * |
||
483 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
484 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
485 | * data could not be read. |
||
486 | * |
||
487 | * @return int |
||
488 | */ |
||
489 | 42 | public function readUInt24() |
|
494 | |||
495 | /** |
||
496 | * Read signed 32-bit integer (long) data from the stream. |
||
497 | * |
||
498 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
499 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
500 | * data could not be read. |
||
501 | * |
||
502 | * @return int |
||
503 | */ |
||
504 | 18 | public function readInt32() |
|
509 | |||
510 | /** |
||
511 | * Read unsigned 32-bit integer (long) data from the stream. |
||
512 | * |
||
513 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
514 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
515 | * data could not be read. |
||
516 | * |
||
517 | * @return int |
||
518 | */ |
||
519 | 24 | View Code Duplication | public function readUInt32() |
520 | { |
||
521 | 24 | switch ($this->getByteOrder()) { |
|
522 | 24 | case ByteOrder::BIG_ENDIAN: |
|
523 | 8 | $format = 'N'; |
|
524 | 8 | break; |
|
525 | 16 | case ByteOrder::LITTLE_ENDIAN: |
|
526 | 8 | $format = 'V'; |
|
527 | 8 | break; |
|
528 | 4 | default: |
|
529 | 8 | $format = 'L'; |
|
530 | 12 | } |
|
531 | |||
532 | 24 | list(, $value) = @unpack($format, $this->read(4)); |
|
533 | 24 | return $value; |
|
534 | } |
||
535 | |||
536 | /** |
||
537 | * Read signed 48-bit integer data from the stream. |
||
538 | * |
||
539 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
540 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
541 | * data could not be read. |
||
542 | * |
||
543 | * @return int |
||
544 | */ |
||
545 | 18 | public function readInt48() |
|
555 | |||
556 | /** |
||
557 | * Read unsigned 48-bit integer data from the stream. |
||
558 | * |
||
559 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
560 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
561 | * data could not be read. |
||
562 | * |
||
563 | * @return int |
||
564 | */ |
||
565 | 42 | public function readUInt48() |
|
570 | |||
571 | /** |
||
572 | * Read signed 64-bit integer (long long) data from the stream. |
||
573 | * |
||
574 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
575 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
576 | * data could not be read. |
||
577 | * |
||
578 | * @return int |
||
579 | */ |
||
580 | 18 | public function readInt64() |
|
585 | |||
586 | /** |
||
587 | * Read unsigned 64-bit integer (long long) data from the stream. |
||
588 | * |
||
589 | * @throws Exception\BadMethodCallException An exception will be thrown for non-readable streams. |
||
590 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
591 | * data could not be read. |
||
592 | * |
||
593 | * @return int |
||
594 | */ |
||
595 | 24 | View Code Duplication | public function readUInt64() |
596 | { |
||
597 | 24 | switch ($this->getByteOrder()) { |
|
598 | 24 | case ByteOrder::BIG_ENDIAN: |
|
599 | 8 | $format = 'J'; |
|
600 | 8 | break; |
|
601 | 16 | case ByteOrder::LITTLE_ENDIAN: |
|
602 | 8 | $format = 'P'; |
|
603 | 8 | break; |
|
604 | 4 | default: |
|
605 | 8 | $format = 'Q'; |
|
606 | 12 | } |
|
607 | |||
608 | 24 | list(, $value) = @unpack($format, $this->read(8)); |
|
609 | 24 | return $value; |
|
610 | } |
||
611 | |||
612 | /** |
||
613 | * Write data to the stream and return the number of bytes written. |
||
614 | * |
||
615 | * @param string $data The data |
||
616 | * |
||
617 | * @throws Exception\BadMethodCallException An exception will be thrown for non-writable streams. |
||
618 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
619 | * data could not be written. |
||
620 | * |
||
621 | * @return int |
||
622 | * @link http://www.php.net/manual/en/function.fwrite.php |
||
623 | */ |
||
624 | 8 | View Code Duplication | public function write($data) |
641 | |||
642 | /** |
||
643 | * Write signed 8-bit integer (char) data to the stream |
||
644 | * |
||
645 | * @param int $value The value |
||
646 | * |
||
647 | * @return int |
||
648 | */ |
||
649 | 6 | public function writeInt8($value) |
|
650 | { |
||
651 | 6 | return $this->write(pack('c', $value)); |
|
652 | } |
||
653 | |||
654 | /** |
||
655 | * Write unsigned 8-bit integer (char) data to the stream |
||
656 | * |
||
657 | * @param int $value The value |
||
658 | * |
||
659 | * @return int |
||
660 | */ |
||
661 | 8 | public function writeUInt8($value) |
|
665 | |||
666 | /** |
||
667 | * Write signed 16-bit integer (short) data to the stream |
||
668 | * |
||
669 | * @param int $value The value |
||
670 | * |
||
671 | * @return int |
||
672 | */ |
||
673 | 18 | public function writeInt16($value) |
|
677 | |||
678 | /** |
||
679 | * Write unsigned 16-bit integer (short) data to the stream |
||
680 | * |
||
681 | * @param int $value The value |
||
682 | * |
||
683 | * @return int |
||
684 | */ |
||
685 | 30 | public function writeUInt16($value) |
|
700 | |||
701 | /** |
||
702 | * Write signed 24-bit integer data to the stream |
||
703 | * |
||
704 | * @param int $value The value |
||
705 | * |
||
706 | * @return int |
||
707 | */ |
||
708 | 18 | public function writeInt24($value) |
|
709 | { |
||
710 | 18 | if ($value & 0x7fffff) { |
|
711 | 6 | $value += 0x1000000; |
|
712 | 3 | } |
|
713 | |||
714 | 18 | return $this->writeUInt24($value); |
|
715 | } |
||
716 | |||
717 | /** |
||
718 | * Write unsigned 24-bit integer data to the stream |
||
719 | * |
||
720 | * @param int $value The value |
||
721 | * |
||
722 | * @return int |
||
723 | */ |
||
724 | 42 | public function writeUInt24($value) |
|
728 | |||
729 | /** |
||
730 | * Write signed 32-bit integer (long) data to the stream |
||
731 | * |
||
732 | * @param int $value The value |
||
733 | * |
||
734 | * @return int |
||
735 | */ |
||
736 | 18 | public function writeInt32($value) |
|
737 | { |
||
738 | 18 | return $this->write($this->alignData(pack('l', $value))); |
|
739 | } |
||
740 | |||
741 | /** |
||
742 | * Write unsigned 32-bit integer (long) data to the stream |
||
743 | * |
||
744 | * @param int $value The value |
||
745 | * |
||
746 | * @return int |
||
747 | */ |
||
748 | 24 | public function writeUInt32($value) |
|
749 | { |
||
750 | 24 | switch ($this->getByteOrder()) { |
|
751 | 24 | case ByteOrder::BIG_ENDIAN: |
|
752 | 8 | $format = 'N'; |
|
753 | 8 | break; |
|
754 | 16 | case ByteOrder::LITTLE_ENDIAN: |
|
755 | 8 | $format = 'V'; |
|
756 | 8 | break; |
|
757 | 4 | default: |
|
758 | 8 | $format = 'L'; |
|
759 | 12 | } |
|
760 | |||
761 | 24 | return $this->write(pack($format, $value)); |
|
762 | } |
||
763 | |||
764 | /** |
||
765 | * Write signed 48-bit integer data to the stream |
||
766 | * |
||
767 | * @param int $value The value |
||
768 | * |
||
769 | * @return int |
||
770 | */ |
||
771 | 18 | public function writeInt48($value) |
|
779 | |||
780 | /** |
||
781 | * Write unsigned 48-bit integer data to the stream |
||
782 | * |
||
783 | * @param int $value The value |
||
784 | * |
||
785 | * @return int |
||
786 | */ |
||
787 | 42 | public function writeUInt48($value) |
|
793 | |||
794 | /** |
||
795 | * Write signed 64-bit integer (long long) data to the stream |
||
796 | * |
||
797 | * @param int $value The value |
||
798 | * |
||
799 | * @return int |
||
800 | */ |
||
801 | 18 | public function writeInt64($value) |
|
805 | |||
806 | /** |
||
807 | * Write unsigned 64-bit integer (long long) data to the stream |
||
808 | * |
||
809 | * @param int $value The value |
||
810 | * |
||
811 | * @return int |
||
812 | */ |
||
813 | 24 | public function writeUInt64($value) |
|
814 | { |
||
815 | 24 | switch ($this->getByteOrder()) { |
|
816 | 24 | case ByteOrder::BIG_ENDIAN: |
|
817 | 8 | $format = 'J'; |
|
818 | 8 | break; |
|
819 | 16 | case ByteOrder::LITTLE_ENDIAN: |
|
820 | 8 | $format = 'P'; |
|
821 | 8 | break; |
|
822 | 4 | default: |
|
823 | 8 | $format = 'Q'; |
|
824 | 12 | } |
|
825 | |||
826 | 24 | return $this->write(pack($format, $value)); |
|
827 | } |
||
828 | |||
829 | /** |
||
830 | * Truncate the stream to a given length. |
||
831 | * |
||
832 | * @param int $size The size to truncate to. |
||
833 | * |
||
834 | * @throws Exception\BadMethodCallException An exception will be thrown for non-writable streams. |
||
835 | * @throws Exception\IOException An exception will be thrown for invalid stream resources or when the |
||
836 | * stream could not be truncated. |
||
837 | * |
||
838 | * @return bool |
||
839 | * @link http://www.php.net/manual/en/function.ftruncate.php |
||
840 | */ |
||
841 | 6 | public function truncate($size) |
|
853 | |||
854 | /** |
||
855 | * Close the stream. |
||
856 | * |
||
857 | * @return bool |
||
858 | * @link http://www.php.net/manual/en/function.fclose.php |
||
859 | */ |
||
860 | 16 | public function close() |
|
864 | } |
||
865 |
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.