2024-11-27 11:16:45 +07:00
< ? 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\VariadicValueResolver ;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory ;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface ;
use Symfony\Component\HttpKernel\Exception\NearMissValueResolverException ;
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 ;
/**
* @param iterable<mixed, ValueResolverInterface> $argumentValueResolvers
*/
2024-12-02 01:18:34 +07:00
public function __construct (
? ArgumentMetadataFactoryInterface $argumentMetadataFactory = null ,
iterable $argumentValueResolvers = [],
private ? ContainerInterface $namedResolvers = null ,
) {
2024-11-27 11:16:45 +07:00
$this -> argumentMetadataFactory = $argumentMetadataFactory ? ? new ArgumentMetadataFactory ();
$this -> argumentValueResolvers = $argumentValueResolvers ? : self :: getDefaultArgumentValueResolvers ();
}
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 ) {
2024-12-02 01:18:34 +07:00
throw new \LogicException ( \sprintf ( 'You can only pin one resolver per argument, but argument "$%s" of "%s()" has more.' , $metadata -> getName (), $metadata -> getControllerName ()));
2024-11-27 11:16:45 +07:00
} 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 (),
];
}
}
$valueResolverExceptions = [];
foreach ( $argumentValueResolvers as $name => $resolver ) {
if ( isset ( $disabledResolvers [ \is_int ( $name ) ? $resolver :: class : $name ])) {
continue ;
}
try {
$count = 0 ;
foreach ( $resolver -> resolve ( $request , $metadata ) as $argument ) {
++ $count ;
$arguments [] = $argument ;
}
} catch ( NearMissValueResolverException $e ) {
$valueResolverExceptions [] = $e ;
}
if ( 1 < $count && ! $metadata -> isVariadic ()) {
2024-12-02 01:18:34 +07:00
throw new \InvalidArgumentException ( \sprintf ( '"%s::resolve()" must yield at most one value for non-variadic arguments.' , get_debug_type ( $resolver )));
2024-11-27 11:16:45 +07:00
}
if ( $count ) {
// continue to the next controller argument
continue 2 ;
}
}
$reasons = array_map ( static fn ( NearMissValueResolverException $e ) => $e -> getMessage (), $valueResolverExceptions );
if ( ! $reasons ) {
$reasons [] = '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.' ;
}
$reasonCounter = 1 ;
if ( \count ( $reasons ) > 1 ) {
foreach ( $reasons as $i => $reason ) {
$reasons [ $i ] = $reasonCounter . ') ' . $reason ;
++ $reasonCounter ;
}
}
2024-12-02 01:18:34 +07:00
throw new \RuntimeException ( \sprintf ( 'Controller "%s" requires the "$%s" argument that could not be resolved. ' . ( $reasonCounter > 1 ? 'Possible reasons: ' : '' ) . '%s' , $metadata -> getControllerName (), $metadata -> getName (), implode ( ' ' , $reasons )));
2024-11-27 11:16:45 +07:00
}
return $arguments ;
}
/**
* @return iterable<int, ValueResolverInterface>
*/
public static function getDefaultArgumentValueResolvers () : iterable
{
return [
new RequestAttributeValueResolver (),
new RequestValueResolver (),
new SessionValueResolver (),
new DefaultValueResolver (),
new VariadicValueResolver (),
];
}
}