Symfony2 form collection: How to remove entity from a collection? - php

I try to remove entities from a collection but it doesn't work.
I think I have a mistake somewhere, but I don't know where.
Here the code from my updateAction:
$em = $this->getDoctrine()->getEntityManager();
$entity = new Person();
if (!$entity) {
throw $this->createNotFoundException('Unable to find Person entity.');
}
$editForm = $this->createForm(new PersonType(), $entity);
$deleteForm = $this->createDeleteForm($id);
$request = $this->getRequest();
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$entity = $editForm->getData();
$em->persist($entity);
foreach($entity->getAddresses() as $address)
{
$em->persist($address);
}
$em->flush();
return $this->redirect($this->generateUrl('person_show', array('id' => $id)));
}
return $this->render('AppPersonBundle:Person:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
);
Note that to remove my entity I remove the div from the html.
I mean I remove <div id="myapp_personbundle_persontype_address_4"> for example.
Is it the right way?

Thanks to this answer, I found a better solution. You can use Doctrine's orphan removal feature:
class Gallery
{
//...
/**
* #ORM\OneToMany(targetEntity="Photo", mappedBy="gallery", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $photos;
//...
public function removePhotos($photo)
{
$this->photos->remove($photo);
$photo->setGallery(null);
}
}

For now, i do :
[...]
$editForm = $this->createForm(new PersonType(), $entity);
$deleteForm = $this->createDeleteForm($id);
$previousCollections = array(
'addresses' => $entity->getAddresses(),
);
$request = $this->getRequest();
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$entity = $editForm->getData();
$this->deleteCollections($em, $previousCollections, $entity);
$em->persist($entity);
foreach($entity->getAddresses() as $address)
{
$em->persist($address);
}
$em->flush();
return $this->redirect($this->generateUrl('person_show', array('id' => $id)));
}
[...]
}
private function deleteCollections($em, $init, $final)
{
if (empty($init)) {
return;
}
if (!$final->getAddresses() instanceof \Doctrine\ORM\PersistentCollection) {
foreach ($init['addresses'] as $addr) {
$em->remove($addr);
}
}
}
And I hope a solution will be found one day with https://github.com/symfony/symfony/issues/1540, but it slow to be found.

Form collection in symfony2 is quite straightforward, it does almost all the work for you. Basically you just need add a collection type and set allow_add, allow_delete flags and add a small JavaScript code. Have a look at the cookbook example

I'm using this solution...
In the controler:
$em = $this->getDoctrine()->getManager();
$enity->removeElements($em);
//Add all your elements again in order to update entity collection.
$entity->addElement($element) ...
....
$em->persist($entity);
$em->flush();
In the entity:
public function removeElements($em)
{
$elements = $this->elements;
foreach ($elements as $element) {
$this->elements->removeElement($element);
$em->remove($element);
$em->persist($this);
}
}
For me it works and it shows less code and I don't have to use the orphanRemoval feature.

Related

I can't redirect to Route in Symfony

everyone.
I am building symphony 5 project, but I have an issue.
The URL is not redirecting after I create or edit an element.
But in other functions, the redirectToRoute() function works.
Strange problem...
These are my controller code.
Please check it and let me know what I missed.
Thanks.
/**
* #Route("/list", name="animals")
*/
public function animals(): Response
{
if (!$this->getUser()) {
$this->redirectToRoute('app_login');
}
$animals = $this->getUser()->getAnimals();
return $this->render('animals/overview.html.twig', [
'animals' => $animals,
]);
}
And
/**
* #Route("/create", name="animal_create")
*/
public function create(Request $request, EntityManagerInterface $entityManager): Response
{
$animal = new Animal();
$customer = $this->em()->getRepository(Customer::class)->findOneBy(array('id' => $request->request->get('customer')));
$animal->setCustomer($customer);
$form = $this->createForm(AnimalType::class, $animal, [
'csrf_protection' => false,
]);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return new Response('failed');
}
$customer->addAnimal($animal);
$entityManager->persist($animal);
$entityManager->flush();
return $this->redirectToRoute('animals');
}
/**
* #Route("/{id}/update", name="animal_update")
*/
public function update(Request $request, Animal $animal, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(AnimalType::class, $animal, [
'csrf_protection' => false,
]);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return new Response('failed');
}
$entityManager->persist($animal);
$entityManager->flush();
return $this->redirectToRoute('animals');
}
You must redirect to the index route only if the form is submitted and validated, otherwise the form must be displayed.
The symfony documentation explains this perfectly:
public function create(Request $request, EntityManagerInterface $entityManager): Response
{
$animal = new Animal();
$customer = $this->em()->getRepository(Customer::class)->findOneBy(array('id' => $request->request->get('customer')));
$animal->setCustomer($customer);
$form = $this->createForm(AnimalType::class, $animal);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$animal = $form->getData();
$customer->addAnimal($animal);
$entityManager->persist($animal);
$entityManager->flush();
return $this->redirectToRoute('animals');
}
// Here it's the name of your twig template for animal creation
return $this->renderForm('animal/create.html.twig', [
'form' => $form,
]);
}
Do the same thing for the update action.

