symfony/symfony · error · DomainException

Class not found:

Error message

Class not found: 

What it means

DefaultMarshaller::handleUnserializeCallback() is registered as unserialize_callback_func inside unmarshall(). When unserialize() encounters a class it cannot autoload, PHP calls this callback with the missing class name, which throws a DomainException. This surfaces missing-class cache corruption loudly instead of letting unserialize silently instantiate __PHP_Incomplete_Class.

Source

Thrown at src/Symfony/Component/Cache/Marshaller/DefaultMarshaller.php:94

                throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
            } elseif (null !== $value = igbinary_unserialize($value)) {
                return $value;
            }

            throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
        } catch (\Error $e) {
            throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
        } finally {
            ini_set('unserialize_callback_func', $unserializeCallbackHandler);
        }
    }

    /**
     * @internal
     */
    public static function handleUnserializeCallback(string $class): never
    {
        throw new \DomainException('Class not found: '.$class);
    }
}

View on GitHub (pinned to 698e28026c)

Solutions

  1. Clear the cache pool to evict blobs referencing the old/missing class.
  2. Restore the class (re-enable its bundle, run composer dump-autoload, fix the namespace).
  3. Invalidate by bumping the cache namespace after any rename of cached value classes.
  4. Keep cached value classes in stable, always-loaded namespaces.

Example fix

// before — blob references App\Legacy\UserDTO which was moved to App\UserDTO
$marshaller->unmarshall($blob); // DomainException: Class not found: App\Legacy\UserDTO

// after — flush stale blobs and/or keep the class autoloadable
$pool->clear();
// or bump namespace to invalidate all old entries
$pool = new RedisAdapter($redis, 'app_v2_');
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure cached value classes are autoloadable before reading
foreach (self::CACHED_VALUE_CLASSES as $fqcn) {
    if (!class_exists($fqcn)) {
        throw new \LogicException("Cached value class $fqcn is not autoloadable; flush cache or restore the class.");
    }
}

Try / catch

try {
    return $marshaller->unmarshall($blob);
} catch (\DomainException $e) {
    if (str_starts_with($e->getMessage(), 'Class not found:')) {
        $pool->deleteItem($key);
        return $loader();
    }
    throw $e;
}

Prevention

When it happens

Trigger: unmarshall() decodes a blob referencing a class that is not autoloadable at read time — e.g. the value's class was renamed/removed/disabled between write and read. Distinct from the adapter-level callback (error 565): this fires within the marshaller's unmarshall() path.

Common situations: Renaming or deleting a cached value class without clearing the cache; running a worker with fewer bundles/extensions enabled than the writer; partial deploy where classmap is out of sync; composer autoload mismatch after a refactor.

Related errors


AI-assisted analysis of symfony/symfony@698e28026c (2026-08-06). Data as JSON: /api/errors/a6262f8fe3925098. Report an issue: GitHub.