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 InputDefinition 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 InputDefinition, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class InputDefinition |
||
31 | { |
||
32 | private $arguments; |
||
33 | private $requiredCount; |
||
34 | private $hasAnArrayArgument = false; |
||
35 | private $hasOptional; |
||
36 | private $options; |
||
37 | private $shortcuts; |
||
38 | |||
39 | /** |
||
40 | * Constructor. |
||
41 | * |
||
42 | * @param array $definition An array of InputArgument and InputOption instance |
||
43 | */ |
||
44 | public function __construct(array $definition = array()) |
||
45 | { |
||
46 | $this->setDefinition($definition); |
||
47 | } |
||
48 | |||
49 | /** |
||
50 | * Sets the definition of the input. |
||
51 | * |
||
52 | * @param array $definition The definition array |
||
53 | */ |
||
54 | public function setDefinition(array $definition) |
||
55 | { |
||
56 | $arguments = array(); |
||
57 | $options = array(); |
||
58 | foreach ($definition as $item) { |
||
59 | if ($item instanceof InputOption) { |
||
60 | $options[] = $item; |
||
61 | } else { |
||
62 | $arguments[] = $item; |
||
63 | } |
||
64 | } |
||
65 | |||
66 | $this->setArguments($arguments); |
||
67 | $this->setOptions($options); |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Sets the InputArgument objects. |
||
72 | * |
||
73 | * @param InputArgument[] $arguments An array of InputArgument objects |
||
74 | */ |
||
75 | public function setArguments($arguments = array()) |
||
76 | { |
||
77 | $this->arguments = array(); |
||
78 | $this->requiredCount = 0; |
||
79 | $this->hasOptional = false; |
||
80 | $this->hasAnArrayArgument = false; |
||
81 | $this->addArguments($arguments); |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Adds an array of InputArgument objects. |
||
86 | * |
||
87 | * @param InputArgument[] $arguments An array of InputArgument objects |
||
88 | */ |
||
89 | public function addArguments($arguments = array()) |
||
90 | { |
||
91 | if (null !== $arguments) { |
||
92 | foreach ($arguments as $argument) { |
||
93 | $this->addArgument($argument); |
||
94 | } |
||
95 | } |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * Adds an InputArgument object. |
||
100 | * |
||
101 | * @param InputArgument $argument An InputArgument object |
||
102 | * |
||
103 | * @throws \LogicException When incorrect argument is given |
||
104 | */ |
||
105 | public function addArgument(InputArgument $argument) |
||
106 | { |
||
107 | if (isset($this->arguments[$argument->getName()])) { |
||
108 | throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); |
||
109 | } |
||
110 | |||
111 | if ($this->hasAnArrayArgument) { |
||
112 | throw new \LogicException('Cannot add an argument after an array argument.'); |
||
113 | } |
||
114 | |||
115 | if ($argument->isRequired() && $this->hasOptional) { |
||
116 | throw new \LogicException('Cannot add a required argument after an optional one.'); |
||
117 | } |
||
118 | |||
119 | if ($argument->isArray()) { |
||
120 | $this->hasAnArrayArgument = true; |
||
121 | } |
||
122 | |||
123 | if ($argument->isRequired()) { |
||
124 | ++$this->requiredCount; |
||
125 | } else { |
||
126 | $this->hasOptional = true; |
||
127 | } |
||
128 | |||
129 | $this->arguments[$argument->getName()] = $argument; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Returns an InputArgument by name or by position. |
||
134 | * |
||
135 | * @param string|int $name The InputArgument name or position |
||
136 | * |
||
137 | * @return InputArgument An InputArgument object |
||
138 | * |
||
139 | * @throws \InvalidArgumentException When argument given doesn't exist |
||
140 | */ |
||
141 | public function getArgument($name) |
||
142 | { |
||
143 | if (!$this->hasArgument($name)) { |
||
144 | throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); |
||
145 | } |
||
146 | |||
147 | $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; |
||
148 | |||
149 | return $arguments[$name]; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Returns true if an InputArgument object exists by name or position. |
||
154 | * |
||
155 | * @param string|int $name The InputArgument name or position |
||
156 | * |
||
157 | * @return bool true if the InputArgument object exists, false otherwise |
||
158 | */ |
||
159 | public function hasArgument($name) |
||
160 | { |
||
161 | $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; |
||
162 | |||
163 | return isset($arguments[$name]); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Gets the array of InputArgument objects. |
||
168 | * |
||
169 | * @return InputArgument[] An array of InputArgument objects |
||
170 | */ |
||
171 | public function getArguments() |
||
172 | { |
||
173 | return $this->arguments; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Returns the number of InputArguments. |
||
178 | * |
||
179 | * @return int The number of InputArguments |
||
180 | */ |
||
181 | public function getArgumentCount() |
||
182 | { |
||
183 | return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Returns the number of required InputArguments. |
||
188 | * |
||
189 | * @return int The number of required InputArguments |
||
190 | */ |
||
191 | public function getArgumentRequiredCount() |
||
192 | { |
||
193 | return $this->requiredCount; |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Gets the default values. |
||
198 | * |
||
199 | * @return array An array of default values |
||
200 | */ |
||
201 | public function getArgumentDefaults() |
||
202 | { |
||
203 | $values = array(); |
||
204 | foreach ($this->arguments as $argument) { |
||
205 | $values[$argument->getName()] = $argument->getDefault(); |
||
206 | } |
||
207 | |||
208 | return $values; |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Sets the InputOption objects. |
||
213 | * |
||
214 | * @param InputOption[] $options An array of InputOption objects |
||
215 | */ |
||
216 | public function setOptions($options = array()) |
||
217 | { |
||
218 | $this->options = array(); |
||
219 | $this->shortcuts = array(); |
||
220 | $this->addOptions($options); |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Adds an array of InputOption objects. |
||
225 | * |
||
226 | * @param InputOption[] $options An array of InputOption objects |
||
227 | */ |
||
228 | public function addOptions($options = array()) |
||
229 | { |
||
230 | foreach ($options as $option) { |
||
231 | $this->addOption($option); |
||
232 | } |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Adds an InputOption object. |
||
237 | * |
||
238 | * @param InputOption $option An InputOption object |
||
239 | * |
||
240 | * @throws \LogicException When option given already exist |
||
241 | */ |
||
242 | public function addOption(InputOption $option) |
||
243 | { |
||
244 | if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { |
||
245 | throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); |
||
246 | } |
||
247 | |||
248 | if ($option->getShortcut()) { |
||
249 | foreach (explode('|', $option->getShortcut()) as $shortcut) { |
||
250 | if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { |
||
251 | throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); |
||
252 | } |
||
253 | } |
||
254 | } |
||
255 | |||
256 | $this->options[$option->getName()] = $option; |
||
257 | if ($option->getShortcut()) { |
||
258 | foreach (explode('|', $option->getShortcut()) as $shortcut) { |
||
259 | $this->shortcuts[$shortcut] = $option->getName(); |
||
260 | } |
||
261 | } |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * Returns an InputOption by name. |
||
266 | * |
||
267 | * @param string $name The InputOption name |
||
268 | * |
||
269 | * @return InputOption A InputOption object |
||
270 | * |
||
271 | * @throws \InvalidArgumentException When option given doesn't exist |
||
272 | */ |
||
273 | public function getOption($name) |
||
274 | { |
||
275 | if (!$this->hasOption($name)) { |
||
276 | throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); |
||
277 | } |
||
278 | |||
279 | return $this->options[$name]; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Returns true if an InputOption object exists by name. |
||
284 | * |
||
285 | * @param string $name The InputOption name |
||
286 | * |
||
287 | * @return bool true if the InputOption object exists, false otherwise |
||
288 | */ |
||
289 | public function hasOption($name) |
||
290 | { |
||
291 | return isset($this->options[$name]); |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * Gets the array of InputOption objects. |
||
296 | * |
||
297 | * @return InputOption[] An array of InputOption objects |
||
298 | */ |
||
299 | public function getOptions() |
||
300 | { |
||
301 | return $this->options; |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * Returns true if an InputOption object exists by shortcut. |
||
306 | * |
||
307 | * @param string $name The InputOption shortcut |
||
308 | * |
||
309 | * @return bool true if the InputOption object exists, false otherwise |
||
310 | */ |
||
311 | public function hasShortcut($name) |
||
312 | { |
||
313 | return isset($this->shortcuts[$name]); |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Gets an InputOption by shortcut. |
||
318 | * |
||
319 | * @param string $shortcut the Shortcut name |
||
320 | * |
||
321 | * @return InputOption An InputOption object |
||
322 | */ |
||
323 | public function getOptionForShortcut($shortcut) |
||
324 | { |
||
325 | return $this->getOption($this->shortcutToName($shortcut)); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Gets an array of default values. |
||
330 | * |
||
331 | * @return array An array of all default values |
||
332 | */ |
||
333 | public function getOptionDefaults() |
||
334 | { |
||
335 | $values = array(); |
||
336 | foreach ($this->options as $option) { |
||
337 | $values[$option->getName()] = $option->getDefault(); |
||
338 | } |
||
339 | |||
340 | return $values; |
||
341 | } |
||
342 | |||
343 | /** |
||
344 | * Returns the InputOption name given a shortcut. |
||
345 | * |
||
346 | * @param string $shortcut The shortcut |
||
347 | * |
||
348 | * @return string The InputOption name |
||
349 | * |
||
350 | * @throws \InvalidArgumentException When option given does not exist |
||
351 | */ |
||
352 | private function shortcutToName($shortcut) |
||
353 | { |
||
354 | if (!isset($this->shortcuts[$shortcut])) { |
||
355 | throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); |
||
356 | } |
||
357 | |||
358 | return $this->shortcuts[$shortcut]; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Gets the synopsis. |
||
363 | * |
||
364 | * @param bool $short Whether to return the short version (with options folded) or not |
||
365 | * |
||
366 | * @return string The synopsis |
||
367 | */ |
||
368 | public function getSynopsis($short = false) |
||
369 | { |
||
370 | $elements = array(); |
||
371 | |||
372 | if ($short && $this->getOptions()) { |
||
373 | $elements[] = '[options]'; |
||
374 | } elseif (!$short) { |
||
375 | foreach ($this->getOptions() as $option) { |
||
376 | $value = ''; |
||
377 | if ($option->acceptValue()) { |
||
378 | $value = sprintf( |
||
379 | ' %s%s%s', |
||
380 | $option->isValueOptional() ? '[' : '', |
||
381 | strtoupper($option->getName()), |
||
382 | $option->isValueOptional() ? ']' : '' |
||
383 | ); |
||
384 | } |
||
385 | |||
386 | $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; |
||
387 | $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); |
||
388 | } |
||
389 | } |
||
390 | |||
391 | if (count($elements) && $this->getArguments()) { |
||
392 | $elements[] = '[--]'; |
||
393 | } |
||
394 | |||
395 | foreach ($this->getArguments() as $argument) { |
||
396 | $element = '<'.$argument->getName().'>'; |
||
397 | if (!$argument->isRequired()) { |
||
398 | $element = '['.$element.']'; |
||
399 | } elseif ($argument->isArray()) { |
||
400 | $element = $element.' ('.$element.')'; |
||
401 | } |
||
402 | |||
403 | if ($argument->isArray()) { |
||
404 | $element .= '...'; |
||
405 | } |
||
406 | |||
407 | $elements[] = $element; |
||
408 | } |
||
409 | |||
410 | return implode(' ', $elements); |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * Returns a textual representation of the InputDefinition. |
||
415 | * |
||
416 | * @return string A string representing the InputDefinition |
||
417 | * |
||
418 | * @deprecated since version 2.3, to be removed in 3.0. |
||
419 | */ |
||
420 | public function asText() |
||
421 | { |
||
422 | @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); |
||
423 | |||
424 | $descriptor = new TextDescriptor(); |
||
425 | $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); |
||
426 | $descriptor->describe($output, $this, array('raw_output' => true)); |
||
427 | |||
428 | return $output->fetch(); |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Returns an XML representation of the InputDefinition. |
||
433 | * |
||
434 | * @param bool $asDom Whether to return a DOM or an XML string |
||
435 | * |
||
436 | * @return string|\DOMDocument An XML string representing the InputDefinition |
||
437 | * |
||
438 | * @deprecated since version 2.3, to be removed in 3.0. |
||
439 | */ |
||
440 | public function asXml($asDom = false) |
||
441 | { |
||
442 | @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); |
||
443 | |||
444 | $descriptor = new XmlDescriptor(); |
||
445 | |||
446 | if ($asDom) { |
||
447 | return $descriptor->getInputDefinitionDocument($this); |
||
448 | } |
||
449 | |||
450 | $output = new BufferedOutput(); |
||
451 | $descriptor->describe($output, $this); |
||
452 | |||
453 | return $output->fetch(); |
||
454 | } |
||
455 | } |
||
456 |