Array Collection, symfony: add a relation [closed] - php

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
I'm new of Symfony and php, and I'm trying to understand, without outcome, the array collection.
Now I have two entity, Mission and User, in relation ManytoMany. I have a form to create new Missions and a form to create new User.
Now I have to create a "modifyMissionAction" that allows me to set the Users for that missions, but I didn't understand how to do it.
I read the documentation here but it doesn't help. How could I do?
Thank you
This is my User Entity is:
abstract class User extends BaseUser
{
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Acme\ManagementBundle\Entity\Mission", inversedBy="users", orphanRemoval=true)
* #ORM\JoinTable(name="user_mission")
*/
private $missions;
/**
* Add missions
*
* #param \Acme\ManagementBundle\Entity\Mission $missions
* #return User
*/
public function addMission(\Acme\ManagementBundle\Entity\Mission $missions)
{
$this->missions[] = $missions;
return $this;
}
//...
And my Mission Entity:
<?php
namespace Acme\ManagementBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* #ORM\Entity
*/
class Mission {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var integer
*/
protected $id;
/**
* #ORM\Column(type="string", length=60)
* #var String
*/
protected $name;
/**
* #ORM\Column(type="string", length=600)
* #var String
*/
protected $description;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Acme\ManagementBundle\Entity\User", mappedBy="missions", cascade={"all"}, orphanRemoval=true)
*/
private $users;
public function __construct(){
$this -> users = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Mission
*/
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 Mission
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Add users
*
* #param \Acme\ManagementBundle\Entity\User $users
* #return Mission
*/
public function addUser(\Acme\ManagementBundle\Entity\User $users)
{
$this->users[] = $users;
return $this;
}
/**
* Remove users
*
* #param \Acme\ManagementBundle\Entity\User $users
*/
public function removeUser(\Acme\ManagementBundle\Entity\User $users)
{
$this->users->removeElement($users);
}
/**
* Get users
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getUsers()
{
return $this->users;
}
public function __toString()
{
return $this->name;
}
}

first of all don't forget to add __constructor for both of classes and init ArrayCollection:
//src/WebHQ/NewBundle/Entity/Mission.php
//...
public function __construct()
{
$this->users = new ArrayCollection();
}
//...
I assume that you want to add controller action witch allows you to relate user object or objects to mission object. Please read embbed forms part of Symfony book
Then create your Action. Most important is to add form element, with is embedded form of users entity:
// src/WebHQ/NewBundle/Controller/MissionController.php
//...
public function newAction(Request $request)
{
$object = new \WebHQ\NewBundle\Entity\Mission();
$form = $this->createFormBuilder($object)
->add('name', 'text')
//...
// Users objects embed form
->add('users', 'user')
//...
->add('save', 'submit')
->getForm();
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($object);
$em->flush();
return $this->redirect($this->generateUrl('web_hq_new_mission_index'));
}
}
return $this->render('WebHQNewBundle:Mission:new.html.twig', array(
'form' => $form->createView(),
//...
));
}
public function editAction($id, Request $request)
{
$object = $this->getDoctrine()
->getRepository('WebHQNewBundle:Mission')
->find($id);
$form = $this->createFormBuilder($object)
->add('name', 'text')
//...
->add('users', 'user')
//...
->add('save', 'submit')
->add('delete', 'submit')
->getForm();
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$form->get('save')->isClicked() ? $em->persist($object) : $em->remove($object);
$em->flush();
return $this->redirect($this->generateUrl('web_hq_new_mission_index'));
}
}
return $this->render('WebHQNewBundle:Mission:edit.html.twig', array(
'form' => $form->createView(),
//...
));
}
//...
Check your routing. You should have route for edit action with {id} param. If name of param not fits you change it in route and in function definition:
// src/WebHQ/NewBundle/Resources/config/route.yml
//...
web_hq_new_mission_new:
pattern: /mission/new
defaults: { _controller: WebHQNewBundle:Mission:new }
web_hq_new_mission_edit:
pattern: /mission/{id}/edit
defaults: { _controller: WebHQNewBundle:Mission:edit }
//...
Then define Form Type for User objects:
// src/WebHQ/NewBundle/Form/Type/UserType.php
namespace WebHQ\NewBundle\Form\Type;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'class' => 'WebHQNewBundle:User',
'property' => 'name',
'empty_value' => 'Choose',
'required' => true,
'multiple' => true,
'query_builder' => function (Options $options) {
return function(EntityRepository $er) use ($options) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
};
},
));
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 'user';
}
}
And register a type in service.yml:
# src/WebHQ/NewBundle/Resources/config/services.yml
#...
services:
web_hq_new.form.type.user:
class: WebHQ\NewBundle\Form\Type\UserType
tags:
- { name: form.type, alias: user }
#...
Good Luck!

Related

Unable to include Collection of Entities inside API POST-call

The error in question:
Entity of type App\Entity\Nutt is missing an assigned ID for field 'squirrel'.
The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called.
If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.
I'm perfectly able to call the api POST to add a Squirrel entity into the database.
And using the id of this Squirrel, I can preform the POST call for the Nutt entity with the result being a correctly related record in the Nutt tabel.
What I can't seem to get working, is allowing the Squirrel api call to include the related collection of Nutts I want to insert in the same api call.
What am I doing wrong?
The JSON call:
{
"name": "Jake",
"nutts": [
{
"size": 10,
"color": "blue"
}
]
}
Entity Squirrel
/**
* #ORM\Entity
* #ORM\Table(name="squirrel")
*/
class Squirrel {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
* #Assert\NotBlank()
*
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Nutt", mappedBy="squirrel", cascade={"persist", "remove"})
*/
private $nutts;
public function __construct()
{
$this->nutts = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getNutts(): ?Collection
{
return $this->nutts;
}
public function setNutts(Collection $nutts)
{
foreach ($nutts as $nutt)
{
$this->nutts->add($nutt);
}
}
public function addNutt(Nutt $nutt): Squirrel
{
$this->nutts->add($nutt);
return $this;
}
}
Entity Squirrel Is updated.
setNutts has been changed to:
public function setNutts(Collection $nutts)
{
foreach ($nutts as $nutt)
{
$nutt->setSquirrel($this);
$this->nutts->add($nutt);
}
}
Entity Nutt
/**
* #ORM\Entity
* #ORM\Table(name="nutt")
*/
class Nutt {
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Squirrel", inversedBy="nutts")
* #ORM\Id
*/
private $squirrel;
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
private $size;
/**
* #ORM\Column(type="text")
* #Assert\NotBlank()
*/
private $color;
/**
* #return Squirrel|null
*/
public function getSquirrel(): ?Squirrel
{
return $this->squirrel;
}
/**
* #param Squirrel|null $squirrel
* #return $this
*/
public function setSquirrel(?Squirrel $squirrel): self
{
$this->squirrel = $squirrel;
return $this;
}
//getters and setters for the rest
}
Entity Nutt has been updated.
Property $squirrel has its id notation removed as it is a relation:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Squirrel", inversedBy="nutts")
*/
private $squirrel;
SquirrelController
/**
* Squirrel controller.
* #Route("/api", name="api_")
*/
class SquirrelController extends AbstractFOSRestController
{
/**
* Lists all Squirrels.
* #Rest\Get("/squirrels")
* #return Response
*/
public function getSquirrelAction()
{
$repository = $this->getDoctrine()->getRepository(Squirrel::class);
$squirrels = $repository->findall();
return $this->handleView($this->view($squirrels));
}
/**
* Create Squirrel.
* #Rest\Post("/squirrel")
*
* #return Response
*/
public function postSquirrelAction(Request $request)
{
$squirrel = new Squirrel();
$form = $this->createForm(SquirrelType::class, $squirrel);
$data = json_decode($request->getContent(), true);
$form->submit($data);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($squirrel);
$em->flush();
return $this->handleView($this->view(['status' => 'ok'], Response::HTTP_CREATED));
}
return $this->handleView($this->view($form->getErrors()));
}
}
And my current focus
The Squirrel Form
class SquirrelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add(
'nutts',
CollectionType::class, [
'entry_type' => NuttType::class,
'allow_add' => true,
'by_reference' => false
])
->add('save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Squirrel::class,
'csrf_protection' => false
));
}
}
There is a nutt form but it works fine.
Question has been solved by #mel in a comment
The Id annotation declares the column as a primary key, so it becomes mandatory. But it's not needed in this case, since squirrel is a relation.
The error itself also hints at the field being null when saving the entity, so setSquirrel is not being called.
You can remove the annotation from size as well.

