Trabajar con imágenes en base de datos con Symfony2

Czhincksx

Buenas. Soy el que preguntó el otro día qué framework utilizar para desarrollo web.

Como quiero darle un toque profesional me he decidido por Symfony2.

Estoy siguiendo esta guía y va bastante bien: http://gitnacho.github.io/symfony-docs-es/

El caso es que estoy un poco atascado en el tema de la persistencia de imágenes. En la web que voy a hacer, un usuario administrador podrá através de un formulario (o de drag and drop, ya veremos) subir imágenes al servidor para que el resto de usuarios puedan acceder a las galerías.

El caso es que no sé cómo trabajar con las entidades de imágenes. Todo eso de que debo almacenar la ruta y tal me lía porque no sé a qué ruta se refiere. Yo pensaba, antes de empezar con esto, que sería tan sencillo como almacenarlas en un campo FILE o similar y guardarlas o leerlas como cualquier otro atributo de una clase.

¿Podríais explicarme de forma muy resumida y esquemática los pasos que debo seguir para trajar con imágenes?

Supongamos que quiero subirlas desde un formulario. Éste se encontraría en un archivo html.twig, ¿no?

Sería crear la entidad, el formulario, el routing y el controlador que sería el encargado de subirlos al servidor. ¿Me equivoco?

Gracias, saludos.

Merkury

#1 Tienes que crear la Entidad, el formtype (que se genera de la entidad) y luego el controlador que sirva el form y por ultimo la ruta.

Y luego tiene unas cuantas particularidades, mira te dejo un ejemplo mio que modifiqué de la guia de gitnacho XD:

Entidad Picture;


namespace QRF\AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

[b]Entity[/b]
/**
 * Picture
 *
 * @ORM\Table()
 * @ORM\Entity
 * @ORM\Entity(repositoryClass="QRF\AdminBundle\Entity\PictureRepository")
 */
class Picture
{
   /**
     * @var string
     *
     * @ORM\Column(name="pic_path", type="string", length=255)
     */
    private $picPath;

    /**
     * @var string
     *
     * @ORM\Column(name="pic_title", type="string", length=255, nullable=true)
     */
    private $picTitle;

    /**
     * @var string
     *
     * @ORM\Column(name="pic_alt", type="string", length=255, nullable=true, nullable=true)
     *
     */
    private $picAlt;
/**
     * @Assert\File(maxSize="6000000")
     *
     */
    private $picFile;
 /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setPicFile(UploadedFile $picFile = null)
    {
        $this->picFile = $picFile;
    }

 /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getPicFile()
    {
        return $this->picFile;
    }
 /**
     * Set picPath
     *
     * @param string $picPath
     * @return Picture
     */
    public function setPicPath($picPath)
    {
        $this->picPath = $picPath;

        return $this;
    }

    /**
     * Get picPath
     *
     * @return string
     */
    public function getPicPath()
    {
        return $this->picPath;
    }

    /**
     * Set picTitle
     *
     * @param string $picTitle
     * @return Picture
     */
    public function setPicTitle($picTitle)
    {
        $this->picTitle = $picTitle;

        return $this;
    }

    /**
     * Get picTitle
     *
     * @return string
     */
    public function getPicTitle()
    {
        return $this->picTitle;
    }

    /**
     * Set picAlt
     *
     * @param string $picAlt
     * @return Picture
     */
    public function setPicAlt($picAlt)
    {
        $this->picAlt = $picAlt;

        return $this;
    }

    /**
     * Get picAlt
     *
     * @return string
     */
    public function getPicAlt()
    {
        return $this->picAlt;
    }

  public function getPicAbsoluteWebPath()
    {
        return null === $this->picPath
            ? null
            : $this->getUploadRootPicDir().'/'.$this->picPath;
    }

    public function getWebPicPath()
    {
        return null === $this->picPath
            ? null
            : $this->getUploadPicDir();
    }

    public function getUploadRootPicDir()
    {
        return __DIR__.'/../../../../web/'.$this->getUploadPicDir();
    }

