want to add optional DateType class in symfony form. It kinda works because I can submit form without setting the date but it auto sets todays date.
TodoType.php
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
->add('Deadline', DateType::class, [
'widget' => 'single_text',
'required' => false,
'empty_data' => ''
])
->add('Submit', SubmitType::class)
;
}
deadline entities
/**
* #ORM\Column(type="date", nullable=true)
*/
private $deadline;
...
public function getDeadline(): ?\DateTimeInterface
{
return $this->deadline;
}
public function setDeadline(\DateTimeInterface $deadline = null): self
{
$this->deadline = $deadline;
return $this;
}
TodoController.php
/**
* #Route("/todos", methods={"GET", "POST"}, name="todos")
*
*/
public function todos(EntityManagerInterface $entityManager, Request $request): Response
{
// Rendering todos
$todos = $entityManager->getRepository(Todo::class)
->findBy(
['owner' => $this->getUser()]
);
// Creating new TODO
$todo = new Todo();
$todo
->setOwner($this->getUser())
->setCreationDate(new \DateTime());
$form = $this->createForm(TodoType::class, $todo);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$entityManager->persist($todo);
$entityManager->flush();
return $this->redirectToRoute('todos');
}
return $this->render('todo/todos.html.twig', [
'todos' => $todos,
'form' => $form->createView(),
]);
}
To render in .twig I used just {{ form(form) }} haven't customized it yet.
Edit: code missing
Every thing looks good.
I tried on my side and it worked fine (in database, i got null):
Form:
$builder->add(
'dateTime', DateType::class, [
'required' => false,
'widget' => 'single_text',
'empty_data' => ''
]
);
Entity
public function __construct() {
// empty
}
/**
* #var DateTime|null
* #ORM\Column(name="date_time", type="datetime", nullable=true)
*/
private ?DateTime $dateTime;
/**
* #return DateTime|null
*/
public function getDateTime(): ?DateTime
{
return $this->dateTime;
}
/**
* #param DateTime|null $dateTime
*
* #return SupportTimeSlot
*/
public function setDateTime(?DateTime $dateTime): SupportTimeSlot
{
$this->dateTime = $dateTime;
return $this;
}
Controller
/**
* #Route("/time-slot-detail/{id}", name="time_slot_detail", methods={"GET", "POST"})
* #param SupportTimeSlot $supportTimeSlot
* #param Request $request
* #param SupportTimeSlotManager $supportTimeSlotManager
*
* #return Response
*/
public function timeSlotDetail(
SupportTimeSlot $supportTimeSlot,
Request $request,
SupportTimeSlotManager $supportTimeSlotManager
): Response
{
$form = $this->createForm(TimeSlotEditType::class, $supportTimeSlot);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$supportTimeSlotManager->save($supportTimeSlot);
return $this->redirectToRoute('boa_support_time_slot_detail', ['id' => $supportTimeSlot->getId()]);
}
return $this->render(
'boa/support/detail.twig', [
'timeSlot' => $supportTimeSlot,
'form' => $form->createView(),
]
);
}
Twig
<div class="row">
<div class="col-12">
{{ form_start(form) }}
{{ form_row(form.dateTime) }}
<button type="submit" class="btn btn-block btn-outline-info">
{% trans %}Save{% endtrans %}
</button>
{{ form_end(form) }}
</div>
</div>
My project contain some datepicker and datetimepicker js, maybe try to instanciate js to check if it come from this.
Otherwise try to debug your $request in your controller. If it provide some date for your deadline attribute, your problem come from twig/js
If $request is null but your entity is populated with data for deadline, your problem come from your construct
If you save your entity with enpty deadline but you gotr one in database, your problem come from your database
Related
I've been staring at this too long, and now I need another set of eyes.
I had previously set up my relationship between two entities, Dish and Ingredient as a many to many relationship, and had the form association working perfectly. However, I realized afterward that I needed another field on my link table to specify "amount" so as a result I created a new entity DishIngredient. It created the same table but instead of a many to many, it's now one table with two Many to One relationships to Dish and Ingredient.
So far so good. I have everything working until I attempt to associated ingredients with dishes. I'm able to successfully generate a form to display each of the Ingredients, however when I submit it, I run into problems. It's attempting to submit to Ingredient instead of DishIngredient.
Here is what I have so far:
My controller:
/**
* #Route("/recipe/ingredient/{id}", name="edit_ingredients")
*/
public function ingredientEdit(Request $request, Dish $dish, $id)
{
$user = $this->getUser();
$dish_id = $dish->getId();
$dish = $this->getDoctrine()->getRepository(Dish::class)->findOneBy(array('id' =>$dish_id ));
$dish_user = $dish->getUser();
//in case someone tries to manually hack into someone else's recipe
if($dish_user != $user) {
return $this->render('dishes/error.html.twig', array(
'dish' => $dish,
'user' => $user
)
);
}
else {
$form = $this->createForm(IngredientType::class, $dish);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$dish = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($dish);
$em->flush();
return $this->redirectToRoute('edit_ingredients', array('id' => $id));
}
return $this->render('dishes/ingredients.html.twig', array(
'form' => $form->createView(),
'dish' => $dish
)
);
}
}
Which then loads this form:
class IngredientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options); // TODO: Change the autogenerated stub
$ingredient = new Ingredient();
$builder
->add('DishIngredients',EntityType::class, array(
'required' => false,
'attr' => array('class' => 'form-control'),
'class' => Ingredient::class,
'query_builder' => function(IngredientRepository $ir) {
return $ir->createQueryBuilder('s')
->orderBy('s.name', 'ASC');
},
'multiple' => true,
'expanded' => true,
))
->add('save', SubmitType::class, array(
'label' => 'Update',
'attr' => array('class' => 'btn btn-primary mt-3')
))
->getForm();
}
}
It displays fine in the twig with the following:
{{ form_start(form) }}
{% for i in form.DishIngredients %}
{{ form_widget(i) }} {{ form_label(i) }}<br>
{% endfor %}
{{ form_end(form) }}
However the problem is that when I attempt to submit it, I get this error:
Expected value of type "App\Entity\DishIngredient" for association
field "App\Entity\Dish#$dishIngredients", got "App\Entity\Ingredient"
instead.
If I change the form to call DishIngredient instead of Ingredient, it shows none of the ingredient; it just creates a text field looking for an input; not what I want or need at all.
Here's the DishIngredient Entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\DishIngredientRepository")
*/
class DishIngredient
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Dish", inversedBy="dishIngredients")
*/
private $dish;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Ingredient", inversedBy="dishIngredients")
*/
private $ingredient;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $amount;
public function getId(): ?int
{
return $this->id;
}
public function getDish(): ?Dish
{
return $this->dish;
}
public function setDish(?Dish $dish): self
{
$this->dish = $dish;
return $this;
}
public function getIngredient(): ?Ingredient
{
return $this->ingredient;
}
public function setIngredient(?Ingredient $ingredient): self
{
$this->ingredient = $ingredient;
return $this;
}
public function getAmount(): ?string
{
return $this->amount;
}
public function setAmount(?string $amount): self
{
$this->amount = $amount;
return $this;
}
}
and here's the Ingredient entity for reference
/**
* #ORM\Entity(repositoryClass="App\Repository\IngredientRepository")
*/
class Ingredient
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\DishIngredient", mappedBy="ingredient")
*/
private $dishIngredients;
public function __toString(): ?string
{
// TODO: Implement __toString() method.
return $this->name;
}
public function __construct()
{
$this->dishes = new ArrayCollection();
$this->dishIngredients = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|DishIngredient[]
*/
public function getDishIngredients(): Collection
{
return $this->dishIngredients;
}
public function addDishIngredient(DishIngredient $dishIngredient): self
{
if (!$this->dishIngredients->contains($dishIngredient)) {
$this->dishIngredients[] = $dishIngredient;
$dishIngredient->setIngredient($this);
}
return $this;
}
public function removeDishIngredient(DishIngredient $dishIngredient): self
{
if ($this->dishIngredients->contains($dishIngredient)) {
$this->dishIngredients->removeElement($dishIngredient);
// set the owning side to null (unless already changed)
if ($dishIngredient->getIngredient() === $this) {
$dishIngredient->setIngredient(null);
}
}
return $this;
}}
Anyone have any ideas here?
--
EDIT:
Okay, so I created a new form titled DishIngredientType, and moved the information from above into it, and then modified IngredientType to have following instead:
$builder
->add('DishIngredients',CollectionType::class, array(
'required' => false,
'attr' => array('class' => 'form-control'),
'entry_type' => DishIngredientType::class,
'entry_options' => ['label' => false],
))
but now I'm only getting a plan text field (no options at all).
This is how they look in DishIngredientType
->add('DishIngredients',EntityType::class, array(
'required' => false,
'attr' => array('class' => 'form-control'),
'class' => Ingredient::class,
'query_builder' => function(IngredientRepository $ir) {
return $ir->createQueryBuilder('s')
->orderBy('s.name', 'ASC');
},
'multiple' => true,
'expanded' => true,
))
I'm not sure what I'm missing here. I know this shouldn't be this difficult.
There are three entities related by ManyToOne relations: A 'Page' can have many 'Block'. (A block can be related to one page). A 'Block' can have many 'Button', (A 'Button' can be in one block).
I have a form system where I can add multiple Blocks, and within each block multiple Buttons. So there is an embedded form inside an embedded for ( 2 layers)
The problem is that only one button gets saved to the database when trying to save multiple.
But when i save one button, I can go back and save multiple at once. I am not able to understand this behavior.
/**
* #ORM\Entity(repositoryClass="App\Repository\PageRepository")
*/
class Page
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
...
/**
* #ORM\OneToMany(targetEntity="App\Entity\Block", mappedBy="page", orphanRemoval=true, cascade={"persist"})
* #ORM\OrderBy({"ordering" = "ASC"})
*/
private $blocks;
...
public function __construct()
{
$this->blocks = new ArrayCollection();
}
/**
* #return Collection|Block[]
*/
public function getBlocks(): Collection
{
return $this->blocks;
}
public function addBlock(Block $block): self
{
if (!$this->blocks->contains($block)) {
$this->blocks[] = $block;
$block->setPage($this);
}
return $this;
}
public function removeBlock(Block $block): self
{
if ($this->blocks->contains($block)) {
$this->blocks->removeElement($block);
// set the owning side to null (unless already changed)
if ($block->getPage() === $this) {
$block->setPage(null);
}
}
return $this;
}
}
Block:
/**
* #ORM\Entity(repositoryClass="App\Repository\BlockRepository")
*/
class Block
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Block\Button", mappedBy="block",
orphanRemoval=true, cascade={"persist"})
*/
private $buttons;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Page", inversedBy="blocks")
* #ORM\JoinColumn(nullable=false)
*/
private $page;
public function __construct()
{
$this->buttons = new ArrayCollection();
}
/**
* #return Collection|Button[]
*/
public function getButtons(): Collection
{
return $this->buttons;
}
public function addButton(Button $button): self
{
if (!$this->buttons->contains($button)) {
$this->buttons[] = $button;
$button->setBlock($this);
}
return $this;
}
public function removeButton(Button $button): self
{
if ($this->buttons->contains($button)) {
$this->buttons->removeElement($button);
// set the owning side to null (unless already changed)
if ($button->getBlock() === $this) {
$button->setBlock(null);
}
}
return $this;
}
}
Button:
/**
* #ORM\Entity(repositoryClass="App\Repository\Block\ButtonRepository")
* #ORM\Table(name="block_button")
*/
class Button
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Block", inversedBy="buttons")
* #ORM\JoinColumn(nullable=false)
*/
private $block;
/**
* #ORM\Column(type="string", length=255)
*/
private $label;
public function getBlock(): ?Block
{
return $this->block;
}
public function setBlock(?Block $block): self
{
$this->block = $block;
return $this;
}
}
Following are the form corresponding form types:
PageType:
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('blocks', CollectionType::class, [
'entry_type' => BlockType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'help' => '<a data-collection="add" class="btn btn-info btn-sm" href="#">Add Block</a>',
'help_html' => true
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Page::class,
]);
}
}
BlockType
class BlockType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('buttons', CollectionType::class, [
'entry_type' => BlockButtonType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'help' => '<a data-collection="add" class="btn btn-info btn-sm" href="#">Add Button</a>',
'help_html' => true,
'attr' => [
'data-field' => 'buttons'
]
])
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Block::class,
]);
}
}
BlockButtonType
class BlockButtonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Button::class,
]);
}
}
The responsible methods to handle form new and edit are as follows:
/**
* #Route("/admin/pages")
*/
class PageController extends AbstractController
{
/**
* #Route("/new", name="admin_page_new", methods={"GET","POST"})
*/
public function new(Request $request, FileUploader $fileUploader):Response
{
$page = new Page();
$form = $this->createForm(PageType::class, $page);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('publish')->isClicked()) {
$page->setPublished(true);
}
// handle uploads here
$this->handleUploads($fileUploader, $page, $form);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($page);
$entityManager->flush();
if ($form->get('close')->isClicked()) {
return $this->redirectToRoute('admin_page_index');
}
$this->addFlash('success', 'Page saved');
return $this->redirectToRoute('admin_page_edit', [
'id' => $page->getId(),
]);
}
return $this->render('admin/page/new.html.twig', [
'page' => $page,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}/edit", name="admin_page_edit", methods={"GET","POST"})
*/
public function edit(Request $request, FileUploader $fileUploader, Page $page): Response
{
$form = $this->createForm(PageType::class, $page);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($page->getParent() != null && $page->getParent()->getId() == $page->getId()) {
$page->setParent(null);
$this->addFlash('error', 'You cannot set a page to be a parent of itself');
return $this->render('admin/page/edit.html.twig', [
'page' => $page,
'form' => $form->createView(),
]);
}
if ($form->get('publish')->isClicked()) {
$page->setPublished(true);
}
// get all blocks and check if anything was uploaded?
$this->handleUploads($fileUploader, $page, $form);
$this->getDoctrine()->getManager()->flush();
if ($form->get('close')->isClicked()) {
return $this->redirectToRoute('admin_page_index');
}
$this->addFlash('success', 'Page saved');
return $this->redirectToRoute('admin_page_edit', [
'id' => $page->getId(),
]);
}
}
Finally the html twig that displays the form:
{% extends 'admin/layouts/admin.html.twig' %}
{% block body %}
{{ form_start(form) }}
<div class="tab-content">
<div class="tab-pane show active" id="details">
{{ form_row(form.blocks) }}
</div>
</div>
{{ form_widget(form.save) }}
{{ form_widget(form.close) }}
{{ form_widget(form.publish) }}
{{ form_rest(form) }}
{{ form_end(form) }}
{% endblock %}
I want to update data and I need to use form builder for creating form. I saw similar questions but it don't work for me.
My form builder. I think it's all right.
class GeneralInfoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, ['label' => false])
->add('email', EmailType::class, ['label' => false])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
]);
}
}
My form on twig. I send id current user on form.
{{ form_start(form, {'action': path('updateInfo',{'id':users.id}), 'method': 'GET'}) }}
{{ form_row(form.username, {'value' : users.username, 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.email, {'value' : users.email, 'attr': {'class': 'form-control'} } ) }}
<button type="submit" class="btn btn-primary">Update</button>
{{ form_end(form) }}
My controller.
/**
* #Route("user/profile/{id}", name="updateInfo")
* #param $id
* #param Request $request
* #return Response
*/
public function updatePersonalInfoAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('App:User')->find($id);
$form = $this->createForm(GeneralInfoType::class, $user);
$username = $form->get('username')->getData();
dump($username);
}
When I try dump username I got "" or null from server.
Try this for your controller:
/**
* #Route("user/profile/{id}", name="updateInfo")
* #param $id
* #param Request $request
* #return Response
*/
public function updatePersonalInfoAction($id, Request $request)
{
$user = $em->getRepository('App:User')->find($id);
$form = $this->createForm(GeneralInfoType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted()) {
$username = $form->get('username')->getData();
dump($username);
// you can save the update user info with this:
$user = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
}
return $this->render('insert_twig_file_path/name', [
'form' => $form->createView()
]);
}
You can also take advantage of some automagical symfony functions to simplify a bit:
/**
* #Route("user/profile/{id}", name="updateInfo")
* #param Request $request
* #param User $user
* #return Response
*/
public function updatePersonalInfoAction(Request $request, User $user)
{
$form = $this->createForm(GeneralInfoType::class, $user);
... the rest unchanged ...
}
I'm trying to make a form that users can easily change their password. I hope my logic is correct, however I'm getting an error as follows;
Expected argument of type "string", "AppBundle\Form\ChangePasswordType" given
Here is my controller;
public function changePasswdAction(Request $request)
{
$changePasswordModel = new ChangePassword();
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// perform some action,
// such as encoding with MessageDigestPasswordEncoder and persist
return $this->redirect($this->generateUrl('homepage'));
}
return $this->render(':security:changepassword.html.twig', array(
'form' => $form->createView(),
));
}
Here is my model;
class ChangePassword
{
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password"
* )
*/
protected $oldPassword;
/**
* #Assert\Length(
* min = 6,
* minMessage = "Password should by at least 6 chars long"
* )
*/
protected $newPassword;
/**
* #return mixed
*/
public function getOldPassword()
{
return $this->oldPassword;
}
/**
* #param mixed $oldPassword
*/
public function setOldPassword($oldPassword)
{
$this->oldPassword = $oldPassword;
}
/**
* #return mixed
*/
public function getNewPassword()
{
return $this->newPassword;
}
/**
* #param mixed $newPassword
*/
public function setNewPassword($newPassword)
{
$this->newPassword = $newPassword;
}
}
Here is my change password type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', 'password');
$builder->add('newPassword', 'repeated', array(
'type' => 'password',
'invalid_message' => 'The password fields must match.',
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
));
}
}
Here is my viewer;
{{ form_widget(form.current_password) }}
{{ form_widget(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.second) }}
The solution mentioned by #dragoste worked well for me.
I changed the following line
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
with this line;
$form = $this->createForm(ChangePasswordType::class, $changePasswordModel);
In recent Symfony releases you can pass only class name in createForm
Change
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
to
$form = $this->createForm(ChangePasswordType::class, $changePasswordModel);
Read more about building forms at
http://symfony.com/doc/current/best_practices/forms.html#building-forms
Now I have problem with submitting post data in my form (my forms looks like:
Task: <input text>
Category: <multiple select category>
DueDate: <date>
<submit>
)
And after submitting my form, I'll get this error:
Found entity of type Doctrine\Common\Collections\ArrayCollection on association Acme\TaskBundle\Entity\Task#category, but expecting Acme\TaskBundle\Entity\Category
My sources:
Task Object Task.php
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="tasks")
*/
class Task
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=200)
* #Assert\NotBlank(
* message = "Task cannot be empty"
* )
* #Assert\Length(
* min = "3",
* minMessage = "Task is too short"
* )
*/
protected $task;
/**
* #ORM\Column(type="datetime")
* #Assert\NotBlank()
* #Assert\Type("\DateTime")
*/
protected $dueDate;
/**
* #Assert\True(message = "You have to agree")
*/
protected $accepted;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
*/
protected $category;
/**
* Constructor
*/
public function __construct()
{
$this->category = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set task
*
* #param string $task
* #return Task
*/
public function setTask($task)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* #return string
*/
public function getTask()
{
return $this->task;
}
/**
* Set dueDate
*
* #param \DateTime $dueDate
* #return Task
*/
public function setDueDate($dueDate)
{
$this->dueDate = $dueDate;
return $this;
}
/**
* Get dueDate
*
* #return \DateTime
*/
public function getDueDate()
{
return $this->dueDate;
}
/**
* Add category
*
* #param \Acme\TaskBundle\Entity\Category $category
* #return Task
*/
public function addCategory(\Acme\TaskBundle\Entity\Category $category)
{
$this->category[] = $category;
return $this;
}
/**
* Remove category
*
* #param \Acme\TaskBundle\Entity\Category $category
*/
public function removeCategory(\Acme\TaskBundle\Entity\Category $category)
{
$this->category->removeElement($category);
}
/**
* Get category
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCategory()
{
return $this->category;
}
}
Category Object Category.php
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=200, unique=true)
* #Assert\NotNull(message="Choose a category", groups = {"adding"})
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="Task", mappedBy="category")
*/
private $tasks;
public function __toString()
{
return strval($this->name);
}
/**
* Constructor
*/
public function __construct()
{
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
* #return Category
*/
public function addTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks[] = $tasks;
return $this;
}
/**
* Remove tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
*/
public function removeTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks->removeElement($tasks);
}
/**
* Get tasks
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTasks()
{
return $this->tasks;
}
}
TaskType TaskType.php
<?php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Acme\TaskBundle\Form\Type\Category;
class TaskType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
'cascade_validation' => true,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task', 'text', array('label' => 'Task'))
->add('dueDate', 'date', array('label' => 'Due Date'))
->add('category', new CategoryType(), array('validation_groups' => array('adding')))
//->add('accepted', 'checkbox')
->add('save', 'submit', array('label' => 'Submit'));
}
public function getName()
{
return 'task';
}
}
CategoryType CategoryType.php
<?php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CategoryType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'entity', array(
'class' => 'AcmeTaskBundle:Category',
'query_builder' => function($repository) { return $repository->createQueryBuilder('c')->orderBy('c.id', 'ASC'); },
'property' => 'name',
'multiple' => true,
'label' => 'Categories',
));
}
public function getName()
{
return 'category';
}
}
and my Controller DefaultController.php:
public function newAction(Request $request)
{
$task = new Task();
$task->setTask('Write name here...');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createForm('task', $task);
$form->handleRequest($request);
if($form->isValid())
{
$this->get('session')->getFlashBag()->add(
'success',
'Task was successfuly created'
);
$em = $this->getDoctrine()->getManager();
/*
$category = $this->getDoctrine()->getManager()->getRepository('AcmeTaskBundle:Category')->findOneByName($form->get('category')->getData());
$task->setCategory($category);
*/
$em->persist($task);
try {
$em->flush();
} catch (\PDOException $e) {
// sth
}
//$nextAction = $form->get('saveAndAdd')->isClicked() ? 'task_new' : 'task_success';
//return $this->redirect($this->generateUrl($nextAction));
}
return $this->render('AcmeTaskBundle:Default:new.html.twig', array('form' => $form->createView()));
}
So, I looked at this problem at the google, but there were different kinds of problems. Any idea?
UPDATE
Full error message:
[2013-09-30 14:43:55] request.CRITICAL: Uncaught PHP Exception Doctrine\ORM\ORMException: "Found entity of type Doctrine\Common\Collections\ArrayCollection on association Acme\TaskBundle\Entity\Task#category, but expecting Acme\TaskBundle\Entity\Category" at /var/www/Symfony/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 762 {"exception":"[object] (Doctrine\\ORM\\ORMException: Found entity of type Doctrine\\Common\\Collections\\ArrayCollection on association Acme\\TaskBundle\\Entity\\Task#category, but expecting Acme\\TaskBundle\\Entity\\Category at /var/www/Symfony/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:762)"} []
UPDATE 2
My TWIG template new.html.twig
<html>
<head>
<title>Task create</title>
</head>
<body>
{% for flashMessage in app.session.flashbag.get('success') %}
<div style="display: block; padding: 15px; border: 1px solid green; margin: 15px; width: 450px;">
{{ flashMessage }}
</div>
{% endfor %}
{{ form_start(form, {'action': path ('task_new'), 'method': 'POST', 'attr': {'novalidate': 'novalidate' }}) }}
{{ form_errors(form) }}
<div>
{{ form_label(form.task) }}:<br>
{{ form_widget(form.task) }} {{ form_errors(form.task) }}<br>
</div>
<div>
{{ form_label(form.category) }}:<br>
{{ form_widget(form.category) }} {{ form_errors(form.category) }}
</div>
<div>
{{ form_label(form.dueDate) }}:<br>
{{ form_widget(form.dueDate) }} {{ form_errors(form.dueDate) }}<br>
</div>
{{ form_end(form) }}
</body>
</html>
You can improve the controller code by changing your setters in the entities:
In Task:
public function addCategory(Category $category)
{
if (!$this->categories->contains($category)) {
$this->categories->add($category);
$category->addTask($this); // Fix this
}
}
And in Category:
public function addTask(Task $task)
{
if (!this->tasks->contains($task)) {
$this->tasks->add($task);
$task->addCategory($this);
}
}
This will keep the elements in the ArrayCollection unique so you won't have to do that checking in your code and will also set the inverse side automatically.
So I found my solution! This code works:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task', 'text', array('label' => 'Task'))
->add('dueDate', 'date', array('label' => 'Date', 'format' => 'ddMMMMyyyy'))
->add('category', 'entity', array('required' => true, 'multiple' => true, 'class' => 'AcmeTaskBundle:Category', 'query_builder' => function($repository) { return $repository->createQueryBuilder('c')->orderBy('c.id', 'ASC'); },))
->add('save', 'submit', array('label' => 'Send'));
}
And my controller is working in this form:
if($form->isValid())
{
$this->get('session')->getFlashBag()->add(
'success',
'Task successfuly added'
);
$em = $this->getDoctrine()->getManager();
foreach($form->get('category')->getData() as $cat)
{
$task->removeCategory($cat);
$task->addCategory($cat);
}
$em->persist($task);
try {
$em->flush();
} catch (\PDOException $e) {
// sth
}
}
I'm forced to use $task->removeCategory($cat) because I'll get error of dublication primary indexes if it will not be here.
Now I'm trying to resolve my problem with embedded and non-embedded forms -> Symfony2, validation embedded and non-embedded forms with same parameters and different results?
I believe that my questions will be helpful for beginners with Symfony2 having same problems.
You are defining a manyToMany relationship from task to category, which means you can have more than one category for each task. If you are trying to have multiple categories in a task, you can try changing this line
->add('category', new CategoryType(), array('validation_groups' => array('adding')))
for something like
->add('category', 'collection', array('type' => new CategoryType()))
check symfony's documentation on form collection and this cookbook
I am not sure, but try to add a setCategory method to your Task entity, because currently you only have the add function and thats why the form component is calling the addFunction instead of the set for the whole arraycollection.
I think problem in your form.
Your task have ManyToMany relation with categotry and stored in field category(logical it is a categories). But in the form you set '->add('category', new CategoryType())' which means that you add new type that is a array or something(data_class not defined) and this type contains another one field.
Solution is to directly define field in your form(without using standalone form type) Or extend category form type from entity type(by default type extended form type).