first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

View File

@ -0,0 +1,146 @@
<?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\HttpKernel\Controller;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;
use Symfony\Component\HttpKernel\Exception\ResolverNotFoundException;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* Responsible for resolving the arguments passed to an action.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class ArgumentResolver implements ArgumentResolverInterface
{
private ArgumentMetadataFactoryInterface $argumentMetadataFactory;
private iterable $argumentValueResolvers;
private ?ContainerInterface $namedResolvers;
/**
* @param iterable<mixed, ArgumentValueResolverInterface|ValueResolverInterface> $argumentValueResolvers
*/
public function __construct(?ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [], ?ContainerInterface $namedResolvers = null)
{
$this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory();
$this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers();
$this->namedResolvers = $namedResolvers;
}
public function getArguments(Request $request, callable $controller, ?\ReflectionFunctionAbstract $reflector = null): array
{
$arguments = [];
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, $reflector) as $metadata) {
$argumentValueResolvers = $this->argumentValueResolvers;
$disabledResolvers = [];
if ($this->namedResolvers && $attributes = $metadata->getAttributesOfType(ValueResolver::class, $metadata::IS_INSTANCEOF)) {
$resolverName = null;
foreach ($attributes as $attribute) {
if ($attribute->disabled) {
$disabledResolvers[$attribute->resolver] = true;
} elseif ($resolverName) {
throw new \LogicException(sprintf('You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.', $metadata->getName(), $this->getPrettyName($controller)));
} else {
$resolverName = $attribute->resolver;
}
}
if ($resolverName) {
if (!$this->namedResolvers->has($resolverName)) {
throw new ResolverNotFoundException($resolverName, $this->namedResolvers instanceof ServiceProviderInterface ? array_keys($this->namedResolvers->getProvidedServices()) : []);
}
$argumentValueResolvers = [
$this->namedResolvers->get($resolverName),
new RequestAttributeValueResolver(),
new DefaultValueResolver(),
];
}
}
foreach ($argumentValueResolvers as $name => $resolver) {
if ((!$resolver instanceof ValueResolverInterface || $resolver instanceof TraceableValueResolver) && !$resolver->supports($request, $metadata)) {
continue;
}
if (isset($disabledResolvers[\is_int($name) ? $resolver::class : $name])) {
continue;
}
$count = 0;
foreach ($resolver->resolve($request, $metadata) as $argument) {
++$count;
$arguments[] = $argument;
}
if (1 < $count && !$metadata->isVariadic()) {
throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at most one value for non-variadic arguments.', get_debug_type($resolver)));
}
if ($count) {
// continue to the next controller argument
continue 2;
}
if (!$resolver instanceof ValueResolverInterface) {
throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver)));
}
}
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or there is a non-optional argument after this one.', $this->getPrettyName($controller), $metadata->getName()));
}
return $arguments;
}
/**
* @return iterable<int, ArgumentValueResolverInterface>
*/
public static function getDefaultArgumentValueResolvers(): iterable
{
return [
new RequestAttributeValueResolver(),
new RequestValueResolver(),
new SessionValueResolver(),
new DefaultValueResolver(),
new VariadicValueResolver(),
];
}
private function getPrettyName($controller): string
{
if (\is_array($controller)) {
if (\is_object($controller[0])) {
$controller[0] = get_debug_type($controller[0]);
}
return $controller[0].'::'.$controller[1];
}
if (\is_object($controller)) {
return get_debug_type($controller);
}
return $controller;
}
}

View File

@ -0,0 +1,93 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Attempt to resolve backed enum cases from request attributes, for a route path parameter,
* leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final since Symfony 6.2
*/
class BackedEnumValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
if (!is_subclass_of($argument->getType(), \BackedEnum::class)) {
return false;
}
if ($argument->isVariadic()) {
// only target route path parameters, which cannot be variadic.
return false;
}
// do not support if no value can be resolved at all
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
return $request->attributes->has($argument->getName());
}
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
if (!is_subclass_of($argument->getType(), \BackedEnum::class)) {
return [];
}
if ($argument->isVariadic()) {
// only target route path parameters, which cannot be variadic.
return [];
}
// do not support if no value can be resolved at all
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
if (!$request->attributes->has($argument->getName())) {
return [];
}
$value = $request->attributes->get($argument->getName());
if (null === $value) {
return [null];
}
if ($value instanceof \BackedEnum) {
return [$value];
}
if (!\is_int($value) && !\is_string($value)) {
throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $argument->getType(), $argument->getName(), get_debug_type($value)));
}
/** @var class-string<\BackedEnum> $enumType */
$enumType = $argument->getType();
try {
return [$enumType::from($value)];
} catch (\ValueError|\TypeError $e) {
throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: ', $argument->getType(), $argument->getName()).$e->getMessage(), $e);
}
}
}

