Symfony2 Forms prevent changing entity's field - php

I'm looking for a way to bind entity to a form, but I need a specific field of it to be mapped (displayed), but not modified by the user submitting the form.
What I have checked so far:
Using disabled attribute - it's not being submitted and entity's field is set to null
Using HTML readonly attribute - it can still be modified by manipulating HTML
Using read_only field option - same as above
My field is a checkbox, but I'd prefer a generic solution for that kind of fields, because I'll have more of them in the future.
I would also like to avoid additional query.
Any ideas?

The 1st & 3th solutions are not good.
I had the same issue a while ago. This is what I did to solve it:
I used the 2nd solution, and since you have the entity in the application, you can simply override any value the user had changed by manipulating the HTML (whitch is a risk that should be handled).
or, you could draw a HTML checkbox that is not mapped (with random id and name), and it will be not mapped to you entity.

I think I have found the right solution to that problem. It's not very flexible, but converting it to extension should make it fairly easy to deal with. The basic version I created just now can be found here. A little explanation:
Make the field with option mapped set to false
create event handler function (setUnmappedField)
attach EventListener to both PRE_SET_DATA and SUBMIT events.
PRE_SET_DATA makes sure the field has a correct value when initially rendering the form.
SUBMIT makes sure the field's value is reverted back to the initial value even if user changed it before submitting the form.
Disabled and readonly attributes are here for UI/UX, it will work without these attributes as well.
Feel free to use it to build a form extension if you need one. I will probably build an extension to make it more flexible later, once I need it.
#EDIT
I just realised this can be done easier - leaving the field mapped! updated gist here. No need for PRE_SET_DATA listener and mapped=false

