Symfony vichuploader in form collection

1.9k views Asked by At

Hi I have an entity Product who containt (OneToMany) Documentation. In Documentation i have a file, i use vichuploader to download it but i have a problem, when save my form i have an error who say me the filename is null (doctrine error). Is it possible to embed a vichuploader field on form collection ? If somebody have an idea :) i have post my files Thank you

Product.php

class Product
{
/**
 * @var int
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=255)
 * @Assert\Type(type="string")
 * @Assert\NotBlank()
 */
private $name;

/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Documentation", mappedBy="product", cascade={"persist", "remove"})
 * @ORM\JoinColumn(nullable=true)
 * @Assert\Valid()
 */
private $documentations;

/**
 * Constructor
 */
public function __construct()
{
    $this->documentations = new \Doctrine\Common\Collections\ArrayCollection();
}

/**
 * Get id
 *
 * @return integer
 */
public function getId()
{
    return $this->id;
}

/**
 * Set name
 *
 * @param string $name
 *
 * @return Product
 */
public function setName($name)
{
    $this->name = $name;

    return $this;
}

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

/**
 * Add documentation
 *
 * @param \AppBundle\Entity\Documentation $documentation
 *
 * @return Product
 */
public function addDocumentation(\AppBundle\Entity\Documentation $documentation)
{
    $documentation->setProduct($this);
    $this->documentations[] = $documentation;

    return $this;
}

/**
 * Remove documentation
 *
 * @param \AppBundle\Entity\Documentation $documentation
 */
public function removeDocumentation(\AppBundle\Entity\Documentation $documentation)
{
    $this->documentations->removeElement($documentation);
}

/**
 * Get documentations
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getDocumentations()
{
    return $this->documentations;
}
}

Documentation.php

class Documentation
{
/**
 * @var int
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Product", inversedBy="documentations")
 */
private $product;

/**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=255)
 * @Assert\Type(type="string")
 * @Assert\NotBlank()
 */
private $name;

/**
 * @Vich\UploadableField(mapping="product_documentations", fileNameProperty="fileName")
 * @Assert\NotBlank(message="Vous devez joindre un fichier", groups={"add"})
 * @Assert\File(
 *     maxSize = "20M",
 *     maxSizeMessage = "Le fichier est trop gros, il doit faire {{ limit }} {{ suffix }}"
 * )
 */
private $file;

/**
 * @ORM\Column(type="string", length=255)
 * @var string
 */
private $fileName;

/**
 * @ORM\Column(name="updated_at", type="datetime")
 * @var \DateTime $updatedAt
 */
private $updatedAt;


/**
 * Get id
 *
 * @return int
 */
public function getId()
{
    return $this->id;
}
/**
 * Constructor
 */
public function __construct()
{
    $this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}

/**
 * Set name
 *
 * @param string $name
 *
 * @return Documentation
 */
public function setName($name)
{
    $this->name = $name;

    return $this;
}

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

/**
 * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $file
 *
 * @return Documentation
 */
public function setFile(File $file = null)
{
    $this->file = $file;
    if ($file) {
        $this->updatedAt = new \DateTimeImmutable();
    }
    return $this;
}

/**
 * @return File|null
 */
public function getFile()
{
    return $this->file;
}

/**
 * @param string $fileName
 *
 * @return Documentation
 */
public function setFileName($fileName)
{
    $this->fileName = $fileName;

    return $this;
}

/**
 * @return string|null
 */
public function getFileName()
{
    return $this->fileName;
}

/**
 * Set updatedAt
 *
 * @param \DateTime $updatedAt
 *
 * @return Documentation
 */
public function setUpdatedAt($updatedAt)
{
    $this->updatedAt = $updatedAt;

    return $this;
}

/**
 * Get updatedAt
 *
 * @return \DateTime
 */
public function getUpdatedAt()
{
    return $this->updatedAt;
}

/**
 * Set product
 *
 * @param \AppBundle\Entity\Product $product
 *
 * @return Documentation
 */
public function setProduct(Product $product = null)
{
    $this->product = $product;

    return $this;
}

/**
 * Get product
 *
 * @return \AppBundle\Entity\Product
 */
public function getProduct()
{
    return $this->product;
}
}

