Request Lifecycle
Introduction
Nothing in the plugin runs on its own — WordPress calls it through hooks. Knowing which hook owns which stage tells you where new code belongs.
Boot sequence
Entry file — plugin headers and the ABSPATH guard, then it requires backend/bootstrap.php. No logic lives here.
bootstrap.php — checks vendor/autoload.php exists (and shows an admin notice if not), loads it, reads .env, then calls Plugin::load().
Plugin — a singleton. Its constructor registers the installer and defers everything else to plugins_loaded, because WordPress isn't ready earlier.
Providers — registered on init at priority 8, before the default 10, so routes exist by the time WordPress dispatches AJAX or REST.
Request dispatch
HookProvider builds a router only for the transport actually in play, so an AJAX request never parses REST routes:
$router = new Router(RequestType::AJAX, Config::VAR_PREFIX, '');
$router->setMiddlewares(Plugin::instance()->middlewares());
include $this->_pluginBackend . 'hooks' . DIRECTORY_SEPARATOR . 'ajax.php';
$router->register();
Including the route file is what registers routes: each Route:: call adds to the router, and register() binds them to WordPress hooks — wp_ajax_* actions for AJAX, register_rest_route() for REST.
Once a route matches, RouteRegister runs the middleware stack, resolves the controller method's arguments by reflection — the Request, then route parameters by name — calls it, and wraps whatever comes back into the response envelope.
Where to add code
| You want to | Add it in |
|---|---|
| A new endpoint | backend/hooks/ajax.php or api.php, plus a controller |
| A check across many routes | A middleware, registered in Plugin::middlewares() |
| A WordPress hook | A provider under app/Providers |
| A table | A migration under backend/db/Migrations |
| Logic too big for a controller | A service under app/Services |
registerProviders() runs on init. Code that must run earlier — rewrite rules, the OAuth callback handler — is wired in the Plugin constructor instead, guarded so it only runs for non-AJAX, non-REST, non-cron requests.