Symfony 2 Persist multiple entities in one form - symfony

I work on a symfony 2 app and I need some help to resolve a case I never had before.
My app has only one page with multiple blocs. Let's consider animals for a simple example. On my page, I have a first bloc where I show N dogs with all their caracteristics, then a second bloc where I show rabbits, then a third bloc with cats ect.
I also have an admin page where the user may modify the displayed data on the main page. The problem I have is that I must use only one page to admin all the blocs. That means that I have N dog, M rabbits, P cats entities displayed on my admin page and when I submit my form, Symfony must delete, update or insert each entity in database.
To do so, I created an entity and a formType for each animal, and a mapped superclass called Website with arraycollections for each entity.
Website Entity :
/**
* Website
* #ORM\MappedSuperclass()
*/
class Website
{
/**
* #var ArrayCollection
*/
private $dogs;
/**
* #var ArrayCollection
*/
private $rabbits;
/**
* #var ArrayCollection
*/
private $cats;
...
}
Website Type :
class WebsiteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('dogs', 'collection', array(
'type' => new DogType(),
'allow_add' => true,
'allow_delete' => true
))
->add('rabbits', 'collection', array(
'type' => new RabbitType(),
'allow_add' => true,
'allow_delete' => true
))
->add('cats', 'collection', array(
'type' => new CatType(),
'allow_add' => true,
'allow_delete' => true
))
->add('save', 'submit')
;
}
}
In my controller :
$manager = $this->getDoctrine()->getManager();
$dogs = $manager->getRepository('MyBundle:Dog')->findAll();
$rabbits = $manager->getRepository('MyBundle:Rabbit')->findAll();
$cats = $manager->getRepository('MyBundle:Cat')->findAll();
$website = new Website();
$website->setDogs($dogs);
$website->setRabbits($rabbits);
$website->setCats($cats);
$form = $this->createForm(new WebsiteType(), $website);
if ($form->handleRequest($request)->isValid()) {
$manager->persist($website);
$manager->flush();
}
return $this->render('MyBundle:Default:admin.html.twig', array(
'form' => $form->createView(),
'website' => $website
));
When I submit the form, I have this error :
The given entity of type 'MyBundle\Entity\Website'
(MyBundle\Entity\Website#0000000040d8aeb900007fd77a072110) has no
identity/no id values set. It cannot be added to the identity map.
What should I do ?

Related

symfony3 - Showing 'This value is not valid' for choice type of dropdown that options adding via ajax

My scenario is,
I need to create a entity(module) with display order and the module is under a topic entity and association made correctly. On form load the topic dropdown and display order dropdown will be blank along the module name. When selecting topic the display order will fill with options via ajax/js. Display order will be 1 to a number that will be the total modules under the specific topic+1 . The upcoming display order will be selected automatically. And that's working perfectly. But my issue is about the display order validation after submit. Its saying 'This value is not valid'. I understands this is due to not giving 'choices' as array in form type, but this case i cant give as static in form type. Please help anyone knows a solution.
class ModuleType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('topic', EntityType::class, [
'class' => 'AppBundle:Topic',
'choice_label' => 'name',
'placeholder' => 'Choose a Topic'
])
->add('name')
->add('description', TextareaType::class)
->add('displayOrder', ChoiceType::class)
->add('save', SubmitType::class, [
'attr' => ['class' => 'form-control button btn-sm nomargin']
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Module'
));
}
}
Try to use the Event Listener.
In your case for exemple:
// TopicType.php
private function addEventListener(FormBuilderInterface $builder)
{
// this function permit to valid values of topics
$annonymFunction = function(FormInterface $form, $diplayOrder) {
$entities = $this->container->get('doctrine.orm.default_entity_manager')
->getRepository('YourBundle:Topic')
->findAll();
if ($entities) {
$topics = array();
foreach($topics as $topic) {
$topics[$topic->getName()] = $topic->getName();
}
}
else $topics = null;
$form->add('topic', EntityType::class, array(
'attr' => array('class' => 'topic'),
'choices' => $topics));
};
$builder
->get('displayOrder')
->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($annonymFunction) {
$annonymFunction($event->getForm()->getParent(), $event->getForm()->getData());
});
}
Hope to help.

unable to transform value for property path "tagname". Expected a Doctrine\Common\Collections\Collection object

