Populate dependent select box using Symfony & Ajax - php

I was not able to find any solution matching my case scenario, so I decided to ask here:
Basically what I need to achieve is to render a form with several select boxes namely the Company, ProductsCategory, and Products. So depending on which category the user chooses, I want to filter and show only the products of that chosen category. I tried to follow the Symfony documentation as mentioned here , but I cannot get it to work. the front-end products select box remains blank even after a category has been set and also the ajax return with status 500 with error:
Return value of App\Entity\ProductsCategory::getProducts() must be an instance of App\Entity\Products or null, instance of Doctrine\ORM\PersistentCollection returned
here are the codes:
My Exportables entity
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ExportablesRepository")
*/
class Exportables
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ExportCompany", inversedBy="exportables")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Products", inversedBy="exportables")
* #ORM\JoinColumn(nullable=false)
*/
private $product;
/**
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ProductsCategory")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
public function getId(): ?int
{
return $this->id;
}
public function getCompany(): ?ExportCompany
{
return $this->company;
}
public function setCompany(?ExportCompany $company): self
{
$this->company = $company;
return $this;
}
public function getProduct(): ?Products
{
return $this->product;
}
public function setProduct(?Products $product): self
{
$this->product = $product;
return $this;
}
public function getIsActive(): ?bool
{
return $this->isActive;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function getCategory(): ?ProductsCategory
{
return $this->category;
}
public function setCategory(?ProductsCategory $category): self
{
$this->category = $category;
return $this;
}
}
ProductsCategory entity:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductsCategoryRepository")
*/
class ProductsCategory
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $categoryTitle;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $categoryDescription;
/**
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Products", mappedBy="category", cascade={"persist", "remove"})
*/
private $products;
public function __construct()
{
$this->product = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCategoryTitle(): ?string
{
return $this->categoryTitle;
}
public function setCategoryTitle(string $categoryTitle): self
{
$this->categoryTitle = $categoryTitle;
return $this;
}
public function getCategoryDescription(): ?string
{
return $this->categoryDescription;
}
public function setCategoryDescription(?string $categoryDescription): self
{
$this->categoryDescription = $categoryDescription;
return $this;
}
public function getIsActive(): ?bool
{
return $this->isActive;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function getProducts(): ?Products
{
return $this->products;
}
public function setProducts(Products $products): self
{
$this->products = $products;
// set the owning side of the relation if necessary
if ($products->getCategory() !== $this) {
$products->setCategory($this);
}
return $this;
}
public function __toString()
{
return $this->categoryTitle;
}
}
Products entity:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductsRepository")
*/
class Products
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $productTitle;
/**
* #ORM\Column(type="text")
*/
private $productDescription;
/**
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ProductsCategory", inversedBy="products", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Exportables", mappedBy="product")
*/
private $exportables;
public function __construct()
{
$this->exportables = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getProductTitle(): ?string
{
return $this->productTitle;
}
public function setProductTitle(string $productTitle): self
{
$this->productTitle = $productTitle;
return $this;
}
public function getProductDescription(): ?string
{
return $this->productDescription;
}
public function setProductDescription(string $productDescription): self
{
$this->productDescription = $productDescription;
return $this;
}
public function getIsActive(): ?bool
{
return $this->isActive;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function getCategory(): ?ProductsCategory
{
return $this->category;
}
public function setCategory(ProductsCategory $category): self
{
$this->category = $category;
return $this;
}
/**
* #return Collection|Exportables[]
*/
public function getExportables(): Collection
{
return $this->exportables;
}
public function addExportable(Exportables $exportable): self
{
if (!$this->exportables->contains($exportable)) {
$this->exportables[] = $exportable;
$exportable->setProduct($this);
}
return $this;
}
public function removeExportable(Exportables $exportable): self
{
if ($this->exportables->contains($exportable)) {
$this->exportables->removeElement($exportable);
// set the owning side to null (unless already changed)
if ($exportable->getProduct() === $this) {
$exportable->setProduct(null);
}
}
return $this;
}
public function __toString(){
return $this->productTitle;
}
}
Exportables Type:
namespace App\Form;
use App\Entity\Products;
use App\Entity\Exportables;
use App\Entity\ProductsCategory;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class ExportablesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('company')
->add('category', EntityType::class, array(
'class' => ProductsCategory::class,
'placeholder' => 'Select a Category...',
))
->add('isActive')
;
$formModifier = function (FormInterface $form, ProductsCategory $cat = null) {
$products = null === $cat ? [] : $cat->getProducts();
dump($products);
$form->add('product', EntityType::class, [
'class' => 'App\Entity\Products',
'placeholder' => '',
'choices' => $products,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getCategory());
}
);
$builder->get('category')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$cat = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $cat);
}
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Exportables::class,
]);
}
}
ExportablesController:
namespace App\Controller;
use App\Entity\Exportables;
use App\Form\ExportablesType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ExportablesController extends AbstractController
{
/**
* #Route("/admin-panel/exportables/new", name="exportables_create")
* #Route("/admin-panel/exportables/{id}/edit", name="exportables_edit")
*/
public function exportables_create_and_edit(Request $request, EntityManagerInterface $em, Exportables $exportables = null)
{
if(!$exportables){
$exportables = new Exportables();
}
$form = $this->createForm(ExportablesType::class, $exportables);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($exportables);
$em->flush();
}
return $this->render('/admin-panel/exportables_create.html.twig', [
'exForm' => $form->createView(),
'editMode' => $exportables->getId() !== null
]);
}
}
Finally, the twig file rendering the form:
{% extends '/admin-panel/base-admin.html.twig' %}
{% block body %}
{{ form_start(exForm) }}
<div class="form-group">
{{ form_row(exForm.company, {'attr':{'class':"form-control"}}) }}
</div>
<div class="form-group">
{{ form_row(exForm.category, {'attr':{'class':"form-control"}}) }}
</div>
{% if exForm.product is defined %}
<div class="form-group">
{{ form_row(exForm.product, {'label': "Product..", 'attr':{'class':"form-control"}}) }}
</div>
{% endif %}
<div class="form-group">
<div class="custom-control custom-checkbox">
{{ form_widget(exForm.isActive, {'attr': {'class': "custom-control-input", 'checked': "checked"}}) }}
<label class="custom-control-label" for="exportables_isActive">Visible on the website?</label>
</div>
</div>
<button type="submit" class="btn btn-success">Create</button>
{{ form_end(exForm) }}
{% endblock %}
{% block javascripts %}
<script>
{# //for some reasons this line doesnt work $(document).ready(function() { #}
jQuery(document).ready(function($){
var $cat = $('#exportables_category');
// When cat gets selected ...
$cat.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected cat value.
var data = {};
data[$cat.attr('name')] = $cat.val();
console.log("cat val " + $cat.val());
//console.log($form.attr('method'));
const url = "{{ path('exportables_create')|escape('js') }}";
//console.log(data);
//console.log(url);
//why undefined?? console.log($form.attr('action'));
// Submit data via AJAX to the form's action path.
$.ajax({
//url : $form.attr('action'),
url : url,
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current position field ...
$('#exportables_product').replaceWith(
// ... with the returned one from the AJAX response.
//$(html).find('#exportables_product')
array(1,2,3)
);
// Position field now displays the appropriate positions.
}
});
});
});
</script>
{% endblock %}
Any help is greatly appreciated.

I didn't take the time to analyse all your code, so I'm not sure it will solve the whole problem, but it may help.
I guess there is something weird inside the ProductsCategory entity class. Indeed the products property is annotated as a OneToMany relation:
/**
* #ORM\OneToMany(targetEntity="App\Entity\Products", mappedBy="category", cascade={"persist", "remove"})
*/
private $products;
This implies that this property refers to a collection (one category can have many products). But the getters/setters for this property are defined later as if it was a OneToOne relation:
public function getProducts(): ?Products
{
return $this->products;
}
public function setProducts(Products $products): self
{
$this->products = $products;
// set the owning side of the relation if necessary
if ($products->getCategory() !== $this) {
$products->setCategory($this);
}
return $this;
}
It looks like you modified the annotation without modifying the getters/setters, which typically should offer the following methods for a OneToMany relation:
public function getProducts(): Collection;
public function addProducts(Product $product): self;
public function removeProducts(Product $product): self;
A last point: you should rename your entity Products to Product, it will greatly improve the readability of your code: indeed, the entity Products actually represent only one product, not several ones.

Related

Entity not found with Symfony 5.1

I have an issue with an Entity.
The goal is to display a Form which the user can use to change his personal information like the Email or password. I created a form for that, but when I created the /Edit Route I get the following error:
"App\Entity\Users object not found by the #ParamConverter annotation."
Here's my Controller:
<?php
namespace App\Controller;
use App\Entity\Users;
use App\Form\EditProfileType;
use App\Form\UsersType;
use App\Repository\UsersRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/users")
*/
class UsersController extends AbstractController
{
/**
* #Route("/", name="users_index", methods={"GET"})
*/
public function index(): Response
{
return $this->render('users/index.html.twig');
}
/**
* #Route("/{id}", name="users_show", methods={"GET"})
*/
public function show(Users $user): Response
{
return $this->render('users/show.html.twig', [
'user' => $user,
]);
}
/**
* #Route("/edit", name="users_edit")
*/
public function editProfile(Request $request): Response
{
$user = $this->getUser();
$form = $this->createForm(EditProfileType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addflash('message', 'Profile mis à jour');
return $this->redirectToRoute('users');
}
return $this->render('users/editprofile.html.twig', [
'form' => $form->createView(),
]);
}
Here's the form:
<?php
namespace App\Form;
use App\Entity\Users;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EditProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('pseudo', TextType::class)
->add('Valider', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Users::class,
]);
}
}
Here's the Entity Users:
<?php
namespace App\Entity;
use App\Repository\UsersRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UsersRepository::class)
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class Users implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\OneToMany(targetEntity=Commentaires::class, mappedBy="auteur", orphanRemoval=true)
*/
private $commentaires;
/**
* #ORM\OneToMany(targetEntity=Notes::class, mappedBy="user", orphanRemoval=true)
*/
private $note;
/**
* #ORM\Column(type="string", length=100)
*/
private $pseudo;
public function __construct()
{
$this->commentaires = new ArrayCollection();
$this->note = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* #return Collection|Commentaires[]
*/
public function getCommentaires(): Collection
{
return $this->commentaires;
}
public function addCommentaire(Commentaires $commentaire): self
{
if (!$this->commentaires->contains($commentaire)) {
$this->commentaires[] = $commentaire;
$commentaire->setAuteur($this);
}
return $this;
}
public function removeCommentaire(Commentaires $commentaire): self
{
if ($this->commentaires->contains($commentaire)) {
$this->commentaires->removeElement($commentaire);
// set the owning side to null (unless already changed)
if ($commentaire->getAuteur() === $this) {
$commentaire->setAuteur(null);
}
}
return $this;
}
/**
* #return Collection|Notes[]
*/
public function getNote(): Collection
{
return $this->note;
}
public function addNote(Notes $note): self
{
if (!$this->note->contains($note)) {
$this->note[] = $note;
$note->setUser($this);
}
return $this;
}
public function removeNote(Notes $note): self
{
if ($this->note->contains($note)) {
$this->note->removeElement($note);
// set the owning side to null (unless already changed)
if ($note->getUser() === $this) {
$note->setUser(null);
}
}
return $this;
}
public function getPseudo(): ?string
{
return $this->pseudo;
}
public function setPseudo(string $pseudo): self
{
$this->pseudo = $pseudo;
return $this;
}
}
Here's the View Editprofile.html.twig:
{% extends 'basefront.html.twig' %}
{% block title %} Profile de {{ app.user.pseudo }} {% endblock %}
{% block body %}
<h1 class="h1 text-center">Modification du profile de {{ app.user.pseudo }}</h1>
{{ form(form) }}
{% endblock %}
order matters. especially when routing without requirements. you have the following routes (update: added prefix, thanks #Cerad!):
/users/ users_index
/users/{id} users_show
/users/edit users_edit
now, when you request the uri /users/edit the UrlMatcher will iterate over your routes to see if and which matches, stopping on the first match.
/users/ obviously doesn't match
/users/{id} does match, since {id} is allowed to be any alpha-numeric string (+ few extra chars), and edit matches that, leading to the users_show route, on which the User object cannot be determined by the id "edit`. And thus, the error message appears. One way to fix this would be to add requirements to the route matching - assuming the id is numerical only:
/**
* #Route("/{id}", ..., requirements={"id":"\d+"})
*/
I also agree with #Cerad that Users is a very bad name for an entity. They're usually in singular form.

Symfony 4 - VichUploaderBundle - filename is not persisted in the database

I have an entity "Support"on witch i want to link a file(PDF, DOC,...). I've followed the documentations and some videos to help, but i alway have this error :
SQLSTATE[23000]: Integrity constraint violation: 1048 The field 'filename' can't be empty (null)
and i don't have any persistance in my database.
here is my entity "Support" : `
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass="App\Repository\SupportRepository")
* #Vich\Uploadable
*/
class Support
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string|null
* #ORM\Column(type="string", length=255)
*
*/
private $filename;
/**
* #Vich\UploadableField(mapping="support_file", fileNameProperty="filename", size="fileSize")
* #var File|null
*/
private $supportFile;
/**
* #ORM\Column(type="integer")
*
* #var int|null
*/
private $fileSize;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTimeInterface|null
*/
private $updatedAt;
/**
* #ORM\Column(type="string", length=255)
*/
private $titre;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $desciption;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Salle", mappedBy="supports")
*/
private $salles;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\TypedeSupport", inversedBy="supports")
* #ORM\JoinColumn(nullable=false)
*/
private $typedeSupport;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Matiere", inversedBy="supports")
*/
private $matiere;
public function __construct()
{
$this->salles = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitre(): ?string
{
return $this->titre;
}
public function setTitre(string $titre): self
{
$this->titre = $titre;
return $this;
}
public function getUrl(): ?string
{
return $this->url;
}
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
public function getDesciption(): ?string
{
return $this->desciption;
}
public function setDesciption(?string $desciption): self
{
$this->desciption = $desciption;
return $this;
}
/**
* #return Collection|Salle[]
*/
public function getSalles(): Collection
{
return $this->salles;
}
public function addSalle(Salle $salle): self
{
if (!$this->salles->contains($salle)) {
$this->salles[] = $salle;
$salle->addSupport($this);
}
return $this;
}
public function removeSalle(Salle $salle): self
{
if ($this->salles->contains($salle)) {
$this->salles->removeElement($salle);
$salle->removeSupport($this);
}
return $this;
}
public function getTypedeSupport(): ?TypedeSupport
{
return $this->typedeSupport;
}
public function setTypedeSupport(?TypedeSupport $typedeSupport): self
{
$this->typedeSupport = $typedeSupport;
return $this;
}
public function getMatiere(): ?Matiere
{
return $this->matiere;
}
public function setMatiere(?Matiere $matiere): self
{
$this->matiere = $matiere;
return $this;
}
public function getFilename(): ?string
{
return $this->filename;
}
public function setFilename(?string $filename): Support
{
$this->fileName = $filename;
return $this;
}
/**
* #return null|File
*/
public function getSupportFile(): ?File
{
return $this->supportFile;
}
public function setSupportFile(?File $supportFile = null): Support
{
$this->supportFile = $supportFile;
if (null !== $supportFile) {
// 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;
}
public function setFileSize(?int $fileSize): void
{
$this->fileSize = $fileSize ;
}
public function getFileSize(): ?int
{
return $this->fileSize;
}
}
here my "newAction" to create a new "Support" from the controller: `
public function new(Request $request, $type, $salle, $matiere): Response
{
$user = $this->getUser();
if ($user->getRoles()==array('ROLE_PROF')) {
$support = new Support();
$type_de_support = $this->typedeSupportRepository->findOneBy(['intitule'=>$type]);
$salle_de_support = $this->salleRepository->findOneBy(['nom'=>$salle]);
$matiere_de_support = $this->matiereRepository->findOneBy(['intitule'=>$matiere]);
$support->setTypedeSupport($type_de_support)
->addSalle($salle_de_support)
->setMatiere($matiere_de_support);
$form = $this->createForm(SupportType::class, $support);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($support);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($support);
$entityManager->flush();
return $this->redirectToRoute('support_index',["type"=>$type,"salle"=>$salle ,"matiere"=>$matiere]);
}`
here is my 'SupportType' to generate the Form: `
namespace App\Form;
use App\Entity\Support;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SupportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('titre')
->add('supportFile', FileType::class)
->add('desciption')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Support::class,
]);
}
}
and then the configuration File :
vich_uploader:
db_driver: orm
mappings:
support_file:
uri_prefix: /supports/teste
upload_destination: '%kernel.project_dir%/public/supports/teste'
namer: vich_uploader.namer_uniqid
the view :
<div class="col-md-4">
<div class="card text-white bg-primary mb-3">
<div class="card-header text-center" style="font-size: 2.3rem;">Modifier le support</div>
<div class="card-body">
<p class="card-text">
{% if error is defined and error %}
<div class=" alert alert-danger" style="font-size: 1.3rem;">
{{error}}
</div>
{% endif %}
{% for message in app.flashes('success') %}
<div class=" alert alert-success" style="font-size: 1.7rem;">
{{message}}
</div>
{% endfor %}</p>
<form method="post" action="" accept-charset="UTF-8">
<input type="hidden" name="action" value="users/send-password-reset-email">
<div class="form-group">
{{form_row(form.titre)}}
</div>
<div class="form-group">
{{form_row(form.supportFile)}}
</div>
<div class="form-group">
{{form_row(form.desciption)}}
</div>
{{ form_rest(form) }}
<input type="submit" value="{{ button_label|default('Save') }}" class="btn btn-success col-12">
</form>
<br><br>
</div>
</div>
</div>
despite the videos and forum i used, i've not found any solution. i need your Help
thanks :-)
The input "supportFile" need to be VichFileType::class and not a FileType::class
class SupportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('titre')
->add('supportFile', VichFileType::class)
->add('desciption')
;
}
...
}
doc : https://github.com/dustin10/VichUploaderBundle/blob/master/docs/form/vich_file_type.md

