Bootstrapping
*************
<div class=perex>
Bootstrapping is the process of initializing the application environment, creating a dependency injection (DI) container, and starting the application. We will discuss:
- how the Bootstrap class initializes the environment
- how applications are configured using NEON files
- how to distinguish between production and development mode
- how to create and configure the DI container
</div>
Applications, whether web-based or scripts run from the command line, begin their execution with some form of environment initialization. In the old days, a file named perhaps `include.inc.php` was responsible for this, included by the initial file. In modern Nette applications, it has been replaced by the `Bootstrap` class, which, as part of the application, can be found in the `app/Bootstrap.php` file. It might look like this, for example:
```php
namespace App;
use Nette;
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// The configurator is responsible for setting up the application environment and services.
$this->configurator = new Configurator;
// Set the directory for temporary files generated by Nette (e.g., compiled templates)
$this->configurator->setTempDirectory($this->rootDir . '/temp');
}
public function bootWebApplication(): Nette\DI\Container
{
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}
private function initializeEnvironment(): void
{
// Nette is smart, and the development mode turns on automatically,
// or you can enable it for a specific IP address by uncommenting the following line:
// $this->configurator->setDebugMode('secret@23.75.345.200');
// Enables Tracy: the ultimate "swiss army knife" debugging tool.
$this->configurator->enableTracy($this->rootDir . '/log');
// RobotLoader: automatically loads all classes in the chosen directory
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
}
private function setupContainer(): void
{
// Load configuration files
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
```
index.php
=========
In the case of web applications, the initial file is `index.php`, located in the [public directory |directory-structure#Public Directory www] `www/`. It instructs the Bootstrap class to initialize the environment and create the DI container. Then, it retrieves the `Application` service from the container, which runs the web application:
```php
$bootstrap = new App\Bootstrap;
// Initialize the environment + create a DI container
$container = $bootstrap->bootWebApplication();
// DI container creates a Nette\Application\Application object
$application = $container->getByType(Nette\Application\Application::class);
// Start the Nette application and process the incoming request
$application->run();
```
.[note]
The `$application` object emits [events |nette:glossary#Events] as it processes the request - `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown`, and `onError` (on an unhandled exception). You can attach handlers to them, which is handy for logging or application-wide monitoring.
As you can see, the [api:Nette\Bootstrap\Configurator] class helps with setting up the environment and creating the dependency injection (DI) container. We will now introduce it in more detail.
Development vs Production Mode
==============================
Nette behaves differently depending on whether it is running on a development or production server:
đ ď¸ Development Mode:
- Displays the Tracy debug bar with useful information (SQL queries, execution time, memory used)
- In case of an error, displays a detailed error page with function calls and variable contents
- Automatically refreshes the cache when Latte templates, configuration files, etc., are changed
đ Production Mode:
- Does not display any debugging information; all errors are written to the log
- In case of an error, displays an ErrorPresenter or a generic "Server Error" page
- Cache is never automatically refreshed!
- Optimized for speed and security
Mode selection is done by autodetection, so usually, there is no need to configure anything or manually switch modes:
- development mode: on localhost (IP address `127.0.0.1` or `::1`) if no proxy is present (i.e., its HTTP header is not detected)
- production mode: everywhere else
If we want to enable development mode in other cases, for example, for programmers accessing from a specific IP address, we use `setDebugMode()`:
```php
$this->configurator->setDebugMode('23.75.345.200'); // an array of IP addresses can also be provided
```
We strongly recommend combining an IP address with a cookie. Store a secret token, e.g., `secret1234`, in the `nette-debug` cookie, and this way, activate development mode for programmers accessing from a specific IP address who also have the mentioned token in their cookie:
```php
$this->configurator->setDebugMode('secret1234@23.75.345.200');
```
We can also disable development mode completely, even for localhost:
```php
$this->configurator->setDebugMode(false);
```
Note that the value `true` forces development mode on, which should **never** happen on a production server.
Autodetection is handled internally by the static method `Configurator::detectDebugMode()`, which you can also call yourself, for example to detect development mode outside the configurator. It accepts an optional whitelist of IP addresses or computer names and returns whether the current request should run in development mode:
```php
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
```
Debugging Tool Tracy
====================
For easy debugging, we will enable the excellent tool [Tracy |tracy:]. In development mode, it visualizes errors, and in production mode, it logs errors to the specified directory:
```php
$this->configurator->enableTracy($this->rootDir . '/log');
```
Temporary Files
===============
Nette uses cache for the DI container, RobotLoader, templates, etc. Therefore, it is necessary to set the path to the directory where the cache will be stored:
```php
$this->configurator->setTempDirectory($this->rootDir . '/temp');
```
On Linux or macOS, set [write permissions |nette:troubleshooting#Setting Directory Permissions] for the `log/` and `temp/` directories.
RobotLoader
===========
Usually, we will want to automatically load classes using [RobotLoader |robot-loader:], so we need to start it and let it load classes from the directory where `Bootstrap.php` is located (i.e., `__DIR__`), and all its subdirectories:
```php
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
```
An alternative approach is to load classes solely through [Composer |best-practices:composer] following PSR-4.
Timezone
========
You can set the default timezone via the configurator.
```php
$this->configurator->setTimeZone('Europe/Prague');
```
DI Container Configuration
==========================
Part of the booting process is the creation of the DI container, or object factory, which is the heart of the entire application. It is actually a PHP class generated by Nette and stored in the cache directory. The factory produces key application objects, and using configuration files, we instruct it how to create and set them up, thereby influencing the behavior of the entire application.
Configuration files are usually written in the [NEON format |neon:format]. In a separate chapter, you can read about [what can be configured |nette:configuring].
.[tip]
In development mode, the container is automatically updated whenever the code or configuration files change. In production mode, it is generated only once, and changes are not checked to maximize performance.
While `createContainer()` builds the container and returns its instance, the `loadContainer()` method returns only the name of the generated container class, which you can then instantiate yourself. This is useful in advanced scenarios.
Configuration files are loaded using `addConfig()`:
```php
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```
If we want to add more configuration files, we can call the `addConfig()` function multiple times.
```php
$configDir = $this->rootDir . '/config';
$this->configurator->addConfig($configDir . '/common.neon');
$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
$this->configurator->addConfig($configDir . '/cli.php');
}
```
The name `cli.php` is not a typo; configuration can also be written in a PHP file that returns it as an array.
We can also add other configuration files in [the `includes` section |dependency-injection:configuration#Including Files].
If items with the same keys appear in configuration files, they will be overwritten, or in the case of [arrays, merged |dependency-injection:configuration#Merging]. A file included later has higher priority than the previous one. The file in which the `includes` section is listed has higher priority than the files included within it.
Static Parameters
-----------------
Parameters used in configuration files can be defined [in the `parameters` section |dependency-injection:configuration#Parameters] and also passed (or overridden) using the `addStaticParameters()` method (whose older, now deprecated alias is `addParameters()`). It is important that different parameter values will cause the generation of additional DI containers, i.e., additional classes.
```php
$this->configurator->addStaticParameters([
'projectId' => 23,
]);
```
The `projectId` parameter can be referenced in the configuration using the standard notation `%projectId%`.
Dynamic Parameters
------------------
We can also add dynamic parameters to the container, whose different values, unlike static parameters, will not cause the generation of new DI containers.
```php
$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```
This way, we can easily add, for example, environment variables, which can then be referenced in the configuration using the notation `%env.variable%`.
```php
$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
```
Default Parameters
------------------
You can use these parameters in the configuration files:
- `%appDir%` is the absolute path to the directory containing the `Bootstrap.php` file
- `%wwwDir%` is the absolute path to the directory containing the entry file `index.php`
- `%tempDir%` is the absolute path to the directory for temporary files
- `%vendorDir%` is the absolute path to the directory where Composer installs libraries
- `%rootDir%` is the absolute path to the root directory of the project
- `%baseUrl%` is the absolute URL to the root directory (a dynamic parameter resolved at runtime; outside an HTTP request it is derived from [http: baseUrl |http:configuration#Application Base URL])
- `%debugMode%` indicates whether the application is in debug mode
- `%consoleMode%` indicates whether the request came through the command line
Imported Services
-----------------
Now we go deeper. Although the purpose of the DI container is to create objects, occasionally there might be a need to insert an existing object into the container. We do this by defining the service with the `imported: true` flag.
```neon
services:
myservice:
type: App\Model\MyCustomService
imported: true
```
And in the bootstrap, we insert the object into the container:
```php
$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
```
Different Environments
======================
Feel free to modify the `Bootstrap` class according to your needs. You can add parameters to the `bootWebApplication()` method to distinguish between web projects. Or you can add other methods, such as `bootTestEnvironment()` which initializes the environment for unit tests, `bootConsoleApplication()` for scripts called from the command line, etc.
```php
public function bootTestEnvironment(): Nette\DI\Container
{
Tester\Environment::setup(); // Nette Tester initialization
$this->setupContainer();
return $this->configurator->createContainer();
}
public function bootConsoleApplication(): Nette\DI\Container
{
$this->configurator->setDebugMode(false);
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}
```
{{maintitle: Bootstrapping a Nette App: From index.php to DI Container}}
{{description: See how a Nette application starts, from index.php and the Configurator class through development mode, Tracy and temporary files to the NEON configuration.}}
Bootstrapping
Bootstrapping is the process of initializing the application environment, creating a dependency injection (DI) container, and
starting the application. We will discuss:
- how the Bootstrap class initializes the environment
- how applications are configured using NEON files
- how to distinguish between production and development mode
- how to create and configure the DI container
Applications, whether web-based or scripts run from the command line, begin their execution with some form of environment
initialization. In the old days, a file named perhaps include.inc.php was responsible for this, included by the
initial file. In modern Nette applications, it has been replaced by the Bootstrap class, which, as part of the
application, can be found in the app/Bootstrap.php file. It might look like this, for example:
namespace App;
use Nette;
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// The configurator is responsible for setting up the application environment and services.
$this->configurator = new Configurator;
// Set the directory for temporary files generated by Nette (e.g., compiled templates)
$this->configurator->setTempDirectory($this->rootDir . '/temp');
}
public function bootWebApplication(): Nette\DI\Container
{
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}
private function initializeEnvironment(): void
{
// Nette is smart, and the development mode turns on automatically,
// or you can enable it for a specific IP address by uncommenting the following line:
// $this->configurator->setDebugMode('secret@23.75.345.200');
// Enables Tracy: the ultimate "swiss army knife" debugging tool.
$this->configurator->enableTracy($this->rootDir . '/log');
// RobotLoader: automatically loads all classes in the chosen directory
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
}
private function setupContainer(): void
{
// Load configuration files
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
index.php
In the case of web applications, the initial file is index.php, located in the public directory www/.
It instructs the Bootstrap class to initialize the environment and create the DI container. Then, it retrieves the
Application service from the container, which runs the web application:
$bootstrap = new App\Bootstrap;
// Initialize the environment + create a DI container
$container = $bootstrap->bootWebApplication();
// DI container creates a Nette\Application\Application object
$application = $container->getByType(Nette\Application\Application::class);
// Start the Nette application and process the incoming request
$application->run();
The $application object emits events as it
processes the request â onStartup, onRequest, onPresenter, onResponse,
onShutdown, and onError (on an unhandled exception). You can attach handlers to them, which is handy for
logging or application-wide monitoring.
As you can see, the Nette\Bootstrap\Configurator class helps with
setting up the environment and creating the dependency injection (DI) container. We will now introduce it in more detail.
Development vs Production Mode
Nette behaves differently depending on whether it is running on a development or production server:
- đ ď¸ Development Mode
- Displays the Tracy debug bar with useful information (SQL queries, execution time, memory used)
- In case of an error, displays a detailed error page with function calls and variable contents
- Automatically refreshes the cache when Latte templates, configuration files, etc., are changed
- đ Production Mode
- Does not display any debugging information; all errors are written to the log
- In case of an error, displays an ErrorPresenter or a generic âServer Errorâ page
- Cache is never automatically refreshed!
- Optimized for speed and security
Mode selection is done by autodetection, so usually, there is no need to configure anything or manually switch modes:
- development mode: on localhost (IP address
127.0.0.1 or ::1) if no proxy is present (i.e., its HTTP
header is not detected)
- production mode: everywhere else
If we want to enable development mode in other cases, for example, for programmers accessing from a specific IP address, we use
setDebugMode():
$this->configurator->setDebugMode('23.75.345.200'); // an array of IP addresses can also be provided
We strongly recommend combining an IP address with a cookie. Store a secret token, e.g., secret1234, in the
nette-debug cookie, and this way, activate development mode for programmers accessing from a specific IP address who
also have the mentioned token in their cookie:
$this->configurator->setDebugMode('secret1234@23.75.345.200');
We can also disable development mode completely, even for localhost:
$this->configurator->setDebugMode(false);
Note that the value true forces development mode on, which should never happen on a production server.
Autodetection is handled internally by the static method Configurator::detectDebugMode(), which you can also call
yourself, for example to detect development mode outside the configurator. It accepts an optional whitelist of IP addresses or
computer names and returns whether the current request should run in development mode:
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
For easy debugging, we will enable the excellent tool Tracy. In development mode, it
visualizes errors, and in production mode, it logs errors to the specified directory:
$this->configurator->enableTracy($this->rootDir . '/log');
Temporary Files
Nette uses cache for the DI container, RobotLoader, templates, etc. Therefore, it is necessary to set the path to the directory
where the cache will be stored:
$this->configurator->setTempDirectory($this->rootDir . '/temp');
On Linux or macOS, set write
permissions for the log/ and temp/ directories.
RobotLoader
Usually, we will want to automatically load classes using RobotLoader, so
we need to start it and let it load classes from the directory where Bootstrap.php is located (i.e.,
__DIR__), and all its subdirectories:
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
An alternative approach is to load classes solely through Composer following PSR-4.
Timezone
You can set the default timezone via the configurator.
$this->configurator->setTimeZone('Europe/Prague');
DI Container Configuration
Part of the booting process is the creation of the DI container, or object factory, which is the heart of the entire
application. It is actually a PHP class generated by Nette and stored in the cache directory. The factory produces key application
objects, and using configuration files, we instruct it how to create and set them up, thereby influencing the behavior of the
entire application.
Configuration files are usually written in the NEON format. In a separate
chapter, you can read about what can be configured.
In development mode, the container is automatically updated whenever the code or configuration files change. In
production mode, it is generated only once, and changes are not checked to maximize performance.
While createContainer() builds the container and returns its instance, the loadContainer() method
returns only the name of the generated container class, which you can then instantiate yourself. This is useful in advanced
scenarios.
Configuration files are loaded using addConfig():
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
If we want to add more configuration files, we can call the addConfig() function multiple times.
$configDir = $this->rootDir . '/config';
$this->configurator->addConfig($configDir . '/common.neon');
$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
$this->configurator->addConfig($configDir . '/cli.php');
}
The name cli.php is not a typo; configuration can also be written in a PHP file that returns it as an array.
We can also add other configuration files in the includes section.
If items with the same keys appear in configuration files, they will be overwritten, or in the case of arrays, merged. AÂ file included later has
higher priority than the previous one. The file in which the includes section is listed has higher priority than the
files included within it.
Static Parameters
Parameters used in configuration files can be defined in the parameters section and
also passed (or overridden) using the addStaticParameters() method (whose older, now deprecated alias is
addParameters()). It is important that different parameter values will cause the generation of additional DI
containers, i.e., additional classes.
$this->configurator->addStaticParameters([
'projectId' => 23,
]);
The projectId parameter can be referenced in the configuration using the standard notation
%projectId%.
Dynamic Parameters
We can also add dynamic parameters to the container, whose different values, unlike static parameters, will not cause the
generation of new DI containers.
$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
This way, we can easily add, for example, environment variables, which can then be referenced in the configuration using the
notation %env.variable%.
$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
Default Parameters
You can use these parameters in the configuration files:
%appDir% is the absolute path to the directory containing the Bootstrap.php file
%wwwDir% is the absolute path to the directory containing the entry file index.php
%tempDir% is the absolute path to the directory for temporary files
%vendorDir% is the absolute path to the directory where Composer installs libraries
%rootDir% is the absolute path to the root directory of the project
%baseUrl% is the absolute URL to the root directory (a dynamic parameter resolved at runtime; outside an HTTP
request it is derived from http: baseUrl)
%debugMode% indicates whether the application is in debug mode
%consoleMode% indicates whether the request came through the command line
Imported Services
Now we go deeper. Although the purpose of the DI container is to create objects, occasionally there might be a need to insert
an existing object into the container. We do this by defining the service with the imported: true flag.
services:
myservice:
type: App\Model\MyCustomService
imported: true
And in the bootstrap, we insert the object into the container:
$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
Different Environments
Feel free to modify the Bootstrap class according to your needs. You can add parameters to the
bootWebApplication() method to distinguish between web projects. Or you can add other methods, such as
bootTestEnvironment() which initializes the environment for unit tests, bootConsoleApplication() for
scripts called from the command line, etc.
public function bootTestEnvironment(): Nette\DI\Container
{
Tester\Environment::setup(); // Nette Tester initialization
$this->setupContainer();
return $this->configurator->createContainer();
}
public function bootConsoleApplication(): Nette\DI\Container
{
$this->configurator->setDebugMode(false);
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}