Syfmony: upload files with dropzone - symfony

I'm developing a king of simple CMS, with Symfony 4.1.
Regarding my question, we have 2 entities:
Post Entity:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post extends BaseEntity
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="boolean")
*/
private $status;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Category", inversedBy="posts")
*/
private $categories;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Picture", mappedBy="post", orphanRemoval=true, cascade={"persist"})
*/
private $pictures;
/**
* #Assert\All({#Assert\Image(mimeTypes="image/jpeg")})
*
*/
private $pictureFiles;
/**
* Post constructor.
*/
public function __construct()
{
$this->categories = new ArrayCollection();
$this->pictures = new ArrayCollection();
}
/**
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return null|string
*/
public function getContent(): ?string
{
return $this->content;
}
/**
* #param string $content
* #return Post
*/
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
/**
* #return bool|null
*/
public function getStatus(): ?bool
{
return $this->status;
}
/**
* #param bool $status
* #return Post
*/
public function setStatus(bool $status): self
{
$this->status = $status;
return $this;
}
/**
* #return Collection|Category[]
*/
public function getCategories(): Collection
{
return $this->categories;
}
/**
* #param Category $category
* #return Post
*/
public function addCategory(Category $category): self
{
if (!$this->categories->contains($category)) {
$this->categories[] = $category;
}
return $this;
}
/**
* #param Category $category
* #return Post
*/
public function removeCategory(Category $category): self
{
if ($this->categories->contains($category)) {
$this->categories->removeElement($category);
}
return $this;
}
/**
* #return Collection|Picture[]
*/
public function getPictures(): Collection
{
return $this->pictures;
}
/**
* #param Picture $picture
* #return Post
*/
public function addPicture(Picture $picture): self
{
if (!$this->pictures->contains($picture)) {
$this->pictures[] = $picture;
$picture->setPost($this);
}
return $this;
}
/**
* #param Picture $picture
* #return Post
*/
public function removePicture(Picture $picture): self
{
if ($this->pictures->contains($picture)) {
$this->pictures->removeElement($picture);
if ($picture->getPost() === $this) {
$picture->setPost(null);
}
}
return $this;
}
/**
* #return mixed
*/
public function getPictureFiles()
{
return $this->pictureFiles;
}
/**
* #param $pictureFiles
* #return Post
*/
public function setPictureFiles($pictureFiles): self
{
foreach ($pictureFiles as $pictureFile) {
/** #var Picture $picture */
$picture = new Picture();
$picture->setImageFile($pictureFile);
$this->addPicture($picture);
}
$this->pictureFiles = $pictureFiles;
return $this;
}
}
Picture Entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
/**
* #ORM\Entity(repositoryClass="App\Repository\PictureRepository")
*/
class Picture
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var File|null
* #Assert\Image(mimeTypes="image/jpeg")
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*/
private $filename;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="pictures")
* #ORM\JoinColumn(nullable=false)
*/
private $post;
/**
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return File|null
*/
public function getImageFile(): ? File
{
return $this->imageFile;
}
/**
* #param File|null $imageFile
* #return Picture
*/
public function setImageFile(? File $imageFile): self
{
$this->imageFile = $imageFile;
return $this;
}
/**
* #return string|null
*/
public function getFilename(): ?string
{
return $this->filename;
}
/**
* #param string $filename
* #return Picture
*/
public function setFilename(string $filename): self
{
$this->filename = $filename;
return $this;
}
/**
* #return Post|null
*/
public function getPost(): ?Post
{
return $this->post;
}
/**
* #param Post|null $post
* #return Picture
*/
public function setPost(?Post $post): self
{
$this->post = $post;
return $this;
}
}
So for adding a Post, I have a PostType:
<?php
namespace App\Form;
use App\Entity\Category;
use App\Entity\Post;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class PostType
* #package App\Form
*/
class PostType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('content', CKEditorType::class)
->add('categories', EntityType::class,
[
'class' => Category::class,
'required' => true,
'choice_label' => 'name',
'multiple' => true,
]
)
->add('pictureFiles', FileType::class,
[
'required' => false,
'multiple' => true,
'label' => 'Add files...',
'attr' =>
[
'action' => '%kernel.project_dir%/public/media/posts'
]
]
)
->add('status')
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Post::class,
]);
}
}
The view corresponding to that form:
{% form_theme form '/admin/form/switch_btn_layout.html.twig' %}
{{ form_start(form) }}
{{ form_errors(form) }}
<div class="form-row">
<div class="col-md-6">
{{ form_row(form.name) }}
{{ form_row(form.categories) }}
{{ form_row(form.status) }}
</div>
<div class="col-md-6 dropzone" id="postDropzone">
{{ form_row(form.pictureFiles, {'attr': {'class': 'dropzone'}} ) }}
<div class="dropzone-previews" style="border: 1px solid red"></div>
</div>
</div>
<div class="form-group">
{{ form_row(form.content) }}
</div>
<div class="form-group">
{{ form_row(form.status) }}
</div>
{{ form_rest(form) }}
<button class="btn btn-success btn-lg btn-block" id="postSubmit">
{{ button_label|default('Save') }}
</button>
{{ form_end(form) }}
As you can see, the "input" for files as the dropzone css class.
Indeed, my project include the oneup_uploader bundle, for dropzone.
Here the configuration for oneup_uploader:
oneup_uploader:
mappings:
# This is a mapping example, remove it and create your own mappings.
post_image:
frontend: dropzone
namer: oneup_uploader.namer.uniqid
storage:
directory: '%kernel.project_dir%/public/media/posts'
And my script for Dropzone:
Dropzone.autoDiscover = false;
var postDropzone = new Dropzone('.dropzone', {
url: '%kernel.project_dir%/public/media/posts',
// url: 'file/post',
maxFiles: 10,
addRemoveLinks: true,
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
});
postDropzone.on("addedfile", function (file) {
file.previewElement.addEventListener("click", function () {
postDropzone.removeFile(file);
})
});
The issue for me is:
no file is save in the folder
the Post entity is save in my DB, but nothing is save for Pictures.
I also tried to not use OneUploaderBundle, and use VichUploader: the saving part in DB is perfect, but I can't link it to dropzone.
Some help guys ?
Thanks a lot !