Form issue when trying to create a checkbox with CollectionType calling to another FromType (cross entities situation)

This case is a case study, I'm trying to resolve this issue in order to explain how to organize entities and create forms to my students.
I have this singular relation between 3 of my entities :
Protagonist <--(OneToMany)--> EventRegistration <--(ManyToOne)--> Event
Which could not be transformed as a many to many relation because there are some columns inside the EventRegistration table :
Protagonist :
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProtagonistRepository")
*/
class Protagonist
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $japaneseName;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $picture;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $background;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updated_at;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="protagonists")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Tag", mappedBy="protagonists")
*/
private $tags;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Registration", mappedBy="protagonist")
*/
private $registrations;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $isAlive;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Event", mappedBy="protagonists")
*/
private $events;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventRegistration", mappedBy="protagonist")
*/
private $eventRegistrations;
public function __construct()
{
$this->tags = new ArrayCollection();
$this->registrations = new ArrayCollection();
$this->events = new ArrayCollection();
$this->eventRegistrations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getJapaneseName(): ?string
{
return $this->japaneseName;
}
public function setJapaneseName(?string $japaneseName): self
{
$this->japaneseName = $japaneseName;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getPicture(): ?string
{
return $this->picture;
}
public function setPicture(?string $picture): self
{
$this->picture = $picture;
return $this;
}
public function getBackground(): ?string
{
return $this->background;
}
public function setBackground(?string $background): self
{
$this->background = $background;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(?\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
/**
* #return Collection|Tag[]
*/
public function getTags(): Collection
{
return $this->tags;
}
public function addTag(Tag $tag): self
{
if (!$this->tags->contains($tag)) {
$this->tags[] = $tag;
$tag->addProtagonist($this);
}
return $this;
}
public function removeTag(Tag $tag): self
{
if ($this->tags->contains($tag)) {
$this->tags->removeElement($tag);
$tag->removeProtagonist($this);
}
return $this;
}
/**
* #return Collection|Registration[]
*/
public function getRegistrations(): Collection
{
return $this->registrations;
}
public function addRegistration(Registration $registration): self
{
if (!$this->registrations->contains($registration)) {
$this->registrations[] = $registration;
$registration->setProtagonist($this);
}
return $this;
}
public function removeRegistration(Registration $registration): self
{
if ($this->registrations->contains($registration)) {
$this->registrations->removeElement($registration);
// set the owning side to null (unless already changed)
if ($registration->getProtagonist() === $this) {
$registration->setProtagonist(null);
}
}
return $this;
}
public function getIsAlive(): ?bool
{
return $this->isAlive;
}
public function setIsAlive(?bool $isAlive): self
{
$this->isAlive = $isAlive;
return $this;
}
/**
* #return Collection|Event[]
*/
public function getEvents(): Collection
{
return $this->events;
}
public function addEvent(Event $event): self
{
if (!$this->events->contains($event)) {
$this->events[] = $event;
$event->addProtagonist($this);
}
return $this;
}
public function removeEvent(Event $event): self
{
if ($this->events->contains($event)) {
$this->events->removeElement($event);
$event->removeProtagonist($this);
}
return $this;
}
/**
* #return Collection|EventRegistration[]
*/
public function getEventRegistrations(): Collection
{
return $this->eventRegistrations;
}
public function addEventRegistration(EventRegistration $eventRegistration): self
{
if (!$this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations[] = $eventRegistration;
$eventRegistration->setProtagonist($this);
}
return $this;
}
public function removeEventRegistration(EventRegistration $eventRegistration): self
{
if ($this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations->removeElement($eventRegistration);
// set the owning side to null (unless already changed)
if ($eventRegistration->getProtagonist() === $this) {
$eventRegistration->setProtagonist(null);
}
}
return $this;
}
}
EventRegistration :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRegistrationRepository")
*/
class EventRegistration
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetimetz")
*/
private $registrationDate;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Protagonist", inversedBy="eventRegistrations")
*/
private $protagonist;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Event", inversedBy="eventRegistrations")
*/
private $event;
public function getId(): ?int
{
return $this->id;
}
public function getRegistrationDate(): ?\DateTimeInterface
{
return $this->registrationDate;
}
public function setRegistrationDate(\DateTimeInterface $registrationDate): self
{
$this->registrationDate = $registrationDate;
return $this;
}
public function getProtagonist(): ?Protagonist
{
return $this->protagonist;
}
public function setProtagonist(?Protagonist $protagonist): self
{
$this->protagonist = $protagonist;
return $this;
}
public function getEvent(): ?Event
{
return $this->event;
}
public function setEvent(?Event $event): self
{
$this->event = $event;
return $this;
}
}
Event :
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRepository")
*/
class Event
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $start_date;
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $end_date;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Protagonist", inversedBy="events")
*/
private $protagonists;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventRegistration", mappedBy="event")
*/
private $eventRegistrations;
public function __construct()
{
$this->protagonists = new ArrayCollection();
$this->eventRegistrations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->start_date;
}
public function setStartDate(?\DateTimeInterface $start_date): self
{
$this->start_date = $start_date;
return $this;
}
public function getEndDate(): ?\DateTimeInterface
{
return $this->end_date;
}
public function setEndDate(?\DateTimeInterface $end_date): self
{
$this->end_date = $end_date;
return $this;
}
/**
* #return Collection|Protagonist[]
*/
public function getProtagonists(): Collection
{
return $this->protagonists;
}
public function addProtagonist(Protagonist $protagonist): self
{
if (!$this->protagonists->contains($protagonist)) {
$this->protagonists[] = $protagonist;
}
return $this;
}
public function removeProtagonist(Protagonist $protagonist): self
{
if ($this->protagonists->contains($protagonist)) {
$this->protagonists->removeElement($protagonist);
}
return $this;
}
/**
* #return Collection|EventRegistration[]
*/
public function getEventRegistrations(): Collection
{
return $this->eventRegistrations;
}
public function addEventRegistration(EventRegistration $eventRegistration): self
{
if (!$this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations[] = $eventRegistration;
$eventRegistration->setEvent($this);
}
return $this;
}
public function removeEventRegistration(EventRegistration $eventRegistration): self
{
if ($this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations->removeElement($eventRegistration);
// set the owning side to null (unless already changed)
if ($eventRegistration->getEvent() === $this) {
$eventRegistration->setEvent(null);
}
}
return $this;
}
}
I can access a collection of eventRegistrations with my Protagonist and Event entity, and I can access the protagonist and event with my EventRegistration entity.
The issue crops up when I try to create a checkbox with all the events available for the protagonist : I don't have any attribute that allows me to make a collection of those events :
ProtagonistType
<?php
namespace App\Form;
use App\Entity\Category;
use App\Entity\Event;
use App\Entity\EventRegistration;
use App\Entity\Protagonist;
use App\Entity\Tag;
use App\Repository\EventRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class ProtagonistType
* #package App\Form
*/
class ProtagonistType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('japaneseName', TextType::class, [
'required' => false
])
->add('description', TextareaType::class)
->add('picture', TextType::class, [
'required' => false
])
->add('background', TextType::class, [
'required' => false
])
->add('isAlive', CheckboxType::class)
->add('category', EntityType::class, [
'class' => Category::class,
'choice_label' => 'title',
'expanded' => true,
'multiple' => false
])
->add('tags', EntityType::class, [
'class' => Tag::class,
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'by_reference' => false,
])
**->add('eventRegistrations', CollectionType::class, [
'entry_type' => EventRegistrationType::class
])**
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Protagonist::class,
]);
}
}
EventRegistrationType :
<?php
namespace App\Form;
use App\Entity\Event;
use App\Entity\EventRegistration;
use App\Repository\EventRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class EventRegistrationType
* #package App\Form
*/
class EventRegistrationType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('event', EntityType::class, [
'class' => Event::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'by_reference' => false,
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => EventRegistration::class,
]);
}
}
The only effective solution I found is to create a ManyToMany relation between Protagonist and Event, then set another Registration table which is on a ManyToOne relation with Protagonist in order to get the protagonists registrations.
Still I'd like to make this many to many relation with extra fields works, I'm all ears for any solution you'd get to solve this issue.
Thank you!
Some theory
Entities are your models, business objects that have identities, data, behavior.
They are the heart, building blocks of your business model.
When we design entities - in the first place we should consider them as objects, that have their own shape and responsibilities instead of them being just containers for data stored in a database. Also, we should care about proper relations between entities.
Ideally, entities should always be valid. If so - they can be persisted at any time.
Persistence is a separate concern.
In general case, it's even not necessary for entities to be persisted into a database. They could just be persisted in memory, file system, key-value storage etc.
Forms is also a separate concern that is closer to working with user interfaces.
Forms help us to render user interface, translate requests from users to some structures of known shape that is easier to work with, than with raw data from requests, and validate this submitted data.
These structures are just containers for data retrieved from requests, they should not have any behavior.
These structures can be invalid at certain times.
What about the issue described?
So making entities playing roles of these forms underlying data structures might be not the best idea ever.
It's just clearly mixing of concerns and rigid coupling between different layers.
That is why you're having these issues.
So instead of using EventRegistration class as a data_class for EventRegistrationType and Protagonist - for ProtagonistType - consider creating separate data structures. Propagate submitted data to entities only when it's successfully validated.
Some useful links to read more on the topic (though the author calls underlying structures - commands somewhy):
Decoupling (Symfony2) Forms from Entities
Form, Command, and Model Validation
Other useful topics:
The Clean Architecture
Domain-Driven Design in PHP

