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.1o::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 archivoBootstrap.php%wwwDir%es la ruta absoluta al directorio que contiene el archivo de entradaindex.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();
}