/
githubmirror
/
symfony
Обзор
Документация
Войти
/
githubmirror
/
symfony
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
8.2
src/Symfony/Component/Console/Attribute/MapInput.php
250 строк
7 KB
Robin Chalas
[Console] Add validation constraints support to `#[MapInput]`
23 мар 2026, 22:58
23 мар 2026, 22:58
9ceb0fb
Код
Авторство
О чём код?
<?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 = []; /** * @param string[]|null $validationGroups */ public function __construct( public readonly ?array $validationGroups = null, ) { } /** * @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 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 \ReflectionClass<object> */ public function getClass(): \ReflectionClass { return $this->class; } /** * @internal * * @return array<string, Argument|Option|self> */ public function getDefinition(): array { return $this->definition; } /** * Creates a populated instance of the DTO from command input. * * @internal */ public function createInstance(InputInterface $input): object { $instance = $this->class->newInstanceWithoutConstructor(); foreach ($this->definition as $name => $spec) { if ($spec instanceof Argument) { $value = $input->getArgument($spec->name); if ($spec->isRequired() && \in_array($value, [null, []], true)) { continue; } $instance->$name = $this->resolveValue($spec->typeName, $value, $spec->default); } elseif ($spec instanceof Option) { $value = $input->getOption($spec->name); $instance->$name = $this->resolveValue($spec->typeName, $value, $spec->default); } elseif ($spec instanceof self) { $instance->$name = $spec->createInstance($input); } } return $instance; } private function resolveValue(string $typeName, mixed $value, mixed $default): mixed { if (null === $value) { return $default; } if ('' === $value) { return $value; } if (is_subclass_of($typeName, \BackedEnum::class)) { return $value instanceof $typeName ? $value : $typeName::tryFrom($value); } if (is_a($typeName, \DateTimeInterface::class, true)) { if ($value instanceof \DateTimeInterface) { return $value; } $class = \DateTimeInterface::class === $typeName ? \DateTimeImmutable::class : $typeName; return new $class($value); } return $value; } /** * @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); } } }