¿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:
- inicializar el entorno
- obtener la factory
- 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:
- La URL es
https://example.com - La aplicación arranca, se crea el contenedor DI y se ejecuta
Application::run(). - El router decodifica la URL en la pareja
Home:default. - Se crea una instancia de la clase
HomePresenter. - Se llama al método
renderDefault()(si existe). - 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 💙