Total Complexity | 57 |
Total Lines | 381 |
Duplicated Lines | 0 % |
Changes | 6 | ||
Bugs | 0 | Features | 0 |
Complex classes like Parser 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.
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 Parser, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
48 | class Parser |
||
49 | { |
||
50 | /** @var SourceHandler */ |
||
51 | protected $sourceHandler; |
||
52 | |||
53 | /** @var int */ |
||
54 | protected $lineNumber; |
||
55 | |||
56 | /** @var string */ |
||
57 | protected $property; |
||
58 | |||
59 | /** |
||
60 | * Reads and parses a string |
||
61 | * |
||
62 | * @param string $string po content |
||
63 | * |
||
64 | * @throws \Exception. |
||
65 | * @return Catalog |
||
66 | */ |
||
67 | public static function parseString($string) |
||
68 | { |
||
69 | $parser = new Parser(new StringSource($string)); |
||
70 | |||
71 | return $parser->parse(); |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Reads and parses a file |
||
76 | * |
||
77 | * @param string $filePath |
||
78 | * |
||
79 | * @throws \Exception. |
||
80 | * @return Catalog |
||
81 | */ |
||
82 | public static function parseFile($filePath) |
||
83 | { |
||
84 | $parser = new Parser(new FileSystem($filePath)); |
||
85 | |||
86 | return $parser->parse(); |
||
87 | } |
||
88 | |||
89 | public function __construct(SourceHandler $sourceHandler) |
||
90 | { |
||
91 | $this->sourceHandler = $sourceHandler; |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * Reads and parses strings of a .po file. |
||
96 | * |
||
97 | * @param SourceHandler . Optional |
||
98 | * |
||
99 | * @throws \Exception, \InvalidArgumentException, ParseException |
||
100 | * @return Catalog |
||
101 | */ |
||
102 | public function parse(Catalog $catalog = null) |
||
103 | { |
||
104 | $catalog = $catalog === null ? new CatalogArray() : $catalog; |
||
105 | $this->lineNumber = 0; |
||
106 | $entry = array(); |
||
107 | $this->property = null; // current property |
||
108 | |||
109 | // Flags |
||
110 | $headersFound = false; |
||
111 | |||
112 | while (!$this->sourceHandler->ended()) { |
||
113 | $line = \trim($this->sourceHandler->getNextLine()); |
||
114 | |||
115 | if ($this->shouldIgnoreLine($line, $entry)) { |
||
116 | $this->lineNumber++; |
||
117 | continue; |
||
118 | } |
||
119 | |||
120 | if ($this->shouldCloseEntry($line, $entry)) { |
||
121 | if (!$headersFound && $this->isHeader($entry)) { |
||
122 | $headersFound = true; |
||
123 | $catalog->addHeaders( |
||
124 | $this->parseHeaders($entry['msgstr']) |
||
125 | ); |
||
126 | } else { |
||
127 | $catalog->addEntry(EntryFactory::createFromArray($entry)); |
||
128 | } |
||
129 | |||
130 | $entry = array(); |
||
131 | $this->property = null; |
||
132 | |||
133 | if (empty($line)) { |
||
134 | $this->lineNumber++; |
||
135 | continue; |
||
136 | } |
||
137 | } |
||
138 | |||
139 | $entry = $this->parseLine($line, $entry); |
||
140 | |||
141 | $this->lineNumber++; |
||
142 | continue; |
||
143 | } |
||
144 | $this->sourceHandler->close(); |
||
145 | |||
146 | // add final entry |
||
147 | if (\count($entry)) { |
||
148 | if ($this->isHeader($entry)) { |
||
149 | $catalog->addHeaders( |
||
150 | $this->parseHeaders($entry['msgstr']) |
||
151 | ); |
||
152 | } else { |
||
153 | $catalog->addEntry(EntryFactory::createFromArray($entry)); |
||
154 | } |
||
155 | } |
||
156 | |||
157 | return $catalog; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * @param string $line |
||
162 | * @param array $entry |
||
163 | * |
||
164 | * @return array |
||
165 | * @throws ParseException |
||
166 | */ |
||
167 | protected function parseLine($line, $entry) |
||
168 | { |
||
169 | $firstChar = \strlen($line) > 0 ? $line[0] : ''; |
||
170 | |||
171 | switch ($firstChar) { |
||
172 | case '#': |
||
173 | $entry = $this->parseComment($line, $entry); |
||
174 | break; |
||
175 | |||
176 | case 'm': |
||
177 | $entry = $this->parseProperty($line, $entry); |
||
178 | break; |
||
179 | |||
180 | case '"': |
||
181 | $entry = $this->parseMultiline($line, $entry); |
||
182 | break; |
||
183 | } |
||
184 | |||
185 | return $entry; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * @param string $line |
||
190 | * @param array $entry |
||
191 | * |
||
192 | * @return array |
||
193 | * @throws ParseException |
||
194 | */ |
||
195 | protected function parseProperty($line, array $entry) |
||
196 | { |
||
197 | list($key, $value) = $this->getProperty($line); |
||
198 | |||
199 | if (!isset($entry[$key])) { |
||
200 | $entry[$key] = ''; |
||
201 | } |
||
202 | |||
203 | switch (true) { |
||
204 | case $key === 'msgctxt': |
||
205 | case $key === 'msgid': |
||
206 | case $key === 'msgid_plural': |
||
207 | case $key === 'msgstr': |
||
208 | $entry[$key] .= $this->unquote($value); |
||
209 | $this->property = $key; |
||
210 | break; |
||
211 | |||
212 | case \strpos($key, 'msgstr[') !== false: |
||
213 | $entry[$key] .= $this->unquote($value); |
||
214 | $this->property = $key; |
||
215 | break; |
||
216 | |||
217 | default: |
||
218 | throw new ParseException(\sprintf('Could not parse %s at line %d', $key, $this->lineNumber)); |
||
219 | } |
||
220 | |||
221 | return $entry; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * @param string $line |
||
226 | * @param array $entry |
||
227 | * |
||
228 | * @return array |
||
229 | * @throws ParseException |
||
230 | */ |
||
231 | protected function parseMultiline($line, $entry) |
||
232 | { |
||
233 | switch (true) { |
||
234 | case $this->property === 'msgctxt': |
||
235 | case $this->property === 'msgid': |
||
236 | case $this->property === 'msgid_plural': |
||
237 | case $this->property === 'msgstr': |
||
238 | case \strpos($this->property, 'msgstr[') !== false: |
||
239 | $entry[$this->property] .= $this->unquote($line); |
||
240 | break; |
||
241 | |||
242 | default: |
||
243 | throw new ParseException( |
||
244 | \sprintf('Error parsing property %s as multiline.', $this->property) |
||
245 | ); |
||
246 | } |
||
247 | |||
248 | return $entry; |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * @param string $line |
||
253 | * @param array $entry |
||
254 | * |
||
255 | * @return array |
||
256 | * @throws ParseException |
||
257 | */ |
||
258 | protected function parseComment($line, $entry) |
||
259 | { |
||
260 | $comment = \trim(\substr($line, 0, 2)); |
||
261 | |||
262 | switch ($comment) { |
||
263 | case '#,': |
||
264 | $line = \trim(\substr($line, 2)); |
||
265 | $entry['flags'] = \preg_split('/,\s*/', $line); |
||
266 | break; |
||
267 | |||
268 | case '#.': |
||
269 | $entry['ccomment'] = !isset($entry['ccomment']) ? array() : $entry['ccomment']; |
||
270 | $entry['ccomment'][] = \trim(\substr($line, 2)); |
||
271 | break; |
||
272 | |||
273 | |||
274 | case '#|': // Previous string |
||
275 | case '#~': // Old entry |
||
276 | case '#~|': // Previous string old |
||
277 | $mode = array( |
||
278 | '#|' => 'previous', |
||
279 | '#~' => 'obsolete', |
||
280 | '#~|' => 'previous-obsolete' |
||
281 | ); |
||
282 | |||
283 | $line = \trim(\substr($line, 2)); |
||
284 | $property = $mode[$comment]; |
||
285 | if ($property === 'previous') { |
||
286 | if (!isset($entry[$property])) { |
||
287 | $subEntry = array(); |
||
288 | } else { |
||
289 | $subEntry = $entry[$property]; |
||
290 | } |
||
291 | |||
292 | $subEntry = $this->parseLine($line, $subEntry); |
||
293 | //$subEntry = $this->parseProperty($line, $subEntry); |
||
|
|||
294 | $entry[$property] = $subEntry; |
||
295 | } else { |
||
296 | $entry = $this->parseLine($line, $entry); |
||
297 | $entry['obsolete'] = true; |
||
298 | } |
||
299 | break; |
||
300 | |||
301 | // Reference |
||
302 | case '#:': |
||
303 | $entry['reference'][] = \trim(\substr($line, 2)); |
||
304 | break; |
||
305 | |||
306 | case '#': |
||
307 | default: |
||
308 | $entry['tcomment'] = !isset($entry['tcomment']) ? array() : $entry['tcomment']; |
||
309 | $entry['tcomment'][] = \trim(\substr($line, 1)); |
||
310 | break; |
||
311 | } |
||
312 | |||
313 | return $entry; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * @param string $msgstr |
||
318 | * |
||
319 | * @return Header |
||
320 | */ |
||
321 | protected function parseHeaders($msgstr) |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * @param string $line |
||
330 | * @param array $entry |
||
331 | * |
||
332 | * @return bool |
||
333 | */ |
||
334 | protected function shouldIgnoreLine($line, array $entry) |
||
335 | { |
||
336 | return empty($line) && \count($entry) === 0; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * @param string $line |
||
341 | * @param array $entry |
||
342 | * |
||
343 | * @return bool |
||
344 | */ |
||
345 | protected function shouldCloseEntry($line, array $entry) |
||
346 | { |
||
347 | $tokens = $this->getProperty($line); |
||
348 | $property = $tokens[0]; |
||
349 | |||
350 | return ($line === '' || ($property === 'msgid' && isset($entry['msgid']))); |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * @param string $value |
||
355 | * @return string |
||
356 | */ |
||
357 | protected function unquote($value) |
||
358 | { |
||
359 | return \stripcslashes(\preg_replace('/^\"|\"$/', '', $value)); |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Checks if entry is a header by |
||
364 | * |
||
365 | * @param array $entry |
||
366 | * |
||
367 | * @return bool |
||
368 | */ |
||
369 | protected function isHeader(array $entry) |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * @param string $line |
||
421 | * |
||
422 | * @return array |
||
423 | */ |
||
424 | protected function getProperty($line) |
||
429 | } |
||
430 | } |
||
431 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.