I use a certain form in several places. In one of them I need to ignore a form element which I set programmatically after the validation.
Because it's just an exception I don't want to create a new form. So I thought, I just remove this element in the controller like:
$myForm->remove('myElement');
The problem is that the form now won't validate. I don't get any errors but the $myForm->isValid() just returns an empty value.
Any ideas what I might be doing wrong?
Ok, finally I found a solution! You can define a ValidationGroup which allows you to set the attributes you'd like to validate. The others are not validated:
$form->setValidationGroup('name', 'email', 'subject', 'message');
$form->setData($data);
if ($form->isValid()) {
...
The first thing I thought about was to remove the validator from your myElement's ValidatorChain. You could get it within the controller with:
$form->getInputFilter()->get( 'myElement' )->getValidatorChain()
It seems like you can't remove from the ValidatorChain, just add. Check this post. Matthew Weier O'Phinney, from Zend, explains why it can't be done and a possible solution for your scenario.
The way I solve this problem, is checking the 'remove condition' when I create the validator in the FormFilter class. If you use annotations I think it doesn't works for you, so Matthew suggestions is the one you should use.
Or you could try the one in this post from #Stoyan Dimov: define two forms, a kind of BasicForm and ExtendedForm. The first one have all the common form elements, the second one is an extended one of the other with the rest of fields. Depending on your condition you could use one or another.
In class ValidatorChain implements Countable, ValidatorInterface, add a new method:
public function remove($name){
foreach ($this->validators as $key => $element) {
$validator = $element['instance'];
if($validator instanceof $name){
unset($this->validators[$key]);
break;
}
}
}
Use like this:
$form->getInputFilter()->get("xxxxx")->getValidatorChain()->remove('xxxxxx');
There must be a validator defined for this particular element that you are trying to remove.
In your controller where you are adding new elements to form, there must be addValidator calling like:
$element->addValidator('alnum');
That is actually causing validation to be failed. So you have removed the element from form but you still have validation defined on that element to be checked.
If you are not able to find this validation adding function in controller, try to see if it has been defined through config file.
You can read further about form validation in zf here: http://framework.zend.com/manual/1.12/en/zend.form.elements.html
I remove the element with:
$form->get('product')->remove('version');
When I post the form I disable the validator on this element with :
$form->getInputFilter()->get('product')->get('version')->setRequired(FALSE);
Related
In my controllers that Gii creates it is common to see the following:
if($model->load(Yii::$app->request->post()) && $model->save()){
//.....do something such as redirect after save....//
}else
{
//.....render the form in initial state.....//
}
This works to test whether a POST is sent from my form && the model that I am specifying has saved the posted information (as I understand it).
I've done this similarly in controllers that I have created myself but in some situations this conditional gets bypassed because one or both of these conditions is failing and the form simply gets rendered in the initial state after I have submitted the form and I can see the POST going over the network.
Can someone explain why this conditional would fail? I believe the problem is with the 'Yii::$app->request->post()' because I have removed the '$model->save()' piece to test and it still bypasses the conditional.
Example code where it fails in my controller:
public function actionFreqopts()
{
$join = new FreqSubtypeJoin();
$options = new Frequency();
$model = new CreateCrystal();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$model->insertFreqopts();
return $this->redirect(['fieldmap', 'id' => $join->id]);
} else {
return $this->render('freqopts', ['join' => $join, 'options' => $options]);
}
}
My initial thought was that I'm not specifying the correct "$model" in that I'm trying to save the posted data to FreqSubtypeJoin() in this case and the $model is CreateCrystal(); however, even when I change the model in this conditional it still fails. It would be helpful if someone could briefly explain what the method 'load' is actually doing in layman's terms if possible.
The load() method of Model class is basically populating the model with data from the user, e.g. a post query.
To do this it firstly loads your array of data in a form that matches how Yii stores your record. It assumes that the data you are trying to load is in the form
_POST['Model name']['attribute name']
This is the first thing to check, and, as long as your _POST data is actually getting to the controller, is often where load() fails, especially if you've set your own field names in the form. This is why if you change the model, the model will not load.
It then check to see what attributes can be massively assigned. This just means whether the attributes can be assigned en-mass, like in the $model->load() way, or whether they have to be set one at a time, like in
$model->title = "Some title";
To decide whether or not an attribute can be massively assigned, Yii looks at your validation rules and your scenarios. It doesn't validate them yet, but if there is a validation rule present for that attribute, in that scenario, then it assumes it can be massively assigned.
So, the next things to check is scenarios. If you've not set any, or haven't used them, then there should be no problem here. Yii will use the default scenario which contains all the attributes that you have validation rules for. If you have used scenarios, then Yii will only allow you to load the attributes that you have declared in your scenario.
The next thing to check is your validation rules. Yii will only allow you to massively assign attributes that have associated rules.
These last two will not usually cause load() to fail, you will just get an incomplete model, so if your model is not loading then I'd suggest looking at the way the data is being submitted from the form and check the array of _POST data being sent. Make sure it has the form I suggested above.
I hope this helps!
When removing an element from a form I would usually use
$this->form->remove('foo');
How would I remove an element from a fieldset? for example my $blocks is a fieldset with multiple elements but this doesn't work to remove unwanted elements
$blocks = $this->form->get('page')->get('blocks');
$blocks->remove('active');
When you remove the Form\Element from within a view it will still be present after the creation of the Form that will be passed to the Controller. I strongly suggest for you to be doing a proper OOP approach to your problem. Mainly there's two solutions for this.
The base is always identical, have a Form\Fieldset that matches your Model / Entity. It can have as many child-fieldsets as you need.
Option 1 - Create different Forms and remove elements
Basically this approach would look something like this:
'EntitySubEditForm' => function ($fem) {
$form = new DefaultForm();
$form->get('fieldset')->remove('foo');
$form->get('fieldset')->remove('bar');
return $form;
}
This will basically function like the approach you went, only at the respective place.
The upside to this approach is that you can render your form using $this->formCollection().
The downside to this approach is that even though you may use caching it simply requires more cache-data (hdd-space). And even though it's cheap by now, no reason to waste it ;)
Option 2 - Just don't validate certain fields
You may choose just to ignore some data passed in your special form.
'EntitySubEditForm' => function($fem) {
$form = new DefaultForm();
$form->setValidationGroup(array(
'id', 'name', 'title',
'etc....'
)); // but NOT 'foo' or 'bar'
return $form;
}
This is the approach I'm going. The reason is that I am caching the created Form-Objects so that Form creation is faster. Setting up a validationGroup then allows me to simply ignore values that are sent having these keys. Remember: unvalidated data is NOT passed from Zend\Form.
The downside to this approach is, that you can't render your form using $this->formCollection(), because the elements are still there and would be rendered. You'd have to manually render respective rows using $this->formRow() or even more manually...
more to read...
You may further be interested in the /docs of DoctrineModule #github because it covers a good use-case and describes well how Zend\Form should be used when Forms for specific actions should have different fields. Iirc it uses Option 1.
I'm using a form which contains wrapped elements. The wrapping happens in the view like described here.
My action looks like this:
$myForm = [definition here]
$myForm->setName('entity');
$myForm->setWrapElements(true);
$request = $this->getRequest();
if ($request->isPost()) {
$myEntity = new Entity();
$myForm->bind($myEntity);
$myForm->setData($request->getPost()->get('entity'));
The problem: When calling $myForm->isValid() it's invalid. When calling $myForm->getData() afterwards it's empty.
I repeated the setName and setWrapElements in the action but with or without it, it doesn't work.
Any ideas what I might be doing wrong? The form definition is untouched and works for non-wrapped forms. So I guess the error is not in there.
P.S.: An echo of $myForm->isValid() returns an empty string. Is there maybe a way to get the error message? The form fields are filled with the data I've put in there and don't show any errors.
Using the following:
$form->getMessages()
Will give you the validation messages.
You can dump the contents or loop the messages in a foreach loop. For example:
foreach($form->getMessages() as $msgId => $msg) {
echo "Validation error: $msgId => $msg"
}
can you try to add line to your code, as I can see in zend's Form.php, element names aren't wrapped with 'entity' till the moment you call prepare();
$myForm->setName('entity');
$myForm->setWrapElements(true);
$myForm->prepare(); // << add this
But I don't believe that it will help, becuase behaviour you describe looks little different. Cau you show us more source code of Entity and var_dumps of things that Aydin and Sina wanted.
In ZF2 data is not binded if the form is not valid. The reason because you see an empty string in the return of isValid is that the return type is a boolean, use var_dump instead.
I need to create a form where the elements (texbox, select, ..) will be dynamically inserted. Right now I have created a empty Form file with just a hidden element and them in my controller I go inserting elements according to certain conditions.
My form file:
class Form_Questions extends Zend_Form {
public function __construct() {
parent::__construct($options);
$this->setName('Questions');
// Hidden Label for error output
$hiddenlabel = new Zend_Form_Element_Hidden('hiddenlabel');
$hiddenlabel->addDecorator(new Form_Decorator_HiddenLabel());
$this->addElements( array($hiddenlabel) );
}
}
In the controller I have something like:
...
$form = new Form_Questions();
$request = $this->getRequest();
if ($request->isPost())
{
$formData = $request->getPost();
if ($form->isValid($request->getPost()))
{
die(var_dump($form->getValues()));
}
}
else
{
//... add textbox, checkbox, ...
// add final submit button
$btn_submit = new Zend_Form_Element_Submit('submit');
$btn_submit->setAttrib('id', 'submitbutton');
$form->addElement($btn_submit);
$this->view->form = $form;
}
The form displays fine but the validation is giving me big trouble. My var_dump() only shows the hidden element that is staticly defined in the Form file. It does not save the dinamic elements so altought I can get them reading what's coming via POST, I can not do something like
$form->getValue('question1');
It behaves like if Zend uses the Form file to store the values when the submit happend, but since the elements are created dinamically they do not persist (either their values) after the post so I can not process them using the standar getValue() way.
I would appreciate any ideas on how to make them "live" til after the post so I can read them as in a normal form.
The form which you are calling isValid() and getValues() methods on is actually your "empty" form - you have instantiated it only a few lines up and haven't added any elements to it at that point.
Remember that POST only sends an array of fieldName => fieldValue type, it doesn't actually send a Zend_Form object.
It is difficult to suggest a new solution without knowing what you are trying to achieve. It is generally better to add all possible elements to your Zend_Form right away, and then only use the ones you need in the view scripts, i.e. echo $this->form->myField;. This will allow isValid() to process all the elements of the form.
It sounds like the form is dynamic in the sense that the questions come from a db, not in then sense that the user modifies the form itself to add new questions.
Assuming this is the case, then I wouldn't add the question fields in the controller. Rather, I'd pass the questions to the form in the constructor and then add the question fields and the validators in the form's init() method. Then in the controller, just standard isPost() and isValid() processing after that.
Or, if you are saying that the questions to be added to the form are somehow a consequence of the hidden label posted, then perhaps you need two forms and two actions: one for the hidden field form and another for the questions.
Ok, the simplest solution I came up with - to my case and considering the really of the code I am currently playing with was to load all the questions I need from the database using a method from my Model (something like fetchQuestions()), them in my controller I go throught the recordset and create the form elements according to the current question of the recordset.
The elements are stacked in an array that is passed to my Form constructor. In the form constructor I read the array and generate all the dynamic elements. I them just echoed the form to the view.
I have not seem why it would be a bad idea to override the Form constructor as I also could not use any of the set/get methods to pass this to my form.
I am using the symfony embedRelation method to embed forms.The code is like this:
public function configure(){
//......
$this->embedRelation('Foos as foos');
$this->getEmbeddedForm('foos')->mergePostValidator(new MenuValidatorSchema());
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this does not work.
}
When embeding forms in Symfony, the top-level form keeps track of everything. The widget schema, validator schema, defaults, etc. of the embedded form are no longer directly used. You can see what's happening in sfForm::embedForm.
Note that in this case, since it's a post validator, it's perfectly acceptable to add it to the top-level form, i.e.:
$this->mergePostValidator(new MenuValidatorSchema());
If you want the validator schema on the embedded form and it has no current post validator, you can simply do:
$this->validatorSchema['foos']->setPostValidator(new MenuValidatorSchema());
If it has an existing one, you'll have to turn them into an sfValidatorAnd, doing something like:
$this->validatorSchema['foos']->setPostValidator(new sfValidatorAnd(array(
'validators' => array(
$this->validatorSchema['foos']->getPostValidator(),
new MenuValidatorSchema()
)
)));
The syntax of that last option is just one reason why setting post validators on the top-level form is the preferred option when available.