Dynamically changing properties of row in Symfony 4.4 [duplicate]

This question already has answers here:
Symfony2 Form Entity Update
(3 answers)
Closed 2 years ago.
I'm making a REST API with Symfony 4.4. The API largely revolves around putting data into a database, using Doctrine. I have figured out how to add rows to the database, but now I'm stuck on changing data. I know how I can take a row from the database and that, in theory, I can change fields by calling the setter of a property, but right now, I seem to be getting an array instead of the desired entity and, seemingly more difficult, I want to be able to dynamically change the properties of the existing row, so that I don't have to include every field of the object of the row I'm changing and call every setter.
Here is my code:
// PersonController.php
/**
* #IsGranted("ROLE_USER")
* #Rest\Post("/addperson")
* #param Request $request
* #return Response
*/
public function addOrUpdatePerson(Request $request)
{
$data = json_decode($request->getContent(), true);
$em = $this->getDoctrine()->getManager();
$person = new Person();
$form = $this->createForm(PersonType::class, $person);
$form->submit($data);
if (!$form->isSubmitted() || !$form->isValid())
{
return $this->handleView($this->view($form->getErrors()));
}
if (isset($data['id']))
{
// This person exists, change the row
// What to do?
}
// This person is new, insert a new row
$em->persist($person);
$em->flush();
return $this->handleView($this->view(['status' => 'ok'], Response::HTTP_CREATED));
}
// PersonType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', IntegerType::class, ['mapped' => false])
->add('inits')
->add('firstname')
->add('lastname')
->add('email')
->add('dateofbirth', DateTimeType::class, [
'widget' => 'single_text',
// this is actually the default format for single_text
'format' => 'yyyy-MM-dd',
])
// Some other stuff
->add('save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Person::class,
'csrf_protection' => false
));
}
I doubt the Person entity is relevant here, but if it is, please let me know and I'll include it ASAP!
As a response to the suggestion of the other question from Symfony 2; it doesn't seem to fix my problem (entirely). As a result of this question, I have changed my function to this (which doesn't work, but doesn't throw any errors):
public function addOrUpdatePerson(Request $request)
{
$data = json_decode($request->getContent(), true);
$em = $this->getDoctrine()->getManager();
if (isset($data['id'])) {
// This person exists
$existing = $em->getRepository(Person::class)->find(['id' => $data['id']]);
$this->getDoctrine()->getManager()->flush();
$form = $this->createForm(PersonType::class, $existing);
$form->handleRequest($request);
// this doesn't seem to do anything
// $em->persist($existing);
$em->flush();
return $this->handleView($this->view($existing));
}
}
I think I'm still missing some info, like what to do at // perform some action, such as save the object to the database. I also notice a lot has changed since Symfony 2, and as a result it is not obvious to me what I should do.
After '$person = new Person()' juste add :
If (isset($data['id']) && 0 < $data['id']) {
$person=$em->getRepository(Person::class)->find($data['id']);
}
If (!$person) {
Throw new \Exception('Person not found');
}
1.) You don't have to use json_decode directly. You can use the following code instead:
// Person controller
/**
* #Route("/person", name="api.person.add", methods={"POST"})
* #Security("is_granted('ROLE_USER')")
*/
public function addPerson(Request $request)
{
$person = new Person();
$form = $this->createForm(PersonType::class, $person);
$form->submit($request->request->all());
if (!$form->isSubmitted() || !$form->isValid()) {
throw new \Exception((string) $form->getErrors(true));
}
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
...
}
2.) When you're updating entity you need to load it first and skip the $em->persist($entity); part. In this case, we provide the ID of the entity via the path variable (there are various ways to provide it but this one is quite common). NOTE: I've set $id parameter as mixed because it can be integer or string if you're using UUID type of IDs.
// Person controller
/**
* #Route("/person/{id}", name=api.person.patch", methods={"PATCH"})
* #Security("is_granted('ROLE_USER')")
*/
public function patchPerson(Request $request, mixed $id)
{
// Load person
$personRepository = $this->getDoctrine()->getRepository(Person::class);
$person = $personRepository->find($id);
if (!$person) { throw new \Exception('Entity not found'); }
$form = $this->createForm(PersonType::class, $person);
$form->submit($request->request->all());
if (!$form->isSubmitted() || !$form->isValid()) {
throw new \Exception((string) $form->getErrors(true));
}
$em = $this->getDoctrine()->getManager();
$em->flush();
...
}
3.) In general usage, we don't set the ID property via posted data (unless it is required). We rather use generated value instead. When you insert new entity you gen use its ID to address it for modifications. Sample:
<?php
namespace App\Entity;
use Ramsey\Uuid\Uuid;
use Doctrine\ORM\Mapping as ORM;
class Person
{
/**
* #var Uuid
*
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
* #Groups({"public"})
*/
protected $id;
// Other entity properties ...
public function getId(): ?string
{
return $this->id;
}
public function setId(string $id): self
{
$this->id = $id;
return $this;
}
// Setters and getters for other entity properties ...
}
4.) Entity class in FormType (PersonType.php) is very relevant. After form submission and validation you access properties of the entity itself within the controller - not the decoded payload data from the request directly. Symfony's form system will make sure that the input data is valid and matches the requirements and constraints set in the entity model or form type specification.
// Person controller
/**
* #Route("/person", name="api.person.add", methods={"POST"})
* #Security("is_granted('ROLE_USER')")
*/
public function addPerson(Request $request)
{
$person = new Person();
$form = $this->createForm(PersonType::class, $person);
$form->submit($request->request->all());
if (!$form->isSubmitted() || !$form->isValid()) {
throw new \Exception((string) $form->getErrors(true));
}
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
$id = $person->getId();
$firstName = $person->getFirstname();
$lastName = $person->getLastname();
// etc
...
}
5.) If you want to use the same method/endpoint for adding and updating entity you can do something like #lasouze mentioned.
// Person controller
/**
* #Route("/person", name=api.person.add_or_update", methods={"POST", "PATCH"})
* #Security("is_granted('ROLE_USER')")
*/
public function patchPerson(Request $request)
{
$id = $request->request->get('id', null);
if (!$id) {
$person = new Person();
} else {
// Load person
$personRepository = $this->getDoctrine()->getRepository(Person::class);
$person = $personRepository->find($id);
if (!$person) { throw new \Exception('Entity not found'); }
}
$form = $this->createForm(PersonType::class, $person);
$form->submit($request->request->all());
if (!$form->isSubmitted() || !$form->isValid()) {
throw new \Exception((string) $form->getErrors(true));
}
$em = $this->getDoctrine()->getManager();
$em->flush();
...
}
PS: $form->submit($request->request->all()); will not work for file uploads because $request->request->all() does not contain parameters provided by $_FILES. In my case I ended up merging data like $form->submit(array_merge($request->request->all(), $request->files->all())); but this is probably not the best solution. I'll update my answer if I'll figure out anything better.

