I have no problem to persist from ManytoMany(normal) and ManytoOne, however, I don't understand the proper way to persist entity from OnetoMany and ManytoMany(Reversed).
Here is an example :
ExampleEntity :
...
/**
* #var ArrayCollection $myentities
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Myentity", mappedBy="exampleentity", cascade={"persist"})
*/
private $myentities;
// getters/setters
/*
public function addMyentity(Myentity $myentitie)
public function removeMyentity(Myentity $myentitie)
public function getMyentities()
public function setMyentities(ArrayCollection $myentities)
*/
...
Form :
...
->add('myentities', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array(
'class' => 'AppBundle\Entity\Myentity',
'multiple' => true
))
...
Controller :
...
if ($form->handleRequest($request)->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($exampleentity);
$em->flush();
}
}
...
So here is the problem, add and remove function are never called, so all "$myentities" are never persist correcly.
I try to update my form with "by_reference => false" but it is not documented for EntityType (and also create other errors).
I try to change my controller but during edit how detect which one have been select (easy) and which ones have been unselected (not easy or not optimised) ?
Doctrine is not supposed to simplify theses kind of tasks ?
Solution in case anyone stumbles upon this same problem:
You need to have proper setters/getters:
class MyEntity
{
/**
* #var Groupe[]|ArrayCollection
*
* #ORM\OneToMany(targetEntity="Groupe", mappedBy="otherEntity")
*/
protected $notifyGroupsOnNewMail;
public function addNotifyGroupsOnNewMail(Groupe $notifyGroupsOnNewMail): self
{
if (!$this->notifyGroupsOnNewMail->contains($notifyGroupsOnNewMail))
{
$this->notifyGroupsOnNewMail[] = $notifyGroupsOnNewMail;
$notifyGroupsOnNewMail->setMyEntity($this);
}
return $this;
}
public function removeNotifyGroupsOnNewMail(Groupe $notifyGroupsOnNewMail): self
{
if ($this->notifyGroupsOnNewMail->contains($notifyGroupsOnNewMail))
{
$this->notifyGroupsOnNewMail->removeElement($notifyGroupsOnNewMail);
// set the owning side to null (unless already changed)
if ($notifyGroupsOnNewMail->getMyEntity() === $this)
{
$notifyGroupsOnNewMail->setMyEntity(null);
}
}
return $this;
}
}
But also have 'by_reference' => false in your form, otherwise your setters are not called, and therefore trying to persist from the reverse side will not save anything:
->add('notifyGroupsOnNewMail', EntityType::class, [
'class' => Groupe::class,
'required' => false,
'multiple' => true,
'by_reference' => false
])
Did you check if during the post request, added child elements has been sent to the server?
If you are using form prototype, maybe you have to check input index.
Related
I have a ManyToMany relation in my Symfony 4.2.6 application and I would like for it to be possible to have this to be null.
So my first entity SpecialOffers is as follows :
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\SpecialOfferRepository")
*/
class SpecialOffer
{
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Neighbourhood", inversedBy="specialOffers")
*/
private $neighbourhood;
public function __construct()
{
$this->neighbourhood = new ArrayCollection();
}
/**
* #return Collection|Neighbourhood[]
*/
public function getNeighbourhood(): Collection
{
return $this->neighbourhood;
}
public function addNeighbourhood(Neighbourhood $neighbourhood): self
{
if (!$this->neighbourhood->contains($neighbourhood)) {
$this->neighbourhood[] = $neighbourhood;
}
return $this;
}
public function removeNeighbourhood(Neighbourhood $neighbourhood): self
{
if ($this->neighbourhood->contains($neighbourhood)) {
$this->neighbourhood->removeElement($neighbourhood);
}
return $this;
}
}
It is related to the neighbourhood class :
/**
* #ORM\Entity(repositoryClass="App\Repository\NeighbourhoodRepository")
*/
class Neighbourhood implements ResourceInterface
{
/**
* #ORM\ManyToMany(targetEntity="App\Entity\SpecialOffer", mappedBy="neighbourhood")
*/
private $specialOffers;
public function __construct()
{
$this->specialOffers = new ArrayCollection();
}
/**
* #return Collection|SpecialOffer[]
*/
public function getSpecialOffers(): Collection
{
return $this->specialOffers;
}
public function addSpecialOffer(SpecialOffer $specialOffer): self
{
if (!$this->specialOffers->contains($specialOffer)) {
$this->specialOffers[] = $specialOffer;
$specialOffer->addNeighbourhood($this);
}
return $this;
}
public function removeSpecialOffer(SpecialOffer $specialOffer): self
{
if ($this->specialOffers->contains($specialOffer)) {
$this->specialOffers->removeElement($specialOffer);
$specialOffer->removeNeighbourhood($this);
}
return $this;
}
}
And finally the form is
class SpecialOfferType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'neighbourhood',
EntityType::class,
[
'class' => Neighbourhood::class,
'label' => 'form.neighbourhood.label',
'translation_domain' => 'Default',
'required' => false,
'placeholder' => 'form.neighbourhood.all'
]
);
}
}
But when I don't select a specific neighbourhood for the Special offer in my form I get the following error :
Could not determine access type for property "neighbourhood" in class "App\Entity\SpecialOffer": The property "neighbourhood" in class "App\Entity\SpecialOffer" can be defined with the methods "addNeighbourhood()", "removeNeighbourhood()" but the new value must be an array or an instance of \Traversable, "NULL" given.
Is there anyway I can make it so that my special offer either contains and array of neighbourhoods or just null ?
I feel like I'm overlooking something really obvious, any help would be greatly appreciated
Test =>
$builder
->add(
'neighbourhood',
EntityType::class,
[
'class' => Neighbourhood::class,
'label' => 'form.neighbourhood.label',
'translation_domain' => 'Default',
'required' => false,
'multiple' => true,
'placeholder' => 'form.neighbourhood.all'
]
);
Since your fields on the entities are both many-to-many, thus expecting an array (or similar) and the form field is of EntityType, which will return one Entity of the expected type or null, I feel like there is some form of asymmetry.
I would consider using the CollectionType from the start or at least setting the multiple option on the form to true, so that the return value is an array.
Another option would be to add a DataTransformer to the form field, which turns null into an empty array and one entity into an array of one entity, and vice-versa.
I'm experiencing an odd error with my form validation in Symfony 4. It is a simple contact form represented by this entity:
class ContactRequest
{
/** #var int */
private $id;
/** #var string */
private $fullName;
//...
/**
* #return string
*/
public function getFullName() : string
{
return $this->fullName;
}
In my controller I'm handling the submission as per Symfony website but there is something I'm missing for I'm getting the following error:
Type error: Return value of App\Entity\ContactRequest::getFullName() must be of the type string, null returned
Now, I know what is the meaning of that: it expects a string to be returned by the method getFullName whereas null is actually returned but I don't understand why.
This is my controller
public function contactSubmit(Request $request, ValidatorInterface $validator)
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if($form->isValid()){
//...
}
$errors = $validator->validate($form);
Shouldn't the handleRequest() method set the values in the entity for me?
To my surprise, when I have initialised the entity before, it worked well notwithstanding the entity is already set in the configureOptions() method in the form:
$contact = new ContactRequest;
$contact
->setFullName($request->request->get('contact')['fullName'])
//...
$form = $this->createForm(
ContactType::class
$contact
);
$form->setData($contact);
$form->handleRequest($request);
However, shouldn't the scope of using the handleRequest() be to avoid setting the entity's values manually? Shouldn't the handleRequest() method take care of setting those values?
I know I could validate the submitted data against the entity too (thing that I've successfully tried), without using the handleRequest() at all but it would piss me off a little. Why would I need to set a form in such a case?
This is the ContactType form:
//...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fullName', TextType::class, [
'required' => true,
'empty_data' => 'a',
'attr' => [
'placeholder' => 'Full Name',
'class' => 'form-control'
]
])
//...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ContactRequest::class
]);
}
In order to find out where your getFullName gets called you could (atleast in dev environment) print the backtrace on the call:
/**
* #return string
*/
public function getFullName() : string
{
if ($this->fullName === null)
{
echo "<pre>getFullName on uninitialized entity:\n";
debug_print_backtrace();
die();
}
return $this->fullName;
}
But as said in the comments: Initializing the entity with a null value in that field and not allowing the getter to return a null value seems kinda odd to me, so : ?string to allow for nullable return values (as of PHP 7.1) seems to be the next best option.
Trying to remove entity record(s) from a form collection that is embedded inside another form. I have successfully added entity record(s) to this same form collection.
The form collection entity (quoteItemDeliverable) is in a 1 to Many relationship with the parent entity (quoteItem).
Using symfony 3.3 / Doctrine and have been working from the respective documentation https://symfony.com/doc/3.3/form/form_collections.html
Using the documentation I can remove the form elements, that being the item in the collection as it exists in the form (twig). The dump() in the controller code below will show the items are removed from the collection in the edit form. But when the $quoteItem is persisted the removed collection items in the form are not removed from the collection in the entity (records not removed).
The controller code we are using
/**
* Edit an existing ring quote entity.
*
* #Route("/{id}/edit", name="quote-ring_edit")
* #Method({"GET", "POST"})
*/
public function editAction (Request $request, QuoteItem $quoteItem)
{
$type = $quoteItem->getCategory();
$form = $this->createForm('UniflyteBundle\Form\QuoteItemType', $quoteItem, ['allow_extra_fields' => true, 'attr' => ['quote-type' => 2, 'category-type' => $type]]);
$form->add('QuoteRing', QuoteRingType::class, ['label' => false]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($quoteItem);
$em = $this->getDoctrine()->getManager();
$em->persist($quoteItem);
$em->flush();
//return $this->redirectToRoute('quote-ring_edit', ['id' => $quoteItem->getId()]);
}
return $this->render("quoteitem/ring.html.twig", [
'quoteItem' => $quoteItem,
'form_new' => false,
'type' => $type,
'form' => $form->createView()
]);
}
The form Type for the collection looks like
->add('quoteItemDeliverables', CollectionType::class, [
'entry_type' => QuoteItemDeliverableType::class,
'entry_options' => array ('label' => false),
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
])
The quoteItem entity to form collection annotation.
/**
* #ORM\OneToMany(
* targetEntity="UniflyteBundle\Entity\QuoteItemDeliverable",
* mappedBy="quoteItem",
* cascade={"persist","detach","remove"}
* )
*/
private $quoteItemDeliverables;
The methods in the quoteItem class for adding and removing items from the collection
/**
* Add quoteItemDeliverable
*
* #param \UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable
*
* #return QuoteItem
*/
public function addQuoteItemDeliverable (\UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable)
{
$quoteItemDeliverable->setQuoteItem($this);
$this->quoteItemDeliverables[] = $quoteItemDeliverable;
return $this;
}
/**
* Remove quoteItemDeliverable
*
* #param \UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable
*/
public function removeQuoteItemDeliverable (\UniflyteBundle\Entity\QuoteItemDeliverable $quoteItemDeliverable)
{
$this->quoteItemDeliverables->removeElement($quoteItemDeliverable);
}
/**
* Get quoteItemDeliverables
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getQuoteItemDeliverables ()
{
return $this->quoteItemDeliverables;
}
The documentation states that for a one-to-many we might have to explicitly remove the relationship (snipplet from documentation link provided above). I don't understand why.
foreach ($originalTags as $tag) {
if (false === $task->getTags()->contains($tag)) {
// remove the Task from the Tag
$tag->getTasks()->removeElement($task);
// if it was a many-to-one relationship, remove the relationship like this
// $tag->setTask(null);
$em->persist($tag);
// if you wanted to delete the Tag entirely, you can also do that
// $em->remove($tag);
}
Does anyone have an idea as to what I am doing wrong with the controller that the dump is reflecting properly but the action with the persist removing the records is not happening?
Thank you in advance for your time and efforts assisting in this challenge.
Update - : I followed DonCallisto ' s advice and was successful in removing items from the collection at the entity level with the following in the controller
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
// NOTE: Remove the quote item deliverables - will need for additional work as well?
foreach ($em->getRepository('UniflyteBundle:QuoteItemDeliverable')->findAll() as $quoteItemDeliverable) {
if (false === $quoteItem->getQuoteItemDeliverables()->contains($quoteItemDeliverable)) {
$em->remove($quoteItemDeliverable);
$em->persist($quoteItem);
}
}
$em->flush();
return $this->redirectToRoute('blah', ['id' => $quoteItem->getId()]);
}
That's because you're removing it from the inversed side of the relation. Doctrine can actually check only for changes in owning side, so it's like you're not removing it at all as owning side isn't changed.
You need to remove the item programmatically trough EntityManager or set orphanRemoval to true in the annotation (but I won't recommend it!).
I won't recommend to use orphanRemoval because, even if it saves you from writing more code, it makes impossible to re-assign removed element to other collections (just saying for keep you safe from future headaches)
Moreover be sure to have by_reference => false in the from at collection field, otherwise add* and remove* methods won't be called.
I have two entites. "Institution" and "User". The Insitution have a onetomany relation to user. I create a form "InstitutionType" and if i want to save a new insitution the "handleReqeust($request)" throw this exception.
Neither the property "users" nor one of the methods "addUser()"/"removeUser()", "setUsers()", "users()", "__set()" or "__call()" exist and have public access in class "App\UserBundle\Entity\Institution".
Entity User
/**
* #ORM\ManyToOne(targetEntity="Institution", inversedBy="users")
* #ORM\JoinColumn(name="institution_id", referencedColumnName="id")
*/
protected $institution;
public function setInstitution(\App\UserBundle\Entity\Institution $institution = null)
{
$this->institution = $institution;
return $this;
}
public function getInstitution()
{
return $this->institution;
}
Entity Institution
/**
* #ORM\OneToMany(targetEntity="App\UserBundle\Entity\User", mappedBy="institution")
*/
protected $users;
public function addUser(\App\UserBundle\Entity\User $users)
{
$this->users[] = $users;
return $this;
}
public function removeUser(\Ebm\UserBundle\Entity\User $users)
{
$this->users->removeElement($users);
}
public function getUsers()
{
return $this->users;
}
InstitutionType
$users = $this->entityManager->getRepository('AppUserBundle:User')->findByIsActive(true);
->add('users', 'entity', array(
'label' => 'responsibleperson',
'attr' => array(),
'class' => 'AppUserBundle:User',
'choices' => $users,
'multiple' => false,
'expanded' => false,
'empty_value' => '----------------')
)
Can someone help my to solve this issue?
Rename addUser to addUsers and removeUser to removeUsers.
Symfony2/Doctrine obviously has no knowledge of singular/plural words and can't guess that to add a single entry to the users collection it would be semantically more correct to call addUser() instead of addUsers.
Please note that you can always use the console command doctrine:generate:entities AcmeDemoBundle:Institution to generate missing fields in the entity classes.
If this doesn't help you need to make sure you use the same notation in your form type like in your entity configuration (annotation, yml or xml).
I'm trying to display a form with a collection. The collection should display an empty sub-form. Due to the projects nature I can't rely on JavaScript to do so.
Googling didn't help and I does not seem to work by adding an empty entity to the collection field.
What I have so far:
public function indexAction($id)
{
$em = $this->getDoctrine()->getManager();
$event = $em->getRepository('EventBundle:EventDynamicForm')->find($id);
$entity = new Booking();
$entity->addParticipant( new Participant() );
$form = $this->createForm(new BookingType(), $entity);
return array(
'event' => $event,
'edit_form' => $form->createView()
);
}
In BookingType.php buildForm()
$builder
->add('Participants', 'collection')
In the Twig template
{{ form_row(edit_form.Participants.0.companyName) }}
If I put the line $entity->addParticipant( new Participant() ); in indexAction() I get an error saying:
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
Yanic\EventBundle\Entity\Participant. You can avoid this error by
setting the "data_class" option to
"Yanic\EventBundle\Entity\Participant" or by adding a view transformer
that transforms an instance of class
Yanic\EventBundle\Entity\Participant to scalar, array or an instance
of \ArrayAccess.
If I delete the said line Twig complains:
Method "0" for object "Symfony\Component\Form\FormView" does not exist in
/Applications/MAMP/htdocs/symfony-standard-2.1/src/Yanic/EventBundle/Resources/views/Booking/index.html.twig
at line 27
EDIT: The addParticipant is the default methos generated by the doctrine:generate:entities command
/**
* Add Participants
*
* #param \Yanic\EventBundle\Entity\Participant $participants
* #return Booking
*/
public function addParticipant(\Yanic\EventBundle\Entity\Participant $participants)
{
$this->Participants[] = $participants;
return $this;
}
I'm sure that I'm doing something wrong, but can't find the clue :-(
I guess you are a bit lost on Symfony2 form collection, though I think you already read http://symfony.com/doc/current/cookbook/form/form_collections.html.
Here I will just emphasize the doc, help other SO readers, and exercise myself a bit on answering question.. :)
First, you must have at least two entities. In your case, Booking and Participant. In Booking entity, add the following. Because you use Doctrine, Participant must be wrapped in ArrayCollection.
use Doctrine\Common\Collections\ArrayCollection;
class Booking() {
// ...
protected $participants;
public function __construct()
{
$this->participants = new ArrayCollection();
}
public function getParticipants()
{
return $this->participants;
}
public function setParticipants(ArrayCollection $participants)
{
$this->participants = $participants;
}
}
Second, your Participant entity could be anything. Just for example:
class Participant
{
private $name;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
Third, your BookingType should contain collection of ParticipantType, something like this:
// ...
$builder->add('participants', 'collection', array('type' => new ParticipantType()));
Fourth, the ParticipantType is straightforward. According to my example before:
// ...
$builder->add('name', 'text', array('required' => true));
Last, in BookingController, add the necessary amount of Participant to create a collection.
// ...
$entity = new Booking();
$participant1 = new Participant();
$participant1->name = 'participant1';
$entity->getParticipants()->add($participant1); // add entry to ArrayCollection
$participant2 = new Participant();
$participant2->name = 'participant2';
$entity->getParticipants()->add($participant2); // add entry to ArrayCollection
think you have to add here the type:
->add('Participants', 'collection', array('type' => 'YourParticipantType'));
Could you also paste in here the declaration of your addParticipant function from the model? Seems that there's something fishy too.