Failed Conditions
Pull Request — master (#40)
by Tobias
06:48
created

src/Handler/BindCommandHandler.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Repository\Discovery\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 40
    public function __construct(DiscoveryManager $discoveryManager, PackageCollection $packages)
62
    {
63 40
        $this->discoveryManager = $discoveryManager;
64 40
        $this->packages = $packages;
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
        $packageNames = ArgsUtil::getPackageNames($args, $this->packages);
78 15
        $bindingStates = $this->getBindingStates($args);
79
80 15
        $printBindingState = count($bindingStates) > 1;
81 15
        $printPackageName = count($packageNames) > 1;
82 15
        $printHeaders = $printBindingState || $printPackageName;
83 15
        $printAdvice = true;
84 15
        $indentation = $printBindingState && $printPackageName ? 8
85 15
            : ($printBindingState || $printPackageName ? 4 : 0);
86
87 15
        foreach ($bindingStates as $bindingState) {
88 15
            $bindingStatePrinted = !$printBindingState;
89
90 15
            foreach ($packageNames as $packageName) {
91 15
                $expr = Expr::method('getContainingPackage', Expr::method('getName', Expr::same($packageName)))
92 15
                    ->andMethod('getState', Expr::same($bindingState));
93
94 15
                $descriptors = $this->discoveryManager->findBindingDescriptors($expr);
95
96 15
                if (empty($descriptors)) {
97 1
                    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 ($printPackageName) {
108 10
                    $prefix = $printBindingState ? '    ' : '';
109 10
                    $io->writeLine(sprintf('%sPackage: %s', $prefix, $packageName));
110 10
                    $io->writeLine('');
111
                }
112
113 14
                $this->printBindingTable($io, $descriptors, $indentation, BindingState::ENABLED === $bindingState);
114
115
                if ($printHeaders) {
116 1
                    $io->writeLine('');
117
                }
118
            }
119
        }
120
121 1
        if ($printAdvice) {
122 1
            $io->writeLine('No bindings found. Use "puli bind <artifact> <type>" to bind an artifact to a type.');
123
        }
124
125 1
        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 5
    public function handleUpdate(Args $args)
175
    {
176 5
        $flags = $args->isOptionSet('force')
177 1
            ? DiscoveryManager::OVERRIDE | DiscoveryManager::IGNORE_TYPE_NOT_FOUND
178 1
                | DiscoveryManager::IGNORE_TYPE_NOT_ENABLED
179 5
            : DiscoveryManager::OVERRIDE;
180
181 5
        $descriptorToUpdate = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
182 5
        $bindingToUpdate = $descriptorToUpdate->getBinding();
183
184 5
        if (!$descriptorToUpdate->getContainingPackage() instanceof RootPackage) {
185 1
            throw new RuntimeException(sprintf(
186 1
                'Can only update bindings in the package "%s".',
187 1
                $this->packages->getRootPackageName()
188
            ));
189
        }
190
191 4
        if ($bindingToUpdate instanceof ResourceBinding) {
192 4
            $updatedBinding = $this->getUpdatedResourceBinding($args, $bindingToUpdate);
193
        } elseif ($bindingToUpdate instanceof ClassBinding) {
194
            $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
        $updatedDescriptor = new BindingDescriptor($updatedBinding);
203
204
        if ($this->bindingsEqual($descriptorToUpdate, $updatedDescriptor)) {
205
            throw new RuntimeException('Nothing to update.');
206
        }
207
208
        $this->discoveryManager->addRootBindingDescriptor($updatedDescriptor, $flags);
209
210
        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 3 View Code Duplication
    public function handleDelete(Args $args)
221
    {
222 3
        $bindingToRemove = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
223
224 1
        if (!$bindingToRemove->getContainingPackage() instanceof RootPackage) {
225 1
            throw new RuntimeException(sprintf(
226 1
                'Can only delete bindings from the package "%s".',
227 1
                $this->packages->getRootPackageName()
228
            ));
229
        }
230
231
        $this->discoveryManager->removeRootBindingDescriptor($bindingToRemove->getUuid());
232
233
        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 2 View Code Duplication
    public function handleEnable(Args $args)
244
    {
245 2
        $bindingToEnable = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
246
247 1
        if ($bindingToEnable->getContainingPackage() instanceof RootPackage) {
248 1
            throw new RuntimeException(sprintf(
249 1
                'Cannot enable bindings in the package "%s".',
250 1
                $bindingToEnable->getContainingPackage()->getName()
251
            ));
252
        }
253
254
        $this->discoveryManager->enableBindingDescriptor($bindingToEnable->getUuid());
255
256
        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 2 View Code Duplication
    public function handleDisable(Args $args)
267
    {
268 2
        $bindingToDisable = $this->getBindingByUuidPrefix($args->getArgument('uuid'));
269
270 1
        if ($bindingToDisable->getContainingPackage() instanceof RootPackage) {
271 1
            throw new RuntimeException(sprintf(
272 1
                'Cannot disable bindings in the package "%s".',
273 1
                $bindingToDisable->getContainingPackage()->getName()
274
            ));
275
        }
276
277
        $this->discoveryManager->disableBindingDescriptor($bindingToDisable->getUuid());
278
279
        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.
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
            if (!$enabled) {
337
                $uuid = sprintf('<bad>%s</bad>', $uuid);
338
            }
339
340
            $paramString = '';
341
342
            if (!empty($parameters)) {
343
                // \xc2\xa0 is a non-breaking space
344
                $paramString = sprintf(
345
                    ' <%s>(%s)</%s>',
346
                    $paramTag,
347
                    implode(",\xc2\xa0", $parameters),
348
                    $paramTag
349
                );
350
            }
351
352
            if ($binding instanceof ResourceBinding) {
353
                $artifact = $binding->getQuery();
354
            } elseif ($binding instanceof ClassBinding) {
355
                $artifact = StringUtil::getShortClassName($binding->getClassName());
356
            } else {
357
                continue;
358
            }
359
360
            $typeString = StringUtil::getShortClassName($binding->getTypeName());
361
362
            $table->addRow(array(
363
                $uuid,
364
                sprintf('<%s>%s</%s>', $artifactTag, $artifact, $artifactTag),
365
                sprintf('<%s>%s</%s>%s', $typeTag, $typeString, $typeTag, $paramString),
366
            ));
367
        }
368
369
        $table->render($io, $indentation);
370
    }
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
            case BindingState::DISABLED:
387
                $io->writeLine('The following bindings are disabled:');
388
                $io->writeLine(' (use "puli bind --enable <uuid>" to enable)');
389
                $io->writeLine('');
390
391
                return;
392
            case BindingState::TYPE_NOT_FOUND:
393
                $io->writeLine('The types of the following bindings could not be found:');
394
                $io->writeLine(' (install or create their type definitions to enable)');
395
                $io->writeLine('');
396
397
                return;
398
            case BindingState::TYPE_NOT_ENABLED:
399
                $io->writeLine('The types of the following bindings are not enabled:');
400
                $io->writeLine(' (remove the duplicate type definitions to enable)');
401
                $io->writeLine('');
402
403
                return;
404
            case BindingState::INVALID:
405
                $io->writeLine('The following bindings have invalid parameters:');
406
                $io->writeLine(' (remove the binding and add again with correct parameters)');
407
                $io->writeLine('');
408
409
                return;
410
        }
411
    }
412
413 12
    private function parseParams(Args $args, array &$bindingParams)
414
    {
415 12
        foreach ($args->getOption('param') as $parameter) {
416 2
            $pos = strpos($parameter, '=');
417
418 2
            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 1
            $key = substr($parameter, 0, $pos);
427 1
            $value = StringUtil::parseValue(substr($parameter, $pos + 1));
428
429 1
            $bindingParams[$key] = $value;
430
        }
431 11
    }
432
433 4
    private function unsetParams(Args $args, array &$bindingParams)
434
    {
435 4
        foreach ($args->getOption('unset-param') as $parameter) {
436 1
            unset($bindingParams[$parameter]);
437
        }
438 4
    }
439
440
    /**
441
     * @param string $uuidPrefix
442
     *
443
     * @return BindingDescriptor
444
     */
445 12 View Code Duplication
    private function getBindingByUuidPrefix($uuidPrefix)
446
    {
447 12
        $expr = Expr::method('getUuid', Expr::startsWith($uuidPrefix));
448 12
        $descriptors = $this->discoveryManager->findBindingDescriptors($expr);
449
450 12
        if (0 === count($descriptors)) {
451 3
            throw new RuntimeException(sprintf('The binding "%s" does not exist.', $uuidPrefix));
452
        }
453
454 9
        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 8
        return reset($descriptors);
462
    }
463
464
    private function bindingsEqual(BindingDescriptor $descriptor1, BindingDescriptor $descriptor2)
465
    {
466
        return $descriptor1->getBinding() == $descriptor2->getBinding();
467
    }
468
469
    /**
470
     * @param Args            $args
471
     * @param ResourceBinding $bindingToUpdate
472
     *
473
     * @return ResourceBinding
474
     */
475 4
    private function getUpdatedResourceBinding(Args $args, ResourceBinding $bindingToUpdate)
476
    {
477 4
        $query = $bindingToUpdate->getQuery();
478 4
        $typeName = $bindingToUpdate->getTypeName();
479 4
        $language = $bindingToUpdate->getLanguage();
480 4
        $bindingParams = $bindingToUpdate->getParameterValues();
481
482 4
        if ($args->isOptionSet('query')) {
483 2
            $query = $args->getOption('query');
484
        }
485
486 4
        if ($args->isOptionSet('type')) {
487
            $typeName = $args->getOption('type');
488
        }
489
490 4
        if ($args->isOptionSet('language')) {
491
            $language = $args->getOption('language');
492
        }
493
494 4
        $this->parseParams($args, $bindingParams);
495 4
        $this->unsetParams($args, $bindingParams);
496
497 4
        return new ResourceBinding(
498 4
            Path::makeAbsolute($query, $this->currentPath),
499
            $typeName,
500
            $bindingParams,
501
            $language,
502
            $bindingToUpdate->getUuid()
0 ignored issues
show
The method getUuid() does not seem to exist on object<Puli\Repository\Discovery\ResourceBinding>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
503
        );
504
    }
505
506
    /**
507
     * @param Args         $args
508
     * @param ClassBinding $bindingToUpdate
509
     *
510
     * @return ClassBinding
511
     */
512
    private function getUpdatedClassBinding(Args $args, ClassBinding $bindingToUpdate)
513
    {
514
        $className = $bindingToUpdate->getClassName();
515
        $typeName = $bindingToUpdate->getTypeName();
516
        $bindingParams = $bindingToUpdate->getParameterValues();
517
518
        if ($args->isOptionSet('class')) {
519
            $className = $args->getOption('class');
520
        }
521
522
        if ($args->isOptionSet('type')) {
523
            $typeName = $args->getOption('type');
524
        }
525
526
        $this->parseParams($args, $bindingParams);
527
        $this->unsetParams($args, $bindingParams);
528
529
        return new ClassBinding(
530
            $className,
531
            $typeName,
532
            $bindingParams,
533
            $bindingToUpdate->getUuid()
0 ignored issues
show
The method getUuid() does not seem to exist on object<Puli\Discovery\Binding\ClassBinding>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
534
        );
535
    }
536
}
537