Symfony edit form dosen't work

i want to edit my data with symfony form and i have a problem
probably with my controller. I have some like this :
public function detailAction($id,Request $request)
{
$order = $this->getDoctrine()->getRepository(OrderMain::class)->find($id);
if (!$order) {
throw $this->notFoundException();
}
$form = $this->createForm(OrderMainType::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// do not enter here
$orderEdit = $form-getData();
$em = $this->getDoctrine()->getManager();
$em->persist($orderEdit);
$em->flush();
}
return $this->render('ModiModiAdminBundle:Order:detail.html.twig',array(
'form' => $form->createView(),
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
/.../
->add('edit', SubmitType::class, array(
'attr' =>array('class'=>'edit'),
));
}
All show corectly but when i click a button my page is reload ( dosen't save changes ). Thanks for help.
It is an issue with your controller method. Below should work for you.
public function detailAction($id,Request $request)
{
$order = $this->getDoctrine()->getRepository(OrderMain::class)->find($id);
if (!$order) {
throw $this->notFoundException();
}
$form = $this->createForm(OrderMainType::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//do not enter here
$em = $this->getDoctrine()->getManager();
$em->flush();
}
return $this->render('ModiModiAdminBundle:Order:detail.html.twig',array(
'form' => $form->createView(),
));
}
You can remove the line of $orderEdit = $form-getData();. When your form is submitted, the entity should be updated based on the submitted data. Since this is already a managed entity, you can also remove $em->persist($orderEdit);
public function edit(Request $request)
{
$id = $request->get('id');
$category = $this->getDoctrine()->getRepository(Category::class)->find($id);
if (!$category) {
throw $this->notFoundException();
}
$form = $this->createForm(CategoryType::class,$category);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em = $this->getDoctrine()->getManager();
$categoryData = $form->getData();
$em->persist($categoryData);
$em->flush();
}
return $this->render('admin/category/edit.html.twig',array(
'form' => $form->createView(),
));
}
you are missing => handle request from code
$form->handleRequest($request);

Update Entity Form

when I try to update one of my entites, I get this exception:
UndefinedMethodException: Attempted to call method "bindRequest" on class "Symfony\Component\Form\Form" in /Applications/MAMP/htdocs/Seotool/src/Seotool/MainBundle/Controller/TaskController.php line 64.
edit Action:
/**
#Route(
* path = "/tasks/edit/{id}",
* name = "edit_task"
* )
* #Template()
*/
public function edit_taskAction($id, Request $request)
{
$request = $this->get('request');
if (is_null($id)) {
$postData = $request->get('task');
$id = $postData['id'];
}
$em = $this->getDoctrine()->getManager();
$task = $em->getRepository('SeotoolMainBundle:Task')->find($id);
$form = $this->createForm(new TaskType(), $task);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// perform some action, such as save the object to the database
$em->flush();
return $this->redirect($this->generateUrl('taskmanager'));
}
}
return array('form' => $form->createView());
}
What's wrong with my code?
Because there is no method bindRequest. The exception is quite explicit. If you check the official API, I suppose you want to use handleRequest