I suggest you do a combination of 1 and 2 . See below for sample
->add('trainings', 'entity', array(
'label'=> 'Upcoming training(s)',
'choice_label' => 'CompleteTitle',
'multiple' => 'true',
'expanded' => 'true',
'by_reference'=>false,
'class' => 'TrainingBundle:Trainings' ,
'query_builder' => function (EntityRepository $er) use ($options) {
return $er->getTrainingByParentId($options['parent_id']);
},
)
)
->add('PastTrainings', 'entity', array(
'label'=> 'Past trainings',
'choice_label' => 'CompleteTitle',
'multiple' => 'true','expanded' => 'false',
'disabled' => 'true',
'class' => 'TrainingBundle:Training' ,'mapped'=>false,
'query_builder' => function (EntityRepository $er) use ($options) {
return $er->getTrainingByParentId($options['parent_id']);
},
)

Related

Selected option not set in EntityType::class formfield symfony3

I'm very new at working with a framework like symfony, but I must say that i am turning in to a fan quite fast. Unfortunately i'm totally stuck for the last few days in a row.
The Context
I'm trying to refactor my old-school php CMS to the symfony3 framework. A user can manage his pages by adding en editing them. Adding a new page to the database works like a charm. Editing a page is also working fine, except one small part of it. The form prefills al fields like it should and a post will edit the entity.
But... for some reason the selectbox won't pre-select with the selected templatetype. This list is build with use of the EntityType::class and fetches the available data from the database by using AppBundle:Templates.
A piece of the code i use for loading and building the form:
// Fetch selected page
$page = $this->getDoctrine()->getRepository('AppBundle:PageMan')->find($id);
// Generate form
$form = $this->createFormBuilder($page)
->add('templateId', EntityType::class, array(
'label' => 'Template type',
'class' => 'AppBundle:Templates',
'placeholder' => 'Kies een template',
'choice_label' => 'name',
'choice_value' => 'id',
'multiple' => false,
'expanded' => false,
'required' => true,
))->getForm();
The last few days i have tried every possibility i could think of and google. I've also checked the following:
templateId is filled with a value after loading the repository. When I change the field to a plain textfield, the value is shown.
AppBundle:Templates returns unique values (only two entity's in the database with id 1 and 2)
The selected="selected" attribute is set when posting (and not redirecting away)
Removing cashe doesn't solve the problem
At his moment i'm out of possible solutions, hope some one can help. It could be something so simple, but i'm just not seeing it anymore.
--Update--
Just found the following inconsistency in a vardump of the generated form. In the 'name'-object you see modelData, normData and viewData pre-filled. But 'templateId' misses content in viewData.
screenshot vardump formbuilder
In the documentation of symfony it states the folowing:
View Data - This is the format that's used to fill in the form fields themselves. It's also the format in which the user will submit the data. When you call Form::submit($data), the $data is in the "view" data format. Source
This could be a possible leed to a solution.
--Update 2--
Just hardcoded $this->viewData in \vendor\symfony\symfony\src\Symfony\Component\Form\Form.php to a hardcoded value that is present in the selectbox. This adds a value to the empty setting as mentioned in the the update above. Now the default value gets selected as it should. I'm going to follow this variable in the code. Hope to find the reason why it doesnt get prefilled.
Actually choice_value is callable, so you can make a function:
'choice_value' => function($page){
return strval( $page->getId() );
}
I think that might work in this case. Please try it.
Finally fixed my problem. Don't know if it's the official way, but in my case it works like a charm.
In my EntityType setting, i'm now passing 'data' => $page, as an extra object to the formbuilder. This results in viewData being filled with a value and that was what i needed to get a pre-selectid selectbox on pageload.
Here the final snippet:
->add('templateId', EntityType::class, array(
'label' => 'Template type',
'class' => TemplateMan::class,
'placeholder' => 'Kies een template',
'choice_label' => 'name',
'choice_value' => 'templateId',
'data' => $page,
'data_class' => null,
'multiple' => false,
'expanded' => false,
'required' => true,
))

symfony 2 form : I would like to use custom field name attribute or manipulate child in form

I'm trying to use Symfony 2 forms to easily use Doctrine findBy API in my Controllers.
Actually, I would like to write this code in my Controller:
$repository->findBy(
$request->get('filters'),
$request->get('orderBy'),
$request->get('limit'),
$request->get('offset')
);
For that, my URL should look like this!
/session?filters[user]=4
/session?filters[user]=4&filters[year]=2014&orderBy=date&limit=10
To be able to give this functionality to my user, a solution I would love to use forms :
Solution n°1
$this->createFormBuilder()
->add('filters', 'array')
->add('user', 'integer')
->add('year', 'integer')
->add('orderBy')
->add('limit')
->add('offset')
;
But this solution or equivalent does not exist. (the type array does not exist)
Solution n°2
$this->createFormBuilder()
->add('filters[user]', 'integer')
->add('filters[year]', 'integer')
->add('orderBy')
->add('limit')
->add('offset')
;
The usage of '[' and ']' chars are not allowed and produce error.
An other solution is to use custom field name, actually the first solution (without the unexistant type "array") will produce this HTML:
<input type="text" id="form_filters" name="form[filters]" required="required" class="form-control">
Which will not suits the URL requirements. Do someone knows how to use custom name attribute?
It would also work If I had the opportunity to change the attribute name to form[filters][something] or even better filters[something] .
I'm not sure as I never did that, but, why don't you try with collection type ?
$this->createFormBuilder()
->add('filters', 'collection', array(
'choices' => array(
'user',
'year',
)
))
[...]
However I'm not sure you want pass parameters via get: if you have a form and url isn't callable from "outside" of your application, POST is far better

Add placeholder attribute to date input in Zend 2

I would like to add a placeholder to my date input to show visitors using firefox which format the date must have.
Using Zend Framework 2, I want to be able to generate an input equivalent to :
<input type="date" placeholder="yyyy-mm-dd">
inside my Form class.
Here is the Form class I have right now :
<?php
namespace MyModule\Form;
use Zend\Form\Form;
class MyModuleForm extends Form {
public function __construct($name = null) {
parent::__construct('myModule');
$this->add(array(
'name' => 'TheDate',
'type' => 'date',
'attributes' => array(
'placeholder' => 'yyyy-mm-dd',
'class' => 'form-control',
)
));
}
}
?>
But Zend seems to skip the "placeholder" attribute and generates only :
<input class="form-control" type="date" value="" name="TheDate">
In the worst case scenario, I know I can also definse the "value" attribute instead (though I would like not to, just in case the user doesn't pay attention and doesn't change it), or add the placeholder manually using javascript. But is there a more elegant way to do what I want to achieve through the Form class of Zend ?
Edit : I found the answer I was looking for since the beginning thank to this post.
The solution is to go into the AbstractHelper.php present in the namespace Zend\Form\View\Helper.
There, there is a protected array of attributes that should be valid globally.
Adding
'placeholder' => true,
to $validGlobalAttributes fixed everything and was what I was a good enough answer to me.
An even better way would be to change this variable by inheritance instead of modifying it into the framework, but I don't have enough time for it right now.
You can't have placeholder on your date input due to the UI, which is triggered onfocus. Therefore you have to hack/fake it.
Change the 'type' => 'date', to 'type' => 'text',. You will see the placeholder now. This Javascript will help you triggering the date field on focus, and keep the placeholder.
$('.form-control').on('focus', function() {
$(this).attr('type', 'date') }
).on('blur', function() {
$(this).attr('type'), 'text') }
)
I guess, Zend FormDate does not support placeholder. You can check this on repo. For example,
Every form element has a Helper in (Zend\Form\View\Helper) and some of them has validTagAttributes as a class element. You can check FormEmail view helper class for this.
But, FormDate dont have a validTagAttributes. So, your invalid attributes ignored in prepareAttributes() method.
I think, if you want to palceholder on your date form element, you can create a custom FormDate element view helper and use it.

Zend_Form and Float number localization

I'm using Zend_Form for handling a form and I have a problem with localization.
I use following field declaration:
$this->addElement('text', 'area', array(
'label' => 'Area:',
'required' => true,
'filters' => array('StringTrim', 'NormalizedToLocalized')
));
The problem is, I use pl_PL locale and in this locale the decimal point separator is "," not ".". But database (MySQL) stores float with "." separator.
I added NormalizedToLocalized to convert e.g. 40.12 into 40,12 in my html form.
But when passing 40,12 in POST request I want Zend_Form to automatically convert back value 40,12 into 40.12 so that it can be passed to DB (I collect values from form using $form->getValues() method). By now it renders 40,12 successfully but in $form->getValues() I get localized, not not normalized value.
So my question is, whether it is possible to create different filter for rendering and getting value of field in Zend Framework.
Thanks in advance for your help!
The filters are not applied until the form is submitted so 'NormalizedToLocalized' will not have any effect until the form is posted. So if you want decimal points and not commas in your data for storage remove the filter (or use 'LocalizedToNormalized') from the form and apply the filter using Zend_Filter for any data you need to display.
If this field always expects a float type you can use a validator to enforce the value and then use the filter to enforce formating for the database.
$this->addElement('text', 'area', array(
'label' => 'Area:',
'required' => true,
'validators' => array('Float'),
'filters' => array('StringTrim', 'LocalizedToNormalized')
));
I'm not familiar with Zend Framework but looking on filter name, shouldn't it be 'LocalizedToNormalized' ?

Symfony form not honoring set defaults

I have a pretty simple form with some fields from a doctrine model.
$this->widgetSchema['fields'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'FieldModel',
'expanded' => true,
'multiple' => true,));
$this->validatorSchema['fields'] = new sfValidatorDoctrineChoice(array(
'model' => 'FieldModel',
'multiple' => true,));
The fields are rendered in the form as checkboxes and I'm able to check and save correctly. This 'fields'-field is converted to a json-structure and saved to the database as text. So far so good.
NOTE: The field 'fields' is stored as TEXT in the database, but the user should be able to select values from a list of checkboxes.
The problem arise when I want to have some of the checkboxes checked by default.
I tried to do:
$this->setDefault('fields', array('key1','key2','key3'));
Where 'keyX' correspond to the actual value of the primary key (string) for Field in the database.
If I do a
$this->getDefault('fields');
I get back exactly what I put in previously.
However, symfony is not outputing any of the checkboxes as checked. I have even tried to remove both the 'expanded' and 'multiple' options to the choice-widget so I get a simple SELECT-box and the provide only one value as default selected.
Setting default values for other widgets (text-inputs, choice, etc) work.
Btw; The Field-model is i18n. Don't know if that matters here, since both storing / retrieving works as expected.
Also; the form is rendered as part of another form by means of include_partial(). Can that sabotage anything? In the 'parent' form class:
$this->embedRelation('TheRelationThatBugsMe');
And then in the _form.php for the 'parent':
include_partial('the_relation_that_bugs_me/form', array('form' => $form['TheRelationThatBugsMe']));
Does anyone have an idea where I might have gone wrong, or at least can give me some pointers as to where I should start digging?
[UPDATE]
If I create a new field in the form 'fields2' (that does not exist as a field in the database) and use the exact same code to create widget, validator and set defaults, then the defaults are rendered correctly. How come it doesn't work setting defaults for a field mapped to a column in the database?
If you're calling setDefault before updateDefaultFromObject gets called in sfDoctrineForm, then the object's values will override form defaults if the object exists. updateDefaultsFromObject contains the relevant logic. You'll have to call setDefault later, or override the method.

Categories