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
I have found the solution I have lost * @Vich\Uploadable in my documentation entity description