symfony2 using validation groups in form - forms

I have a Entity with a property:
/**
* #var string $name
*
* #Assert\NotBlank(groups={"foobar"})
* #ORM\Column(name="name", type="string", length=225, nullable=false)
*/
private $name;
The Form:
class MyType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => '...',
'validation_group' => array('foobar'),
);
}
public function getName()
{
...
}
}
In the Controller I bind the Request and call $form->isValid()
But how to define the validation_group?

From inside your FormType class you can define the validation groups associated to that type by setting your default options:
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('group1', 'group2'),
);
}

I had exactly the same problem. I solved it that way ...
// Entity
$employee = new Employee();
// Form creation
$form = $this->createForm(new EmployeeForm(), $employee, array('validation_groups'=>'registration'));
I hope that helps!

When building the form in the controller, add a 'validation_groups' item to the options array:
$form = $this->createFormBuilder($users, array(
'validation_groups' => array('foobar'),
))->add(...)
;
It is described in the forms page of the symfony2 book: http://symfony.com/doc/current/book/forms.html#validation-groups

For me, on symfony 2.1, i solved it by adding 'Default' in validation_groups like:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('Default', 'registration')
));
}

I made a little blog post related to this problem: http://marcjuch.li/blog/2013/04/21/how-to-use-validation-groups-in-symfony/
In this post I’m going to show how to use validation groups in symfony with the example of an order form which should offer the possibility to use separate billing and shipping adresses. This includes 3 steps:
Group validation contstraints for the shipping related form fields together
Determine which validation contraints are applied, depending on the
checkbox value in the submitted form
Copy data from non-shipping fields to shipping fields if checkbox is
not selected

You can also define validation groups dynamically:
// MyCustomType.php
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return array('person');
}
return array('company');
},
));
}

Related

Symfony - How to call the Service Container object from form events?

How to call the Service Container object from form events?
I created a form where the webmaster can create books. I want to create a Sylius Product when an Book is created through this form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'attr' => array('class'=>'block1')
))
->addEventListener(
FormEvents::PRE_SET_DATA,
array($this, 'onPreSetData')
)
;
}
public function onPreSetData(FormEvent $event)
{
$book = $event->getData();
$form = $event->getForm();
$productFactory = $this->container->get('sylius.factory.product');
}
For that purpose, I need to reach 'sylius.factory.product' service as explained there: http://docs.sylius.org/en/latest/book/products/products.html
$productFactory = $this->container->get('sylius.factory.product');
I can access it from any Controller, but unfortunately I can't access it from the BookType class I defined.
Here is the error returned by Symfony when I try to access it from the buildForm() or onPreSetData() function:
Notice: Undefined property: FrontendBundle\Form\BookType::$container
Pass the service, to the form, via the options
Actually this process is very well explained in Symfony Documentation: http://symfony.com/doc/current/form/form_dependencies.html
So I updated the way I was calling my form like that:
$editForm = $this->createForm($form, $item, array( //WITH SYLIUS
'sylius_factory_product' => $this->get('sylius.factory.product')
));
Instead:
$editForm = $this->createForm($form, $item); //WIHOUT SYLIUS
Plus, I adapted the configureOptions function like that:
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'FrontendBundle\Entity\Book'
))
->setRequired('sylius_factory_product')
;
}
Instead:
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'FrontendBundle\Entity\Book'
))
;
}
You can now access your service in the buildForm function like so:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$productFactory = $options['sylius_factory_product'];
//...
}

How to generate a 'collection' form in Symfony, generated by each object within an entity?

