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 MapCommandHandler 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 MapCommandHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
35 | class MapCommandHandler |
||
36 | { |
||
37 | /** |
||
38 | * @var RepositoryManager |
||
39 | */ |
||
40 | private $repoManager; |
||
41 | |||
42 | /** |
||
43 | * @var PackageCollection |
||
44 | */ |
||
45 | private $packages; |
||
46 | |||
47 | /** |
||
48 | * @var string |
||
49 | */ |
||
50 | private $currentPath = '/'; |
||
51 | |||
52 | /** |
||
53 | * Creates the handler. |
||
54 | * |
||
55 | * @param RepositoryManager $repoManager The repository manager. |
||
56 | * @param PackageCollection $packages The loaded packages. |
||
57 | */ |
||
58 | 24 | public function __construct(RepositoryManager $repoManager, PackageCollection $packages) |
|
63 | |||
64 | /** |
||
65 | * Handles the "puli map --list" command. |
||
66 | * |
||
67 | * @param Args $args The console arguments. |
||
68 | * @param IO $io The I/O. |
||
69 | * |
||
70 | * @return int The status code. |
||
71 | */ |
||
72 | 12 | public function handleList(Args $args, IO $io) |
|
73 | { |
||
74 | 12 | $packageNames = ArgsUtil::getPackageNames($args, $this->packages); |
|
75 | 12 | $states = $this->getPathMappingStates($args); |
|
76 | |||
77 | 12 | $printState = count($states) > 1; |
|
78 | 12 | $printPackageName = count($packageNames) > 1; |
|
79 | 12 | $printHeaders = $printState || $printPackageName; |
|
80 | 12 | $printAdvice = true; |
|
81 | 12 | $indentation = ($printState && $printPackageName) ? 8 |
|
82 | 12 | : ($printState || $printPackageName ? 4 : 0); |
|
83 | |||
84 | 12 | foreach ($states as $state) { |
|
85 | 12 | $statePrinted = !$printState; |
|
86 | |||
87 | 12 | if (PathMappingState::CONFLICT === $state) { |
|
88 | 7 | $expr = Expr::method('getContainingPackage', Expr::method('getName', Expr::in($packageNames))) |
|
89 | 7 | ->andMethod('getState', Expr::same($state)); |
|
90 | |||
91 | 7 | $mappings = $this->repoManager->findPathMappings($expr); |
|
92 | |||
93 | 7 | if (!$mappings) { |
|
|
|||
94 | 1 | continue; |
|
95 | } |
||
96 | |||
97 | 6 | $printAdvice = false; |
|
98 | |||
99 | 6 | if ($printState) { |
|
100 | 5 | $this->printPathMappingStateHeader($io, $state); |
|
101 | } |
||
102 | |||
103 | 6 | $this->printConflictTable($io, $mappings, $printState ? 4 : 0); |
|
104 | |||
105 | 6 | if ($printHeaders) { |
|
106 | 6 | $io->writeLine(''); |
|
107 | } |
||
108 | |||
109 | 6 | continue; |
|
110 | } |
||
111 | |||
112 | 11 | foreach ($packageNames as $packageName) { |
|
113 | 11 | $expr = Expr::method('getContainingPackage', Expr::method('getName', Expr::same($packageName))) |
|
114 | 11 | ->andMethod('getState', Expr::same($state)); |
|
115 | |||
116 | 11 | $mappings = $this->repoManager->findPathMappings($expr); |
|
117 | |||
118 | 11 | if (!$mappings) { |
|
119 | 1 | continue; |
|
120 | } |
||
121 | |||
122 | 10 | $printAdvice = false; |
|
123 | |||
124 | 10 | if (!$statePrinted) { |
|
125 | 6 | $this->printPathMappingStateHeader($io, $state); |
|
126 | 6 | $statePrinted = true; |
|
127 | } |
||
128 | |||
129 | 10 | View Code Duplication | if ($printPackageName) { |
130 | 6 | $prefix = $printState ? ' ' : ''; |
|
131 | 6 | $io->writeLine(sprintf('%sPackage: %s', $prefix, $packageName)); |
|
132 | 6 | $io->writeLine(''); |
|
133 | } |
||
134 | |||
135 | 10 | $this->printMappingTable($io, $mappings, $indentation, PathMappingState::ENABLED === $state); |
|
136 | |||
137 | 10 | if ($printHeaders) { |
|
138 | 11 | $io->writeLine(''); |
|
139 | } |
||
140 | } |
||
141 | } |
||
142 | |||
143 | 12 | if ($printAdvice) { |
|
144 | 1 | $io->writeLine('No path mappings. Use "puli map <path> <file>" to map a Puli path to a file or directory.'); |
|
145 | } |
||
146 | |||
147 | 12 | return 0; |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * Handles the "puli map" command. |
||
152 | * |
||
153 | * @param Args $args The console arguments. |
||
154 | * |
||
155 | * @return int The status code. |
||
156 | */ |
||
157 | 3 | View Code Duplication | public function handleAdd(Args $args) |
169 | |||
170 | /** |
||
171 | * Handles the "puli map --update" command. |
||
172 | * |
||
173 | * @param Args $args The console arguments. |
||
174 | * |
||
175 | * @return int The status code. |
||
176 | */ |
||
177 | 6 | public function handleUpdate(Args $args) |
|
178 | { |
||
179 | 6 | $flags = $args->isOptionSet('force') |
|
180 | 1 | ? RepositoryManager::OVERRIDE | RepositoryManager::IGNORE_FILE_NOT_FOUND |
|
181 | 6 | : RepositoryManager::OVERRIDE; |
|
182 | 6 | $repositoryPath = Path::makeAbsolute($args->getArgument('path'), $this->currentPath); |
|
183 | 6 | $mappingToUpdate = $this->repoManager->getRootPathMapping($repositoryPath); |
|
184 | 6 | $pathReferences = array_flip($mappingToUpdate->getPathReferences()); |
|
185 | |||
186 | 6 | foreach ($args->getOption('add') as $pathReference) { |
|
187 | 3 | $pathReferences[$pathReference] = true; |
|
188 | } |
||
189 | |||
190 | 6 | foreach ($args->getOption('remove') as $pathReference) { |
|
191 | 2 | unset($pathReferences[$pathReference]); |
|
192 | } |
||
193 | |||
194 | 6 | if (0 === count($pathReferences)) { |
|
195 | 1 | $this->repoManager->removeRootPathMapping($repositoryPath); |
|
196 | |||
197 | 1 | return 0; |
|
198 | } |
||
199 | |||
200 | 5 | $updatedMapping = new PathMapping($repositoryPath, array_keys($pathReferences)); |
|
201 | |||
202 | 5 | if ($this->mappingsEqual($mappingToUpdate, $updatedMapping)) { |
|
203 | 1 | throw new RuntimeException('Nothing to update.'); |
|
204 | } |
||
205 | |||
206 | 4 | $this->repoManager->addRootPathMapping($updatedMapping, $flags); |
|
207 | |||
208 | 4 | return 0; |
|
209 | } |
||
210 | |||
211 | /** |
||
212 | * Handles the "puli map --delete" command. |
||
213 | * |
||
214 | * @param Args $args The console arguments. |
||
215 | * |
||
216 | * @return int The status code. |
||
217 | */ |
||
218 | 3 | public function handleDelete(Args $args) |
|
219 | { |
||
220 | 3 | $repositoryPath = Path::makeAbsolute($args->getArgument('path'), $this->currentPath); |
|
221 | |||
222 | 3 | if (!$this->repoManager->hasRootPathMapping($repositoryPath)) { |
|
223 | 1 | throw new RuntimeException(sprintf( |
|
224 | 1 | 'The path "%s" is not mapped in the package "%s".', |
|
225 | $repositoryPath, |
||
226 | 1 | $this->packages->getRootPackageName() |
|
227 | )); |
||
228 | } |
||
229 | |||
230 | 2 | $this->repoManager->removeRootPathMapping($repositoryPath); |
|
231 | |||
232 | 2 | return 0; |
|
233 | } |
||
234 | |||
235 | /** |
||
236 | * Prints a list of path mappings. |
||
237 | * |
||
238 | * @param IO $io The I/O. |
||
239 | * @param PathMapping[] $mappings The path mappings. |
||
240 | * @param int $indentation The number of spaces to indent the |
||
241 | * output. |
||
242 | * @param bool $enabled Whether the path mappings are enabled. |
||
243 | * If not, the output is printed in red. |
||
244 | */ |
||
245 | 10 | private function printMappingTable(IO $io, array $mappings, $indentation = 0, $enabled = true) |
|
246 | { |
||
247 | 10 | $table = new Table(PuliTableStyle::borderless()); |
|
248 | |||
249 | 10 | $table->setHeaderRow(array('Puli Path', 'Real Path(s)')); |
|
250 | |||
251 | 10 | $pathTag = $enabled ? 'c1' : 'bad'; |
|
252 | |||
253 | 10 | foreach ($mappings as $mapping) { |
|
254 | 10 | if ($enabled) { |
|
255 | 9 | $pathReferences = array(); |
|
256 | |||
257 | 9 | foreach ($mapping->getPathReferences() as $pathReference) { |
|
258 | // Underline referenced packages |
||
259 | 9 | $pathReference = preg_replace('~^@([^:]+):~', '@<u>$1</u>:', $pathReference); |
|
260 | |||
261 | // Highlight path parts |
||
262 | 9 | $pathReference = preg_replace('~^(@([^:]+):)?(.*)$~', '$1<c2>$3</c2>', $pathReference); |
|
263 | |||
264 | 9 | $pathReferences[] = $pathReference; |
|
265 | } |
||
266 | |||
267 | 9 | $pathReferences = implode(', ', $pathReferences); |
|
268 | } else { |
||
269 | 7 | $pathReferences = '<bad>'.implode(', ', $mapping->getPathReferences()).'</bad>'; |
|
270 | } |
||
271 | |||
272 | 10 | $table->addRow(array( |
|
273 | 10 | sprintf('<%s>%s</%s>', $pathTag, $mapping->getRepositoryPath(), $pathTag), |
|
274 | 10 | $pathReferences, |
|
275 | )); |
||
276 | } |
||
277 | |||
278 | 10 | $table->render($io, $indentation); |
|
279 | 10 | } |
|
280 | |||
281 | /** |
||
282 | * Prints a list of conflicting path mappings. |
||
283 | * |
||
284 | * @param IO $io The I/O. |
||
285 | * @param PathMapping[] $mappings The path mappings. |
||
286 | * @param int $indentation The number of spaces to indent the |
||
287 | * output. |
||
288 | */ |
||
289 | 6 | private function printConflictTable(IO $io, array $mappings, $indentation = 0) |
|
290 | { |
||
291 | /** @var PathConflict[] $conflicts */ |
||
292 | 6 | $conflicts = array(); |
|
293 | 6 | $shortPrefix = str_repeat(' ', $indentation); |
|
294 | 6 | $prefix = str_repeat(' ', $indentation + 4); |
|
295 | 6 | $printNewline = false; |
|
296 | |||
297 | 6 | foreach ($mappings as $mapping) { |
|
298 | 6 | foreach ($mapping->getConflicts() as $conflict) { |
|
299 | 6 | $conflicts[spl_object_hash($conflict)] = $conflict; |
|
300 | } |
||
301 | } |
||
302 | |||
303 | 6 | foreach ($conflicts as $conflict) { |
|
304 | 6 | if ($printNewline) { |
|
305 | 5 | $io->writeLine(''); |
|
306 | } |
||
307 | |||
308 | 6 | $io->writeLine(sprintf('%sConflicting path: %s', $shortPrefix, $conflict->getRepositoryPath())); |
|
309 | 6 | $io->writeLine(''); |
|
310 | |||
311 | 6 | $table = new Table(PuliTableStyle::borderless()); |
|
312 | |||
313 | 6 | $table->setHeaderRow(array('Package', 'Puli Path', 'Real Path(s)')); |
|
314 | |||
315 | 6 | foreach ($conflict->getMappings() as $mapping) { |
|
316 | 6 | $table->addRow(array( |
|
317 | 6 | '<bad>'.$mapping->getContainingPackage()->getName().'</bad>', |
|
318 | 6 | '<bad>'.$mapping->getRepositoryPath().'</bad>', |
|
319 | 6 | '<bad>'.implode(', ', $mapping->getPathReferences()).'</bad>', |
|
320 | )); |
||
321 | } |
||
322 | |||
323 | 6 | $io->writeLine(sprintf('%sMapped by the following mappings:', $prefix)); |
|
324 | 6 | $io->writeLine(''); |
|
325 | |||
326 | 6 | $table->render($io, $indentation + 4); |
|
327 | |||
328 | 6 | $printNewline = true; |
|
329 | } |
||
330 | 6 | } |
|
331 | |||
332 | /** |
||
333 | * Returns the path mapping states selected in the console arguments. |
||
334 | * |
||
335 | * @param Args $args The console arguments. |
||
336 | * |
||
337 | * @return int[] The selected {@link PathMappingState} constants. |
||
338 | */ |
||
339 | 12 | private function getPathMappingStates(Args $args) |
|
340 | { |
||
341 | $states = array( |
||
342 | 12 | PathMappingState::ENABLED => 'enabled', |
|
343 | 12 | PathMappingState::NOT_FOUND => 'not-found', |
|
344 | 12 | PathMappingState::CONFLICT => 'conflict', |
|
345 | ); |
||
346 | |||
347 | 12 | $states = array_filter($states, function ($option) use ($args) { |
|
348 | 12 | return $args->isOptionSet($option); |
|
349 | 12 | }); |
|
350 | |||
351 | 12 | return array_keys($states) ?: PathMappingState::all(); |
|
352 | } |
||
353 | |||
354 | /** |
||
355 | * Prints the header for a path mapping state. |
||
356 | * |
||
357 | * @param IO $io The I/O. |
||
358 | * @param int $pathMappingState The {@link PathMappingState} constant. |
||
359 | */ |
||
360 | 6 | View Code Duplication | private function printPathMappingStateHeader(IO $io, $pathMappingState) |
381 | |||
382 | 5 | private function mappingsEqual(PathMapping $mapping1, PathMapping $mapping2) |
|
387 | } |
||
388 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.