View File

@ -0,0 +1,98 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Psr\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Attribute\MapDateTime;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Convert DateTime instances from request attribute variable.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Tim Goudriaan <tim@codedmonkey.com>
*/
final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
public function __construct(
private readonly ?ClockInterface $clock = null,
) {
}
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
return is_a($argument->getType(), \DateTimeInterface::class, true) && $request->attributes->has($argument->getName());
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if (!is_a($argument->getType(), \DateTimeInterface::class, true) || !$request->attributes->has($argument->getName())) {
return [];
}
$value = $request->attributes->get($argument->getName());
$class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType();
if (!$value) {
if ($argument->isNullable()) {
return [null];
}
if (!$this->clock) {
return [new $class()];
}
$value = $this->clock->now();
}
if ($value instanceof \DateTimeInterface) {
return [$value instanceof $class ? $value : $class::createFromInterface($value)];
}
$format = null;
if ($attributes = $argument->getAttributes(MapDateTime::class, ArgumentMetadata::IS_INSTANCEOF)) {
$attribute = $attributes[0];
$format = $attribute->format;
}
if (null !== $format) {
$date = $class::createFromFormat($format, $value, $this->clock?->now()->getTimeZone());
if (($class::getLastErrors() ?: ['warning_count' => 0])['warning_count']) {
$date = false;
}
} else {
if (false !== filter_var($value, \FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
$value = '@'.$value;
}
try {
$date = new $class($value, $this->clock?->now()->getTimeZone());
} catch (\Exception) {
$date = false;
}
}
if (!$date) {
throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $argument->getName()));
}
return [$date];
}
}

View File

@ -0,0 +1,48 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields the default value defined in the action signature when no value has been given.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class DefaultValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic());
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if ($argument->hasDefaultValue()) {
return [$argument->getDefaultValue()];
}
if (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()) {
return [null];
}
return [];
}
}

View File

@ -0,0 +1,90 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Provides an intuitive error message when controller fails because it is not registered as a service.
*
* @author Simeon Kolev <simeon.kolev9@gmail.com>
*/
final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
private ContainerInterface $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
$controller = $request->attributes->get('_controller');
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
$controller = $controller[0].'::'.$controller[1];
} elseif (!\is_string($controller) || '' === $controller) {
return false;
}
if ('\\' === $controller[0]) {
$controller = ltrim($controller, '\\');
}
if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) {
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
}
return false === $this->container->has($controller);
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
$controller = $request->attributes->get('_controller');
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
$controller = $controller[0].'::'.$controller[1];
} elseif (!\is_string($controller) || '' === $controller) {
return [];
}
if ('\\' === $controller[0]) {
$controller = ltrim($controller, '\\');
}
if (!$this->container->has($controller)) {
$controller = (false !== $i = strrpos($controller, ':'))
? substr($controller, 0, $i).strtolower(substr($controller, $i))
: $controller.'::__invoke';
}
if ($this->container->has($controller)) {
return [];
}
$what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller);
$message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what);
throw new RuntimeException($message);
}
}

View File

