PHP básico

Armar el sitio por partes

Lección 6 de 8 · 14 min

El menú que quedó distinto en tres páginas

Un sitio pequeño tiene ocho páginas, y las ocho llevan la misma cabecera con el mismo menú. Copiado ocho veces. Entra un servicio nuevo, se añade al menú… y unas semanas después alguien nota que en tres páginas ese enlace no está.

No es descuido: es que ocho copias no se mantienen. La solución de PHP es escribir la cabecera una vez y pedirle a cada página que la traiga.

partes/cabecera.php
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title><?= htmlspecialchars($titulo ?? 'Mi sitio') ?></title>
</head>
<body>
<nav>
    <a href="/">Inicio</a>
    <a href="/servicios.php">Servicios</a>
    <a href="/contacto.php">Contacto</a>
</nav>
servicios.php, que la usa
<?php
$titulo = 'Nuestros servicios';
require __DIR__ . '/partes/cabecera.php';
?>

<h1>Nuestros servicios</h1>
<p>Contenido propio de esta página.</p>

<?php require __DIR__ . '/partes/pie.php'; ?>

Ya está. El menú vive en un solo archivo, y el día que entre un servicio nuevo se añade una vez y aparece en las ocho páginas.

Fíjate en $titulo: se define antes de traer la cabecera, y la cabecera lo usa. El archivo incluido comparte las variables de quien lo incluye, como si su contenido estuviera escrito justo ahí.

Cuatro formas de incluir, y cuál usar

require

Si el archivo falta, detiene el programa.

Es lo que quieres para todo lo imprescindible: la cabecera, la conexión a la base de datos, la configuración. Sin eso, la página no tiene sentido.

include

Si el archivo falta, avisa y sigue.

Solo para lo accesorio: un banner opcional, un bloque que puede no existir. Usado por costumbre, produce páginas rotas a medias que nadie entiende.

require_once

Igual que require, pero si ese archivo ya se incluyó, no lo vuelve a incluir.

Es el que se usa para archivos con funciones: incluirlo dos veces daría «ya existe una función con ese nombre» y el programa muere.

__DIR__

No es una forma de incluir: es la carpeta donde está este archivo.

Ponerlo delante hace que la ruta funcione sin importar desde dónde se llame a la página. Sin él, las rutas relativas se rompen en cuanto un archivo incluye a otro.

Escribir plantillas que se puedan leer

Cuando el archivo es sobre todo HTML con algo de PHP, las llaves y los <?php echo ... ?> lo vuelven ilegible. PHP tiene dos atajos pensados justo para esto.

<?= $x ?> es <?php echo $x; ?>, y a diferencia de las etiquetas cortas siempre está disponible. Y las estructuras se pueden escribir con : y endforeach en vez de llaves:

La lista de servicios, como plantilla
<ul>
<?php foreach ($servicios as $s): ?>
    <li>
        <strong><?= htmlspecialchars($s['nombre']) ?></strong>
        <?php if ($s['destacado']): ?>
            <span class="badge">Destacado</span>
        <?php endif; ?>
    </li>
<?php endforeach; ?>
</ul>

Fíjate en que cada dato que se imprime pasa por htmlspecialchars(), tal como quedó en la lección 5. En una plantilla es donde más se olvida, porque uno está pensando en el diseño.

La etiqueta de cierre que conviene no escribir

Ahora sí, la promesa de la lección 1. En un archivo que solo tiene PHP -una configuración, un archivo de funciones- no se escribe el ?> final. Lo recomienda la propia documentación oficial.

El motivo: cualquier espacio o salto de línea después del ?> se envía al navegador como contenido de la página. Y en cuanto se ha enviado algo, PHP ya no puede mandar cabeceras.

El error más desconcertante del principiante
Warning: Cannot modify header information - headers already sent by
(output started at /var/www/config.php:12) in /var/www/entrar.php on line 4

Cuando veas ese mensaje, léelo entero: el paréntesis te dice exactamente qué archivo y qué línea empezaron a imprimir. Casi siempre es un salto de línea suelto detrás de un ?> en un archivo incluido.

