The framework boots through PHP_SF\System\Kernel — a configuration and wiring class that registers controllers, translation files, templates, and the application user class before handing off to the router. This page covers what happens during boot, in what order, and how to configure it.
There are three places where the kernel boots, each for a different context:
| File | Context |
|---|---|
public/index.php |
HTTP requests |
bin/console |
Symfony console commands |
tests/bootstrap.php |
PHPUnit and Codeception test suites |
All three follow the same wiring pattern — the differences are only in what happens after the framework kernel is configured.
This is the main entry point for every HTTP request. The full boot sequence is:
// 1. Autoloader
require_once __DIR__ . '/../vendor/autoload.php';
// 2. Global helper functions
require_once __DIR__ . '/../functions/functions.php';
// 3. Application constants
require_once __DIR__ . '/../config/constants.php';
// 4. Symfony debug handler (only when DEV_MODE = true)
if ( DEV_MODE )
Debug::enable();
// 5. Event listeners registration
require_once __DIR__ . '/../config/eventListeners.php';
// 6. Environment variables
( new Dotenv )->bootEnv( __DIR__ . '/../.env' );
// 7. Framework kernel — wire everything together
$kernel = ( new PHP_SF\Kernel )
->addTranslationFiles( __DIR__ . '/../lang' )
->addControllers( __DIR__ . '/../App/Http/Controller' )
->setHeaderTemplateClassName( header::class )
->setFooterTemplateClassName( footer::class )
->setApplicationUserClassName( User::class )
->addTemplatesDirectory( 'templates', 'App\View' );
// 8. Restore error handlers after kernel boot
// (only in test context — not in index.php)
// 9. Attempt to log in user from session
auth::logInUser();
// 10. Boot router — finds route and dispatches, or falls through
Router::init( $kernel );
// 11. If no framework route matched, boot Symfony kernel
Kernel::addRoutesToSymfony();
require_once __DIR__ . '/../autoload_runtime.php';
Steps 1–6 happen before the framework kernel is instantiated. Constants and helper functions must be available before anything else because the kernel itself uses them internally.
If Router::init() finds a matching route, it dispatches the request and calls exit() — steps 11 and beyond never execute. Only unmatched requests fall through to Symfony. See Symfony Fallback for details.
PHP_SF\System\Kernel is configured via a fluent builder interface. All methods return $this so they can be chained.
addControllers(string $path): self->addControllers( __DIR__ . '/../App/Http/Controller' )
Registers a directory to scan for controller classes. The router will recursively scan this directory for PHP files containing classes with #[Route] attributes.
You can call this multiple times to register controllers from multiple directories:
$kernel = ( new PHP_SF\Kernel )
->addControllers( __DIR__ . '/../App/Http/Controller' )
->addControllers( __DIR__ . '/../App/Http/AdminController' );
Throws DirectoryNotFoundException if the path doesn't exist.
addTranslationFiles(string $path): self->addTranslationFiles( __DIR__ . '/../lang' )
Registers a directory containing locale files. The directory must contain PHP files named after locale keys — e.g. en.php, pl.php, uk.php — each returning an associative array of translation strings.
The Translator will load all files for every locale in LANGUAGES_LIST from every registered directory, merging them. This allows the framework's own translation strings and your application's strings to coexist in separate directories:
$kernel = ( new PHP_SF\Kernel )
->addTranslationFiles( __DIR__ . '/../lang' )
->addTranslationFiles( __DIR__ . '/../vendor/nations-original/php-simple-framework/lang' );
Throws DirectoryNotFoundException if the path doesn't exist.
setApplicationUserClassName(string $className): self->setApplicationUserClassName( User::class )
Registers the application's user entity class. This must implement PHP_SF\System\Interface\UserInterface. The framework uses this class in:
auth middleware — to verify authentication and load the current userauth::logInUser() — to load the user from session on each requestKernel::getApplicationUserClassName() — used internally when the framework needs to instantiate or query the userThrows InvalidConfigurationException if the class doesn't exist. This method must be called — the framework will throw if getApplicationUserClassName() is called before it is set.
setHeaderTemplateClassName(string $className): self->setHeaderTemplateClassName( header::class )
Sets the view class rendered before every non-API response. Defaults to PHP_SF\Templates\Layout\header. Must extend AbstractView.
Can be overridden at runtime from within a middleware using $this->changeHeaderTemplateClassName() — useful for rendering pages with no header (e.g. the blank middleware in the template project swaps both header and footer to an empty view for the welcome page).
setFooterTemplateClassName(string $className): self->setFooterTemplateClassName( footer::class )
Sets the view class rendered after every non-API response. Defaults to PHP_SF\Templates\Layout\footer. Same rules as the header.
addTemplatesDirectory(string $directory, string $namespace): self->addTemplatesDirectory( 'templates', 'App\View' )
Registers a templates directory and its PSR-4 namespace for the template cache system. When TEMPLATES_CACHE_ENABLED = true, the TemplatesCache uses these mappings to locate source files, compile them, and cache the result under a unified namespace.
The directory path is relative to the project root. The namespace must match the PSR-4 namespace declared in the template files.
Event listeners must be registered before the kernel boots. This is handled in config/eventListeners.php, which is loaded explicitly in public/index.php and tests/bootstrap.php before the kernel is instantiated:
// config/eventListeners.php
PHP_SF\System\Core\MiddlewareEventDispatcher::addEventListeners(
\App\EventListeners\ExampleEventListener::class,
);
addEventListeners() accepts any number of class names. Each class must extend AbstractEventListener. The listeners registered here will be dispatched when their associated middleware executes.
If you add a new event listener and it isn't firing, the most likely cause is that it wasn't registered in this file. See Events & Listeners for how to write listeners.
The console entry point wires the framework kernel the same way as public/index.php, then boots a Symfony Application on top:
// bin/console (simplified)
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../functions/functions.php';
require_once __DIR__ . '/../config/constants.php';
( new Dotenv )->bootEnv( __DIR__ . '/../.env' );
$kernel = ( new PHP_SF\Kernel() )
->addTranslationFiles( __DIR__ . '/../lang' )
->addControllers( __DIR__ . '/../App/Http/Controller' )
->setApplicationUserClassName( User::class );
return function () {
$kernel = Kernel::getInstance();
$kernel->boot();
return new Application( $kernel );
};
The framework kernel is booted so that em(), ca(), translation functions, and other framework helpers are available inside console commands. The Symfony Application then takes over and runs the requested command.
The test bootstrap follows the same pattern with two key additions — it calls Router::loadRoutesOnly( $kernel ) to pre-populate the PHP_SF route list (needed by PhpSfRouteLoader for Codeception functional tests), and it sets APP_ENV defaults before bootEnv() so Codeception (which doesn't pre-set APP_ENV) correctly loads .env.test:
// PHPUnit sets APP_ENV=test via phpunit.xml.dist <server> before this runs.
// Codeception does not, so we default it here so bootEnv('.env') picks up .env.test.
$_SERVER['APP_ENV'] ??= 'test';
$_ENV['APP_ENV'] ??= 'test';
( new Dotenv() )->bootEnv( __DIR__ . '/../.env' );
$kernel = ( new PHP_SF\Kernel() )
->addTranslationFiles( __DIR__ . '/../lang' )
->addControllers( __DIR__ . '/../App/Http/Controller' )
->setHeaderTemplateClassName( header::class )
->setFooterTemplateClassName( footer::class )
->setApplicationUserClassName( User::class )
->addTemplatesDirectory( 'templates', 'App\View' );
Router::loadRoutesOnly( $kernel );
auth::logInUser();
$GLOBALS['kernel'] = $kernel;
The kernel instance is stored in $GLOBALS['kernel'] so it's accessible in test classes that need it — for example the MiddlewaresExecutorTest retrieves it via $GLOBALS['kernel'].
Every entry point calls auth::logInUser() with no arguments immediately after the kernel boots. This attempts to restore the authenticated user from the current session:
auth::logInUser();
When called with no arguments it checks the session for session_user_id, and if found, loads the corresponding user entity from the database and stores it in auth::$user. If no session exists or the user isn't found, it does nothing — the request continues as unauthenticated.
This must happen after the kernel boots because it needs em() (the entity manager) to load the user, which in turn requires the Doctrine connection to be available.
Once booted, the Symfony kernel instance is available anywhere via:
App\Kernel::getInstance()
This returns the same instance that was created during boot. It's used internally by the framework to access the Symfony container, router, and other Symfony services when needed — for example routeLink() falls back to the Symfony router for routes not in the framework's own route list.
Missing setApplicationUserClassName() — the framework throws InvalidConfigurationException with a clear message if this isn't set before auth middleware or auth::logInUser() is called.
Event listeners not registered — if a listener isn't in config/eventListeners.php it will never fire, with no error. This is the first thing to check when a listener appears to do nothing.
Loading order in public/index.php — constants must be loaded before the framework kernel, and the framework kernel must boot before Router::init(). Changing the load order will cause undefined constant errors or missing route registrations.