@ -0,0 +1,125 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters.
*
* @author Ruud Kamphuis <ruud@ticketswap.com>
* @author Nicolas Grekas <p@tchwork.com>
* @author Mateusz Anders <anders_mateusz@outlook.com>
*/
final class QueryParameterValueResolver implements ValueResolverInterface
{
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if (!$attribute = $argument->getAttributesOfType(MapQueryParameter::class)[0] ?? null) {
return [];
}
$name = $attribute->name ?? $argument->getName();
if (!$request->query->has($name)) {
if ($argument->isNullable() || $argument->hasDefaultValue()) {
return [];
}
throw new NotFoundHttpException(sprintf('Missing query parameter "%s".', $name));
}
$value = $request->query->all()[$name];
$type = $argument->getType();
if (null === $attribute->filter && 'array' === $type) {
if (!$argument->isVariadic()) {
return [(array) $value];
}
$filtered = array_values(array_filter((array) $value, \is_array(...)));
if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name));
}
return $filtered;
}
$options = [
'flags' => $attribute->flags | \FILTER_NULL_ON_FAILURE,
'options' => $attribute->options,
];
if ('array' === $type || $argument->isVariadic()) {
$value = (array) $value;
$options['flags'] |= \FILTER_REQUIRE_ARRAY;
} else {
$options['flags'] |= \FILTER_REQUIRE_SCALAR;
}
$enumType = null;
$filter = match ($type) {
'array' => \FILTER_DEFAULT,
'string' => \FILTER_DEFAULT,
'int' => \FILTER_VALIDATE_INT,
'float' => \FILTER_VALIDATE_FLOAT,
'bool' => \FILTER_VALIDATE_BOOL,
default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) {
'int' => \FILTER_VALIDATE_INT,
'string' => \FILTER_DEFAULT,
default => throw new \LogicException(sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')),
}
};
$value = filter_var($value, $attribute->filter ?? $filter, $options);
if (null !== $enumType && null !== $value) {
$enumFrom = static function ($value) use ($type) {
if (!\is_string($value) && !\is_int($value)) {
return null;
}
try {
return $type::from($value);
} catch (\ValueError) {
return null;
}
};
$value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value);
}
if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name));
}
if (!\is_array($value)) {
return [$value];
}
$filtered = array_filter($value, static fn ($v) => null !== $v);
if ($argument->isVariadic()) {
$filtered = array_values($filtered);
}
if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name));
}
return $argument->isVariadic() ? $filtered : [$filtered];
}
}

View File

@ -0,0 +1,40 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields a non-variadic argument's value from the request attributes.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class RequestAttributeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
return !$argument->isVariadic() && $request->attributes->has($argument->getName());
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : [];
}
}

View File

@ -0,0 +1,201 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
use Symfony\Component\Serializer\Exception\UnsupportedFormatException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Exception\ValidationFailedException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Konstantin Myakshin <molodchick@gmail.com>
*
* @final
*/
class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscriberInterface
{
/**
* @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT
* @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
*/
private const CONTEXT_DENORMALIZE = [
'disable_type_enforcement' => true,
'collect_denormalization_errors' => true,
];
/**
* @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
*/
private const CONTEXT_DESERIALIZE = [
'collect_denormalization_errors' => true,
];
public function __construct(
private readonly SerializerInterface&DenormalizerInterface $serializer,
private readonly ?ValidatorInterface $validator = null,
private readonly ?TranslatorInterface $translator = null,
) {
}
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$attribute = $argument->getAttributesOfType(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0]
?? $argument->getAttributesOfType(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0]
?? null;
if (!$attribute) {
return [];
}
if ($argument->isVariadic()) {
throw new \LogicException(sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName()));
}
$attribute->metadata = $argument;
return [$attribute];
}
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
{
$arguments = $event->getArguments();
foreach ($arguments as $i => $argument) {
if ($argument instanceof MapQueryString) {
$payloadMapper = 'mapQueryString';
$validationFailedCode = $argument->validationFailedStatusCode;
} elseif ($argument instanceof MapRequestPayload) {
$payloadMapper = 'mapRequestPayload';
$validationFailedCode = $argument->validationFailedStatusCode;
} else {
continue;
}
$request = $event->getRequest();
if (!$type = $argument->metadata->getType()) {
throw new \LogicException(sprintf('Could not resolve the "$%s" controller argument: argument should be typed.', $argument->metadata->getName()));
}
if ($this->validator) {
$violations = new ConstraintViolationList();
try {
$payload = $this->$payloadMapper($request, $type, $argument);
} catch (PartialDenormalizationException $e) {
$trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p);
foreach ($e->getErrors() as $error) {
$parameters = [];
$template = 'This value was of an unexpected type.';
if ($expectedTypes = $error->getExpectedTypes()) {
$template = 'This value should be of type {{ type }}.';
$parameters['{{ type }}'] = implode('|', $expectedTypes);
}
if ($error->canUseMessageForUser()) {
$parameters['hint'] = $error->getMessage();
}
$message = $trans($template, $parameters, 'validators');
$violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null));
}
$payload = $e->getData();
}
if (null !== $payload && !\count($violations)) {
$violations->addAll($this->validator->validate($payload, null, $argument->validationGroups ?? null));
}
if (\count($violations)) {
throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), iterator_to_array($violations))), new ValidationFailedException($payload, $violations));
}
} else {
try {
$payload = $this->$payloadMapper($request, $type, $argument);
} catch (PartialDenormalizationException $e) {
throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e);
}
}
if (null === $payload) {
$payload = match (true) {
$argument->metadata->hasDefaultValue() => $argument->metadata->getDefaultValue(),
$argument->metadata->isNullable() => null,
default => throw new HttpException($validationFailedCode)
};
}
$arguments[$i] = $payload;
}
$event->setArguments($arguments);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments',
];
}
private function mapQueryString(Request $request, string $type, MapQueryString $attribute): ?object
{
if (!$data = $request->query->all()) {
return null;
}
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
}
private function mapRequestPayload(Request $request, string $type, MapRequestPayload $attribute): ?object
{
if (null === $format = $request->getContentTypeFormat()) {
throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, 'Unsupported format.');
}
if ($attribute->acceptFormat && !\in_array($format, (array) $attribute->acceptFormat, true)) {
throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, sprintf('Unsupported format, expects "%s", but "%s" given.', implode('", "', (array) $attribute->acceptFormat), $format));
}
if ($data = $request->request->all()) {
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
}
if ('' === $data = $request->getContent()) {
return null;
}
if ('form' === $format) {
throw new HttpException(Response::HTTP_BAD_REQUEST, 'Request payload contains invalid "form" data.');
}
try {
return $this->serializer->deserialize($data, $type, $format, self::CONTEXT_DESERIALIZE + $attribute->serializationContext);
} catch (UnsupportedFormatException $e) {
throw new HttpException(Response::HTTP_UNSUPPORTED_MEDIA_TYPE, sprintf('Unsupported format: "%s".', $format), $e);
} catch (NotEncodableValueException $e) {
throw new HttpException(Response::HTTP_BAD_REQUEST, sprintf('Request payload contains invalid "%s" data.', $format), $e);
}
}
}

