QueryParameterValueResolver.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
  13. use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
  14. use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
  15. use Symfony\Component\HttpKernel\Exception\HttpException;
  16. use Symfony\Component\Uid\AbstractUid;
  17. /**
  18. * Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters.
  19. *
  20. * @author Ruud Kamphuis <ruud@ticketswap.com>
  21. * @author Nicolas Grekas <p@tchwork.com>
  22. * @author Mateusz Anders <anders_mateusz@outlook.com>
  23. * @author Ionut Enache <i.ovidiuenache@yahoo.com>
  24. */
  25. final class QueryParameterValueResolver implements ValueResolverInterface
  26. {
  27. public function resolve(Request $request, ArgumentMetadata $argument): array
  28. {
  29. if (!$attribute = $argument->getAttributesOfType(MapQueryParameter::class)[0] ?? null) {
  30. return [];
  31. }
  32. $name = $attribute->name ?? $argument->getName();
  33. $validationFailedCode = $attribute->validationFailedStatusCode;
  34. if (!$request->query->has($name)) {
  35. if ($argument->isNullable() || $argument->hasDefaultValue()) {
  36. return [];
  37. }
  38. throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Missing query parameter "%s".', $name));
  39. }
  40. $value = $request->query->all()[$name];
  41. $type = $argument->getType();
  42. if (null === $attribute->filter && 'array' === $type) {
  43. if (!$argument->isVariadic()) {
  44. return [(array) $value];
  45. }
  46. $filtered = array_values(array_filter((array) $value, \is_array(...)));
  47. if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
  48. throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name));
  49. }
  50. return $filtered;
  51. }
  52. $options = [
  53. 'flags' => $attribute->flags | \FILTER_NULL_ON_FAILURE,
  54. 'options' => $attribute->options,
  55. ];
  56. if ('array' === $type || $argument->isVariadic()) {
  57. $value = (array) $value;
  58. $options['flags'] |= \FILTER_REQUIRE_ARRAY;
  59. } else {
  60. $options['flags'] |= \FILTER_REQUIRE_SCALAR;
  61. }
  62. $uidType = null;
  63. if (is_subclass_of($type, AbstractUid::class)) {
  64. $uidType = $type;
  65. $type = 'uid';
  66. }
  67. $enumType = null;
  68. $filter = match ($type) {
  69. 'array' => \FILTER_DEFAULT,
  70. 'string' => isset($attribute->options['regexp']) ? \FILTER_VALIDATE_REGEXP : \FILTER_DEFAULT,
  71. 'int' => \FILTER_VALIDATE_INT,
  72. 'float' => \FILTER_VALIDATE_FLOAT,
  73. 'bool' => \FILTER_VALIDATE_BOOL,
  74. 'uid' => \FILTER_DEFAULT,
  75. default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) {
  76. 'int' => \FILTER_VALIDATE_INT,
  77. 'string' => \FILTER_DEFAULT,
  78. default => throw new \LogicException(\sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool, uid or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')),
  79. },
  80. };
  81. $value = filter_var($value, $attribute->filter ?? $filter, $options);
  82. if (null !== $enumType && null !== $value) {
  83. $enumFrom = static function ($value) use ($type) {
  84. if (!\is_string($value) && !\is_int($value)) {
  85. return null;
  86. }
  87. try {
  88. return $type::from($value);
  89. } catch (\ValueError) {
  90. return null;
  91. }
  92. };
  93. $value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value);
  94. }
  95. if (null !== $uidType) {
  96. $value = \is_array($value) ? array_map([$uidType, 'fromString'], $value) : $uidType::fromString($value);
  97. }
  98. if (null === $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
  99. throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name));
  100. }
  101. if (!\is_array($value)) {
  102. return [$value];
  103. }
  104. $filtered = array_filter($value, static fn ($v) => null !== $v);
  105. if ($argument->isVariadic()) {
  106. $filtered = array_values($filtered);
  107. }
  108. if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
  109. throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name));
  110. }
  111. return $argument->isVariadic() ? $filtered : [$filtered];
  112. }
  113. }