symfony2 fatal error Cannot redeclare class

Ok, I've been at this for two hours now and I see some other people have had this error, but I can't seem to match their causes/resolutions with mine.
Fatal error: require() [function.require]: Cannot redeclare class companycontroller in /var/www/biztv_symfony/vendor/symfony/src/Symfony/Component/ClassLoader/DebugUniversalClassLoader.php on line 55
The terminal gives a better error message pointing me to the end clause of the actual class that it reports having trouble with (trying to redeclare).
If I remove or rename the file companyController.php it throws a Symfony2 error saying that the it went looking for the class but didn't find it where it was expected.
If I put the file back in its place, apache throws a php error saying that the class companyController can't be redeclared.
I only have it once?!
Here is the entire class... if anyone has patience to try and help me out...
<?php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use BizTV\BackendBundle\Entity\company;
use BizTV\BackendBundle\Form\companyType;
/**
* company controller
*
*/
class companyController extends Controller
{
/**
* Lists all company entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getEntityManager();
$entities = $em->getRepository('BizTVBackendBundle:company')->findAll();
return $this->render('BizTVBackendBundle:company:index.html.twig', array(
'entities' => $entities
));
}
/**
* Finds and displays a company entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('BizTVBackendBundle:company')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find company entity.');
}
$deleteForm = $this->createDeleteForm($id);
return $this->render('BizTVBackendBundle:company:show.html.twig', array(
'entity' => $entity,
'delete_form' => $deleteForm->createView(),
));
}
/**
* Displays a form to create a new company entity.
*
*/
public function newAction()
{
$entity = new company();
$form = $this->createForm(new companyType(), $entity);
return $this->render('BizTVBackendBundle:company:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
/**
* Creates a new company entity.
*
*/
public function createAction()
{
$entity = new company();
$request = $this->getRequest();
$form = $this->createForm(new companyType(), $entity);
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
/* Create adminuser for this company to go along with it */
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->createUser();
//make password (same as username)
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user); //get encoder for hashing pwd later
$tempPassword = $entity->getCompanyName(); //set password to equal company name
//Get company
$tempCompanyId = $entity->getId(); //get the id of the just-inserted company (so that we can retrieve that company object below for relating it to the user object later)
$tempCompany = $em->getRepository('BizTVBackendBundle:company')->find($tempCompanyId); //get the company object that this admin-user will belong to
$user->setUsername($entity->getCompanyName() . "/admin"); //set username to $company/admin
$user->setEmail('admin.'.$entity->getCompanyName().'#example.com'); //set email to non-functioning (#example)
$user->setPassword($encoder->encodePassword($tempPassword, $user->getSalt())); //set password with hash
$user->setCompany($tempCompany); //set company for this user
$user->setConfirmationToken(null); //we don't need email confirmation of account
$user->setEnabled(true); //without a confirmation token, we of course also need to flag the account as enabled manually
$user->addRole('ROLE_ADMIN');
$userManager->updateUser($user);
return $this->redirect($this->generateUrl('company_show', array('id' => $entity->getId())));
}
return $this->render('BizTVBackendBundle:company:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
/**
* Displays a form to edit an existing company entity.
*
*/
public function editAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('BizTVBackendBundle:company')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find company entity.');
}
$editForm = $this->createForm(new companyType(), $entity);
$deleteForm = $this->createDeleteForm($id);
return $this->render('BizTVBackendBundle:company:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
/**
* Edits an existing company entity.
*
*/
public function updateAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('BizTVBackendBundle:company')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find company entity.');
}
$editForm = $this->createForm(new companyType(), $entity);
$deleteForm = $this->createDeleteForm($id);
$request = $this->getRequest();
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('company_edit', array('id' => $id)));
}
return $this->render('BizTVBackendBundle:company:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
/**
* Deletes a company entity.
*
*/
public function deleteAction($id)
{
$form = $this->createDeleteForm($id);
$request = $this->getRequest();
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('BizTVBackendBundle:company')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find company entity.');
}
$em->remove($entity);
$em->flush();
}
return $this->redirect($this->generateUrl('company'));
}
private function createDeleteForm($id)
{
return $this->createFormBuilder(array('id' => $id))
->add('id', 'hidden')
->getForm()
;
}
}
So, turns out that was a clumpsy typo by moi there.
But for anyone else who runs into this error message in Symfony2:
Fatal error: require() [function.require]: Cannot redeclare class...
Here is a hint: check if you have accidentally deleted or typo:ed the namespace in the file that contains the definition of the class that php claims it is trying to re-define.
The php error message doesn't really give you a clue to look for that... =)
Personally, I juste removed cache manually and it worked
rm -rf app/cache/*
Clearing cache was not fixing my problem.
redeclare class - Likely there is tow classes with the same name
Sometimes, if you got seduced by copy/paste, check your classnames, namespaces and for other "typos" that could have happened. (copy/paste is the devil of programming :/)
Similar to other answers, in my case I had renamed the class but not the containing file. Every class should be declared in a file with the same name. So check that, too.
In my case, it was a use statement under the namespace which used the same classname (but another path).
namespace Bsz\RecordTab;
use \Bsz\Config\Libraries; // I used this in constructor
class Libraries
{
...
}
Without the use directive, it Worked

Categories