I am working with two ManyToMany related entities, namely category and tag.
The entity Tag(relevant details):
/**
*
* #var string
*
* #ORM\Column(name="tagname", type="string")
*/
protected $tagname;
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="tags")
*/
protected $categories;
The entity Category(relevant details):
/**
*
* #var string
*
* #ORM\Column(name="CategoryName", type="string",length=200)
*/
protected $categoryname;
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="categories")
*/
protected $tags;
I have a form with a select-input(CategoryType) and a multiple select-input(TagType) fields. Both the fields are EntityType fields. The TagType is embedded inside the CatgoryType.
For this I am not able to utilise the cascade=persist functionality and am adding the submitted tags manually inside my controller. On submission the form data gets persisted in the database without any issues.
The problem is, after submission, when I fetch the submitted category(and the associated tags) in my controller, and pass it to the form, I get this error - Unable to transform value for property path "tagname": Expected a Doctrine\Common\Collections\Collection object.
The var_dump result of the fetched category object(var_dump($category->getTags()->getValues());) gives me an array of the associated Tag objects, with the property protected 'tagname' => string 'tag1'.
From what I understand Interface Collection is quite similar to a php array and My guess is that the tagname field expects all the tagnames in an ArrayCollection or Collection object format. I am not sure whether what is the specific difference.
However I am still clueless how do I pass the already persisted category object in my form.
Here are the categoryname and tags field in the CategoryType:
$builder->add('categoryname', EntityType::class, array(
'class' => 'AppBundle:Category',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.id', 'ASC');
},
'choice_label' => 'categoryname',
'expanded' => false,
'multiple' => false,
'label' => 'Choose Category',
));
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
));
Here is the embedded tagname field in the TagType:
$builder->add('tagname', EntityType::class, array(
'class' => 'AppBundle:Tag',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('t')
->orderBy('t.id', 'ASC');
},
'choice_label' => 'tagname',
'expanded' => false,
'multiple' => true,
'label' => 'Choose Tags',
));
Any ideas?
Try to rid off 'multiple' => true in embedded form. This worked for me.
I've had a similar problem where my EntityType form element gave me this error message:
Unable to reverse value for property path 'my_property' Expected an array.
Setting multiple to false would make the implementation useless, so a better option for me was to set the empty_data to an empty array:
"empty_data" => [],
In your case you might be able to solve the problem by setting the empty_data to a Collection, something like this will probably work:
"empty_data" => new ArrayCollection,
Here is how you do it (from source: https://www.youtube.com/watch?v=NNCpj4otnrc):
***And here is a text version of this image #1, the reason I did not add text because it was hard to read when the format is not proper!
<?php
namespace App\Form;
use App\Entity\Post;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'attr' => [
'placeholder' => 'Etner the title here',
'class' => 'custom_class'
]
])
->add('description', TextareaType::class, [
'attr' => [
'placeholder' => 'Enter teh description here',
]
])
->add('category', EntityType::class, [
'class' => 'App\Entity\Category'
])
->add('save', SubmitType::class, [
'attr' => [
'class' => 'btn btn-success'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Post::class,
]);
}
}
***And here is a text version of this image #2, the reason I did not add text because it was hard to read when the format is not proper!
class FormController extends AbstractController
{
/**
* #Route("/form", name="form")
* #param Request $request
* #return Response
*/
public function index(Request $request)
{
$post = new Post(); // exit('this');
/*$post->setTitle('welcome');
$post->setDescription('description here');*/
$form = $this->createForm(PostType::class, $post, [
'action' => $this->generateUrl('form'),
'method' => 'POST'
]);
// handle the request
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// var_dump($post); die();
// save to database
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
}
return $this->render('form/index.html.twig', [
'postaform' => $form->createView()
]);
}
My assumption was wrong, I need to either make tagname field an array or create a ManyToOne or ManyToMany relationship with any other entity so it can be an arraycollection. Only then it is possible to use tagname as a multiple select field.
/**
*
* #var array
*
* #ORM\Column(name="tagname", type="array")
*/
protected $tagname;
or
/**
* #ORM\ManyToMany(targetEntity="SOME_ENTITY", mappedBy="SOME_PROPERTY")
*/
protected $tagname;
or
/**
* #ORM\ManyToOne(targetEntity="SOME_ENTITY", mappedBy="SOME_PROPERTY")
*/
protected $tagname;
The exception thrown is pretty explicit, and the problem is probably here:
$builder->add('tagname', EntityType::class, array()
According to your Tag entity, Tag::$tagname is not an entity collection, it's a string. You should add the property with
$builder->add('tagname', TextType::class, array()
shouldn't you ?

Symfony2 another Entity in Formbuilder

i got a problem / understanding problem.
I have 3 db tables
user, usergroup and user2usergroup
the foreign fields:
user.id => user2usergroup.user_id
user2usergroup.group_id => usergroup.id
So now i want to generate and edit my user object and get the relationship to my formbuilder.
I tried some ideas and researched for help.
Not this is the acutal state:
controller:
/**
* creates user form
* #param users $entity
* #param string $sUrl
* #return Form
*/
public function createUserForm(users $entity, $sUrl){
$form = $this->createForm(new userType(), $entity, array(
'action' => $this->generateUrl($sUrl),
'method' => 'POST',
'groups' => $this->getUserGroups()
));
return $form;
}
formbuilder
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options){
$groups = $options['groups'];
$builder
->add('enabled','checkbox',array(
'required' => false))
->add('locked','checkbox',array(
'required' => false))
->add('username','text',array(
'required' => true))
->add('email','email',array(
'required' => true))
->add('password','password',array(
'required' => true))
->add('roles', 'choice', array(
'choices' => array('ROLE_ADMIN' => 'Admin', 'ROLE_USER' => 'Benutzer'),
'required' => true,
'multiple' => true
))
->add('groups','entity',array(
'class' => 'UniteUserBundle:usergroup',
'query_builder' => function(EntityRepository $er){
return $er->findAll();
},
))
->add('save','submit')
;
}
Maybe you can help me to understand
- how to get all groups to the form
- how to edit the user and see which groups he is in (edit view)
- how i can persist the groups i selected in the user form to the user2usergroup
Thanks a lot my friends =)
To show the groups where a user belongs to is not to difficult:
{% for usergroup in entity.getUserGroups() %}
<li>{{ usergroup.name }}</li>
{% endfor %}
To edit them in a USER form is more complicated since you are editing one user but this user can be in zero, one or multiple groups. That means that each group in the list could be edited and deleted and that you can add a new group.
For this situation you can use the Collection field type. In this article you can read step by step how to use it.

