---
title: Bootstrap & Dispatcher
description: How a Nibiru request flows from index.php through the dispatcher to your controller.
---

A Nibiru request takes one trip through three files: `index.php`, `core/framework.php`, and `core/c/dispatcher.php`. Reading them in that order tells you everything.

## index.php

```php
<?php
require_once 'core/framework.php';
```

That's the whole file. `index.php` exists only to give the web server a target.

## core/framework.php

`framework.php` requires the framework's classes in dependency order — settings, registry, router, engine, autoloader, all DB drivers, all 28 form types, view, controller, modules, auth, debug, display — and finishes with:

```php
Nibiru\Dispatcher::getInstance()->run();
```

That single call is your application's heartbeat.

## Dispatcher::run()

The simplified flow:

```php
public function run() {
    date_default_timezone_set(Config::getInstance()->getConfig()
        [View::NIBIRU_SETTINGS]['timezone']);

    if (Config::getInstance()->getConfig()
        [self::CONFIG_GENERATOR_SECTION][self::GENERATOR_DATABASE]) {
        new Model(false);                          // 1. (re)generate models from schema
    }

    Router::getInstance()->route();                // 2. parse the URL
    Auto::loader()->loadModelFiles();              // 3. load model files
    Auto::loader()->loadModules();                 // 4. load module classes

    $tpl = Router::getInstance()->tplName();
    $controllerFile = __DIR__ . "/../../application/controller/{$tpl}Controller.php";

    if (is_file($controllerFile)) {                // 5. controller file exists
        require_once $controllerFile;
        $class = "Nibiru\\{$tpl}Controller";
        $controller = new $class();

        if (array_key_exists('_action', $_REQUEST)) {
            $action = $_REQUEST['_action'] . 'Action';
            $controller->navigationAction();
            if (method_exists($controller, $action)) {
                $controller->$action();            // 6. optional named action
            }
            $controller->pageAction();
        } else {
            $controller->navigationAction();
            $controller->pageAction();
        }

        Display::getInstance()->display();         // 7. render Smarty
    } else {
        // 8. soft 404 — render the configured error controller
    }
}
```

## The action sequence

Every request goes through the same three steps if `?_action=foo` is set:

1. `navigationAction()` — populate menus, breadcrumbs.
2. `fooAction()` — run the named action.
3. `pageAction()` — last chance to assign template data.

Without `_action`, only steps 1 and 3 run.

This means **stateless render-time logic belongs in `pageAction()`**, and **navigation data belongs in `navigationAction()`**, even if it feels like duplicating effort. Two controllers in the same project will both have a `navigationAction()`; that's correct.

## Soft 404

If the matched controller file doesn't exist, Nibiru renders a *soft 404* — it returns a 200 OK and renders the controller named in `[ENGINE] error.controller` (default: `error`). This is intentional: it lets you serve nice error pages without server-level configuration.

If you want a real `404 Not Found` HTTP code, set it in your error controller:

```php
public function pageAction() {
    http_response_code(404);
    View::assign(['title' => 'Lost in the void']);
}
```

## Auto::loader()

Two auto-loaders run before the controller is constructed:

- **`loadModelFiles()`** scans `application/model/` and includes every `.php` file there. Generated models are flat files, not namespaced packages, so this is a simple `require_once` loop.
- **`loadModules()`** walks `application/module/<name>/` and loads each module's main class plus its trait, plugin and interface files. The Registry indexes each module's settings INI at the same time.

Both use the configured paths in `[SETTINGS] modules.path` etc., so you can override them per environment.

## SEO URL handling

Before the controller resolution, `Router::route()` runs `handleSeoUrls()` which detects URLs of the form `/controller/<slug>/<id>` (where the second segment is *not* a known action and the third is numeric). Those get rewritten internally to `/controller/detail/` with `$_REQUEST['id']` and `$_REQUEST['slug']` populated. Read more in [Routing](/core/routing/).

## When to override the dispatcher

Almost never. The cleaner extension points are:

- **Custom error controller** — set `[ENGINE] error.controller`.
- **Pre-controller hooks** — load a module that registers an observer, then attach it inside `navigationAction()`.
- **Cron-style entry** — run the framework headless via the `nibiru` CLI rather than `index.php`.