Might be useful for new visitors.
You can use a library that extends Symfony Form and adds a new type DropzneType.
1.Install the library
composer require emrdev/symfony-dropzone
This way you will have a new form type DropzoneType
2.Use the type in your form like this
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
{
// userFiles is OneToMany
$builder->add('userFiles', DropzoneType::class, [
'class' => File::class,
'maxFiles' => 6,
'uploadHandler'=>'uploadHandler', // route name
'removeHandler'=> 'removeHandler'// route name
]);
}
Change the uploadHandler and removeHandler options to your endpoints
3.Route uploadHandler/removeHandler might look something like this
/**
* #Route("/uploadhandler", name="uploadHandler")
*/
public function uploadhandler(Request $request, ImageUploader $uploader) {
$doc = $uploader->upload($request->files->get('file'));
$file = new File();
$file->setSrc($doc['src']);
...
$this->getDoctrine()->getManager()->persist($file);
$this->getDoctrine()->getManager()->flush();
return new JsonResponse($file);
}
/**
* #Route("/removeHandler/{id}", name="removeHandler")
*/
public function removeHandler(Request $request,File $file = null) {
$this->getDoctrine()->getManager()->remove($file);
$this->getDoctrine()->getManager()->flush();
return new JsonResponse(true);
}
note that uploadhandler should return a File object

you should pass upload url instead of upload directory
generate url in twig - {{ oneup_uploader_endpoint('post_image') }}
var postDropzone = new Dropzone('.dropzone', {
url: '{{ oneup_uploader_endpoint('post_image') }}',
// url: '%kernel.project_dir%/public/media/posts',
// url: 'file/post',
maxFiles: 10,
addRemoveLinks: true,
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
});

Related

"Multiple Upload" with VichUploaderBundle on Symfony 3

I try to allowed the multiple upload with the bundle VichUploader. On the project, i have a class Theater which own a main image but also a number of secondary images (Collection of images). Actually each secondary images is a Ressource.
So a Theater have onetomany Resources and a Resource is connected to one theater.
But when i try to create i can access to my form but i have an error when i try to save which is :
Expected argument of type "AppBundle\Entity\Resources", "AppBundle\Entity\Theatre" given
Here this is my class Theater with only details for multiple upload :
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Theatre
*
* #ORM\Table(name="theatre")
* #ORM\Entity
* #Vich\Uploadable
*/
class Theatre
{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Resources", mappedBy="theatre", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $images;
// ..
/**
* Constructor
*/
public function __construct()
{
$this->images = new \Doctrine\Common\Collections\ArrayCollection();
}
// MultiUpload
/**
* #return ArrayCollection
*/
public function getImages()
{
return $this->images;
}
/**
* #param ArrayCollection $pictures
*/
public function setImages($pictures)
{
$this->images = $pictures;
}
public function getAttachImages()
{
return null;
}
/**
* #param array $files
*
* #return array
*/
public function setAttachImages(array $files=array())
{
if (!$files) return [];
foreach ($files as $file) {
if (!$file) return [];
$this->attachImages($file);
}
return [];
}
/**
* #param UploadedFile|null $file
*/
public function attachImages(UploadedFile $file=null)
{
if (!$file) {
return;
}
$picture = new Resources();
$picture->setImage($file);
$this->addImage($picture);
}
/**
* Add image.
*
* #param \AppBundle\Entity\Resources $image
*
*
*/
public function addImage(\AppBundle\Entity\Resources $image)
{
$image->setTheatre($this);
//$this->images->add($image);
$this->images[] = $image;
//return $this;
}
/**
* Remove image.
*
* #param \AppBundle\Entity\Resources $image
*
*
*/
public function removeImage(\AppBundle\Entity\Resources $image)
{
$image->setTheatre(null);
$this->images->removeElement($image);
// return $this->images->removeElement($image);
}
Then my class Resources :
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Resources
*
* #ORM\Table(name="resources")
* #ORM\Entity
* #Vich\Uploadable
*/
class Resources
{
/**
* #var Theatre
* #ORM\ManyToOne(targetEntity="Theatre", inversedBy="images")
*/
private $theatre;
/**
* #Vich\UploadableField(mapping="uploads_image", fileNameProperty="url")
* #Assert\File(
* mimeTypes = {"image/png", "image/jpeg", "image/jpg"},
* mimeTypesMessage = "Please upload a valid valid IMAGE"
* )
*
*
* #var File $image
*/
protected $image;
/**
* #ORM\Column(type="string", length=255, name="url")
*
* #var array $url
*/
protected $url;
/**
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Theatre
*/
public function setImage(File $image = null)
{
$this->image = $image;
}
public function getImage()
{
return $this->image;
}
// ..
/**
* Set theatre.
*
* #param \AppBundle\Entity\Theatre $theatre
*
* #return Resources
*/
public function setTheatre(\AppBundle\Entity\Theatre $theatre)
{
$this->theatre = $theatre;
return $this;
}
/**
* Get theatre.
*
* #return \AppBundle\Entity\Theatre
*/
public function getTheatre()
{
return $this->theatre;
}
/**
* Set url.
*
* #param string $url
*
* #return Resources
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get url.
*
* #return array
*/
public function getUrl()
{
return $this->url;
}
Then I add to the builder :
namespace AppBundle\Form\Collection;
use AppBundle\Entity\Theatre;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Vich\UploaderBundle\Form\Type\VichFileType;
class TheatreImages extends AbstractType{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('attachImages', FileType::class, ['multiple'=>true, 'required'=>false])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Theatre::class,
));
}
/**
* #return string
*/
public function getName()
{
return 'app_theatreImages';
}
}
I add my config with the bundle easyAdminBundle:
easy_admin:
entities:
Theatre:
class: AppBundle\Entity\Theatre
list:
title: 'Liste des théâtres'
fields:
- 'Name'
- 'adress'
- 'Metro station'
- { property: 'Main image', type: 'image', template: 'theatreFile.html.twig', base_path: '%app.path.theatre_images%' }
new:
title: 'Création théâtre'
fields:
- { type: 'section', label: 'Information du théâtre' }
- {property: 'name', label: 'Nom'}
- {property: 'number_of_seats', type: 'integer', label: 'Nombre de sièges'}
- {property: 'about', label: 'description'}
- { property: 'imageFile', type: 'vich_file', label: 'image', type_options: { required: false}}
- {property: 'images', type: 'collection', type_options: {entry_type: 'AppBundle\Form\Collection\TheatreImages', by_reference: false}}
- { type: 'section', label: 'Localisation du théâtre' }
- {property: 'adress', label: 'adresse'}
- {property: 'metro_station', label: 'Station de métro'}
- {property: 'location_coordinates', label: 'Coordonnées'}
edit:
title: "Édition théâtre"
fields:
- { type: 'section', label: 'Information du théâtre' }
- {property: 'name', label: 'Nom'}
- {property: 'number_of_seats', type: 'integer', label: 'Nombre de sièges'}
- {property: 'about', label: 'description'}
- { property: 'imageFile', type: 'vich_file', label: 'image', type_options: { required: false}}
- {property: 'images', type: 'collection', type_options: {entry_type: 'AppBundle\Form\Collection\TheatreImages', by_reference: false}}
- { type: 'section', label: 'Localisation du théâtre' }
- {property: 'adress', label: 'adresse'}
- {property: 'metro_station', label: 'Station de métro'}
- {property: 'location_coordinates', label: 'Coordonnées'}
Thanks in advance.
I have created a solution for a very useful bundle [VichUploader] which is missing the functionality of multiple uploads and it works on every Symfony version, I have created on [Symfony] 5.2.
It's on OneToMany relation and it works fine. So I have used CollectionType and [VichFileType] in my custom forms and little trick in my controller, Here the code and to see all the project, you can find it in my GitHub
[a link] https://github.com/malek-laatiri
Admission.php
class Admission
{
/**
* #ORM\OneToMany(targetEntity=Diplome::class, mappedBy="admission")
*/
private $diplomes;
/**
* #return Collection|Diplome[]
*/
public function getDiplomes(): Collection
{
return $this->diplomes;
}
public function addDiplome(Diplome $diplome): self
{
if (!$this->diplomes->contains($diplome)) {
$this->diplomes[] = $diplome;
$diplome->setAdmission($this);
}
return $this;
}
public function removeDiplome(Diplome $diplome): self
{
if ($this->diplomes->removeElement($diplome)) {
// set the owning side to null (unless already changed)
if ($diplome->getAdmission() === $this) {
$diplome->setAdmission(null);
}
}
return $this;
}
}
Diplome.php
<?php
namespace App\Entity;
use App\Repository\DiplomeRepository;
use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass=DiplomeRepository::class)
* #Vich\Uploadable
*/
class Diplome
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Admission::class, inversedBy="diplomes",cascade={"persist","remove"})
*/
private $admission;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #Vich\UploadableField(mapping="product_image", fileNameProperty="name")
* #var File
*/
private $file;
public function getId(): ?int
{
return $this->id;
}
public function getAdmission(): ?Admission
{
return $this->admission;
}
public function setAdmission(?Admission $admission): self
{
$this->admission = $admission;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getFile()
{
return $this->file;
}
public function setFile( $file)
{
$this->file = $file;
return $this;
}
}
AdmissionType.php
<?php
namespace App\Form;
use App\Entity\Admission;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;
class Admission1Type extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('diplomes', CollectionType::class, [
'entry_type' => DiplomeType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'label'=>false,
'by_reference' => false,
'disabled' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Admission::class,
]);
}
}
DiplomeType.php
<?php
namespace App\Form;
use App\Entity\Diplome;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;
class DiplomeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file',VichFileType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Diplome::class,
"allow_extra_fields" => true,
]);
}
}
_form.html.twig
<ul id="diplomes-fields-list"
data-prototype="{{ form_widget(form.diplomes.vars.prototype)|e }}"
data-widget-tags="{{ '<li></li>'|e }}"
data-widget-counter="{{ form.diplomes|length }}">
{% for emailField in form.diplomes %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>
<button type="button"
class="add-another-collection"
data-list-selector="#diplomes-fields-list">Add another email
</button>
script.js
jQuery(document).ready(function () {
jQuery('.add-another-collection').click(function (e) {
var list = $("#diplomes-fields-list");
var counter = list.data('widget-counter') | list.children().length;
var newWidget = list.attr('data-prototype');
newWidget = newWidget.replace(/__name__/g, counter);
counter++;
list.data('widget-counter', counter);
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
newElem.append('remove');
$('.remove-tag').click(function(e) {
e.preventDefault();
$(this).parent().remove();
});
});
});
Controller.php
$admission = new Admission();
$form = $this->createForm(Admission1Type::class, $admission);
$form->handleRequest($request);
$entityManager = $this->getDoctrine()->getManager();
if ($form->isSubmitted() && $form->isValid()) {
foreach ($form->getData()->getNotes() as $dip){
$entityManager->persist($dip);
$admission->addNote($dip);
}
$entityManager->persist($admission);
$entityManager->flush();
Vihcuploader not support multiple file uploads well, you have to create a custom form type to handle it maybe this solution would help:
github user soluton

How to preselect generated checkboxes in Symfony 4?

Let's imagine that we want to assign categories to a post, we use an EntityType to generate them based on the amount of categories we have, so we just add the next block of code to our form:
Controller:
->add('categories', EntityType::class, array(
'class' => Category::class,
'choice_label' => 'category_description',
'multiple' => true,
'expanded' => true,
'required' => false,
))
And then save them to the database when the form is submitted, ManyToMany, using ArrayCollection:
foreach($post_data['categories'] as $form_category)
{
$database_category = $database_manager->getRepository(Category::class)->find($form_category->getId());
$post->addCategory($database_category);
}
Entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
...
/**
* #ORM\ManyToMany(targetEntity="Category", cascade={"persist"})
* #ORM\JoinTable(name="junction_table",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")}
* )
*/
private $categories;
public function __construct() {
$this->categories = new ArrayCollection();
}
...
public function getCategories()
{
return $this->categories;
}
public function addCategory(Category $category): self
{
$this->categories->add($category);
return $this;
}
}
Image:
Generated checkboxes with description and index
But what if i want to have those checkboxes preselected when i go to edit mode so the user knows which ones were selected last time, how would you approach that?
Also how would you remove one if it is deselected?
If your form fields are mapped to an entity, the data should be set automatically.
You don't even have to add the categories manually to the post.
Here is a full sample of working code that matches what you want to do:
//src/Entity/Group.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\GroupRepository")
* #ORM\Table(name="`group`")
*/
class Group
{
/**
* Group constructor.
*/
public function __construct()
{
$this->players = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Player", inversedBy="groups")
*/
private $players;
/**
* #return mixed
*/
public function getPlayers()
{
return $this->players;
}
/**
* #param mixed $players
*
* #return Group
*/
public function setPlayers($players)
{
$this->players = $players;
return $this;
}
/**
* #param Player $player
*
* #return Group
*/
public function addPlayer(Player $player)
{
$this->players->add($player);
return $this;
}
/**
* #param Player $player
*
* #return Group
*/
public function removePlayer(Player $player)
{
$this->players->removeElement($player);
return $this;
}
}
//src/Entity/Player.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\PlayerRepository")
*/
class Player
{
/**
* #return string
*/
public function __toString()
{
return $this->name;
}
/**
* Player constructor.
*/
public function __construct()
{
$this->groups = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Group", mappedBy="players")
*/
private $groups;
/**
* #return mixed
*/
public function getGroups()
{
return $this->groups;
}
/**
* #param mixed $groups
*
* #return Player
*/
public function setGroups($groups)
{
$this->groups = $groups;
return $this;
}
/**
* #param Group $group
*
* #return Player
*/
public function addGroup(Group $group)
{
$this->groups->add($group);
return $this;
}
/**
* #param Group $group
*
* #return Player
*/
public function removeGroup(Group $group)
{
$this->groups->removeElement($group);
return $this;
}
}
//src/Form/GroupType.php
namespace App\Form;
use App\Entity\Group;
use App\Entity\Player;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GroupType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'players',
EntityType::class,
[
'class' => Player::class,
'multiple' => true,
'expanded' => true,
'required' => false,
]
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Group::class,
]
);
}
}
//src/Controller/GroupController.php
namespace App\Controller;
use App\Entity\Group;
use App\Form\GroupType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class GroupController extends Controller
{
/**
* #Route("/edit-group/{id}", name="group_edit")
* #param Request $request
* #param Group $group
*
* #return Response
*/
public function editGroup(Request $request, Group $group)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(GroupType::class, $group);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($group);
$em->flush();
}
return new Response(
$this->renderView(
'group/edit-group.html.twig',
array(
'form' => $form->createView(),
)
)
);
}
}
//templates/group/edit-group.html.twig
{% extends 'base.html.twig' %}
{% block body %}
<div class="container">
<div class="row">
<h1>Edit group</h1>
</div>
{{ form_start(form) }}
{{ form_row(form.players) }}
<button type="submit" class="btn btn-success">Edit</button>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
{% endblock %}

Upload image using VichUploaderBundle

I have a problem with VichUploaderBundle, I followed all the instructions but I dont know how my Controller should handle this after finished form becouse image failed to uploaded and database did not write.
my Entity class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #Vich\Uploadable
*/
class Product
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ..... other fields
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="integer")
*
* #var integer
*/
private $imageSize;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Product
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
var_dump($image);
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*
* #return Product
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string|null
*/
public function getImageName()
{
return $this->imageName;
}
/**
* #param integer $imageSize
*
* #return Product
*/
public function setImageSize($imageSize)
{
$this->imagesize = $imageSize;
return $this;
}
/**
* #return integer|null
*/
public function getImageSize()
{
return $this->imageSize;
}
}
my FormType class:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class AvatarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', FileType::class, array('label' => 'Brochure (PDF file)'))
;
}
public function getName()
{
return 'avatar_form';
}
}
config:
vich_uploader:
db_driver: orm # or mongodb or propel or phpcr
mappings:
product_image:
uri_prefix: /image/avatar
upload_destination: '%kernel.root_dir%/../web/image/avatar'
Twig template:
<div>
{{ form_start(form) }}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form.imageFile) }}
<button type="submit" class="btn btn-default">Submit</button>
{{ form_end(form) }}
</div>
and finally Controller:
namespace AppBundle\Controller\User;
use AppBundle\Entity\Product;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use AppBundle\Controller\InitController;
use AppBundle\Form\AvatarType;
class UserController extends Controller implements InitController
{
/**
* #Route("/user/avatar", name="AvatarAction")
*/
public function AvatarAction(Request $request)
{
$form = $this->createForm('AppBundle\Form\AvatarType',null,array(
// To set the action use $this->generateUrl('route_identifier')
'action' => $this->generateUrl('AvatarAction'),
'method' => 'POST'
));
$form->handleRequest($request);
if ($request->isMethod('POST')) {
if($form->isValid()){
//$track = new Product();
//$em = $this->getDoctrine()->getManager();
//$em->persist($track);
//$em->flush();
}
return $this->render('default/avatar.html.twig',array(
'form' => $form->createView()
));
}
}
Thanks for all your advice!
Maybe this is not the solution, but your line :
->add('imageFile', FileType::class, array('label' => 'Brochure (PDF file)')):
Should be :
->add('imageFile', VichFileType::class, array('label' => 'Brochure (PDF file)'))
More info : https://github.com/dustin10/VichUploaderBundle/blob/master/Resources/doc/form/vich_file_type.md
What Symfony tells you as error ?
Have a nice day

How to fix: Semantical Error Annotation ORM\Column is not allowed to be declared on method

When I try to create or edit a Game, I get this error:
[Semantical Error] Annotation #ORM\Column is not allowed to be
declared on method AppBundle\Entity\Game::setType(). You may only use
this annotation on these code elements: PROPERTY, ANNOTATION.
What's causing this and what can I do to fix this?
Game
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Game
*
* #ORM\Table(name="game")
* #ORM\Entity(repositoryClass="AppBundle\Repository\GameRepository")
*/
class Game
{
/**
* #ORM\OneToMany(targetEntity="PlayLog", mappedBy="game")
*/
private $playlogs;
public function __construct()
{
$this->playlogs = new ArrayCollection();
}
/**
* #ORM\ManyToOne(targetEntity="Type", inversedBy="games")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id")
*/
private $type;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Assert\NotBlank()
* #Assert\Length(
* min = "3",
* max = "100"
* )
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Game
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getType()
{
return $this->type;
}
/**
* #ORM\Column (options={"default" = none})
* #param mixed $type
*/
public function setType($type)
{
$this->type = $type;
}
/**
* #return mixed
*/
public function getPlaylogs()
{
return $this->playlogs;
}
/**
* #param mixed $playlogs
*/
public function setPlaylogs($playlogs)
{
$this->playlogs = $playlogs;
}
public function addPlayLog(PlayLog $playlog)
{
$this->playlog->add($playlog);
$playlog->setPlayLogs($this);
}
}
GameController
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Game;
use AppBundle\Entity\PlayLog;
use AppBundle\Entity\Type;
use AppBundle\Form\GameType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\HttpFoundation\Request;
/**
* Game controller.
*
* #Route("game")
*/
class GameController extends Controller
{
/**
* Lists all game entities.
*
* #Route("/", name="game_index")
* #Method("GET")
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$dql = "SELECT game FROM AppBundle:Game game JOIN game.type type ORDER BY game.name";
$query = $em->createQuery($dql);
/*
* #var $paginator \Knp\Component\Pager\Paginator
*/
$paginator = $this->get('knp_paginator');
$result = $paginator->paginate(
$query,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 25)
);
// dump(get_class($paginator));
return $this->render('game/index.html.twig', array(
'games' => $result,
'max_limit_error' => 25
));
}
/**
* Creates a new game entity.
*
* #Route("/new", name="game_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$game = new Game();
$form = $this->createForm('AppBundle\Form\GameType', $game);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($game);
$em->flush($game);
return $this->redirectToRoute('game_show', array('id' => $game->getId()));
}
return $this->render('game/new.html.twig', array(
'game' => $game,
'form' => $form->createView(),
));
}
/**
* Finds and displays a game entity.
*
* #Route("/{id}", name="game_show")
* #Method("GET")
*/
public function showAction(Game $game)
{
$deleteForm = $this->createDeleteForm($game);
return $this->render('game/show.html.twig', array(
'game' => $game,
'delete_form' => $deleteForm->createView(),
));
}
/**
* Displays a form to edit an existing game entity.
*
* #Route("/{id}/edit", name="game_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Game $game)
{
$deleteForm = $this->createDeleteForm($game);
$editForm = $this->createForm('AppBundle\Form\GameType', $game);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('game_show', array('id' => $game->getId()));
}
return $this->render('game/edit.html.twig', array(
'game' => $game,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
/**
* Displays a form to edit an existing game entity.
*
* #Route("/{id}/log", name="game_log")
* #Method({"GET", "POST"})
*/
public function addLogAction(Request $request, Game $game)
{
$playlog = new PlayLog();
$form = $this->createForm(GameType::class, $game);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
//Save playLog
$em = $this->getDoctrine()->getManager();
$em->persist($playlog);
$em->flush();
}
// Render / return view incl. formulier.
return $this->render('game/log.html.twig', array(
'game' => $game,
'form' => $form->createView(),
));
}
/**
* Deletes a game entity.
*
* #Route("/{id}", name="game_delete")
* #Method("DELETE")
*/
public function deleteAction(Request $request, Game $game)
{
$form = $this->createDeleteForm($game);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->remove($game);
$em->flush($game);
}
return $this->redirectToRoute('game_index');
}
/**
* Creates a form to delete a game entity.
*
* #param Game $game The game entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm(Game $game)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('game_delete', array('id' => $game->getId())))
->setMethod('DELETE')
->getForm()
;
}
}
Type
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Type
*
* #ORM\Table(name="type")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TypeRepository")
*/
class Type
{
/**
* #ORM\OneToMany(targetEntity="Game", mappedBy="type")
*/
private $games;
public function __construct()
{
$this->games = new ArrayCollection();
}
/**
* #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)
*/
private $name;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Type
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getGames()
{
return $this->games;
}
/**
* #param mixed $games
*/
public function setGames($games)
{
$this->games = $games;
}
public function addGame(Game $game)
{
$this->games->add($game);
$game->setType($this);
}
public function removeGame(Game $game)
{
$this->games->removeElement($game);
}
}
TypeType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TypeType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name') ;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Type'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_type';
}
}
game/new.html.twig
{% extends 'base.html.twig' %}
{% import "macros.html.twig" as macro %}
{% block content %}
<h1>New Game</h1>
<div class="col-md-4 col-md-offset-4">
{{ form_start(form) }}
<table class="table table-bordered">
<tr>
<td>
Name
</td>
<td>
{{ form_widget(form.name) }}
</td>
</tr>
<tr>
<td>
Type
</td>
<td>
{{ form_widget(form.type) }}
</td>
</tr>
</table>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
{{ macro.btnSubmitAndCancel() }}
</div>
</div>
{{ form_end(form) }}
The error tells you that you cannot use the #ORM\Column() annotation on a method. You have to remove it from your Game entity:
/**
* #ORM\Column (options={"default" = none}) <--- REMOVE ME PLEASE
* #param mixed $type
*/
public function setType($type)
{
$this->type = $type;
}

