Subir imágenes y datos con el mismo formulario html

Actualizar foto de perfil
Actualizar foto de perfil

¡Hola Kumbieros! Hoy les traigo un tutorial básico para quienes deseen conocer como subir imágenes y datos con el mismo formulario. Es una pregunta recurrente que hemos tenido en nuestro chat de Slack y no es sencilla de responder por ese medio, por eso les presentaré un ejemplo práctico de la creación y actualización de usuario con foto de perfil.

Crearemos un modelo, un controlador y tres vistas, usaremos la librería Upload incluida en KumbiaPHP para gestionar la subida del archivo. Al final dejo el enlace con el código fuente en un repositorio en Github para que puedan ver el ejemplo completo, incluso descargarlo y ponerlo a funcionar.

Base de datos

Lo primero es la tabla en la base de datos:


CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL DEFAULT '0',
  `email` varchar(255) NOT NULL,
  `photo` varchar(255) DEFAULT 'default.png',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Modelo

Ahora el modelo que hereda de ActiveRecord para que nos de la capacidad de operaciones con la tabla de la base de datos:

Archivo: app/models/user.php


<?php

/**
* Clase para manejar los datos del usuario, tabla 'user'
*/
class User extends ActiveRecord 
{

}

Controlador

Ahora el controlador que nos permitirá pasar y recibir datos de las vistas. Arrancaremos con el listado de usuarios y para esto creamos la función index.

Archivo: app/controllers/user_controller.php


<?php
/**
 * Controlador para las acciones y vistas con el usuario
 */
class UserController extends AppController
{

    public function index()
    {
        $this->data = (new User)->find();
    }
}

Vistas

Y su respectiva vista.

Archivo: app/views/user/index.phtml


<h1>Lista de usuarios</h1>
<?= Html::linkAction('create', 'Crear', 'class="button"') ?>
<?php View::content() ?>
<?php if (count($data)) { ?>
    <table class="u-full-width">
        <thead>
            <tr>            
                <th>Nombre</th>
                <th>Edad</th>
                <th>Correo</th>
                <th>Foto</th>
                <th>Acciones</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($data as $item) { ?>
                <tr>
                    <td><?= $item->name ?></td>
                    <td><?= $item->age ?></td>
                    <td><?= $item->email ?></td>
                    <td><?= $item->photo ?></td>
                    <td><?= Html::linkAction("edit/$item->id", 'Editar') ?> | <?= Html::linkAction("update_photo/$item->id", 'Actualizar foto') ?></td>
                </tr> 
            <?php } ?>        
        </tbody>
    </table>
<?php } else { ?>
    <h2>No hay ningún registro</h2>
<?php } ?>

Cómo pueden observar hemos dejado listo los enlaces a las acciones de Crear, Editar y Actualizar foto, usando el helper Html::linkAction() por compatibilidad para cuando ustedes lo prueben sin importar la carpeta de instalación de la app, porque lo ideal es que los enlaces sean en HTML directamente.

Vista para subir imágenes y datos

Ahora viene lo interesante y empezaremos con un formulario con el atributo enctype igual a ‘multipart/form-data’ usando el helper Form::openMultipart(), como en la siguiente vista:

Archivo: app/views/user/create.phtml


<h1>Crear usuario</h1>
<?= Html::linkAction('', 'Listar usuarios', 'class="button"') ?>
<?php View::content() ?>
<?= Form::openMultipart() ?>
<div class="row">
    <div class="six columns">
        <label for="user_name">Nombre</label>
        <?= Form::text('user.name', 'class="u-full-width" required') ?>        
    </div>   
</div>
<div class="row">    
    <div class="six columns">
        <label for="user_email">Correo electrónico</label>
        <?= Form::email('user.email','class="u-full-width" placeholder="user@mailbox.com"  required') ?>
    </div>
</div>
<div class="row">
    <div class="six columns">
        <label for="user_age">Edad</label>
        <?= Form::number('user.age', 'required') ?>        
    </div>   
</div>
<div class="row">
    <div class="six columns">
        <label for="photo">Foto de perfil</label>
        <?= Form::file('photo') ?>        
    </div>   
</div>
<input class="button-primary" value="Enviar" type="submit">
<?= Form::close() ?>

Hemos usado el helper Form para crear los input de tipo texto, numérico y de tipo file. El de tipo file usando el Form::file() pasándole como parámetro la cadena de texto «photo» para identificar el archivo que estamos subiendo.

Añadiendo funcionalidad

Ahora crearemos la respectiva función en el controlador que nos cargará esta vista y nos permitirá crear un usuario y subir su imagen de perfil:

Archivo: app/controllers/user_controller.php


    public function create()
    {
        //se verifica si se ha enviado via POST los datos
        if (Input::hasPost('user')) {
            $obj = new User;
            //En caso que falle la operación de guardar
            if ($obj->saveWithPhoto(Input::post('user'))) {
                //Mensaje de éxito y retorna al listado
                Flash::valid('Usuario creado');
                return Redirect::to();
            }
            //Si falla se hacen persistentes los datos en el formulario
            $this->data = Input::post('user');
            return;
        }
    }

El modelo

Si lo pudieron notar el modelo User tiene un método llamado saveWithPhoto() en el cual hemos encapsulado la lógica que nos ha de permtir crear el usuario en la base de datos y subir la imagen al servidor, esto es lo correcto ya que no debe existir lógica en el controlador, el controlador debe ser lo más simple posible.
Archivo: app/models/user.php


<?php
/**
 * Clase para manejar los datos del usuario, tabla 'user'
 */
