Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
13 / 13 |
|
100.00% |
19 / 19 |
|
100.00% |
11 / 11 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
| Command | |
100.00% |
13 / 13 |
|
100.00% |
19 / 19 |
|
100.00% |
11 / 11 |
|
100.00% |
4 / 4 |
10 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
4 / 4 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| full | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| title | |
100.00% |
3 / 3 |
|
100.00% |
6 / 6 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| of | |
100.00% |
5 / 5 |
|
100.00% |
6 / 6 |
|
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Console; |
| 6 | |
| 7 | use Attribute; |
| 8 | use ReflectionClass; |
| 9 | use ValueError; |
| 10 | |
| 11 | /** |
| 12 | * Declares a class as a console command. |
| 13 | * |
| 14 | * The name consists of letters, digits, `.`, `_`, and `-`, starts with a |
| 15 | * letter or digit, and is matched case-insensitively. It may carry a group |
| 16 | * prefix of the same form, separated by a single colon, e.g. `db:migrate`; |
| 17 | * any other name is rejected. |
| 18 | * The prefix namespaces the command on the command line. The help overview |
| 19 | * lists the command under its `group`, by default the capitalized prefix or, |
| 20 | * without one, General; commands with the same group share a section. |
| 21 | * |
| 22 | * The attribute and its properties are public API; the methods serve the |
| 23 | * runner. |
| 24 | * |
| 25 | * @api |
| 26 | */ |
| 27 | #[Attribute(Attribute::TARGET_CLASS)] |
| 28 | final class Command |
| 29 | { |
| 30 | /** |
| 31 | * A single colon only: a second one would collide with the prefixed |
| 32 | * lookup of another command. |
| 33 | */ |
| 34 | private const string NAME = '/^(?:([a-z0-9][a-z0-9._-]*):)?([a-z0-9][a-z0-9._-]*)\z/'; |
| 35 | |
| 36 | public readonly string $name; |
| 37 | public readonly string $prefix; |
| 38 | |
| 39 | public function __construct( |
| 40 | string $name, |
| 41 | public readonly string $description = '', |
| 42 | public readonly string $group = '', |
| 43 | ) { |
| 44 | if (preg_match(self::NAME, strtolower($name), $parts) !== 1) { |
| 45 | throw new ValueError("Invalid command name '{$name}'"); |
| 46 | } |
| 47 | |
| 48 | $this->prefix = $parts[1]; |
| 49 | $this->name = $parts[2]; |
| 50 | } |
| 51 | |
| 52 | /** @internal */ |
| 53 | public function full(): string |
| 54 | { |
| 55 | return $this->prefix === '' ? $this->name : "{$this->prefix}:{$this->name}"; |
| 56 | } |
| 57 | |
| 58 | /** @internal */ |
| 59 | public function title(): string |
| 60 | { |
| 61 | if ($this->group !== '') { |
| 62 | return $this->group; |
| 63 | } |
| 64 | |
| 65 | return $this->prefix === '' ? 'General' : ucfirst($this->prefix); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Reads the attribute off a command instance or class. |
| 70 | * |
| 71 | * @internal |
| 72 | * |
| 73 | * @param class-string|object $command |
| 74 | */ |
| 75 | public static function of(object|string $command): self |
| 76 | { |
| 77 | $class = is_object($command) ? $command::class : $command; |
| 78 | $attributes = new ReflectionClass($class)->getAttributes(self::class); |
| 79 | |
| 80 | if ($attributes === []) { |
| 81 | throw new ValueError("Command class '{$class}' has no #[Command] attribute"); |
| 82 | } |
| 83 | |
| 84 | return $attributes[0]->newInstance(); |
| 85 | } |
| 86 | } |
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.
| 40 | string $name, |
| 41 | public readonly string $description = '', |
| 42 | public readonly string $group = '', |
| 43 | ) { |
| 44 | if (preg_match(self::NAME, strtolower($name), $parts) !== 1) { |
| 45 | throw new ValueError("Invalid command name '{$name}'"); |
| 48 | $this->prefix = $parts[1]; |
| 49 | $this->name = $parts[2]; |
| 50 | } |
| 55 | return $this->prefix === '' ? $this->name : "{$this->prefix}:{$this->name}"; |
| 55 | return $this->prefix === '' ? $this->name : "{$this->prefix}:{$this->name}"; |
| 55 | return $this->prefix === '' ? $this->name : "{$this->prefix}:{$this->name}"; |
| 55 | return $this->prefix === '' ? $this->name : "{$this->prefix}:{$this->name}"; |
| 56 | } |
| 75 | public static function of(object|string $command): self |
| 76 | { |
| 77 | $class = is_object($command) ? $command::class : $command; |
| 77 | $class = is_object($command) ? $command::class : $command; |
| 77 | $class = is_object($command) ? $command::class : $command; |
| 77 | $class = is_object($command) ? $command::class : $command; |
| 78 | $attributes = new ReflectionClass($class)->getAttributes(self::class); |
| 79 | |
| 80 | if ($attributes === []) { |
| 81 | throw new ValueError("Command class '{$class}' has no #[Command] attribute"); |
| 84 | return $attributes[0]->newInstance(); |
| 85 | } |
| 61 | if ($this->group !== '') { |
| 62 | return $this->group; |
| 65 | return $this->prefix === '' ? 'General' : ucfirst($this->prefix); |
| 65 | return $this->prefix === '' ? 'General' : ucfirst($this->prefix); |
| 65 | return $this->prefix === '' ? 'General' : ucfirst($this->prefix); |
| 65 | return $this->prefix === '' ? 'General' : ucfirst($this->prefix); |
| 66 | } |