View File

@ -0,0 +1,40 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields the same instance as the request object passed along.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class RequestValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class);
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class) ? [$request] : [];
}
}

View File

@ -0,0 +1,99 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields a service keyed by _controller and argument name.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ServiceValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
private ContainerInterface $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
$controller = $request->attributes->get('_controller');
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
$controller = $controller[0].'::'.$controller[1];
} elseif (!\is_string($controller) || '' === $controller) {
return false;
}
if ('\\' === $controller[0]) {
$controller = ltrim($controller, '\\');
}
if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) {
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
}
return $this->container->has($controller) && $this->container->get($controller)->has($argument->getName());
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
$controller = $request->attributes->get('_controller');
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
$controller = $controller[0].'::'.$controller[1];
} elseif (!\is_string($controller) || '' === $controller) {
return [];
}
if ('\\' === $controller[0]) {
$controller = ltrim($controller, '\\');
}
if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) {
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
}
if (!$this->container->has($controller) || !$this->container->get($controller)->has($argument->getName())) {
return [];
}
try {
return [$this->container->get($controller)->get($argument->getName())];
} catch (RuntimeException $e) {
$what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller);
$message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage());
if ($e->getMessage() === $message) {
$message = sprintf('Cannot resolve %s: %s', $what, $message);
}
$r = new \ReflectionProperty($e, 'message');
$r->setValue($e, $message);
throw $e;
}
}
}

View File

@ -0,0 +1,59 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields the Session.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class SessionValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
if (!$request->hasSession()) {
return false;
}
$type = $argument->getType();
if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) {
return false;
}
return $request->getSession() instanceof $type;
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if (!$request->hasSession()) {
return [];
}
$type = $argument->getType();
if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) {
return [];
}
return $request->getSession() instanceof $type ? [$request->getSession()] : [];
}
}

View File

@ -0,0 +1,64 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Provides timing information via the stopwatch.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class TraceableValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
private ArgumentValueResolverInterface|ValueResolverInterface $inner;
private Stopwatch $stopwatch;
public function __construct(ArgumentValueResolverInterface|ValueResolverInterface $inner, Stopwatch $stopwatch)
{
$this->inner = $inner;
$this->stopwatch = $stopwatch;
}
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
if ($this->inner instanceof ValueResolverInterface) {
return true;
}
$method = $this->inner::class.'::'.__FUNCTION__;
$this->stopwatch->start($method, 'controller.argument_value_resolver');
$return = $this->inner->supports($request, $argument);
$this->stopwatch->stop($method);
return $return;
}
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$method = $this->inner::class.'::'.__FUNCTION__;
$this->stopwatch->start($method, 'controller.argument_value_resolver');
yield from $this->inner->resolve($request, $argument);
$this->stopwatch->stop($method);
}
}

