I got stuck with this stupid error messages in Zend Framework 2. Spent two hours and nothing, I still got two different error messages with NotEmpty validator.
$nameNotEmptyMessage = 'Pole wymagane';
$inputFilter->add(array(
'name' => 'name',
'required' => true,
'filters' => array(
new Filter\StringTrim()
),
'validators' => array(
new Validator\NotEmpty(array(
'messages' => array(
Validator\NotEmpty::INVALID => $nameNotEmptyMessage,
Validator\NotEmpty::IS_EMPTY => $nameNotEmptyMessage
)
))
)
));
And what? It works pretty cool when I send form with "name" element inside it. When it's empty, I've got my message. But, when I send form without the "name" element inside it, the message is still "Value is required and can't be empty"! Why?
I need to have in both cases the same message because sometimes the form won't have "name" element.
Thank you guys very much!
Thanks for replying to the comment to clarify the problem. First I must say, I feel your pain on this one. After having not used ZF2 for over a year and spending some time figuring this out, it highlights yet another painful thing in ZF2.
That said, here is my simple solution. Not necessary correct, or the best way to go about it but it solves your problem with the least amount of code. In your controller where you instantiate and validate the form, add this code before you call new Form:
if (!isset($_POST['name'])) {
$this->getRequest()
->setPost(
new \Zend\Stdlib\Parameters(
array('name' => '')
)
);
}
This seemed the simplest route rather than setting up a default validation translator and adding a duplicate message. It is just checking to see if the name field was not present, and if so sets it with any empty value.
Now the reason why we have to do this:
This happens because of how \Zend\InputFilter\Input works. When the InputFilter runs, it sees the element is missing. It then checks the required property, sees it's true but the input has no value. No value being different than empty.
When the filter is required but has no input, \Zend\InputFilter\Input::prepareRequiredValidationFailureMessage is called. If you look at the source of that function, you'll see:
protected function prepareRequiredValidationFailureMessage()
{
$notEmpty = new NotEmpty();
$templates = $notEmpty->getOption('messageTemplates');
return [
NotEmpty::IS_EMPTY => $templates[NotEmpty::IS_EMPTY],
];
}
Notice how it creates a new NotEmpty validator and fetches its default message, thus bypassing the translation you set within the InputFilter.
The alternative:
One alternative, which may be more correct, but also requires more code and could arguably be more confusing looking back later would be to create a new class implementing \Zend\Validator\Translator\TranslatorInterface and set an array with the translation for that message and then calling \Zend\Validator\AbstractValidator::setDefaultTranslator passing that so the default translator will be used on that message.
This is just difficult because there is no short way of setting up that object because of how the classes are inherited and I wasn't able to find a quick solution to set up the default translator with just that message. Also, if you have another translator in place, it might interfere with it.
So it seems the simplest thing is to just check for the absence of that form field and populate it with any empty value if it's missing.
Hope that helps!
Other more simple way to fix this is to overwrite the Form setData($data) and to check if $data['name'] is set. Something like this:
public function setData($data)
{
if(!isset($data['name'])){
$data['name'] = '';
}
return parent::setData($data);
}
Related
I was investigating CakePHP (2.3.4) Model::save method to insert new records and ran into a little snag:
//Two fields provided, email field intended to fail validation
$data = array('Member' => array(
'username' => 'hello',
'email' => 'world'
));
$this->Member->create();
var_dump($this->Member->save($data, true));
The above save() will return false and no data will be written to the database. However if I change the data to:
//One field provided, intended to pass validation
$data = array('Member' => array(
'username' => 'hello'
));
then save() will attempt to write a new record to database with a blank email field. I realize that skipping validation for unspecified fields might be a useful behavior during updates, but is there a CakePHP recommended way to handle partially empty data sets when creating new records? Thanks a lot.
Edit:
Thanks to Sam Delaney for the tip. In case anybody else gets stumped, this did the trick: CakePHP Data Validation: field 'required' key
This key accepts either a boolean, or create or update. Setting this key to true will make the field always required. While setting it to create or update will make the field required only for update or create operations. If ‘required’ is evaluated to true, the field must be present in the data array. For example, if the validation rule has been defined as follows:
I had originally baked the model and forgotten that required => true was left out of the validation rule. Setting it to true or 'create' would've avoided me blank records getting inserted due to gibberish data array.
IMHO what you've experienced is the desired behavior. Consider that you have the same two fields within a form and the user provides a value for only username. Both username and email field are submitted even though email is empty. Then on the server, you try to save the record only to find out that it has failed validation which you feedback to the user. On the other hand, perhaps in your system it is perfectly possible to create a user without requiring an email (for example root access account), CakePHP's validation implementation allows both of these scenarios.
If this flexibility isn't what you desire, just set the required attribute for your validation rule as true:
public $validate = array(
'email' => array(
'rule' => 'email',
'required' => true
)
);
This will satisfy your all or nothing requirement.
I've done quite a few Lithium tutorials (links below in case they help someone else, and also to show I've done my homework:) and I understand the most basic parts of creating models, views, controllers and using MVC to create a DB record based on form input.
However, I'm new to MVC for webapps and Lithium, and I'm not sure how I should write my code in more complicated situations. This is a general question, but two specific validation questions that I have are:
How should I validate date data submitted from the form?
How should I check that the two user email fields have the same value?
I would be very grateful for any help with these questions, and concrete examples like this will also really help me understand how to do good MVC coding in other situations as well!
Date entry - validating data split across multiple form inputs
For UI reasons, the sign up form asks users to enter their DOB in three fields:
<?=$this->form->field('birthday', array('type' => 'select', 'list' => array(/*...*/))); ?>
<?=$this->form->field('birthmonth', array('type' => 'select', 'list' => array(/*...*/))); ?>
<?=$this->form->field('birthyear', array('type' => 'select', 'list' => array(/*...*/))); ?>
What is the best way to validate this server-side? I think I should take advantage of the automagic validation, but I'm not sure of the best way do that for a set of variables that aren't really part of the Model. E.g.:
Should I post-process the $this->request->data in UsersController? E.g. modify $this->request->data inside UsersController before passing it to Users::create.
Should I pull the form fields out of $this->request->data and use a static call to Validator::isDate inside UsersController?
Is there a way to write a validation rule in the model for combinations of form variables that aren't part of the model?
should I override Users::create and do all the extra validation and post-processing there?
All of these seem like they could work, although some seem a little bit ugly and I don't know which ones could cause major problems for me in the future.
[EDIT: Closely related to this is the problem of combining the three form fields into a single field to be saved in the model]
Email entry - checking two form fields are identical, but only storing one
For common sense/common practice, the sign up form asks users to specify their email address twice:
<?=$this->form->field('email_address'); ?>
<?=$this->form->field('verify_email_address'); ?>
How can I write an automagic validation rule that checks these two form fields have the same value, but only saves email_address to the database?
This feels like it's pretty much the same question as the above one because the list of possible answers that I can think of is the same - so I'm submitting this as one question, but I'd really appreciate your help with both parts, as I think the solution to this one is going to be subtle and different and equally enlightening!
[EDIT: Closely related to this is the problem of not storing verify_email_address into my model and DB]
Some background reading on Lithium
I've read others, but these three tutorials got me to where I am with users and sign up forms now...
Blog tutorial
Extended blog tutorial
MySQL blog tutorial
Some other StackOverflow questions on closely related topics (but not answering it and also not Lithium-specific)
One answer to this question suggests creating a separate controller (and model and...?) - it doesn't feel very "Lithium" to me, and I'm worried it could be fragile/easily buggy as well
This wonderful story convinced me I was right to be worried about putting it in the controller, but I'm not sure what a good solution would be
This one on views makes me think I should put it in the model somehow, but I don't know the best way to do this in Lithium (see my bulleted list under Date Entry above)
And this Scribd presentation asked the question I'm hoping to answer on the last page... whereupon it stopped without answering it!
NB: CakePHP-style answers are fine too. I don't know it, but it's similar and I'm sure I can translate from it if I need to!
I'd recommend doing this in the Model rather than the Controller - that way it happens no matter where you do the save from.
For the date field issue, in your model, override the save() method and handle converting the multiple fields in the data to one date field before calling parent::save to do the actual saving. Any advanced manipulation can happen there.
The technique described in your comment of using a hidden form field to get error messages to display sounds pretty good.
For comparing that two email fields are equal, I'd recommend defining a custom validator. You can do this in your bootstrap using Validator::add.
use lithium\util\Validator;
use InvalidArgumentException;
Validator::add('match', function($value, $format = null, array $options = array()) {
$options += array(
'against' => '',
'values' => array()
);
extract($options);
if (array_key_exists($against, $values)) {
return $values[$against] == $value;
}
return false;
});
Then in your model:
public $validates = array(
"email" => array(
"match",
"message" => "Please re-type your email address.",
"against" => "email2"
)
);
Edit: Per the comments, here's a way to do custom rule validation in a controller:
public function save() {
$entity = MyModel::create($this->request->data);
$rules = array(
"email" => array(
"match",
"message" => "Please re-type your email address.",
"against" => "email2"
)
);
if (!$entity->validates($rules)) {
return compact('entity');
}
// if your model defines a `$_schema` and sets `$_meta = array('locked' => true)`
// then any fields not in the schema will not be saved to the db
// here's another way using the `'whitelist'` param
$blacklist = array('email2', 'some', 'other', 'fields');
$whitelist = array_keys($entity->data());
$whitelist = array_diff($whitelist, $blacklist);
if ($entity->save(null, compact('whitelist'))) {
$this->redirect(
array("Controller::view", "args" => array($entity->_id)),
array('exit' => true)
);
}
return compact('entity');
}
An advantage of setting the data to the entity is that it will be automatically prefilled in your form if there's a validation error.
Okay, so I'm fairly new to CakePHP. This is the setup:
I've got a Model (Reservation), and a controller, (ReservationController).
I'm trying to provide a simple add() functionality.
The request url is: www.example.com/reservations/add/3
Where 3 is the ID of the event this reservation is for.
So far, so good. The problem is, the form is constructed like this:
<h2>Add reservation</h2>
<?php echo $form->create('Reservation');
echo $form->input('first_name');
echo $form->input('last_name');
echo $form->input('email');
echo $form->input('tickets_student'); echo $form->input('tickets_nstudent');
echo $form->end('Add');?>
When I hit the send button, the request URL becomes simply www.example.com/reservations/add/, and the event id is lost.
I've solved it now by grabbing the id in the controller, and make it available to the form:
// make it available for the form
$this->set('event_id', $eventid);
And then, in the form:
$form->create('Reservation',array('url' => array($event_id)));
It works, but it strikes me as a bit ugly. Isn't there an easier way to make sure the form POST action gets made to the current url, instead of the url without the id?
Nik's answer will fail if the website isn't in the server public_html root.
This answer is more solid:
<?php echo $form->create('Reservation',array('url'=>'/reservation/add/'.$data['id']));?>
Following the strictest convention for just a moment, reading a URL like /reservations/add/3 would be, well, confusing. You're calling on the ReservationsController to act on the Reservation model, but passing it an event ID. Calling /reservations/edit/3 is far less confusing, but just as wrong for your situation since the id value, "3", would be assumed to be a reservation identifier.
Essentially, you're making an ambiguous request at best. This is a simple form to create a reservation and that reservation has to be associated with an event. In a "traditional" scenario, the form would allow the user to select an event from some kind of list. After all, the foreign key, probably event_id in this case, is really just another property of a reservation. The list would have to be populated in the controller; this is usually done via a find( 'list' ) call.
If you already know the event that you want to create the reservation against (which you apparently do), then I'd probably select the analogous method of using a hidden field in the form. You still have to set the value in the controller just as you're doing, but the end result is a bit more Cake-y. The hidden field would be named data[Reservation][event_id] (again, I'm assuming the name of your foreign key field).
$form->create('Reservation',array('action' => 'add',$eventId);
and in the controller:
function add($eventId = null)
{
if($eventId == null)
{
// error state
throw new NotFoundException('Invalid id');
}
...
}
I do it all the time.
You can do following:
$form->create('Reservation',array('url' => $this->Html->url()));
this way all your variables from the url will be added in the form action :)
As Rob Wilkerson suggests, the issue is your URL route doesn't accurately describe the operation being performed. It becomes further confusing when you want to edit the reservation: /reservations/edit/6. Now the number in the URL means something different.
The URL convention I use for situations like these (adapted to your particular case) is /events/3/reservations/add. It takes a bit more up-front to configure your routes, but I've found it's superior for clarity down the road.
Sample routes.php:
Router::connect(
'/events/:event_id/reservations/:action',
array('controller'=>'reservations','action'=>'index'),
array(
'pass' => array('event_id'), // pass the event_id as a param to the action
'event_id' => '[0-9]+',
'actions'=>'add|index' // only reverse-routes with 'action'=>'add' or
// 'action'=>'index' will match this route
)
)
// Usage: array('controller'=>'reservations','action'=>'add','event_id'=>3)
Router::connect(
'/events/:event_id/reservations/:id/:action',
array('controller'=>'reservations'),
array(
'pass' => array('id'), // pass the reservation id as a param to the action
'id' => '[0-9]+',
'event_id' => '[0-9]+',
'actions'=>'edit|delete' // only reverse-routes with 'action'=>'edit' or
// 'action'=>'delete' will match this route
)
)
// Usage: array('controller'=>'reservations','action'=>'edit','event_id'=>3,'id'=>6)
In your FormHelper::create call, you'd have to specify most of the reverse-route you want to follow, but again, the up-front cost will pay dividends in the future. It's usually better to be explicit with Cake than to hope its automagic always works correctly, especially as the complexity of your application increases.
I’m using Zend_Filter_Input to validate form data and want to customize the error messages, if a user does not enter a value. It is important that each field gets a different error message.
With Zend Framework 1.8.0 I used the following array for the “validator” parameter of Zend_Filter_Input:
$validators = array(
'salutation' => array(
new Zend_Validate_NotEmpty(),
Zend_Filter_Input::MESSAGES => array(
Zend_Validate_NotEmpty::IS_EMPTY => "Please enter a salutation"
)
),
/* ... */
);
Since I’ve upgraded to ZF 1.8.4, I always get the default message for empty fields (“You must give a non-empty value for field '%field%'”). Obviously Zend_Filter_Input does not call the Zend_Validate_NotEmpty validator anymore, if the field is empty.
Is there a way to change this behavior or another way to get customized “empty” messages for each field?
It seems that Zend_Filter_Input changed its behaviour when handling empty fields. Empty fields are never processed by rule validators. If a field is empty and allowEmpty is set to true, none of your validators are used. If the field is empty and allowEmpty is set to false, then the default message for empty values is set. Currently there is no way to customize this message for a specific field.
The behavior has not changed. This is a bug (http://framework.zend.com/issues/browse/ZF-7394)
try this:
$validators = array(
'salutation' => array('NotEmpty', Zend_Filter_Input::MESSAGES => 'Please enter a salutation')
);
I don't know why, but seems they changed the constant "isEmpty" with "NotEmpty" (without including it in the Zend_Validate_NotEmpty class).
Sometimes I just go nuts with Zend. :)
When using a Zend_Form, the only way to validate that an input is not left blank is to do
$element->setRequired(true);
If this is not set and the element is blank, it appears to me that validation is not run on the element.
If I do use setRequired(), the element is automatically given the standard NotEmpty validator. The thing is that the error message with this validator sucks, "Value is empty, but a non-empty value is required". I want to change this message. At the moment I have done this by changing the Zend_Validate_NotEmpty class, but this is a bit hacky.
I would ideally like to be able to use my own class (derived from Zend_Validate_NotEmpty) to perform the not empty check.
I did it this way (ZF 1.5):
$name = new Zend_Form_Element_Text('name');
$name->setLabel('Full Name: ')
->setRequired(true)
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator($MyNotEmpty);
so, the addValidator() is the interesting part. The Message is set in an "Errormessage File" (to bundle all custom messages in one file):
$MyNotEmpty = new Zend_Validate_NotEmpty();
$MyNotEmpty->setMessage($trans->translate('err.IS_EMPTY'),Zend_Validate_NotEmpty::IS_EMPTY);
hope this helps...
By default, setRequired(true) tells isValid() to add a NonEmpty validation if one doesn't already exist. Since this validation doesn't exist until isValid() is called, you can't set the message.
The easiest solution is to simply manually add a NonEmpty validation before isValid() is called and set it's message accordingly.
$username = new Zend_Form_Element_Text('username');
$username->setRequired(true)
->addValidator('NotEmpty', true, array('messages' => array('isEmpty' => 'Empty!')));
Add a NotEmpty validator, and add your own message:
// In the form class:
$username = $this->createElement('text', 'username');
$username->setRequired(); // Note that this seems to be required!
$username->addValidator('NotEmpty', true, array(
'messages' => array(
'isEmpty' => 'my localized err msg')));
Note that the NotEmpty validator doesn't seem to be triggered unless you've also called setRequired() on the element.
In the controller (or wherever), call $form->setTranslator($yourTranslator) to localize the error message when it's printed to the page.
Change the error message.
As far as I can see Changing the error message has no way of changing the message of a specific error. Plus the manual makes it look like like this is a function belonging to Zend_Form, but I get method not found when using it on an instance of Zend_Form.
And example of the useage would be really great.