Passed
Pull Request — master (#129)
by Evgeniy
11:18
created

CommandLoader::validateAliases()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Console;
6
7
use Psr\Container\ContainerInterface;
8
use RuntimeException;
9
use Symfony\Component\Console\Command\Command;
10
use Symfony\Component\Console\Command\LazyCommand;
11
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
12
use Symfony\Component\Console\Exception\CommandNotFoundException;
13
14
use function array_shift;
15
use function explode;
16
17
final class CommandLoader implements CommandLoaderInterface
18
{
19
    private ContainerInterface $container;
20
21
    /**
22
     * @psalm-var array<string, array{
23
     *     name: non-empty-string,
24
     *     aliases: non-empty-string[],
25
     *     class: class-string<Command>,
26
     *     hidden: bool,
27
     * }>
28
     */
29
    private array $commandMap;
30
31
    /**
32
     * @var string[]
33
     */
34
    private array $commandNames;
35
36
    /**
37
     * @param array $commandMap An array with command names as keys and service ids as values.
38
     *
39
     * @psalm-param array<string, class-string> $commandMap
40
     */
41 43
    public function __construct(ContainerInterface $container, array $commandMap)
42
    {
43 43
        $this->container = $container;
44 43
        $this->setCommandMap($commandMap);
45 43
    }
46
47 11
    public function get(string $name)
48
    {
49 11
        if (!$this->has($name)) {
50 1
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
51
        }
52
53 10
        $commandName = $this->commandMap[$name]['name'];
54 10
        $commandAliases = $this->commandMap[$name]['aliases'];
55 10
        $commandClass = $this->commandMap[$name]['class'];
56 10
        $commandHidden = $this->commandMap[$name]['hidden'];
57
58 10
        $description = $commandClass::getDefaultDescription();
59
60 10
        if ($description === null) {
61
            return $this->getCommandInstance($name);
62
        }
63
64 10
        return new LazyCommand(
65 10
            $commandName,
66
            $commandAliases,
67
            $description,
68
            $commandHidden,
69 10
            fn () => $this->getCommandInstance($name),
70
        );
71
    }
72
73 11
    public function has(string $name): bool
74
    {
75 11
        return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]['class']);
76
    }
77
78 1
    public function getNames(): array
79
    {
80 1
        return $this->commandNames;
81
    }
82
83 8
    private function getCommandInstance(string $name): Command
84
    {
85 8
        $commandName = $this->commandMap[$name]['name'];
86 8
        $commandClass = $this->commandMap[$name]['class'];
87 8
        $commandAliases = $this->commandMap[$name]['aliases'];
88
89
        /** @var Command $command */
90 8
        $command = $this->container->get($commandClass);
91
92 8
        if ($command->getName() !== $commandName) {
93 1
            $command->setName($commandName);
94
        }
95
96 8
        if ($command->getAliases() !== $commandAliases) {
97 2
            $command->setAliases($commandAliases);
98
        }
99
100 8
        return $command;
101
    }
102
103
    /**
104
     * @psalm-param array<string, class-string> $commandMap
105
     */
106 43
    private function setCommandMap(array $commandMap): void
107
    {
108 43
        $this->commandMap = [];
109 43
        $this->commandNames = [];
110
111 43
        foreach ($commandMap as $name => $class) {
112 43
            $aliases = explode('|', $name);
113
114 43
            $hidden = false;
115 43
            if ($aliases[0] === '') {
116 1
                $hidden = true;
117 1
                array_shift($aliases);
118
            }
119
120 43
            $this->validateAliases($aliases);
121
122 43
            $primaryName = array_shift($aliases);
123
124 43
            $item = [
125 43
                'name' => $primaryName,
126 43
                'aliases' => $aliases,
127 43
                'class' => $class,
128 43
                'hidden' => $hidden,
129
            ];
130
131 43
            $this->commandMap[$primaryName] = $item;
132 43
            $this->commandNames[] = $primaryName;
133
134 43
            foreach ($aliases as $alias) {
135 43
                $this->commandMap[$alias] = $item;
136
            }
137
        }
138 43
    }
139
140
    /**
141
     * @psalm-param string[] $aliases
142
     *
143
     * @psalm-assert non-empty-string[] $aliases
144
     */
145 43
    private function validateAliases(array $aliases): void
146
    {
147 43
        foreach ($aliases as $alias) {
148 43
            if ($alias === '') {
149 2
                throw new RuntimeException('Do not allow empty command name or alias.');
150
            }
151
        }
152 43
    }
153
}
154