class User extends ActiveRecord
{
    /**
     * Guarda un usuario y sube la foto de un usuario.
     *
     * @param array $data Arreglo con los datos de usuario
     * @return boolean
     * @throws Exception
     */
    public function saveWithPhoto($data)
    {
        //Inicia la transacción
        $this->begin();
        //Intenta crear el usuario con los datos pasados
        if ($this->create($data)) {
            //Intenta subir y actualizar la foto
            if ($this->updatePhoto()) {
                //Se confirma la transacción
                $this->commit();
                return true;
            }
        }

        //Si algo falla se regresa la transacción
        $this->rollback();
        return false;
    }

    /**
     * Sube y actualiza la foto del usuario.
     *
     * @return boolean|null
     */
    public function updatePhoto()
    {
        //Intenta subir la foto que viene en el campo 'photo'
        if ($photo = $this->uploadPhoto('photo')) {
            //Modifica el campo photo del usuario y lo intenta actualizar
            $this->photo = $photo;
            return $this->update();
        }
    }

    /**
     * Sube la foto y retorna el nombre del archivo generado.
     *
     * @param string $imageField Nombre de archivo recibido por POST
     * @return string|false
     */
    public function uploadPhoto($imageField)
    {
        //Usamos el adapter 'image'
        $file = Upload::factory($imageField, 'image');
        //le asignamos las extensiones a permitir
        $file->setExtensions(array('jpg', 'png', 'gif', 'jpeg'));
        //Intenta subir el archivo
        if ($file->isUploaded()) {
            //Lo guarda usando un nombre de archivo aleatorio y lo retorna.
            return $file->saveRandom();
        }
        //Si falla al subir
        return false;
    }
}

No solo hemos creado la función saveWithPhoto(), ya que meter toda esa lógica en una sola función la hace más compleja y dificil de leer. Las otras dos funciones nos permiten tener unas funciones mucho mas cortas, fáciles de entender y que se puedan reutilizar. La función uploadPhoto() nos permite subir imágenes, aunque en este caso solo vamos a subir una sola.

Ejemplo en funcionamiento

Así se vería nuestro formulario ingresando a la url user/create

Subir imágenes y datos del usuario
Formulario para la creación de un usuario subiendo imagen de perfil

Completando los datos del formulario, seleccionando una imagen de nuestro disco duro y dando clic en el botón Enviar hemos creado nuestro primer registro:

Lista de usuarios creados
Lista de usuarios creados

Podemos crear un formulario solo para actualizar la foto de perfil y otro para editar los datos como las siguientes:

Actualizar foto de perfil
Actualizar foto de perfil
Editar datos del usuario
Editar datos del usuario

Ver y descargar código completo

En el siguiente enlace puedes ver el repositorio con el código fuente para subir imágenes y datos: https://github.com/henrystivens/upload-image-and-data


6 thoughts on “Subir imágenes y datos con el mismo formulario html”

  1. SALUDOS!

    Viendo tus ejemplos de código y los resultados que muestras en este pequeño ejemplo, veo que esto me ayudaría muchísimo en un pequeño proyecto que tengo y del cual como aun estoy en pañales en esto de la programación web, asta el momento he visto ejemplo códigos y copiado métodos aplicándolos a mi proyecto. Pero estoy detenido en el aspecto que en mi formulario no puedo ingresar el código correcto para adjuntar fotos o archivos al mismo y que me los guarde junto con los datos ingresados, Y ahora me topo con tu ejemplo y veo que es prácticamente lo que necesito en si, solo que no entiendo como descargar todos los códigos almacenarlos de manera local y ponerlos en practica para al final desmenuzar las partes de tu código que apliquen para mi proyecto. descargue el repositorio completo pero no entiendo como organizarlo para ejecutarlo de manera que localmente funcione, espero me puedas orientar a poder descargar tu codigo de una manera que pueda ejecutar ,, gracias

  2. Hola José,

    La pregunta puede ser obvia, pero no quiero suponer… ¿Tu proyecto lo estás realizando con KumbiaPHP?

    El código fuente como lo has podido ver está disponible en Github, el cual contiene unas instrucciones para correr en local usando Docker: https://github.com/henrystivens/upload-image-and-data/blob/master/README.md

    En este ejemplo no aborda el tema de instalación del framwework, pero puedes revisar la documentación:

    https://github.com/KumbiaPHP/Documentation/blob/master/es/to-install.md#instalar-kumbiaphp

    Si no conoces el framework la documentación oficial es: https://github.com/KumbiaPHP/Documentation

    CRUD en KumbiaPHP v1.0: http://wiki.kumbiaphp.com/V1.0_CRUD_en_KumbiaPHP_Framework

  3. Tengo 2 consultas:
    la primera es que me funciona a la perfección, pero no me muestra la imagen al editarla. ¿Cómo puedo hacer que la imagen se vea?
    la segunda pregunta es, ¿cómo puedo subir multiples imagenes en un solo id?

    Gracias amigo, su tutorial me costo un poco hacerlo funcionar pero esta muy bueno.

  4. Hola Rodrigo,

    1. Debes tener en cuenta que en el ejemplo he separado en dos vistas esa parte, una vista para ver y actualizar la imagen y otra solo para editar la información del usuario donde no se ve la foto.

    Vista para ver y editar la foto: https://github.com/henrystivens/upload-image-and-data/blob/master/default/app/views/user/update_photo.phtml

    Acción en el controlador para actualizar la foto: https://github.com/henrystivens/upload-image-and-data/blob/master/default/app/controllers/user_controller.php#L49

    Para ver la foto en cualquier vista, una vez cargada la información del usuario, con la siguiente línea puedes mostrarla:

    <?= Html::img("upload/{$user->photo}") ?>

    2. Sin entrar en detalles te diría que debes añadir varios input de tipo File y capturarlos, aunque es la manera manual de hacerlo creo que es la simple. Ya si quieres darle más usabilidad hay que trabajarlo con javascript.

    Saludos!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

© Kumbia Team