symfony/routing · warning · BadMethodCallException
Cannot unserialize Symfony\Component\Routing\Route
Error message
Cannot unserialize Symfony\Component\Routing\Route
What it means
Route::__unserialize() guards against unserializing attacker-crafted payloads: if path, host, or condition come back as Stringable objects, deserialization could trigger arbitrary code via __toString(), so Symfony refuses with this BadMethodCallException. It only fires on malicious or hand-mangled serialized data — legitimate serialized Route objects contain plain strings.
Solutions
- Never unserialize untrusted input; use json_encode/json_decode or a signed serialization format instead
- If you must unserialize, validate/allow-list the payload structure and classes first (e.g. allowed_classes: [])
- Regenerate the serialized data from a trusted source; do not attempt to hand-craft Route payloads
Example fix
// before $route = unserialize($userInput); // after $data = json_decode($userInput, true); $route = (new Route($data['path'] ?? '/'))->setHost($data['host'] ?? '');
Defensive patterns
Strategy: type-guard
Validate before calling
$data = unserialize($payload, ['allowed_classes' => false]); if (isset($data['path']) && is_object($data['path'])) { throw new \RuntimeException('Suspicious Route payload'); } Type guard
function safeRoutePayload(array $data): bool { foreach (['path','host','condition'] as $k) { if (isset($data[$k]) && $data[$k] instanceof \Stringable) return false; } return true; } Try / catch
try { $route = unserialize($payload); } catch (\BadMethodCallException $e) { if (str_contains($e->getMessage(), 'Cannot unserialize Symfony\\Component\\Routing\\Route')) { /* reject payload, log security event */ } throw $e; } Prevention
- Never unserialize user-controlled input
- Use allowed_classes => [] in unserialize when class restoration is unnecessary
- Switch to JSON for interchange of route data
When it happens
Trigger: unserialize() called on a crafted/malformed payload representing a Symfony Route where 'path', 'host' or 'condition' entries are objects implementing Stringable; unserializing user-supplied data that was doctored.
Common situations: Security-driven: apps that unserialize untrusted input containing Route-like payloads; payload tampering in caches or queues; passing random data to unserialize and hitting this guard.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- Cannot unserialize Loader\Configurator\ImportConfigurator
- Parameter " " for route " " must match " " (" " given) to…
- Parameters for route
- Route aliases cannot be used on non-invokable class
- The " ()" method must not be called.
AI-assisted analysis of symfony/routing@83fa223250 (2026-09-14).
Data as JSON: /api/errors/864ad52f90a693f3.
Report an issue: GitHub.
Appendix: source
Thrown at Route.php:82
'path' => $this->path,
'host' => $this->host,
'defaults' => $this->defaults,
'requirements' => $this->requirements,
'options' => $this->options,
'schemes' => $this->schemes,
'methods' => $this->methods,
'condition' => $this->condition,
'compiled' => $this->compiled,
];
}
public function __unserialize(array $data): void
{
if (($data['path'] ?? null) instanceof \Stringable
|| ($data['host'] ?? null) instanceof \Stringable
|| ($data['condition'] ?? null) instanceof \Stringable
) {
throw new \BadMethodCallException('Cannot unserialize '.self::class);
}
$this->path = $data['path'];
$this->host = $data['host'];
$this->defaults = $data['defaults'];
$this->requirements = $data['requirements'];
$this->options = $data['options'];
$this->schemes = $data['schemes'];
$this->methods = $data['methods'];
if (isset($data['condition'])) {
$this->condition = $data['condition'];
}
if (isset($data['compiled'])) {
$this->compiled = $data['compiled'];
}
}
View on GitHub (pinned to 83fa223250)