symfony2 form: how to save entity and how to add more then one entity to the same form?

I have this function in a entitytype class
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...other controls
->add('types', 'entity', array(
'class' => 'MyApplicationBundle:Type',
'property' => 'type',
'expanded' => false,
'multiple' => true))
->add('save', 'submit');
}
the archive entity has a type property, many to may relation
/**
* #ORM\ManyToMany(targetEntity="Type", mappedBy="archives")
**/
private $types;
the type entity has a archives property on the other side
/**
* #ORM\ManyToMany(targetEntity="Archive", inversedBy="types")
* #ORM\JoinTable(name="types_archives")
**/
private $archives;
the form is correctly displayed with a select multiple control but I'm only able to save in the archive table, not in the types_archives table. Any idea on how to fix?
also, can I add more then one entity to the same type?
thank you
If just one side of relations saved in database try to do following steps:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...other controls
->add('types', 'entity', array(
'class' => 'MyApplicationBundle:Type',
// This makes form call setter method on related entity
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'property' => 'type',
'expanded' => false,
'multiple' => true))
->add('save', 'submit');
}
in Archive entity:
public function addType(Type $type){
$this->types[] = $type;
$type->addArchive($this);
}
public function removeType(Type $type){
$this->types->removeElement($type);
$type->setArchive(null);
}
I hope this helps about first part of your question.
For the second part you can use collection type check out following link:
http://symfony.com/doc/current/reference/forms/types/collection.html
Here are some directions that I would give you.
1. To save the related entities, try reading about "cascade persist" if you are using doctrine for example.
2. To have multiple entities on the form, read about "class composition". A composite object which you will set as the form's "data class" will allow you contain multiple entity objects.

symfony2 Entity Object vs. integer crashes

I have defined a entity like :
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client_id;
.....
public function setClientId($clientId = null)
{
$this->client_id = $clientId;
return $this;
}
There are two controllers with which I can create a new db entry. the first one is "admin-only" where the admin can create a db entry with a client id of his choice:
->add('client_id', 'entity', array(
'data_class' => null,
'attr' => array(
'class' => 'selectstyle'),
'class' => 'PrUserBundle:Client',
'property' => 'name',
'required' => true,
'label' => 'staff.location',
'empty_value' => 'admin.customer_name',
'empty_data' => null
)
)
......
// Handling the form
$em = $this->getDoctrine()->getManager();
$saniType->setName($form->get('name')->getData());
$saniType->setClientId($form->get('client_id')->getData());
$saniType->setCreated(new \DateTime(date('Y-m-d H:m:s')));
$saniType->setCreatedBy($user->getUsername());
$em->persist($saniType);
$em->flush();
The second one is for the client itself, where he's not able to set a different client id. Therefor I just removed the form field "client_id" and replace it by the users->getClientId():
$saniType->setSpecialClientId($form->get('client_id')->getData());
When I add an entry as admin, it work fine. If I try to add one as "client", it crashes with following error message
Warning: spl_object_hash() expects parameter 1 to be object, integer given in /var/www/symfony/webprojekt/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1389
I'm a newby in symfony, so I haven't got the strength to figure out what happens. I only knew that in the first case, admin will submit a object (entity). In the second (client) case, I set an integer as I get one by
$user = $this->container->get('security.context')->getToken()->getUser();
Is there a solution for it so that I can handle entity object AND given integers like it comes when the client add an entry?
You should change you relation defnition to:
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
.....
public function setClient($client = null)
{
$this->client = $client;
return $this;
}
Then in form:
->add('client', 'entity', array(
'data_class' => null,
'attr' => array('class' => 'selectstyle'),
'class' => 'PrUserBundle:Client',
'property' => 'name',
'required' => true,
'label' => 'staff.location',
'empty_value' => 'admin.customer_name',
'empty_data' => null
)
)
Handling the form:
form = $this->createForm(new SaniType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('some_success'));
}
More about handling form: http://symfony.com/doc/current/book/forms.html#handling-form-submissions
Also worth nothing:
for auto update properties like createdBy / updatedBy i would recommend you to use Doctrine Extension: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md
You don't have to know the client id. You have to set the Client himself.
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
.....
public function setClient(\Pr\UserBundle\Entity\Client $client = null)
{
$this->client = $client;
return $this;
}
I found a Solution:
Symfony2: spl_object_hash() expects parameter 1 to be object, string given in Doctrine
It's a workaround but it's ok for the moment.

Resources