Having read through the Symfony forms collection/entity types I'm trying to generate a form that only involves these two attributes of an entity (Person) but uses all instances of Person in the database. The purpose of the form is to provide a activation tickbox for every person on a single page so that multiple status' can be flushed to the database on form submission. Eg, the form should look something like:
☑ John Smith
☐ Jane Doe
☑ ...etc
☑ ...etc
My attempts below are not working as they just return an empty page, although I can see that $allpersons is populated
My Person entity:
class Person
{
/**
* #ORM\COLUMN(type="boolean")
*/
protected $active = true;
/**
* #ORM\COLUMN(type="string", length=100)
*/
protected $fullname ;
/* ...many other attributes... */
}
My Controller:
class DefaultController extends BaseController {
public function ​activePersonsAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$persons = $em->getRepository('AppBundle:Person')->findAll();
$form = $this->createForm(AllPersonsType::class, $persons);
$form->handleRequest($request);
if ($form->isSubmitted() && ($form->isValid())) {
$em = $this->getDoctrine()->getManager();
$persons = $form->getData();
foreach ($persons as $person) {
$em->persist($person);
}
$em->flush();
return $this->redirectToRoute('home_user');
}
return $this->render('activatePersons.html.twig', array(
'page_title' => 'Active Persons',
'form' => $form->createView(),
));
}
}
My FormTypes:
class AllPersonsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('persons', CollectionType::class, array(
'entry_type' => ActivatePersonType::class
));
}
public function getName()
{
return 'person';
}
}
class ActivatePersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('active',CheckboxType::class)
->add('fullname', TextType::class);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Person',
));
}
public function getName()
{
return 'person';
}
}
You pass $persons collection as form data to AllPersonsType, while the type expects that you pass an array with persons key and persons collection as value.
$form = $this->createForm(AllPersonsType::class, ['persons' => $persons]);

Entity form type in Symfony2

I'm trying to make a RESTFUL api and when I process PUT, it always responded me as
{
"code": 500,
"message": "The form's view data is expected to be an instance of class My\\Bundle\\Entity\\Post, but is a(n) array. You can avoid this error by setting the \"data_class\" option to null or by adding a view transformer that transforms a(n) array to an instance of My\\Bundle\\Entity\\Post."
}
Here is how I write the PUT action.
public function putPostAction($postKey, Request $request){
$post = $this->getDoctrine()->getManager()->getRepository('MyBundle:Post')
->findPost($postKey);
$form = $form = $this->createFormBuilder($post,
array('data_class' => 'My\Bundle\Entity\Post'))->getForm();
// or use createForm like
// $form = $this->createForm(new PostType(), $post);
$form->submit($request);
if($form->isValid()){
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
return $post;
}
return array(
"form" => $form
);
}
Speaking entity type, it looks like
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('cityKey')
->add('status')
->add('text')
->add('imageKey')
->add('createTime')
->add('updateTime')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\Bundle\Entity\Post',
'csrf_protection' => false
));
}
/**
* #return string
*/
public function getName()
{
return '';
}
Regarding the error message, I modified the Resolver as
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'csrf_protection' => false
));
}
This time I got another error,
{
"code": 500,
"message": "EntityManager#persist() expects parameter 1 to be an entity object, array given."
}
Any idea? Thanks in advance.
findPost function is returning an array of objects not an object. You should call instead find($id) which returns only one Post object.
So it should be:
$post = $this->getDoctrine()->getManager()
->getRepository('MyBundle:Post')
->find($postKey);

Symfony2 - adding form for oneToMany strange rendering with a "0"

I have a form which works fine. It persists data to two entities (News and Illustration). In fact, it will be possible, in a near future, that the user can add multiple illustrations to the news. BUT, not now :)...
So i've the backend ready for this and when I run the action which calls the formbuilder which builds the form with two FormTypes:
class NewsType extends AbstractType
{
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('date', 'date')
->add('titre', 'text')
->add('contenu', 'textarea')
->add('publication', 'checkbox', array('required' => false))
->add('illustrations', 'collection', array(
'type' => new IllustrationType(),
'allow_add' => false,
'required' => false
));
}
public function getName()
{
return 'News';
}
}
class IllustrationType extends AbstractType
{
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('file', 'file');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Fcbg\NewsBundle\Entity\Illustration',
'cascade_validation' => true,
));
}
public function getName()
{
return 'Illustration';
}
}
The form output renders something like that:
Some idea why I have the "illustrations: 0" ? XD
Thx a lot
You see this because you didn't say how many Illustration you want to display (1? 2? 0? 100?). In doubt, Symfony chose to display none.
Either you allow the user to create one or several new Illustration by setting 'allow_add' => true, (what you don't want to do yet, I understood), or you manually create one (or even several) Illustration in your NewsController.php:
public function addAction()
{
$news = new News();
$Illustration = new Illustration();
$news->addIllustration($illustration);
// create your form and display it
}

How to clear field value with Symfony2 forms

I'm writing my own CAPTCHA class and when the form doesn't validate, I don't want to pre-populate the captcha input with the previous answer, for obvious reasons. I just want to the clear the input before it's rendered.
I've discovered the data option is only for the default value, which is overwritten by what the user enters. I tried the following code:
$form->get('captcha')->setData(null);
.. After the request is bound with the form, but an AlreadyBoundException is thrown. I have actually managed to get it working with:
if (isset($formView->children['captcha'])) {
$formView->children['captcha']->vars['value'] = null;
}
But that just looks wrong, and definitely not up to Symfony standards. I've looked through the other options you can provide when building the form, but I can't see anything of note.
Does anyone have any idea?
By the way, I half expect Symfony2 comes packaged with a CAPTCHA solution, this is mainly a learning exercise while I get used to the framework.
I think you want to handle this form field like Symfony handles a password field: it won't get populated. Let's take a look at the PasswordType:
namespace Symfony\Component\Form\Extension\Core\Type;
class PasswordType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
if ($options['always_empty'] || !$form->isSubmitted()) {
$view->vars['value'] = '';
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'always_empty' => true,
'trim' => false,
));
}
//...
}
So, it's pretty simple: just add $view->vars['value'] = '' in the buildView method of your FormType (i.e. CaptchaType). That means the data of the field is not being cleared, but it won't be passed to the Twig template. Different approach, but the result is the same: the password field stays empty after validation failed.
If you are really lazy, you can use the PasswordType, but since the input of that field will be masked (*****), will that make an annoying captcha field even worse.
Your Form Type maybe look like this:
class CaptchaType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['value'] = '';
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return __NAMESPACE__.'\TextType';
}
/**
* {#inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'captcha';
}
}
Edit:
Just found that CaptchaBundle took the same approach.
There is a funny way to modify the Request before handling it. However I'd look into Stephan's answer as it seems more clean.
Something like so:
public function indexAction(Request $request)
{
$form = $this->createForm(Form::class);
$subData=$request->request->get('form');
$subData['task']=null;
$request->request->set('form',$subData);
$form->handleRequest($request);
if ($form->isValid()) {
//do stuff
}
return $this->render('default/index.html.twig', array(
'form' => $form->createView()
));
}
Get submitted data with the name 'form' as an array of values, change the said value to null, then set the request's value with the new one and have the form handle it.
And a simple form
class Form extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task')
->add('save', SubmitType::class);
}
}
No matter what you type, the data will always be null after submitting the form. Of course, you need to verify the captcha before setting the value to null.
You can pass an incomplete entity to the action called when your control finds form invalid.
public function updateAction(Request $request, $id)
{
$entity = $this->EM()->getRepository('Bundle:Entity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Entity entity.');
}
$form = $this->createForm(new RecommendationType()
,$entity
,array(
'attr' => array(
,'entity' => $entity
)
)
);
$form->bind($request);
if ($form->isValid()) {
$this->EM()->persist($entity);
$this->EM()->flush();
return $this->redirect($this->generateUrl('entity_show'
,array('id' => $id)));
} else {
$entity->setCapthca(Null);
}
return $this->render('Bundle:Entity:edit.html.twig'
,array(
'entity' => $entity
,'form' => $form->createView()
)
);
}
The create action would have similar modification.