¿Cómo funcionan las aplicaciones?

Está leyendo el capítulo fundacional de la documentación de Nette. Aprenderá de la A a la Z todo el principio de funcionamiento de las aplicaciones web, desde el instante en que nace una petición hasta que el script PHP termina de ejecutarse. Tras leerlo entenderá:

  • cómo funciona todo
  • qué son Bootstrap, Presenter y el contenedor DI
  • qué aspecto tiene la estructura de directorios

Estructura de directorios

Abra el esqueleto de ejemplo de una aplicación web llamado WebProject. Mientras lee, puede consultar los archivos de los que hablamos.

La estructura de directorios tiene más o menos este aspecto:

web-project/
├── app/                      ← directorio de la aplicación
│   ├── Core/                 ← clases básicas necesarias para funcionar
│   │   └── RouterFactory.php ← configuración de las direcciones URL
│   ├── Presentation/         ← presenters, plantillas y compañía
│   │   ├── @layout.latte     ← plantilla del layout
│   │   └── Home/             ← directorio del presenter Home
│   │       ├── HomePresenter.php ← clase del presenter Home
│   │       └── default.latte ← plantilla de la acción default
│   └── Bootstrap.php         ← clase de arranque Bootstrap
├── assets/                   ← recursos (SCSS, TypeScript, imágenes fuente)
├── bin/                      ← scripts ejecutados desde la línea de comandos
├── config/                   ← archivos de configuración
│   ├── common.neon
│   └── services.neon
├── log/                      ← errores registrados
├── temp/                     ← archivos temporales, caché, …
├── vendor/                   ← bibliotecas instaladas por Composer
│   ├── ...
│   └── autoload.php          ← autocarga de todos los paquetes instalados
├── www/                      ← directorio público, document root del proyecto
│   ├── assets/               ← archivos estáticos compilados (CSS, JS, imágenes, ...)
│   ├── .htaccess             ← reglas de mod_rewrite
│   └── index.php             ← archivo inicial que lanza la aplicación
└── .htaccess                 ← prohíbe el acceso a todos los directorios salvo www

Puede cambiar la estructura de directorios como quiera, renombrar o mover carpetas; es del todo flexible. Nette dispone además de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su base de URL.

En aplicaciones algo mayores podemos organizar las carpetas de presenters y plantillas en subdirectorios y agrupar las clases en espacios de nombres, lo que llamamos módulos.

El directorio www/ representa el directorio público o document-root del proyecto. Puede renombrarlo sin necesidad de configurar nada más del lado de la aplicación. Solo hace falta configurar el alojamiento para que el document-root apunte a ese directorio.

También puede descargar WebProject directamente, con Nette incluido, mediante Composer:

composer create-project nette/web-project

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

La aplicación WebProject está lista para funcionar; no hace falta configurar absolutamente nada y puede verla directamente en el navegador accediendo a la carpeta www/.

Petición HTTP

Todo empieza cuando un usuario abre una página en su navegador. El navegador envía una petición HTTP al servidor. Esa petición apunta a un único archivo PHP situado en el directorio público www/, que es index.php. Supongamos que la petición es a la dirección https://example.com/product/123. Gracias a una configuración del servidor adecuada, también esa URL se asigna al archivo index.php, que se ejecuta entonces.

Su tarea es:

  1. inicializar el entorno
  2. obtener la factory
  3. ejecutar la aplicación de Nette, que atiende la petición

¿Qué factory? ¡No fabricamos tractores, construimos webs! Espere, se explicará enseguida.

Por “inicializar el entorno” entendemos, por ejemplo, activar Tracy, una herramienta magnífica para registrar o visualizar errores. En un servidor de producción registra los errores; en un entorno de desarrollo los muestra directamente. La inicialización incluye, por tanto, determinar si el sitio funciona en modo de producción o de desarrollo. Nette usa para ello una autodetección inteligente: si ejecuta el sitio en localhost, funciona en modo de desarrollo. No necesita configurar nada y la aplicación está lista al instante tanto para el desarrollo como para el despliegue en vivo. Estos pasos se realizan y se describen en detalle en el capítulo sobre la clase Bootstrap.

El tercer punto (sí, nos hemos saltado el segundo, pero volveremos a él) es lanzar la aplicación. De atender las peticiones HTTP en Nette se encarga la clase Nette\Application\Application (en adelante, Application). Así que, cuando decimos ejecutar la aplicación, nos referimos en concreto a llamar al método, muy bien llamado, run() de un objeto de esta clase.

