Completed
Push — master ( 0bfae6...7513f1 )
by Bernhard
04:21
created

PackageCommandHandler   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 357
Duplicated Lines 5.88 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 97.14%

Importance

Changes 16
Bugs 3 Features 6
Metric Value
wmc 52
c 16
b 3
f 6
lcom 1
cbo 13
dl 21
loc 357
ccs 136
cts 140
cp 0.9714
rs 7.9487

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A handleInstall() 0 11 2
A handleRename() 0 9 1
A handleList() 0 12 2
A handleDelete() 0 15 2
A handleClean() 0 11 2
B getSelectedStates() 0 18 5
B getSelectedPackages() 0 28 6
C printPackagesByState() 0 29 7
A printPackagesWithFormat() 0 14 4
A printPackageState() 21 21 4
C printPackageTable() 0 28 11
B printNotLoadablePackages() 0 34 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PackageCommandHandler 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 PackageCommandHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/cli package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Cli\Handler;
13
14
use Puli\Cli\Style\PuliTableStyle;
15
use Puli\Cli\Util\StringUtil;
16
use Puli\Manager\Api\Environment;
17
use Puli\Manager\Api\Package\Package;
18
use Puli\Manager\Api\Package\PackageCollection;
19
use Puli\Manager\Api\Package\PackageManager;
20
use Puli\Manager\Api\Package\PackageState;
21
use RuntimeException;
22
use Webmozart\Console\Api\Args\Args;
23
use Webmozart\Console\Api\IO\IO;
24
use Webmozart\Console\UI\Component\Table;
25
use Webmozart\Expression\Expr;
26
use Webmozart\PathUtil\Path;
27
28
/**
29
 * Handles the "package" command.
30
 *
31
 * @since  1.0
32
 *
33
 * @author Bernhard Schussek <[email protected]>
34
 */
