symfony/symfony · error · Symfony\Component\Cache\Exception\InvalidArgumentException
Cache key "%s" has non-serializable "%s" value.
Error message
Cache key "%s" has non-serializable "%s" value.
What it means
PhpFilesAdapter::doSave() (PhpFilesAdapter.php:219-224) serializes object/array values via VarExporter into PHP files that OPcache compiles. If VarExporter::export throws (resource inside, closure, unsupported internal object), InvalidArgumentException is raised, naming the offending key and value type.
Source
Thrown at src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php:223
return $now < $expiresAt;
}
protected function doSave(array $values, int $lifetime): array|bool
{
$ok = true;
$expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
$allowCompile = self::isSupported();
foreach ($values as $key => $value) {
unset($this->values[$key]);
$isStaticValue = true;
if (null === $value) {
$value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) {
try {
$value = VarExporter::export($value, $isStaticValue);
} catch (\Exception $e) {
throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e);
}
} elseif (\is_string($value)) {
// Wrap "N;" in a closure to not confuse it with an encoded `null`
if ('N;' === $value) {
$isStaticValue = false;
}
$value = var_export($value, true);
} elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)));
} else {
$value = var_export($value, true);
}
$encodedKey = rawurlencode($key);
if ($isStaticValue) {
$value = "return [{$expiry}, {$value}];";
} elseif ($this->appendOnly) {View on GitHub (pinned to 698e28026c)
Solutions
- Convert non-exportable fields to scalars/arrays before saving.
- Make objects implement `__serialize`/`__unserialize` and keep them free of resources/closures.
- Switch to an adapter that uses PHP's native serialize() (e.g. FilesystemAdapter) if you must store resource-bearing objects — though resources still won't survive.
Example fix
// before $item->set($pdoStatement); // PDOStatement $pool->save($item); // after $item->set($pdoStatement->fetchAll(\PDO::FETCH_ASSOC)); $pool->save($item);
Defensive patterns
Strategy: validation
Validate before calling
foreach ($values as $k => $v) {
if (is_object($v) || is_array($v)) {
try { \Symfony\Component\VarExporter\VarExporter::export($v); }
catch (\Throwable $e) { throw new \RuntimeException(sprintf('%s not exportable', $k), 0, $e); }
}
}
$pool->save($item); Type guard
function isExportable(mixed $value): bool
{
if (is_resource($value) || $value instanceof \Closure) { return false; }
try { \Symfony\Component\VarExporter\VarExporter::export($value); return true; }
catch (\Throwable) { return false; }
} Try / catch
try {
$pool->save($item);
} catch (\Symfony\Component\Cache\Exception\InvalidArgumentException $e) {
// convert value to a safe representation and retry, or log and skip
} Prevention
- Cache DTOs composed only of scalars/arrays/serializable objects.
- Close file/stream handles before storing their data.
- Add VarExporter::export smoke tests for cached value types.
When it happens
Trigger: Calling `$pool->save($item)` (or saveDeferred/commit) where the item's value is an object/array containing a resource, closure, or VarExporter-incompatible internal class.
Common situations: Caching entities that hold open file handles, GD images, PDO statements, or SplFileInfo; caching DTOs with closure-based computed properties.
Related errors
- Cache key "%s" has non-serializable "%s" value.
- Cache directory is not writable (%s).
- Cannot serialize Symfony\Component\Cache\Adapter\TagAwareAda
- Cannot unserialize Symfony\Component\Cache\Adapter\TagAwareA
- Cannot serialize Symfony\Component\Cache\Traits\AbstractAdap
AI-assisted analysis of symfony/symfony@698e28026c (2026-08-06).
Data as JSON: /api/errors/6ebd7bd227b203c8.
Report an issue: GitHub.