Request Lifecycle
Every HTTP request to Pubvana passes through a defined sequence of steps. Understanding this sequence is critical when building plugins that need to intercept, modify, or extend request handling.
Full Lifecycle Sequence
1. Apache receives HTTP request
2. mod_rewrite rewrites to public/index.php
3. public/index.php bootstraps CI4
4. CI4 fires pre_system event
5. Filters run (before phase)
6. Router matches route
7. Controller::method() executes
8. ThemeService renders view
9. Response sent to browser
Step 1–2: Apache and mod_rewrite
The vhost DocumentRoot is public/. Apache's .htaccess in public/ rewrites all requests to index.php. Only public/ is web-accessible — app/, plugins/, themes/, and writable/ are outside the document root.
Step 3: CI4 Bootstrap
public/index.php defines FCPATH, sets the ENVIRONMENT from $_SERVER['CI_ENVIRONMENT'] (or .env), and calls CodeIgniterCodeIgniter::run(). This bootstraps the framework: loads app/Config/, initialises the request and response objects, and fires events.
Step 4: pre_system Event
The most important hook for plugin developers. Registered in app/Config/Events.php:
Events::on('pre_system', static function () {
// 1. Boot all active plugins
service('pluginManager')->boot();
// 2. Sync locale from session
$locale = session('locale');
if ($locale && in_array($locale, config('App')->supportedLocales)) {
service('request')->setLocale($locale);
}
// 3. Populate supportedLocales from settings
config('App')->supportedLocales = setting('App.supportedLocales') ?? ['en'];
});
PluginManager::boot() at this stage:
- Queries the
pluginstable foris_active = 1records - Registers each plugin's PSR-4 namespace via
spl_autoload_register - Calls
Plugin::register()on each plugin (event hooks, SDK init) - Requires each plugin's
Config/Routes.phpinto the CI4 router
Because this runs before routing, plugin routes are available to the router as if they were core routes.
Step 5: Filters (Before Phase)
Global Filters
| Filter | Action |
|---|---|
honeypot | Adds honeypot field; rejects bots |
csrf | Validates CSRF token on POST/PUT/DELETE |
maintenance | If maintenance mode on, redirects non-admins to maintenance page |
session (Shield) | Authenticates session-based users |
Route-Specific Filters
Admin routes declare ['filter' => ['admin_auth', 'totp']]:
admin_auth— requires the user to be logged in and in the admin/superadmin group.totp— if 2FA is enabled site-wide, requires a completed TOTP challenge in session.
Plugin CSRF exemptions (returned from getCsrfExemptions()) are injected into the CSRF filter's except list during PluginManager::boot().
Step 6: Route Matching
CI4's router matches the request URI against defined routes. Route resolution order:
- Core routes defined in
app/Config/Routes.php - Plugin routes (loaded at pre_system into the same router instance)
- Auto-routing (disabled — Pubvana uses explicit routes only)
Step 7: Controller Execution
The matched controller is instantiated. Pubvana controllers extend either:
AppControllersBaseController— for public-facing pagesAppControllersAdminBaseAdminController— for admin panel pages
Step 8: View Rendering
Public Views (ThemeService)
return $this->themeService->view('home', $data);
Admin Views (CI4 view())
return view('Plugins\MyPlugin\Views\admin\index', $data);
Diagram
Browser
│
▼
Apache → mod_rewrite → public/index.php
│
▼
CI4 bootstrap (Config/, Services)
│
▼
pre_system event
├── PluginManager::boot()
│ ├── Register namespaces (spl_autoload_register)
│ ├── Plugin::register() × N
│ └── Load Config/Routes.php × N
├── Locale sync
└── supportedLocales population
│
▼
Filters (before): honeypot → csrf → maintenance → session → admin_auth → totp
│
▼
Router → Controller::method()
│
▼
ThemeService::view() or view() [admin]
│
▼
Filters (after)
│
▼
Response → Browser