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 |
||
11 | class IcoParser implements ParserInterface |
||
12 | { |
||
13 | /** |
||
14 | * @inheritdoc |
||
15 | */ |
||
16 | 5 | public function isSupportedBinaryString($data) |
|
20 | |||
21 | /** |
||
22 | * Reads the ICONDIR header and verifies it looks sane |
||
23 | * @param string $data |
||
24 | * @return array|null - null is returned if the file doesn't look like an .ico file |
||
25 | */ |
||
26 | 17 | private function parseIconDir($data) |
|
34 | |||
35 | /** |
||
36 | * @inheritdoc |
||
37 | */ |
||
38 | 16 | public function parse($data) |
|
39 | { |
||
40 | 16 | $icondir = $this->parseIconDir($data); |
|
41 | 16 | if (!$icondir) { |
|
42 | 1 | throw new \InvalidArgumentException('Invalid ICO file format'); |
|
43 | } |
||
44 | |||
45 | //nibble the header off our data |
||
46 | 15 | $data = substr($data, 6); |
|
47 | |||
48 | //parse the ICONDIRENTRY headers |
||
49 | 15 | $icon = new Icon(); |
|
50 | 15 | $data = $this->parseIconDirEntries($icon, $data, $icondir['Count']); |
|
51 | |||
52 | // Extract additional headers for each extracted ICONDIRENTRY |
||
53 | 15 | for ($i = 0; $i < count($icon); ++$i) { |
|
|
|||
54 | 15 | $signature = unpack('LFourCC', substr($data, $icon[$i]->fileOffset, 4)); |
|
55 | 15 | if ($signature['FourCC'] == 0x474e5089) { |
|
56 | 2 | $this->parsePng($icon[$i], $data); |
|
57 | 2 | } else { |
|
58 | 15 | $this->parseBmp($icon[$i], $data); |
|
59 | } |
||
60 | 15 | } |
|
61 | |||
62 | 15 | return $icon; |
|
63 | } |
||
64 | |||
65 | 15 | private function parseIconDirEntries(Icon $icon, $data, $count) |
|
66 | { |
||
67 | 15 | for ($i = 0; $i < $count; ++$i) { |
|
68 | 15 | $icoDirEntry = unpack( |
|
69 | 15 | 'Cwidth/Cheight/CcolorCount/Creserved/Splanes/SbitCount/LsizeInBytes/LfileOffset', |
|
70 | $data |
||
71 | 15 | ); |
|
72 | 15 | $icoDirEntry['fileOffset'] -= ($count * 16) + 6; |
|
73 | 15 | if ($icoDirEntry['colorCount'] == 0) { |
|
74 | 11 | $icoDirEntry['colorCount'] = 256; |
|
75 | 11 | } |
|
76 | 15 | if ($icoDirEntry['width'] == 0) { |
|
77 | 2 | $icoDirEntry['width'] = 256; |
|
78 | 2 | } |
|
79 | 15 | if ($icoDirEntry['height'] == 0) { |
|
80 | 2 | $icoDirEntry['height'] = 256; |
|
81 | 2 | } |
|
82 | |||
83 | 15 | $entry = new IconImage($icoDirEntry); |
|
84 | 15 | $icon[] = $entry; |
|
85 | |||
86 | 15 | $data = substr($data, 16); |
|
87 | 15 | } |
|
88 | |||
89 | 15 | return $data; |
|
90 | } |
||
91 | |||
92 | 2 | private function parsePng(IconImage $entry, $data) |
|
98 | |||
99 | 15 | private function parseBmp(IconImage $entry, $data) |
|
100 | { |
||
101 | 15 | $bitmapInfoHeader = unpack( |
|
102 | 'LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/' . |
||
103 | 15 | 'LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant', |
|
104 | 15 | substr($data, $entry->fileOffset, 40) |
|
105 | 15 | ); |
|
106 | |||
107 | 15 | $entry->setBitmapInfoHeader($bitmapInfoHeader); |
|
108 | |||
109 | 15 | switch ($entry->bitCount) { |
|
110 | 15 | case 32: |
|
111 | 15 | case 24: |
|
112 | 9 | $this->parseTrueColorImageData($entry, $data); |
|
113 | 9 | break; |
|
114 | 8 | case 8: |
|
115 | 8 | case 4: |
|
116 | 6 | $this->parsePaletteImageData($entry, $data); |
|
117 | 6 | break; |
|
118 | 2 | case 1: |
|
119 | 2 | $this->parseMonoImageData($entry, $data); |
|
120 | 2 | break; |
|
121 | 15 | } |
|
122 | 15 | } |
|
123 | |||
124 | 9 | private function parseTrueColorImageData(IconImage $entry, $data) |
|
130 | |||
131 | 6 | private function parsePaletteImageData(IconImage $entry, $data) |
|
132 | { |
||
133 | //var_dump($entry->colorCount); |
||
146 | |||
147 | 2 | private function parseMonoImageData(IconImage $entry, $data) |
|
161 | } |
||
162 |
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: