my problem concerns one field from User form, namely Username. Since assertion validation works for other fields in the entity I find it odd to behave like this - skipping the Assertion rule I did point for username attribute in User entity and passing null attribute to userFormNewHandler which is generating an error, especially when I find it not really different than other fields. I wonder, what am I missing?
UserType.php:
<?php
namespace App\UserBundle\Form\Type;
use App\UserBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, ['required'=>true,
'invalid_message' => 'Username must not be empty!'])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'mapped' => false,
'first_options' => array('label' => 'New password'),
'second_options' => array('label' => 'Confirm new password'),
'invalid_message' => 'The password fields must match.',
'required' => true,
'constraints' => [
new NotBlank([
'message' => 'Password field must not be blank!'
])]
))
->add('active_status', ChoiceType::class, [
'choices' => [
'Active' => true,
'Inactive' => false,
],])
->add('first_name',TextType::class, [
'required'=>true, 'invalid_message' => 'First name must not be empty!'])
->add('last_name', TextType::class, [
'required'=>true, 'invalid_message' => 'Last name must not be empty!'])
->add('email', EmailType::class, [
'required'=>true, 'invalid_message' => 'Email must not be empty!']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
And this is my User.php entity:
<?php
namespace App\UserBundle\Entity;
use App\UserBundle\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
* #Assert\NotBlank(message="Fill username field")
*/
private $username;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\Column(type="boolean")
*/
private $active_status;
/**
* #ORM\Column(type="string", length=30)
* #Assert\NotBlank(message="Fill first name field")
*/
private $first_name;
/**
* #ORM\Column(type="string", length=30)
* #Assert\NotBlank(message="Fill last name field")
*/
private $last_name;
/**
* #ORM\Column(type="string", length=40)
* #Assert\NotBlank(message="Fill email field")
*/
private $email;
/**
* Representation of account status
*/
public function getActiveStatus(): bool
{
return $this->active_status;
}
/**
* Setting account status
*/
public function setActiveStatus(bool $active_status): self
{
$this->active_status = $active_status;
return $this;
}
/**
* Representation of username
*/
public function getUsername(): string
{
return (string) $this->username;
}
/**
* Setting username for user
*/
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
//...
and also _form.html.twig, where form is being rendered:
{{ form_start(form, { attr: {novalidate: 'novalidate'} }) }}
{{ form_widget(form) }}
<button class="btn" >{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
I found it! That was because I did point out in setter Username's method to look strictly for string and I guess that prevented me from passing null attribute to the form. Now PHP validation works correctly :)
Part generating error:
User.php
/**
* Setting username for user
*/
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
Fix:
/**
* Setting username for user
*/
public function setUsername($username): self
{
$this->username = $username;
return $this;
}
Related
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 installed Symfony3 and I'm trying to validate a formchild (entity child/not mapped fields) inside a normal form, with #Assert\Valid annotation. I couldn't do it so I tried the example from the Manual:
http://symfony.com/doc/current/reference/constraints/Valid.html
This example, in Symfony 3, doesn't work (at least for me).
This is where #Assert\Valid is used. How does Symfony knows in this case (example from manual) to valid the Address Entity and not any other Entity?
/**
* #Assert\Valid
*/
protected $address;
Has someone tried to test the example from the manual to see if it works?
Can someone please provide the working example from the manual? I don't know what I'm doing wrong..
This is my TestingController.php:
namespace WebsiteBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use WebsiteBundle\Entity\Author;
use WebsiteBundle\Form\Type\AuthorRegistrationType;
class TestingController extends Controller
{
public function registerAccountAction(Request $request)
{
$author = new Author();
$form = $this->createForm(AuthorRegistrationType::class, $author, array(
'required' => false
));
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
echo "It works";
}
return $this->render('TemplatesBundle:Default:testing_registration.html.twig', array(
'form' => $form->createView(),
));
}
}
The AuthorRegistrationType.php:
namespace WebsiteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorRegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("firstname")
->add("lastname")
->add("zipcode", TextType::class, array('mapped' => false))
->add("street", TextType::class, array('mapped' => false))
->add('save', SubmitType::class);
}
}
Author Entity:
namespace WebsiteBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* #Assert\NotBlank
* #Assert\Length(min = 4)
*/
protected $firstname;
/**
* #Assert\NotBlank
*/
protected $lastname;
/**
* #Assert\Valid
*/
protected $address;
/**
* #return mixed
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* #param mixed $firstname
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
}
/**
* #return mixed
*/
public function getLastname()
{
return $this->lastname;
}
/**
* #param mixed $lastname
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
}
/**
* #return mixed
*/
public function getAddress()
{
return $this->address;
}
/**
* #param mixed $address
*/
public function setAddress(Address $address)
{
$this->address = $address;
}
}
Address Entity:
namespace WebsiteBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Address
{
/**
* #Assert\NotBlank()
*/
protected $street;
/**
* #Assert\NotBlank
* #Assert\Length(max = 5)
*/
protected $zipCode;
/**
* #return mixed
*/
public function getStreet()
{
return $this->street;
}
/**
* #param mixed $street
*/
public function setStreet($street)
{
$this->street = $street;
}
/**
* #return mixed
*/
public function getZipCode()
{
return $this->zipCode;
}
/**
* #param mixed $zipCode
*/
public function setZipCode($zipCode)
{
$this->zipCode = $zipCode;
}
}
This is what I get now:
Firstname
This value should not be blank.
Lastname
This value should not be blank.
Street
Zipcode
So: the main Entity is validated, but the inherited one (Street/Zipcode) is "ignored"..
How can I validate (with this metod, not creating Custom Validation) the child entity?
Thanks
The street and zipcode fields in your AuthorRegistrationTypeare not related to your Address entity. What is the reason you did it this way? I would create a separate form type for your Address entity:
namespace WebsiteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class AddressType extends AbstractType
{
protected function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('street', TextType::class)
->add('zipCode', TextType::class)
;
}
}
Then you can embed this in your AuthorRegistrationType:
namespace WebsiteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorRegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("firstname")
->add("lastname")
->add("address", AddressType::class)
->add('save', SubmitType::class);
}
}
xabbuh is right:
I added in the controller the following:
$address = new Address();
$author->setAddress($address);
before
$form = $this->createForm(AuthorRegistrationType::class, $author, array(
'required' => false
));
And in AddressType:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'WebsiteBundle\Entity\Address'
));
}
And it works!
In your entity Author you have an attribute address, which is another entity Address. In my opinion this is called embedded entity, not inherited. However I'm not a native English speaker, so I cannot guarantee.
The issue is in your entity Author:
/**
* #Assert\Valid
*/
protected $address;
This will validate the field itself, but not the fields street and zipCode from the entity Address. To achieve that, you should tell the validation to traverse to the entity Address and look up for the validations in there:
/**
* #Assert\Valid(
* traverse = true
* )
*/
protected $address;
This should do the job.
EDIT:
I couldn't see the relation between these two entities immediately in your examples. Now I see that Author can have only one Address. That means the above suggested code won't do the work for you. It is necessary only if you have a relation where an Author has a field address which is a collection, i.e one author can have many addresses.
I've been struggling with this for a few days now, so I hope somebody can help.
I have a OneToMany relation between a table Resources and my FOSUserBundle provided User entity, so that one user can have posted many resources.
Note: the entities have been edited for brevity, however I don't believe I removed anything important for this question
My Resources entity:
<?php
namespace SFI\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="SFI\MainBundle\Entity\ResourcesRepository")
* #ORM\Table(name="resources")
*/
class Resources {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id()
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
/**
* #ORM\Column(type="string", length=255)
*/
protected $type;
/**
* #ORM\Column(type="string", length=255)
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $description;
/**
* #ORM\Column(type="datetime")
*/
protected $created_time;
/**
* #ORM\ManyToOne(targetEntity="SFI\UserBundle\Entity\User", inversedBy="created_by")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
*/
protected $created_by;
---rest of getters and setters---
/**
* Set created_by
*
* #param \SFI\UserBundle\Entity\User $createdBy
* #return Resources
*/
public function setCreatedBy(\SFI\UserBundle\Entity\User $createdBy = null)
{
$this->created_by = $createdBy;
return $this;
}
/**
* Get created_by
*
* #return \SFI\UserBundle\Entity\User
*/
public function getCreatedBy()
{
return $this->created_by;
}
}
My User entity:
<?php
namespace SFI\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="SFI\UserBundle\Entity\UserRepository")
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
* #Assert\Length(
* min=3,
* max="255",
* minMessage="The name is too short.",
* maxMessage="The name is too long.",
* groups={"Registration", "Profile"}
* )
*/
protected $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return User
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #ORM\OneToMany(targetEntity="SFI\MainBundle\Entity\Resources", mappedBy="created_by")
*/
protected $created_by;
/**
* Add created_by
*
* #param \SFI\MainBundle\Entity\Resources $createdBy
* #return User
*/
public function addCreatedBy(\SFI\MainBundle\Entity\Resources $createdBy)
{
$this->created_by[] = $createdBy;
return $this;
}
/**
* Remove created_by
*
* #param \SFI\MainBundle\Entity\Resources $createdBy
*/
public function removeCreatedBy(\SFI\MainBundle\Entity\Resources $createdBy)
{
$this->created_by->removeElement($createdBy);
}
/**
* Get created_by
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCreatedBy()
{
return $this->created_by;
}
}
I have a couple of Form Types but the main one I'm working with is ResourceType:
<?php
namespace SFI\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ResourceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//Setting date and time
$created_at = new \DateTime('now');
$builder
->add('name', 'text', array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Resource name',
),
))
->add('type', 'choice', array(
'required' => true,
'empty_value' => 'Choose a type',
'choices' => array('w' => 'Website', 'v' => 'Video', 'a' => 'Audio'),
'attr' => array(
'class' => 'form-control',
),
))
->add('link', 'text', array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => 'Add a link',
),
))
->add('description', 'textarea', array(
'required' => true,
'attr' => array(
'class' => 'textarea',
'style' => 'width: 100%; height: 200px; font-size: 14px; line-height: 18px; border: 1px solid #dddddd; padding: 10px;',
'placeholder' => 'Write a description...',
),
))
->add('created_time', 'datetime', array(
'disabled' => true,
'data' => $created_at,
))
->add('save', 'submit', array(
'attr' => array('class' => 'btn btn-primary'),
));
}
public function getName()
{
return 'resource';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SFI\MainBundle\Entity\Resources',
));
}
}
My controller for the Resource form part:
public function resourcesAction(Request $request)
{
$breadcrumbs = $this->get("white_october_breadcrumbs");
$breadcrumbs->addItem("SFI Portalen", $this->get("router")->generate("sfi_static_index"));
$breadcrumbs->addItem("Teacher Dashboard", $this->get("router")->generate("sfi_teacher_dashboard"));
$breadcrumbs->addItem("Resources");
$em = $this->getDoctrine()->getManager();
$resource = new Resources();
$newResourceForm = $this->createForm(new ResourceType(), $resource);
$newResourceForm->handleRequest($request);
if ($newResourceForm->isValid()) {
$session = $this->getRequest()->getSession();
//$session->getFlashBag()->add('notice', 'Form processed');
$session->getFlashBag()->add('notice', 'Form processed, testing, no persist');
//$em->persist($resource);
//$em->flush();
return $this->redirect($this->generateUrl('sfi_teacher_resources'));
}
return $this->render('SFIMainBundle:Teacher:resources.html.twig', array(
'form' => $newResourceForm->createView(),
));
}
What I need to do is relate these entities when a new Resource is created, to "assign" it to the logged in user via the relation in the tables.
I've looked into embedded forms and other suggestions on Symfony's documentation, but I can't wrap my head around how to apply what I read to this specific example. I have followed the example with the tasks and the tags and the categories and all that, but I can't understand how it applies to my situation, having the User entity in another bundle, and a User form type that relates the user to their school and just adds an extra full name field, while inheriting FOSUserBundle's original form type.
I also have a funny feeling that I haven't tackled or "layed out" the relationship correctly in my code so as to make it easier to understand which objects relate.
Any help would be much appreciated! Also please let me know if you require other code or further information.
You should just be able to set the user manually before persisting the Resources entity.
if ($newResourceForm->isValid()) {
...
$resource->setCreatedBy($this->getUser());
$em->persist($resource);
$em->flush();
...
}
Symfony makes use of the $this->getUser() shortcut in controllers (see http://symfony.com/doc/current/book/security.html#retrieving-the-user-object). I'm assuming you only allow this form when the user is logged in.
Also if you are using Doctrine I would recommend using it to generate your entities and getters/setters for you, as it will generate sensible names that coincide with Symfony conventions. For instance, your entity would end up being named 'Resource' vs. 'Resources', and your variables would be camelCased ($createdBy vs. $created_by).
OK so based on Jason's suggestion, and the previous one by sjagr, I have renamed a few things for consistency and better understand, and I have written the following, which works correctly!
Thank you all for your help!
public function resourcesAction(Request $request)
{
$breadcrumbs = $this->get("white_october_breadcrumbs");
$breadcrumbs->addItem("SFI Portalen", $this->get("router")->generate("sfi_static_index"));
$breadcrumbs->addItem("Teacher Dashboard", $this->get("router")->generate("sfi_teacher_dashboard"));
$breadcrumbs->addItem("Resources");
$em = $this->getDoctrine()->getManager();
$resource = new Resource();
$createdBy = $em->getRepository('SFIUserBundle:User')->find($this->getUser()->getId());
$newResourceForm = $this->createForm(new ResourceType(), $resource);
$newResourceForm->handleRequest($request);
if ($newResourceForm->isValid()) {
//Getting current date and time
$created_time = new \DateTime('now');
$resource->setCreatedTime($created_time);
$resource->setCreatedBy($createdBy);
$session = $this->getRequest()->getSession();
$session->getFlashBag()->add('notice', 'Form processed');
$em->persist($resource);
$em->flush();
return $this->redirect($this->generateUrl('sfi_teacher_resources'));
}
return $this->render('SFIMainBundle:Teacher:resources.html.twig', array(
'form' => $newResourceForm->createView(),
));
}
All I needed was to pass the current user's entire object in setCreatedBy and the relation works correctly.
Thank you all again!
I want to handle all validations with #Assert so I'm using Models for my web forms (Form Type) which are not mapped to database. The question I have is, is it an acceptable practise in Symfony world?
I know that one disadvantage of this way is not being able to automatically generate setters and getters. I read up on it but didn't get a clear picture so that's why I'm asking.
A rough example:
LoginType.php
namespace User\RecordBundle\Resources\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LoginType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('username', 'text', array('label' => 'Username'))
->add('button', 'submit', array('label' => 'Login'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'User\RecordBundle\Entity\UserEntity'));
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'login';
}
}
LoginModel.php
namespace User\RecordBundle\Resources\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class LoginModel
* Mapped to Login form
*
* #package User\RecordBundle\Resources\Form\Model
*/
class LoginModel
{
/**
* #Assert\NotBlank(message = "The Username field should not be blank.")
*
* #var string $username
*/
protected $username;
/**
* #return string $username
*/
public function getUsername()
{
return $this->username;
}
/**
* #param string $username
* #return $this
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
}
This case: your FormType is not related to any Entity, must be rare in a well planned application. So rarely Model with FormType solution can be used, I don't have any objections to it. Remark: Specifically for User handling I recommend you to use friends of symfony created: FOS\UserBundle\FOSUserBundle().
You said that you're new in Symfony, so I summarized here the general practice of making a Form, which is related to an Entity and user will be available to fill some part of it.
class code:
class Entity
{
/**
* #Assert\NotBlank(message = "The data is empty.")
*/
private $data;
}
form type code:
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityRepository;
class EntityType extends AbstractType
{
/**
* #var \Doctrine\Common\Persistence\ObjectManager
*/
protected $om;
protected $admin;
protected $edit;
public function __construct($om, $admin = false, $new = false)
{
$this->om = $om;
$this->admin = $admin;
$this->new = $;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// You can show specific parts of the Entity for the admin to fill and reduced group of fields for the common user.
// You can set specific behaviour in case of creation and editing.
// You can add a collection type field, and select specific entities with ObjectManager. To make your form more flexible.
$builder
->add('data', 'text', array(
'label' => 'Data text',
'label_attr' => array(
'class' => 'reqfield'
),
'attr' => array(
'class' => 'css-class-of-the-field'
'...' => $etc,
))
// You don't need to add submit button
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Application\EntityBundle\Entity\Entity'
));
}
public function getName()
{
return 'application_entity_bundle_entity';
}
}
// Actions like: new and edit, can use your formType above.
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).