DocumentationType.php

class DocumentationType extends AbstractType
{

/**
 * @param FormBuilderInterface $builder
 * @param array                $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add(
            'name',
            TextType::class,
            array(
                'attr'  => array(
                    'placeholder' => 'Nom'
                ),
                'label' => 'Nom :'
            )
        )
        ->add(
            'file',
            FileType::class,
            array(
                'attr' => array(
                    'placeholder' => 'Fichier'
                ),
                'required' => false,
                'label'        => 'Fichier :'
            )
        )
    ;
}

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => Documentation::class,
        'attr' => array(
            'novalidate' => 'novalidate'
        )
    ));
}
}

ProductType.php

class ProductType extends AbstractType
{
/**
 * {@inheritdoc}
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('name')
        ->add(
            'documentations',
            CollectionType::class,
            array(
                'entry_type' => DocumentationType::class,
                'by_reference' => false,
                'allow_add'    => true,
                'allow_delete' => true,
                'label' => 'Fichier(s) :',
                'prototype' => true
            )
        )
    ;
}

/**
 * {@inheritdoc}
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Product'
    ));
}

/**
 * {@inheritdoc}
 */
public function getBlockPrefix()
{
    return 'appbundle_product';
}
}

new.html.twig

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

{% block body %}
<h1>Product creation</h1>

{{ form_start(form) }}
    {{ form_row(form.name) }}
    <a href="#" id="add_documentation" data-prototype-add="div#appbundle_product_documentations" class="btn btn-default btn-collection-add">
        <span class="glyphicon glyphicon-plus"></span> Ajouter une documentation
    </a>
    {{ form_row(form.documentations) }}
    <input type="submit" value="Create" />
{{ form_end(form) }}

<ul>
    <li>
        <a href="{{ path('product_index') }}">Back to the list</a>
    </li>
</ul>

<script>
    // On ajoute un nouveau champ à chaque clic sur le lien d'ajout.
    $('.btn-collection-add').click(function (e) {
        addDocumentation($($(this).data('prototype-add')));
        e.preventDefault(); // évite qu'un # apparaisse dans l'URL
        return false;
    });

    // La fonction qui ajoute un formulaire CategoryType
    function addDocumentation($container) {
        var index = $container.find(':input').length;
        // Dans le contenu de l'attribut « data-prototype », on remplace :
        // - le texte "__name__" qu'il contient par le numéro du champ
        var template = $container.attr('data-prototype').replace(/__name__/g, index);
        // On crée un objet jquery qui contient ce template
        var $prototype = $(template);
        // On ajoute le prototype modifié à la fin de la balise <div>
        $container.append($prototype);
        // Enfin, on incrémente le compteur pour que le prochain ajout se fasse avec un autre numéro
        index++;
    }

    // Ajout du listener sur le clic du lien pour effectivement supprimer l'entrée de la collection.
    $(document).on('click', '.btn-collection-delete', function (e) {
        $(this).closest('.panel').remove();
        e.preventDefault(); // évite qu'un # apparaisse dans l'URL
        return false;
    });
</script>

{% endblock %}

NewAction on controller

public function newAction(Request $request)
{
    $product = new Product();
    $form = $this->createForm(
        'AppBundle\Form\ProductType',
        $product,
        array(
            'validation_groups' => array('add')
        )
    );
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($product);
        $em->flush();

        return $this->redirectToRoute('product_show', array('id' => $product->getId()));
    }

    return $this->render('product/new.html.twig', array(
        'product' => $product,
        'form'    => $form->createView(),
    ));
}

My vich_uploader section on config.yml

vich_uploader:
db_driver: orm
mappings:
    product_documentations:
        uri_prefix:         /products/documentations
        upload_destination: '%kernel.root_dir%/../web/products/documentations'
        inject_on_load:     false
        delete_on_update:   true
        delete_on_remove:   true
1

There are 1 answers

0
eldiablo62 On BEST ANSWER

I have found the solution I have lost * @Vich\Uploadable in my documentation entity description