symfony/http-kernel · error · TypeError
The value of the "$key" option of the
Error message
The value of the "$key" option of the "%s" attribute must evaluate to a string, "%s" given.
What it means
The #[RateLimit] key option may be a string expression evaluated via the ControllerArgumentsEvent evaluator; its result is used as the limiter bucket key. If the evaluation yields a non-string, a TypeError is thrown naming the actual type.
Solutions
- Make the expression return a string, e.g. key: "request.headers.get('X-Api-Key', 'anonymous')".
- Cast inside the expression or use a fallback: key: "user.getId() ~ ''" is discouraged; prefer explicit string functions.
- Ensure default values are strings when header/attribute may be absent.
Example fix
// before
#[RateLimit(limiter: 'basic', key: 'request.headers.get("X-Api-Key")')]
// after
#[RateLimit(limiter: 'basic', key: "request.headers.get('X-Api-Key', 'anonymous')")] Defensive patterns
Strategy: type-guard
Validate before calling
$key = $attribute->key;
if ($key !== null && !is_string($key)) { /* expression must be string-typed source */ } Type guard
function isValidRateLimitKey(mixed $evaluated): bool { return is_string($evaluated); } Try / catch
try { $listener->onKernelControllerAttribute($event, 'kernel.controller', ...); } catch (\TypeError $e) { /* fall back to IP-based key */ } Prevention
- Provide non-null defaults in key expressions (headers.get with default)
- Cast numeric ids to string in expressions
- Cover key expressions with unit tests asserting string results
When it happens
Trigger: Annotating with #[RateLimit(key: 'expr...')] where the expression evaluates to an int, array, or null instead of a string, during onKernelControllerAttribute.
Common situations: Using key: 'request.headers.get("X-Api-Key")' which returns null when the header is missing; an expression returning a numeric user id rather than casting it to string.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Nested expressions in validation groups are not supported…
- The value of the "$if" option of the
- Rate limiter " " does not exist. Did you forget to…
- Request payload contains invalid "form" data.
- Unsupported format: " ".
AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13).
Data as JSON: /api/errors/2a854c39738a765f.
Report an issue: GitHub.
Appendix: source
Thrown at EventListener/RateLimitAttributeListener.php:63
* @param ControllerAttributeEvent<RateLimit, ControllerArgumentsEvent> $event
*/
public function onKernelControllerAttribute(ControllerAttributeEvent $event, ?string $eventName = null, ?EventDispatcherInterface $dispatcher = null): void
{
$request = $event->kernelEvent->getRequest();
$attribute = $event->attribute;
if ($attribute->methods && !\in_array($request->getMethod(), $attribute->methods, true)) {
return;
}
if (!$this->limiters->has($attribute->limiter)) {
throw new \InvalidArgumentException(\sprintf('Rate limiter "%s" does not exist. Did you forget to configure it? Available limiters: "%s".', $attribute->limiter, implode('", "', array_keys($this->limiters->getProvidedServices()))));
}
if (null === $attribute->key) {
$key = ($request->getClientIp() ?? 'unknown').'~'.$request->getMethod().'~'.$request->getPathInfo();
} elseif (!\is_string($key = $event->evaluate($attribute->key))) {
throw new \TypeError(\sprintf('The value of the "$key" option of the "%s" attribute must evaluate to a string, "%s" given.', RateLimit::class, get_debug_type($key)));
}
$rateLimit = $this->limiters->get($attribute->limiter)->create($key)->consume($attribute->tokens);
$candidate = $attribute->exposeHeaders && null !== $rateLimit->getResetAt()
? new AppliedRateLimit($rateLimit, $attribute->tokens)
: null;
if (!$rateLimit->isAccepted()) {
$request->attributes->set(self::RATE_LIMIT_ATTRIBUTE, $candidate);
if ($dispatcher && class_exists(RateLimitExceededEvent::class)) {
$dispatcher->dispatch(new RateLimitExceededEvent($rateLimit, $attribute->limiter, $key));
}
throw new TooManyRequestsHttpException(max(0, $rateLimit->getRetryAfter()->getTimestamp() - time()));
}
View on GitHub (pinned to aa3a39d728)