    public function getUploadPicDir()
    {
        return 'bundles/adminbundle/img/Pictures/';
    }
  public function upload()
    {
        if (null === $this->getPicFile()) {
            return;
        }

        $this -> getPicFile()->move(
            $this->getUploadRootPicDir(),
            $this->getPicFile()->getClientOriginalName()
        );


    $this->picPath = $this->getUploadPicDir().$this->getPicFile()->getClientOriginalName();

    $this->picFile = null;

} 

Básicamente en la entidad tienes que crearte los metodos para acceder a la ruta temporal, a la ruta web y lo necesario para pillar el archivo, una vez eso una función upload en la que combinas los métodos. En la entidad no te olvides de use Symfony\Component\HttpFoundation\File\UploadedFile;

Una vez tienes eso, te vas al formtype y te creas algo así:
FormType

class PictureType extends AbstractType
{
 public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('picFile', 'file', array('required' => true))
            ->add('picTitle', 'text', array('attr' => array('placeholder' => '<img> tag title property')))
            ->add('picAlt', 'text', array('attr' => array('placeholder' => '<img> tag alt property')))
     ;
    }

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'QRF\AdminBundle\Entity\Picture'
    ));
}

public function getName()
{
    return 'qrf_adminbundle_picturetype';
}
}

Y en el controlador debería quedarte así:
Controller

 public function uploadPicAction()
    {
        $picture = new Picture();
        $form = $this -> createForm(new PictureType(), $picture);

    if($this->getRequest()->isMethod('POST'))
    {
        $form->bind($this->getRequest());
        if($form->isValid())
        {
            $picture->upload();
            $em = $this -> getDoctrine() -> getManager();
            $em->persist($picture);
            $em->flush();
 
}else
            {
                $this->get('session')->getFlashBag()->add('fail', 'Fallo en el envío del formulario');
                return $this->redirect($this->generateUrl('qrf_ruta'));
            }
        }

    return $this->render('QRFAdminBundle:Dir:file.html.twig', array('newPicForm' => $form->createView()));
}

Fijate que una vez comprobado el isValid() del form, lo primero que hago es hacer el upload.

Y en la vista cuando crees el form acuerdate de esto te lo dejo en formato twig:
Enctype si no el fichero no se sube!

 <form action="{{  path('qrf_ruta') }}" method="POST" class="add_form" {{ form_enctype(newPicForm) }}>

Espero que todo esto que te dejo (que yo me volví loquisimo para conseguir hacerlo BIEN) te sirva :P si tienes dudas ya sabes

2 2 respuestas
Czhincksx

#2 ¡Muchas gracias! ¡Voy a probarlo ya mismo! :D

Edit:

No me tira XD

El primer fallo que me dio fue al crear la tabla en la base de datos con el --force, me decía que no tenía campo ID. Lo cree y me dejó. La base de datos está así:

La entidad Picture así:

<?php

namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;


/**
 * Picture
 *
 * @ORM\Table()
 * @ORM\Entity
 * @ORM\Entity(repositoryClass="QRF\AdminBundle\Entity\PictureRepository")
 */
class Picture
{
   /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */ 
    protected $id;
   /**
     * @var string
     *
     * @ORM\Column(name="pic_path", type="string", length=255)
     */
    private $picPath;

    /**
     * @var string
     *
     * @ORM\Column(name="pic_title", type="string", length=255, nullable=true)
     */
    private $picTitle;

