Curso de PHP gratis · Capítulo 9 de 13 · con Fina Fichero
Include, require y ficheros en PHP: leer y escribir archivos
Los proyectos reales no caben en un archivo. Aprenderás a separar configuración y funciones con require_once y a guardar datos en ficheros que sobreviven cuando el programa termina.
Un programa, muchos archivos
Los proyectos de verdad no viven en un único archivo. Lo normal es separar:
- Configuración (
config.php): nombres, precios, contraseñas de la base de datos... - Funciones reutilizables (
funciones.php), que usan varias páginas. - Plantillas (
cabecera.php,pie.php) que se repiten en todas las páginas de una web.
Para usar un archivo desde otro, PHP tiene cuatro instrucciones: include, require, include_once y require_once. Todas hacen lo mismo en esencia:
Cuando PHP llega a require 'funciones.php';, lee ese archivo, lo compila y lo ejecuta en ese punto, como si su código estuviera escrito ahí.
Encima del editor tienes los archivos del proyecto. Tu programa es main.php (el del editor) y usa las funciones de herramientas.php.
require no es una función, es una instrucción del lenguaje: no hacen falta paréntesis. Se escribe require 'archivo.php';.
herramientas.php
<?php
function sacos(int $kilos): int {
return intdiv($kilos + 24, 25); // sacos de 25 kg, redondeando hacia arriba
}
function etiqueta(string $cultivo, int $kilos): string {
return ucfirst($cultivo) . ': ' . sacos($kilos) . ' sacos';
}main.php
<?php
require 'herramientas.php';
echo etiqueta('heno', 120), "\n";
echo etiqueta('zanahoria', 30), "\n";
echo "En total: ", sacos(150), " sacos\n";Heno: 5 sacos Zanahoria: 2 sacos En total: 6 sacos
include frente a require
La diferencia está en qué pasa si el archivo no existe:
include: lanza un Warning y el programa sigue como si nada.require: lanza un Error y el programa se para.
¿Cuál usar? Casi siempre require: si falta el archivo de configuración o el de funciones, seguir adelante solo produce errores más raros después. include se reserva para cosas opcionales (un banner, un aviso).
Ejecuta el ejemplo: el include falla y el programa continúa; el require falla y ya no se imprime nada más.
Fíjate en el aviso: Failed to open stream: No such file or directory. Cuando lo veas, revisa el nombre y la ruta del archivo: es el error más común con include.
aviso.php
<?php
echo "Aviso: mañana llueve\n";main.php
<?php
include 'aviso.php';
include 'banner.php'; // no existe: Warning y sigue
echo "La granja sigue funcionando\n";
require 'config.php'; // no existe: Error y se para
echo "Esto ya no se imprime\n";Aviso: mañana llueve La granja sigue funcionando
_once: una sola vez
¿Qué pasa si cargas dos veces un archivo que declara funciones? Que PHP intenta declararlas otra vez:
Fatal error: Cannot redeclare function sacos()
En un proyecto grande pasa enseguida: main.php carga herramientas.php, pero también carga informe.php, que a su vez carga herramientas.php...
La solución son las versiones _once: require_once e include_once. PHP apunta cada archivo que ya ha cargado y, si se lo pides otra vez, no hace nada (devuelve true).
Regla práctica: para archivos con funciones o clases, usa siempre require_once. Para plantillas que quieres pintar varias veces (una fila de una tabla, por ejemplo), include o require sin _once.
herramientas.php
<?php
function sacos(int $kilos): int {
return intdiv($kilos + 24, 25);
}informe.php
<?php
require_once 'herramientas.php';
function informe(int $kilos): string {
return "Informe: $kilos kg son " . sacos($kilos) . " sacos";
}main.php
<?php
require_once 'herramientas.php';
require_once 'informe.php'; // informe.php vuelve a pedir herramientas.php
require_once 'herramientas.php'; // y aquí otra vez: no pasa nada
echo informe(80), "\n";
// Prueba a cambiar los require_once por require: ¿qué error sale?Informe: 80 kg son 4 sacos
informe.php hace require_once 'herramientas.php' y tu programa ya lo había cargado antes. ¿Qué pasa?
- Se vuelve a ejecutar y las funciones se declaran dos veces
- No se ejecuta otra vez: PHP recuerda que ya lo cargó
- Da un Warning y sigue
Ver respuesta
B. No se ejecuta otra vez: PHP recuerda que ya lo cargó
Eso es: _once comprueba la lista de archivos ya cargados. Si está, no hace nada y devuelve true.
Comparte tus variables (y puede devolver)
El archivo incluido se ejecuta en el ámbito donde escribes el require:
- Si lo haces en el programa principal, el archivo ve (y puede cambiar) tus variables globales, y las que cree quedan disponibles después.
- Si lo haces dentro de una función, ve las variables locales de esa función.
Esto funciona, pero es un poco peligroso: un archivo que crea variables "por sorpresa" es difícil de seguir. Por eso los archivos de configuración suelen usar otra idea: devolver un valor con return.
// config.php return ['granja' => 'Zendaida', 'campos' => 4];
$config = require 'config.php'; // $config es el array
require es una expresión: vale lo que devuelva el return del archivo (o 1 si no hay return). Así queda clarísimo de dónde sale cada dato.
Abre la Lupa Zend y fíjate: $config es un array normal en la tabla de símbolos, y $tema apareció "de la nada" desde colores.php.
config.php
<?php
return [
'granja' => 'Zendaida',
'campos' => 4,
'dron' => 'BIT-8',
];colores.php
<?php
// crea una variable en quien lo incluya (funciona, pero es menos claro)
$tema = 'cyberpunk';main.php
<?php
$config = require 'config.php';
echo "Granja {$config['granja']} con {$config['campos']} campos\n";
require 'colores.php';
echo "Tema: $tema\n";Granja Zendaida con 4 campos Tema: cyberpunk
Rutas y __DIR__
Una ruta relativa como 'lib/precios.php' se busca a partir de la carpeta desde la que se ejecuta el programa, que no siempre es la del archivo que hace el require. Cuando unos archivos incluyen a otros que están en subcarpetas, esto da sorpresas.
La solución es la constante mágica __DIR__: la carpeta del archivo donde está escrita. Construye las rutas a partir de ella:
require_once __DIR__ . '/lib/precios.php';
Así la ruta funciona se ejecute desde donde se ejecute. Fíjate en que lib/granja.php también la usa para cargar su vecino precios.php.
Otras constantes mágicas: __FILE__ (la ruta completa del archivo actual) y __LINE__ (la línea).
lib/precios.php
<?php
const PRECIOS = ['heno' => 2, 'zanahoria' => 5, 'calabaza' => 12];lib/granja.php
<?php
require_once __DIR__ . '/precios.php';
function valor(string $cultivo, int $cantidad): int {
return PRECIOS[$cultivo] * $cantidad;
}main.php
<?php
require_once __DIR__ . '/lib/granja.php';
echo "10 zanahorias valen ", valor('zanahoria', 10), " monedas\n";
echo "3 calabazas valen ", valor('calabaza', 3), " monedas\n";10 zanahorias valen 50 monedas 3 calabazas valen 36 monedas
Leer ficheros
Además de código, los archivos guardan datos. Para leerlos:
file_get_contents('notas.txt'): todo el contenido como un string.file('notas.txt'): un array con una línea en cada elemento.file_exists('notas.txt'):truesi existe. Compruébalo antes de leer algo que puede no estar.
Ojo con file(): cada línea conserva su salto de línea final ("heno\n"). Casi siempre querrás quitarlo con estas opciones:
$lineas = file('cosecha.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
FILE_IGNORE_NEW_LINES: quita el\nde cada línea.FILE_SKIP_EMPTY_LINES: se salta las líneas vacías (solo funciona junto con la anterior).
El | combina las dos opciones: cada una es un bit de un número.
cosecha.txt
heno zanahoria heno
main.php
<?php
if (!file_exists('cosecha.txt')) {
exit("No hay registro de cosecha\n");
}
$texto = file_get_contents('cosecha.txt');
echo strlen($texto), " bytes\n";
var_dump(file('cosecha.txt'));
var_dump(file('cosecha.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));21 bytes
array(4) {
[0]=>
string(5) "heno
"
[1]=>
string(10) "zanahoria
"
[2]=>
string(1) "
"
[3]=>
string(5) "heno
"
}
array(3) {
[0]=>
string(4) "heno"
[1]=>
string(9) "zanahoria"
[2]=>
string(4) "heno"
}Escribir ficheros
Para escribir, file_put_contents:
file_put_contents('diario.txt', "Hoy: 12 de heno\n"); // crea o SOBRESCRIBE file_put_contents('diario.txt', "Mañana: más\n", FILE_APPEND); // añade al final
Devuelve el número de bytes escritos. Sin FILE_APPEND, borra lo que hubiera. Con FILE_APPEND, añade: perfecto para un registro (log).
Esto es muy importante por lo que verás en la Posada del Request: PHP olvida todas las variables cuando termina cada ejecución. Lo que escribes en un fichero, en cambio, sigue ahí la próxima vez. Es la forma más sencilla de que un programa tenga memoria.
Debajo de la salida verás cómo quedan los archivos después de ejecutar.
Para ficheros enormes existe también fopen, fgets y fclose, que leen línea a línea sin cargarlo todo en memoria. Y unlink('x.txt') borra un fichero.
<?php
file_put_contents('diario.txt', "Día 1: BIT-8 despierta\n");
file_put_contents('diario.txt', "Día 2: primera cosecha\n", FILE_APPEND);
$bytes = file_put_contents('diario.txt', "Día 3: ¡zanahorias!\n", FILE_APPEND);
echo "Última escritura: $bytes bytes\n";
echo file_get_contents('diario.txt');Última escritura: 22 bytes Día 1: BIT-8 despierta Día 2: primera cosecha Día 3: ¡zanahorias!
Guardar datos con JSON
Un fichero solo guarda texto. Si quieres guardar un array (una partida, una lista de pedidos...), conviértelo a texto con JSON y vuelve a convertirlo al leer:
file_put_contents('partida.json', json_encode($partida, JSON_PRETTY_PRINT)); $partida = json_decode(file_get_contents('partida.json'), true);
El true de json_decode pide arrays asociativos (sin él, obtendrías objetos).
Seguridad: nunca hagas include de una ruta que venga del usuario (include $_GET['pagina'];). Un atacante podría pedir ../../secretos.php y leer o ejecutar lo que quiera. Es una vulnerabilidad clásica llamada inclusión de ficheros locales (LFI). Si necesitas elegir un archivo según lo que pida el usuario, compáralo con una lista blanca de nombres permitidos.
partida.json
{
"nombre": "BIT-8",
"nivel": 3,
"inventario": {
"heno": 40,
"zanahoria": 12
}
}main.php
<?php
$partida = json_decode(file_get_contents('partida.json'), true);
echo "Nivel {$partida['nivel']}, heno: {$partida['inventario']['heno']}\n";
$partida['nivel']++;
$partida['inventario']['heno'] += 10;
file_put_contents('partida.json', json_encode($partida, JSON_PRETTY_PRINT));
echo file_get_contents('partida.json'), "\n";Nivel 3, heno: 40
{
"nombre": "BIT-8",
"nivel": 4,
"inventario": {
"heno": 50,
"zanahoria": 12
}
}Una web hace include $_GET["seccion"] . ".php"; para mostrar cada sección. ¿Qué problema tiene?
- Ninguno: el
.phpfinal lo protege - Un atacante puede elegir qué archivo del servidor se carga
- Solo que es más lento que
require
Ver respuesta
B. Un atacante puede elegir qué archivo del servidor se carga
Exacto: con ?seccion=../../config cargaría otro archivo. La solución es una lista blanca: in_array($seccion, ['inicio', 'tienda'], true) antes de incluir nada.
Ejercicios de Include, require y ficheros en PHP resueltos
Intenta resolverlos antes de mirar la solución. En el juego puedes ejecutarlos y comprobarlos automáticamente.
Encargo 1. La configuración
Fina guarda los datos de cada granja en un config.php. Cada granja tiene el suyo, así que tu programa no puede llevar los datos escritos a mano.
Carga config.php con require (devuelve un array) y escribe exactamente:
Granja Zendaida: 4 campos de zanahoria
usando los datos del array. Lo probaremos con otro config.php distinto.
Archivos del ejercicio:
config.php
<?php
// config.php: los datos de la granja, en un solo sitio
return [
'granja' => 'Zendaida',
'campos' => 4,
'cultivo' => 'zanahoria',
];Pistas
$config = require 'config.php';guarda en$configlo que devuelve el archivo.- Luego:
echo "Granja {$config['granja']}: ...";
Ver solución
<?php
$config = require 'config.php';
echo "Granja {$config['granja']}: {$config['campos']} campos de {$config['cultivo']}";
Predicción 2. ¿Cuántas visitas?
Fina lleva la cuenta de las visitas a la Biblioteca con un archivo muy curioso. Mira contador.php en la pestaña Archivos.
Elige lo que imprime el programa.
Archivos del ejercicio:
contador.php
<?php
$visitas++;
echo "Visita $visitas\n";
return $visitas * 10;<?php
$visitas = 0;
$a = include 'contador.php';
$b = include 'contador.php';
$c = include_once 'contador.php';
echo $a + $b + $c;Visita 1 Visita 2 31Visita 1 Visita 2 Visita 3 60Visita 1 Visita 2 30Visita 1 10
Ver solución
Visita 1 Visita 2 31
Eso es: include lo ejecuta cada vez (10 y 20), pero include_once ve que ya se cargó, no lo ejecuta y devuelve true, que al sumar vale 1.
Misión del dron 3. La hoja de ruta
Fina ha escrito en ruta.txt las órdenes para que BIT-8 recoja cuatro libros del campo, una por línea. Las direcciones están escritas igual que las constantes del dron: NORTE vale "norte".
Lee ruta.txt y ejecuta sus órdenes en orden: cosechar significa cosechar(), y cualquier otra línea es una dirección para mover(). Tu programa debe funcionar leyendo el fichero, no copiando la ruta a mano.
Archivos del ejercicio:
ruta.txt
norte cosechar este este cosechar norte norte cosechar oeste cosechar
Esta misión mueve al dron BIT-8 por un campo de cultivo: juégala en PHPlay para ver tu código animado.
Pistas
- Mira lo que imprime
print_r: cada orden lleva un\npegado. Usafile('ruta.txt', FILE_IGNORE_NEW_LINES). - Recorre las órdenes con
foreachy dentro:if ($orden === 'cosechar') { cosechar(); } else { mover($orden); }
Ver solución
<?php
$ordenes = file('ruta.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($ordenes as $orden) {
if ($orden === 'cosechar') {
cosechar();
} else {
mover($orden);
}
}
Encargo 4. El diario de BIT-8
BIT-8 quiere llevar un diario. Cada vez que pasa algo, lo apunta en diario.txt... sin borrar lo que ya había.
Declara registrar(string $mensaje): void, que añada el mensaje y un salto de línea al final de diario.txt. Si el fichero no existe, se crea; si ya existe, se conserva lo que tenía. No imprimas nada: la probaremos nosotros.
Pistas
file_put_contents('diario.txt', ...)escribe, pero sin más opciones sobrescribe.- Pásale un tercer argumento:
file_put_contents('diario.txt', $mensaje . "\n", FILE_APPEND);
Ver solución
<?php
function registrar(string $mensaje): void {
file_put_contents('diario.txt', $mensaje . "\n", FILE_APPEND);
}
Caza de bugs 5. Las zanahorias invisibles
Fina cuenta las zanahorias del almacén leyendo inventario.txt. Hay tres, se ven a simple vista... pero el programa dice que cero.
Arregla el programa para que imprima exactamente Zanahorias: 3.
Archivos del ejercicio:
inventario.txt
zanahoria heno zanahoria calabaza zanahoria
Código de partida:
<?php
$lineas = file('inventario.txt');
$zanahorias = 0;
foreach ($lineas as $linea) {
if ($linea === 'zanahoria') {
$zanahorias++;
}
}
echo "Zanahorias: $zanahorias";
Pistas
- Añade
var_dump($linea);dentro del bucle. ¿Cuántos caracteres tiene cada línea? - Cada línea termina en
"\n". Usafile('inventario.txt', FILE_IGNORE_NEW_LINES)(o compara contrim($linea)).
Ver solución
<?php
$lineas = file('inventario.txt', FILE_IGNORE_NEW_LINES);
$zanahorias = 0;
foreach ($lineas as $linea) {
if ($linea === 'zanahoria') {
$zanahorias++;
}
}
echo "Zanahorias: $zanahorias";
Acertijo 6. El examen de la Biblioteca
Fina te pone delante un formulario de préstamo: "Para llevarte libros, antes hay que aprobar".
Responde a las cinco preguntas.
Si el archivo no existe, ¿qué hace
require?- Da un Warning y sigue
- Lanza un Error y el programa se para
- Crea el archivo vacío
Ver respuesta
B. Lanza un Error y el programa se para
¿Qué conviene usar para cargar un archivo lleno de funciones?
includerequire_oncefile_get_contents
Ver respuesta
B.
require_onceSi
config.phptermina conreturn ['x' => 1];, ¿qué vale$ctras$c = require 'config.php';?1true- El array
['x' => 1]
Ver respuesta
C. El array
['x' => 1]¿Qué hace
file_put_contents('a.txt', 'hola')sia.txtya tenía texto?- Lo sustituye por
hola - Añade
holaal final - Da un error porque ya existe
Ver respuesta
A. Lo sustituye por
hola- Lo sustituye por
¿Qué es
__DIR__?- La carpeta desde la que se ejecuta el programa
- La carpeta del archivo donde está escrito
- Una función que lista los archivos
Ver respuesta
B. La carpeta del archivo donde está escrito
Encargo 7. Guardar la partida
BIT-8 quiere recordar su nivel y su heno aunque lo apaguen. Necesita guardar y cargar su partida.
Declara dos funciones:
guardar(array $partida): void, que guarde el array enpartida.jsonconjson_encode.cargar(): array, que lo lea conjson_decode(..., true). Sipartida.jsonno existe, debe devolver['nivel' => 1, 'heno' => 0].
Pistas
- En
cargar(), empieza porif (!file_exists('partida.json')) { return ['nivel' => 1, 'heno' => 0]; } - Después:
return json_decode(file_get_contents('partida.json'), true);
Ver solución
<?php
function guardar(array $partida): void {
file_put_contents('partida.json', json_encode($partida));
}
function cargar(): array {
if (!file_exists('partida.json')) {
return ['nivel' => 1, 'heno' => 0];
}
return json_decode(file_get_contents('partida.json'), true);
}
Chuleta: lo esencial
require 'a.php';ejecuta el archivo en ese punto y en el ámbito actual. Si no existe: Error y se para.include: igual, pero si no existe da un Warning y sigue.require_once/include_once: solo la primera vez (para funciones y clases).$config = require 'config.php';recoge lo que devuelve elreturndel archivo (o1).- Rutas seguras:
require_once __DIR__ . '/lib/x.php'; - Leer:
file_get_contents($f)(string),file($f, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)(array de líneas),file_exists($f). - Escribir:
file_put_contents($f, $texto)sobrescribe; conFILE_APPENDañade.unlink($f)borra. - Guardar arrays:
json_encodeal escribir,json_decode($texto, true)al leer. - Nunca
includecon datos del usuario (LFI): usa una lista blanca.