View File

@ -0,0 +1,52 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Uid\AbstractUid;
final class UidValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
return !$argument->isVariadic()
&& \is_string($request->attributes->get($argument->getName()))
&& null !== $argument->getType()
&& is_subclass_of($argument->getType(), AbstractUid::class, true);
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if ($argument->isVariadic()
|| !\is_string($value = $request->attributes->get($argument->getName()))
|| null === ($uidClass = $argument->getType())
|| !is_subclass_of($uidClass, AbstractUid::class, true)
) {
return [];
}
try {
return [$uidClass::fromString($value)];
} catch (\InvalidArgumentException $e) {
throw new NotFoundHttpException(sprintf('The uid for the "%s" parameter is invalid.', $argument->getName()), $e);
}
}
}

View File

@ -0,0 +1,50 @@
<?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\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Yields a variadic argument's values from the request attributes.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class VariadicValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
/**
* @deprecated since Symfony 6.2, use resolve() instead
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
return $argument->isVariadic() && $request->attributes->has($argument->getName());
}
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if (!$argument->isVariadic() || !$request->attributes->has($argument->getName())) {
return [];
}
$values = $request->attributes->get($argument->getName());
if (!\is_array($values)) {
throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), get_debug_type($values)));
}
return $values;
}
}

View File

@ -0,0 +1,32 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* An ArgumentResolverInterface instance knows how to determine the
* arguments for a specific action.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ArgumentResolverInterface
{
/**
* Returns the arguments to pass to the controller.
*
* @param \ReflectionFunctionAbstract|null $reflector
*
* @throws \RuntimeException When no value could be provided for a required argument
*/
public function getArguments(Request $request, callable $controller/* , \ReflectionFunctionAbstract $reflector = null */): array;
}

View File

