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 getid3_dsdiff 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 getid3_dsdiff, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class getid3_dsdiff extends getid3_handler |
||
23 | { |
||
24 | /** |
||
25 | * @return bool |
||
26 | */ |
||
27 | public function Analyze() { |
||
28 | $info = &$this->getid3->info; |
||
29 | |||
30 | $this->fseek($info['avdataoffset']); |
||
31 | $DSDIFFheader = $this->fread(4); |
||
32 | |||
33 | // https://dsd-guide.com/sites/default/files/white-papers/DSDIFF_1.5_Spec.pdf |
||
34 | if (substr($DSDIFFheader, 0, 4) != 'FRM8') { |
||
35 | $this->error('Expecting "FRM8" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSDIFFheader, 0, 4)).'"'); |
||
36 | return false; |
||
37 | } |
||
38 | unset($DSDIFFheader); |
||
39 | $this->fseek($info['avdataoffset']); |
||
40 | |||
41 | $info['encoding'] = 'ISO-8859-1'; // not certain, but assumed |
||
42 | $info['fileformat'] = 'dsdiff'; |
||
43 | $info['mime_type'] = 'audio/dsd'; |
||
44 | $info['audio']['dataformat'] = 'dsdiff'; |
||
45 | $info['audio']['bitrate_mode'] = 'cbr'; |
||
46 | $info['audio']['bits_per_sample'] = 1; |
||
47 | |||
48 | $info['dsdiff'] = array(); |
||
49 | while (!$this->feof() && ($ChunkHeader = $this->fread(12))) { |
||
50 | if (strlen($ChunkHeader) < 12) { |
||
51 | $this->error('Expecting chunk header at offset '.(isset($thisChunk['offset']) ? $thisChunk['offset'] : 'N/A').', found insufficient data in file, aborting parsing'); |
||
52 | break; |
||
53 | } |
||
54 | $thisChunk = array(); |
||
55 | $thisChunk['offset'] = $this->ftell() - 12; |
||
56 | $thisChunk['name'] = substr($ChunkHeader, 0, 4); |
||
57 | if (!preg_match('#^[\\x21-\\x7E]+ *$#', $thisChunk['name'])) { |
||
58 | // "a concatenation of four printable ASCII characters in the range ' ' (space, 0x20) through '~'(0x7E). Space (0x20) cannot precede printing characters; trailing spaces are allowed." |
||
59 | $this->error('Invalid chunk name "'.$thisChunk['name'].'" ('.getid3_lib::PrintHexBytes($thisChunk['name']).') at offset '.$thisChunk['offset'].', aborting parsing'); |
||
60 | } |
||
61 | $thisChunk['size'] = getid3_lib::BigEndian2Int(substr($ChunkHeader, 4, 8)); |
||
62 | $datasize = $thisChunk['size'] + ($thisChunk['size'] % 2); // "If the data is an odd number of bytes in length, a pad byte must be added at the end. The pad byte is not included in ckDataSize." |
||
63 | |||
64 | switch ($thisChunk['name']) { |
||
65 | View Code Duplication | case 'FRM8': |
|
|
|||
66 | $thisChunk['form_type'] = $this->fread(4); |
||
67 | if ($thisChunk['form_type'] != 'DSD ') { |
||
68 | $this->error('Expecting "DSD " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['form_type']).'", aborting parsing'); |
||
69 | break 2; |
||
70 | } |
||
71 | // do nothing further, prevent skipping subchunks |
||
72 | break; |
||
73 | View Code Duplication | case 'PROP': // PROPerty chunk |
|
74 | $thisChunk['prop_type'] = $this->fread(4); |
||
75 | if ($thisChunk['prop_type'] != 'SND ') { |
||
76 | $this->error('Expecting "SND " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['prop_type']).'", aborting parsing'); |
||
77 | break 2; |
||
78 | } |
||
79 | // do nothing further, prevent skipping subchunks |
||
80 | break; |
||
81 | case 'DIIN': // eDIted master INformation chunk |
||
82 | // do nothing, just prevent skipping subchunks |
||
83 | break; |
||
84 | |||
85 | case 'FVER': // Format VERsion chunk |
||
86 | if ($thisChunk['size'] == 4) { |
||
87 | $FVER = $this->fread(4); |
||
88 | $info['dsdiff']['format_version'] = ord($FVER[0]).'.'.ord($FVER[1]).'.'.ord($FVER[2]).'.'.ord($FVER[3]); |
||
89 | unset($FVER); |
||
90 | } else { |
||
91 | $this->warning('Expecting "FVER" chunk to be 4 bytes, found '.$thisChunk['size'].' bytes, skipping chunk'); |
||
92 | $this->fseek($datasize, SEEK_CUR); |
||
93 | } |
||
94 | break; |
||
95 | case 'FS ': // sample rate chunk |
||
96 | if ($thisChunk['size'] == 4) { |
||
97 | $info['dsdiff']['sample_rate'] = getid3_lib::BigEndian2Int($this->fread(4)); |
||
98 | $info['audio']['sample_rate'] = $info['dsdiff']['sample_rate']; |
||
99 | } else { |
||
100 | $this->warning('Expecting "FVER" chunk to be 4 bytes, found '.$thisChunk['size'].' bytes, skipping chunk'); |
||
101 | $this->fseek($datasize, SEEK_CUR); |
||
102 | } |
||
103 | break; |
||
104 | case 'CHNL': // CHaNneLs chunk |
||
105 | $thisChunk['num_channels'] = getid3_lib::BigEndian2Int($this->fread(2)); |
||
106 | if ($thisChunk['num_channels'] == 0) { |
||
107 | $this->warning('channel count should be greater than zero, skipping chunk'); |
||
108 | $this->fseek($datasize - 2, SEEK_CUR); |
||
109 | } |
||
110 | for ($i = 0; $i < $thisChunk['num_channels']; $i++) { |
||
111 | $thisChunk['channels'][$i] = $this->fread(4); |
||
112 | } |
||
113 | $info['audio']['channels'] = $thisChunk['num_channels']; |
||
114 | break; |
||
115 | case 'CMPR': // CoMPRession type chunk |
||
116 | $thisChunk['compression_type'] = $this->fread(4); |
||
117 | $info['audio']['dataformat'] = trim($thisChunk['compression_type']); |
||
118 | $humanReadableByteLength = getid3_lib::BigEndian2Int($this->fread(1)); |
||
119 | $thisChunk['compression_name'] = $this->fread($humanReadableByteLength); |
||
120 | if (($humanReadableByteLength % 2) == 0) { |
||
121 | // need to seek to multiple of 2 bytes, human-readable string length is only one byte long so if the string is an even number of bytes we need to seek past a padding byte after the string |
||
122 | $this->fseek(1, SEEK_CUR); |
||
123 | } |
||
124 | unset($humanReadableByteLength); |
||
125 | break; |
||
126 | case 'ABSS': // ABSolute Start time chunk |
||
127 | $ABSS = $this->fread(8); |
||
128 | $info['dsdiff']['absolute_start_time']['hours'] = getid3_lib::BigEndian2Int(substr($ABSS, 0, 2)); |
||
129 | $info['dsdiff']['absolute_start_time']['minutes'] = getid3_lib::BigEndian2Int(substr($ABSS, 2, 1)); |
||
130 | $info['dsdiff']['absolute_start_time']['seconds'] = getid3_lib::BigEndian2Int(substr($ABSS, 3, 1)); |
||
131 | $info['dsdiff']['absolute_start_time']['samples'] = getid3_lib::BigEndian2Int(substr($ABSS, 4, 4)); |
||
132 | unset($ABSS); |
||
133 | break; |
||
134 | case 'LSCO': // LoudSpeaker COnfiguration chunk |
||
135 | // 0 = 2-channel stereo set-up |
||
136 | // 3 = 5-channel set-up according to ITU-R BS.775-1 [ITU] |
||
137 | // 4 = 6-channel set-up, 5-channel set-up according to ITU-R BS.775-1 [ITU], plus additional Low Frequency Enhancement (LFE) loudspeaker. Also known as "5.1 configuration" |
||
138 | // 65535 = Undefined channel set-up |
||
139 | $thisChunk['loundspeaker_config_id'] = getid3_lib::BigEndian2Int($this->fread(2)); |
||
140 | break; |
||
141 | case 'COMT': // COMmenTs chunk |
||
142 | $thisChunk['num_comments'] = getid3_lib::BigEndian2Int($this->fread(2)); |
||
143 | for ($i = 0; $i < $thisChunk['num_comments']; $i++) { |
||
144 | $thisComment = array(); |
||
145 | $COMT = $this->fread(14); |
||
146 | $thisComment['creation_year'] = getid3_lib::BigEndian2Int(substr($COMT, 0, 2)); |
||
147 | $thisComment['creation_month'] = getid3_lib::BigEndian2Int(substr($COMT, 2, 1)); |
||
148 | $thisComment['creation_day'] = getid3_lib::BigEndian2Int(substr($COMT, 3, 1)); |
||
149 | $thisComment['creation_hour'] = getid3_lib::BigEndian2Int(substr($COMT, 4, 1)); |
||
150 | $thisComment['creation_minute'] = getid3_lib::BigEndian2Int(substr($COMT, 5, 1)); |
||
151 | $thisComment['comment_type_id'] = getid3_lib::BigEndian2Int(substr($COMT, 6, 2)); |
||
152 | $thisComment['comment_ref_id'] = getid3_lib::BigEndian2Int(substr($COMT, 8, 2)); |
||
153 | $thisComment['string_length'] = getid3_lib::BigEndian2Int(substr($COMT, 10, 4)); |
||
154 | $thisComment['comment_text'] = $this->fread($thisComment['string_length']); |
||
155 | if ($thisComment['string_length'] % 2) { |
||
156 | // commentText[] is the description of the Comment. This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count. |
||
157 | $this->fseek(1, SEEK_CUR); |
||
158 | } |
||
159 | $thisComment['comment_type'] = $this->DSDIFFcmtType($thisComment['comment_type_id']); |
||
160 | $thisComment['comment_reference'] = $this->DSDIFFcmtRef($thisComment['comment_type_id'], $thisComment['comment_ref_id']); |
||
161 | $thisComment['creation_unix'] = mktime($thisComment['creation_hour'], $thisComment['creation_minute'], 0, $thisComment['creation_month'], $thisComment['creation_day'], $thisComment['creation_year']); |
||
162 | $thisChunk['comments'][$i] = $thisComment; |
||
163 | |||
164 | $commentkey = ($thisComment['comment_reference'] ?: 'comment'); |
||
165 | $info['dsdiff']['comments'][$commentkey][] = $thisComment['comment_text']; |
||
166 | unset($thisComment); |
||
167 | } |
||
168 | break; |
||
169 | case 'MARK': // MARKer chunk |
||
170 | $MARK = $this->fread(22); |
||
171 | $thisChunk['marker_hours'] = getid3_lib::BigEndian2Int(substr($MARK, 0, 2)); |
||
172 | $thisChunk['marker_minutes'] = getid3_lib::BigEndian2Int(substr($MARK, 2, 1)); |
||
173 | $thisChunk['marker_seconds'] = getid3_lib::BigEndian2Int(substr($MARK, 3, 1)); |
||
174 | $thisChunk['marker_samples'] = getid3_lib::BigEndian2Int(substr($MARK, 4, 4)); |
||
175 | $thisChunk['marker_offset'] = getid3_lib::BigEndian2Int(substr($MARK, 8, 4)); |
||
176 | $thisChunk['marker_type_id'] = getid3_lib::BigEndian2Int(substr($MARK, 12, 2)); |
||
177 | $thisChunk['marker_channel'] = getid3_lib::BigEndian2Int(substr($MARK, 14, 2)); |
||
178 | $thisChunk['marker_flagraw'] = getid3_lib::BigEndian2Int(substr($MARK, 16, 2)); |
||
179 | $thisChunk['string_length'] = getid3_lib::BigEndian2Int(substr($MARK, 18, 4)); |
||
180 | $thisChunk['description'] = ($thisChunk['string_length'] ? $this->fread($thisChunk['string_length']) : ''); |
||
181 | if ($thisChunk['string_length'] % 2) { |
||
182 | // markerText[] is the description of the marker. This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count. |
||
183 | $this->fseek(1, SEEK_CUR); |
||
184 | } |
||
185 | $thisChunk['marker_type'] = $this->DSDIFFmarkType($thisChunk['marker_type_id']); |
||
186 | unset($MARK); |
||
187 | break; |
||
188 | case 'DIAR': // artist chunk |
||
189 | case 'DITI': // title chunk |
||
190 | $thisChunk['string_length'] = getid3_lib::BigEndian2Int($this->fread(4)); |
||
191 | $thisChunk['description'] = ($thisChunk['string_length'] ? $this->fread($thisChunk['string_length']) : ''); |
||
192 | if ($thisChunk['string_length'] % 2) { |
||
193 | // This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count. |
||
194 | $this->fseek(1, SEEK_CUR); |
||
195 | } |
||
196 | |||
197 | if ($commentkey = (($thisChunk['name'] == 'DIAR') ? 'artist' : (($thisChunk['name'] == 'DITI') ? 'title' : ''))) { |
||
198 | @$info['dsdiff']['comments'][$commentkey][] = $thisChunk['description']; |
||
199 | } |
||
200 | break; |
||
201 | case 'EMID': // Edited Master ID chunk |
||
202 | if ($thisChunk['size']) { |
||
203 | $thisChunk['identifier'] = $this->fread($thisChunk['size']); |
||
204 | } |
||
205 | break; |
||
206 | |||
207 | case 'ID3 ': |
||
208 | $endOfID3v2 = $this->ftell() + $datasize; // we will need to reset the filepointer after parsing ID3v2 |
||
209 | |||
210 | getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); |
||
211 | $getid3_temp = new getID3(); |
||
212 | $getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp); |
||
213 | $getid3_id3v2 = new getid3_id3v2($getid3_temp); |
||
214 | $getid3_id3v2->StartingOffset = $this->ftell(); |
||
215 | if ($thisChunk['valid'] = $getid3_id3v2->Analyze()) { |
||
216 | $info['id3v2'] = $getid3_temp->info['id3v2']; |
||
217 | } |
||
218 | unset($getid3_temp, $getid3_id3v2); |
||
219 | |||
220 | $this->fseek($endOfID3v2); |
||
221 | break; |
||
222 | |||
223 | case 'DSD ': // DSD sound data chunk |
||
224 | case 'DST ': // DST sound data chunk |
||
225 | // actual audio data, we're not interested, skip |
||
226 | $this->fseek($datasize, SEEK_CUR); |
||
227 | break; |
||
228 | default: |
||
229 | $this->warning('Unhandled chunk "'.$thisChunk['name'].'"'); |
||
230 | $this->fseek($datasize, SEEK_CUR); |
||
231 | break; |
||
232 | } |
||
233 | |||
234 | @$info['dsdiff']['chunks'][] = $thisChunk; |
||
235 | //break; |
||
236 | } |
||
237 | if (empty($info['audio']['bitrate']) && !empty($info['audio']['channels']) && !empty($info['audio']['sample_rate']) && !empty($info['audio']['bits_per_sample'])) { |
||
238 | $info['audio']['bitrate'] = $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'] * $info['audio']['channels']; |
||
239 | } |
||
240 | |||
241 | return true; |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * @param int $cmtType |
||
246 | * |
||
247 | * @return string |
||
248 | */ |
||
249 | public static function DSDIFFcmtType($cmtType) { |
||
258 | |||
259 | /** |
||
260 | * @param int $cmtType |
||
261 | * @param int $cmtRef |
||
262 | * |
||
263 | * @return string |
||
264 | */ |
||
265 | public static function DSDIFFcmtRef($cmtType, $cmtRef) { |
||
293 | |||
294 | /** |
||
295 | * @param int $markType |
||
296 | * |
||
297 | * @return string |
||
298 | */ |
||
299 | public static function DSDIFFmarkType($markType) { |
||
309 | |||
310 | } |
||
311 |
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.