cakephp/cakephp · error · RuntimeException

Controller must be set first.

Error message

Controller must be set first.

What it means

ComponentRegistry::getController() is a typed accessor that throws RuntimeException when the registry's controller has never been assigned via setController(). Components are created against a controller, so using the registry before that wiring is a programming-order bug.

Solutions

  1. Ensure setController() is called before any code reads getController()
  2. Only access the controller in Component::initialize()/later lifecycle hooks, not in the constructor
  3. If using the registry standalone, inject a controller instance first
  4. In tests, construct the Controller and call setController() before creating components

Example fix

// before
$registry = new ComponentRegistry();
$registry->getController(); // throws
// after
$controller = new ArticlesController($request, $response);
$registry = new ComponentRegistry($controller);
$registry->getController();
Defensive patterns

Strategy: type-guard

Validate before calling

if ($registry->getController() === null) { /* older API */ }
// prefer checking before use in component initialize():
if (!$this->getController()) { return; }

Type guard

function hasController(ComponentRegistry $r): bool {
    try { $r->getController(); return true; } catch (\RuntimeException $e) { return false; }
}

Try / catch

try {
    $controller = $registry->getController();
} catch (\RuntimeException $e) {
    return; // not in a controller context (shell/test)
}

Prevention

When it happens

Trigger: Accessing getController() from a component or registry consumer during construction/bootstrap before setController() was called, or using a ComponentRegistry created manually without a controller.

Common situations: Using components outside a controller context (shell/console, tests instantiating ComponentRegistry directly), event handlers firing before the controller is attached.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of cakephp/cakephp@1128eba9b0 (2026-09-12). Data as JSON: /api/errors/40b212c2fc806ae7. Report an issue: GitHub.

Appendix: source

Thrown at src/Controller/ComponentRegistry.php:101

     * @return $this
     */
    public function setController(Controller $controller)
    {
        $this->_Controller = $controller;
        $this->setEventManager($controller->getEventManager());

        return $this;
    }

    /**
     * Get the controller associated with the collection.
     *
     * @return \Cake\Controller\Controller Controller instance.
     */
    public function getController(): Controller
    {
        if ($this->_Controller === null) {
            throw new RuntimeException('Controller must be set first.');
        }

        return $this->_Controller;
    }

    /**
     * Resolve a component classname.
     *
     * Part of the template method for {@link \Cake\Core\ObjectRegistry::load()}.
     *
     * @param string $class Partial classname to resolve.
     * @return class-string<\Cake\Controller\Component>|null Either the correct class name or null.
     */
    protected function _resolveClassName(string $class): ?string
    {
        /** @var class-string<\Cake\Controller\Component>|null */
        return App::className($class, 'Controller/Component', 'Component');
    }

View on GitHub (pinned to 1128eba9b0)