Nette actúa como un mentor y le guía para que escriba aplicaciones limpias según metodologías probadas. Una de las más asentadas es la inyección de dependencias, abreviada DI. No queremos cargarle ahora con la explicación de la DI; para eso hay un capítulo aparte. La consecuencia esencial es que los objetos clave los suele crear una factory de objetos conocida como contenedor DI (o DIC). Sí, esa es la factory mencionada antes. Ella produce también el objeto Application para nosotros, y por eso necesitamos primero el contenedor. Lo obtenemos con la clase Configurator, dejamos que cree el objeto Application, llamamos a su método run() y con ello arranca la aplicación de Nette. Eso es justo lo que ocurre en el archivo index.php.

Nette Application

La clase Application tiene una única tarea: responder a la petición HTTP.

Las aplicaciones escritas en Nette se dividen en muchos presenters (en otros frameworks puede encontrarse el término “controlador”, que es en esencia lo mismo). Son clases que representan cada una una página concreta del sitio web: la página de inicio, un producto de una tienda en línea, un formulario de acceso, un feed de sitemap, etc. Una aplicación puede tener desde un presenter hasta miles.

Application empieza preguntando al router qué presenter debe atender la petición actual. El router determina la responsabilidad. Examina la URL de entrada https://example.com/product/123 y, según su configuración, decide que esa tarea corresponde, por ejemplo, al presenter Product, que debe ejecutar la acción show para el producto de id: 123. Es buena práctica escribir la pareja presenter + acción separada por dos puntos, así: Product:show.

El router ha transformado, por tanto, la URL en la pareja Presenter:acción + parámetros, en nuestro caso Product:show + id: 123. Puede ver qué aspecto tiene ese router en el archivo app/Core/RouterFactory.php, y lo describimos en detalle en el capítulo Enrutamiento.

Continuemos. Application conoce ya el nombre del presenter y puede seguir adelante. Lo hace creando una instancia de la clase ProductPresenter, que contiene el código del presenter Product. Más exactamente, pide al contenedor DI que cree el presenter, porque crear objetos es cosa suya.

El presenter podría tener este aspecto:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private ProductRepository $repository,
	) {
	}

	public function renderShow(int $id): void
	{
		// obtiene los datos del modelo y se los pasa a la plantilla
		$this->template->product = $this->repository->getProduct($id);
	}
}

El presenter toma el relevo en la atención de la petición. La tarea está clara: ejecutar la acción show con id: 123. En la terminología de los presenters, eso significa que se llama al método renderShow(), que recibe 123 en el parámetro $id.

Un presenter puede atender varias acciones, es decir, puede tener varios métodos render<Acción>(). Recomendamos, sin embargo, diseñar los presenters con una acción, o con las menos posibles.

Así pues, se ha llamado al método renderShow(123). Su código es un ejemplo ficticio, pero muestra cómo se pasan los datos a la plantilla: escribiendo en $this->template.

A continuación, el presenter devuelve una respuesta. Puede ser una página HTML, una imagen, un documento XML, el envío de un archivo del disco, JSON o quizá una redirección a otra página. Lo importante es que, si no indicamos explícitamente cómo responder (que es el caso de ProductPresenter), la respuesta será renderizar una plantilla en una página HTML. ¿Por qué? Porque en el 99 % de los casos queremos renderizar una plantilla. Por eso el presenter adopta ese comportamiento como predeterminado, para simplificarnos el trabajo. Esa es la esencia de Nette.

Ni siquiera necesitamos indicar qué plantilla renderizar; el framework deduce la ruta automáticamente. En el caso de la acción show, simplemente intenta cargar la plantilla show.latte situada en el mismo directorio que la clase ProductPresenter. También trata de encontrar el layout en el archivo @layout.latte (más detalles en búsqueda de plantillas).

Después se renderizan las plantillas. Con eso termina la tarea del presenter y de toda la aplicación. Si la plantilla no existe, se devuelve una página de error 404. Puede aprender más sobre los presenters en la página Presenters.

Por si acaso, recapitulemos todo el proceso con una URL ligeramente distinta:

  1. La URL es https://example.com
  2. La aplicación arranca, se crea el contenedor DI y se ejecuta Application::run().
  3. El router decodifica la URL en la pareja Home:default.
  4. Se crea una instancia de la clase HomePresenter.
  5. Se llama al método renderDefault() (si existe).
  6. Se renderiza la plantilla, por ejemplo default.latte, junto con el layout, por ejemplo @layout.latte.

Puede que se haya encontrado ahora con muchos conceptos nuevos, pero creemos que tienen sentido. Desarrollar aplicaciones en Nette es notablemente sencillo.

Plantillas

