The Kernel class is Core's execution layer. It handles two related but distinct responsibilities: loading PHP files, and instantiating and invoking controllers. This page is aimed at developers who want to understand what happens beneath Route and App, or who are extending Core itself.
Most applications never call
Kerneldirectly — it is used internally byRouteandApp. This page is reference material.
Earlier versions of Webrium had controller-dispatching logic inside the File class, alongside filesystem utilities like reading, writing, and streaming. That mixed two unrelated responsibilities into a single class.
The framework now separates these concerns:
File— pure filesystem I/O (reading, writing, streaming, metadata)Kernel— PHP file execution and controller dispatchingRoute— URL matching and request routing
Each class stays focused on a single responsibility, and the controller dispatch flow can be followed and tested independently of file operations.
These methods wrap PHP's native include / require constructs with an existence check, returning a boolean instead of triggering a warning when a file is missing.
use Webrium\Kernel;
Kernel::run($path); // include
Kernel::runOnce($path); // include_once
Kernel::requireFile($path); // require
Kernel::requireOnce($path); // require_onceAll four return true if the file existed and was loaded, or false otherwise.
Loads multiple PHP files from a registered directory alias in one call. It is used during application bootstrap to pull in configuration files (or any other PHP file that needs to be executed at boot time) — not for route files, which have their own dedicated Route::source() method.
For example, the default application skeleton uses it in public/index.php to load the database configuration:
Kernel::source('config', ['DB.php']);It returns the number of files that were successfully included.
This is the heart of the Kernel — it takes a fully-qualified class name and a method name, and runs the full controller lifecycle.
Kernel::executeControllerMethod(
string $className, // e.g. App\Controllers\UserController
string $methodName, // e.g. 'index'
array $params = [] // route parameters, passed positionally
): void;- Class resolution — checks
class_exists($className). If the class is not found, an error is reported throughDebugand execution stops. - Instantiation — creates a new instance of the controller:
new $className(). boot()hook — if the controller defines aboot()method, it is called before the action.- Argument resolution — route parameters are matched against the method signature and scalar values are coerced into the declared types (see below).
- Method dispatch — if the target method exists, it is called with the resolved arguments and its return value is passed to
Header::respond(). teardown()hook — if the controller defines ateardown()method, it is called after the response has been sent.
executeControllerMethod()
│
├─► class_exists? ──No──► Debug::triggerError() ──► return
│
├─► new $className()
│
├─► boot() if defined
│
├─► method_exists($methodName)?
│ │
│ ├─Yes─► resolve arguments ──► Header::respond($controller->$methodName(...$args))
│ │
│ └─No──► Debug::triggerError()
│
└─► teardown() if defined
Route parameters always arrive as strings from the URL. To let controllers declare native scalar types — even under declare(strict_types=1) — the Kernel inspects the target method's signature via reflection and coerces each positional value to the declared type.
class PostController
{
// $id arrives as the string "42" from the URL,
// and is cast to int before the method is called.
public function show(int $id) { /* ... */ }
}Rules:
- Only built-in scalar types are coerced:
int,float,bool,string. - Untyped, union-typed, and class-typed parameters are passed through unchanged.
- A non-numeric value bound to
intorfloatis passed through unchanged so PHP raises a properTypeErrorinstead of silently becoming0. boolvalues are parsed withFILTER_VALIDATE_BOOLEAN; unrecognized values are passed through unchanged.
This makes typed controller signatures work naturally without per-route casting boilerplate.
Kernel::executeControllerMethod() deliberately knows nothing about directory structure or namespaces — it only accepts a fully-qualified class name. Building that name is Route's responsibility:
// String syntax: 'UserController@index'
// → Route builds: App\Controllers\UserController
[$shortClass, $method] = explode('@', $handlerString, 2);
$fqcn = 'App\\Controllers\\' . $shortClass;
Kernel::executeControllerMethod($fqcn, $method, $params);// Array syntax: [UserController::class, 'index']
// → the FQCN is already complete and is passed through directly
Kernel::executeControllerMethod($handler[0], $handler[1], $params);This separation means Kernel has no dependency on Directory and makes no assumption about where controllers live — it simply runs whatever class and method it is given. If you ever need controllers in a different namespace or directory, that logic lives in Route, not in Kernel.
App::run()
│
├─► Debug::initialize()
│
└─► Route::run()
│
├─► matches route?
│
├─► middleware passes?
│
└─► dispatch()
│
├─► Closure ──────► Header::respond()
│
└─► Controller ──► Kernel::executeControllerMethod()