Best approach to duplicating a form on request in Symfony2 - php

I have looked all over and have found limited answers and maybe because its so obvious that its never needed to be asked.
I am trying to create a form. The purpose of this form is to allow members to recommend others to the site. You need to fill out first_name, last_name, and email. The problem is I want them to be able to send multiple emails at a time. So I probably would have another button that says "add another" and then it duplicates the form (first_name, last_name, and email).
I have looked at collections and I thought this was a great option because it allows you to allow_add and allows you to prototype but I am not sure how to make it work. I also considered embedding but I am not looking for an immediate response or validation so I don't think this is the solution.
I know javascript is going to be utilized to make this work.
What I have so far... Let me know if I am close.
This is where it is called.
public function referralFormAction(Request $request, $hash)
{
if (isset($hash)) {
$referral = new Referrals();
$referralForm = $this->createFormBuilder($referral)
//I know that 'email' needs to be replaced with something different, an object?
->add('email', 'collection', array(
'required' => false,
'allow_add' => true,
'prototype' => true,
//Not sure if I can set type like this.
'type' => new ReferralType(),
))
->getForm();
$referralForm->handleRequest($request);
if ($referralForm->isValid()) {
//do something
}
return $this->render('FuelFormBundle:Default:referralForm.html.twig', array('referralForm' => $referralForm->createView()));
} else {
throw $this->createNotFoundException('The product does not exist');
}
}
This is the Type
class ReferralType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('first_name', 'text', array());
$builder->add('last_name', 'text', array());
$builder->add('email', 'email', array());
$builder->add('Send', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Fuel\FormBundle\Entity\Referrals',
));
}
public function getName()
{
return 'referral';
}

This documentation about How to Embed a Collection of Forms will teach you how to do exactly what you need; it is pretty self-explanatory and very well explained.