How to get user object inside form Repository in Symfony 2.8

I am developing a web app on Symfony. I have 2 entities that is question about, "Line" and "Dosier". So, 1 dosier can have many lines.
Now I'm implementing the CRUD for the Line entity, so Line entity has a "dropdown" with all Dosiers from DataBase.
The problem is that in dropdown are all dosiers from any users, but I need in this dropdown to have just options that has user_id = currentUser_id.
So, my controller action :
/**
* #Route("/add-line", name="addLine")
*/
public function createLineAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
$line = new Line();
$form = $this->createForm(LineType::class, $line);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($line);
$em->flush();
return $this->redirectToRoute('homepage');
}
return $this->render('AppBundle:default:formLines.html.twig', array(
'form' => $form->createView(),
));
}//create dossier action
My LineType (form builder)
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class LineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')->add('dosier')
->add('dosier', EntityType::class, array(
'class' => 'AppBundle:Dosier',
'query_builder' => function($repo) {
return $repo->dosiersOfCurrentUser();
},
'choice_label' => 'name',
))
->add('save', SubmitType::class, array(
'label' => 'Save',
'attr'=> array('class'=>'btn btn-success submitButton')
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Line'
));
}
public function getBlockPrefix()
{
return 'appbundle_line';
}
}
My Line.php (entity)
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Line
*
* #ORM\Table(name="line")
* #ORM\Entity(repositoryClass="AppBundle\Repository\LineRepository")
*/
class Line
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Dosier", inversedBy="lines")
* #ORM\JoinColumn(name="dosier_id", referencedColumnName="id")
*/
private $dosier;
/**
* #ORM\OneToMany(targetEntity="Loan", mappedBy="line")
*/
private $loans;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
public function __construct()
{
$this->loans = new ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Line
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add loan
*
* #param \AppBundle\Entity\Loan $loan
*
* #return Line
*/
public function addLoan(\AppBundle\Entity\Loan $loan)
{
$this->loans[] = $loan;
return $this;
}
/**
* Remove loan
*
* #param \AppBundle\Entity\Loan $loan
*/
public function removeLoan(\AppBundle\Entity\Loan $loan)
{
$this->loans->removeElement($loan);
}
/**
* Get loans
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getLoans()
{
return $this->loans;
}
/**
* Set dosier
*
* #param \AppBundle\Entity\Dosier $dosier
*
* #return Line
*/
public function setDosier(\AppBundle\Entity\Dosier $dosier = null)
{
$this->dosier = $dosier;
return $this;
}
/**
* Get dosier
*
* #return \AppBundle\Entity\Dosier
*/
public function getDosier()
{
return $this->dosier;
}
}
and my repository : DosierRepository.php
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class DosierRepository extends \Doctrine\ORM\EntityRepository
{
public function dosiersOfCurrentUser() {
return $this->createQueryBuilder('dosier')
->where('dosier.userId = 1 ')
->orderBy('dosier.name', 'DESC');
}
}
How can I get the current user, or at least the current user id, to make a query like ... select from Dosier where dosier.user_id = $???
In controller where you are buiding your form you need pass the user object to your form type
$tokenStorage = $this->get('security.token_storage');
$form = $this->createForm(new LineType($tokenStorage), $line);
//... other stuff
Now in your form type recieve this tokenStorage object and retrieve user object and pass to your repo function
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
//.. other use statements
class LineType extends AbstractType
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$userId = $this->user->getId();
$builder
->add('name')->add('dosier')
->add('dosier', EntityType::class, array(
'class' => 'AppBundle:Dosier',
'query_builder' => function($repo) use($userId) {
return $repo->dosiersOfCurrentUser($userId);
},
'choice_label' => 'name',
))
->add('save', SubmitType::class, array(
'label' => 'Save',
'attr'=> array('class'=>'btn btn-success submitButton')
)
);
}
}
In repo apply your filter
class DosierRepository extends \Doctrine\ORM\EntityRepository
{
public function dosiersOfCurrentUser($userId) {
return $this->createQueryBuilder('dosier')
->where('dosier.userId = :userId ')
->setParameter('userId',$userId)
->orderBy('dosier.name', 'DESC');
}
}

