Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er) - Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link - Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest - Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen) - Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
147
vendor/symfony/console/Attribute/Argument.php
vendored
Executable file
147
vendor/symfony/console/Attribute/Argument.php
vendored
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
use Symfony\Component\Console\Attribute\Reflection\ReflectionMember;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\Suggestion;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\String\UnicodeString;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_PROPERTY)]
|
||||
class Argument
|
||||
{
|
||||
private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];
|
||||
|
||||
private string|bool|int|float|array|null $default = null;
|
||||
private array|\Closure $suggestedValues;
|
||||
private ?int $mode = null;
|
||||
/**
|
||||
* @var string|class-string<\BackedEnum>
|
||||
*/
|
||||
private string $typeName = '';
|
||||
private ?InteractiveAttributeInterface $interactiveAttribute = null;
|
||||
|
||||
/**
|
||||
* Represents a console command <argument> definition.
|
||||
*
|
||||
* If unset, the `name` value will be inferred from the parameter definition.
|
||||
*
|
||||
* @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
|
||||
*/
|
||||
public function __construct(
|
||||
public string $description = '',
|
||||
public string $name = '',
|
||||
array|callable $suggestedValues = [],
|
||||
) {
|
||||
$this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function tryFrom(\ReflectionParameter|\ReflectionProperty $member): ?self
|
||||
{
|
||||
$reflection = new ReflectionMember($member);
|
||||
|
||||
if (!$self = $reflection->getAttribute(self::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $reflection->getType();
|
||||
$name = $reflection->getName();
|
||||
|
||||
if (!$type instanceof \ReflectionNamedType) {
|
||||
throw new LogicException(\sprintf('The %s "$%s" of "%s" must have a named type. Untyped, Union or Intersection types are not supported for command arguments.', $reflection->getMemberName(), $name, $reflection->getSourceName()));
|
||||
}
|
||||
|
||||
$self->typeName = $type->getName();
|
||||
$isBackedEnum = is_subclass_of($self->typeName, \BackedEnum::class);
|
||||
|
||||
if (!\in_array($self->typeName, self::ALLOWED_TYPES, true) && !$isBackedEnum) {
|
||||
throw new LogicException(\sprintf('The type "%s" on %s "$%s" of "%s" is not supported as a command argument. Only "%s" types and backed enums are allowed.', $self->typeName, $reflection->getMemberName(), $name, $reflection->getSourceName(), implode('", "', self::ALLOWED_TYPES)));
|
||||
}
|
||||
|
||||
if (!$self->name) {
|
||||
$self->name = (new UnicodeString($name))->kebab();
|
||||
}
|
||||
|
||||
$self->default = $reflection->hasDefaultValue() ? $reflection->getDefaultValue() : null;
|
||||
|
||||
$isOptional = $reflection->hasDefaultValue() || $reflection->isNullable();
|
||||
$self->mode = $isOptional ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
|
||||
if ('array' === $self->typeName) {
|
||||
$self->mode |= InputArgument::IS_ARRAY;
|
||||
}
|
||||
|
||||
if (\is_array($self->suggestedValues) && !\is_callable($self->suggestedValues) && 2 === \count($self->suggestedValues) && ($instance = $reflection->getSourceThis()) && $instance::class === $self->suggestedValues[0] && \is_callable([$instance, $self->suggestedValues[1]])) {
|
||||
// In case that the callback is declared as a static method `[Foo::class, 'methodName']` - yet it is not callable,
|
||||
// while non-static method `[Foo $instance, 'methodName']` would be callable, we transform the callback on the fly into a non-static version.
|
||||
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
|
||||
}
|
||||
|
||||
if ($isBackedEnum && !$self->suggestedValues) {
|
||||
$self->suggestedValues = array_column($self->typeName::cases(), 'value');
|
||||
}
|
||||
|
||||
$self->interactiveAttribute = Ask::tryFrom($member, $self->name);
|
||||
|
||||
if ($self->interactiveAttribute && $isOptional) {
|
||||
throw new LogicException(\sprintf('The %s "$%s" argument of "%s" cannot be both interactive and optional.', $reflection->getMemberName(), $self->name, $reflection->getSourceName()));
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function toInputArgument(): InputArgument
|
||||
{
|
||||
$suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues;
|
||||
|
||||
return new InputArgument($this->name, $this->mode, $this->description, $this->default, $suggestedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function resolveValue(InputInterface $input): mixed
|
||||
{
|
||||
$value = $input->getArgument($this->name);
|
||||
|
||||
if (is_subclass_of($this->typeName, \BackedEnum::class) && (\is_string($value) || \is_int($value))) {
|
||||
return $this->typeName::tryFrom($value) ?? throw InvalidArgumentException::fromEnumValue($this->name, $value, $this->suggestedValues);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getInteractiveAttribute(): ?InteractiveAttributeInterface
|
||||
{
|
||||
return $this->interactiveAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return InputArgument::REQUIRED === (InputArgument::REQUIRED & $this->mode);
|
||||
}
|
||||
}
|
||||
51
vendor/symfony/console/Attribute/AsCommand.php
vendored
Executable file
51
vendor/symfony/console/Attribute/AsCommand.php
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
/**
|
||||
* Service tag to autoconfigure commands.
|
||||
*
|
||||
* @final since Symfony 7.3
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class AsCommand
|
||||
{
|
||||
/**
|
||||
* @param string $name The name of the command, used when calling it (i.e. "cache:clear")
|
||||
* @param string|null $description The description of the command, displayed with the help page
|
||||
* @param string[] $aliases The list of aliases of the command. The command will be executed when using one of them (i.e. "cache:clean")
|
||||
* @param bool $hidden If true, the command won't be shown when listing all the available commands, but it can still be run as any other command
|
||||
* @param string|null $help The help content of the command, displayed with the help page
|
||||
* @param string[] $usages The list of usage examples, displayed with the help page
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public ?string $description = null,
|
||||
array $aliases = [],
|
||||
bool $hidden = false,
|
||||
public ?string $help = null,
|
||||
public array $usages = [],
|
||||
) {
|
||||
if (!$hidden && !$aliases) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = explode('|', $name);
|
||||
$name = array_merge($name, $aliases);
|
||||
|
||||
if ($hidden && '' !== $name[0]) {
|
||||
array_unshift($name, '');
|
||||
}
|
||||
|
||||
$this->name = implode('|', $name);
|
||||
}
|
||||
}
|
||||
147
vendor/symfony/console/Attribute/Ask.php
vendored
Executable file
147
vendor/symfony/console/Attribute/Ask.php
vendored
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
use Symfony\Component\Console\Attribute\Reflection\ReflectionMember;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_PROPERTY)]
|
||||
class Ask implements InteractiveAttributeInterface
|
||||
{
|
||||
public ?\Closure $normalizer;
|
||||
public ?\Closure $validator;
|
||||
private \Closure $closure;
|
||||
|
||||
/**
|
||||
* @param string $question The question to ask the user
|
||||
* @param string|bool|int|float|null $default The default answer to return if the user enters nothing
|
||||
* @param bool $hidden Whether the user response must be hidden or not
|
||||
* @param bool $multiline Whether the user response should accept newline characters
|
||||
* @param bool $trimmable Whether the user response must be trimmed or not
|
||||
* @param int|null $timeout The maximum time the user has to answer the question in seconds
|
||||
* @param callable|null $validator The validator for the question
|
||||
* @param int|null $maxAttempts The maximum number of attempts allowed to answer the question.
|
||||
* Null means an unlimited number of attempts
|
||||
*/
|
||||
public function __construct(
|
||||
public string $question,
|
||||
public string|bool|int|float|null $default = null,
|
||||
public bool $hidden = false,
|
||||
public bool $multiline = false,
|
||||
public bool $trimmable = true,
|
||||
public ?int $timeout = null,
|
||||
?callable $normalizer = null,
|
||||
?callable $validator = null,
|
||||
public ?int $maxAttempts = null,
|
||||
) {
|
||||
$this->normalizer = $normalizer ? $normalizer(...) : null;
|
||||
$this->validator = $validator ? $validator(...) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function tryFrom(\ReflectionParameter|\ReflectionProperty $member, string $name): ?self
|
||||
{
|
||||
$reflection = new ReflectionMember($member);
|
||||
|
||||
if (!$self = $reflection->getAttribute(self::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $reflection->getType();
|
||||
|
||||
if (!$type instanceof \ReflectionNamedType) {
|
||||
throw new LogicException(\sprintf('The %s "$%s" of "%s" must have a named type. Untyped, Union or Intersection types are not supported for interactive questions.', $reflection->getMemberName(), $name, $reflection->getSourceName()));
|
||||
}
|
||||
|
||||
$self->closure = function (SymfonyStyle $io, InputInterface $input) use ($self, $reflection, $name, $type) {
|
||||
if ($reflection->isProperty() && isset($this->{$reflection->getName()})) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($reflection->isParameter() && !\in_array($input->getArgument($name), [null, []], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('bool' === $type->getName()) {
|
||||
$self->default ??= false;
|
||||
|
||||
if (!\is_bool($self->default)) {
|
||||
throw new LogicException(\sprintf('The "%s::$default" value for the %s "$%s" of "%s" must be a boolean.', self::class, $reflection->getMemberName(), $name, $reflection->getSourceName()));
|
||||
}
|
||||
|
||||
$question = new ConfirmationQuestion($self->question, $self->default);
|
||||
} else {
|
||||
$question = new Question($self->question, $self->default);
|
||||
}
|
||||
$question->setHidden($self->hidden);
|
||||
$question->setMultiline($self->multiline);
|
||||
$question->setTrimmable($self->trimmable);
|
||||
$question->setTimeout($self->timeout);
|
||||
|
||||
if (!$self->validator && $reflection->isProperty() && 'array' !== $type->getName()) {
|
||||
$self->validator = function (mixed $value) use ($reflection): mixed {
|
||||
return $this->{$reflection->getName()} = $value;
|
||||
};
|
||||
}
|
||||
|
||||
$question->setValidator($self->validator);
|
||||
$question->setMaxAttempts($self->maxAttempts);
|
||||
|
||||
if ($self->normalizer) {
|
||||
$question->setNormalizer($self->normalizer);
|
||||
} elseif (is_subclass_of($type->getName(), \BackedEnum::class)) {
|
||||
/** @var class-string<\BackedEnum> $backedType */
|
||||
$backedType = $reflection->getType()->getName();
|
||||
$question->setNormalizer(fn (string|int $value) => $backedType::tryFrom($value) ?? throw InvalidArgumentException::fromEnumValue($reflection->getName(), $value, array_column($backedType::cases(), 'value')));
|
||||
}
|
||||
|
||||
if ('array' === $type->getName()) {
|
||||
$value = [];
|
||||
while ($v = $io->askQuestion($question)) {
|
||||
if ("\x4" === $v || \PHP_EOL === $v || ($question->isTrimmable() && '' === $v = trim($v))) {
|
||||
break;
|
||||
}
|
||||
$value[] = $v;
|
||||
}
|
||||
} else {
|
||||
$value = $io->askQuestion($question);
|
||||
}
|
||||
|
||||
if (null === $value && !$reflection->isNullable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($reflection->isProperty()) {
|
||||
$this->{$reflection->getName()} = $value;
|
||||
} else {
|
||||
$input->setArgument($name, $value);
|
||||
}
|
||||
};
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getFunction(object $instance): \ReflectionFunction
|
||||
{
|
||||
return new \ReflectionFunction($this->closure->bindTo($instance, $instance::class));
|
||||
}
|
||||
}
|
||||
51
vendor/symfony/console/Attribute/Interact.php
vendored
Executable file
51
vendor/symfony/console/Attribute/Interact.php
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
class Interact implements InteractiveAttributeInterface
|
||||
{
|
||||
private \ReflectionMethod $method;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function tryFrom(\ReflectionMethod $method): ?self
|
||||
{
|
||||
/** @var self|null $self */
|
||||
if (!$self = ($method->getAttributes(self::class)[0] ?? null)?->newInstance()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$method->isPublic() || $method->isStatic()) {
|
||||
throw new LogicException(\sprintf('The interactive method "%s::%s()" must be public and non-static.', $method->getDeclaringClass()->getName(), $method->getName()));
|
||||
}
|
||||
|
||||
if ('__invoke' === $method->getName()) {
|
||||
throw new LogicException(\sprintf('The "%s::__invoke()" method cannot be used as an interactive method.', $method->getDeclaringClass()->getName()));
|
||||
}
|
||||
|
||||
$self->method = $method;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getFunction(object $instance): \ReflectionFunction
|
||||
{
|
||||
return new \ReflectionFunction($this->method->getClosure($instance));
|
||||
}
|
||||
}
|
||||
20
vendor/symfony/console/Attribute/InteractiveAttributeInterface.php
vendored
Executable file
20
vendor/symfony/console/Attribute/InteractiveAttributeInterface.php
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface InteractiveAttributeInterface
|
||||
{
|
||||
public function getFunction(object $instance): \ReflectionFunction;
|
||||
}
|
||||
188
vendor/symfony/console/Attribute/MapInput.php
vendored
Executable file
188
vendor/symfony/console/Attribute/MapInput.php
vendored
Executable file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
use Symfony\Component\Console\Attribute\Reflection\ReflectionMember;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Interaction\Interaction;
|
||||
|
||||
/**
|
||||
* Maps a command input into an object (DTO).
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_PROPERTY)]
|
||||
final class MapInput
|
||||
{
|
||||
/**
|
||||
* @var array<string, Argument|Option|self>
|
||||
*/
|
||||
private array $definition = [];
|
||||
|
||||
private \ReflectionClass $class;
|
||||
|
||||
/**
|
||||
* @var list<Interact>
|
||||
*/
|
||||
private array $interactiveAttributes = [];
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function tryFrom(\ReflectionParameter|\ReflectionProperty $member): ?self
|
||||
{
|
||||
$reflection = new ReflectionMember($member);
|
||||
|
||||
if (!$self = $reflection->getAttribute(self::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $reflection->getType();
|
||||
|
||||
if (!$type instanceof \ReflectionNamedType) {
|
||||
throw new LogicException(\sprintf('The input %s "%s" must have a named type.', $reflection->getMemberName(), $member->name));
|
||||
}
|
||||
|
||||
if (!class_exists($class = $type->getName())) {
|
||||
throw new LogicException(\sprintf('The input class "%s" does not exist.', $type->getName()));
|
||||
}
|
||||
|
||||
$self->class = new \ReflectionClass($class);
|
||||
|
||||
foreach ($self->class->getProperties() as $property) {
|
||||
if ($argument = Argument::tryFrom($property)) {
|
||||
$self->definition[$property->name] = $argument;
|
||||
} elseif ($option = Option::tryFrom($property)) {
|
||||
$self->definition[$property->name] = $option;
|
||||
} elseif ($input = self::tryFrom($property)) {
|
||||
$self->definition[$property->name] = $input;
|
||||
}
|
||||
|
||||
if (isset($self->definition[$property->name]) && (!$property->isPublic() || $property->isStatic())) {
|
||||
throw new LogicException(\sprintf('The input property "%s::$%s" must be public and non-static.', $self->class->name, $property->name));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$self->definition) {
|
||||
throw new LogicException(\sprintf('The input class "%s" must have at least one argument or option.', $self->class->name));
|
||||
}
|
||||
|
||||
foreach ($self->class->getMethods() as $method) {
|
||||
if ($attribute = Interact::tryFrom($method)) {
|
||||
$self->interactiveAttributes[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function resolveValue(InputInterface $input): object
|
||||
{
|
||||
$instance = $this->class->newInstanceWithoutConstructor();
|
||||
|
||||
foreach ($this->definition as $name => $spec) {
|
||||
// ignore required arguments that are not set yet (may happen in interactive mode)
|
||||
if ($spec instanceof Argument && $spec->isRequired() && \in_array($input->getArgument($spec->name), [null, []], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$instance->$name = $spec->resolveValue($input);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setValue(InputInterface $input, object $object): void
|
||||
{
|
||||
foreach ($this->definition as $name => $spec) {
|
||||
$property = $this->class->getProperty($name);
|
||||
|
||||
if (!$property->isInitialized($object) || \in_array($value = $property->getValue($object), [null, []], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match (true) {
|
||||
$spec instanceof Argument => $input->setArgument($spec->name, $value),
|
||||
$spec instanceof Option => $input->setOption($spec->name, $value),
|
||||
$spec instanceof self => $spec->setValue($input, $value),
|
||||
default => throw new LogicException('Unexpected specification type.'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<Argument>
|
||||
*/
|
||||
public function getArguments(): iterable
|
||||
{
|
||||
foreach ($this->definition as $spec) {
|
||||
if ($spec instanceof Argument) {
|
||||
yield $spec;
|
||||
} elseif ($spec instanceof self) {
|
||||
yield from $spec->getArguments();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<Option>
|
||||
*/
|
||||
public function getOptions(): iterable
|
||||
{
|
||||
foreach ($this->definition as $spec) {
|
||||
if ($spec instanceof Option) {
|
||||
yield $spec;
|
||||
} elseif ($spec instanceof self) {
|
||||
yield from $spec->getOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return iterable<Interaction>
|
||||
*/
|
||||
public function getPropertyInteractions(): iterable
|
||||
{
|
||||
foreach ($this->definition as $spec) {
|
||||
if ($spec instanceof self) {
|
||||
yield from $spec->getPropertyInteractions();
|
||||
} elseif ($spec instanceof Argument && $attribute = $spec->getInteractiveAttribute()) {
|
||||
yield new Interaction($this, $attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return iterable<Interaction>
|
||||
*/
|
||||
public function getMethodInteractions(): iterable
|
||||
{
|
||||
foreach ($this->definition as $spec) {
|
||||
if ($spec instanceof self) {
|
||||
yield from $spec->getMethodInteractions();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->interactiveAttributes as $attribute) {
|
||||
yield new Interaction($this, $attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
194
vendor/symfony/console/Attribute/Option.php
vendored
Executable file
194
vendor/symfony/console/Attribute/Option.php
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute;
|
||||
|
||||
use Symfony\Component\Console\Attribute\Reflection\ReflectionMember;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\Suggestion;
|
||||
use Symfony\Component\Console\Exception\InvalidOptionException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\String\UnicodeString;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PARAMETER | \Attribute::TARGET_PROPERTY)]
|
||||
class Option
|
||||
{
|
||||
private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];
|
||||
private const ALLOWED_UNION_TYPES = ['bool|string', 'bool|int', 'bool|float'];
|
||||
|
||||
private string|bool|int|float|array|null $default = null;
|
||||
private array|\Closure $suggestedValues;
|
||||
private ?int $mode = null;
|
||||
/**
|
||||
* @var string|class-string<\BackedEnum>
|
||||
*/
|
||||
private string $typeName = '';
|
||||
private bool $allowNull = false;
|
||||
private string $memberName = '';
|
||||
private string $sourceName = '';
|
||||
|
||||
/**
|
||||
* Represents a console command --option definition.
|
||||
*
|
||||
* If unset, the `name` value will be inferred from the parameter definition.
|
||||
*
|
||||
* @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
|
||||
* @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
|
||||
*/
|
||||
public function __construct(
|
||||
public string $description = '',
|
||||
public string $name = '',
|
||||
public array|string|null $shortcut = null,
|
||||
array|callable $suggestedValues = [],
|
||||
) {
|
||||
$this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function tryFrom(\ReflectionParameter|\ReflectionProperty $member): ?self
|
||||
{
|
||||
$reflection = new ReflectionMember($member);
|
||||
|
||||
if (!$self = $reflection->getAttribute(self::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$self->memberName = $reflection->getMemberName();
|
||||
$self->sourceName = $reflection->getSourceName();
|
||||
|
||||
$name = $reflection->getName();
|
||||
$type = $reflection->getType();
|
||||
|
||||
if (!$reflection->hasDefaultValue()) {
|
||||
throw new LogicException(\sprintf('The option %s "$%s" of "%s" must declare a default value.', $self->memberName, $name, $self->sourceName));
|
||||
}
|
||||
|
||||
if (!$self->name) {
|
||||
$self->name = (new UnicodeString($name))->kebab();
|
||||
}
|
||||
|
||||
$self->default = $reflection->getDefaultValue();
|
||||
$self->allowNull = $reflection->isNullable();
|
||||
|
||||
if ($type instanceof \ReflectionUnionType) {
|
||||
return $self->handleUnion($type);
|
||||
}
|
||||
|
||||
if (!$type instanceof \ReflectionNamedType) {
|
||||
throw new LogicException(\sprintf('The %s "$%s" of "%s" must have a named type. Untyped or Intersection types are not supported for command options.', $self->memberName, $name, $self->sourceName));
|
||||
}
|
||||
|
||||
$self->typeName = $type->getName();
|
||||
$isBackedEnum = is_subclass_of($self->typeName, \BackedEnum::class);
|
||||
|
||||
if (!\in_array($self->typeName, self::ALLOWED_TYPES, true) && !$isBackedEnum) {
|
||||
throw new LogicException(\sprintf('The type "%s" on %s "$%s" of "%s" is not supported as a command option. Only "%s" types and BackedEnum are allowed.', $self->typeName, $self->memberName, $name, $self->sourceName, implode('", "', self::ALLOWED_TYPES)));
|
||||
}
|
||||
|
||||
if ('bool' === $self->typeName && $self->allowNull && \in_array($self->default, [true, false], true)) {
|
||||
throw new LogicException(\sprintf('The option %s "$%s" of "%s" must not be nullable when it has a default boolean value.', $self->memberName, $name, $self->sourceName));
|
||||
}
|
||||
|
||||
if ($self->allowNull && null !== $self->default) {
|
||||
throw new LogicException(\sprintf('The option %s "$%s" of "%s" must either be not-nullable or have a default of null.', $self->memberName, $name, $self->sourceName));
|
||||
}
|
||||
|
||||
if ('bool' === $self->typeName) {
|
||||
$self->mode = InputOption::VALUE_NONE;
|
||||
if (false !== $self->default) {
|
||||
$self->mode |= InputOption::VALUE_NEGATABLE;
|
||||
}
|
||||
} elseif ('array' === $self->typeName) {
|
||||
$self->mode = InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY;
|
||||
} else {
|
||||
$self->mode = InputOption::VALUE_REQUIRED;
|
||||
}
|
||||
|
||||
if (\is_array($self->suggestedValues) && !\is_callable($self->suggestedValues) && 2 === \count($self->suggestedValues) && ($instance = $reflection->getSourceThis()) && $instance::class === $self->suggestedValues[0] && \is_callable([$instance, $self->suggestedValues[1]])) {
|
||||
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
|
||||
}
|
||||
|
||||
if ($isBackedEnum && !$self->suggestedValues) {
|
||||
$self->suggestedValues = array_column($self->typeName::cases(), 'value');
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function toInputOption(): InputOption
|
||||
{
|
||||
$default = InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $this->mode) ? null : $this->default;
|
||||
$suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues;
|
||||
|
||||
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $default, $suggestedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function resolveValue(InputInterface $input): mixed
|
||||
{
|
||||
$value = $input->getOption($this->name);
|
||||
|
||||
if (null === $value && \in_array($this->typeName, self::ALLOWED_UNION_TYPES, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_subclass_of($this->typeName, \BackedEnum::class) && (\is_string($value) || \is_int($value))) {
|
||||
return $this->typeName::tryFrom($value) ?? throw InvalidOptionException::fromEnumValue($this->name, $value, $this->suggestedValues);
|
||||
}
|
||||
|
||||
if ('array' === $this->typeName && $this->allowNull && [] === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('bool' !== $this->typeName) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($this->allowNull && null === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value ?? $this->default;
|
||||
}
|
||||
|
||||
private function handleUnion(\ReflectionUnionType $type): self
|
||||
{
|
||||
$types = array_map(
|
||||
static fn (\ReflectionType $t) => $t instanceof \ReflectionNamedType ? $t->getName() : null,
|
||||
$type->getTypes(),
|
||||
);
|
||||
|
||||
sort($types);
|
||||
|
||||
$this->typeName = implode('|', array_filter($types));
|
||||
|
||||
if (!\in_array($this->typeName, self::ALLOWED_UNION_TYPES, true)) {
|
||||
throw new LogicException(\sprintf('The union type for %s "$%s" of "%s" is not supported as a command option. Only "%s" types are allowed.', $this->memberName, $this->name, $this->sourceName, implode('", "', self::ALLOWED_UNION_TYPES)));
|
||||
}
|
||||
|
||||
if (false !== $this->default) {
|
||||
throw new LogicException(\sprintf('The option %s "$%s" of "%s" must have a default value of false.', $this->memberName, $this->name, $this->sourceName));
|
||||
}
|
||||
|
||||
$this->mode = InputOption::VALUE_OPTIONAL;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
109
vendor/symfony/console/Attribute/Reflection/ReflectionMember.php
vendored
Executable file
109
vendor/symfony/console/Attribute/Reflection/ReflectionMember.php
vendored
Executable file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Attribute\Reflection;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReflectionMember
|
||||
{
|
||||
public function __construct(
|
||||
private readonly \ReflectionParameter|\ReflectionProperty $member,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
*
|
||||
* @param class-string<T> $class
|
||||
*
|
||||
* @return T|null
|
||||
*/
|
||||
public function getAttribute(string $class): ?object
|
||||
{
|
||||
return ($this->member->getAttributes($class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)?->newInstance();
|
||||
}
|
||||
|
||||
public function getSourceName(): string
|
||||
{
|
||||
if ($this->member instanceof \ReflectionProperty) {
|
||||
return $this->member->getDeclaringClass()->name;
|
||||
}
|
||||
|
||||
$function = $this->member->getDeclaringFunction();
|
||||
|
||||
if ($function instanceof \ReflectionMethod) {
|
||||
return $function->class.'::'.$function->name.'()';
|
||||
}
|
||||
|
||||
return $function->name.'()';
|
||||
}
|
||||
|
||||
public function getSourceThis(): ?object
|
||||
{
|
||||
if ($this->member instanceof \ReflectionParameter) {
|
||||
return $this->member->getDeclaringFunction()->getClosureThis();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getType(): ?\ReflectionType
|
||||
{
|
||||
return $this->member->getType();
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->member->getName();
|
||||
}
|
||||
|
||||
public function hasDefaultValue(): bool
|
||||
{
|
||||
if ($this->member instanceof \ReflectionParameter) {
|
||||
return $this->member->isDefaultValueAvailable();
|
||||
}
|
||||
|
||||
return $this->member->hasDefaultValue();
|
||||
}
|
||||
|
||||
public function getDefaultValue(): mixed
|
||||
{
|
||||
$defaultValue = $this->member->getDefaultValue();
|
||||
|
||||
if ($defaultValue instanceof \BackedEnum) {
|
||||
return $defaultValue->value;
|
||||
}
|
||||
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
public function isNullable(): bool
|
||||
{
|
||||
return (bool) $this->member->getType()?->allowsNull();
|
||||
}
|
||||
|
||||
public function getMemberName(): string
|
||||
{
|
||||
return $this->member instanceof \ReflectionParameter ? 'parameter' : 'property';
|
||||
}
|
||||
|
||||
public function isParameter(): bool
|
||||
{
|
||||
return $this->member instanceof \ReflectionParameter;
|
||||
}
|
||||
|
||||
public function isProperty(): bool
|
||||
{
|
||||
return $this->member instanceof \ReflectionProperty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user