how to use form builder to make form from entity relation of entity in symfony4

TL;TR:How to make form fields from formbuilder with relation column
Here is my Level Entity
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\LevelRepository")
*/
class Level
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=10)
*/
private $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $description;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Area", mappedBy="levelid", orphanRemoval=true)
*/
private $areas;
public function __construct()
{
$this->areas = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
/**
* #return Collection|Area[]
*/
public function getAreas(): Collection
{
return $this->areas;
}
public function addArea(Area $area): self
{
if (!$this->areas->contains($area)) {
$this->areas[] = $area;
$area->setLevelid($this);
}
return $this;
}
public function removeArea(Area $area): self
{
if ($this->areas->contains($area)) {
$this->areas->removeElement($area);
// set the owning side to null (unless already changed)
if ($area->getLevelid() === $this) {
$area->setLevelid(null);
}
}
return $this;
}
}
and this is my Area Entity
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\AreaRepository")
*/
class Area
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Property", mappedBy="area")
*/
private $property;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Level", inversedBy="areas")
* #ORM\JoinColumn(nullable=false)
*/
private $levelid;
public function __construct()
{
$this->property = new ArrayCollection();
$this->projects = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|property[]
*/
public function getProperty(): Collection
{
return $this->property;
}
public function addProperty(property $property): self
{
if (!$this->property->contains($property)) {
$this->property[] = $property;
$property->setArea($this);
}
return $this;
}
public function removeProperty(property $property): self
{
if ($this->property->contains($property)) {
$this->property->removeElement($property);
// set the owning side to null (unless already changed)
if ($property->getArea() === $this) {
$property->setArea(null);
}
}
return $this;
}
public function getLevelid(): ?Level
{
return $this->levelid;
}
public function setLevelid(?Level $levelid): self
{
$this->levelid = $levelid;
return $this;
}
}
Area connected to Level
And this is Area Form builder
<?php
namespace App\Form;
use App\Entity\Area;
// use App\Entity\District;
use App\Entity\Level;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class AreaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('Level', EntityType::class, array(
// looks for choices from this entity
'class' => Level::class,
'label' => 'Level',
// uses the User.username property as the visible option string
'choice_label' => 'name',
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Area::class,
]);
}
}
And the error is
Neither the property "Level" nor one of the methods "getLevel()", "level()", "isLevel()", "hasLevel()", "__get()" exist and have public access in class "App\Entity\Area".
Your problem is caused by form field and Entity field name mismatch. AreaType tries to refer to level property using Symfony's PropertyAccessor on Area while it does not have such field.
You should either rename your entity field from $levelid to $level (because it actually holds an Entity, not ID), changing setter and getter accordingly (recommended solution) or change form field name from Level to levelid.
You can find more information about Symfony Forms in official docummentation.

