symfony/routing · error · LogicException

The " :: ()" method must return a RouteCollection: " "…

Error message

The "%s::%s()" method must return a RouteCollection: "%s" returned.

What it means

ObjectLoader::load() calls a loader method on a routing resource object (a class with #[Route] attributes or a load() method) and requires it to return a RouteCollection. Symfony throws this LogicException when the method returns something else (null, an array, another object). This is an internal contract between the router and resource loader classes.

Solutions

  1. Ensure the loader method returns a RouteCollection on every code path: $collection = new RouteCollection(); ...; return $collection;
  2. Fix methods that return $collection->all() or an iterator — return the RouteCollection itself
  3. Add a RouteCollection return type declaration to the method so PHP enforces the contract at runtime
  4. If the method legitimately has no routes, return an empty new RouteCollection() instead of null

Example fix

// before
public function loadRoutes(): array
{
    return $this->routes->all();
}
// after
public function loadRoutes(): RouteCollection
{
    return $this->routes;
}
Defensive patterns

Strategy: validation

Validate before calling

if (!$routes instanceof \Symfony\Component\Routing\RouteCollection) { throw new \LogicException('Loader must return a RouteCollection'); }

Type guard

function isRouteCollection(mixed $v): bool { return $v instanceof \Symfony\Component\Routing\RouteCollection; }

Try / catch

try { $collection = $loaderObject->load($resource, $env); } catch (\LogicException $e) { // log loader class/method, fix return type }

Prevention

When it happens

Trigger: A resource class's load(Object $resource, ?string $type = null) method (or a method named by the resource type) returns null early, returns an array instead of RouteCollection, or has a typo in return path that skips the return statement.

Common situations: Hand-written resource loader classes for annotation/attribute routing; a custom loader method accidentally returning $routes->all() (an array) or returning nothing on an early-exit branch; refactoring that changed the return type without updating all returns.

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


AI-assisted analysis of symfony/routing@83fa223250 (2026-09-14). Data as JSON: /api/errors/2b51bbe2420520d9. Report an issue: GitHub.

Appendix: source

Thrown at Loader/ObjectLoader.php:56

        if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) {
            throw new \InvalidArgumentException(\sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object'));
        }

        $parts = explode('::', $resource);
        $method = $parts[1] ?? '__invoke';

        $loaderObject = $this->getObject($parts[0]);

        if (!\is_callable([$loaderObject, $method])) {
            throw new \BadMethodCallException(\sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource));
        }

        $routeCollection = $loaderObject->$method($this, $this->env);

        if (!$routeCollection instanceof RouteCollection) {
            $type = get_debug_type($routeCollection);

            throw new \LogicException(\sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', get_debug_type($loaderObject), $method, $type));
        }

        // make the object file tracked so that if it changes, the cache rebuilds
        $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection);

        return $routeCollection;
    }

    private function addClassResource(\ReflectionClass $class, RouteCollection $collection): void
    {
        do {
            if (is_file($class->getFileName())) {
                $collection->addResource(new FileResource($class->getFileName()));
            }
        } while ($class = $class->getParentClass());
    }
}

View on GitHub (pinned to 83fa223250)