    /**
     * @var string
     *
     * @ORM\Column(name="pic_alt", type="string", length=255, nullable=true, nullable=true)
     *
     */
    private $picAlt;
/**
     * @Assert\File(maxSize="6000000")
     *
     */
    private $picFile;
    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
    
/** * Sets file. * * @param UploadedFile $file */ public function setPicFile(UploadedFile $picFile = null) { $this->picFile = $picFile; } /** * Get file. * * @return UploadedFile */ public function getPicFile() { return $this->picFile; } /** * Set picPath * * @param string $picPath * @return Picture */ public function setPicPath($picPath) { $this->picPath = $picPath; return $this; } /** * Get picPath * * @return string */ public function getPicPath() { return $this->picPath; } /** * Set picTitle * * @param string $picTitle * @return Picture */ public function setPicTitle($picTitle) { $this->picTitle = $picTitle; return $this; } /** * Get picTitle * * @return string */ public function getPicTitle() { return $this->picTitle; } /** * Set picAlt * * @param string $picAlt * @return Picture */ public function setPicAlt($picAlt) { $this->picAlt = $picAlt; return $this; } /** * Get picAlt * * @return string */ public function getPicAlt() { return $this->picAlt; } public function getPicAbsoluteWebPath() { return null === $this->picPath ? null : $this->getUploadRootPicDir().'/'.$this->picPath; } public function getWebPicPath() { return null === $this->picPath ? null : $this->getUploadPicDir(); } public function getUploadRootPicDir() { return __DIR__.'/../../../../web/'.$this->getUploadPicDir(); } public function getUploadPicDir() { return 'bundles/adminbundle/img/Pictures/'; } public function upload() { if (null === $this->getPicFile()) { return; } $this -> getPicFile()->move( $this->getUploadRootPicDir(), $this->getPicFile()->getClientOriginalName() ); $this->picPath = $this->getUploadPicDir().$this->getPicFile()->getClientOriginalName(); $this->picFile = null; } }

En el routing tengo:

acme_store_image:
    path:  /img
    defaults: { _controller: AcmeStoreBundle:Default:img}

PictureType.php:

<?php

namespace Acme\StoreBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class PictureType extends AbstractType
{
 public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('picFile', 'file', array('required' => true))
            ->add('picTitle', 'text', array('attr' => array('placeholder' => '<img> tag title property')))
            ->add('picAlt', 'text', array('attr' => array('placeholder' => '<img> tag alt property')))
     ;
    }

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Acme\StoreBundle\Entity\Picture'
    ));
}

public function getName()
{
    return 'acme_storebundle_picturetype';
}
}
?>

El DefaultController del bundle Store:

public function imgAction()
    {
        $picture = new Picture();
        $form = $this -> createForm(new PictureType(), $picture);

    if($this->getRequest()->isMethod('POST'))
    {
        $form->bind($this->getRequest());
        if($form->isValid())
        {
            $picture->upload();
            $em = $this -> getDoctrine() -> getManager();
            $em->persist($picture);
            $em->flush();
 
         }else
        {
            $this->get('session')->getFlashBag()->add('fail', 'Fallo en el envío del formulario');
            return $this->redirect($this->generateUrl('acme_store_image'));
        }
    }

    return $this->render('AcmeStoreBundle:Default:img.html.twig', array('PictureType' => $form->createView()));
}

Por último el img.html.twig es así:

 {% extends '::base.html.twig' %}

 {% block body %}
     <form action="{{  path('acme_store_image') }}" method="POST" class="add_form" {{ form_enctype(PictureType) }}>
 {% endblock %}

No me queda muy claro por ejemplo en tu código a qué hace referencia por ejemplo newPicForm. Es algo que aparece sólo en el controlador y en el hmtl.twig?

1 respuesta
Czhincksx

#2 Te cito porque supongo que no has visto el edit al haberte citado antes de hacerlo.

Aunque bueno, la duda va para cualquiera que me pueda ayudar XD He creado un formulario normal como el del ejemplo de gitnacho. Un Task. Todo funciona bieny se sube correctamente a la base de datos.