How to fix "but the new value must be an array or an instance of \Traversable" error in Symfony

I build a management app of bagages and voyages with Symfony 4, I created a "ManyToMany" relation between this entities.
When I want to add a new bagage with a destination (voyage) I have this error :
I have addVoyage and removeVoyage in my classes Voyage and Bagage.
You will find above my classes Bagage.php, my form BagageType.php and my controller BagageController.php
Bagage.php
<?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\BagageRepository")
*/
class Bagage
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
* #Assert\Length(min=5, max=50)
*/
private $nom;
/**
* #ORM\Column(type="json", nullable=true)
*/
private $objets = [];
/**
* #ORM\Column(type="datetime")
*/
private $dateCreation;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Voyage", mappedBy="bagages")
*/
private $voyages;
public function __construct()
{
$this->voyages = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getObjets(): ?array
{
return $this->objets;
}
public function setObjets(?array $objets): self
{
$this->objets = $objets;
return $this;
}
public function getDateCreation(): ?\DateTimeInterface
{
return $this->dateCreation;
}
public function setDateCreation(\DateTimeInterface $dateCreation): self
{
$this->dateCreation = $dateCreation;
return $this;
}
/**
* #return Collection|Voyage[]
*/
public function getVoyages(): Collection
{
return $this->voyages;
}
public function addVoyage(Voyage $voyage): self
{
if (!$this->voyages->contains($voyage)) {
$this->voyages[] = $voyage;
$voyage->addBagage($this);
}
return $this;
}
public function removeVoyage(Voyage $voyage): self
{
if ($this->voyages->contains($voyage)) {
$this->voyages->removeElement($voyage);
$voyage->removeBagage($this);
}
return $this;
}
}
?>
BagageType.php
<?php
namespace App\Form;
use App\Entity\Voyage;
use App\Entity\Bagage;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BagageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('voyages', EntityType::class, [
'class' => Voyage::class,
'choice_label' => 'lieu'
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Bagage::class,
]);
}
}
?>
BagageController.php
<?php
/**
* #Route("/bagages/nouveau", name="bagage_creation")
* #Route("/bagages/{id}/edit", name="bagage_edit")
*/
public function form(Bagage $bagage = null, Request $request, ObjectManager $manager)
{
if(!$bagage){
$bagage = new Bagage();
}
$form = $this->createForm(BagageType::class, $bagage);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
if(!$bagage->getId()){
$bagage->setDateCreation(new \Datetime());
}
$manager->persist($bagage);
$manager->flush();
return $this->redirectToRoute('bagage_show', [
'id' => $bagage->getId()
]);
}
return $this->render('bagages/create.html.twig', [
//creation d'une vue pour twig pour afficher le formulaire
'formBagage' => $form->createView(),
'editMode' => $bagage->getId() !== null
]);
}
?>
Does it miss an addVoyage call in my controller ?
EDIT
Voyage.php
<?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\VoyageRepository")
*/
class Voyage
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $lieu;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Bagage", inversedBy="voyages")
*/
private $bagages;
public function __construct()
{
$this->bagages = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getLieu(): ?string
{
return $this->lieu;
}
public function setLieu(string $lieu): self
{
$this->lieu = $lieu;
return $this;
}
/**
* #return Collection|Bagage[]
*/
public function getBagages(): Collection
{
return $this->bagages;
}
public function addBagage(Bagage $bagage): self
{
if (!$this->bagages->contains($bagage)) {
$this->bagages[] = $bagage;
}
return $this;
}
public function removeBagage(Bagage $bagage): self
{
if ($this->bagages->contains($bagage)) {
$this->bagages->removeElement($bagage);
}
return $this;
}
}
It looks like you should set multiple attribute to true, making the field's value an array instead of a single entity.
->add('voyages', EntityType::class, [
'class' => Voyage::class,
'choice_label' => 'lieu',
'multiple' => true,
]);

Categories