35
class PackageCommandHandler
36
{
37
    /**
38
     * @var array
39
     */
40
    private static $stateStrings = array(
41
        PackageState::ENABLED => 'enabled',
42
        PackageState::NOT_FOUND => 'not-found',
43
        PackageState::NOT_LOADABLE => 'not-loadable',
44
    );
45
46
    /**
47
     * @var PackageManager
48
     */
49
    private $packageManager;
50
51
    /**
52
     * Creates the handler.
53
     *
54
     * @param PackageManager $packageManager The package manager.
55
     */
56 20
    public function __construct(PackageManager $packageManager)
57
    {
58 20
        $this->packageManager = $packageManager;
59 20
    }
60
61
    /**
62
     * Handles the "package --list" command.
63
     *
64
     * @param Args $args The console arguments.
65
     * @param IO   $io   The I/O.
66
     *
67
     * @return int The status code.
68
     */
69 11
    public function handleList(Args $args, IO $io)
70
    {
71 11
        $packages = $this->getSelectedPackages($args);
72
73 11
        if ($args->isOptionSet('format')) {
74 1
            $this->printPackagesWithFormat($io, $packages, $args->getOption('format'));
75
        } else {
76 10
            $this->printPackagesByState($io, $packages, $this->getSelectedStates($args));
77
        }
78
79 11
        return 0;
80
    }
81
82
    /**
83
     * Handles the "package --install" command.
84
     *
85
     * @param Args $args The console arguments.
86
     *
87
     * @return int The status code.
88
     */
89 5
    public function handleInstall(Args $args)
90
    {
91 5
        $packageName = $args->getArgument('name');
92 5
        $installPath = Path::makeAbsolute($args->getArgument('path'), getcwd());
93 5
        $installer = $args->getOption('installer');
94 5
        $env = $args->isOptionSet('dev') ? Environment::DEV : Environment::PROD;
95
96 5
        $this->packageManager->installPackage($installPath, $packageName, $installer, $env);
97
98 5
        return 0;
99
    }
100
101
    /**
102
     * Handles the "package --rename" command.
103
     *
104
     * @param Args $args The console arguments.
105
     *
106
     * @return int The status code.
107
     */
108 1
    public function handleRename(Args $args)
109
    {
110 1
        $packageName = $args->getArgument('name');
111 1
        $newName = $args->getArgument('new-name');
112
113 1
        $this->packageManager->renamePackage($packageName, $newName);
114
115 1
        return 0;
116
    }
117
118
    /**
119
     * Handles the "package --delete" command.
120
     *
121
     * @param Args $args The console arguments.
122
     *
123
     * @return int The status code.
124
     */
125 2
    public function handleDelete(Args $args)
126
    {
127 2
        $packageName = $args->getArgument('name');
128
129 2
        if (!$this->packageManager->hasPackage($packageName)) {
130 1
            throw new RuntimeException(sprintf(
131 1
                'The package "%s" is not installed.',
132
                $packageName
133
            ));
134
        }
135
136 1
        $this->packageManager->removePackage($packageName);
137
138 1
        return 0;
139
    }
140
141
    /**
142
     * Handles the "package --clean" command.
143
     *
144
     * @param Args $args The console arguments.
145
     * @param IO   $io   The I/O.
146
     *
147
     * @return int The status code.
148
     */
149 1
    public function handleClean(Args $args, IO $io)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
150
    {
151 1
        $expr = Expr::method('getState', Expr::same(PackageState::NOT_FOUND));
152
153 1
        foreach ($this->packageManager->findPackages($expr) as $package) {
154 1
            $io->writeLine('Removing '.$package->getName());
155 1
            $this->packageManager->removePackage($package->getName());
156
        }
157
158 1
        return 0;
159
    }
160
161
    /**
162
     * Returns the package states that should be displayed for the given
163
     * console arguments.
164
     *
165
     * @param Args $args The console arguments.
166
     *
167
     * @return int[] A list of {@link PackageState} constants.
168
     */
169 11
    private function getSelectedStates(Args $args)
170
    {
171 11
        $states = array();
172
173 11
        if ($args->isOptionSet('enabled')) {
174 3
            $states[] = PackageState::ENABLED;
175
        }
176
177 11
        if ($args->isOptionSet('not-found')) {
178 2
            $states[] = PackageState::NOT_FOUND;
179
        }
180
181 11
        if ($args->isOptionSet('not-loadable')) {
182 1
            $states[] = PackageState::NOT_LOADABLE;
183
        }
184
185 11
        return $states ?: PackageState::all();
186
    }
187
188
    /**
189
     * Returns the packages that should be displayed for the given console
190
     * arguments.
191
     *
192
     * @param Args $args The console arguments.
193
     *
194
     * @return PackageCollection The packages.
195
     */
196 11
    private function getSelectedPackages(Args $args)
197
    {
198 11
        $states = $this->getSelectedStates($args);
199 11
        $expr = Expr::true();
200 11
        $envs = array();
201
202 11
        if ($states !== PackageState::all()) {
203 5
            $expr = $expr->andMethod('getState', Expr::in($states));
204
        }
205
206 11
        if ($args->isOptionSet('installer')) {
207 2
            $expr = $expr->andMethod('getInstallInfo', Expr::method('getInstallerName', Expr::same($args->getOption('installer'))));
208
        }
209
210 11
        if ($args->isOptionSet('prod')) {
211 2
            $envs[] = Environment::PROD;
212
        }
213
214 11
        if ($args->isOptionSet('dev')) {
215 2
            $envs[] = Environment::DEV;
216
        }
217
218 11
        if (count($envs) > 0) {
219 3
            $expr = $expr->andMethod('getInstallInfo', Expr::method('getEnvironment', Expr::in($envs)));
220
        }
221
222 11
        return $this->packageManager->findPackages($expr);
223
    }
224
225
    /**
226
     * Prints packages with intermediate headers for the package states.
227
     *
228
     * @param IO                $io       The I/O.
229
     * @param PackageCollection $packages The packages to print.
230
     * @param int[]             $states   The states to print.
231
     */
232 10
    private function printPackagesByState(IO $io, PackageCollection $packages, array $states)
233
    {
234 10
        $printStates = count($states) > 1;
235
236 10
        foreach ($states as $state) {
237 10
            $filteredPackages = array_filter($packages->toArray(), function (Package $package) use ($state) {
238 10
                return $state === $package->getState();
239 10
            });
240
241 10
            if (0 === count($filteredPackages)) {
242 2
                continue;
243
            }
244
245 10
            if ($printStates) {
246 6
                $this->printPackageState($io, $state);
247
            }
248
249 10
            if (PackageState::NOT_LOADABLE === $state) {
250 5
                $this->printNotLoadablePackages($io, $filteredPackages, $printStates);
251
            } else {
252 9
                $styleTag = PackageState::ENABLED === $state ? null : 'bad';
253 9
                $this->printPackageTable($io, $filteredPackages, $styleTag, $printStates);
254
            }
255
256 10
            if ($printStates) {
257 10
                $io->writeLine('');
258
            }
259
        }
260 10
    }
261
262
    /**
263
     * Prints packages using the given format.
264
     *
265
     * @param IO                $io       The I/O.
266
     * @param PackageCollection $packages The packages to print.
267
     * @param string            $format   The format string.
268
     */
269 1
    private function printPackagesWithFormat(IO $io, PackageCollection $packages, $format)
270
    {
271 1
        foreach ($packages as $package) {
272 1
            $installInfo = $package->getInstallInfo();
273
274 1
            $io->writeLine(strtr($format, array(
275 1
                '%name%' => $package->getName(),
276 1
                '%installer%' => $installInfo ? $installInfo->getInstallerName() : '',
277 1
                '%install_path%' => $package->getInstallPath(),
278 1
                '%state%' => self::$stateStrings[$package->getState()],
279 1
                '%env%' => $installInfo ? $installInfo->getEnvironment() : Environment::PROD,
280
            )));
281
        }
282 1
    }
283
284
    /**
285
     * Prints the heading for a given package state.
286
     *
287
     * @param IO  $io           The I/O.
288
     * @param int $packageState The {@link PackageState} constant.
289
     */
290 6 View Code Duplication
    private function printPackageState(IO $io, $packageState)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
291
    {
292
        switch ($packageState) {
293 6
            case PackageState::ENABLED:
294 6
                $io->writeLine('The following packages are currently enabled:');
295 6
                $io->writeLine('');
296
297 6
                return;
298
            case PackageState::NOT_FOUND:
299 4
                $io->writeLine('The following packages could not be found:');
300 4
                $io->writeLine(' (use "puli package --clean" to remove)');
301 4
                $io->writeLine('');
302
303 4
                return;
304
            case PackageState::NOT_LOADABLE:
305 4
                $io->writeLine('The following packages could not be loaded:');
306 4
                $io->writeLine('');
307
308 4
                return;
309
        }
310
    }
311
312
    /**
313
     * Prints a list of packages in a table.
314
     *
315
     * @param IO          $io       The I/O.
316
     * @param Package[]   $packages The packages.
317
     * @param string|null $styleTag The tag used to style the output. If `null`,
318
     *                              the default colors are used.
319
     * @param bool        $indent   Whether to indent the output.
320
     */
321 9
    private function printPackageTable(IO $io, array $packages, $styleTag = null, $indent = false)
322
    {
323 9
        $table = new Table(PuliTableStyle::borderless());
324 9
        $table->setHeaderRow(array('Package Name', 'Installer', 'Env', 'Install Path'));
325
326 9
        $installerTag = $styleTag ?: 'c1';
327 9
        $envTag = $styleTag ?: 'c1';
328 9
        $pathTag = $styleTag ?: 'c2';
329
330 9
        ksort($packages);
331
332 9
        foreach ($packages as $package) {
333 9
            $packageName = $package->getName();
334 9
            $installInfo = $package->getInstallInfo();
335 9
            $installPath = $installInfo ? $installInfo->getInstallPath() : '.';
336 9
            $installer = $installInfo ? $installInfo->getInstallerName() : '';
337 9
            $env = $installInfo ? $installInfo->getEnvironment() : Environment::PROD;
338
339 9
            $table->addRow(array(
340 9
                $styleTag ? sprintf('<%s>%s</%s>', $styleTag, $packageName, $styleTag) : $packageName,
341 9
                $installer ? sprintf('<%s>%s</%s>', $installerTag, $installer, $installerTag) : '',
342 9
                sprintf('<%s>%s</%s>', $envTag, $env, $envTag),
343 9
                sprintf('<%s>%s</%s>', $pathTag, $installPath, $pathTag),
344
            ));
345
        }
346
347 9
        $table->render($io, $indent ? 4 : 0);
348 9
    }
349
350
    /**
351
     * Prints not-loadable packages in a table.
352
     *
353
     * @param IO        $io       The I/O.
354
     * @param Package[] $packages The not-loadable packages.
355
     * @param bool      $indent   Whether to indent the output.
356
     */
357 5
    private function printNotLoadablePackages(IO $io, array $packages, $indent = false)
358
    {
359 5
        $rootDir = $this->packageManager->getContext()->getRootDirectory();
360 5
        $table = new Table(PuliTableStyle::borderless());
361 5
        $table->setHeaderRow(array('Package Name', 'Error'));
362
363 5
        ksort($packages);
364
365 5
        foreach ($packages as $package) {
366 5
            $packageName = $package->getName();
367 5
            $loadErrors = $package->getLoadErrors();
368 5
            $errorMessage = '';
369
370 5
            foreach ($loadErrors as $loadError) {
371 5
                $errorMessage .= StringUtil::getShortClassName(get_class($loadError)).': '.$loadError->getMessage()."\n";
372
            }
373
374 5
            $errorMessage = rtrim($errorMessage);
375
376 5
            if (!$errorMessage) {
377
                $errorMessage = 'Unknown error.';
378
            }
379
380
            // Remove root directory
381 5
            $errorMessage = str_replace($rootDir.'/', '', $errorMessage);
382
383 5
            $table->addRow(array(
384 5
                sprintf('<bad>%s</bad>', $packageName),
385 5
                sprintf('<bad>%s</bad>', $errorMessage),
386
            ));
387
        }
388
389 5
        $table->render($io, $indent ? 4 : 0);
390 5
    }
391
}
392