CommandLoader   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 132
Duplicated Lines 0 %

Test Coverage

Coverage 98.28%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 16
eloc 51
c 4
b 0
f 0
dl 0
loc 132
ccs 57
cts 58
cp 0.9828
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getCommandInstance() 0 18 3
A has() 0 3 1
A getNames() 0 3 1
A __construct() 0 3 1
A get() 0 23 3
A validateAliases() 0 5 3
A setCommandMap() 0 32 4
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
    /**
20
     * @psalm-var array<string, array{
21
     *     name: non-empty-string,
22
     *     aliases: non-empty-string[],
23
     *     class: class-string<Command>,
24
     *     hidden: bool,
25
     * }>
26
     */
27
    private array $commandMap;
28
29
    /**
30
     * @var string[]
31
     */
32
    private array $commandNames;
33
34
    /**
35
     * @param array $commandMap An array with command names as keys and service ids as values.
36
     *
37
     * @psalm-param array<string, class-string<Command>> $commandMap
38
     */
39 49
    public function __construct(private ContainerInterface $container, array $commandMap)
40
    {
41 49
        $this->setCommandMap($commandMap);
42
    }
43
44 15
    public function get(string $name): Command
45
    {
46 15
        if (!$this->has($name)) {
47 1
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
48
        }
49
50 14
        $commandName = $this->commandMap[$name]['name'];
51 14
        $commandAliases = $this->commandMap[$name]['aliases'];
52 14
        $commandClass = $this->commandMap[$name]['class'];
53 14
        $commandHidden = $this->commandMap[$name]['hidden'];
54
55 14
        $description = $commandClass::getDefaultDescription();
56
57 14
        if ($description === null) {
58
            return $this->getCommandInstance($name);
59
        }
60
61 14
        return new LazyCommand(
62 14
            $commandName,
63 14
            $commandAliases,
64 14
            $description,
65 14
            $commandHidden,
66 14
            fn () => $this->getCommandInstance($name),
67 14
        );
68
    }
69
70 15
    public function has(string $name): bool
71
    {
72 15
        return isset($this->commandMap[$name]);
73
    }
74
75 3
    public function getNames(): array
76
    {
77 3
        return $this->commandNames;
78
    }
79
80 10
    private function getCommandInstance(string $name): Command
81
    {
82 10
        $commandName = $this->commandMap[$name]['name'];
83 10
        $commandClass = $this->commandMap[$name]['class'];
84 10
        $commandAliases = $this->commandMap[$name]['aliases'];
85
86
        /** @var Command $command */
87 10
        $command = $this->container->get($commandClass);
88
89 9
        if ($command->getName() !== $commandName) {
90 1
            $command->setName($commandName);
91
        }
92
93 9
        if ($command->getAliases() !== $commandAliases) {
94 2
            $command->setAliases($commandAliases);
95
        }
96
97 9
        return $command;
98
    }
99
100
    /**
101
     * @psalm-param array<string, class-string<Command>> $commandMap
102
     */
103 49
    private function setCommandMap(array $commandMap): void
104
    {
105 49
        $this->commandMap = [];
106 49
        $this->commandNames = [];
107
108 49
        foreach ($commandMap as $name => $class) {
109 49
            $aliases = explode('|', $name);
110
111 49
            $hidden = false;
112 49
            if ($aliases[0] === '') {
113 2
                $hidden = true;
114 2
                array_shift($aliases);
115
            }
116
117
            /** @var string[] $aliases Fix for psalm. See {@link https://github.com/vimeo/psalm/issues/9261}. */
118
119 49
            $this->validateAliases($aliases);
120
121 49
            $primaryName = array_shift($aliases);
122
123 49
            $item = [
124 49
                'name' => $primaryName,
125 49
                'aliases' => $aliases,
126 49
                'class' => $class,
127 49
                'hidden' => $hidden,
128 49
            ];
129
130 49
            $this->commandMap[$primaryName] = $item;
131 49
            $this->commandNames[] = $primaryName;
132
133 49
            foreach ($aliases as $alias) {
134 48
                $this->commandMap[$alias] = $item;
135
            }
136
        }
137
    }
138
139
    /**
140
     * @psalm-param string[] $aliases
141
     *
142
     * @psalm-assert non-empty-string[] $aliases
143
     */
144 49
    private function validateAliases(array $aliases): void
145
    {
146 49
        foreach ($aliases as $alias) {
147 49
            if ($alias === '') {
148 2
                throw new RuntimeException('Do not allow empty command name or alias.');
149
            }
150
        }
151
    }
152
}
153