Lección 9: Ficheros
Es posible importar o llamar el código desde otro archivo con PHP. De esta manera conseguiremos dividir nuestras página dinámicas en fragmentos más pequeños y fáciles de gestionar evitando el famoso código Spaghetti (escribir todo nuestro código en un solo fichero con un interminable número de líneas). Para esta buena práctica disponemos en nuestro bat-cinturón de hasta 4 herramientas diferentes.
Función | Descripción | Caso de error | Ejemplo |
---|---|---|---|
include '' |
Incluye el fichero en cada ocasión. | Da una advertenia, pero continua la ejecución. | include 'tu_archivo.php' |
include_once '' |
Incluye el fichero en una ocasión. | Da una advertenia, pero continua la ejecución. | include_once 'tu_archivo.php' |
require '' |
Incluye el fichero en cada ocasión. | Para la ejecución (Error fatal). | require 'tu_archivo.php' |
require_once '' |
Incluye el fichero en una ocasión. | Para la ejecución (Error fatal). | require_once 'tu_archivo.php' |
Veamos con un ejemplo como funciona. Voy a tener un archivo llamado header.php
con el siguiente contenido.
<html>
<head>
</head>
<body>
Otro con el nombre footer.php
.
<footer>Soy yo</footer>
</body>
</html>
Ahora creo un fichero nuevo.
<?php include 'header.php'; ?>
<h1>Inicio</h1>
<?php include 'footer.php'; ?>
Me daria el siguiente HTML.
<html>
<head>
</head>
<body>
<h1>Inicio</h1>
<footer>Soy yo</footer>
</body>
</html>
Potente, ¿verdad? Evitaremos repartir partes de nuestro código que sean muy reiterativas.
En esta ocasión voy a repetir el include
de footer.
Ahora creo un fichero nuevo.
<?php include 'header.php'; ?>
<h1>Inicio</h1>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>
Lo que me generaría un pie duplicado.
<html>
<head>
</head>
<body>
<h1>Inicio</h1>
<footer>Soy yo</footer>
</body>
</html>
<footer>Soy yo</footer>
</body>
</html>
Lo podemos prevenir con include_once
.
<?php include 'header.php'; ?>
<h1>Inicio</h1>
<?php include_once 'footer.php'; ?>
<?php include_once 'footer.php'; ?>
Si ya ha sido llamado, lo ingora.
<html>
<head>
</head>
<body>
<h1>Inicio</h1>
<footer>Soy yo</footer>
</body>
</html>
Subir un archivo
Un fichero es un elemento en binario que no es ni un número o ni un texto: imagen, video, música, doc, iso… Nosotros somos incapaces de leerlo a no ser que tengamos un ojo biónico y un chip en el cerebro. Si careces de estos dos requisitos solo podrás subirlo, por medio de un formulario, y almacenarlo en una carpeta.
Se debe tratar de una forma especial. Necesitaremos usar siempre el method POST y añadir enctype=”multipart/form-data”. Por último usar el input de tipo archivo (file).
<!-- Formulario -->
<form method="post" enctype="multipart/form-data">
<p>
<!-- Campo imagen -->
<input type="file" name="fichero_usuario">
</p>
<p>
<!-- Botón submit -->
<input type="submit" value="Enviar">
</p>
</form>
Cuando nuestro formulario sea enviado el archivo estará almacenado en una variable llamada $_FILES. La cual es un array
con toda la información que vas a necesitar.
Nombre | Ejemplo de contenido | Descripción |
---|---|---|
$_FILES[‘fichero_usuario’][‘name’] | ‘foto_en_la_playa.jpg’ | Nombre del archivo |
$_FILES[‘fichero_usuario’][‘type’] | ‘image/png’ | MIME (formato del archivo) |
$_FILES[‘fichero_usuario’][‘size’] | 3232424 | Tamaño en bytes (5MB -> 5 x 1024 x 1024 bytes) |
$_FILES[‘fichero_usuario’][‘error’] | 0 | Código de error. El 0 es que todo ha ido bien, los otros puedes encontrarlos aquí |
$_FILES[‘fichero_usuario’][‘tmp_name’] | 213 | Nombre temporal |
Ahora solo tendremos que moverlo de la carpeta temporal a la definitiva, usando el método move_uploaded_file().
move_uploaded_file($_FILES['fichero_usuario']['tmp_name'], $fichero_subido);
Aquí puedes ver un ejemplo completo.
<html>
<body>
<?php
//======================================================================
// PROCESAR IMAGEN
//======================================================================
// Comprobamos si nos llega los datos por POST
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Definir directorio donde se guardará
$dir_subida = './subidos/';
// Definir la ruta final del archivo
$fichero_subido = $dir_subida . basename($_FILES['fichero_usuario']['name']);
// Mueve el archivo de la carpeta temporal a la ruta definida
if (move_uploaded_file($_FILES['fichero_usuario']['tmp_name'], $fichero_subido)) {
// Mensaje de confirmación donde todo ha ido bien
echo '<p>Se subió perfectamente.</p>';
// Muestra la imagen que acaba de ser subida
echo '<p><img width="500" src="' . $fichero_subido . '"></p>';
} else {
// Mensaje de error: ¿Límite de tamaño? ¿Ataque?
echo '<p>¡Ups! Algo ha pasado.</p>';
}
}
?>
<!-- Formulario -->
<form method="post" enctype="multipart/form-data">
<p>
<!-- Campo imagen -->
<input type="file" name="fichero_usuario">
</p>
<p>
<!-- Botón submit -->
<input type="submit" value="Enviar">
</p>
</form>
</body>
</html>
Multiarchivo (subir varios archivos)
Es posible subir varios archivos bajo el mismo nombre. Solo habrá que añadir unos corchetes ([]
) después del name
como si fuera un array
.
<!-- Formulario -->
<form method="post" enctype="multipart/form-data">
<p>
<!-- Campos de imágenes -->
<input type="file" name="imagen[]">
<input type="file" name="imagen[]">
<input type="file" name="imagen[]">
</p>
<p>
<!-- Botón submit -->
<input type="submit" value="Enviar">
</p>
</form>
Al recibir los datos tendremos que iterar la variable, igual que un array
. Es recomendable comprobar en cada caso que el archivo se ha subido correctamente.
// Comprobamos si nos llega los datos por POST
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Iteramos todos los archivos
foreach ($_FILES["imagen"]["error"] as $posicion => $error) {
// Comprobamos si se ha subido correctamente
if ($error == UPLOAD_ERR_OK) {
// Definir directorio donde se guardará
$dir_subida = './subidos/';
// Definir la ruta final del archivo
$fichero_subido = $dir_subida . basename($_FILES['imagen']['name'][$posicion]);
// Mueve el archivo de la carpeta temporal a la ruta definida
if (move_uploaded_file($_FILES['imagen']['tmp_name'][$posicion], $fichero_subido)) {
// Mensaje de confirmación donde todo ha ido bien
echo '<p>Se subió perfectamente' . $_FILES['imagen']['name'][$posicion] . '.</p>';
// Muestra la imagen que acaba de ser subida
echo '<p><img width="500" src="' . $fichero_subido . '"></p>';
} else {
// Mensaje de error: ¿Límite de tamaño? ¿Ataque?
echo '<p>¡Ups! Algo ha pasado.</p>';
}
}
}
}
Junto quedaría de la siguiente forma.
<html>
<body>
<?php
//======================================================================
// PROCESAR IMAGENES
//======================================================================
// Comprobamos si nos llega los datos por POST
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Iteramos todos los archivos
foreach ($_FILES["imagen"]["error"] as $posicion => $error) {
// Comprobamos si se ha subido correctamente
if ($error == UPLOAD_ERR_OK) {
// Definir directorio donde se guardará
$dir_subida = './subidos/';
// Definir la ruta final del archivo
$fichero_subido = $dir_subida . basename($_FILES['imagen']['name'][$posicion]);
// Mueve el archivo de la carpeta temporal a la ruta definida
if (move_uploaded_file($_FILES['imagen']['tmp_name'][$posicion], $fichero_subido)) {
// Mensaje de confirmación donde todo ha ido bien
echo '<p>Se subió perfectamente' . $_FILES['imagen']['name'][$posicion] . '.</p>';
// Muestra la imagen que acaba de ser subida
echo '<p><img width="500" src="' . $fichero_subido . '"></p>';
} else {
// Mensaje de error: ¿Límite de tamaño? ¿Ataque?
echo '<p>¡Ups! Algo ha pasado.</p>';
}
}
}
}
?>
<!-- Formulario -->
<form method="post" enctype="multipart/form-data">
<p>
<!-- Campos de imágenes -->
<input type="file" name="imagen[]">
<input type="file" name="imagen[]">
<input type="file" name="imagen[]">
</p>
<p>
<!-- Botón submit -->
<input type="submit" value="Enviar">
</p>
</form>
</body>
</html>
Borrar archivos
Para eliminar un archivo debemos usar el método unlink
.
unlink('archivo');
Tan solo hay que dar la ruta que deseamos.
unlink('subidos/coche_rojo.jpg');
Evitar que se sobrescriba
¿Qué pasa si subimos dos archivos con el mismo nombre? Pues que el anterior desaparecería, se sobrescribiría por tener el mismo nombre y guardarse en el mismo lugar. Debemos garantiza que el archivo posee un nombre único.
Un truco para solucionarlo generando un hash, o una secuencia alfanumérica única por cada archivo que sustituya al nombre. Un algoritmo muy popular es SHA-1.
En criptografía, SHA-1 (Secure Hash Algorithm 1) es una función hash criptográfica que ha sido rota, pero que sigue siendo ampliamente utilizada para generar un número hexadecimal de 40 dígitos. Su autor fue la Agencia de Seguridad Nacional de Estados Unidos, siendo usando para el procesamiento de información de Estados Unidos.
Si quisiéramos obtener un hash de un texto, deberíamos usar sha1().
echo sha1('texto');
// ea631551f5569f612dd702b900c596c2a99c0dfd
Para archivos disponemos de una función determinada llamada sha1_file()
.
echo sha1_file($_FILES['fichero_usuario']['tmp_name']);
// 2d68e69c476166978146c4f8e523ba8f87acbbc3
Si tuvieramos un archivo llamado reloj.jpg
.
$fichero_subido = $dir_subida . sha1_file($_FILES['fichero_usuario']['tmp_name']) . basename($_FILES['fichero_usuario']['name']);
echo $fichero_subido;
// subidos/2d68e69c476166978146c4f8e523ba8f87acbbc3reloj.jpg
Por muchos archivos reloj.jpg
que suban nunca se sobrescribirán, a no ser que a nivel binario sean exactamente iguales (lo cual tampoco sería un problema porque sería el mismo fichero).
Tamaño máximo
Si quieres limitar el tamaño de todos los archivos puedes hacerlo añadiendo un input
especial.
<input type="hidden" name="MAX_FILE_SIZE" value="20000" />
El value
se mide en bytes
.
Si un archivo supera nuestra frontera dará un error, pero nunca llegará a subirse.
Otra forma de cambiarlo, en este caso permanente, es modificando unas variables de PHP en su archivo de configuración.
sudo nano /etc/php/{versión}/cli/php.ini
Edita las siguientes variables si quieres limitarlo a 100Mb
.
upload_max_filesize=100Mb
post_max_size=100Mb
Igualmente valida el tamaño con PHP, es fácil manipular el límite dentro del navegador. Recuerda: nunca te fíes del usuario.
Procesamiento de imágenes
PHP no esta solamente limitado a la generación de HTML y mover archivos, también puede procesar imágenes. Existen una multitud de posibilidades.
- Redimensionar (utilizado para crear miniaturas).
- Recortar.
- Aplicar filtros de color.
- Crear imágenes (como suena).
- Añadir marcas de agua.
- Cambiar de formato.
Para crear una minuatura podrías hacerlo usando la libreria nativa Imagick.
Primero tendremos que instalarla en el sistema. Con Ubuntu o Debian es muy sencillo.
sudo apt install php-imagick
Aahora ya puedes trabajar con ella. En el siguiente ejemplo se captura imagen.jpg
y se redimensiona a 100px
de ancho. Por último se guarda con el nombre de miniatura.jpg
.
$imagen = new Imagick('imagen.jpg');
// Si se proporciona 0 como parámetro de ancho o alto,
// se mantiene la proporción de aspecto
$imagen->thumbnailImage(100, 0);
// La guarda
file_put_contents('miniatura.jpg', $imagen);
Dispone de diversas herramientas para manipular formatos tan conocidos como: JPEG, GIF, PNG y WebP (entre otros). Puedes ver más en la documentación.
Ejemplo completo
Puedes ver un ejemplo que recoge todos los casos anteriores.
- Valida que se ha adjuntado una imagen en el formulario.
- Valida que sea una imagen en jpg o png.
- Valida que no supere cierto tamaño. En este caso 2Mb.
- Crea una miniatura. En este caso de 100px de ancho.
- Cambia el nombre aleatorio para evitar posibles conflictos con otros archivos.
- Muestra la miniatura en el HTML.
<?php
//======================================================================
// VARIABLES
//======================================================================
$errorAvatar = 0;
$avatar = null;
$rutaThumbnail = null;
// Definir directorio donde se guardará
define('PATH_AVATAR', './subidos/');
define('PATH_AVATAR_THUMBNAIL', './subidos/thumbnails/');
// Tamanyo max avatar: 2 Mb
define('MAX_SIZE_AVATAR_MB', 2);
define('MAX_SIZE_AVATAR', MAX_SIZE_AVATAR_MB * 1024 * 1024);
// En /etc/php/7.4/cli/php.ini edita las siguientes variables
// upload_max_filesize=100Mb
// post_max_size=100Mb
// Anchura miniatura
define('WIDTH_THUMBNAIL', 100);
//======================================================================
// PROCESAR FORMULARIO
//======================================================================
// Comprobamos si nos llega los datos por POST
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES)) {
//-----------------------------------------------------
// Recoger avatar
//-----------------------------------------------------
// Verifica si existe el directorio, y en caso contrario lo crea
if (!is_dir(PATH_AVATAR_THUMBNAIL)) {
mkdir(PATH_AVATAR_THUMBNAIL, 0775, true);
}
// Definir la ruta final del archivo
$nombreFoto = sha1_file($_FILES['avatar']['tmp_name']) . basename($_FILES['avatar']['name']);
$ficheroSubido = PATH_AVATAR . $nombreFoto;
//-----------------------------------------------------
// Control de errores
//-----------------------------------------------------
// Tamanyo maximo
if ($_FILES['avatar']['size'] > MAX_SIZE_AVATAR) {
$errorAvatar = 1;
}
// Solo JPG y PNG
if ($_FILES['avatar']['type'] !== 'image/png' && $_FILES['avatar']['type'] !== 'image/jpeg') {
$errorAvatar = 2;
}
// Obligatorio
if ($_FILES['avatar']['size'] === 0) {
$errorAvatar = 3;
}
//-----------------------------------------------------
// Procesar imagen
//-----------------------------------------------------
if ($errorAvatar === 0) {
if (move_uploaded_file($_FILES['avatar']['tmp_name'], $ficheroSubido)) {
// Mueve el archivo de la carpeta temporal a la ruta definida
$avatar = $ficheroSubido;
// Creamos una miniatura
// No olvides instalarlo con: sudo apt install php-imagick
$imagen = new Imagick($avatar);
// Si se proporciona 0 como parámetro de ancho o alto,
// se mantiene la proporción de aspecto
$imagen->thumbnailImage(WIDTH_THUMBNAIL, 0);
$rutaThumbnail = PATH_AVATAR_THUMBNAIL . $nombreFoto;
file_put_contents($rutaThumbnail, $imagen);
}
}
}
?>
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Perfil</title>
</head>
<body>
<?php if (isset($rutaThumbnail)): ?>
<img src="<?= $rutaThumbnail; ?>" alt="mi avatar" width="<?= WIDTH_THUMBNAIL ?>">
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<p>
<label>
Foto:
<input type="file" name="avatar">
</label>
</p>
<?php if ($errorAvatar === 1): ?>
<p style="color: red">
Tamaño demasiado grande, intente que tenga menos de <?= MAX_SIZE_AVATAR_MB ?>Mb
</p>
<?php elseif ($errorAvatar === 2): ?>
<p style="color: red">
Solo admitido imagenes en JPG o PNG.
</p>
<?php elseif ($errorAvatar === 3): ?>
<p style="color: red">
Debes incluir una imagen
</p>
<?php endif; ?>
<p>
<input type="submit" value="Guardar">
</p>
</form>
</body>
</html>
Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.
¿Me invitas a un café? ☕
Puedes hacerlo usando el terminal.
ssh customer@andros.dev -p 5555
Comentarios
Nuevo comentario
Nueva replica {{ formatEllipsisAuthor(replyComment.author) }}
Escribe el primer comentario
{{ comments.length }} comentarios