I'm playing around with Cakephp 3.0 and wondering how I go about validating data which is not being saved to the database.
For example I know in the model (which appears to be now known as a "table") you add a validationDefault method to the model which is called automatically when data is being saved to the database i.e a new user is being added to the database. But how would I go about validating data from a login form which is not saving to the database and then show those errors?
For example on a user login, I would want to check whether fields have been entered, doesn't exceed a certain size etc
Taken from the official docs, you can instantiate a validator in your controller and validate your data by passing it $this->request->data()
use Cake\Validation\Validator;
...
$validator = new Validator();
$validator
->validatePresence('email')
->add('email', 'validFormat', [
'rule' => 'email',
'message' => 'E-mail must be valid'
])
->validatePresence('name')
->notEmpty('name', 'We need your name.')
->validatePresence('comment')
->notEmpty('comment', 'You need to give a comment.');
$errors = $validator->errors($this->request->data());
if (!empty($errors)) {
// Send an email.
}
http://book.cakephp.org/3.0/en/core-libraries/validation.html
Related
I cannot get the validation to work properly when updating entity data. The validation does not work after changing the initial data. The code below provides an example:
// in controller
$user = $this->Users->newEntity([
'mail' => 'wrong',
'password' => 'password',
'auth_level' => 0,
]);
debug($user->getErrors()); // Will show error: wrong email format
$user->mail = "correct#correct.correct";
debug($user->getErrors()); // Will show no errors
$user->mail = "wrong";
debug($user->getErrors()); //Will show no errors
if (TableRegistry::get('users')->save($user)) {
// This actually executes
}
My validation rule in the model is as follows:
public function validationDefault(Validator $validator): Validator
{
$validator
->email('mail')
->add('mail', 'unique',
[
'on' => ['create', 'update'],
'rule' => 'validateUnique',
'provider' => 'table',
'message' => "Email already in use"
]
);
return $validator
}
I tried creating rules with "on" => "update", but it does not help.
What I am trying to achieve is to get an entity, change the email address and save it back to database. Upon saving, the email field is neither validated for format nor uniqueness.
For the sake of completeness.
There is a difference between Application Rules and Validation Rules.
Validation Rules validate data typically coming from user's input (newEntity(), patchEntity()). Entity is regenerated. Baked ones are in "validationDefault" function within Tables.
Application Rules establish some rules for data modified within application code which is expected to be 'safe' (setters). Entity is not regenerated. Baked ones are in "buildRules" function within Tables.
"save()" function won't go through validation rules, but through application rules.
When saving data that needs to pass through validation rules because it's assigned/set within the application but it's data coming from user's input, make sure you use patchEntity().
More info: https://github.com/cakephp/cakephp/issues/6654
The solution is to always update entity data with
$model->patchEntity($entity, $newdata)
Yesterday I posted a question CakePHP 3 - Using reusable validators but am still struggling to see how to validate data when it is not tied to a particular database table, or set of fields in a table.
What I'm trying to do is upload a CSV file. The data in the CSV may well end up in the database, but before any of that happens, I want to validate the file to make sure it's valid - extension is .csv, MIME type is text/csv, file size is <1 Mb. This particular validation has absolutely nothing to do with a database.
Where does such validation go, since it's nothing to do with a database table?
The approach I've used is as follows - but this does not work:
I have a UploadsController.php with an upload() method. This method handles the upload (form posts to /uploads/upload)
I have added the following to my src/Model/Table/UploadsTable.php (because I don't know where else to put such code):
public function validationDefault(Validator $validator)
{
$validator
->add('submittedfile', [
'mimeType' => [
'rule' => array('mimeType', array('text/csv')),
'message' => 'Upload csv file only',
'allowEmpty' => TRUE,
],
'fileSize' => [
'rule' => array('fileSize', '<=', '1MB'),
'message' => 'File must be less than 1MB.',
'allowEmpty' => TRUE,
]
]);
return $validator;
}
In UploadsController::upload() I have this:
if ($this->request->is('post')) {
debug($this->request->data['submittedfile']);
$uploads = TableRegistry::get('Uploads');
$entity = $uploads->newEntity($this->request->data['submittedfile']);
debug($entity);
}
No matter what file I upload, no errors are returned. Even if I comment-out the entire validationDefault method, the behaviour doesn't change.
This is becoming very frustrating as all of the documentation on Cake's website talks about data relating to DB tables. Well, what if you're trying to validate something that's nothing to do with a DB table?
I've opened this as a new question, because the last one doesn't really address this problem.
Other questions posted about this do not address this problem, because in this case they are writing the file info to a DB table, and therefore validating the file at the same time. In my case I'm not trying to do that, I just want to validate the file, before considering anything to do with the DB at all. I also want the code to validate a CSV to be re-usable (i.e. not in one specific Controller method) because if I want to check for a valid CSV in 5 different parts of the application, why should I repeat code that does the same thing all over?
Use a model-less form, it has validation built in, to validate your uploaded file. If you want your validation code to be reusable put it in a trait or separate class
In theory you could then do something like this:
$form = new CsvForm();
if ($this->request->is('post')) {
$result = $form->execute($this->request->getData());
if ($result && $this->Model->saveMany($result) {
$this->Flash->success('We will get back to you soon.');
} else {
$this->Flash->error('There was a problem submitting your form.');
}
}
Let your form validate the CSV document and return the pure CSV data, or already turn the rows into a set of entites you save.
I am building a Laravel-based eCommerce solution with a shopping cart. I have a page for each product where you can choose a product option, whether you want to subscribe, how often you want to receive shipments, the quantity you want, and then add it to your shopping cart. I want to validate this combination of choices before adding to the customer's shopping cart. To validate, I need to check against the database for this product option to make sure that it can be subscribed to, that the quantity chosen is below the maximum quantity allowed for that product, etc.
With CodeIgniter, I would write a custom callback, pull in the inputs from the POST request, and check everything in one call to the database. If there was a validation failure, I would then output a relevant conditional message for what caused the validation to fail. (i.e. "You can't subscribe to that product" or "The quantity you selected is more than the maximum quantity allowed for the product.")
This seems a little more difficult to accomplish with Laravel. I was looking through the documentation and articles online, and I found an approach that seems to work.
I have my validation method in my model, CartLine. Then, in a new Form Request, I overwrite the getValidatorInstance method with the following:
public function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->after(function() use ($validator) {
$cartLine = new CartLine;
$cartLine->validate($this->all());
if (!$cartLine->valid()) {
$validator->errors()->add('product', $cartLine->errors());
}
});
return $validator;
}
I have a couple of questions:
When I first experimented with this solution, I tried to use method injection for the instance of CarLine inside the closure, like so:
$validator->after(function(Validator $validator, CartLine $cartLine) use ($validator) {
$cartLine->validate($this->all());
if (!$cartLine->valid()) {
$validator->errors()->add('product', $cartLine->errors());
}
});
But I kept getting an error saying that the closure was expecting an instance of CartLine and none was given. I fixed this by newing up CartLine inside the closure, but it's obviously not as elegant. Why doesn't method injection work here?
Does anyone have a better or more elegant solution for how to accomplish this type of custom validation with conditional error messages in Laravel?
you may pass the custom messages as the third argument to the Validator::make method:
$messages = [
'required' => 'The :attribute field is required.',];
$validator = Validator::make($input, $rules, $messages);
Where :attribute will be replaced by actual name of field.
Also you can provide validations only for particular attribute as below
$messages = [
'email.required' => 'We need to know your e-mail address!',];
I've been working with PHP for about a year, but I do it as a hobby. I dont have anybody I can go to as a teacher or a mentor to give me advice on what I may be doing completely wrong, or what I could do better. I've done quite a few different things within that year, so I wouldnt consider myself a complete noob.
Anyways, I have just started using a framework (Kohana), and there really arent that many tutorials out there, so I'm not entirely sure if I'm doing things in a good way.
I have a few code snippets that I would like to post to get some feedback pertaining to what I just said.
For Starters
User Controller
class User_Controller extends Template_Controller{
public function register()
{
// logged in users cant register
if($this->logged_in)
{
url::redirect('user/profile');
}
// initially show an empty form
$form = $errors = array
(
'username' => '',
'email' => '',
'password' => '',
'gender' => '',
'dob_month' => '',
'dob_day' => '',
'dob_year' => '',
'date_of_birth' => '',
'captcha' => '',
'registration' => ''
);
// check for a form submission
if($this->input->post('register'))
{
// get the form
$post = $this->input->post();
// prepare the data for validation
$post['date_of_birth'] = "{$post['dob_year']}-{$post['dob_month']}-{$post['dob_day']}";
// create a new user
$user = ORM::factory('user');
// validate and register the user.
if($user->register($post, TRUE))
{
// SEND EMAIL
// login using the collected data
if(Auth::instance()->login($post->username, $post->password, TRUE))
{
// redirect the user to the profile page
//url::redirect("user/profile/{$user->id}");
}
}
// get validation errors and repopulate the form
$form = arr::overwrite($form, $post->as_array());
$errors = arr::overwrite($errors, $post->errors('registration_errors'));
}
// template variables
$this->template->title = 'Sign Up';
$this->template->body = new View('layout_1');
// layout variables
$this->template->body->left = new View('user/registration_form');
$this->template->body->right = 'Right Side Content';
// registration form variables
$this->template->body->left->form = $form;
$this->template->body->left->errors = $errors;
$this->template->body->left->captcha = new Captcha('register');
}
}
Register Function within User_Model
class User_Model extends ORM{
public function register(array& $user, $save = FALSE)
{
$user = new Validation($user);
// logged in users cant register
if(Auth::instance()->logged_in())
{
$user->add_error('registration', 'logged_in');
return FALSE;
}
// trim everything
$user->pre_filter('trim')
// everything is required
->add_rules('*', 'required')
// username must be 5 - 30 alphanumeric characters and available
->add_rules('username', 'length[5,30]', 'valid::alpha_numeric', array($this, 'username_available'))
// email must be valid format and available
->add_rules('email', 'valid::email', array($this, 'email_available'))
// password must be 5 - 15 characters and alpha dash
->add_rules('password', 'length[5,15]', 'valid::alpha_dash')
// gender must be either male or female. capitalize first letter
->add_rules('gender', array($this, 'valid_gender'))
->post_filter('ucfirst', 'gender')
// dob must be a valid date, and user must be old enough.
->add_callbacks('date_of_birth', array($this, 'check_dob'))
// captcha must be entered correctly.
->add_rules('captcha', 'Captcha::valid');
// add the registration date
$this->registration_date = date::unix2mysql(); // helper function transforms the current unix to mysql datetime format
// validate the information. an ORM function.
$result = parent::validate($user, $save);
// was the user info valid?
if($result === TRUE)
{
// was the user saved?
if($save === TRUE)
{
// add a login role
$this->add(ORM::factory('role', 'login'));
$this->save();
}
}
else
{
$user->add_error('registration', 'failed');
}
return $result;
}
}
Mostly all my models follow the same format when validating info.
I have some other things I would appreciate feedback on as well, but I dont want to overwhelm anybody.
Thanks a lot for your time
EDIT: I'm sorry, I should've posted both the user controller and model. I've been reading alot about how models should be fat, and controllers should be skinny. Thats why I created a register function in the model to validate the info instead of doing so within the controller. The register function takes an array, but turns that array into a validation object so that I can retrieve the user input, and the errors. I've seen a few tutorials on Kohana where it was done this way.
First, I would not put the register() method into the User model. A model should be a representation of the object in the database and generally only contains your "CRUD" methods (create, retrieve, update, delete), getter and setter methods, and maybe some static helper methods related to the model. By putting your register() method into the model, you're making the model do presentation logic that should really be done by a User controller, since this is a response to a user action. A controller handles user actions, validates those user actions, then updates the model if the validation is successful.
In your example, the user is attempting to create a new account. He fills out a form and clicks submit. The form's POST action should point to a controller's method, like /user/register, and that method will use the Validation library to validate the form data sent by the user. Only if that data validates successfully should you create a User model instance, set the properties of that model to what the user input, and then use the model's save() method to save to the database. If the validation fails, you report the error to the user and you don't create a User model at all since you don't have a valid data set to create a model with yet.
Next, you are checking to see if the user is logged in. Again, this should be in the controller, not the model. Besides that, the user should not be able to get to this register process in the first place if he is already logged in. The controller method that creates the user registration form view should check to see if the user's logged in, and if he is, then he should be redirected to another page. Even if the user is playing tricks and manages to submit the form (maybe he logged in via another window while having the form open in an old window), your register method should check for that first and not create a $user Validation object yet.
I can see in your code that there are some confusing items based on your model set up. For example, you're passing the $user array into the method, which I presume is the form data. But you're using the "pass by reference" operator (&) which is unnecessary in PHP5 since all objects are now passed by reference. But after that you're recasting $user as a Validation object. Are you using the $user Validation object elsewhere and require it to be passed by reference? If so, that's another flaw in the logic as all of this processing needs to be in the controller and the $_POST values can be used directly in the controller instead of having to pass around a Validation object.
Later on, you're validating the user information with parent::validate($user, $save). Why is the validate() method being called on parent as a static method? If this is a model, it should be extending Kohana's core Model class, and "parent" references the Model class. Is your model extending the Validation class? Also, why are you passing in the $user Validation object to the validation() method? Doing that is required if you need to do recursion (to validate elements again after making changes from previous filters), but it looks like you're not doing anything to require recursion. You should be calling validate() on the $user Validation object:
$user->validate();
without any arguments. The validation errors will become part of the $user object, so you can check for errors using
$user->errors();
Finally, while Kohana allows you to use method chaining, I would not use one long chain to set up the rules and other items for the validation. It's confusing and may cause debugging to be difficult. Put each of those on its own line and perform each directly on the $user object.
I dont know Kohanna so im not sure what the lay of the land is on their MVC separation but typically i would make register an action on a controller. The main thing i disagee with in your code is that the Model is coupled to the Authentication system internally. The authentication check should be made outside the class and the control flow decision should be made outside as well, OR the result of the authentication check should be passed in to the Model for use in its internal operation.
Typically i might do something like the following pseudo code:
// in my controller class for User or whatever
public function registerAction()
{
// get the form data from the request if its POST, ortherwise a blank array
$userData = $this->getRequest('user', array(), 'POST');
// create a user
$user = new User($userData);
if(Auth::instance()->logged_in())
{
// we are logged in add an error to the user object for use by the view
$user->getValidator()->add_error('registration', 'logged_in');
}
elseif($user->is_valid())
{
// user data is valid, set the view with the success message
$user->save();
$this->setView('register_success');
}
/**
* render the designated view, by default this would be the one containing the
* registration form which displays errors if they exist - however if we success-
* fully registered then the view with the success message we set above will be
* displayed.
*/
$this->render();
}
Want to show a custom message in form's error list, if the two fields did not match.
the from is as follows,
'old_password' =>'Old Password*',
'new_password' =>'New Password*',
'confirm_password' =>'Confirm Password*',
I want that the old password should match the value from the database, the value in new password and confirm password should also match.
please help me.
In Symfony 1.1 and later, to compare if the two form fields match you need to set up a post validator, like:
$this->validatorSchema->setPostValidator(
new sfValidatorSchemaCompare(
'new_password',
sfValidatorSchemaCompare::EQUAL,
'confirm_password',
array(),
array('invalid' => 'Your custom error message here!!')
)
);
Try reading Symfony forms in Action, it should solve most of your problems about form creation and validation within the Symfony framework