Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/Illuminate/Console/Attributes/Argument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Illuminate\Console\Attributes;

use Illuminate\Contracts\Console\ConsoleInput;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Argument implements ConsoleInput
{
public function __construct(
protected string $description = '',
protected ?string $as = null,
) {
}

public function getDescription(): string
{
return $this->description;
}

public function getAlias(): ?string
{
return $this->as;
}
}
16 changes: 16 additions & 0 deletions src/Illuminate/Console/Attributes/ArtisanCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Illuminate\Console\Attributes;

#[\Attribute(\Attribute::TARGET_CLASS)]
class ArtisanCommand
{
public function __construct(
public string $name,
public string $description = '',
public string $help = '',
public array $aliases = [],
public bool $hidden = false,
) {
}
}
37 changes: 37 additions & 0 deletions src/Illuminate/Console/Attributes/Option.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Illuminate\Console\Attributes;

use Illuminate\Contracts\Console\ConsoleInput;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Option implements ConsoleInput
{
public function __construct(
protected string $description = '',
protected ?string $as = null,
protected ?string $shortcut = null,
protected bool $negatable = false,
) {
}

public function getDescription(): string
{
return $this->description;
}

public function getAlias(): ?string
{
return $this->as;
}

public function getShortcut(): ?string
{
return $this->shortcut;
}

public function isNegatable(): bool
{
return $this->negatable;
}
}
75 changes: 60 additions & 15 deletions src/Illuminate/Console/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Illuminate\Console;

use Illuminate\Console\Reflections\CommandReflection;
use Illuminate\Support\Traits\Macroable;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -12,6 +13,7 @@ class Command extends SymfonyCommand
use Concerns\CallsCommands,
Concerns\HasParameters,
Concerns\InteractsWithIO,
Concerns\HasAttributeSyntax,
Macroable;

/**
Expand Down Expand Up @@ -56,34 +58,50 @@ class Command extends SymfonyCommand
*/
protected $hidden = false;

/**
* The Reflection of the Command.
*
* @var CommandReflection
*/
protected CommandReflection $reflection;

/**
* Create a new console command instance.
*
* @return void
*/
public function __construct()
{
// We will go ahead and set the name, description, and parameters on console
// commands just to make things a little easier on the developer. This is
// so they don't have to all be manually specified in the constructors.
$this->reflection = new CommandReflection($this);
$this->intiCommandData();
$this->configureCommand();
}

/**
* Configure the console command using a fluent or attribute definition.
*
* We will go ahead and set the name, description, and parameters on console
* commands just to make things a little easier on the developer. This is
* so they don't have to all be manually specified in the constructors.
*
* @return void
*/
protected function configureCommand(): void
{
if (isset($this->signature)) {
$this->configureUsingFluentDefinition();
} else {
parent::__construct($this->name);
}

// Once we have constructed the command, we'll set the description and other
// related properties of the command. If a signature wasn't used to build
// the command we'll set the arguments and the options on this command.
$this->setDescription((string) $this->description);

$this->setHelp((string) $this->help);
return;
}

$this->setHidden($this->isHidden());
if ($this->reflection->usesInputAttributes()) {
$this->configureUsingAttributeDefinition();

if (! isset($this->signature)) {
$this->specifyParameters();
return;
}

parent::__construct($this->name);
$this->specifyParameters();
}

/**
Expand Down Expand Up @@ -131,6 +149,9 @@ public function run(InputInterface $input, OutputInterface $output): int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->hydrateArguments();
$this->hydrateOptions();

$method = method_exists($this, 'handle') ? 'handle' : '__invoke';

return (int) $this->laravel->call([$this, $method]);
Expand Down Expand Up @@ -161,6 +182,30 @@ protected function resolveCommand($command)
return $command;
}

/**
* Once we have constructed the command, we'll set the description and other
* related properties of the command ether by the command attribute or by command properties
* them self.
*
* @return void
*/
protected function intiCommandData(): void
{
if ($this->reflection->usesCommandAttribute()) {
$this->initCommandDataFromAttribute();

return;
}

if (! isset($this->signature)) {
parent::__construct($this->name);
}

$this->setDescription((string) $this->description);
$this->setHelp((string) $this->help);
$this->setHidden($this->isHidden());
}

