Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
29 / 29 |
|
96.88% |
31 / 32 |
|
66.67% |
14 / 21 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
| Commands | |
100.00% |
29 / 29 |
|
96.88% |
31 / 32 |
|
66.67% |
14 / 21 |
|
100.00% |
5 / 5 |
25.48 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| add | |
100.00% |
15 / 15 |
|
100.00% |
12 / 12 |
|
71.43% |
5 / 7 |
|
100.00% |
1 / 1 |
6.84 | |||
| entries | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| addArray | |
100.00% |
9 / 9 |
|
100.00% |
12 / 12 |
|
62.50% |
5 / 8 |
|
100.00% |
1 / 1 |
7.90 | |||
| validClass | |
100.00% |
3 / 3 |
|
83.33% |
5 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
2.50 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Console; |
| 6 | |
| 7 | use Closure; |
| 8 | use ValueError; |
| 9 | |
| 10 | /** |
| 11 | * Collects command registrations. |
| 12 | * |
| 13 | * Accepts command instances, class-strings of zero-argument constructible |
| 14 | * commands, and lazy factories keyed by class-string: |
| 15 | * |
| 16 | * $commands = new Commands([ |
| 17 | * new Greet($translator), |
| 18 | * Simple::class, |
| 19 | * Expensive::class => fn() => new Expensive($db), |
| 20 | * ]); |
| 21 | * |
| 22 | * Commands carry their metadata in a #[Command] attribute. For a |
| 23 | * lightweight one-off, register an anonymous class — attributes work |
| 24 | * inline: |
| 25 | * |
| 26 | * $commands->add(new #[Command('cache:clear', 'Clears the cache')] class { |
| 27 | * public function __invoke(Io $io): int { ... } |
| 28 | * }); |
| 29 | * |
| 30 | * @api |
| 31 | */ |
| 32 | final class Commands |
| 33 | { |
| 34 | /** @var list<Entry> */ |
| 35 | private array $entries = []; |
| 36 | |
| 37 | public function __construct(array|object|string $commands = []) |
| 38 | { |
| 39 | $this->add($commands); |
| 40 | } |
| 41 | |
| 42 | public function add(array|object|string $commands): void |
| 43 | { |
| 44 | if ($commands instanceof Closure) { |
| 45 | throw new ValueError( |
| 46 | 'Closure commands are not supported; use an anonymous class with a #[Command] attribute', |
| 47 | ); |
| 48 | } |
| 49 | |
| 50 | if ($commands instanceof Commands) { |
| 51 | foreach ($commands->entries() as $entry) { |
| 52 | $this->entries[] = $entry; |
| 53 | } |
| 54 | |
| 55 | return; |
| 56 | } |
| 57 | |
| 58 | if (is_array($commands)) { |
| 59 | $this->addArray($commands); |
| 60 | |
| 61 | return; |
| 62 | } |
| 63 | |
| 64 | if (is_string($commands)) { |
| 65 | $this->entries[] = Entry::fromClass($this->validClass($commands)); |
| 66 | |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | $this->entries[] = Entry::fromInstance($commands); |
| 71 | } |
| 72 | |
| 73 | /** @return list<Entry> */ |
| 74 | public function entries(): array |
| 75 | { |
| 76 | return $this->entries; |
| 77 | } |
| 78 | |
| 79 | private function addArray(array $commands): void |
| 80 | { |
| 81 | foreach ($commands as $key => $item) { |
| 82 | if (is_string($key)) { |
| 83 | if (!$item instanceof Closure) { |
| 84 | throw new ValueError("Factory for command class '{$key}' must be a closure"); |
| 85 | } |
| 86 | |
| 87 | $this->entries[] = Entry::fromFactory($this->validClass($key), $item); |
| 88 | |
| 89 | continue; |
| 90 | } |
| 91 | |
| 92 | if (!is_object($item) && !is_string($item)) { |
| 93 | throw new ValueError('Invalid command registration'); |
| 94 | } |
| 95 | |
| 96 | $this->add($item); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | /** @return class-string */ |
| 101 | private function validClass(string $class): string |
| 102 | { |
| 103 | if (!class_exists($class)) { |
| 104 | throw new ValueError("Unknown command class '{$class}'"); |
| 105 | } |
| 106 | |
| 107 | return $class; |
| 108 | } |
| 109 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 37 | public function __construct(array|object|string $commands = []) |
| 38 | { |
| 39 | $this->add($commands); |
| 40 | } |
| 42 | public function add(array|object|string $commands): void |
| 43 | { |
| 44 | if ($commands instanceof Closure) { |
| 45 | throw new ValueError( |
| 46 | 'Closure commands are not supported; use an anonymous class with a #[Command] attribute', |
| 50 | if ($commands instanceof Commands) { |
| 51 | foreach ($commands->entries() as $entry) { |
| 51 | foreach ($commands->entries() as $entry) { |
| 51 | foreach ($commands->entries() as $entry) { |
| 52 | $this->entries[] = $entry; |
| 51 | foreach ($commands->entries() as $entry) { |
| 52 | $this->entries[] = $entry; |
| 53 | } |
| 54 | |
| 55 | return; |
| 58 | if (is_array($commands)) { |
| 59 | $this->addArray($commands); |
| 60 | |
| 61 | return; |
| 64 | if (is_string($commands)) { |
| 65 | $this->entries[] = Entry::fromClass($this->validClass($commands)); |
| 66 | |
| 67 | return; |
| 70 | $this->entries[] = Entry::fromInstance($commands); |
| 71 | } |
| 79 | private function addArray(array $commands): void |
| 80 | { |
| 81 | foreach ($commands as $key => $item) { |
| 81 | foreach ($commands as $key => $item) { |
| 81 | foreach ($commands as $key => $item) { |
| 82 | if (is_string($key)) { |
| 83 | if (!$item instanceof Closure) { |
| 84 | throw new ValueError("Factory for command class '{$key}' must be a closure"); |
| 87 | $this->entries[] = Entry::fromFactory($this->validClass($key), $item); |
| 88 | |
| 89 | continue; |
| 92 | if (!is_object($item) && !is_string($item)) { |
| 92 | if (!is_object($item) && !is_string($item)) { |
| 92 | if (!is_object($item) && !is_string($item)) { |
| 93 | throw new ValueError('Invalid command registration'); |
| 81 | foreach ($commands as $key => $item) { |
| 82 | if (is_string($key)) { |
| 83 | if (!$item instanceof Closure) { |
| 84 | throw new ValueError("Factory for command class '{$key}' must be a closure"); |
| 85 | } |
| 86 | |
| 87 | $this->entries[] = Entry::fromFactory($this->validClass($key), $item); |
| 88 | |
| 89 | continue; |
| 90 | } |
| 91 | |
| 92 | if (!is_object($item) && !is_string($item)) { |
| 93 | throw new ValueError('Invalid command registration'); |
| 94 | } |
| 95 | |
| 96 | $this->add($item); |
| 81 | foreach ($commands as $key => $item) { |
| 82 | if (is_string($key)) { |
| 83 | if (!$item instanceof Closure) { |
| 84 | throw new ValueError("Factory for command class '{$key}' must be a closure"); |
| 85 | } |
| 86 | |
| 87 | $this->entries[] = Entry::fromFactory($this->validClass($key), $item); |
| 88 | |
| 89 | continue; |
| 90 | } |
| 91 | |
| 92 | if (!is_object($item) && !is_string($item)) { |
| 93 | throw new ValueError('Invalid command registration'); |
| 94 | } |
| 95 | |
| 96 | $this->add($item); |
| 97 | } |
| 98 | } |
| 76 | return $this->entries; |
| 77 | } |
| 101 | private function validClass(string $class): string |
| 102 | { |
| 103 | if (!class_exists($class)) { |
| 103 | if (!class_exists($class)) { |
| 103 | if (!class_exists($class)) { |
| 103 | if (!class_exists($class)) { |
| 104 | throw new ValueError("Unknown command class '{$class}'"); |
| 107 | return $class; |
| 108 | } |