BindCommandHandler::handleList()   F
last analyzed

Complexity

Conditions 14
Paths 960

Size

Total Lines 52
Code Lines 31

Duplication

Lines 5
Ratio 9.62 %

Code Coverage

Tests 31
CRAP Score 14

Importance

Changes 6
Bugs 1 Features 0
Metric Value
c 6
b 1
f 0
dl 5
loc 52
ccs 31
cts 31
cp 1
rs 3.266
cc 14
eloc 31
nc 960
nop 2
crap 14

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\ArgsUtil;
16
use Puli\Cli\Util\StringUtil;
17
use Puli\Discovery\Binding\ClassBinding;
18
use Puli\Discovery\Binding\ResourceBinding;
19
use Puli\Manager\Api\Discovery\BindingDescriptor;
20
use Puli\Manager\Api\Discovery\BindingState;
21
use Puli\Manager\Api\Discovery\DiscoveryManager;
22
use Puli\Manager\Api\Module\ModuleList;
23
use Puli\Manager\Api\Module\RootModule;
24
use RuntimeException;
25
use Webmozart\Console\Api\Args\Args;
26
use Webmozart\Console\Api\IO\IO;
27
use Webmozart\Console\UI\Component\Table;
28
use Webmozart\Expression\Expr;
29
use Webmozart\PathUtil\Path;
30
31
/**
32
 * Handles the "bind" command.
33
 *
34
 * @since  1.0
35
 *
36
 * @author Bernhard Schussek <[email protected]>
37
 */
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)
62
    {
63 40
        $this->discoveryManager = $discoveryManager;
64 40
        $this->modules = $modules;
65 40
    }
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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)
136
    {
137 8
        $flags = $args->isOptionSet('force')
138 1
            ? DiscoveryManager::OVERRIDE | DiscoveryManager::IGNORE_TYPE_NOT_FOUND
139 1
                | DiscoveryManager::IGNORE_TYPE_NOT_ENABLED
140 8
            : 0;
141
142 8
        $bindingParams = array();
143 8
        $artifact = $args->getArgument('artifact');
144
145 8
        $this->parseParams($args, $bindingParams);
146
147 7
        if (false !== strpos($artifact, '\\') || $args->isOptionSet('class')) {
148 2
            $binding = new ClassBinding(
149
                $artifact,
150 2
                $args->getArgument('type'),
151
                $bindingParams
152
            );
153
        } else {
154 5
            $binding = new ResourceBinding(
155 5
                Path::makeAbsolute($artifact, $this->currentPath),
156 5
                $args->getArgument('type'),
157
                $bindingParams,
158 5
                $args->getOption('language')
159
            );
160
        }
161
162 7
        $this->discoveryManager->addRootBindingDescriptor(new BindingDescriptor($binding), $flags);
163
164 7
        return 0;
165
    }
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)
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...
221
    {
222 4
        $bindingToRemove = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
223
224 2
        if (!$bindingToRemove->getContainingModule() instanceof RootModule) {
225 1
            throw new RuntimeException(sprintf(
226 1
                'Can only delete bindings from the module "%s".',
227 1
                $this->modules->getRootModuleName()
228
            ));
229
        }
230
231 1
        $this->discoveryManager->removeRootBindingDescriptor($bindingToRemove->getUuid());
232
233 1
        return 0;
234
    }
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)
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...
244
    {
245 3
        $bindingToEnable = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
246
247 2
        if ($bindingToEnable->getContainingModule() instanceof RootModule) {
248 1
            throw new RuntimeException(sprintf(
249 1
                'Cannot enable bindings in the module "%s".',
250 1
                $bindingToEnable->getContainingModule()->getName()
251
            ));
252
        }
253
254 1
        $this->discoveryManager->enableBindingDescriptor($bindingToEnable->getUuid());
255
256 1
        return 0;
257
    }
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)
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...
267
    {
268 3
        $bindingToDisable = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
269
270 2
        if ($bindingToDisable->getContainingModule() instanceof RootModule) {
271 1
            throw new RuntimeException(sprintf(
272 1
                'Cannot disable bindings in the module "%s".',
273 1
                $bindingToDisable->getContainingModule()->getName()
274
            ));
275
        }
276
277 1
        $this->discoveryManager->disableBindingDescriptor($bindingToDisable->getUuid());
278
279 1
        return 0;
280
    }
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
0 ignored issues
show
Documentation introduced by
Should the return type not be array<integer|string>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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)
414
    {
415 14
        foreach ($args->getOption('param') as $parameter) {
416 4
            $pos = strpos($parameter, '=');
417
418 4
            if (false === $pos) {
419 1
                throw new RuntimeException(sprintf(
420
                    'The "--param" option expects a parameter in the form '.
421 1
                    '"key=value". Got: "%s"',
422
                    $parameter
423
                ));
424
            }
425
426 3
            $key = substr($parameter, 0, $pos);
427 3
            $value = StringUtil::parseValue(substr($parameter, $pos + 1));
428
429 3
            $bindingParams[$key] = $value;
430
        }
431 13
    }
