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!
Related
Im creating an edit page for my model where I have the following code:
<f:form action="createEditSave" addQueryString="1" method="POST" object="{activity}" objectName="activity">
<f:form.select property="competence" options="{competences}" prependOptionLabel="" prependOptionValue="" optionLabelField="name" />
</f:form>
When I submit the form(and select an option in the select) I expect the model in my createEditSaveAction to contain in this case a competence. But it remains empty even though the request does contain the argument. Am I wrong in thinking it should automatically select the object and must I handle it myself in my createEditSaveAction?
I believe my html is correct.
When submitting my form I receive no validation errors and get correctly send to my createEditSaveAction. Only here I expect my Activity model to have a competence model but the setCompetence() function receives a empty objectStorage.
Depends on what happened to send you back to the form:
If the problem was validation errors the value should be prefilled since it will exist in the request data from the referring request, which is then read by the ViewHelper to determine the current value.
If you redirected to an edit action the property value only exists on the object you passed and if that object is, for example, not assigned as a template variable in the action that displays your form.
It may help if you specify the optionValueField property - and it may also help if you explicitly pass a value for the field.
Don't forget that if your use case is somehow too specialised to fit this, you can always use f:form.select.option in a loop to render options manually, including selected attribute.
Final word: from the naming of your action it looks like you have one action that is capable of both creating and updating an object. This is bad practice - you should be using two different actions for this, and on the "new" action your object argument should be optional and null by default, on "edit" it should be mandatory. It may also be a good opportunity to review your controller in general, to confirm that it actually does use properly declared and annotated arguments and argument types for all the actions. If you circumvent the framework, things like form validation and subsequently resolving of field values may not work like you expect.
Resolved my own issue. The problem was the annotation in my model which said it was a objectStorage.
Had a similar issue with my form, I had to include both the name and the propery tag.
<f:form.select name="foo" property="foo">
<f:form.select.option value="bar">Bar</f:form.select.option>
</f:form.select>
I am currently building a web app which has two models, Donor and Donation Models respectively. It has multiple user roles. When the staff user first registers a donor, I want him to be redirected to another form which allows him to fill in the Donation details(the donor is registered once the first donation is successful).
Firs of all, should I create a donation controller, from which I would redirect the user using:
return $this->redirect(array('controller'=>'donations','action'=>'add'));
For the above to work, it requires me to save the newly registered donor's id in a session like so :
$this->Session->write('id', $this->Donor->id);
So the user is redirected to 'donations/add' in the url, and this works fine.. However I think this has some flaws. I was wandering whether I should create another action inside the Donor controller called 'add_donation', which will have its respective 'View'. The idea is to be able to form a url of the sort : 'donors/add_donation/4' (4 being the donor_id ! )
This URL follows this construct: 'controller/action/id'
If anyone could shed some light on best practices, or describe any caveats to my solution(the former, using session etc.) , please do help a brother out! Ill be deeply indebted to you! Thanks in advance!
After you saved the data you can do this in the DonorsController:
$this->redirect(array(
'controller' => 'donations',
'action' => 'add',
$this->Donor->getLastInsertId()
));
There is no need to return a redirect, it's useless because you get redirected. Notice that we pass the last inserted record id as get param in the redirect. The redirect method of the controller calls by default _stop() which calls exit().
CakePHP3: There is a discussion about changing that default behavior in 3.0. Looks like in CakePHP 3.0 the redirect() won't exit() by default any more.
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
I would not use the session here, specially not by saving it to a totally meaningless and generic value named "id". If at all I would use always meaningful names and namespaces, for example Donor.lastInsertId as session key.
It's not always clear where to put things if they're related but the rule of thumb goes that things should go into the domain they belong to, which is pretty clear in this case IMHO.
Edit:
Leaving this edit here just if someone else needs it - it does not comply with the usage scenario of the asker.
If you have the user logged in at this stage, modify the add function to check if the userId passed is the same as the one logged in:
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
if ($this->Auth->user('id') != $donorId) {
throw new InvalidArgumentException();
}
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
You can use also the same controller using more models with uses.
Or you can also to ask to another controller with Ajax and morover to get response with Json.
There is a sample in the bottom of the official documentation http://kohanaframework.org/3.2/guide/kohana/security/validation
But obviously it wont work at the request as long as $post['username'] in View is used but the $post array is empty on first request.
So how do you restore the values in this case? Any general solution?
PS: yes, I do understand I could do isset($post['username']) ? $post['username'] : ''; but it is just annoying
I use the model to display the data in the form. That way the initial form value is the initial value in the model.
I then update the model data with POST data in the controller, if there are validation errors, the model data will contain the POST data. This means I don't have to put any conditional logic in the view, and I just do: Form::input('name', $model->name)
Here's a more detailed explanation of this approach: Kohana ORM and Validation, having problems
I use Arr::get function:
echo Form::input('name', Arr::get($post, 'name'))
I was just looking at the old documentation on Building and Validating a Form.
You can see from the sample code that first you need to initialize an array with the form field names as the key and set the value to an empty string. And if there's an error, fill in the values of each element. In the views, you can simply call Form::input() normally without any if statement or some sort.
I guess Kohana has already been built this way from the start. And it doesn't seem to change. You'll probably just need to do the same thing.
First, let me say, that I find the sfFormPropel form's interface inconsistent.
There is bind(), which returns nothing, but triggers validation, save() which returns the saved object, and bindAndSave(), which returns boolean, actually the return value of isValid(). Now, I have a working application, but I don't feel the code is right, and I'm quite new to symfony, so perhaps I'm missing something.
The object I need to create needs some external properties, that are not presented in the form, are external to the model, and are handled by the application (for example, the userId of the user, that created the entity, an external-generated guid, etc.).
Right now the flow is as follows:
get values from request and bind them to form
check if form is valid
if it's valid, add additional values and bind them to form one more time
save the form and return the object
The obvious answer would to add application-specific values to the values, retrieved from request, but It does not make sense to bind the application-specific values if the form is not valid, since they can be potentially expensive operations, may create database records, etc. Additionally, it should not be possible to pass those values with the post request, they should come from application only.
Now, I though that I have to let the model do these things, but since the data is external to the model, action still need to pass it to the model. The problem is, if I call $form->getObject() after bind(), it still has the old data, and not the data submitted.
What is the correct way to implement this kind of post-processing?
Second bounty is started to award the other valuable answer
The correct way would be setting your default values on the object you are passing to the form constructor. For example if you want to set the logged in user id on an object you are creating:
$article = new Article();
$article->setUserId($this->getUser()->getId());
$form = new ArticleForm($article);
if ($request->isMethod('post')) {
$form->bind($request->getParameter('article'));
if ($form->isValid()) {
$form->save();
}
}
Likewise for existing object, you can load the record and change any properties before passing it to the form constructor.
EDIT:
If you want to modify the object after validating, you can use $form->updateObject() like Grad suggests in his response. If the generated values depend on the submitted values, you can override sfFormObject::processValues():
class UserForm {
public function processValues($values) {
$values['hash'] = sha1($values['id'] . $values['username']);
return parent::processValues($values);
}
}
In case you need something from the action, you can always pass it as an option to the form:
$form = new UserForm($user, array('foo' => $bar));
That way, you can use $this->getOption('foo') anywhere in your form code, eg. in processValues().
It kind of depends of who has "knowledge" about the extra attributes. If they're really request specific, thus need to be processed in the controller, I go for binding, testing if valid and then update the bound object. To get the updated object with the bound (and validated) fields use the updateObject function.
$form->bind(..)
if ($form->isValid()) {
$obj = $form->updateObject(); // Updates the values of the object with the cleaned up values. (returns object)
$obj->foo = 'bar';
$obj->save();
}
But since this normally is also behaviour that is form specific, I usually go for overriding the Form class. By overriding the doUpdateValues() function you can easily access submitted data, and append your own data. Of course you can also go higher in the chain, and override the save() function.
To set custom data for this form, you can also 'publish' public methods, which can then be used by the controller.
With the new version of CodeIgniter; you can only set rules in a static form_validation.php file. I need to analyze the posted info (i.e. only if they select a checkbox). Only then do I want certain fields to be validated. What's the best way to do this, or must I use the old form validation class that is deprecated now?
You cannot only set rules in the config/form_validation.php file. You can also set them with:
$this->form_validation->set_rules();
More info on: http://codeigniter.com/user_guide/libraries/form_validation.html#validationrules
However, the order of preference that CI has, is to first check if there are rules set with set_rules(), if not, see if there are rules defined in the config file.
So, if you have added rules in the config file, but you make a call to set_rules() in the action, the config rules will never be reached.
Knowing that, for conditional validations, I would have a specific method in a model that initializes the form_validation object depending on the input (for that particular action). The typical situation where I've had the need to do this, is on validating shipping and billing addresses (are they the same or different).
Hope that helps. :)
You could write your own function which checks whether said checkbox is selected, and applies the validation manually.
function checkbox_selected($content) {
if (isset($_REQUEST['checkbox'])) {
return valid_email($content);
}
}
$this->form_validation->set_rules('email', 'Email', 'callback_checkbox_selected');
If you want to avoid writing your own validation function, I came across this site which suggests that, if you're dynamically setting your rules using the Form Validation class, you can simply build up the rule string argument to set_rules() dynamically.
You first test the POST data to determine if your condition is satisfied (eg. checkbox selected) and then, as necessary, add a "|required" to the rule string you pass to set_rules().