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.
Related
I learned Symfony 3 and I want create form class to upload File, so i created ImageType, cutom form type to handle image uploaded in NewsType (form with some description and this field):
class ImageType extends AbstractType
{
private $path;
public function __construct($path)
{
$this->path = $path;
}
public function getParent()
{
return FileType::class;
}
public function getName()
{
return 'image';
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'image_name' => ''
));
}
/**
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['image_name'] = $options['image_name'];
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAttribute('image_name', $options['image_name'])
->addModelTransformer(new ImageTransformer($this->path))
}
}
I use ImageTransformer to transform file name like 124324235342.jpg to instance File class. Form work fine when i created and saved date to database, but how manage entity in edit mode ?
public function editAction(Request $request, News $news)
{
$path = $this->getParameter('upload_directory') . $news->getImage();
$image = $news->getImage();
$form = $this->createForm(NewsType::class, $news, ['image_name' => $image]);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$this->get('app.image_uploader')->uploadNews($news, $image);
$em = $this->getDoctrine()->getManager();
$em->persist($news);
$em->flush();
return $this->redirectToRoute('admin_news_index');
}
return $this->render('admin/news/form.html.twig', [
'form' => $form->createView(),
'news' => $news
]);
}
I want handle case to use same form to edit database entity. I populated form, but when user not upload image I don't want change this field and live old value. How accomplish this ?
The simplest method for your code:
In setter of your News type:
function setImage($image) {
if(strlen($image)) {
$this->image = $image;
}
}
This allow you to do not worry about case of lacking image, when user edits other fields. In this case $this->fileName in News will be not overwritten.
Then in service app.image_uploader you should check if file with given name exist. If not then you should not overwrite this file.
But there are some other poroblems:
1) You should validate extension of file.
2) You should use unique names for your files in your server different than names from users. Yo can use both names, your unique for string files in hard drive, and name form user in user interface, but you should not use name of file form user to store file in your hard drive.
I recommend to read about these problems in docs:
https://symfony.com/doc/current/controller/upload_file.html
Or on stack overflow:
Symfony2 file upload step by step
I have created form which requires data transformer, but got myself into single problem: I transform data by exploding string (string should be be exploded to 3 parts), everything works, if I supply correct format string, but otherwise it throws error inside data transformer, because transformation cannot occur if wrong string format is supplied (this is expected behavior).
So the question is is there a way to validate form field for correct string before data transformation? I know that data transformation by default occurs before validation, but maybe there's a way to do it other way around?
I found one solution that might work on this thread: Combine constraints and data transformers ,
but it's looks like rough solution, besides I need to translate validation message, and I would really like to do it using default translation methods for symfony forms (without using translation service)
I thought, and also someone from symfony IRC (Iltar) suggested do it by using events, but I'm not sure how to go about this - how to attach data transformer dynamically to form field? Or maybe there's other way?
It's maybe too late but I eventually manage to do it.
Maybe it will help you.
Here is my FormType:
class PersonType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('mother', 'personSelector', array('personEntity' => $options['personEntity']));
}
}
Here is my customField where are validations:
class PersonSelectorType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$transformer = new PersonByFirstnameAndLastnameTransformer($this->entityManager,$options);
$builder->addModelTransformer($transformer);
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmitForm'));
}
public function onPreSubmitForm(FormEvent $event){
$mother = $event->getData();
$form = $event->getForm();
$options = $form->getConfig()->getOptions();
if (!empty($mother)){
preg_match('#(.*) (.*)#', $mother, $personInformations);
if (count($personInformations) != 3){
$form->addError(new FormError('[Format incorrect] Le format attendu est "Prénom Nom".'));
}else{
$person = $this->entityManager->getRepository($options['personEntity'])->findOneBy(array('firstname' => $personInformations[1],'lastname' =>$personInformations[2]));
if ($person === null) {
$form->addError(new FormError('Il n\'existe pas de person '.$personInformations[1].' '.$personInformations[2].'.'));
}
}
}
}
}
Here is my transformer:
class PersonByFirstnameAndLastnameTransformer implements DataTransformerInterface{
public function reverseTransform($firstnameAndLastname) {
if (empty($firstnameAndLastname)) { return null; }
preg_match('#(.*) (.*)#', $firstnameAndLastname, $personInformations);
$person = $this->entityManager->getRepository($this->options['personEntity'])->findOneBy(array('firstname' =>$personInformations[1],'lastname' =>$personInformations[2]));
if (count($personInformations) == 3){
$person = $this->entityManager->getRepository($this->options['personEntity'])->findOneBy(array('firstname' =>$personInformations[1],'lastname' =>$personInformations[2]));
}
return $person;
}
public function transform($person) {
if ($person === null) { return ''; }
return $person->getFirstname().' '.$person->getLastname();
}
}
Perhaps you could pass the instance of your form to your transformer. If the string doesn't parse correctly, simply add a validation error to the form, like so:
<?php
// src/Acme/MyBundle/Form/DataTransformer/StringTransformer.php
namespace Acme\MyBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\MyBundle\Entity\MyEntity;
use Acme\MyBundle\Entity\AnotherEntity;
use Acme\MyBundle\Type\MyEntityType;
class StringTransformer implements DataTransformerInterface
{
/**
* #var MyEntityType
*/
private $form;
/**
* #param ObjectManager $om
*/
public function __construct(MyEntityType $form)
{
$this->form = $form;
}
/**
* Transforms an object (entity) to a string (number).
*
* #param MyEntity|null $entity
* #return string
*/
public function transform($value)
{
// ...
}
/**
* Transforms a string (number) to an object (entity).
*
* #param string $number
*
* #return MyEntity|null
*
* #throws TransformationFailedException if object (entity) is not found.
*/
public function reverseTransform($value)
{
$collection = new ArrayCollection();
try{
$vals = explode(',', $value);
foreach($vals as $v){
$entity = new AnotherEntity();
$entity->setValue($v);
$collection->add($v);
}
} catch(\Exception $e){
$this->form
->get('my_location')
->addError(new FormError('error message'));
}
return $collection;
}
}
but it's looks like rough solution, besides I need to translate validation message, and I would really like to do it using default translation methods for symfony forms (without using translation service)
I know this question is old, but as any answer has been marked yet as the right solution, I share with you another approach.
emottet solution, using a presubmit listener to validate the data before the model transformer has been applied, is a good approach, based on this discussion.
If you want to keep using Symfony validation system for these errors too, you could use Symfony validator service (ValidatorInterface) in your pre-submit listener and pass it the required constraints, for example:
$builder
->add('whatever1', TextType::class)
->add('whatever2', TextType::class)
;
$builder->get('whatever1')
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
/** #var ConstraintViolationListInterface $errors */
if ($errors = $this->validator->validate($data, new Choice([
'choices' => $allowedChoices,
'message' => 'message.in.validators.locale.xlf'
]))) {
/** #var ConstraintViolationInterface $error */
foreach ($errors as $error) {
$form->addError(new FormError($error->getMessage()));
}
}
})
->addModelTransformer($myTransformer)
;
Kind of redundant, but it works. More info here.
Edited my post to be similar to Symfony Cookbook and added some code.
http://symfony.com/doc/current/cookbook/form/form_collections.html
Note that the Entity/Form code posted in the part is the same as the one in the doc linked above.
I have a "Task" entity, which is linked to a "Tag" entity.To keep it simple, "Task" has a single field "description", and a "tag" has a single field "name".A "Tag" is linked to one "Task", and a "Task" is linked to many "Tags".
Entity:
class Task
{
protected $description;
protected $tags;
public function __construct()
{ $this->tags = new ArrayCollection(); }
public function getDescription()
{ return $this->description;}
public function setDescription($description)
{ $this->description = $description; }
public function getTags()
{ return $this->tags; }
public function setTags(ArrayCollection $tags)
{ $this->tags = $tags; }
}
class Tag
{
public $name;
}
At the moment, I use a Collection of "Tags" in the "Task" form to edit all them at once, as described in Symfony CookBook:
Form:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Tag',
));
}
public function getName()
{
return 'tag';
}
}
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('tags', 'collection', array('type' => new TagType()));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
));
}
public function getName()
{
return 'task';
}
}
But a problem appears when I create a collection of "Tags" larger than a thousand elements. At this time, the form take seconds and seconds to load, and sometimes it crashes due to memory.
$task = new Task();
$tag1 = new Tag();
$tag1->name = 'tag1';
$task->getTags()->add($tag1);
$tag2 = new Tag();
$tag2->name = 'tag2';
$task->getTags()->add($tag2);
//Create a couple thousand more item here ...
//The script crashes here, when the form is being created
$form = $this->createForm(new TaskType(), $task);
The error does not come from Doctrine, which handles very well the whole thing, but from Symfony Form.
Is it a good idea to handle my form using Symfony2 built-in form system (with Collections) in this case, or should I handle it like in the old days, with raw html inputs and server-side validation/saves?
I'm wondering if your issue has nothing to do with the form part of this, but the hydration of the tag objects. If you're asking doctrine to hydrate a ton of objects, it's going to use a big chunk of memory. You may want to look into another method of hydrating the tags, perhaps HYDRATE_ARRAY instead of HYDRATE_OBJECT.
I've a problem displaying form errors with {% form_errors(form) %}. Message displayed is wrong, fields are inverted. Don't know if this is a bug or my bad.
Anyway, this is part of my controller action, where i'm creating a new form type not bound to any object or entity:
class MyController extends Controller
{
public function newAction(Request $request)
{
// NOTE not passing any data to createForm method
$context = $this->get('security.context');
$form = $this->createForm(new SendMailType($context));
if('GET' == $request->getMethod()) // GET
return array('form' => $form->createView());
$form->bindRequest($request); // POST
if(!$form->isValid()) return array('form' => $form->createView());
return new Response('Form is valid');
}
}
Where SendMailType has two fields, recipient and text. recipient can't be blank.
class SendMailType extends AbstractType
{
/**
* #var Symfony\Component\Security\Core\SecurityContext
*/
protected $context;
public function __construct(SecurityContext $context)
{
$this->context = $context;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('recipient', 'text')
->add('text', 'textarea');
}
public function getDefaultOptions(array $options)
{
return array(
'validation_constraint' => new Collection(array(
'recipient' => array(new NotBlank()),
))
);
}
public function getName() { return 'send_mail'; }
}
On POST using {% form_errors(form) %} i'm getting the error:
Fields "text" are invalid.
As you can see this is completely wrong, because the field text has no validation rules specified. On the other hand, the recipient field itself shows the correct error:
This value should not be blank.
Kind of surprised that it worked at all. In any event this will solve the problem:
public function getDefaultOptions(array $options)
{
return array(
'validation_constraint' => new Collection(array(
'allowExtraFields' => true,
'fields' => array('recipient' => array(new NotBlank())),
))
);
}
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');
},
));
}