Rompe justo lo que se hace después: header('Location: ...') para redirigir, las cookies y el arranque de la sesión de la lección 7.

Dónde NO va el archivo de configuración

Al partir el sitio en archivos aparece uno especial: el que guarda la contraseña de la base de datos, la clave del servicio de correo, los datos que no puede ver nadie.

La reacción normal es guardarlo como config.php junto a las demás páginas y confiar en que, al ser .php, el servidor lo ejecute en vez de mostrarlo. Es verdad… mientras el servidor esté ejecutando PHP.

Las tres formas en las que eso se cae

  1. El archivo con otra extensión

    config.inc, config.txt, config.php.bak, config.php~ del editor. Ninguno lo ejecuta PHP: el servidor los entrega como texto plano, con la contraseña dentro, a quien acierte el nombre.

  2. El día que PHP no está

    Una actualización mal hecha o un módulo que no carga y el servidor deja de interpretar PHP. Durante esos minutos, cada .php se descarga como texto: todo tu código y todas tus claves.

  3. Lo que genera la propia aplicación

    Cachés, copias de seguridad, exportaciones, registros. Se cuelan porque «es solo un temporal», y un punto delante del nombre no esconde nada.

La solución es de estructura, no de permisos: lo que no es una página se saca de la carpeta pública. El servidor solo publica una carpeta -a menudo llamada public_html o public-, y todo lo demás vive un nivel por encima, donde no hay ninguna dirección que lleve hasta ahí.

La forma de la carpeta, y cómo se comprueba
mi-sitio/
├── config.php          <- claves. FUERA de lo publicado
├── incluye/
│   └── funciones.php
└── public_html/        <- lo único que el servidor publica
    ├── index.php       require __DIR__ . '/../config.php';
    ├── servicios.php
    └── partes/
        ├── cabecera.php
        └── pie.php
Y no se supone: se comprueba
curl -o /dev/null -w "%{http_code}\n" https://ejemplo.com/config.php
curl -o /dev/null -w "%{http_code}\n" https://ejemplo.com/config.php.bak
Resultado
404
404

Un 404 es la respuesta correcta: ahí no hay nada que pedir. Un 200 significa que el archivo se está entregando, y entonces da igual lo bien escrita que esté tu aplicación.

Es una comprobación de treinta segundos y hay que hacerla cada vez que se publica un archivo nuevo, no una vez al año. Suponer que está bien es exactamente cómo se filtran las cosas.

Si tu hosting no te deja salir de la carpeta pública

Pasa, y tiene arreglo

En algunos alojamientos compartidos solo tienes acceso a public_html. Entonces el archivo se protege desde el propio servidor, con una regla en .htaccess que deniegue el acceso a esa carpeta o a ese archivo.

Funciona, pero es un candado que depende de la configuración: si alguien mueve el sitio o cambia el servidor, el candado se queda atrás. Por eso, cuando se puede elegir, se prefiere sacar el archivo de ahí.

Y en cualquiera de los dos casos

Se comprueba con curl, igual que arriba. La única diferencia entre un sitio protegido y uno que cree estarlo es que en el primero alguien pidió la dirección y miró el número que salió.

Compruébalo tú mismo

Un archivo con las funciones del sitio se incluye desde dos sitios distintos y sale «cannot redeclare function». ¿Qué usas?

Sale «Cannot modify header information - headers already sent by (output started at config.php:12)». ¿Qué buscas?

Guardas una copia de config.php como config.php.bak en la misma carpeta. ¿Qué pasa?

Lo que sigue

Tu sitio ya está partido en piezas y la configuración está donde debe. Falta que el servidor sepa quién está al otro lado: en la siguiente lección haces un inicio de sesión, guardas una contraseña sin poder leerla nunca y cierras una zona privada de verdad.

Siguiente: Sesiones y una zona con contraseña

Continuar ← Formularios: recibir y validar en el servidor