The answer to this is pretty sad. What you see above in the question is correct however I didn't populate any data in the form so there is nothing to show. So moral of the story is you either need to populate data so it will show something or add the JavaScript to finish the function so it will populate the text boxes.
I did find this line initially in the Symfony2 Reference for Collections
In both cases, no input fields would render unless your emails data
array already contained some emails.
But I disregarded it because I understood it to mean something different at the time.
I did find this which was a huge help https://groups.google.com/forum/#!topic/symfony2/DjwwzOfUIuQ
And when I couldn't figure out my problem I messaged him and he responded with another helpful tidbit of info.
$form = $this->createFormBuilder()
->add('foo', 'foo_type', array(
'data_class' => 'Foo',
)
->add('bar', 'bar_type', array(
'data_class' => 'Bar',
)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$foo = $form->get('foo')->getData();
$bar = $form->get('bar')->getData();
// do stuff
}
Big thanks to Bernhard for the help!

Related

Checking which form field value has changed Symfony 3

I need to check inside the FormType which field has changed. Is there any method to do it? I've searched for a while, then tried to get edited entities field in few ways (with form events too) to catch the edited fields, but no simple result.
Is there any way to do it easy, or I need to be more creative in making such thing? The best it would be, if I can get an example with entity type, but any clue would be great.
P.S. I cant do it on client-side - I must do it on server side for particular reason.
Done with this:
https://stackoverflow.com/a/33923626/8732955
Suppose we want to check the "status" field in our ImportantObject, code needs to look like that
if($form->isSubmitted() && $form->isValid())
{
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeSet = $uow->getEntityChangeSet($importantObject);
if(isset($changeSet['status'])){
//do something with that knowledge
}
}
Old post but interesting question.
How I solved it to check a relation between entities but it also works for a single field value. Easier than dealing with doctrine listeners.
Imagine you have a user with multiple tags and a form with checkboxes to add or remove tags
In the controller, create a new variable that contains the value to monitor :
$oldValue = '';
foreach ( $user->getTags() as $tag )
$oldValue .= $tag->getId().";";
Give it to the formType as an option
$form = $this->get('form.factory')->create(userType::class, $user,
['oldValue' => $oldValue ]);
In the formType, create an hidden field
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
public function buildForm(FormBuilderInterface $builder, array $options)
....
$oldValue = $options['oldValue'];
$builder
->add('oldValue', HiddenType::class, [
'data' => $oldValue,
'mapped' => false,
]);
...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'pathToEntity',
'oldValue' => null,
));
}
Back in the controller get your old field value :
if ( $form->isSubmitted() && $form->isValid() )
{
// Stuff
$em->flush();
// Check changes :
$oldValue = $form->get('oldValue')->getData();
$oldValues = explode(';', $oldValue);
$newValues = $user->getTags();
Compare arrays and finish the stuff...

Symfony 3: Embedded form whole or nothing validation error display

I have the exactly same problem as described here: Optional embed form in Symfony 2:
I have a form for the entity Person that has an embedded form for the entity Phone. The user can leave all fields of Phone empty and the form will be valid. But if a single field of Phone was filled-in, all Phone-fields must be valid.
During my first approach, I simply annotated the Phone property of Person with #Assert\Valid() without #Assert\NotNull(). That works fine only when entering a new Person. When editing an existing Person and the Phone property was already filled-in, the deletion of all Phone fields (which should be valid) does not result into a valid submit.
The validation of this solution with a validation callback function works with some modifications for Symfony 3:
/**
*
* #Assert\Callback()
*/
public function validatePhone(ExecutionContextInterface $context)
{
if (/* Fields are not empty */)
{
$context->getValidator()->inContext($context)->validate($this->phone);
}
}
But after submitting the form, validation errors for the phone fields are not shown on the page. I can only see them in the debug toolbar.
Maybe, this solution needs to be modified somehow, to let the errors be displayed after form submission?
But maybe even my first approach might work, if it is somehow possible to set the property Phone of an existing Person object to null, if all form fields of Phone have been cleared?
try to use cascade_validation (be carefull removed from symfony3) and error_bubbling in your formType class
->add('phone', 'collection', array(
'type' => 'text',
'allow_add' => true,
'error_bubbling' => false,
'cascade_validation' => true,
));
Found the answer myself:
The modification of the solution from the other post that I made in order try to let it work for Symfony 3.3 needs to be differently:
/**
*
* #Assert\Callback()
*/
public function validatePhone(ExecutionContextInterface $context)
{
if (/* Fields are not empty */)
{
$context->getValidator()->validate($this->phone);
}
}
I needed a solution to an non-class form, what I've done is:
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('status', TextType::class)
->add('invoice', SomeSubForm::class, [
'required' => false
]);
;
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if (!isset($data['invoice'])) {
$event->getForm()->add('invoice', HiddenType::class, [
'required' => false
]);
}
});
}
SubForm has NotBlank asserts on properties.

Add a required form field based on submitted data in Symfony2

