PLANTILLAS EN PHP CON require¶
Estos apuntes son material de consulta para el Lab 05. Cubren los conceptos que se van a aplicar en el mismo: el principio DRY aplicado al HTML repetido entre páginas, la diferencia entre require e include, cómo extraer cabecera y pie a plantillas reutilizables, el paso de variables a una plantilla incluida, la construcción de rutas seguras con __DIR__, la separación entre ficheros públicos y privados del proyecto, el paso de CSS embebido a CSS externo enlazado con <link>, y el patrón clásico para marcar el elemento activo de un menú de navegación.
El problema: HTML que se repite¶
Cuando una aplicación web crece, cada .php empieza a repetir el mismo bloque de HTML "de estructura": <!DOCTYPE>, <html>, <head>, <title>, una cabecera con el logo y el menú, y un pie con la firma o el aviso legal. La parte que realmente cambia entre páginas es muy pequeña: el contenido específico, encajado entre el header y el footer.
Si tienes 10 páginas y quieres cambiar el color de fondo del menú, el texto del pie o añadir un enlace nuevo, tienes que editar las 10 páginas. Y mantener sincronizadas todas esas copias es una fuente garantizada de incoherencias: en algunas páginas el menú dirá una cosa, en otras otra, alguien olvidará una y se quedará desactualizada.
El principio DRY (Don't Repeat Yourself) resume la solución: si escribes lo mismo dos veces, extráelo y reutilízalo desde un único sitio. Vale para HTML igual que para cualquier código.
require e include¶
PHP ofrece dos construcciones para "pegar" el contenido de un fichero externo dentro de la página actual:
| Construcción | Si el fichero no existe |
|---|---|
include 'fragmento.php'; |
Muestra un aviso (warning) y continúa ejecutando la página. |
require 'fragmento.php'; |
Lanza un error fatal y detiene la página por completo. |
Para incluir plantillas obligatorias (sin las cuales la página no tiene sentido), usa siempre require. Es preferible que la página se rompa de golpe a que pinte media página rota: si la plantilla falta, queremos enterarnos en seguida.
require_once
Existe una variante require_once que comprueba si el fichero ya se ha incluido antes en la misma petición, y en ese caso no lo vuelve a incluir. Es útil cuando un mismo fragmento podría llegar a incluirse dos veces por dos rutas distintas (por ejemplo, dos plantillas que comparten utilidades). Para cabeceras y pies, require normal es suficiente.
Plantillas: extraer cabecera y pie¶
El patrón básico consiste en partir cada página en tres piezas:
- Cabecera común: todo lo que va desde
<!DOCTYPE>hasta justo antes del contenido específico de la página. - Contenido único de esa página: el bloque que cambia (una tabla, un formulario, un listado...).
- Pie común: todo lo que va desde el final del contenido hasta
</html>.
Las dos piezas comunes (1) y (3) se guardan como plantillas en ficheros separados, y cada página final solo escribe (2) entre dos require:
El resultado en el navegador es una única página HTML completa: PHP ejecuta los dos require en el servidor antes de enviar nada al navegador, fusiona los tres trozos, y el navegador recibe un <!DOCTYPE html>...</html> íntegro. Si abres "Ver código fuente", no encontrarás los require por ningún lado — el navegador solo ve el HTML resultante.
Plantillas como fragmentos¶
Una convención muy extendida: los ficheros que son fragmentos de plantilla (no páginas completas servibles por URL) llevan prefijo _ en el nombre: _header.php, _footer.php, _menu.php. Es solo una convención visual, pero ayuda a distinguir de un vistazo qué ficheros del proyecto son páginas y cuáles son piezas internas.
Otra convención importante: la cabecera abre etiquetas que el pie cierra. Así, _header.php puede terminar con <main> abierto, el contenido específico se mete dentro, y _footer.php empieza con </main>. La página final solo se ocupa del hueco entre ambos:
Pasar variables a una plantilla¶
Una plantilla no es solo HTML estático: puede contener <?= ... ?> y leer variables. Las variables que define la página que incluye la plantilla están disponibles dentro de la plantilla, como si la hubieras escrito en el sitio del require:
Dentro de _header.php, $titulo_pagina vale 'Catálogo de libros' porque la página la había definido antes del require. Esto es lo que permite que una sola plantilla sirva para muchas páginas, cada una con su título, su sección activa, sus datos de cabecera…
Valores por defecto con isset()¶
Las variables que la plantilla espera pueden no estar definidas si una página se olvida de hacerlo. Si en _header.php haces <?= $titulo_pagina ?> y la página que lo incluye no definió $titulo_pagina, PHP imprime un aviso (undefined variable).
Para evitarlo, podemos declarar un valor por defecto si no se ha establecido al incluir la plantilla. Para ello comprobamos que exista con isset():
isset($var) devuelve true si la variable existe y no es null, y false si no. Ya la vimos para comprobar claves de array (isset($incidencia['prioridad'])); aquí la aplicamos a una variable suelta. Es la versión "larga" del operador ?? (visto en el apunte sobre control de flujo): las dos formas siguientes son equivalentes:
## Forma larga con isset()
if (!isset($titulo_pagina)) {
$titulo_pagina = 'Sin título';
}
## Forma corta con el operador ??
$titulo_pagina = $titulo_pagina ?? 'Sin título';
En plantillas se ve más a menudo la forma con isset() porque hace muy visible "esto es un valor por defecto para una variable opcional".
Rutas seguras con __DIR__¶
Hasta ahora los ejemplos han usado require 'fragmento.php'; con una ruta corta. Eso solo funciona si el "directorio de trabajo" del proceso PHP es el correcto, cosa que no siempre se cumple según cómo arranque la petición. Para escribir rutas que funcionen siempre, conviene partir del directorio del fichero actual.
__DIR__ es una constante mágica de PHP que vale, en cualquier punto de un fichero, la ruta absoluta del directorio donde está ese fichero. Si pagina.php vive en /var/www/miapp/public/, dentro de pagina.php la constante __DIR__ vale literalmente /var/www/miapp/public.
El operador . concatena cadenas. Por eso esta línea construye la ruta exacta a una plantilla situada un nivel por encima:
Cómo se "lee" esa línea, paso a paso:
__DIR__→/var/www/miapp/public__DIR__ . '/../templates/_header.php'→ la cadena/var/www/miapp/public/../templates/_header.php- el sistema operativo resuelve
..y carga/var/www/miapp/templates/_header.php
| Forma de escribir la ruta | Problema |
|---|---|
require '/var/www/miapp/templates/_header.php'; |
Solo funciona en esa máquina concreta. Si despliegas el proyecto en otra máquinqa con otra carpeta raíz, deja de funcionar. |
require '../templates/_header.php'; |
Depende del directorio de trabajo del proceso PHP, que no siempre es el que crees. |
require __DIR__ . '/../templates/_header.php'; |
Siempre funciona, porque parte del directorio del fichero actual, conocido con seguridad. |
La tercera es la forma estándar en proyectos PHP profesionales.
Separar public/ de lo privado¶
Por defecto, Apache (o cualquier servidor web) sirve por URL todos los ficheros que están dentro del directorio configurado como DocumentRoot. Si el DocumentRoot es public/ y dentro pones un fichero llamado _header.php, cualquiera puede abrir http://tusitio/_header.php en su navegador y verá el HTML.
Eso es un problema por dos razones:
- Servir media página rota confunde al usuario y al buscador.
- Filtrar información sensible: en cuanto las plantillas dejen de ser HTML inerte y empiecen a mostrar datos de la sesión, nombres de usuario, importes o cualquier valor del backend, servirlas sueltas filtra esos datos.
La regla operativa: solo los ficheros que el usuario debe poder pedir directamente viven dentro de public/ (páginas finales, CSS, JS, imágenes). Todo lo demás — plantillas, datos, ficheros de configuración, librerías internas — vive fuera de public/, en directorios hermanos como templates/, data/, src/. Apache no los sirve aunque alguien adivine sus nombres.
Mala práctica: plantillas dentro de public/
Poner las plantillas en public/templates/ o directamente en public/ deja al descubierto su contenido por URL. Funciona — en el sentido de que el require desde la página las encuentra — pero es inseguro. Mueve siempre los fragmentos a un directorio hermano de public/ y úsalos solo desde el código del servidor.
CSS: del <style> embebido al <link> externo¶
Las primeras versiones de una página suelen llevar el CSS dentro de la propia plantilla, en un bloque <style> dentro del <head>:
Funciona, pero arrastra dos limitaciones:
- Los estilos viajan en cada petición. Si tienes 100 páginas, el mismo CSS se envía 100 veces.
- Para cambiar el aspecto general hay que editar la plantilla.
La forma estándar es extraer el CSS a un fichero externo y enlazarlo desde el <head> con <link>:
El fichero estilos.css se guarda en public/css/estilos.css y el navegador lo pide aparte.
Cliente y servidor: cómo se carga un CSS externo¶
Es importante entender la diferencia con require, porque parece que hacen "lo mismo" (incluir un fichero) pero en realidad ocurren en momentos y máquinas distintas:
requirese ejecuta en el servidor. PHP lee el fichero incluido, lo "pega" donde aparece elrequire, y manda al navegador el HTML resultante ya fusionado.<link rel="stylesheet">lo procesa el navegador. El servidor manda un HTML que menciona la ruta del CSS, pero el navegador, al recibirlo, hace una segunda petición HTTP a esa ruta para descargar el CSS. Solo entonces lo aplica a la página.
Esta diferencia explica por qué las plantillas pueden quedarse fuera de public/ (el navegador no las pide nunca, las usa solo el servidor) pero el CSS sí tiene que estar dentro (el navegador hace una petición HTTP directa a esa URL).
| Recurso | Lo carga | Tiene que estar accesible por URL |
|---|---|---|
_header.php |
Servidor (con require) |
No — vive fuera de public/ |
estilos.css |
Navegador (con <link>) |
Sí — vive en public/css/ |
logo.png |
Navegador (con <img>) |
Sí — vive en public/img/ |
Una ventaja añadida del CSS externo: el navegador lo cachea después de la primera petición, así que al cargar la segunda página el CSS ya está en local y no se vuelve a descargar.
Patrón "elemento de menú activo"¶
En cualquier sitio web con varias páginas, el menú de navegación suele resaltar visualmente la página en la que estás. Ejemplo típico: el enlace de la sección actual aparece en negrita, subrayado, o con un fondo distinto.
El patrón clásico (sin JavaScript) consiste en que cada página le dice a la plantilla cuál es su sección, mediante una variable acordada. La plantilla decide qué enlace pinta destacado.
Pieza 1 — cada página define una variable:
Pieza 2 — la plantilla la usa para decidir:
Pieza 3 — el CSS define cómo se ve un enlace .activo:
Cuando una página se carga, su $pagina_activa viaja a la plantilla, el if/else decide qué <a> pintar, y el CSS lo destaca. Si una página nueva quiere aparecer en el menú destacada, basta con que defina su valor de $pagina_activa antes del require.
Defensa frente a páginas que olvidan definirla¶
Igual que con $titulo_pagina, la plantilla debe poner un valor por defecto para $pagina_activa, por si una página lo olvida:
Con cadena vacía como defecto, la comparación $pagina_activa === 'catalogo' da false y ningún enlace queda marcado. Sin error en pantalla, sin aviso de variable indefinida.
Por qué este patrón sigue siendo útil hoy
Los frameworks modernos (Laravel, Symfony, Twig) tienen sistemas de plantillas mucho más sofisticados — herencia de layouts, secciones nombradas, slots — pero la idea central es la misma: separar lo común de lo específico y pasar variables desde la página a la plantilla. Entender este patrón "a mano" ayuda a leer cualquier código de plantillas posterior.