Lección 9: Ficheros | Curso PHP 8.x

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>

7-1 7-2 7-3

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me invitas a un café? ☕

Puedes hacerlo usando el terminal.

ssh customer@andros.dev -p 5555

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario