Bootstrap
Bootstrap is the bootstrapping code that initializes the environment, creates a dependency injection (DI) container, and starts the application. We will discuss:
- how it is configured using NEON files
- how to distinguish between production and development mode
- how to create 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:
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();
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.
Debugging Tool Tracy
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.
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 (it has an alias 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 static parameters in the configuration files:
%appDir%
is the absolute path to the directory containing theBootstrap.php
file%wwwDir%
is the absolute path to the directory containing the entry fileindex.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%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 we 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();
}