jueves, 25 de septiembre de 2014

Upload Files and persist to Doctrine Entity - Parte I - User Entity

Para poder subir archivos (en este caso imágenes), poder visualizarlos, guardarlos en una carpeta con un hash por nombre  y guardar el nombre original, cambiarlos al editarlos y borrarlos al eliminar el registro, primero debemos crear una entidad, o editar la que tengamos.

En este caso vamos darle a los usuarios la posibilidad de subir una foto para su perfil, para ello editamos la entidad User que tenemos creada:

nano src/Petramas/MainBundle/Entity/User.php
debemos agregar 2 nuevos campos para ser persistidos:

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

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

y luego 2 campos virtuales que no serán persistidos:

    private $file;
    
    private $temp;

las anotaciones hacen la diferencia.

Si en este punto actualizamos la entidad:

php app/console doctrine:generate:entities Petramas

se generarán los getters y setters de los 2 campos que tienen anotaciones:

    /**
     * Set path
     *
     * @param string $path
     * @return GelImage
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

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

    /**
     * Set original
     *
     * @param string $original
     * @return GelImage
     */
    public function setOriginal($original)
    {
        $this->original = $original;

        return $this;
    }

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

luego agregamos los métodos get y set de $file

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile($file)
    {
        $this->file = $file;
        // check if we have an old image path
        if (isset($this->path)) {
            // store the old name to delete after the update
            $this->temp = $this->path;
            $this->path = null;
        } else {
            $this->path = 'initial';
        }
    }

    /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getFile()
    {
        return $this->file;
    }

ahora agregamos los métodos que devuelven las rutas a emplear para la subida la imágen:

    /**
     * Returns the absolute path to the file
     * @return string
     */
    public function getAbsolutePath()
    {
        return null === $this->path
            ? null
            : $this->getUploadRootDir().'/'.$this->path;
    }

    /**
     * Returns the web path
     * @return string
     */
    public function getWebPath()
    {
        return null === $this->path
            ? null
            : $this->getUploadDir().'/'.$this->path;
    }
    /**
     * Returns the absolute directory path where uploaded documents should be saved
     * @return string 
     */ 
    protected function getUploadRootDir()
    {
        return __DIR__ . '/../../../../web/' . $this->getUploadDir();
    }
 
    /**
     * Gets rid of the __DIR__ so it doesn't screw up when displaying uploaded doc/image in the view
     * @return string  
     */
    public function getUploadDir()
    {
        return 'uploads/documents';
    }

Así que debemos crear la carpeta donde se subirán las imágenes:

mkdir web/uploads/documents
chmod 777 web/uploads/documents

ahora vamos a cambiar el nombre del archivo subido por un hash a ser almacenado en el campo $path

    /**
     * Renames uploaded file
     * @return string 
     */ 
    public function preUpload()
    {
        if (null !== $this->getFile()) {
            // do whatever you want to generate a unique name
            $filename = sha1(uniqid(mt_rand(), true));
            $this->path = $filename.'.'.$this->getFile()->guessExtension();
            $this->setOriginal($this->getFile()->getClientOriginalName());
        }
    }

luego agregamos lifecycle callbacks a la entidad, si no lo hemos hecho antes al persistir fecha y hora de la creación y edición de usuarios.

/**
 * User
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Petramas\MainBundle\Repository\UserRepository")
 * @ORM\HasLifecycleCallbacks
 */
class User
{

por fin creamos el método para subir archivos y lo llamamos después de que se registra en al base de datos un nuevo usuario o cuando lo actualizamos (PostPersist, PostUpdate).

    /**
     * @ORM\PostPersist()
     * @ORM\PostUpdate()
     */
    public function upload()
    {
        if (null === $this->getFile()) {
            return;
        }

        // if there is an error when moving the file, an exception will
        // be automatically thrown by move(). This will properly prevent
        // the entity from being persisted to the database on error
        $this->getFile()->move($this->getUploadRootDir(), $this->path);

        // check if we have an old image
        if (isset($this->temp)) {
            // delete the old image
            unlink($this->getUploadRootDir().'/'.$this->temp);
            // clear the temp image path
            $this->temp = null;
        }
        $this->file = null;
    }

y llamamos a preUpload antes de persistir o actualizar un usuario:

    /**
     * Defaults when inserting a user
     * @ORM\PrePersist()
     */
    public function prePersistTasks()
    {
        $this->setCreated(new \DateTime());
        $this->setUpdated(new \DateTime());
        $this->setStatus(1);
        $this->preUpload();
    }
    
    /**
     * Defaults when updating a user
     * @ORM\PreUpdate()
     */
    public function preUpdateTasks()
    {
        $this->setUpdated(new \DateTime());
        $this->preUpload();
    }

por último creamos el método llamado al eliminar un usuario:

    /**
     * @ORM\PostRemove()
     */
    public function removeUpload()
    {
        if ($file = $this->getAbsolutePath()) {
            unlink($file);
        }
    }