symfony/http-kernel · error · LogicException

" ::warmUp()" should return a list of files or classes but…

Error message

"%s::warmUp()" should return a list of files or classes but "%s" is none of them.

What it means

CacheWarmerAggregate::warmUp() iterates each warmer's warmUp() return values and requires them to be existing files/directories under the cache dir or class names for preload. A LogicException is thrown when a warmer returns an item that is neither (e.g. a non-existent path or invalid string), catching warmer implementations that violate the contract.

Solutions

  1. Fix the warmer so warmUp() returns only existing absolute file paths under the cache/build dir or valid class names.
  2. If the warmer writes no files, return [] (or only class names to preload).
  3. Verify paths exist after writing them in the warmer (file_exists assertion) before returning them.

Example fix

// before
class MyWarmer implements CacheWarmerInterface
{
    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        return ['var/cache/prod/missing.xml']; // never written
    }
}
// after
class MyWarmer implements CacheWarmerInterface
{
    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        $file = $cacheDir.'/my/data.php';
        file_put_contents($file, '<?php return [];');
        return [$file];
    }
}
Defensive patterns

Strategy: validation

Validate before calling

$items = $warmer->warmUp($cacheDir, $buildDir);
foreach ($items as $item) {
    $valid = is_dir($item) || is_file($item) || (class_exists($item) && interface_exists($item) === false);
    if (!$valid) { throw new \AssertionError("Invalid warmer return: $item"); }
}

Try / catch

try { $aggregate->warmUp($cacheDir); } catch (\LogicException $e) { /* fix the offending warmer named in the message */ }

Prevention

When it happens

Trigger: A custom CacheWarmerInterface::warmUp() returns an array containing a path that is not an existing file under dirname($cacheDir)/dirname($buildDir), or a malformed entry that is neither a valid path nor a class name.

Common situations: Custom warmers returning generated file paths that were never written, returning relative paths outside the cache dir, or (after Symfony 6.3 contract change) returning wrong/empty string items; upgrading Symfony where warmer return values became meaningful for preload.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13). Data as JSON: /api/errors/21ab8a53e1102238. Report an issue: GitHub.

Appendix: source

Thrown at CacheWarmer/CacheWarmerAggregate.php:98

                return null;
            });
        }

        $preload = [];
        try {
            foreach ($this->warmers as $warmer) {
                if (!$this->optionalsEnabled && $warmer->isOptional()) {
                    continue;
                }
                if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) {
                    continue;
                }

                $start = microtime(true);
                foreach ($warmer->warmUp($cacheDir, $buildDir) as $item) {
                    if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item)) || ($buildDir && str_starts_with($item, \dirname($buildDir)) && !is_file($item))) {
                        throw new \LogicException(\sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item));
                    }
                    $preload[] = $item;
                }

                if ($io?->isDebug()) {
                    $io->info(\sprintf('"%s" completed in %0.2fms.', $warmer::class, 1000 * (microtime(true) - $start)));
                }
            }
        } finally {
            if ($collectDeprecations) {
                restore_error_handler();

                if ($h = fopen($this->deprecationLogsFilepath, 'c+')) {
                    flock($h, \LOCK_EX);

                    set_error_handler(static fn () => true);
                    try {
                        $previousLogs = unserialize(stream_get_contents($h), ['allowed_classes' => false]);

View on GitHub (pinned to aa3a39d728)