Symfony 3: allow_delete in Forms not working

I have a problem with my form in Symfony 3. I have one-to-many-to-one join (Doctrine 2). It handles Orders (Order), Products (Product) and joining entity (OrderProduct), which holds amount of product in order.
I have a form for add and update order entries, which uses Collection of OrderProducts. It's all based on documentation (link).
In the form I have a button for add a product (from documentation, adds a <li> to DOM) and every added has a button for remove it (from documentation, removes <li> from DOM). This part is working - adding to and removing from DOM.
Adding products works (as at the new order, than when editting).
But my problem is with removing. Products which was succesfully deleted from form, are still appearing in $editForm->getData().
OrderProduct Form
namespace AppBundle\Form;
use AppBundle\Entity\ProductType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrderProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('amount')
->add('product')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'AppBundle\Entity\OrderProduct',
)
);
}
public function getName()
{
return 'app_bundle_order_product_type';
}
}
Order Form
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customer')
->add('date', null, array('widget' => 'single_text'))
->add('payment', null, array('widget' => 'single_text'))
->add('processed', null, array('widget' => 'single_text'))
->add(
'orderProducts',
CollectionType::class,
array(
'entry_type' => OrderProductType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype' => true,
'delete_empty' => true,
'entry_options' => array('data_class' => 'AppBundle\Entity\OrderProduct'),
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
}
public function getName()
{
return 'app_bundle_order_type';
}
}
Current action in OrderController
(Adding product works, removing not)
public function editAction (Request $request, $orderId) {
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('AppBundle:Order')->find($orderId);
if (!$order) {
throw $this->createNotFoundException('No order found for id '.$orderId);
}
$editForm = $this->createForm(OrderType::class, $order);
$editForm->add('submit', SubmitType::class);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$order = $editForm->getData();
//print '<pre>';
//var_dump($order->getOrderProducts());
//die();
$orderProducts = $order->getOrderProducts();
$em->persist($order);
foreach ($orderProducts as $oneOrderProduct) {
$oneOrderProduct->setOrder($order);
$em->persist($oneOrderProduct);
}
//print '<pre>';
//var_dump($order->getOrderProducts());
//die();
$em->flush();
return $this->redirectToRoute('one_order', array('orderId' => $order->getId()));
}
return $this->render(
'order/new.html.twig', array(
'form' => $editForm->createView(),
));
}
I know that I must remove removed OrderProducts from Order in editAction, but now I can't, because from the form is sent all OrderProducts.
Order Entity
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\OrderProduct;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
/**
* #ORM\Entity
* #ORM\Table(name="order_")
*/
class Order
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="orders")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
/**
* #ORM\Column(type="date")
*/
private $date;
/**
* #ORM\Column(type="date")
*/
private $payment;
/**
* #ORM\Column(type="date")
*/
private $processed;
public function __toString()
{
return strval($this->getId());
}
/**
* #ORM\OneToMany(targetEntity="OrderProduct", mappedBy="order")
*/
private $orderProducts;
public function __construct()
{
$this->orderProducts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set date
*
* #param \DateTime $date
*
* #return Order
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set payment
*
* #param \DateTime $payment
*
* #return Order
*/
public function setPayment($payment)
{
$this->payment = $payment;
return $this;
}
/**
* Get payment
*
* #return \DateTime
*/
public function getPayment()
{
return $this->payment;
}
/**
* Set processed
*
* #param \DateTime $processed
*
* #return Order
*/
public function setProcessed($processed)
{
$this->processed = $processed;
return $this;
}
/**
* Get processed
*
* #return \DateTime
*/
public function getProcessed()
{
return $this->processed;
}
/**
* Set customer
*
* #param \AppBundle\Entity\Customer $customer
*
* #return Order
*/
public function setCustomer(\AppBundle\Entity\Customer $customer = null)
{
$this->customer = $customer;
return $this;
}
/**
* Get customer
*
* #return \AppBundle\Entity\Customer
*/
public function getCustomer()
{
return $this->customer;
}
/**
* Add orderProduct
*
* #param \AppBundle\Entity\OrderProduct $orderProduct
*
* #return Order
*/
public function addOrderProduct(\AppBundle\Entity\OrderProduct $orderProduct)
{
$this->orderProducts[] = $orderProduct;
return $this;
}
/**
* Remove orderProduct
*
* #param \AppBundle\Entity\OrderProduct $orderProduct
*/
public function removeOrderProduct(\AppBundle\Entity\OrderProduct $orderProduct)
{
$this->orderProducts->removeElement($orderProduct);
}
/**
* Get orderProducts
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getOrderProducts()
{
return $this->orderProducts;
}
}
But in POST it's OK as you can see here, here I removed 2 of 4 products. Problem appears in form handling.
If by_reference is set to false the underlying Order entity MUST have a method in your case called [removeOrderProduct]. What also might be the problem is that you are not specifying data_class option inside the configureOptions method. In your case if Order entity is in 'AppBundle\Entity\Order', then the configureOptions method should contain:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Order',
));
}
And I would suggest you do the same in OrderProductType class also.
My guess would be that since you haven't specified the 'data_class' option in your OrderType class, the by_reference option in the 'orderProducts' field might not be able to figure out where to look for the [removeOrderProduct] method. So set that option and make sure you have that method inside your Order entity class.
IF this is not the problem then you should provide more information about your order entity and where exactly are you calling the getData method.
Update:
Looking at your code I can't identify the problem, that might be causing the entities to not get removed. But I have spotted a few oddities in your code:
In the controller, when handling form submittions it is not necessary to call the getData method: after you call handleRequest the $order object gets updated and holds the new information (since objects are passed by reference, the form can't apply the changes without changing the original $order as well). So there is no need to $order = $form->getData(), since you have already defined the $order variable before and it holds the reference to the same object to which the form mapped the posted values.
If that does not help, I suggest you add die; statements all over the place, just to make sure that at each step the right methods are called. For instance add die; to the removeOrderProduct method, to check if its hit. If it was hit, the problem is not going to be evident from the data you have provided to us, so it will require further debugging.
Also it might not be a problem now, but if you want to remove the products not present in the list after submittion, you have to call $order->getOrderProducts and add each of the items to a new collection that holds the previous orderProducts (before submittion) and compare that to the values after submittion to figure out which ones need removing.
I solved it last night :)
This is working controller action:
/**
* #Route("/{orderId}/edit", name="edit_order", requirements={"orderId": "\d+"})
* #param Request $request
* #param $orderId
*
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function editAction(Request $request, $orderId)
{
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('AppBundle:Order')->find($orderId);
if (!$order) {
return $this->redirectToRoute('new_order');
}
$originalOrderProducts = new ArrayCollection();
foreach ($order->getOrderProducts() as $orderProduct) {
$originalOrderProducts->add($orderProduct);
}
$editForm = $this->createForm(OrderType::class, $order);
$editForm->add('submit', SubmitType::class);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$orderProducts = $order->getOrderProducts();
foreach ($originalOrderProducts as $oneOriginalOrderProduct) {
if (false === $order->getOrderProducts()->contains($oneOriginalOrderProduct)) {
$order->removeOrderProduct($oneOriginalOrderProduct);
$em->remove($oneOriginalOrderProduct);
$em->flush();
}
}
foreach ($orderProducts as $oneOrderProduct) {
if ($oneOrderProduct->getAmount() == 0) {
$order->removeOrderProduct($oneOrderProduct);
$em->remove($oneOrderProduct);
} else {
if (!$originalOrderProducts->contains($oneOrderProduct)) {
$oneOrderProduct->setOrder($order);
}
$em->persist($oneOrderProduct);
}
$em->persist($order);
$em->flush();
}
return $this->redirectToRoute('order');
}
return $this->render('order/new.html.twig', array('form' => $editForm->createView()));
}

Persist form data Symfony 2.8

I am new to Symfony and I follow this documentation http://symfony.com/doc/current/cookbook/form/form_collections.html to build a basic embed collection form.
Follow the documentation. We have Tag and Task. Task has oneToMany relation with Tag and Tag has ManyToOne relation with Task.
I also use EntityType::class in TaskType with queryBuilder to pre-populate select list of existing Task entity in database on the form.
I have completed build the form and everything work fine. However, when i submitted form instead of using existing "Task ID",a new Task with new ID but with the same data is generated. This new task ID is assigned to new Tag entity. I would want use the existing Task ID for new Tags.
Below are my classes with the YAML file:
TaskController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Task;
use AppBundle\Entity\Tag;
use AppBundle\Form\Type\TaskType;
class TaskController extends Controller
{
public function newAction(Request $request)
{
$task = new Task();
// dummy code - this is here just so that the Task has some tags
// otherwise, this isn't an interesting example
$tag1 = new Tag();
$tag1->setName('tag1');
$tag1->addTask($task);
$task->getTags()->add($tag1);
$form = $this->createForm(TaskType::class, $task);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
}
return $this->render('AppBundle:Task:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Tag.php
namespace AppBundle\Entity;
/**
* Tag
*/
class Tag
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $name;
/**
* #var \AppBundle\Entity\Task
*/
private $task;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set task
*
* #param \AppBundle\Entity\Task $task
*
* #return Tag
*/
public function setTask(\AppBundle\Entity\Task $task = null)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* #return \AppBundle\Entity\Task
*/
public function getTask()
{
return $this->task;
}
public function addTask(Task $task)
{
$this->setTask($task);
}
}
TagType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Tag',
));
}
}
Tag.orm.yml
AppBundle\Entity\Tag:
type: entity
table: null
repositoryClass: AppBundle\Repository\TagRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: 255
manyToOne:
task:
targetEntity: Task
inversedBy: tags
joinColumn:
name: task_id
referencedColumnName: id
cascade: [ persist ]
lifecycleCallbacks: { }
Task.php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Task
*/
class Task
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $description;
/**
* #var ArrayCollection
*/
protected $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set description
*
* #param string $description
*
* #return Task
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Get tags
*
* #return ArrayCollection
*/
public function getTags()
{
return $this->tags;
}
public function addTag(Tag $tag)
{
$tag->addTask($this);
$this->tags->add($tag);
}
/**
* Remove tag
*
* #param \AppBundle\Entity\Tag $tag
*/
public function removeTag(\AppBundle\Entity\Tag $tag)
{
$this->tags->removeElement($tag);
}
public function __toString()
{
return (string) $this->getDescription();
}
}
TaskType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
class TaskType extends AbstractType
{
function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description', EntityType::class,array(
'class' => 'AppBundle:Task',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('d');
},));
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'by_reference' => false,
));
$builder->add('save', SubmitType::class, array('label' => 'Save'));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task',
));
}
}
Task.orm.yaml
AppBundle\Entity\Task:
type: entity
table: null
repositoryClass: AppBundle\Repository\TaskRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
description:
type: string
length: 255
oneToMany:
tags:
targetEntity: Tag
mappedBy: task
cascade: [ persist ]
lifecycleCallbacks: { }
Oh i see the problem.
When you submit the form (and the new task with tags is saved), you display the same form, so user expect this to be "edit" form, but it is not.
The correct way how to do this, is to create new controller action for editing the task, for example editAction(), which would be similar to newAction(), only that the route would have parameter to identify the task (e.g. task_id) and in the beginning instead of creating new Task with $task = new Task(); you would fetch the task from DB, for example:
$task = $this->get("doctrine")->getManager()->getRepository("AppBundle:Task")->findOneById($task_id);
you could also reuse the same form type.
THEN, you add redirect in original newAction after entity is persisted to this new edit action:
return $this->redirectToRoute("task_edit", array("task_id" => $task->getId()));
so after submitting the add form, you would be redirected to edit action, so next time the task is changed and for is submitted, the task is updated and not created new one.
So what I need to do is to use $form->getData() to get submitted entities (Task and Tags) and then persist them manually to the database.
if ($form->isValid()) {
$data = $form->getData();
$description = $data->getDescription();
$tags = new ArrayCollection();
$tags = $data->getTags();
foreach ($tags as $tag) {
$tag->addTask($description);
}
$em = $this->getDoctrine()->getManager();
$em->persist($tag);
$em->flush();
}