Puesto que hablamos de plantillas, Nette usa el sistema de plantillas Latte. Por eso los archivos de plantilla tienen la extensión .latte. Latte se emplea ante todo porque es el sistema de plantillas más seguro para PHP, y también el más intuitivo. No necesita aprender mucho nuevo; basta con saber PHP y unas cuantas etiquetas. Encontrará todo lo necesario en la documentación.

En la plantilla, los enlaces a otros presenters y acciones se crean así:

<a n:href="Product:show $productId">product detail</a>

Basta con escribir la conocida pareja Presenter:acción en lugar de la URL real e incluir los parámetros necesarios. El truco está en n:href, que le dice a Nette que procese ese atributo. Generará entonces:

<a href="/product/456">product detail</a>

De generar las URL se encarga el router mencionado antes. Los routers de Nette son excepcionales porque saben hacer no solo la transformación de una URL en la pareja Presenter:acción, sino también la inversa: generar una URL a partir del nombre del presenter, la acción y los parámetros. Gracias a ello puede cambiar por completo el formato de las URL de toda su aplicación terminada sin tocar un solo carácter de las plantillas ni de los presenters: basta con modificar el router. Esto permite además la llamada canonización, otra función única de Nette que mejora el SEO (optimización para buscadores) al impedir automáticamente que exista contenido duplicado en URL distintas. A muchos programadores esta capacidad les parece asombrosa.

Componentes interactivos

Tenemos que contarle una cosa más sobre los presenters: llevan incorporado un sistema de componentes. Quien tenga más experiencia recordará algo parecido de Delphi o de ASP.NET Web Forms; React o Vue.js se apoyan en conceptos algo emparentados. En el mundo de los frameworks de PHP, esta es una función completamente única.

Los componentes son unidades independientes y reutilizables que insertamos en las páginas (es decir, en los presenters). Pueden ser formularios, datagrids, menús, encuestas: en esencia, cualquier cosa que tenga sentido reutilizar. Podemos crear nuestros propios componentes o aprovechar alguno de la amplísima oferta de componentes de código abierto.

Los componentes influyen de raíz en la forma de desarrollar aplicaciones. Abren nuevas posibilidades para componer páginas a partir de unidades ya preparadas. Y además tienen algo en común con Hollywood.

Contenedor DI y configuración

El contenedor DI, o factory de objetos, es el corazón de toda la aplicación.

No se preocupe, no es ninguna caja negra mágica, por mucho que las líneas anteriores lo puedan sugerir. En realidad es una clase PHP bastante prosaica, generada por Nette y guardada en el directorio de caché. Contiene muchos métodos con nombres del estilo createServiceAbcd(), cada uno capaz de crear y devolver un objeto concreto. Sí, también hay un método createServiceApplication__application() que produce la instancia de Nette\Application\Application que necesitábamos en index.php para ejecutar la aplicación. Y hay métodos para crear los distintos presenters, y así sucesivamente.

A los objetos creados por el contenedor DI se los llama, por algún motivo, servicios.

Lo verdaderamente especial de esta clase es que usted no la programa: lo hace el framework. Genera realmente el código PHP y lo guarda en disco. Usted se limita a dar instrucciones sobre qué objetos debe saber crear el contenedor y cómo exactamente. Esas instrucciones se escriben en archivos de configuración, que usan el formato NEON y, por tanto, tienen la extensión .neon.

Los archivos de configuración sirven puramente para instruir al contenedor DI. Así, por ejemplo, si indica la opción expiration: 14 days en la sección session, el contenedor DI, al crear el objeto Nette\Http\Session que representa la sesión, llamará a su método setExpiration('14 days') y convertirá así la configuración en realidad.

Tiene preparado todo un capítulo que describe qué se puede configurar y cómo definir sus propios servicios.

En cuanto profundice un poco en la creación de servicios, se encontrará con el término autowiring. Es una función que le simplificará la vida de forma increíble. Sabe pasar automáticamente los objetos allí donde los necesita (por ejemplo, en los constructores de sus clases) sin que usted tenga que hacer nada. Descubrirá que el contenedor DI de Nette es un pequeño milagro.

¿Y ahora qué?

Hemos repasado los principios fundamentales de las aplicaciones de Nette. Ha sido, de momento, una visión superficial, pero pronto profundizará y, con el tiempo, creará aplicaciones web estupendas. ¿Adónde ir ahora? ¿Ha probado ya el tutorial Cree su primera aplicación?

Además de lo descrito arriba, Nette ofrece todo un arsenal de clases útiles, una capa de base de datos, etc. Pruebe a navegar por la documentación. O visite el blog. Descubrirá muchas cosas interesantes.

Que el framework le traiga mucha alegría 💙

versión: 4.x