432
433 6
    private function unsetParams(Args $args, array &$bindingParams)
434
    {
435 6
        foreach ($args->getOption('unset-param') as $parameter) {
436 1
            unset($bindingParams[$parameter]);
437
        }
438 6
    }
439
440
    /**
441
     * @param string $uuidPrefix
442
     *
443
     * @return BindingDescriptor
444
     */
445 17 View Code Duplication
    private function getBindingByUuidPrefix($uuidPrefix)
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...
446
    {
447 17
        $expr = Expr::method('getUuid', Expr::startsWith($uuidPrefix));
448 17
        $descriptors = $this->discoveryManager->findBindingDescriptors($expr);
449
450 17
        if (0 === count($descriptors)) {
451 3
            throw new RuntimeException(sprintf('The binding "%s" does not exist.', $uuidPrefix));
452
        }
453
454 14
        if (count($descriptors) > 1) {
455 1
            throw new RuntimeException(sprintf(
456 1
                'More than one binding matches the UUID prefix "%s".',
457
                $uuidPrefix
458
            ));
459
        }
460
461 13
        return reset($descriptors);
462
    }
463
464 6
    private function bindingsEqual(BindingDescriptor $descriptor1, BindingDescriptor $descriptor2)
465
    {
466 6
        return $descriptor1->getBinding() == $descriptor2->getBinding();
467
    }
468
469
    /**
470
     * @param Args            $args
471
     * @param ResourceBinding $bindingToUpdate
472
     *
473
     * @return ResourceBinding
474
     */
475 5
    private function getUpdatedResourceBinding(Args $args, ResourceBinding $bindingToUpdate)
476
    {
477 5
        $query = $bindingToUpdate->getQuery();
478 5
        $typeName = $bindingToUpdate->getTypeName();
479 5
        $language = $bindingToUpdate->getLanguage();
480 5
        $bindingParams = $bindingToUpdate->getParameterValues();
481
482 5
        if ($args->isOptionSet('query')) {
483 3
            $query = $args->getOption('query');
484
        }
485
486 5
        if ($args->isOptionSet('type')) {
487 1
            $typeName = $args->getOption('type');
488
        }
489
490 5
        if ($args->isOptionSet('language')) {
491 1
            $language = $args->getOption('language');
492
        }
493
494 5
        $this->parseParams($args, $bindingParams);
495 5
        $this->unsetParams($args, $bindingParams);
496
497 5
        return new ResourceBinding(
498 5
            Path::makeAbsolute($query, $this->currentPath),
499
            $typeName,
500
            $bindingParams,
501
            $language,
502 5
            $bindingToUpdate->getUuid()
503
        );
504
    }
505
506
    /**
507
     * @param Args         $args
508
     * @param ClassBinding $bindingToUpdate
509
     *
510
     * @return ClassBinding
511
     */
512 1
    private function getUpdatedClassBinding(Args $args, ClassBinding $bindingToUpdate)
513
    {
514 1
        $className = $bindingToUpdate->getClassName();
515 1
        $typeName = $bindingToUpdate->getTypeName();
516 1
        $bindingParams = $bindingToUpdate->getParameterValues();
517
518 1
        if ($args->isOptionSet('class')) {
519 1
            $className = $args->getOption('class');
520
        }
521
522 1
        if ($args->isOptionSet('type')) {
523 1
            $typeName = $args->getOption('type');
524
        }
525
526 1
        $this->parseParams($args, $bindingParams);
527 1
        $this->unsetParams($args, $bindingParams);
528
529 1
        return new ClassBinding(
530
            $className,
531
            $typeName,
532
            $bindingParams,
533 1
            $bindingToUpdate->getUuid()
534
        );
535
    }
536
}
537