@ -0,0 +1,35 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Responsible for resolving the value of an argument based on its metadata.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*
* @deprecated since Symfony 6.2, implement ValueResolverInterface instead
*/
interface ArgumentValueResolverInterface
{
/**
* Whether this resolver can resolve the value for the given ArgumentMetadata.
*/
public function supports(Request $request, ArgumentMetadata $argument): bool;
/**
* Returns the possible value(s).
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable;
}

View File

@ -0,0 +1,63 @@
<?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\HttpKernel\Controller;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Container;
/**
* A controller resolver searching for a controller in a psr-11 container when using the "service::method" notation.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class ContainerControllerResolver extends ControllerResolver
{
protected $container;
public function __construct(ContainerInterface $container, ?LoggerInterface $logger = null)
{
$this->container = $container;
parent::__construct($logger);
}
protected function instantiateController(string $class): object
{
$class = ltrim($class, '\\');
if ($this->container->has($class)) {
return $this->container->get($class);
}
try {
return parent::instantiateController($class);
} catch (\Error $e) {
}
$this->throwExceptionIfControllerWasRemoved($class, $e);
if ($e instanceof \ArgumentCountError) {
throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e);
}
throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e);
}
private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous): void
{
if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) {
throw new \InvalidArgumentException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous);
}
}
}

View File

@ -0,0 +1,44 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface;
/**
* Acts as a marker and a data holder for a Controller.
*
* Some methods in Symfony accept both a URI (as a string) or a controller as
* an argument. In the latter case, instead of passing an array representing
* the controller, you can use an instance of this class.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see FragmentRendererInterface
*/
class ControllerReference
{
public $controller;
public $attributes = [];
public $query = [];
/**
* @param string $controller The controller name
* @param array $attributes An array of parameters to add to the Request attributes
* @param array $query An array of parameters to add to the Request query string
*/
public function __construct(string $controller, array $attributes = [], array $query = [])
{
$this->controller = $controller;
$this->attributes = $attributes;
$this->query = $query;
}
}

View File

@ -0,0 +1,279 @@
<?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\HttpKernel\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Attribute\AsController;
/**
* This implementation uses the '_controller' request attribute to determine
* the controller to execute.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class ControllerResolver implements ControllerResolverInterface
{
private ?LoggerInterface $logger;
private array $allowedControllerTypes = [];
private array $allowedControllerAttributes = [AsController::class => AsController::class];
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* @param array<class-string> $types
* @param array<class-string> $attributes
*/
public function allowControllers(array $types = [], array $attributes = []): void
{
foreach ($types as $type) {
$this->allowedControllerTypes[$type] = $type;
}
foreach ($attributes as $attribute) {
$this->allowedControllerAttributes[$attribute] = $attribute;
}
}
/**
* @throws BadRequestException when the request has attribute "_check_controller_is_allowed" set to true and the controller is not allowed
*/
public function getController(Request $request): callable|false
{
if (!$controller = $request->attributes->get('_controller')) {
$this->logger?->warning('Unable to look for the controller as the "_controller" parameter is missing.');
return false;
}
if (\is_array($controller)) {
if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) {
try {
$controller[0] = $this->instantiateController($controller[0]);
} catch (\Error|\LogicException $e) {
if (\is_callable($controller)) {
return $this->checkController($request, $controller);
}
throw $e;
}
}
if (!\is_callable($controller)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller));
}
return $this->checkController($request, $controller);
}
if (\is_object($controller)) {
if (!\is_callable($controller)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller));
}
return $this->checkController($request, $controller);
}
if (\function_exists($controller)) {
return $this->checkController($request, $controller);
}
try {
$callable = $this->createController($controller);
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$e->getMessage(), 0, $e);
}
if (!\is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($callable));
}
return $this->checkController($request, $callable);
}
/**
* Returns a callable for the given controller.
*
* @throws \InvalidArgumentException When the controller cannot be created
*/
protected function createController(string $controller): callable
{
if (!str_contains($controller, '::')) {
$controller = $this->instantiateController($controller);
if (!\is_callable($controller)) {
throw new \InvalidArgumentException($this->getControllerError($controller));
}
return $controller;
}
[$class, $method] = explode('::', $controller, 2);
try {
$controller = [$this->instantiateController($class), $method];
} catch (\Error|\LogicException $e) {
try {
if ((new \ReflectionMethod($class, $method))->isStatic()) {
return $class.'::'.$method;
}
} catch (\ReflectionException) {
throw $e;
}
throw $e;
}
if (!\is_callable($controller)) {
throw new \InvalidArgumentException($this->getControllerError($controller));
}
return $controller;
}
/**
* Returns an instantiated controller.
*/
protected function instantiateController(string $class): object
{
return new $class();
}
private function getControllerError(mixed $callable): string
{
if (\is_string($callable)) {
if (str_contains($callable, '::')) {
$callable = explode('::', $callable, 2);
} else {
return sprintf('Function "%s" does not exist.', $callable);
}
}
if (\is_object($callable)) {
$availableMethods = $this->getClassMethodsWithoutMagicMethods($callable);
$alternativeMsg = $availableMethods ? sprintf(' or use one of the available methods: "%s"', implode('", "', $availableMethods)) : '';
return sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.', get_debug_type($callable), $alternativeMsg);
}
if (!\is_array($callable)) {
return sprintf('Invalid type for controller given, expected string, array or object, got "%s".', get_debug_type($callable));
}
if (!isset($callable[0]) || !isset($callable[1]) || 2 !== \count($callable)) {
return 'Invalid array callable, expected [controller, method].';
}
[$controller, $method] = $callable;
if (\is_string($controller) && !class_exists($controller)) {
return sprintf('Class "%s" does not exist.', $controller);
}
$className = \is_object($controller) ? get_debug_type($controller) : $controller;
if (method_exists($controller, $method)) {
return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className);
}
$collection = $this->getClassMethodsWithoutMagicMethods($controller);
$alternatives = [];
foreach ($collection as $item) {
$lev = levenshtein($method, $item);
if ($lev <= \strlen($method) / 3 || str_contains($item, $method)) {
$alternatives[] = $item;
}
}
asort($alternatives);
$message = sprintf('Expected method "%s" on class "%s"', $method, $className);
if (\count($alternatives) > 0) {
$message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives));
} else {
$message .= sprintf('. Available methods: "%s".', implode('", "', $collection));
}
return $message;
}
private function getClassMethodsWithoutMagicMethods($classOrObject): array
{
$methods = get_class_methods($classOrObject);
return array_filter($methods, fn (string $method) => 0 !== strncmp($method, '__', 2));
}
private function checkController(Request $request, callable $controller): callable
{
if (!$request->attributes->get('_check_controller_is_allowed', false)) {
return $controller;
}
$r = null;
if (\is_array($controller)) {
[$class, $name] = $controller;
$name = (\is_string($class) ? $class : $class::class).'::'.$name;
} elseif (\is_object($controller) && !$controller instanceof \Closure) {
$class = $controller;
$name = $class::class.'::__invoke';
} else {
$r = new \ReflectionFunction($controller);
$name = $r->name;
if (str_contains($name, '{closure}')) {
$name = $class = \Closure::class;
} elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
$class = $class->name;
$name = $class.'::'.$name;
}
}
if ($class) {
foreach ($this->allowedControllerTypes as $type) {
if (is_a($class, $type, true)) {
return $controller;
}
}
}
$r ??= new \ReflectionClass($class);
foreach ($r->getAttributes() as $attribute) {
if (isset($this->allowedControllerAttributes[$attribute->getName()])) {
return $controller;
}
}
if (str_contains($name, '@anonymous')) {
$name = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $name);
}
if (-1 === $request->attributes->get('_check_controller_is_allowed')) {
trigger_deprecation('symfony/http-kernel', '6.4', 'Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class);
return $controller;
}
throw new BadRequestException(sprintf('Callable "%s()" is not allowed as a controller. Did you miss tagging it with "#[AsController]" or registering its type with "%s::allowControllers()"?', $name, self::class));
}
}

