One to many relation throw exception on save - symfony

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).

Related

ManyToMany new value must be an array or an instance of \Traversable, "NULL" given

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.

Symfony form submission with handleRequest issue

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.

Symfony Forms persist multiple EntityType

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.

Symfony2 Unidirectional ManyToMany Collection Form

I'm stuck :-)
Maybe I just have the wrong keywords in my research. But I don't get through. So I hope one of the crowd can help me!
I have a unidirectional ManyToMany Association. When I try to submit the form (and therefore persist), I get the error:
A new entity was found through the relationship 'TrainerInnenPool\AppBundle\Entity\Trainer#applicationFields' that was not configured to cascade persist operations for entity: TrainerInnenPool\AppBundle\Entity\ApplicationField#0000000022ef36c600000000087bcbc3.
When I do "cascade persist" a new Entity is created which actually already exists.
I have 2 Entities:
Trainer
ApplicationField
The Trainer has a unidirectional ManyToMany association to the ApplicationField:
class Trainer {
public function __construct()
{
$this->applicationFields = new ArrayCollection();
}
[...]
/**
* #ORM\ManyToMany(targetEntity="ApplicationField")
*/
protected $applicationFields;
The ApplicationField has a self-referencing OneToMany association:
class ApplicationField {
public function __construct() {
$this->children = new ArrayCollection();
}
[...]
/**
* #ORM\OneToMany(targetEntity="ApplicationField", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="ApplicationField", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
I want to create a form where I can add a Trainer - ApplicationField association.
Therefore I have an ApplicationFieldCollectionType:
class ApplicationFieldCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
'allow_add' => true,
'label' => false
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\Trainer',
));
}
The embeded Type is as follows:
class ApplicationFieldType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('applicationFieldName', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'mapped' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent is NULL');
}
));
$builder->add('name', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent = 1');
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\ApplicationField',
));
}
Last missing part: The controller:
public function editApplicationField($id, Request $request)
{
$entityManager = $this->getDoctrine()->getEntityManager();
$trainer = $entityManager->getRepository('TrainerInnenPoolAppBundle:Trainer')->find($id);
$editForm = $this->createForm(new ApplicationFieldCollectionType(), $trainer);
if ($request->getMethod() == 'POST') {
$editForm->handleRequest($request);
$entityManager->flush();
}
When I fetch the ApplicationField entities from the Trainer and try to persist those,
$editForm->handleRequest($request);
$af = $trainer->getApplicationFields();
foreach ($af as $applicationField) {
$trainer->addApplicationField($applicationField);
$entityManager->persist($applicationField);
}
$entityManager->flush();
I'm not able to do so, since I get a "Duplicate Entry For Key PRIMARY" - Exception.
I think I terribly miss any obvious point. If someone could help me, gave me a hint or just mentioned to update my question with information, I would be so thankful.
Kind regards...
Your nested type setters are not called because of a missing property:
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
...
'by_reference' => false
...
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
When you plan to have collection fields that are addable/deletable("allow_add", "allow_delete"), you should always provide the "by_reference" => false option, in order to call the setters directly on the related entities and then build the association, rather than chain methods from the original entity.
Hope this helps!
Well you need the cascade={"persist"} annotation because you want to persist the whole entity with its association to ApplicationField. If you don't use cascade={"persist"}, you'd have to manually persist the entities.
The entities are already added to the trainer, so if you want to manually persist the entities you should remove the line
$trainer->addApplicationField($applicationField);
and only execute the persist.
This should work. Try it. But I think the effect will be the same as if you use cascade persist. So it's not the final solution I think, but the first step to understand the problem, why the manual persist didn't work before.

Form widget not bound to a Doctrine2 association?

I have a form for creating a new BroadcastMessage entity and i need to display a widget of type <select multiple="multiple"> bound to excludedUsers property, not directly related to a Doctrine2 an association.
Inside my BroadcastMessageType class (inherits from AbstractType):
$builder->add('excludedUsers, 'entity', array(
'class' => 'Acme\MyBundle\Enrity\User',
'property' => 'username',
'multiple' => true
));
This of course works for creating a new BroadcastMessage; but on editAction i need a complex query to get excluded users. I need to compute excluded users looking for a record in a cross-reference table named broadcast_message_reference.
My question is fairly simple: where to add this "complex query" in order to get excludedUsers property correctly bound to the <select multiple="multiple"> widget? Inside my getExludedUsers method? If yes, how i'm supposed to access the entity repository for that query?
class BroadcastMessage
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private excludedUsers;
public function __costrunct()
{
$this->excludedUsers = new ArrayCollection();
}
public function addExcludedUser(Acme\MyBundle\Enrity\User $user)
{
$this->excludedUsers[] = $user;
return $this;
}
public function getExcludedUsers() { return $this->excludedUsers; }
}
Actually, I think, you need query_builder option within your form item definition:
$builder->add('excludedUsers', 'entity', array(
'class' => 'Acme\MyBundle\Enrity\User',
'property' => 'username',
'multiple' => true,
'query_builder' => function(EntityManager $em){
// you have an instance of EntityManager so you may build
// arbitrary QueryBuilder. Just remember to return it
// for example:
$qb = $em->createQueryBuilder()
->from('Acme\MyBundle\Enrity\User u')
->where('u.excluded = true');
return $qb;
},
'property' => 'username'
));
This is just rough example, but you can find more about entity form type here.
I think your problem can be solved by Form events. Create a service for EventSubscriber class, inject EntityManager and then subscribe for FormEvents::SET_DATA event.

Resources