Subir imagenes sin habilitar la extension php_fileinfo

Estoy trabajando en un proyecto hecho en Symfony2, un framework muy util y que simplifica mucho las cosas, me toco justamente el tema de subir una imagen al servidor.


Para ello ocupe un Bundle llamado VichUploader, el cual permite y facilita mucho el tema de subir imagenes, segui la configuracion  basica que requeria y finalmente mi config,yml quedo asi

vich_uploader:
    db_driver: orm
    gaufrette: true
    storage: vich_uploader.storage.gaufrette
    mappings:
       product_image:
            uri_prefix: /images/update
            upload_destination: product_image_fs
            namer: vich_uploader.namer_uniqid
            delete_on_remove: true #El archivo debe ser eliminado del sistema de archivos cuando se quita la entidad
            delete_on_update: true #el archivo debe ser eliminado del sistema de archivos cuando el archivo se sustituye por otra

Una vez funcionando sin errores, hice la respectiva prueba de subir una imagen, en este momento me aparecio un lindo error

Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)

La solucion a esto es en teoria muy sencilla, solo deberiamos habilitar la extension «php_infofile» y listo

extension=php_fileinfo.dll

Pero… que sucede si tenemos nuestra pagina web alojada en un Hosting que NO tiene habilitada esta extension? pues tenemos dos soluciones

  1. buscamos otro hosting
  2. Intentamos otra manera de subir una imagen

En mi caso tuve que ocupar la segunda opcion 🙁  y aqui fue donde vino una linda odisea que por lo menos logre encontrar una solucion

Primero quitaremos la validacion existente en nuestra entidad para las extensiones mimes

mimeTypes = {"image/jpg", "image/jpeg", "image/png"},

Debido a que lo reemplazaremos por una validacion con Constraint, por lo tanto nos quedaria asi

use AN\PortalBundle\Validator\Constraints as ANAssert;

class ArchivosCandidato
{
   /**
     * @Assert\File(
     *     maxSize="3M"
     * )
     * @Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
     *
     * @var File $image
     * @ANAssert\ContainsMimeType
     */
    protected $file;

   //...
}

Notese que solo estamos validando el tamaño del archivo y mediante el «ContainsMimeType», validaremos la extension

Creamos nuestro archivo «ContainsMimeType.php»

namespace AN\PortalBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class ContainsMimeType extends Constraint
{
    public $message = 'errores.ContainsMimeType';
}

Creamos nuestro archivo «ContainsMimeTypeValidator.php» el cual contendra la logica y las extensiones permitidas, esto lo puedes pasar a un parametro en tu archivo de configuracion, pero eso lo vera cada uno

namespace AN\PortalBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class ContainsMimeTypeValidator extends ConstraintValidator
{
    public function validate($file, Constraint $constraint)
    {
        //Default
        $return = false;

        //Tipos permitidos
        $listType = "image/png,image/jpeg,image/pjpeg";

        try
        {
            //Tipo actual
            $type = $file->getClientMimeType();

            //Si esta dentro del listado, seteamos true para que no muestra una excepcion
            if(substr_count($listType, $type) > 0)
                $return = true;
        }
        catch(\Exception $e)
        {}

        //Definimos si mostramos o no el mensaje de error o excepcion
        if($return == false)
        {
             $this->context->addViolation($constraint->message, array(
                    '{{ types }}'   => '"'.$listType .'"',
                ));
        }
    }
}

La idea es bastante simple, setea una variable de retorno, y si no encuentra la extension u ocurre algun error agrega un constraint con el error

Con esto ya estariamos validando la extension de nuestro archivo, pero si estamos ocupando «vich» seguiriamos teniendo problemas, esto debido a que si ocupamos

namer: vich_uploader.namer_uniqid

Esta funcion aun sigue ocupando «php_fileinfo», por lo tanto tenemos dos soluciones:

  • Ocupar «vich_uploader.namer_origname»
  • Clonar o crear nuestro propio «vich_uploader.namer_uniqid»

Yo queria seguir teniendo un ID unico por cada imagen, asi que ocupe la segunda solucion propuesta, y clone el «vich_uploader.namer_uniqid», si tu optas por la primera opcion, lo que sigue a continuacion no deberas tomarlo en cuenta.

Clonando «vich_uploader.namer_uniqid»

En mi caso tengo las rutas en parameters.yml para cambiarlos mas facilmente

#Crea nombre unico para archivos con VICH
gestor_vich_namer_uniqid.class: AN\PortalBundle\Services\Naming\UniqidNamer

En nuestro archivo «services.yml»

#Creamos un Servicio que creara un nombre Unico para subir archivos con VICH
an.gestor.vichnameruniqid:
    class: "%gestor_vich_namer_uniqid.class%"

En config.yml indicamos que estamos llamando a un nuevo namer

vich_uploader:
    db_driver: orm
    gaufrette: true
    storage: vich_uploader.storage.gaufrette
    mappings:
        product_image:
            uri_prefix: %vich_url_images%%locale%
            upload_destination: product_image_fs
            namer: an.gestor.vichnameruniqid

Nosete que aqui solo cambia el «namer:» lo demas sigue tal cual lo tienes

Por ultimo, creamos nuestro archivo «UniqidNamer.php» (ocupe el mismo nombre porque es casi lo mismo)

namespace AN\PortalBundle\Services\Naming;
use Vich\UploaderBundle\Naming\NamerInterface;

/**
 * UniqidNamer
 *
 */
class UniqidNamer implements NamerInterface
{
    /**
     * {@inheritDoc}
     */
    public function name($obj, $field)
    {
        $refObj = new \ReflectionObject($obj);

        $refProp = $refObj->getProperty($field);
        $refProp->setAccessible(true);

        $file = $refProp->getValue($obj);

        $name = uniqid();

        //Seteamos la extension
        $extension = strtolower($file->getClientOriginalExtension());

        if (strlen($extension) > 0) {
            $name = sprintf('%s.%s', $name, $extension);
        }

        return $name;
    }
}

Aqui lo unico diferente en comparacion con el archivo original es en la forma de obtener la extension, como ya validamos con nuestro Constraint que la extension fuera correcta, aqui solo ocupamos la extension original

Eso seria todo, si es que no se me olvido nada

El post oficial en donde deje esto fue aqui

Atte, Ariel