View File

@ -0,0 +1,41 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* A ControllerResolverInterface implementation knows how to determine the
* controller to execute based on a Request object.
*
* A Controller can be any valid PHP callable.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ControllerResolverInterface
{
/**
* Returns the Controller instance associated with a Request.
*
* As several resolvers can exist for a single application, a resolver must
* return false when it is not able to determine the controller.
*
* The resolver must only throw an exception when it should be able to load a
* controller but cannot because of some errors made by the developer.
*
* @return callable|false A PHP callable representing the Controller,
* or false if this resolver is not able to determine the controller
*
* @throws \LogicException If a controller was found based on the request but it is not callable
*/
public function getController(Request $request): callable|false;
}

View File

@ -0,0 +1,62 @@
<?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\HttpKernel\Controller;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Renders error or exception pages from a given FlattenException.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ErrorController
{
private HttpKernelInterface $kernel;
private string|object|array|null $controller;
private ErrorRendererInterface $errorRenderer;
public function __construct(HttpKernelInterface $kernel, string|object|array|null $controller, ErrorRendererInterface $errorRenderer)
{
$this->kernel = $kernel;
$this->controller = $controller;
$this->errorRenderer = $errorRenderer;
}
public function __invoke(\Throwable $exception): Response
{
$exception = $this->errorRenderer->render($exception);
return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders());
}
public function preview(Request $request, int $code): Response
{
/*
* This Request mimics the parameters set by
* \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with
* the additional "showException" flag.
*/
$subRequest = $request->duplicate(null, null, [
'_controller' => $this->controller,
'exception' => new HttpException($code, 'This is a sample exception.'),
'logger' => null,
'showException' => false,
]);
return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}

View File

@ -0,0 +1,45 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableArgumentResolver implements ArgumentResolverInterface
{
private ArgumentResolverInterface $resolver;
private Stopwatch $stopwatch;
public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch)
{
$this->resolver = $resolver;
$this->stopwatch = $stopwatch;
}
/**
* @param \ReflectionFunctionAbstract|null $reflector
*/
public function getArguments(Request $request, callable $controller/* , \ReflectionFunctionAbstract $reflector = null */): array
{
$reflector = 2 < \func_num_args() ? func_get_arg(2) : null;
$e = $this->stopwatch->start('controller.get_arguments');
try {
return $this->resolver->getArguments($request, $controller, $reflector);
} finally {
$e->stop();
}
}
}

View File

@ -0,0 +1,41 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableControllerResolver implements ControllerResolverInterface
{
private ControllerResolverInterface $resolver;
private Stopwatch $stopwatch;
public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch)
{
$this->resolver = $resolver;
$this->stopwatch = $stopwatch;
}
public function getController(Request $request): callable|false
{
$e = $this->stopwatch->start('controller.get_callable');
try {
return $this->resolver->getController($request);
} finally {
$e->stop();
}
}
}

View File

@ -0,0 +1,28 @@
<?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\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Responsible for resolving the value of an argument based on its metadata.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ValueResolverInterface
{
/**
* Returns the possible value(s).
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable;
}