Bootstrapping

El bootstrapping es el proceso de inicializar el entorno de la aplicación, crear el contenedor de inyección de dependencias (DI) y arrancar la aplicación. Hablaremos de:

  • cómo inicializa el entorno la clase Bootstrap
  • cómo se configuran las aplicaciones con archivos NEON
  • cómo distinguir entre el modo de producción y el de desarrollo
  • cómo crear y configurar el contenedor DI

Las aplicaciones, ya sean web o scripts ejecutados desde la línea de comandos, empiezan su ejecución con alguna forma de inicialización del entorno. Antaño se encargaba de ello un archivo llamado quizá include.inc.php, incluido por el archivo inicial. En las aplicaciones modernas de Nette lo ha sustituido la clase Bootstrap que, como parte de la aplicación, se encuentra en el archivo app/Bootstrap.php. Podría tener, por ejemplo, este aspecto:

namespace App;

use Nette;
use Nette\Bootstrap\Configurator;

class Bootstrap
{
	private Configurator $configurator;
	private string $rootDir;

	public function __construct()
	{
		$this->rootDir = dirname(__DIR__);
		// El configurador se encarga de preparar el entorno y los servicios de la aplicación.
		$this->configurator = new Configurator;
		// Establece el directorio de los archivos temporales que genera Nette (p. ej. las plantillas compiladas)
		$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 es listo y el modo de desarrollo se activa automáticamente,
		// o puede activarlo para una dirección IP concreta descomentando la línea siguiente:
		// $this->configurator->setDebugMode('secret@23.75.345.200');

		// Activa Tracy: la navaja suiza definitiva para depurar.
		$this->configurator->enableTracy($this->rootDir . '/log');

		// RobotLoader: carga automáticamente todas las clases del directorio elegido
		$this->configurator->createRobotLoader()
			->addDirectory(__DIR__)
			->register();
	}

	private function setupContainer(): void
	{
		// Carga los archivos de configuración
		$this->configurator->addConfig($this->rootDir . '/config/common.neon');
	}
}

index.php

En las aplicaciones web, el archivo inicial es index.php, situado en el directorio público www/. Le pide a la clase Bootstrap que inicialice el entorno y cree el contenedor DI. Después obtiene del contenedor el servicio Application, que ejecuta la aplicación web:

$bootstrap = new App\Bootstrap;
// Inicializa el entorno + crea un contenedor DI
$container = $bootstrap->bootWebApplication();
// El contenedor DI crea un objeto Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// Arranca la aplicación Nette y procesa la petición entrante
$application->run();

El objeto $application emite eventos mientras atiende la petición: onStartup, onRequest, onPresenter, onResponse, onShutdown y onError (ante una excepción no capturada). Puede engancharles manejadores, algo práctico para registrar o para supervisar toda la aplicación.

Como ve, la clase Nette\Bootstrap\Configurator ayuda a configurar el entorno y a crear el contenedor de inyección de dependencias (DI). La presentaremos ahora en detalle.

Modo de desarrollo y modo de producción

Nette se comporta de forma distinta según se ejecute en un servidor de desarrollo o de producción:

🛠️ Modo de desarrollo
Muestra la barra de depuración de Tracy con información útil (consultas SQL, tiempo de ejecución, memoria usada)
Ante un error, muestra una página de error detallada con las llamadas a funciones y el contenido de las variables
Refresca automáticamente la caché al cambiar las plantillas de Latte, los archivos de configuración, etc.
🚀 Modo de producción
No muestra ninguna información de depuración; todos los errores se escriben en el registro
Ante un error, muestra un ErrorPresenter o una página genérica de “Server Error”
¡La caché no se refresca nunca automáticamente!
Está optimizado para la velocidad y la seguridad

El modo se elige por autodetección, así que normalmente no hace falta configurar nada ni cambiar de modo a mano:

  • modo de desarrollo: en localhost (dirección IP 127.0.0.1 o ::1) si no hay ningún proxy presente (es decir, no se detecta su cabecera HTTP)
  • modo de producción: en todos los demás casos

Si queremos activar el modo de desarrollo en otros casos, por ejemplo para los programadores que se conectan desde una dirección IP concreta, usamos setDebugMode():

$this->configurator->setDebugMode('23.75.345.200'); // también se puede indicar un array de direcciones IP

Recomendamos vivamente combinar la dirección IP con un cookie. Guarde un token secreto, por ejemplo secret1234, en el cookie nette-debug y active así el modo de desarrollo para los programadores que se conecten desde una dirección IP concreta y tengan además ese token en su cookie:

$this->configurator->setDebugMode('secret1234@23.75.345.200');

También podemos desactivar por completo el modo de desarrollo, incluso en localhost:

$this->configurator->setDebugMode(false);

Tenga en cuenta que el valor true fuerza el modo de desarrollo, lo que nunca debería ocurrir en un servidor de producción.

De la autodetección se encarga internamente el método estático Configurator::detectDebugMode(), al que también puede llamar usted mismo, por ejemplo para detectar el modo de desarrollo fuera del configurador. Acepta una lista blanca opcional de direcciones IP o nombres de equipo y devuelve si la petición actual debe ejecutarse en modo de desarrollo:

$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');

Herramienta de depuración Tracy

Para depurar con comodidad activaremos la excelente herramienta Tracy. En modo de desarrollo visualiza los errores y, en modo de producción, los registra en el directorio indicado:

$this->configurator->enableTracy($this->rootDir . '/log');

Archivos temporales

