Hello I'm trying to make dynamical form and I have some problems.. I have been trying to follow http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html and some related tutorials but without result.
Currently I'm getting following error:
Catchable Fatal Error: Argument 2 passed to
UserBundle\Form\Type\CompanyRegistrationFormType::UserBundle\Form\Type{closure}()
must be an instance of UserBundle\Entity\Sector, instance of
UserBundle\Entity\Company given, called in
UserBundle/Form/Type/CompanyRegistrationFormType.php on line 82 and defined
I have 3 entity table:
Company.php
/**
* #ORM\OneToMany(targetEntity="Sector", mappedBy="company")
*/
protected $sector;
Sector.php
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string")
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Company", inversedBy="sector")
*/
protected $company;
/**
* #ORM\OneToMany(targetEntity="MainCategory", mappedBy="sector")
*/
protected $mainCategory;
MainCategory.php
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Sector", inversedBy="mainCategory")
*/
protected $sector;
FormType.php
$builder->add('sector', 'entity', array(
'class' => 'UserBundle:Company'));
$formModifier = function (FormInterface $form, Sector $sector = null) {
$mainCategories = null === $sector ? array() : $sector->getAvailableMainCategories();
$form->add('mainCategory', 'entity', array(
'class' => 'UserBundle:Sector',
'choices' => $mainCategories));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data);
});
$builder->get('sector')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$sector = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $sector);
});
and twig:
{{ form_row(form.sector) }} {# <select id="company_sector" #}
{{ form_row(form.mainCategory) }} {# select id="company_mainCategory" #}
<script>
var $sector = $('#company_sector');
$sport.change(function(){
var $form = $(this).closest('form');
var data = {};
data[$sector.attr('name')] = $sector.val();
$.ajax({
url : $form.attr('action'),
type : $form.attr('method'),
data : data,
success: function(html) {
$('#company_mainCategory').replaceWith(
$(html).find('#company_mainCategory'));
}
});
});
</script>
Line 82
$formModifier($event->getForm(), $data);
if I change it to
$formModifier($event->getForm(), $data->getSector());
I get following error:
Notice: Undefined property:
UserBundle\Entity\Company::$mainCategory
can someone help me out? Thanks for your time!
$builder->add('sector', 'entity', array(
'class' => 'UserBundle:Company'));
You have to pass in class parameter classname associated with entity Sector (i suppose UserBundle:Sector)
Documentation for symfony2 entity field type
Related
I want to generate dependent dropdown list in Symfony 3.4
I have followed the exact example(copy and paste) : https://ourcodeworld.com/articles/read/652/how-to-create-a-dependent-select-dependent-dropdown-in-symfony-3
I have created all the tables and populated them.
I really didn't change any thing I want to test the code as it is before using it in my project.
the result :
Update:
Person entity:
/**
* Person
*
* #ORM\Table(name="person")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PersonRepository")
*/
class Person
{
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, nullable=false)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="last_name", type="string", length=255, nullable=false)
*/
private $lastName;
/**
* #var \AppBundle\Entity\City
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\City")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="city_id", referencedColumnName="id")
* })
*/
private $city;
/**
* #var \AppBundle\Entity\Neighborhood
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Neighborhood")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="neighborhood_id", referencedColumnName="id")
* })
*/
private $neighborhood;
Person type
class PersonType extends AbstractType
{
private $em;
/**
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('lastName');
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
protected function addElements(FormInterface $form, City $city = null) {
// 4. Add the province element
$form->add('city', EntityType::class, array(
'required' => true,
'data' => $city,
'placeholder' => 'Select a City...',
'class' => 'AppBundle:City'
));
$neighborhoods = array();
if ($city) {
$repoNeighborhood = $this->em->getRepository('AppBundle:Neighborhood');
$neighborhoods = $repoNeighborhood->createQueryBuilder("q")
->where("q.city = :cityid")
->setParameter("cityid", $city->getId())
->getQuery()
->getResult();
}
$form->add('neighborhood', EntityType::class, array(
'required' => true,
'placeholder' => 'Select a City first ...',
'class' => 'AppBundle:Neighborhood',
'choices' => $neighborhoods
));
}
function onPreSubmit(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$city = $this->em->getRepository('AppBundle:City')->find($data['city']);
$this->addElements($form, $city);
}
function onPreSetData(FormEvent $event) {
$person = $event->getData();
$form = $event->getForm();
$city = $person->getCity() ? $person->getCity() : null;
$this->addElements($form, $city);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Person'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_person';
}
}
views/Person.html.twig:
{% extends 'base.html.twig' %}
{% block body %}
<h1>Person creation</h1>
{{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" value="Create" />
{{ form_end(form) }}
<ul>
<li>
Back to the list
</li>
</ul>
{% endblock %}
{% block javascripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
$('#appbundle_person_city').change(function () {
var citySelector = $(this);
$.ajax({
url: "{{ path('person_list_neighborhoods') }}",
type: "GET",
dataType: "JSON",
data: {
cityid: citySelector.val()
},
success: function (neighborhoods) {
var neighborhoodSelect = $("#appbundle_person_neighborhood");
neighborhoodSelect.html('');
neighborhoodSelect.append('<option value> Select a neighborhood of ' + citySelector.find("option:selected").text() + ' ...</option>');
$.each(neighborhoods, function (key, neighborhood) {
neighborhoodSelect.append('<option value="' + neighborhood.id + '">' + neighborhood.name + '</option>');
});
},
error: function (err) {
alert("An error ocurred while loading data ...");
}
});
});
</script>
{% endblock %}
Person controller:
class PersonController extends Controller
{
/**
* Returns a JSON string with the neighborhoods of the City with the providen id.
*
* #param Request $request
* #return JsonResponse
*/
public function listNeighborhoodsOfCityAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$neighborhoodsRepository = $em->getRepository("AppBundle:Neighborhood");
$neighborhoods = $neighborhoodsRepository->createQueryBuilder("q")
->where("q.city = :cityid")
->setParameter("cityid", $request->query->get("cityid"))
->getQuery()
->getResult();
$responseArray = array();
foreach($neighborhoods as $neighborhood){
$responseArray[] = array(
"id" => $neighborhood->getId(),
"name" => $neighborhood->getName()
);
}
// Return array with structure of the neighborhoods of the providen city id
return new JsonResponse($responseArray);
}
}
AppBundle/config/routing.yml
person_list_neighborhoods:
path: /get-neighborhoods-from-city
defaults: { _controller: "AppBundle:Person:listNeighborhoodsOfCity" }
methods: GET
I'm trying to create an todo-list element.
This is my list element class:
<?php
/**
* Created by PhpStorm.
* User: Alan
* Date: 03-Feb-17
* Time: 3:18 AM
*/
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="Todos")
*/
class Todos
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="integer", length=100)
*/
private $categoryId;
/**
* #ORM\Column(type="integer", length=100)
*/
private $userId;
/**
* #ORM\Column(type="datetime", length=100)
*/
private $init_date;
/**
* #ORM\Column(type="datetime", length=100)
*/
private $comp_date;
//SETTERS AND GETTERS....
}
For this I have generated a formType
<?php
namespace AppBundle\Form;
use ....
class TodosType extends AbstractType
{
private $user;
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class, array(
'label' => 'Nazwa',
'attr' => array(
'class' => 'name-pick'
)
))
->add('categoryId', HiddenType::class)
->addEventListener(FormEvents::POST_SUBMIT, function(FromEvent $e){
$e->getData()->setUserId($this->user->getId());
$e->getData()->setInitDate($this->timestamp(new \DateTime()));
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Todos'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_todos';
}
public function __construct($storage)
{
$this->user = $storage->getToken()->getUser();
}
}
And a Service to get the users Id:
app.form.todos:
class: AppBundle\Form\TodosType
arguments: ["#security.token_storage"]
tags:
- { name: form.type, alias: app_user_todos }
Now in my Twig I have it listed like so in order to have the category id values assigned properly
{ form_start(form) }}
{{ form_widget(form.name) }}
{{ form_widget(form.categoryId, {'value': thisCat[0].id}) }}
{# TODO: FIND A BETTER WAY TO SEND THE categoryId#}
{{ form_end(form) }}
Which on submit is serializeArray()'d and send to my Ajax Controllers method which isn't supposed to do much other then to assign the values and insert them into the database:
/**
* #Route("/ajax/addTodo", name="AddTodoAjax")
*/
public function AddTodoAjax(Request $request)
{
$form = $this->createForm(TodosType::class);
$form->handleRequest($request);
if($form->isValid() && $form->isSubmitted()){
$todo = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($todo);
$em->flush();
return $this->json($todo->getId());
}
}
}
Now the problem is, running the system in this configuration produces an 500 (Internal Server Error) with error content being:
Type error: Argument 1 passed to
AppBundle\Form\TodosType::AppBundle\Form{closure}() must be an
instance of AppBundle\Form\FromEvent, instance of
Symfony\Component\Form\FormEvent given (500 Internal Server Error)
This is the first time I've had such an issue.
I tried looking for the question here but all the similar problems are nothing but...well similar.
Does anyone know how to fix this issue?
All help would be amazing.
In your form type, you have
->addEventListener(FormEvents::POST_SUBMIT, function(FromEvent $e){
That needs to be
->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $e){
I am working on a REST webservice (FOSRestBundle 2.0.0, Symfony 3.1.3) and testing the creation of entities. The creation itself works fine with a correct set of data but if I try to omit a required value the controller still says the form is valid.
The entity itself:
class Customer implements ExportableEntity
{
use Traits\FilterableTrait;
use Traits\UuidTrait;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Serializer\Exclude()
* #Serializer\ReadOnly()
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="customer_index", type="integer", unique=true)
*/
private $customerIndex;
/**
* #var string
*
* #ORM\Column(name="customerName", type="string", length=255)
*/
private $customerName;
// [... accessors ...]
The controller:
/**
* #ApiDoc(
* resource=false,
* description="Create a new customer",
* section="Customers",
* statusCode={
* 200="Action successful",
* 403="Authorization required but incorrect / missing information or unsufficient rights",
* 500="Returned if action failed for unknown reasons"
* }
* )
*
* #param Customer $customer
* #return RestResponse
*/
public function postCustomerAction(Request $request) {
$manager = $this->container->get('corebundle.managers.customer');
// Internal usage only, no link with the WS issue
$manager->setChecksEnabled(false);
$customer = new Customer();
$form = $this->get('form.factory')->createNamed(null, CustomerType::class, $customer, ['csrf_protection' => false]);
$form->handleRequest($request);
//if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
print('VALID');
exit();
$manager->create($customer);
// Return 201 + Location
}
return \FOS\RestBundle\View\View::create($form, 400);
}
And the FormType:
class CustomerType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customerName', Type\TextType::class, array('label' => 'Customer name'))
->add('customerIndex', Type\IntegerType::class, array('label' => 'Customer Index'))
->add('comment', Type\TextareaType::class, array('label' => 'Comments',
'required' => false, ))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'NetDev\CoreBundle\Entity\Customer'));
}
/**
* #return string
*/
public function getBlockPrefix()
{
return 'netdev_corebundle_customer';
}
}
If I try to create a new Customer and omit the "customerIndex" field, I belieev that I should get an invalid form error but I ain't getting it.
I tried to change the "handleRequest" with
$form->submit([])
and
$form->submit($request->request->get($form->getName()))
to no avail. If I add a "NotBlank()" constraint to the entity itself it works but I am under the impression that this would be a workaround, not a fix. Did I miss something ?
$form->isValid()
This line will verify that your submitted data respected all the constraints written in your entity files (with Assert annotation, for example #Assert\NotBlank()).
So, you did not miss something.
I am trying to create an admin page for a sports club website so that each month, the admin user can generate new invoices (Invoice entity) for all active members (Member entity) of the club.
I'm trying to create the form so that I have one row for each member pre-populated with their standard monthly fee and the current date (both of which can be changed for individual member entries if needed):
I have tried just about everything I can think of to get this working in a form but so far I've had no success. Below is the code as it currently stands but this gives me an individual form for just the last member....any advice on what I'm doing wrong would be very welcome - thanks in advance!
Entities (Member and Invoice):
class Member
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Invoice", mappedBy="member_id")
*/
protected $invoice_ids;
/**
* #ORM\COLUMN(type="string", length=100)
* #Assert\NotBlank()
*/
protected $firstname;
/**
* #ORM\COLUMN(type="string", length=100)
*/
protected $familyname;
/**
* #ORM\COLUMN(type="boolean")
*/
protected $active = true;
/**
* #ORM\COLUMN(type="decimal", precision=7, scale=2)
* #Assert\Regex(
* pattern="/^\s*-?[1-9]\d*(\.\d{1,2})?\s*$/",
* match=true,
* message="Error")
*/
protected $defaultinvoiceamount;
public function __construct()
{
$this->invoice_ids = new ArrayCollection();
}
public function FullName()
{
return $this->firstname . ' ' . $this->familyname;
}
}
class Invoice
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Member", inversedBy="invoice_ids")
* #ORM\JoinColumn(name="member_id", referencedColumnName="id")
*/
protected $member_id;
/**
* #ORM\COLUMN(type="decimal", precision=7, scale=2)
* #Assert\Regex(
* pattern="/^\s*-?[1-9]\d*(\.\d{1,2})?\s*$/",
* match=true,
* message="Error")
*/
protected $amount;
/**
* #ORM\COLUMN(type="datetime")
* #Assert\DateTime()
*/
protected $invoicedate;
/**
* #ORM\COLUMN(type="datetime")
* #Assert\DateTime()
*/
protected $createdate;
/**
* #ORM\COLUMN(type="text", nullable=True)
*/
protected $comments;
}
FormType:
class InvoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('member_id', EntityType::class, array(
'class' => 'AppBundle:Member',
'choice_label' => 'FullName',
'attr' => array(
'readonly' => 'readonly'
)
)
)
->add('invoicedate', DateType::class, array(
'widget' => 'single_text',
'data' => new \DateTime('now'),
'format' => 'dd/MMM/yyyy',
'label' => 'Date of invoice',
))
->add('createdate', DateType::class, array(
'widget' => 'single_text',
'data' => new \DateTime('now'),
'format' => 'dd/MMM/yyyy',
'label' => 'Date invoice recorded in database',
'disabled' => 'true'
))
->add('amount', MoneyType::class, array(
'label' => 'Amount',
))
->add('comments')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Invoice',
));
}
public function getName()
{
return 'invoice';
}
}
Repository:
class MemberRepository extends EntityRepository
{
/**
* #return \Doctrine\ORM\QueryBuilder
*/
public function findAllActiveMembers()
{
return $this->getEntityManager()
->createQuery
(
'SELECT m
FROM AppBundle:Member m
WHERE m.active= :active
ORDER BY m.surname, m.firstname'
)
->setParameter('active' ,true)
->getResult();
}
}
The Controller:
/**
* #Route("/batchinvoices" ,name="batchinvoices")
*/
public function newBatchInvoicesAction(Request $request)
{
$members = $this->getDoctrine()->getRepository('AppBundle:Members')->findAllActiveMembers();
foreach ($members as $member) {
$invoices = new Invoice();
$invoices->setMemberId($member);
$form=$this->createForm(InvoiceType::class, $invoices);
}
$form->handleRequest($request);
if ($form->isSubmitted() && ($form->isValid())) {
$em = $this->getDoctrine()->getManager();
$em->persist($invoices);
$em->flush();
return $this->redirectToRoute('invoices_added');
}
return $this->render('admin/batchinvoices.html.twig', array(
'form' => $form->createView(),
));
}
The reason you are only getting the last entry in the form is that you are continually overwriting your own variables. Take a look at this code specifically:
foreach ($members as $member) {
$invoices = new Invoice();
$invoices->setMemberId($member);
$form=$this->createForm(InvoiceType::class, $invoices);
}
You are continually overwriting the $form value every single time through your loop, so not only can you only handle the last entry, it is the only one that will show up.
I have come across this situation before but it was usually just to delete a single record. If you are fine with displaying all the forms at once but only updating one member at a time, you can generate a single form for each member (like you are doing now), and then add it to a forms array that you pass to your template. So your code would now look like:
$forms = array();
foreach ($members as $member) {
$invoices = new Invoice();
$invoices->setMemberId($member);
$forms[] = $this->createForm(InvoiceType::class, $invoices)->createView();
}
//...
return $this->render('admin/batchinvoices.html.twig', array(
'forms' => $forms,
));
Notice that I am calling ->createView() which is what is acceptable for the Twig template. Then your twig template is going to look something like this:
{% for form in forms %}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endfor %}
Obviously you could change the templating to your liking, or even put the individual form definition in its own template and include that like so:
{% for form in forms %}
{{ include('#AppBundle/YourController/batchInvoiceForm.html.twig', {'form': form}) }}
{% endfor %}
Keep in mind that doing this would only allow you to update one record at a time, so if you didn't want to refresh the page you would want to create an AJAX request that posts to a separate controller action and handles the modification of results and sending a success/fail response back. The benefit of doing it this way is that you're not posting heaps of data for all members when you're only modifying a small amount.
It could be simpler to just display all member information and then have an Edit button that takes you to a separate form just for updating that member that would then redirect back to your list after submitting - unless I need on-the-fly or bulk updates I always go for this route.
If you want to update all records at once you will have to use the CollectionType as Onema said.
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).