One-to-Many Doctrine relationship not working?

In my Symdony2 project I've two related entities: "Reglement and "Article". This should be many-to-one relationship, because each "Reglement" can have many "Articles", and each "Article" can belongs to one "Reglement".
Moreover, I need a user interface to manage Reglement and Articles. So, when adding a Reglement, user should be able to add many articles it belongs.
I've already achieved this by setting up a One-To-Many relation in my Doctrine entites. Everything is working like a charm, including user interface build on custom form types in Symfony2 (I've used "Collection" form field type to allow user to add "Articles" in "Reglement). The only problem I've is that I can save only the last article in the database !!
Here is my "Reglement" entity source code:
<?php
namespace GestionEnvironnementale\ISO14001Bundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Reglement
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="GestionEnvironnementale\ISO14001Bundle\Entity\ReglementRepository")
*/
class Reglement
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="domaineApplication", type="string", length=255)
*/
private $domaineApplication;
/**
* #var string
*
* #ORM\Column(name="texteLegislatif", type="text")
*/
private $texteLegislatif;
/**
* #var string
*
* #ORM\Column(name="contenuText", type="text")
*/
private $contenuText;
/**
* #ORM\OneToMany(targetEntity="GestionEnvironnementale\ISO14001Bundle\Entity\Article", cascade={"persist"}, mappedBy="reglement")
*/
private $articles;
public function __construct()
{
$this->articles = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getArticles()
{
return $this->articles;
}
public function setArticles(ArrayCollection $articles)
{
foreach ($articles as $article) {
$article->addReglement($this);
}
$this->articles = $articles;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set domaineApplication
*
* #param string $domaineApplication
* #return Reglement
*/
public function setDomaineApplication($domaineApplication)
{
$this->domaineApplication = $domaineApplication;
return $this;
}
/**
* Get domaineApplication
*
* #return string
*/
public function getDomaineApplication()
{
return $this->domaineApplication;
}
/**
* Set texteLegislatif
*
* #param string $texteLegislatif
* #return Reglement
*/
public function setTexteLegislatif($texteLegislatif)
{
$this->texteLegislatif = $texteLegislatif;
return $this;
}
/**
* Get texteLegislatif
*
* #return string
*/
public function getTexteLegislatif()
{
return $this->texteLegislatif;
}
/**
* Set contenuText
*
* #param string $contenuText
* #return Reglement
*/
public function setContenuText($contenuText)
{
$this->contenuText = $contenuText;
return $this;
}
/**
* Get contenuText
*
* #return string
*/
public function getContenuText()
{
return $this->contenuText;
}
/**
* Add articles
*
* #param \GestionEnvironnementale\ISO14001Bundle\Entity\Article $articles
* #return Reglement
*/
public function addArticle(\GestionEnvironnementale\ISO14001Bundle\Entity\Article $articles)
{
$this->articles[] = $articles;
return $this;
}
/**
* Remove articles
*
* #param \GestionEnvironnementale\ISO14001Bundle\Entity\Article $articles
*/
public function removeArticle(\GestionEnvironnementale\ISO14001Bundle\Entity\Article $articles)
{
$this->articles->removeElement($articles);
}
}
And here is my Article entity source code:
<?php
namespace GestionEnvironnementale\ISO14001Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Article
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="GestionEnvironnementale\ISO14001Bundle\Entity\ArticleRepository")
*/
class Article
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="GestionEnvironnementale\ISO14001Bundle\Entity\Reglement", inversedBy="articles")
* #ORM\JoinColumn(nullable=false)
*/
private $reglement;
/**
* #var string
*
* #ORM\Column(name="exigenceArticle", type="string", length=255)
*/
private $exigenceArticle;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set exigenceArticle
*
* #param string $exigenceArticle
* #return Article
*/
public function setExigenceArticle($exigenceArticle)
{
$this->exigenceArticle = $exigenceArticle;
return $this;
}
/**
* Get exigenceArticle
*
* #return string
*/
public function getExigenceArticle()
{
return $this->exigenceArticle;
}
/**
* Set reglement
*
* #param \GestionEnvironnementale\ISO14001Bundle\Entity\Reglement $reglement
* #return Article
*/
public function setReglement(\GestionEnvironnementale\ISO14001Bundle\Entity\Reglement $reglement)
{
$this->reglement = $reglement;
return $this;
}
/**
* Get reglement
*
* #return \GestionEnvironnementale\ISO14001Bundle\Entity\Reglement
*/
public function getReglement()
{
return $this->reglement;
}
public function addReglement(Reglement $reglement)
{
if (!$this->reglements->contains($reglement)) {
$this->reglements->add($reglement);
}
}
}
and here is my reglement form :
<?php
namespace GestionEnvironnementale\ISO14001Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ReglementType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('domaineApplication')
->add('texteLegislatif')
->add('contenuText')
->add('articles', 'collection', array(
'type' => new ArticleType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
));
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'GestionEnvironnementale\ISO14001Bundle\Entity\Reglement'
));
}
/**
* #return string
*/
public function getName()
{
return 'gestionenvironnementale_iso14001bundle_reglementtype';
}
}
and my article form :
<?php
namespace GestionEnvironnementale\ISO14001Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ArticleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('exigenceArticle')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'GestionEnvironnementale\ISO14001Bundle\Entity\Article'
));
}
/**
* #return string
*/
public function getName()
{
return 'gestionenvironnementale_iso14001bundle_article';
}
}
And here is part of my ReglementController source code:
public function ajouterReglementAction()
{
$reglement = new Reglement();
$form = $this->createForm(new ReglementType(), $reglement);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$reglement->getArticles()->clear();
$em = $this->getDoctrine()->getManager();
$em->persist($reglement);
$em->flush();
foreach ($form->get('articles')->getData() as $ac) {
$ac->setReglement($reglement);
$em->persist($ac);
}
$em->flush();
$this->get('session')->getFlashBag()->add('info', 'Reglement bien enregistré');
return $this->redirect( $this->generateUrl('reglement_voir', array('id' => $reglement->getId())));
}
}
return $this->render('ISO14001Bundle:Reglements:ajouter.html.twig', array(
'form' => $form->createView()
));
}
finnally the form view source code :
<div class="well">
<form method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<br/> <input type="submit" value="Envoyer" class="btn btn-primary" />
</form>
<script src="{{ asset('js/jquery-2.1.1.min.js') }}"></script>
<script type="text/javascript">
$(document).ready(function() {
var $container = $('div#gestionenvironnementale_iso14001bundle_reglementtype_articles');
var $lienAjout = $('Ajouter un article');
$container.append($lienAjout);
$lienAjout.click(function(e) {
ajouterArticle($container);
e.preventDefault();
return false;
});
var index = $container.find(':input').length;
if (index == 0) {
ajouterArticle($container);
} else {
$container.children('div').each(function() {
ajouterLienSuppression($(this));
});
}
function ajouterArticle($container) {
var $prototype = $($container.attr('data-prototype').replace(/__name__label__/g, 'Article n°' + (index+1))
.replace(/\$\$name\$\$/g, index));
ajouterLienSuppression($prototype);
$container.append($prototype);
index++;
}
function ajouterLienSuppression($prototype) {
$lienSuppression = $('Supprimer');
$prototype.append($lienSuppression);
$lienSuppression.click(function(e) {
$prototype.remove();
e.preventDefault();
return false;
});
}
});
I hope you can help me ;)
hi everyone I found the solution of my problem ^_^
the error is in line 27 of form view, there must be __name__ instead of $$name$$ as follows :
function ajouterArticle($container) {
var $prototype = $($container.attr('data-prototype').replace(/__name__label__/g, 'Article n°' + (index+1))
.replace(/__name__/g, index));
ajouterLienSuppression($prototype);
$container.append($prototype);
index++;
}
good luck ;)

Resources