Bootstrap and Autoloading
How a class you never require becomes available, and why a misplaced file is a class that does not exist.
Overview
Application code uses no Composer autoloader. Startup registers one loader that maps a class name to a directory, so the rule is positional. A class is found by where its file sits and what it is called.
A new file in the right place needs no registration, and the same file one directory over is invisible. A wrong mapping gives a class-not-found error that looks like a typo and is a location.
Reference
Namespaced Classes
A class under the root namespace resolves through a fixed map. The prefix picks the directory, the class name picks the file. Two file names are tried: as written, then lower case.
| Namespace | Directory |
|---|---|
WISECP\Components\{Name} | coremio/components/{Name}.php |
WISECP\Operations\{Name} | coremio/operations/{Name}.php |
WISECP\AdminComponents\{Name} | templates/admin/components/{Name}.php |
WISECP\CronJobs\{Name} | coremio/cronjobs/{Name}.php |
WISECP\Api\{Sub}\{Name} | coremio/api/{Sub}/{Name}.php |
WISECP\Modules\{Type}\{Name} | coremio/modules/{Type}/{Name}/{Name}.php |
The module branch is not a fixed list. Startup registers a namespace for every type directory it finds, so a new module type autoloads as soon as its directory exists. The API branch is the one exception to the flat map: it follows sub-namespaces into sub-directories.
The Startup Object
The startup class registers the loader, and the root bootstrap file constructs it. What follows is one object with four public methods.
public static ?self $init = null; // the running instance
public string $target; // the requested address, normalised
public array $route; // its segments
public array $routes; // the active route table
public \Controllers $controller; // the controller object, once built
public \Router $router; // the Router instance
public static ?\Language $lang = null; // the Language instance
public function __construct(); // registers the autoloader, config, session, errors
public function initRouter(): void; // builds the route table; done for you on the web
public function getAddress(bool $prefix = false): string; // the site address, optionally language-prefixed
public function run(): bool; // resolves the address and runs the controller
index.php.
APP_URI. Pass true for the address with the active language prefix.
Classes Without a Namespace
Core classes and helpers carry no namespace. Three directories are searched in order and the first hit wins. A module has its own namespace, so it reaches them with a leading backslash.
Controllers and Models Are Not Autoloaded
Their lowercase namespaces mirror their directory but are absent from the map above. Routing includes the controller file by path, and the controller's constructor includes the model file by name. On the web the difference is invisible. In a command line script a model has to be required before it can be constructed.
// \WISECP\controllers\admin\{name} -> coremio/controllers/admin/{name}.php
// \WISECP\models\admin\{name} -> coremio/models/admin/{name}.php
// Neither is in the autoload map, so class_exists() alone answers false here.
require_once CORE_DIR . 'models' . DS . 'website' . DS . 'products.php';
$model = new \WISECP\models\website\products();
Constants You Will See
| Constant | Value | Declared in |
|---|---|---|
ROOT_DIR | The installation directory, with a trailing separator | bootstrap.php |
CORE_DIR | ROOT_DIR . "coremio" . DS | bootstrap.php |
CLASS_DIR, HELPER_DIR, COMPONENTS_DIR, OPERATIONS_DIR | CORE_DIR plus classes, helpers, components, operations | bootstrap.php |
MODULE_DIR, CONTROLLER_DIR, MODEL_DIR, LANG_DIR | CORE_DIR plus modules, controllers, models, locale | bootstrap.php |
CONFIG_DIR, STORAGE_DIR, CACHE_DIR, LOG_DIR | Configuration, and storage with cache and logs beneath it | bootstrap.php |
TEMPLATE_DIR | ROOT_DIR . "templates" . DS | bootstrap.php |
DS | DIRECTORY_SEPARATOR. Join paths with it, not with a literal slash | bootstrap.php |
CRON | true, defined when the interpreter is the command line | bootstrap.php |
APP_URI | The site address, taken from the startup object | bootstrap.php |
ADMINISTRATOR | The resolved admin directory name, a string, not a boolean. Defined only while the panel is served, so shared code tests it with defined() | coremio/init.php |
DEMO_MODE, DEVELOPMENT, ERROR_DEBUG | The debug settings: demo, developer, error | coremio/init.php |
LOG_SAVE, LOG_SAVE_MODULE | The logging settings: general and per module | coremio/init.php |
Example
A command line script starts the same way a request does.
// The command line is detected during startup; nothing has to be declared first.
require __DIR__ . '/bootstrap.php';
// Namespaced: resolved through the map above.
$table = new \WISECP\Components\Table("widgetList");
// No namespace: resolved from the class, helper and component directories.
$rate = Money::exChange(100, 'USD', 'EUR');
// From inside a module, the same core classes need a leading backslash
// because the module's own namespace would be searched first.
$module = \Modules::getInstance('Currency', 'AcmeRates');
Keeping a Script Off the Web
A script inside the installation directory is also a URL: anyone who guesses the file name can trigger it. A script meant for a shell refuses everything else before it loads the application.
// Answer 404 in any web context, and do it before the application is loaded.
if (PHP_SAPI !== 'cli'
|| !defined('STDIN')
|| isset($_SERVER['REQUEST_METHOD'])
|| isset($_SERVER['HTTP_HOST'])
|| isset($_SERVER['REMOTE_ADDR'])
|| isset($_SERVER['SERVER_SOFTWARE'])) {
if (!headers_sent()) {
@http_response_code(404);
@header('Content-Type: text/plain; charset=utf-8');
@header('Cache-Control: no-store');
}
exit;
}
require __DIR__ . '/bootstrap.php';
Each part of the guard is deliberate. Interpreter mode alone is not trusted: a misconfigured server can run a file through a command line binary. Standard input must exist, and any web-server variable refuses the run. The answer is 404, not 403, so a probe cannot confirm the file exists. The guard sits above the require, so a refused request loads nothing.
A file that is only included needs a different guard: refuse when the application's constants are absent, as templates do. Neither guard fits a script that must stay reachable over the web. A cron entry point triggered by URL is one. It needs a secret in the address and a check on the other side.
Pitfalls
Directory, file and class have to agree; the loader tries the lower-case file name as a fallback. A mismatch can work on a case-insensitive filesystem and fail on the server.
Starting only the initialiser gives a partly built application with no helpers. Require the root bootstrap file, which runs the whole startup.
Booting from a shell sets the non-request flag, so a script declares nothing. The flag sends a startup failure to standard error and exits non-zero. It also keeps the theme out of a generated view and short-circuits the consent layer. Those answers suit a script and not a page. Older scripts that declare the flag themselves are unaffected.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.