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 BindCommandHandler 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 BindCommandHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
38 | class BindCommandHandler |
||
39 | { |
||
40 | /** |
||
41 | * @var DiscoveryManager |
||
42 | */ |
||
43 | private $discoveryManager; |
||
44 | |||
45 | /** |
||
46 | * @var ModuleList |
||
47 | */ |
||
48 | private $modules; |
||
49 | |||
50 | /** |
||
51 | * @var string |
||
52 | */ |
||
53 | private $currentPath = '/'; |
||
54 | |||
55 | /** |
||
56 | * Creates the handler. |
||
57 | * |
||
58 | * @param DiscoveryManager $discoveryManager The discovery manager |
||
59 | * @param ModuleList $modules The loaded modules |
||
60 | */ |
||
61 | 40 | public function __construct(DiscoveryManager $discoveryManager, ModuleList $modules) |
|
66 | |||
67 | /** |
||
68 | * Handles the "bind --list" command. |
||
69 | * |
||
70 | * @param Args $args The console arguments |
||
71 | * @param IO $io The I/O |
||
72 | * |
||
73 | * @return int The status code |
||
74 | */ |
||
75 | 15 | public function handleList(Args $args, IO $io) |
|
76 | { |
||
77 | 15 | $moduleNames = ArgsUtil::getModuleNames($args, $this->modules); |
|
78 | 15 | $bindingStates = $this->getBindingStates($args); |
|
79 | |||
80 | 15 | $printBindingState = count($bindingStates) > 1; |
|
81 | 15 | $printModuleName = count($moduleNames) > 1; |
|
82 | 15 | $printHeaders = $printBindingState || $printModuleName; |
|
83 | 15 | $printAdvice = true; |
|
84 | 15 | $indentation = $printBindingState && $printModuleName ? 8 |
|
85 | 15 | : ($printBindingState || $printModuleName ? 4 : 0); |
|
86 | |||
87 | 15 | foreach ($bindingStates as $bindingState) { |
|
88 | 15 | $bindingStatePrinted = !$printBindingState; |
|
89 | |||
90 | 15 | foreach ($moduleNames as $moduleName) { |
|
91 | 15 | $expr = Expr::method('getContainingModule', Expr::method('getName', Expr::same($moduleName))) |
|
92 | 15 | ->andMethod('getState', Expr::same($bindingState)); |
|
93 | |||
94 | 15 | $descriptors = $this->discoveryManager->findBindingDescriptors($expr); |
|
95 | |||
96 | 15 | if (empty($descriptors)) { |
|
97 | 2 | continue; |
|
98 | } |
||
99 | |||
100 | 14 | $printAdvice = false; |
|
101 | |||
102 | 14 | if (!$bindingStatePrinted) { |
|
103 | 7 | $this->printBindingStateHeader($io, $bindingState); |
|
104 | 7 | $bindingStatePrinted = true; |
|
105 | } |
||
106 | |||
107 | 14 | View Code Duplication | if ($printModuleName) { |
|
|||
108 | 10 | $prefix = $printBindingState ? ' ' : ''; |
|
109 | 10 | $io->writeLine(sprintf('%sModule: %s', $prefix, $moduleName)); |
|
110 | 10 | $io->writeLine(''); |
|
111 | } |
||
112 | |||
113 | 14 | $this->printBindingTable($io, $descriptors, $indentation, BindingState::ENABLED === $bindingState); |
|
114 | |||
115 | 14 | if ($printHeaders) { |
|
116 | 15 | $io->writeLine(''); |
|
117 | } |
||
118 | } |
||
119 | } |
||
120 | |||
121 | 15 | if ($printAdvice) { |
|
122 | 1 | $io->writeLine('No bindings found. Use "puli bind <artifact> <type>" to bind an artifact to a type.'); |
|
123 | } |
||
124 | |||
125 | 15 | return 0; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * Handles the "bind <query> <type>" command. |
||
130 | * |
||
131 | * @param Args $args The console arguments |
||
132 | * |
||
133 | * @return int The status code |
||
134 | */ |
||
135 | 8 | public function handleAdd(Args $args) |
|
166 | |||
167 | /** |
||
168 | * Handles the "bind --update <uuid>" command. |
||
169 | * |
||
170 | * @param Args $args The console arguments |
||
171 | * |
||
172 | * @return int The status code |
||
173 | */ |
||
174 | 7 | public function handleUpdate(Args $args) |
|
175 | { |
||
176 | 7 | $flags = $args->isOptionSet('force') |
|
177 | 1 | ? DiscoveryManager::OVERRIDE | DiscoveryManager::IGNORE_TYPE_NOT_FOUND |
|
178 | 1 | | DiscoveryManager::IGNORE_TYPE_NOT_ENABLED |
|
179 | 7 | : DiscoveryManager::OVERRIDE; |
|
180 | |||
181 | 7 | $descriptorToUpdate = $this->getBindingByUuidPrefix($args->getArgument('uuid')); |
|
182 | 7 | $bindingToUpdate = $descriptorToUpdate->getBinding(); |
|
183 | |||
184 | 7 | if (!$descriptorToUpdate->getContainingModule() instanceof RootModule) { |
|
185 | 1 | throw new RuntimeException(sprintf( |
|
186 | 1 | 'Can only update bindings in the module "%s".', |
|
187 | 1 | $this->modules->getRootModuleName() |
|
188 | )); |
||
189 | } |
||
190 | |||
191 | 6 | if ($bindingToUpdate instanceof ResourceBinding) { |
|
192 | 5 | $updatedBinding = $this->getUpdatedResourceBinding($args, $bindingToUpdate); |
|
193 | } elseif ($bindingToUpdate instanceof ClassBinding) { |
||
194 | 1 | $updatedBinding = $this->getUpdatedClassBinding($args, $bindingToUpdate); |
|
195 | } else { |
||
196 | throw new RuntimeException(sprintf( |
||
197 | 'Cannot update bindings of type %s.', |
||
198 | get_class($bindingToUpdate) |
||
199 | )); |
||
200 | } |
||
201 | |||
202 | 6 | $updatedDescriptor = new BindingDescriptor($updatedBinding); |
|
203 | |||
204 | 6 | if ($this->bindingsEqual($descriptorToUpdate, $updatedDescriptor)) { |
|
205 | 1 | throw new RuntimeException('Nothing to update.'); |
|
206 | } |
||
207 | |||
208 | 5 | $this->discoveryManager->addRootBindingDescriptor($updatedDescriptor, $flags); |
|
209 | |||
210 | 5 | return 0; |
|
211 | } |
||
212 | |||
213 | /** |
||
214 | * Handles the "bind --delete" command. |
||
215 | * |
||
216 | * @param Args $args The console arguments |
||
217 | * |
||
218 | * @return int The status code |
||
219 | */ |
||
220 | 4 | View Code Duplication | public function handleDelete(Args $args) |
235 | |||
236 | /** |
||
237 | * Handles the "bind --enable" command. |
||
238 | * |
||
239 | * @param Args $args The console arguments |
||
240 | * |
||
241 | * @return int The status code |
||
242 | */ |
||
243 | 3 | View Code Duplication | public function handleEnable(Args $args) |
258 | |||
259 | /** |
||
260 | * Handles the "bind --disable" command. |
||
261 | * |
||
262 | * @param Args $args The console arguments |
||
263 | * |
||
264 | * @return int The status code |
||
265 | */ |
||
266 | 3 | View Code Duplication | public function handleDisable(Args $args) |
281 | |||
282 | /** |
||
283 | * Returns the binding states selected in the console arguments. |
||
284 | * |
||
285 | * @param Args $args The console arguments |
||
286 | * |
||
287 | * @return int[] The selected {@link BindingState} constants |
||
288 | */ |
||
289 | 15 | private function getBindingStates(Args $args) |
|
290 | { |
||
291 | $states = array( |
||
292 | 15 | BindingState::ENABLED => 'enabled', |
|
293 | 15 | BindingState::DISABLED => 'disabled', |
|
294 | 15 | BindingState::TYPE_NOT_FOUND => 'type-not-found', |
|
295 | 15 | BindingState::TYPE_NOT_ENABLED => 'type-not-enabled', |
|
296 | 15 | BindingState::INVALID => 'invalid', |
|
297 | ); |
||
298 | |||
299 | 15 | $states = array_filter($states, function ($option) use ($args) { |
|
300 | 15 | return $args->isOptionSet($option); |
|
301 | 15 | }); |
|
302 | |||
303 | 15 | return array_keys($states) ?: BindingState::all(); |
|
304 | } |
||
305 | |||
306 | /** |
||
307 | * Prints a list of binding descriptors. |
||
308 | * |
||
309 | * @param IO $io The I/O |
||
310 | * @param BindingDescriptor[] $descriptors The binding descriptors |
||
311 | * @param int $indentation The number of spaces to indent |
||
312 | * @param bool $enabled Whether the binding descriptors |
||
313 | * are enabled. If not, the output |
||
314 | * is printed in red |
||
315 | */ |
||
316 | 14 | private function printBindingTable(IO $io, array $descriptors, $indentation = 0, $enabled = true) |
|
317 | { |
||
318 | 14 | $table = new Table(PuliTableStyle::borderless()); |
|
319 | |||
320 | 14 | $table->setHeaderRow(array('UUID', 'Glob', 'Type')); |
|
321 | |||
322 | 14 | $paramTag = $enabled ? 'c1' : 'bad'; |
|
323 | 14 | $artifactTag = $enabled ? 'c1' : 'bad'; |
|
324 | 14 | $typeTag = $enabled ? 'u' : 'bad'; |
|
325 | |||
326 | 14 | foreach ($descriptors as $descriptor) { |
|
327 | 14 | $parameters = array(); |
|
328 | 14 | $binding = $descriptor->getBinding(); |
|
329 | |||
330 | 14 | foreach ($binding->getParameterValues() as $parameterName => $parameterValue) { |
|
331 | 1 | $parameters[] = $parameterName.'='.StringUtil::formatValue($parameterValue); |
|
332 | } |
||
333 | |||
334 | 14 | $uuid = substr($descriptor->getUuid(), 0, 6); |
|
335 | |||
336 | 14 | if (!$enabled) { |
|
337 | 10 | $uuid = sprintf('<bad>%s</bad>', $uuid); |
|
338 | } |
||
339 | |||
340 | 14 | $paramString = ''; |
|
341 | |||
342 | 14 | if (!empty($parameters)) { |
|
343 | // \xc2\xa0 is a non-breaking space |
||
344 | 1 | $paramString = sprintf( |
|
345 | 1 | ' <%s>(%s)</%s>', |
|
346 | $paramTag, |
||
347 | 1 | implode(",\xc2\xa0", $parameters), |
|
348 | $paramTag |
||
349 | ); |
||
350 | } |
||
351 | |||
352 | 14 | if ($binding instanceof ResourceBinding) { |
|
353 | 14 | $artifact = $binding->getQuery(); |
|
354 | } elseif ($binding instanceof ClassBinding) { |
||
355 | 6 | $artifact = StringUtil::getShortClassName($binding->getClassName()); |
|
356 | } else { |
||
357 | continue; |
||
358 | } |
||
359 | |||
360 | 14 | $typeString = StringUtil::getShortClassName($binding->getTypeName()); |
|
361 | |||
362 | 14 | $table->addRow(array( |
|
363 | 14 | $uuid, |
|
364 | 14 | sprintf('<%s>%s</%s>', $artifactTag, $artifact, $artifactTag), |
|
365 | 14 | sprintf('<%s>%s</%s>%s', $typeTag, $typeString, $typeTag, $paramString), |
|
366 | )); |
||
367 | } |
||
368 | |||
369 | 14 | $table->render($io, $indentation); |
|
370 | 14 | } |
|
371 | |||
372 | /** |
||
373 | * Prints the header for a binding state. |
||
374 | * |
||
375 | * @param IO $io The I/O |
||
376 | * @param int $bindingState The {@link BindingState} constant |
||
377 | */ |
||
378 | 7 | private function printBindingStateHeader(IO $io, $bindingState) |
|
379 | { |
||
380 | switch ($bindingState) { |
||
381 | 7 | case BindingState::ENABLED: |
|
382 | 7 | $io->writeLine('The following bindings are currently enabled:'); |
|
383 | 7 | $io->writeLine(''); |
|
384 | |||
385 | 7 | return; |
|
386 | 6 | case BindingState::DISABLED: |
|
387 | 6 | $io->writeLine('The following bindings are disabled:'); |
|
388 | 6 | $io->writeLine(' (use "puli bind --enable <uuid>" to enable)'); |
|
389 | 6 | $io->writeLine(''); |
|
390 | |||
391 | 6 | return; |
|
392 | 5 | case BindingState::TYPE_NOT_FOUND: |
|
393 | 5 | $io->writeLine('The types of the following bindings could not be found:'); |
|
394 | 5 | $io->writeLine(' (install or create their type definitions to enable)'); |
|
395 | 5 | $io->writeLine(''); |
|
396 | |||
397 | 5 | return; |
|
398 | 5 | case BindingState::TYPE_NOT_ENABLED: |
|
399 | 5 | $io->writeLine('The types of the following bindings are not enabled:'); |
|
400 | 5 | $io->writeLine(' (remove the duplicate type definitions to enable)'); |
|
401 | 5 | $io->writeLine(''); |
|
402 | |||
403 | 5 | return; |
|
404 | 5 | case BindingState::INVALID: |
|
405 | 5 | $io->writeLine('The following bindings have invalid parameters:'); |
|
406 | 5 | $io->writeLine(' (remove the binding and add again with correct parameters)'); |
|
407 | 5 | $io->writeLine(''); |
|
408 | |||
409 | 5 | return; |
|
410 | } |
||
411 | } |
||
412 | |||
413 | 14 | private function parseParams(Args $args, array &$bindingParams) |
|
432 | |||
433 | 6 | private function unsetParams(Args $args, array &$bindingParams) |
|
439 | |||
440 | /** |
||
441 | * @param string $uuidPrefix |
||
442 | * |
||
443 | * @return BindingDescriptor |
||
444 | */ |
||
445 | 17 | View Code Duplication | private function getBindingByUuidPrefix($uuidPrefix) |
463 | |||
464 | 6 | private function bindingsEqual(BindingDescriptor $descriptor1, BindingDescriptor $descriptor2) |
|
468 | |||
469 | /** |
||
470 | * @param Args $args |
||
471 | * @param ResourceBinding $bindingToUpdate |
||
472 | * |
||
473 | * @return ResourceBinding |
||
474 | */ |
||
475 | 5 | private function getUpdatedResourceBinding(Args $args, ResourceBinding $bindingToUpdate) |
|
505 | |||
506 | /** |
||
507 | * @param Args $args |
||
508 | * @param ClassBinding $bindingToUpdate |
||
509 | * |
||
510 | * @return ClassBinding |
||
511 | */ |
||
512 | 1 | private function getUpdatedClassBinding(Args $args, ClassBinding $bindingToUpdate) |
|
536 | } |
||
537 |
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.