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 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
  • %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();
}