Icono del sitio KumbiaPHP Framework PHP en español

Subir imágenes y datos con el mismo formulario html

¡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

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

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

Actualizar foto de perfil
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

Salir de la versión móvil