I have a form with a status select. If a certain status is selected and the form is submitted it should reload and require an additional field.
I have read Dynamic generation for submitted Forms and almost every other post on the internet and about this topic and tried different event combinations (and got different errors) but I still struggle to make this to work correctly.
This is what I have so far:
FormType
private function addProcessAfterField(FormInterface $form)
{
$form->add('processAfterDate', 'date', array('required' => true));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', 'entity', array(
'class' => 'Acme\Bundle\ApplicationBundle\Entity\LeadStatusCode',
'choices' => $this->allowedTypes
));
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$form = $event->getForm();
$data = $event->getData();
if ($data->getStatus()->getId() == LeadStatusCode::INTERESTED_LATER) {
$this->addProcessAfterField($form);
}
});
$builder->get('status')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event){
$data = $event->getData();
if ($data == LeadStatusCode::INTERESTED_LATER && !$event->getForm()->getParent()->getData()->getProcessAfterDate()) {
$this->addProcessAfterField($event->getForm()->getParent());
}
});
$builder->add('comment', 'textarea', array('mapped' => false));
$builder->add('Update', 'submit');
}
Error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Proxies\__CG__\Acme\Bundle\ApplicationBundle\Entity\Lead::setProcessAfterDate() must be an instance of DateTime, null given, called in /var/www/application.dev/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 360 and defined in /var/www/application.dev/app/cache/dev/doctrine/orm/Proxies/__CG__AcmeBundleApplicationBundleEntityLead.php line 447
As already mentioned I tried different event combinations, one was almost working but then the date was never persisted to the entity so I added the \DateTime type-hint to the setProcessAfterDate() method. I am not sure if I don`t understand the event system correctly or if the error lies somewhere else.
Well, it might not be the best way to solve it, but to make long story short:
$form->handleRequest($request);
if($form->isValid()) // check if the basic version of the form is ok
{
$form = $this->createForm(new XXXXForm(), $form->getData()); // you recreate the form with the data that was submitted, so you rebuild the form with new data
if($form->isValid())
{
// ok
}
// not ok
}
Then inside buildForm function, you base the "required" attribute value of fields based on what you want:
'required' => $this->getCheckRequired($options)
private function getCheckRequired($options) // checks whether field should be required based on data bound to the form
{
if($options && isset($options['data'])
{
switch $options['data']->getStatus():
// whatever
;
}
return false;
}
As I said, this is not the best solution, and it doesn't fix your approach, but rather proposes a different one, but it does the job

Symfony2 multiple choice is not validating

I'm creating a simple list of shop carts with users and products assigned to it.
My form for new cart looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cartName', 'text', array('label' =>'Nazwa koszyka:'))
->add('user', new UserForm(), array('data_class' => 'Zadanie\Bundle\Entity\User', 'label' => false))
->add('products','entity', array('label' => 'Wybierz produkty:', 'class' =>'Zadanie\Bundle\Entity\Product' , 'multiple' => true, 'required' => true))
->add('Zapisz', 'submit');
}
and everything is great except that i can submit the form even without selecting any product.
By far i just added "required" by jquery, but i don't like that. Can somebody explain to me why it is not working properly? :P
EDIT:
Here is the code from controller:
/**
* #Route("/cart/edit/{id}",name="_edit_cart")
* #Template()
*/
public function editAction($id, Request $request)
{
$cart = $this->getDoctrine()->getRepository('ZadanieBundle:Cart')->find($id);
if($cart == null)
{
throw $this->createNotFoundException('Nie znaleziono rekordu');
}
$form = $this->createForm(new CartForm(), $cart);
$form->handleRequest($request);
if($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$data = $form->getData();
$em->persist($data);
$em->flush();
$this->get('session')->getFlashBag()->set('message', 'Koszyk zaktualizowano.');
return $this->redirect($this->generateUrl('_main_carts'));
}
return array('form' => $form->createView());
}
SECOND EDIT:
i found a SOLUTION, ( don't know if the best, but works :) ) so if anybody encounters that:
You have to create your validation file ( validation.yml for example) under YourBundle/Resources/config, in which you have to put information about properties. In my case it was:
Zadanie\Bundle\Entity\Cart:
properties:
cartname:
- NotBlank: ~
user:
- NotBlank: ~
constraints:
- Callback:
methods:
- [Zadanie\Bundle\Form\MyValidator, isUserValid]
and then i created MyValidator:
namespace Zadanie\Bundle\Form;
use Symfony\Component\Validator\ExecutionContextInterface;
use Zadanie\Bundle\Entity\Cart;
class MyValidator {
public static function isUserValid(Cart $cart, ExecutionContextInterface $context)
{
if(!$cart->getUser()->getName())
$context->addViolationAt('name', 'Proszę podać imię.', array(), null);
if(!$cart->getUser()->getSurname())
$context->addViolationAt('surname', 'Proszę podać nazwisko.', array(), null);
if(count($cart->getProducts()) == 0)
$context->addViolationAt('products', 'Proszę wybrać produkt.', array(), null);
}
}
#Mati, regarding your first question about how the required option works, this option only sets the required attribute in HTML5 so does not do anything server side. From the documentation
As of HTML5, many browsers can natively enforce certain validation
constraints on the client side. The most common validation is
activated by rendering a required attribute on fields that are
required. For browsers that support HTML5, this will result in a
native browser message being displayed if the user tries to submit the
form with that field blank.
Regarding your solution, that will certainly work though you may want to consider relying on the built-in validators. I'm fairly sure the product count constraint can use the built-in Count Collection constraint.

Symfony2 invalid dynamic form with no error

I got a problem with a dynamic form on symfony2. I'm trying to generate some fields for a submitted form. In others words, the user enters some values, submits the form, and according to these values, my dynamics fields are added to this same form (which is, obviously, displayed a second time). To do that, I used this example from the cookbook : http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data
So, here is my FormationType class
class FormationType extends AbstractType
{
private $em;
private $context;
public function __construct($em, $context) {
$this->em = $em;
$this->context = $context;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('date')
->add('type', 'choice', array(
'mapped' => false,
'choices' => Formationlist::getTypeTypes(false),
'empty_value' => false,
))
->add('cost')
->add('travelCost')
->add('maximum')
->add('location')
->add('schedule')
;
$formModifier = function(FormInterface $form, $type) {
$formationList = $this->em->getRepository('CoreBundle:FormationList')->findBy(array("year" => 1, "type" => $type));
$form->add('formationList', 'entity', array(
'label'=> 'Titre formation',
'choices' => $formationList,
'class' => 'CoreBundle:FormationList',
'property' => 'title',)
);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($formModifier) {
$data = $event->getForm();
$type = $data->get('type')->getData();
$formModifier($event->getForm(), $type);
}
);
$builder->get('type')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($formModifier) {
$type = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $type);
}
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'EXAMPLE\CoreBundle\Entity\Formation'
));
}
public function getName()
{
return 'example_corebundle_formationtype';
}
}
So, the two addEventListener work pretty well. The first time my form is displayed, the field in formModifier is not loaded, as expected. My controller class is the following one :
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$contextSrv = $this->get('example.service.context');
$context = $contextSrv->getContext();
$entity = new Formation();
$form = $this->createForm(new FormationType($em, $context), $entity);
$form->bind($request);
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('formation_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
Since one of my dynamic field can't be null, the first time the form is submitted, it can't be valid. So, the FormationType is loaded a second time. That means, if the field "type" was filled, my formModifier() function can load the dynamic field (formationList). Until there, everything works pretty well, and I got my new field.
But, after a second "submit" on the form...nothing happen. The page is just reloaded, and no errors are displayed.
I checked the form content with
var_dump($request->request->get('example_corebundle_formationtype'));
-> Every fields (including the dynamic one) are filled with valid values.
I also try this :
foreach($form->all() as $item) {
echo $item->getName();
var_dump($item->getErrors());
}
-> These lines don't show any error. But, the form is never valid.
var_dump($form->isValid());
-> It returns false. So the form is invalid.
Finally, if I remove the whole dynamic part, my form works.
I don't understand what's wrong. There is no errors displayed by the form, and the csrf token seems right. Did I miss something ? Thanks for your help.
I know this is a bit outdated but comes up quite high on Google.
The getErrors() metod returns only Form's global errors not error messages for the underlying fields, you need either getErrors(true) or more sophisticated method when using embeded forms in a form. Please see: https://knpuniversity.com/blog/symfony-debugging-form-errors for more information.
There is probably a validation error lying somewhere in your form.
Instead of your complicated calls to Form::getErrors() - which is not fully recursive, as the errors of any field deeper than the 2nd level will not be displayed - you should use Form::getErrorsAsString().
This is a debug method created by the Symfony guys for developers such as you, trying to understand where a validation error could lie in complex forms.
If no error is displayed although it should, this may be a form theming error. When creating a custom form theme, it is possible that a developper overrides or forgets to display the error block of a field.
Another possible source of the problem lies is the general display of the form. If you display your form using {{ form_widget(form) }}, then any error that bubbles to the top form will never be displayed. Make then sure that you use {{ form_row(form) }} instead.
I also encountered this problem a few times.
In my case I posted data in JSON format, so I had to do a request listener with a high priority which transforms json data into normal POST data, which is available in $request->request.
One scenario where the $form is invalid and there is no errors in also when the post data is empty, try to make a dump of $request->request->all() to see if you have the data.

Categories