Managing users/roles/groups in FOSUserBundle

I am developing a simple CRUD to manage users/roles/groups of the application in which I am working. To manage users I'm using FOSUserBundle. What I want to do can be accomplished in several ways:
Assigning roles to groups and then assign users to these groups
Assigning roles to users directly
But I have no idea how. I knew that FOSUser BaseUser class already has a column roles and in the documentation of FOSUser explains how to establish a ManyToMany relationship between users and groups but do not talk anything about roles. The only idea that comes to mind is to create an entity to manage the roles as well as a form for the same purpose, something like what you see below:
Role Entity
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="fos_role")
* #ORM\Entity(repositoryClass="UserBundle\Entity\Repository\RoleRepository")
*
* #see User
* #see \UserBundle\Role\RoleHierarchy
*
*/
class Role implements RoleInterface
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=80, unique=true)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Role", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
* #var Role[]
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Role", mappedBy="parent")
* #var ArrayCollection|Role[]
*/
private $children;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*/
private $users;
public function __construct($role = "")
{
if (0 !== strlen($role)) {
$this->name = strtoupper($role);
}
$this->users = new ArrayCollection();
$this->children = new ArrayCollection();
}
/**
* #see RoleInterface
*/
public function getRole()
{
return $this->name;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getUsers()
{
return $this->users;
}
public function addUser($user, $addRoleToUser = true)
{
$this->users->add($user);
$addRoleToUser && $user->addRole($this, false);
}
public function removeUser($user)
{
$this->users->removeElement($user);
}
public function getChildren()
{
return $this->children;
}
public function addChildren(Role $child, $setParentToChild = true)
{
$this->children->add($child);
$setParentToChild && $child->setParent($this, false);
}
public function getDescendant(& $descendants = array())
{
foreach ($this->children as $role) {
$descendants[spl_object_hash($role)] = $role;
$role->getDescendant($descendants);
}
return $descendants;
}
public function removeChildren(Role $children)
{
$this->children->removeElement($children);
}
public function getParent()
{
return $this->parent;
}
public function setParent(Role $parent, $addChildToParent = true)
{
$addChildToParent && $parent->addChildren($this, false);
$this->parent = $parent;
}
public function __toString()
{
if ($this->children->count()) {
$childNameList = array();
foreach ($this->children as $child) {
$childNameList[] = $child->getName();
}
return sprintf('%s [%s]', $this->name, implode(', ', $childNameList));
}
return sprintf('%s', $this->name);
}
}
Role Form Type
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class RoleType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('parent');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\UserBundle\Entity\Role'
));
}
/**
* #return string
*/
public function getName()
{
return 'role';
}
}
If so what would add to my user form would look something like this
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', 'text')
->add('email', 'email')
->add('enabled', null, array(
'label' => 'Habilitado',
'required' => false
))
->add('rolesCollection', 'entity', array(
'class' => 'UserBundle:Role',
'multiple' => true,
'expanded' => true,
'attr' => array('class' => 'single-line-checks')
))
->add('groups', 'entity', array(
'class' => 'UserBundle:Group',
'multiple' => true,
'expanded' => true,
));
}
But I do not know if it is the right way to handle the roles since in this case would be creating a new table in my DB called fos_roles where were the relationships between users/roles is handled but relationships between groups/roles stay out of it, then that's where I'm a little lost and need help from the more experienced in that tell me and alert if I'm on track and that would make them to achieve what I explain in the first two points. Any advice or help? How do you deal with this?
Symfony Roles
The way FOSUserBundle deals with Roles is to store them in the roles column that you've seen, in a serialised format like this: a:1:{i:0;s:10:"ROLE_ADMIN";}. So there's no need for any other tables or entities^.
^ This is in contrast to Groups, which need to be explicitly configured, are represented by a separate Table/Entity, and do involve relating Users to Groups in the DB. Groups let you define arbitrary collections of Roles which can then be given to each User as a discrete bundle.
A User can be a member of any number of Roles. They're identified by strings starting with "ROLE_", you can just start using a new Role.
What the Roles mean for your application is completely up to you, but they're quite a high-level tool - a User is either in a particular Role or they aren't.
You put people in Roles either via the Symfony console:
php app/console fos:user:promote testuser ROLE_ADMIN
Or in PHP:
$user = $this->getUser();
$userManager = $container->get('fos_user.user_manager');
$user->addRole('ROLE_ADMIN');
$userManager->updateUser($user);
And you can test membership in PHP:
$user = $this->getUser();
if ($user->hasRole('ROLE_ADMIN'))
{
//do something
}
Or using Annotations:
/**
* #Security("has_role('ROLE_ADMIN')")
*/
public function adminAction()
{
//...
or
/**
* #Security("has_role('ROLE_ADMIN')")
*/
class AdminController
{
//...
I added the functionality to add default group to the user during registration by overriding the confirmAction in Registration Controller
What I did is I overrided the Registration Controller in my project Bundle by defining the parent to FosUserBUndle .
Then created a function confirmedAction and in the body of the function added this code
$repository = $em->getRepository('AdminAdminBundle:Group');
$group = $repository->findOneByName('staff');
$em = $this->getDoctrine()->getEntityManager();
$user = $this->getUser();
$user->addGroup($group);
$userManager = $this->get('fos_user.user_manager');
$userManager->updateUser($user);
if (!is_object($user) || !$user instanceof FOS\UserBundle\Model\UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->render('FOSUserBundle:Registration:confirmed.html.twig',
['user' => $user]);
And it perfectly saved in db with group assignment.
Hope this will help some one in need as there is little information about the implementation in official fosuserbundle doc.

Categories