/**
* {@inheritdoc}
*
Expand Down
151 changes: 151 additions & 0 deletions src/Illuminate/Console/Concerns/HasAttributeSyntax.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace Illuminate\Console\Concerns;

use Illuminate\Console\Reflections\ArgumentReflection;
use Illuminate\Console\Reflections\OptionReflection;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

trait HasAttributeSyntax
{
/**
* Configure the console command using an Attribute definition.
*
* @return void
*/
protected function configureUsingAttributeDefinition(): void
{
$this->configureArgumentsUsingAttributeDefinition();
$this->configureOptionsUsingAttributeDefinition();
}

protected function initCommandDataFromAttribute(): void
{
parent::__construct($this->name = $this->reflection->getName());
$this->setDescription($this->reflection->getDescription());
$this->setHelp($this->reflection->getHelp());
$this->setHidden($this->reflection->isHidden());
$this->setAliases($this->reflection->getAliases());
}

protected function configureArgumentsUsingAttributeDefinition(): void
{
$this->reflection
->getArguments()
->each(function (ArgumentReflection $argumentReflection) {
$this->getDefinition()
->addArgument(
$this->propertyToArgument($argumentReflection)
);
});
}

protected function configureOptionsUsingAttributeDefinition(): void
{
$this->reflection
->getOptions()
->each(function (OptionReflection $optionReflection) {
$this->getDefinition()
->addOption($this->propertyToOption($optionReflection));
});
}

protected function hydrateArguments(): void
{
$this->reflection
->getArguments()
->each(function (ArgumentReflection $argumentReflection) {
$this->{$argumentReflection->getName()} = $argumentReflection->castTo($this->argument($argumentReflection->getAlias() ?? $argumentReflection->getName()));
});
}

protected function hydrateOptions(): void
{
$this->reflection
->getOptions()
->each(function (OptionReflection $optionReflection) {
$consoleName = $optionReflection->getAlias() ?? $optionReflection->getName();

if (! $optionReflection->hasRequiredValue()) {
$this->{$optionReflection->getName()} = $optionReflection->castTo($this->option($consoleName));

return;
}

if ($this->option($consoleName) === null) {
return;
}

$this->{$optionReflection->getName()} = $optionReflection->castTo($this->option($consoleName));
});
}

protected function propertyToArgument(ArgumentReflection $argument): InputArgument
{
return match (true) {
$argument->isArray() && ! $argument->isOptional() => $this->makeInputArgument($argument,
InputArgument::IS_ARRAY | InputArgument::REQUIRED),

$argument->isArray() => $this->makeInputArgument($argument, InputArgument::IS_ARRAY,
$argument->getDefaultValue()),

$argument->isOptional() || $argument->getDefaultValue() => $this->makeInputArgument($argument,
InputArgument::OPTIONAL, $argument->getDefaultValue()),

default => $this->makeInputArgument($argument, InputArgument::REQUIRED),
};
}

protected function propertyToOption(OptionReflection $option): InputOption
{
return match (true) {
$option->hasValue() && $option->isArray() => $this->makeInputOption(
$option,
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
$option->getDefaultValue()
),

$option->hasValue() && ! $option->isOptional() => $this->makeInputOption($option,
InputOption::VALUE_REQUIRED),

$option->hasValue() => $this->makeInputOption($option, InputOption::VALUE_OPTIONAL,
$option->getDefaultValue()),

$option->isNegatable() => $this->makeInputOption(
$option,
InputOption::VALUE_NEGATABLE,
$option->getDefaultValue() !== null ? $option->getDefaultValue() : false
),

default => $this->makeInputOption($option, InputOption::VALUE_NONE),
};
}

protected function makeInputArgument(
ArgumentReflection $argument,
int $mode,
string|bool|int|float|array|null $default = null
): InputArgument {
return new InputArgument(
$argument->getAlias() ?? $argument->getName(),
$mode,
$argument->getDescription(),
$default
);
}

protected function makeInputOption(
OptionReflection $option,
int $mode,
string|bool|int|float|array|null $default = null
): InputOption {
return new InputOption(
$option->getAlias() ?? $option->getName(),
$option->getShortcut(),
$mode,
$option->getDescription(),
$default
);
}
}
13 changes: 13 additions & 0 deletions src/Illuminate/Console/Reflections/ArgumentReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Illuminate\Console\Reflections;

use Illuminate\Console\Attributes\Argument;

class ArgumentReflection extends InputReflection
{
public static function isArgument(\ReflectionProperty $property): bool
{
return ! empty($property->getAttributes(Argument::class));
}
}
Loading