Completed
Pull Request — master (#32)
by Bernhard
48:01
created

BindCommandHandler::handleList()   F

Complexity

Conditions 13
Paths 480

Size

Total Lines 45
Code Lines 27

Duplication

Lines 5
Ratio 11.11 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 5
loc 45
ccs 0
cts 27
cp 0
rs 3.2937
cc 13
eloc 27
nc 480
nop 2
crap 182

How to fix   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\Package\PackageCollection;
23
use Puli\Manager\Api\Package\RootPackage;
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 PackageCollection
47
     */
48
    private $packages;
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 PackageCollection $packages         The loaded packages.
60
     */
61 39
    public function __construct(DiscoveryManager $discoveryManager, PackageCollection $packages)
62
    {
63 39
        $this->discoveryManager = $discoveryManager;
64 39
        $this->packages = $packages;
65 39
    }
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
    public function handleList(Args $args, IO $io)
76
    {
77
        $packageNames = ArgsUtil::getPackageNames($args, $this->packages);
78
        $bindingStates = $this->getBindingStates($args);
79
80
        $printBindingState = count($bindingStates) > 1;
81
        $printPackageName = count($packageNames) > 1;
82
        $printHeaders = $printBindingState || $printPackageName;
83
        $indentation = $printBindingState && $printPackageName ? 8
84
            : ($printBindingState || $printPackageName ? 4 : 0);
85
86
        foreach ($bindingStates as $bindingState) {
87
            $bindingStatePrinted = !$printBindingState;
88
89
            foreach ($packageNames as $packageName) {
90
                $expr = Expr::method('getContainingPackage', Expr::method('getName', Expr::same($packageName)))
91
                    ->andMethod('getState', Expr::same($bindingState));
92
93
                $descriptors = $this->discoveryManager->findBindingDescriptors($expr);
94
95
                if (empty($descriptors)) {
96
                    continue;
97
                }
98
99
                if (!$bindingStatePrinted) {
100
                    $this->printBindingStateHeader($io, $bindingState);
101
                    $bindingStatePrinted = true;
102
                }
103
104 View Code Duplication
                if ($printPackageName) {
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...
105
                    $prefix = $printBindingState ? '    ' : '';
106
                    $io->writeLine(sprintf('%sPackage: %s', $prefix, $packageName));
107
                    $io->writeLine('');
108
                }
109
110
                $this->printBindingTable($io, $descriptors, $indentation, BindingState::ENABLED === $bindingState);
111
112
                if ($printHeaders) {
113
                    $io->writeLine('');
114
                }
115
            }
116
        }
117
118
        return 0;
119
    }
120
121
    /**
122
     * Handles the "bind <query> <type>" command.
123
     *
124
     * @param Args $args The console arguments.
125
     *
126
     * @return int The status code.
127
     */
128 8
    public function handleAdd(Args $args)
129
    {
130 8
        $flags = $args->isOptionSet('force')
131 1
            ? DiscoveryManager::OVERRIDE | DiscoveryManager::IGNORE_TYPE_NOT_FOUND
132 1
                | DiscoveryManager::IGNORE_TYPE_NOT_ENABLED
133 8
            : 0;
134
135 8
        $bindingParams = array();
136 8
        $artifact = $args->getArgument('artifact');
137
138 8
        $this->parseParams($args, $bindingParams);
139
140 7
        if (false !== strpos($artifact, '\\') || $args->isOptionSet('class')) {
141 2
            $binding = new ClassBinding(
142
                $artifact,
143 2
                $args->getArgument('type'),
144
                $bindingParams
145
            );
146
        } else {
147 5
            $binding = new ResourceBinding(
148 5
                Path::makeAbsolute($artifact, $this->currentPath),
149 5
                $args->getArgument('type'),
150
                $bindingParams,
151 5
                $args->getOption('language')
152
            );
153
        }
154
155 7
        $this->discoveryManager->addRootBindingDescriptor(new BindingDescriptor($binding), $flags);
156
157
        return 0;
158
    }
159
160
    /**
161
     * Handles the "bind --update <uuid>" command.
162
     *
163
     * @param Args $args The console arguments.
164
     *
165
     * @return int The status code.
166
     */
167
    public function handleUpdate(Args $args)
168
    {
169
        $flags = $args->isOptionSet('force')
170
            ? DiscoveryManager::OVERRIDE | DiscoveryManager::IGNORE_TYPE_NOT_FOUND
171
                | DiscoveryManager::IGNORE_TYPE_NOT_ENABLED
172
            : DiscoveryManager::OVERRIDE;
173
174
        $descriptorToUpdate = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
175
        $bindingToUpdate = $descriptorToUpdate->getBinding();
176
177
        if (!$descriptorToUpdate->getContainingPackage() instanceof RootPackage) {
178
            throw new RuntimeException(sprintf(
179
                'Can only update bindings in the package "%s".',
180
                $this->packages->getRootPackageName()
181
            ));
182
        }
183
184
        if ($bindingToUpdate instanceof ResourceBinding) {
185
            $updatedBinding = $this->getUpdatedResourceBinding($args, $bindingToUpdate);
186
        } elseif ($bindingToUpdate instanceof ClassBinding) {
187
            $updatedBinding = $this->getUpdatedClassBinding($args, $bindingToUpdate);
188
        } else {
189
            throw new RuntimeException(sprintf(
190
                'Cannot update bindings of type %s.',
191
                get_class($bindingToUpdate)
192
            ));
193
        }
194
195
        $updatedDescriptor = new BindingDescriptor($updatedBinding);
196
197
        if ($this->bindingsEqual($descriptorToUpdate, $updatedDescriptor)) {
198
            throw new RuntimeException('Nothing to update.');
199
        }
200
201
        $this->discoveryManager->addRootBindingDescriptor($updatedDescriptor, $flags);
202
203
        return 0;
204
    }
205
206
    /**
207
     * Handles the "bind --delete" command.
208
     *
209
     * @param Args $args The console arguments.
210
     *
211
     * @return int The status code.
212
     */
213 1 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...
214
    {
215 1
        $bindingToRemove = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
216
217
        if (!$bindingToRemove->getContainingPackage() instanceof RootPackage) {
218
            throw new RuntimeException(sprintf(
219
                'Can only delete bindings from the package "%s".',
220
                $this->packages->getRootPackageName()
221
            ));
222
        }
223
224
        $this->discoveryManager->removeRootBindingDescriptor($bindingToRemove->getUuid());
225
226
        return 0;
227
    }
228
229
    /**
230
     * Handles the "bind --enable" command.
231
     *
232
     * @param Args $args The console arguments.
233
     *
234
     * @return int The status code.
235
     */
236 1 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...
237
    {
238 1
        $bindingToEnable = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
239
240
        if ($bindingToEnable->getContainingPackage() instanceof RootPackage) {
241
            throw new RuntimeException(sprintf(
242
                'Cannot enable bindings in the package "%s".',
243
                $bindingToEnable->getContainingPackage()->getName()
244
            ));
245
        }
246
247
        $this->discoveryManager->enableBindingDescriptor($bindingToEnable->getUuid());
248
249
        return 0;
250
    }
251
252
    /**
253
     * Handles the "bind --disable" command.
254
     *
255
     * @param Args $args The console arguments.
256
     *
257
     * @return int The status code.
258
     */
259 1 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...
260
    {
261 1
        $bindingToDisable = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
262
263
        if ($bindingToDisable->getContainingPackage() instanceof RootPackage) {
264
            throw new RuntimeException(sprintf(
265
                'Cannot disable bindings in the package "%s".',
266
                $bindingToDisable->getContainingPackage()->getName()
267
            ));
268
        }
269
270
        $this->discoveryManager->disableBindingDescriptor($bindingToDisable->getUuid());
271
272
        return 0;
273
    }
274
275
    /**
276
     * Returns the binding states selected in the console arguments.
277
     *
278
     * @param Args $args The console arguments.
279
     *
280
     * @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...
281
     */
282
    private function getBindingStates(Args $args)
283
    {
284
        $states = array(
285
            BindingState::ENABLED => 'enabled',
286
            BindingState::DISABLED => 'disabled',
287
            BindingState::TYPE_NOT_FOUND => 'type-not-found',
288
            BindingState::TYPE_NOT_ENABLED => 'type-not-enabled',
289
            BindingState::INVALID => 'invalid',
290
        );
291
292
        $states = array_filter($states, function ($option) use ($args) {
293
            return $args->isOptionSet($option);
294
        });
295
296
        return array_keys($states) ?: BindingState::all();
297
    }
298
299
    /**
300
     * Prints a list of binding descriptors.
301
     *
302
     * @param IO                  $io          The I/O.
303
     * @param BindingDescriptor[] $descriptors The binding descriptors.
304
     * @param int                 $indentation The number of spaces to indent.
305
     * @param bool                $enabled     Whether the binding descriptors
306
     *                                         are enabled. If not, the output
307
     *                                         is printed in red.
308
     */
309
    private function printBindingTable(IO $io, array $descriptors, $indentation = 0, $enabled = true)
310
    {
311
        $table = new Table(PuliTableStyle::borderless());
312
313
        $table->setHeaderRow(array('UUID', 'Glob', 'Type'));
314
315
        $paramTag = $enabled ? 'c1' : 'bad';
316
        $artifactTag = $enabled ? 'c1' : 'bad';
317
        $typeTag = $enabled ? 'u' : 'bad';
318
319
        foreach ($descriptors as $descriptor) {
320
            $parameters = array();
321
            $binding = $descriptor->getBinding();
322
323
            foreach ($binding->getParameterValues() as $parameterName => $parameterValue) {
324
                $parameters[] = $parameterName.'='.StringUtil::formatValue($parameterValue);
325
            }
326
327
            $uuid = substr($descriptor->getUuid(), 0, 6);
328
329
            if (!$enabled) {
330
                $uuid = sprintf('<bad>%s</bad>', $uuid);
331
            }
332
333
            $paramString = '';
334
335
            if (!empty($parameters)) {
336
                // \xc2\xa0 is a non-breaking space
337
                $paramString = sprintf(
338
                    ' <%s>(%s)</%s>',
339
                    $paramTag,
340
                    implode(",\xc2\xa0", $parameters),
341
                    $paramTag
342
                );
343
            }
344
345
            if ($binding instanceof ResourceBinding) {
346
                $artifact = $binding->getQuery();
347
            } elseif ($binding instanceof ClassBinding) {
348
                $artifact = StringUtil::getShortClassName($binding->getClassName());
349
            } else {
350
                continue;
351
            }
352
353
            $typeString = StringUtil::getShortClassName($binding->getTypeName());
354
355
            $table->addRow(array(
356
                $uuid,
357
                sprintf('<%s>%s</%s>', $artifactTag, $artifact, $artifactTag),
358
                sprintf('<%s>%s</%s>%s', $typeTag, $typeString, $typeTag, $paramString),
359
            ));
360
        }
361
362
        $table->render($io, $indentation);
363
    }
364
365
    /**
366
     * Prints the header for a binding state.
367
     *
368
     * @param IO  $io           The I/O.
369
     * @param int $bindingState The {@link BindingState} constant.
370
     */
371
    private function printBindingStateHeader(IO $io, $bindingState)
372
    {
373
        switch ($bindingState) {
374
            case BindingState::ENABLED:
375
                $io->writeLine('The following bindings are currently enabled:');
376
                $io->writeLine('');
377
378
                return;
379
            case BindingState::DISABLED:
380
                $io->writeLine('The following bindings are disabled:');
381
                $io->writeLine(' (use "puli bind --enable <uuid>" to enable)');
382
                $io->writeLine('');
383
384
                return;
385
            case BindingState::TYPE_NOT_FOUND:
386
                $io->writeLine('The types of the following bindings could not be found:');
387
                $io->writeLine(' (install or create their type definitions to enable)');
388
                $io->writeLine('');
389
390
                return;
391
            case BindingState::TYPE_NOT_ENABLED:
392
                $io->writeLine('The types of the following bindings are not enabled:');
393
                $io->writeLine(' (remove the duplicate type definitions to enable)');
394
                $io->writeLine('');
395
396
                return;
397
            case BindingState::INVALID:
398
                $io->writeLine('The following bindings have invalid parameters:');
399
                $io->writeLine(' (remove the binding and add again with correct parameters)');
400
                $io->writeLine('');
401
402
                return;
403
        }
404
    }
405
406 8
    private function parseParams(Args $args, array &$bindingParams)
407
    {
408 8
        foreach ($args->getOption('param') as $parameter) {
409 2
            $pos = strpos($parameter, '=');
410
411 2
            if (false === $pos) {
412 1
                throw new RuntimeException(sprintf(
413
                    'The "--param" option expects a parameter in the form '.
414 1
                    '"key=value". Got: "%s"',
415
                    $parameter
416
                ));
417
            }
418
419 1
            $key = substr($parameter, 0, $pos);
420 1
            $value = StringUtil::parseValue(substr($parameter, $pos + 1));
421
422 1
            $bindingParams[$key] = $value;
423
        }
424 7
    }
425
426
    private function unsetParams(Args $args, array &$bindingParams)
427
    {
428
        foreach ($args->getOption('unset-param') as $parameter) {
429
            unset($bindingParams[$parameter]);
430
        }
431
    }
432
433
    /**
434
     * @param string $uuidPrefix
435
     *
436
     * @return BindingDescriptor
437
     */
438 3 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...
439
    {
440 3
        $expr = Expr::method('getUuid', Expr::startsWith($uuidPrefix));
441 3
        $descriptors = $this->discoveryManager->findBindingDescriptors($expr);
442
443 3
        if (0 === count($descriptors)) {
444 3
            throw new RuntimeException(sprintf('The binding "%s" does not exist.', $uuidPrefix));
445
        }
446
447
        if (count($descriptors) > 1) {
448
            throw new RuntimeException(sprintf(
449
                'More than one binding matches the UUID prefix "%s".',
450
                $uuidPrefix
451
            ));
452
        }
453
454
        return reset($descriptors);
455
    }
456
457
    private function bindingsEqual(BindingDescriptor $descriptor1, BindingDescriptor $descriptor2)
458
    {
459
        return $descriptor1->getBinding() == $descriptor2->getBinding();
460
    }
461
462
    /**
463
     * @param Args            $args
464
     * @param ResourceBinding $bindingToUpdate
465
     *
466
     * @return ResourceBinding
467
     */
468
    private function getUpdatedResourceBinding(Args $args, ResourceBinding $bindingToUpdate)
469
    {
470
        $query = $bindingToUpdate->getQuery();
471
        $typeName = $bindingToUpdate->getTypeName();
472
        $language = $bindingToUpdate->getLanguage();
473
        $bindingParams = $bindingToUpdate->getParameterValues();
474
475
        if ($args->isOptionSet('query')) {
476
            $query = $args->getOption('query');
477
        }
478
479
        if ($args->isOptionSet('type')) {
480
            $typeName = $args->getOption('type');
481
        }
482
483
        if ($args->isOptionSet('language')) {
484
            $language = $args->getOption('language');
485
        }
486
487
        $this->parseParams($args, $bindingParams);
488
        $this->unsetParams($args, $bindingParams);
489
490
        return new ResourceBinding(
491
            Path::makeAbsolute($query, $this->currentPath),
492
            $typeName,
493
            $bindingParams,
494
            $language,
495
            $bindingToUpdate->getUuid()
496
        );
497
    }
498
499
    /**
500
     * @param Args         $args
501
     * @param ClassBinding $bindingToUpdate
502
     *
503
     * @return ClassBinding
504
     */
505
    private function getUpdatedClassBinding(Args $args, ClassBinding $bindingToUpdate)
506
    {
507
        $className = $bindingToUpdate->getClassName();
508
        $typeName = $bindingToUpdate->getTypeName();
509
        $bindingParams = $bindingToUpdate->getParameterValues();
510
511
        if ($args->isOptionSet('class')) {
512
            $className = $args->getOption('class');
513
        }
514
515
        if ($args->isOptionSet('type')) {
516
            $typeName = $args->getOption('type');
517
        }
518
519
        $this->parseParams($args, $bindingParams);
520
        $this->unsetParams($args, $bindingParams);
521
522
        return new ClassBinding(
523
            $className,
524
            $typeName,
525
            $bindingParams,
526
            $bindingToUpdate->getUuid()
527
        );
528
    }
529
}
530