Nette usa caché para el contenedor DI, RobotLoader, las plantillas, etc. Por eso hay que indicar la ruta del directorio donde se guardará esa caché:

$this->configurator->setTempDirectory($this->rootDir . '/temp');

En Linux o macOS, dé permisos de escritura a los directorios log/ y temp/.

RobotLoader

Normalmente querremos cargar las clases automáticamente con RobotLoader, así que hay que ponerlo en marcha y dejar que cargue las clases del directorio donde está Bootstrap.php (es decir, __DIR__) y de todos sus subdirectorios:

$this->configurator->createRobotLoader()
	->addDirectory(__DIR__)
	->register();

Un enfoque alternativo es cargar las clases únicamente mediante Composer, siguiendo PSR-4.

Zona horaria

Puede fijar la zona horaria predeterminada mediante el configurador.

$this->configurator->setTimeZone('Europe/Prague');

Configuración del contenedor DI

Parte del proceso de arranque es la creación del contenedor DI, o factory de objetos, que es el corazón de toda la aplicación. En realidad es una clase PHP generada por Nette y guardada en el directorio de caché. La factory produce los objetos clave de la aplicación y, mediante los archivos de configuración, le indicamos cómo crearlos y ajustarlos, con lo que influimos en el comportamiento de toda la aplicación.

Los archivos de configuración se escriben normalmente en formato NEON. En un capítulo aparte puede leer qué se puede configurar.

En modo de desarrollo, el contenedor se actualiza automáticamente cada vez que cambian el código o los archivos de configuración. En modo de producción se genera una sola vez y los cambios no se comprueban, para maximizar el rendimiento.

Mientras que createContainer() construye el contenedor y devuelve su instancia, el método loadContainer() devuelve solo el nombre de la clase de contenedor generada, que puede instanciar usted mismo. Esto resulta útil en escenarios avanzados.

Los archivos de configuración se cargan con addConfig():

$this->configurator->addConfig($this->rootDir . '/config/common.neon');

Si queremos añadir más archivos de configuración, podemos llamar varias veces a la función addConfig().

$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');
}

El nombre cli.php no es una errata; la configuración también se puede escribir en un archivo PHP que la devuelva como array.

También podemos añadir otros archivos de configuración en la sección includes.

Si en los archivos de configuración aparecen elementos con las mismas claves, se sobrescriben o, en el caso de los arrays, se fusionan. Un archivo incluido después tiene más prioridad que el anterior. El archivo en el que figura la sección includes tiene más prioridad que los archivos incluidos dentro de ella.

Parámetros estáticos

Los parámetros usados en los archivos de configuración se pueden definir en la sección parameters y también pasar (o sobrescribir) con el método addStaticParameters() (cuyo alias antiguo, ahora obsoleto, es addParameters()). Es importante saber que valores distintos de los parámetros provocan la generación de contenedores DI adicionales, es decir, de clases adicionales.

$this->configurator->addStaticParameters([
	'projectId' => 23,
]);

El parámetro projectId se puede referenciar en la configuración con la notación habitual %projectId%.

Parámetros dinámicos

Al contenedor también podemos añadir parámetros dinámicos, cuyos distintos valores, a diferencia de los estáticos, no provocan la generación de nuevos contenedores DI.

$this->configurator->addDynamicParameters([
	'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);

Así podemos añadir con facilidad, por ejemplo, las variables de entorno, que se pueden referenciar después en la configuración con la notación %env.variable%.

$this->configurator->addDynamicParameters([
	'env' => getenv(),
]);

Parámetros predeterminados

Puede usar estos parámetros en los archivos de configuración:

  • %appDir% es la ruta absoluta al directorio que contiene el archivo Bootstrap.php
  • %wwwDir% es la ruta absoluta al directorio que contiene el archivo de entrada index.php
  • %tempDir% es la ruta absoluta al directorio de los archivos temporales
  • %vendorDir% es la ruta absoluta al directorio donde Composer instala las bibliotecas
  • %rootDir% es la ruta absoluta al directorio raíz del proyecto
  • %baseUrl% es la URL absoluta al directorio raíz (un parámetro dinámico resuelto en tiempo de ejecución)
  • %debugMode% indica si la aplicación está en modo de depuración
  • %consoleMode% indica si la petición llegó por la línea de comandos

Servicios importados

Vamos ahora un poco más al fondo. Aunque el cometido del contenedor DI es crear objetos, alguna vez puede surgir la necesidad de insertar en el contenedor un objeto ya existente. Lo hacemos definiendo el servicio con la bandera imported: true.

services:
	myservice:
		type: App\Model\MyCustomService
		imported: true

Y en el bootstrap insertamos el objeto en el contenedor:

$this->configurator->addServices([
	'myservice' => new App\Model\MyCustomService('foobar'),
]);

Entornos distintos

No dude en modificar la clase Bootstrap según sus necesidades. Puede añadir parámetros al método bootWebApplication() para distinguir entre proyectos web. O podemos añadir otros métodos, como bootTestEnvironment(), que inicializa el entorno para las pruebas unitarias, bootConsoleApplication() para los scripts llamados desde la línea de comandos, etc.

public function bootTestEnvironment(): Nette\DI\Container
{
	Tester\Environment::setup(); // inicialización de Nette Tester
	$this->setupContainer();
	return $this->configurator->createContainer();
}

public function bootConsoleApplication(): Nette\DI\Container
{
	$this->configurator->setDebugMode(false);
	$this->initializeEnvironment();
	$this->setupContainer();
	return $this->configurator->createContainer();
}
versión: 4.x