I'm trying to learn Symfony, and I found it don't access the doctrine from the entity.
I created an Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Facility
*
* #ORM\Table()
* #ORM\Entity
* #ORM\Entity(repositoryClass="AppBundle\Entity\FacilityRepository")
*/
class Facility
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="label", type="string", length=200)
*/
private $label;
/**
* #ORM\OneToOne(targetEntity="Facility")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set label
*
* #param string $label
* #return Facility
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* Get label
*
* #return string
*/
public function getLabel()
{
return $this->label;
}
/**
* Set parent
*
* #param \AppBundle\Entity\Facility $parent
* #return Facility
*/
public function setParent(\AppBundle\Entity\Facility $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* #return \AppBundle\Entity\Facility
*/
public function getParent()
{
return $this->parent;
}
public function __toString()
{
return $this->label;
}
}
and for FacilityType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FacilityType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('parent')
->add('label')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Facility'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_facility';
}
}
How can I get just the parents in the $builder->add('parent') (dropdown select) and not all the data.
Thanks in advance.
You don't need to access repository from an entity class. Entities should be plain php objects. If you want to limit options in your dropdown use query_builder property.
$builder->add('parent', 'entity', array(
'class' => 'AppBundle:Facility',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('f')
->where('/* ... */');
},
));
It depends on your case. If your filter criteria is fixed, then #b.b3rn4rd's answer is best. If the criteria depends on the current facility then you probably want to use a form event listener. Symfony forms have a fairly powerful architecture, where a single form type is created and then cloned for each instance. The buildForm() method is only called once for each form even if the field type is repeated multiple times on the page. In some cases the field is only rendered once, and that is fine but the safest way is to use a form listener. Rather than adding a generic parent field to the form builder, you are adding a specific field to the form instance.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('label');
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
/** #var Facility $facility */
$facility = $event->getData();
$form->add(
'parent',
'entity',
array(
'class' => 'AppBundle:Facility',
'query_builder' => function (FacilityRepository $repository) use ($facility) {
return $repository->createQueryBuilder('f')->findParents($facility);
},
)
);
});
}
Related
I'm making a a app in Symfony. I get Entities Answer and Question which are related. I want to enable users to add the answer to the question but I've got the problem with getting question_id to AnswerEntity.
Here it what I came up with:
Answer Controller
<?php
/**
* Answer Controller
*/
namespace App\Controller;
use App\Entity\Answer;
use App\Entity\Question;
use App\Form\AnswerType;
use App\Repository\AnswerRepository;
use App\Repository\QuestionRepository;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Flex\PackageFilter;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class AnswerController.
*
* #Route("/answer")
*/
class AnswerController extends AbstractController
{
private $answerRepository;
private $answer;
private $paginator;
/**
* AnswerController constructor
*
* #param \App\Repository\AnswerRepository $answerRepository Answer Repository
* #param \Knp\Component\Pager\PaginatorInterface $paginator
*/
public function __construct(AnswerRepository $answerRepository, PaginatorInterface $paginator)
{
$this->answerRepository = $answerRepository;
$this->paginator = $paginator;
}
/**
* Index action.
*
* #param \Symfony\Component\HttpFoundation\Request $request HTTP request
* #return \Symfony\Component\HttpFoundation\Response HTTP response
*
* #Route(
* "/",
* methods={"GET"},
* name="answer_index",
* )
*/
public function index(Request $request, PaginatorInterface $paginator, AnswerRepository $answerRepository): Response
{
$pagination = $paginator->paginate(
$answerRepository->queryAll(),
$request->query->getInt('page', 1),
AnswerRepository::PAGINATOR_ITEMS_PER_PAGE
);
return $this->render(
'answer/index.html.twig',
['pagination' => $pagination]
);
}
/**
* Create action.
*
* #param \Symfony\Component\HttpFoundation\Request $request HTTP request
*
* #param \App\Repository\AnswerRepository $answerRepository Answer repository
*
* #return \Symfony\Component\HttpFoundation\Response HTTP response
*
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*
* #Route(
* "/create",
* methods={"GET", "POST"},
* name="answer_create",
* )
*/
public function create(Request $request, AnswerRepository $answerRepository): Response
{
$answer = new Answer();
$form = $this->createForm(AnswerType::class, $answer);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$answer->setQuestion($this->getQuestion());
$answerRepository->save($answer);
$this->addFlash('success', 'answer_created_successfully');
return $this->redirectToRoute('answer_index');
}
return $this->render(
'answer/create.html.twig',
['form' => $form->createView()]
);
}
}
AnswerForm:
<?php
/**
* Answer type.
*/
namespace App\Form;
use App\Entity\Answer;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class AnswerType.
*/
class AnswerType extends AbstractType
{
/**
* Builds the form.
*
* This method is called for each type in the hierarchy starting from the
* top most type. Type extensions can further modify the form.
*
* #see FormTypeExtensionInterface::buildForm()
*
* #param \Symfony\Component\Form\FormBuilderInterface $builder The form builder
* #param array $options The options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add(
'AnswerText',
TextType::class,
[
'label' => 'label_answertext',
'required' => true,
'attr' => ['max_length' => 200],
]
);
$builder->add(
'Nick',
TextType::class,
[
'label' => 'label_nick',
'required' => true,
'attr' => ['max_length' => 64],
]
);
$builder->add(
'Email',
EmailType::class,
[
'label' => 'label_email',
'required' => true,
'attr' => ['max_length' => 64],
]
);
$builder->add('is_best', HiddenType::class, [
'data' => '0',
]);
}
/**
* Configures the options for this type.
*
* #param \Symfony\Component\OptionsResolver\OptionsResolver $resolver The resolver for the options
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['data_class' => Answer::class]);
}
/**
* Returns the prefix of the template block name for this type.
*
* The block prefix defaults to the underscored short class name with
* the "Type" suffix removed (e.g. "UserProfileType" => "user_profile").
*
* #return string The prefix of the template block name
*/
public function getBlockPrefix(): string
{
return 'answer';
}
}
and AnswerEntity
<?php
namespace App\Entity;
use App\Repository\AnswerRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=AnswerRepository::class)
* #ORM\Table(name="answers")
*/
class Answer
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $answer_text;
/**
* #ORM\ManyToOne(targetEntity=Question::class, inversedBy="answer")
* #ORM\JoinColumn(nullable=false)
*/
private $question;
/**
* #ORM\Column(type="string", length=64)
*/
private $email;
/**
* #ORM\Column(type="string", length=64)
*/
private $Nick;
/**
* #ORM\Column(type="integer")
*/
private $isBest;
public function getId(): ?int
{
return $this->id;
}
public function getAnswerText(): ?string
{
return $this->answer_text;
}
public function setAnswerText(string $answer_text): void
{
$this->answer_text = $answer_text;
}
public function getQuestion(): ?Question
{
return $this->question;
}
public function setQuestion(?Question $question): void
{
$this->question = $question;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getNick(): ?string
{
return $this->Nick;
}
public function setNick(string $Nick): self
{
$this->Nick = $Nick;
return $this;
}
public function getIsBest(): ?int
{
return $this->isBest;
}
public function setIsBest(int $isBest): self
{
$this->isBest = $isBest;
return $this;
}
}
The error is:
[enter image description here][1]
[1]: https://i.stack.imgur.com/fYGpo.png
but I have a function getQuestion in AnswerEntity so why doesn't it read that? It read the function setQuestion which is the same Entity.
Is there any chance to do it another way?
Also I'm adding part of my question template code where I get to create_answer
<a href="{{ url('answer_create', {id: questions.id}) }}" title="{{ 'create_answer'|trans }}">
{{ 'create_answer'|trans }}
</a>
Why don't you have the questionId in your route. I think it's a mandatory data to know which question the users tries to answer to.
With a requestParam id in the route and a method param typed Question the ParamConverter can inject the right question in your controller method
/**
* ...
* #Route(
* "/create/{id}",
* methods={"GET", "POST"},
* name="answer_create",
* )
*/
public function create(Request $request, AnswerRepository $answerRepository, Question $question): Response
{
$answer = new Answer();
$answer->setQuestion($question);
// ....
The answer is in the error image you posted, there is no $this->getQuestion() in a controller unless you create that function.
You can get the question entity by any identifier with Doctrine
$question = this->getDoctrine()->getRepository(Question::class)->find($question_id);
and then you can set the question relation in your $answer object
$answer->setQuestion($question);
I tried to find an answer for my question but it was unsuccessful. I'm using Symfony (Ive been using it 2 months) and I have a problem when I want to make many to many relationship.
I have homes and I have services. One home can have a lot of services and one service can have a lot of homes. Everything is ok and I understand the way many to many works with the doctrine, i persisted all values before flush in my controller, but i always get this message:
An exception occurred while executing 'INSERT INTO homess_services (home_id, service_id) VALUES (?, ?)' with params [25, 7]:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '25' for key 'home_id'
My codes are (home entity):
<?php
namespace Filip\SymfonyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Filip\SymfonyBundle\FilipSymfonyBundle;
/**
* Home
*
* #ORM\Table(name="homes")
* #ORM\Entity
*
*
*/
class Home
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Service", inversedBy="homes")
* #ORM\JoinTable(name="homess_services")
*/
protected $services;
public function __construct() {
$this->photos = new ArrayCollection();
$this->services = new ArrayCollection();
}
/**
* Renders a publication as a string
*
* #return string
*/
public function __toString (){
return $this->getName();
}
/**
* Add services
*
* #param \Filip\SymfonyBundle\Entity\Service $services
* #return Home
*/
public function addService(\Filip\SymfonyBundle\Entity\Service $services)
{
$this->services[] = $services;
return $this;
}
/**
* Remove services
*
* #param \Filip\SymfonyBundle\Entity\Service $services
*/
public function removeService(\Filip\SymfonyBundle\Entity\Service $services)
{
$this->services->removeElement($services);
}
/**
* Get services
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getServices()
{
return $this->services;
}
Service entity:
<?php
namespace Filip\SymfonyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Filip\SymfonyBundle\FilipSymfonyBundle;
/**
* Service
*
* #ORM\Table("services")
* #ORM\Entity
*/
class Service
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Home", mappedBy="services")
*/
protected $homes;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM
\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="category", type="string", length=45)
*/
private $category;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45)
*/
private $name;
/**
* Renders a service as a string
*
* #return string
*/
/**
* Get id
*
6
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set category
*
* #param string $category
* #return Service
*/
public function setCategory($category)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return string
*/
public function getCategory()
{
return $this->category;
}
/**
* Set name
*
* #param string $name
* #return Service
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function __construct()
{
$this->homes = new ArrayCollection();
}
/**
* Add homes
*
* #param \Filip\SymfonyBundle\Entity\Home $homes
* #return Service
*/
public function addHome(\Filip\SymfonyBundle\Entity\Home $homes)
{
$this->homes[] = $homes;
return $this;
}
/**
* Remove homes
*
* #param \Filip\SymfonyBundle\Entity\Home $homes
*/
public function removeHome(\Filip\SymfonyBundle\Entity\Home $homes)
{
$this->homes->removeElement($homes);
}
/**
* Get homes
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getHomes()
{
return $this->homes;
}
}
Form(HomeType):
<?php
namespace Filip\SymfonyBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class HomeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('city')
->add('region')
->add('phone')
->add('address')
->add('email')
->add('website')
->add('content')
->add('youtubeApi')
->add('premium')
->add('services' , 'entity' , array(
'class' => 'FilipSymfonyBundle:Service' ,
'property' => 'name' ,
'expanded' => true ,
'multiple' => true ,
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Filip\SymfonyBundle\Entity\Home'
));
}
/**
* #return string
*/
public function getName()
{
return 'filip_symfonybundle_home';
}
}
Home controller:
/**
* Creates a new Home entity.
*
* #Route("/", name="home_create")
* #Method("POST")
* #Template("FilipSymfonyBundle:Home:new.html.twig")
*/
public function createAction(Request $request)
{
$entity = new Home();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$services = $entity->getServices();
foreach($services as $service) {
$entity->addService($service);
$em->persist($service);
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('home_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
/**
* Creates a form to create a Home entity.
*
* #param Home $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Home $entity)
{
$form = $this->createForm(new HomeType(), $entity, array(
'action' => $this->generateUrl('home_create'),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Create'));
return $form;
}
in the if($form->isValid()) part you don't need to call the addService() method in the foreach loop as Symfony will do this for you on the handleRequest() call.
If the Home entity is at the owning side of the ManyToMany relation you don't need to persist every Service object too. So you can try to remove the whole foreach loop too.
I have a form which works as I expect it to (code below) and I can populate this with one row from a database via Doctrine.
However, I'm struggling to see how to repeat the elements of this form to edit all rows (e.g. 20 of them) in one form; with the hope of updating all fields of all rows in a single form on a single page.
For example:
name[0]
description[0]
url[0]
name[1]
description[1]
url[1]
name[2]
description[2]
url[2]
I've see the use of collection's suggested but they don't seem to suit repeating all of the form from what I can understand.
Form:
namespace Acme\Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'name',
'text',
array(
'label' => 'Company Name'
)
)
->add(
'description',
'text',
array(
'label' => 'Company Description'
)
)
->add(
'url',
'text',
array(
'label' => 'Company URL'
)
)
->add(
'Update Companies',
'submit'
);
}
public function getName()
{
return 'ContactType';
}
}
Entity:
<?php
namespace Acme\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Contacts
*
* #ORM\Table(options={"engine":"MyISAM", "collate":"utf8_general_ci", "charset":"utf8"})
* #ORM\Entity
*/
class Contacts
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="text", nullable=true)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="description", type="text", nullable=true)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="url", type="text", nullable=true)
*/
private $url;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Contacts
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set description
*
* #param string $description
* #return Contacts
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set url
*
* #param string $url
* #return Contacts
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get url
*
* #return string
*/
public function getUrl()
{
return $this->url;
}
}
I think the collections will suit your need.
Create a form class which have one field, the collection of your ContactType :
class MyFormClassType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'myField',
'collection',
array(
'type' => new ContactType ()
)
);
}
public function getName()
{
return 'my_class';
}
}
With this you can add, in the same form, a collection of Contact !
Read More, from the doc
I have a problem for a while and I have read a lot on this topic with similar problem but cant implement the answers in my case.
I have a select field that I populate with Ajax. so in my form builder I have this code :
VilleType.php
/**
* #ORM\Entity(repositoryClass="MDB\AnnonceBundle\Entity\RegisterRepository")
*/
class VilleType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nomComplet', 'choice'
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MDB\AdresseBundle\Entity\Ville'
));
}
/**
* #return string
*/
public function getName() {
return 'mdb_adressebundle_ville';
}
}
But my form never validates because their is no value in this choice field. But I can't add the value inside cause I don't know in advance what user will enter as a value.
So my question is how to disable verification on this field from Symfony. Or allow it to accept all value.
Thanks
EDIT
Here, I tried a new approach. I use The event Listener to modify my field with the value than the user submitted.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nomComplet', 'choice');
$builder->get('nomComplet')->addEventListener(
FormEvents::PRE_SUBMIT, function(FormEvent $event) /* use ($formModifier) */ {
$ville = $event->getData();
$event->getForm()->add('nomComplet', 'choice', array('choices' => $ville));
// $formModifier($event->getForm()->getParent(), $ville);
}
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MDB\AdresseBundle\Entity\Ville'
));
}
/**
* #return string
*/
public function getName() {
return 'mdb_adressebundle_ville';
}
}
MDB\AdresseBundle\Entity\Ville.php
<?php
namespace MDB\AdresseBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Ville
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="MDB\AdresseBundle\Entity\VilleRepository");
*/
class Ville
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nomComplet", type="string", length=255)
*/
private $nomComplet;
/**
* #var string
*
* #ORM\Column(name="nomClean", type="string", length=255)
*/
private $nomClean;
/**
* #var array
*
* #ORM\Column(name="cp", type="simple_array")
*/
private $cp;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set nomComplet
*
* #param string $nomComplet
* #return Ville
*/
public function setNomComplet($nomComplet)
{
$this->nomComplet = $nomComplet;
return $this;
}
/**
* Get nomComplet
*
* #return string
*/
public function getNomComplet()
{
return $this->nomComplet;
}
/**
* Set nomClean
*
* #param string $nomClean
* #return Ville
*/
public function setNomClean($nomClean)
{
$this->nomClean = $nomClean;
return $this;
}
/**
* Get nomClean
*
* #return string
*/
public function getNomClean()
{
return $this->nomClean;
}
/**
* Set cp
*
* #param array $cp
* #return Ville
*/
public function setCp($cp)
{
$this->cp = $cp;
return $this;
}
/**
* Get cp
*
* #return array
*/
public function getCp()
{
return $this->cp;
}
public function __toString()
{
return $this->nomComplet;
}
}
But still its not working, I have following error:
You cannot add children to a simple form. Maybe you should set the option "compound" to true?
So if someone know how to use this way with Event Listener it would be great.
Thanks
This should work
https://github.com/LPodolski/choiceAjaxLoad/blob/master/src/AppBundle/Form/ItemType.php
Entire project demonstrating this case:
https://github.com/LPodolski/choiceAjaxLoad
Code in case file is removed/altered:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('choiceField', 'choice', array(
'attr' => array(
'class' => 'choiceField'
)
))
;
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$form->remove('choiceField');
$form->add('choiceField', 'choice', array(
'attr' => array(
'class' => 'choiceField',
),
'choices' => array(
$data['choiceField'] => $data['choiceField'],
)
));
});
$builder->add('save', 'submit');
}
So my question is how to disable verification on this field from Symfony.
According to the Documentation you can suppress form validation by using the POST_SUBMIT event and prevent the ValidationListener from being called.
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$event->stopPropagation();
}, 900); // Always set a higher priority than ValidationListener
First of all: the error I'm getting is: Entities passed to the choice field must be managed
I have these entities:
- user (belongs to one or many teams)
- team (has one or 2 users)
- challenge (has 2 teams)
I'd like to build a ChallengeType form where a user can fill in the two users for the two teams and create the challenge. I think I need an embedded form here.
I've made a TeamType Form class: (I would expect to get a select box from this, where all users are listed)
<?php
namespace Tennisconnect\DashboardBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class TeamType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('players', 'entity', array(
'class' => 'TennisconnectUserBundle:User',
'multiple' => true
));
}
public function getName()
{
return 'team';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Tennisconnect\DashboardBundle\Entity\Team');
}
}
This is the ChallengeType form class:
<?php
namespace Tennisconnect\DashboardBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ChallengeType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('teams', 'collection', array('type' => new TeamType()));
}
public function getName()
{
return 'challenge';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Tennisconnect\DashboardBundle\Entity\Challenge');
}
}
Challenge entity:
namespace Tennisconnect\DashboardBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Tennisconnect\DashboardBundle\Entity\Team;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="challenge")
*/
class Challenge
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="Team", mappedBy="teams")
*/
protected $teams;
public function __construct()
{
$this->teams = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add teams
*
* #param Tennisconnect\DashboardBundle\Entity\Team $teams
*/
public function addTeam(Team $teams)
{
$this->teams[] = $teams;
}
/**
* Get teams
*
* #return Doctrine\Common\Collections\Collection
*/
public function getTeams()
{
return $this->teams;
}
}
Team entity:
namespace Tennisconnect\DashboardBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Tennisconnect\UserBundle\Entity\User;
use Tennisconnect\DashboardBundle\Entity\Challenge;
use Tennisconnect\DashboardBundle\Entity\Match;
/**
* #ORM\Entity
* #ORM\Table(name="team")
*/
class Team
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="Tennisconnect\UserBundle\Entity\User", mappedBy="teams")
*/
protected $players;
/**
* #ORM\ManyToMany(targetEntity="challenge", inversedBy="teams", cascade= {"persist"})
*/
protected $challenges;
/**
* #ORM\ManyToMany(targetEntity="Match", inversedBy="teams")
*/
protected $matches;
public function __construct()
{
$this->players = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add players
*
* #param Tennisconnect\UserBundle\Entity\User $players
*/
public function addUser(User $players)
{
$this->players[] = $players;
}
/**
* Get players
*
* #return Doctrine\Common\Collections\Collection
*/
public function getPlayers()
{
return $this->players;
}
/**
* Add matches
*
* #param Tennisconnect\DashboardBundle\Entity\Match $matches
*/
public function addMatch(Match $matches)
{
$this->matches[] = $matches;
}
/**
* Get matches
*
* #return Doctrine\Common\Collections\Collection
*/
public function getMatches()
{
return $this->matches;
}
/**
* Add challenges
*
* #param Tennisconnect\DashboardBundle\Entity\challenge $challenges
*/
public function addchallenge(challenge $challenges)
{
$this->challenges[] = $challenges;
}
/**
* Get challenges
*
* #return Doctrine\Common\Collections\Collection
*/
public function getChallenges()
{
return $this->challenges;
}
}
Challenge controller:
class ChallengeController extends Controller
{
public function newAction()
{
$challenge = new Challenge();
$form = $this->createForm(new ChallengeType(), $challenge);
return $this->render('TennisconnectDashboardBundle:Challenge:new.html.twig', array('form' => $form->createView()));
}
}
You've created forms that are displaying a ManyToMany collection; set the multiple option in your formbuilder for those widgets to true (it defaults false, which fundamentally conflicts with a ToMany relationship).
If you have the error Entities passed to the choice field must be managed. Maybe persist them in the entity manager? with a ManyToMany relationship between 2 entities, when using a form type, it may come from your entity constructor :
If your form is "TeamType", try to remove the ArrayCollection initialization of your "Team" entity.
Your Team class become :
namespace Tennisconnect\DashboardBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Tennisconnect\UserBundle\Entity\User;
use Tennisconnect\DashboardBundle\Entity\Challenge;
use Tennisconnect\DashboardBundle\Entity\Match;
/**
* #ORM\Entity
* #ORM\Table(name="team")
*/
class Team
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="Tennisconnect\UserBundle\Entity\User", mappedBy="teams")
*/
protected $players;
/**
* #ORM\ManyToMany(targetEntity="challenge", inversedBy="teams", cascade= {"persist"})
*/
protected $challenges;
/**
* #ORM\ManyToMany(targetEntity="Match", inversedBy="teams")
*/
protected $matches;
public function __construct()
{
// REMOVE $this->players = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add players
*
* #param Tennisconnect\UserBundle\Entity\User $players
*/
public function addUser(User $players)
{
$this->players[] = $players;
}
/**
* Get players
*
* #return Doctrine\Common\Collections\Collection
*/
public function getPlayers()
{
return $this->players;
}
/**
* Add matches
*
* #param Tennisconnect\DashboardBundle\Entity\Match $matches
*/
public function addMatch(Match $matches)
{
$this->matches[] = $matches;
}
/**
* Get matches
*
* #return Doctrine\Common\Collections\Collection
*/
public function getMatches()
{
return $this->matches;
}
/**
* Add challenges
*
* #param Tennisconnect\DashboardBundle\Entity\challenge $challenges
*/
public function addchallenge(challenge $challenges)
{
$this->challenges[] = $challenges;
}
/**
* Get challenges
*
* #return Doctrine\Common\Collections\Collection
*/
public function getChallenges()
{
return $this->challenges;
}
}
The problem is solved.
I had to add the "allow_add" option to my collection in the ChallengeType class.
The challenge controller class needed some editing too. I added 2 teams to the Challenge object before passing it through to the form.