Al hacerlo he aprendido como va el tema, creo yo, pero me sigue sin funcionar el formulario de subir imágenes. De momento mi problema básico es que no me renderiza el formulario. He visto que faltaban algunos includes (use Acme\StoreBundle\Form\Type\PictureType; por ejemplo) y creo que lo tengo exactamente igual que el otro formulario, pero no va :(

Edit:

He subido los ficheros a github

https://github.com/Cebado/pruebasSymfony/tree/master/PruebaSymfony2/src/Acme/StoreBundle

Está todo en el StoreBundle

El fallo está en el DefaultController, en el Entity, en el Form, en el View o en el Routing. Lo comparo con el Task que se encuentra en el mismo Bundle y no encuentro diferencias que hagan que ni se renderice el formulario. :(

1 respuesta
Merkury

#4 Leete el tema de formularios y utilizar FormTypes y el uso de twig que la verdad va MUY BIEN para agilizar el desarrollo front-end

Lo que dices en #3 del newPicForm es un objeto del tipo newTypeForm, que sirve para renderizar un formulario con todos los campos de la entidad.

Basicamente en el controller creo un objeto del tipo Picture y lo llamo $picture, luego defino otro objeto ($form) en el que instancio un nuevo FormType del tipo PictureType y le paso el objeto $picture para que al subir se persista bien.

Y al final de todo en el render envio el form como 'newPicForm' => $form y esto lo que hace es pasar a twig todo el formulario.

Si pones en la vista {{ newPicForm }} twig te renderiza esto:

Es un poco lioso de explicar... XD

1 respuesta
Czhincksx

#5 Muchas gracias por tu ayuda y por las molestias que te estás tomando.

La verdad es que estoy en uno de esos momentos en los que por mucho que mires el código no sabes qué falla :/

Estas son las novedades:

En PictureType he comentado la función setDefaultOptions y compila, pero el código que escupe es el siguiente:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Welcome!</title>
                <link rel="icon" type="image/x-icon" href="/PruebaSymfony2/web/favicon.ico" />
    </head>
    <body>
        
<form action="/PruebaSymfony2/web/img" method="POST" class="add_form" enctype="multipart/form-data"> </body> </html>

Vamos que crea el formulario pero no le añade campos :/

2 respuestas
Gantorys

Siempre me ha dado pereza mirar Symfony2, ahora viendo esto más aún

2 respuestas
Cobre

#6 creo que te falta el {{ form_widget(PictureType) }} y cerrar el </form> y ponerle el boton de enviar, desde la 2.3 le puedes añadir botones desde el FormType con el

->add('add','submit')

y sino a mano en el twig

{% extends '::base.html.twig' %}

 {% block body %}
     <form action="{{  path('acme_store_image') }}" method="POST" class="add_form" {{ form_enctype(PictureType) }}>
 {{ form_widget(PictureType) }}
</form>
 {% endblock %}
2 respuestas
Merkury

#6 Si comentas el setDefaults options mal, pegame la parte del controlador y el form type.

#7 Parece mas complicado de lo que es.

#8 Agregar botones en el formType es una mierda porque si luego quieres manejarlos con jQuery o algo asi GG tiens que montarlo con el array 'attr' pasar el array de propiedades etc, es mucho mas practico agregarlo directamente en la vista como toda la vida.

2 respuestas
Cobre

#9 yo aun no lo he usado la verdad, pero parece interesante desde el controlador puedes comprobar que boton ha apretado con:

$nextAction = $form->get('saveAndAdd')->isClicked()
        ? 'task_new'
        : 'task_success';

Bueno y decirle a #7 que gracias a darle duro a Symfony2 ya tengo trabajo :-)

2 respuestas
Merkury

#10 ¿En serio? ¿Donde? XD

Yo tambien tengo trabajo programando en Symfony, pero no gracias a él.

Cobre

Bueno no es que me lo diera symfony en si, termine las practicas del ciclo(DAW) que era un proyecto en symfony,.. y como ya tenia una base, pues ahí me quede xd

Gantorys

#10 Sí, es cierto que Symfony es de los frameworks más solicitados de PHP, por eso mi intención de mirarlo más a fondo. Lo que pasa que me he hecho a Laravel y cuesta cambiarme (aunque Laravel utiliza componentes de Symfony, puede que eso me quite algo de pereza xd)

1 respuesta
Cobre

#13 Lo mas pesado de Symfony es empezar, yo cuando empeze me acuerdo de dejarlo un par de semanas de lo pesado que se me hacia, pero ahora mismo no lo cambio por nada.
Tambien he de decir que no he tocado otro framework, pase de PHP a pelo a Symfony xd

Merkury

Pues joder yo empece y con la guia de gitnacho y molestando a KoRMuZ cada 2x3 y se me hizo bastante ligero.

Antes había probado CI y la verdad salí hasta los cojones del routing y de el tema de BBDD, llegue a symfony y fue como ohhh si oohh si si si XD

Cobre

jajaja, yo por desgracia no tenia a nadie y la verdad no suelo preguntar en los foros, asi me costo instalar el sonata admin como unos 3 dias y el fosuser otros tanto,...ahora tengo al lado gente que tb trabaja con symfony y vas descubriendo cosillas nuevas, como el validador de e-mail te comprueba si el dominio existe, osea que si pones en un campo email [email protected] te lo echa para atras xd

1 respuesta
Merkury

#16 El email no lo valida XD, vamos teniendo el módulo FOS, yo tengo en la BBDD metidos usuarios con qatag@gdsgaa y pasa eh XD

#16 Una pregunta asi tonta tu sabes configurar el mailer en localhost? :3

1 respuesta
Cobre

#17 http://symfony.com/doc/current/reference/constraints/Email.html
el checkMx : true, te comprueba el dominio.
pruebalo y me dices, que la verdad, yo aun no he tocado nada de envios de emails, el lunes le pregunto al que me dijo lo del checkmx y te digo.

1 respuesta
Merkury

#18 Ida de perola no he dicho nada, lo probare :D

Czhincksx

#8 no estoy en casa pero en cuanto llegue lo miro.

#9 está todo el código en el enlace a git de #4, desde el móvil no puedo poner esa parte ahora.

Creo que compila al comentar esa función porque no se crea bien el formulario e intenta darle valor por defecto a unos campos que no existen o algo así.

Muchas gracias a todos por la ayuda y por participar en el hilo :D Me estáis ayudando mucho. Cuando aprendo un lenguaje nuevo o empiezo con un nuevo framework me cuesta muchísimo siempre.

1 respuesta
Merkury

#20 Como a todos XD

Luego le echo un vistazo.

#21 A ver he revisado tu código y el error esta en la vista, creo que me explique muy mal XD.

Lo del enctype hay que ponerlo porque estás subiendo una fichero por el POST pero para generar el formulario sería así:


  <h3>Agregar foto</h3>
            <form action="{{  path('qrf_admin_uploadpic') }}" method="POST" class="add_form" {{ form_enctype(newPicForm) }}>
                <fieldset>
                  {{ form_widget(newPicForm) }}
                  {{ form_error(new_PicForm) }}
                </fieldset>
                <input type="submit" value="Enviar" class="btn btn-success">
                <a class="btn btn-danger" href="{{ path('qrf_admin_homepage') }}">Volver</a>
            </form>

Esto te generaría el form con los nombres que tienes el FormType (Altpic, PicFile, etc...) para que ya quedase un form totalmente personalizado se haría así:


  <h3>Agregar foto</h3>
            <form action="{{  path('qrf_admin_uploadpic') }}" method="POST" class="add_form" {{ form_enctype(newPicForm) }}>
                <fieldset>
                    {{ form_label(newPicForm.picFile, 'Foto') }}
                    {{ form_widget(newPicForm.picFile) }}
                    {{ form_label(newPicForm.picTitle, 'Foto Title') }}
                    {{ form_widget(newPicForm.picTitle) }}
                    {{ form_label(newPicForm.picAlt, 'Foto Alt') }}
                    {{ form_widget(newPicForm.picAlt) }}
                    {{ form_label(newPicForm.QRFile, 'Código QR') }}
                    {{ form_widget(newPicForm.QRFile) }}
                    {{ form_label(newPicForm.qrcodeTitle, 'Código QR Title') }}
                    {{ form_widget(newPicForm.qrcodeTitle) }}
                    {{ form_label(newPicForm.qrcodeAlt, 'Código QR Atl') }}
                    {{ form_widget(newPicForm.qrcodeAlt) }}
                    {{ form_label(newPicForm.enabled, 'Visible') }}
                    {{ form_widget(newPicForm.enabled) }}
                    {{ form_rest(newPicForm) }}
                </fieldset>
                <input type="submit" value="Enviar" class="btn btn-success">
                <a class="btn btn-danger" href="{{ path('qrf_admin_homepage') }}">Volver</a>
            </form>

Prueba y ya contaras :D

1 respuesta
Czhincksx

#21 Qué va, no fue culpa tuya. Lo que pasa es que estoy muy espeso jajaja. Muchas gracias, ya me funciona :D

Usuarios habituales

  • Czhincksx
  • Merkury
  • Cobre
  • Gantorys