Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
29 / 29
96.88% covered (success)
96.88%
31 / 32
66.67% covered (warning)
66.67%
14 / 21
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Commands
100.00% covered (success)
100.00%
29 / 29
96.88% covered (success)
96.88%
31 / 32
66.67% covered (warning)
66.67%
14 / 21
100.00% covered (success)
100.00%
5 / 5
25.48
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
12 / 12
71.43% covered (warning)
71.43%
5 / 7
100.00% covered (success)
100.00%
1 / 1
6.84
 entries
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addArray
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
12 / 12
62.50% covered (warning)
62.50%
5 / 8
100.00% covered (success)
100.00%
1 / 1
7.90
 validClass
100.00% covered (success)
100.00%
3 / 3
83.33% covered (warning)
83.33%
5 / 6
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
2.50
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Console;
6
7use Closure;
8use 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 */
32final 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}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Commands->__construct
37    public function __construct(array|object|string $commands = [])
38    {
39        $this->add($commands);
40    }
Commands->add
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',
42    public function add(array|object|string $commands): void
43    {
44        if ($commands instanceof Closure) {
 
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) {
 
51            foreach ($commands->entries() as $entry) {
52                $this->entries[] = $entry;
53            }
54
55            return;
42    public function add(array|object|string $commands): void
43    {
44        if ($commands instanceof Closure) {
 
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;
53            }
54
55            return;
42    public function add(array|object|string $commands): void
43    {
44        if ($commands instanceof Closure) {
 
50        if ($commands instanceof Commands) {
 
51            foreach ($commands->entries() as $entry) {
 
51            foreach ($commands->entries() as $entry) {
52                $this->entries[] = $entry;
53            }
54
55            return;
42    public function add(array|object|string $commands): void
43    {
44        if ($commands instanceof Closure) {
 
50        if ($commands instanceof Commands) {
 
58        if (is_array($commands)) {
 
59            $this->addArray($commands);
60
61            return;
42    public function add(array|object|string $commands): void
43    {
44        if ($commands instanceof Closure) {
 
50        if ($commands instanceof Commands) {
 
58        if (is_array($commands)) {
 
64        if (is_string($commands)) {
 
65            $this->entries[] = Entry::fromClass($this->validClass($commands));
66
67            return;
42    public function add(array|object|string $commands): void
43    {
44        if ($commands instanceof Closure) {
 
50        if ($commands instanceof Commands) {
 
58        if (is_array($commands)) {
 
64        if (is_string($commands)) {
 
70        $this->entries[] = Entry::fromInstance($commands);
71    }
Commands->addArray
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");
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) {
 
87                $this->entries[] = Entry::fromFactory($this->validClass($key), $item);
88
89                continue;
 
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");
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    }
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)) {
 
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');
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)) {
 
92            if (!is_object($item) && !is_string($item)) {
 
92            if (!is_object($item) && !is_string($item)) {
 
92            if (!is_object($item) && !is_string($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);
 
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");
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    }
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)) {
 
92            if (!is_object($item) && !is_string($item)) {
 
92            if (!is_object($item) && !is_string($item)) {
 
93                throw new ValueError('Invalid command registration');
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)) {
 
92            if (!is_object($item) && !is_string($item)) {
 
92            if (!is_object($item) && !is_string($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);
 
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");
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    }
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");
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    }
79    private function addArray(array $commands): void
80    {
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");
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    }
Commands->entries
76        return $this->entries;
77    }
Commands->validClass
101    private function validClass(string $class): string
102    {
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
104            throw new ValueError("Unknown command class '{$class}'");
101    private function validClass(string $class): string
102    {
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
107        return $class;
108    }
101    private function validClass(string $class): string
102    {
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
104            throw new ValueError("Unknown command class '{$class}'");
101    private function validClass(string $class): string
102    {
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
103        if (!class_exists($class)) {
 
107        return $class;
108    }