Cakephp validate data on update entity - php

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)

Related

Can Laravel ignore further validation rules if you use exclude_if and it is TRUE?

I'm using the latest version of Laravel.
I am using Validator in my store controller and essentially I have a field called "current" which is required and is expected to be boolean. I also have a 2nd field, which is optional, called "to_date" of which the javascript plugin (moment.js) will provide either a date, or if null, provides a string "Invalid date" to the controller.
I wish to be able to validate "to_date" as a date using Validator if "current" is equal to 0. If "current" is equal to 1 then I wish to ignore validation for field "to_date"
My code started as:
$validator = Validator::make($request->all(), [
'current' => 'required|boolean',
'to_date' => 'date',
]);
But of course this failed because when "to_date" is not provided, the front-end provides a string "Invalid date" which fails the validation. And then I explored Laravel's validation rules a bit more and found 'exclude_if' which seemed to be appropriate.
$validator = Validator::make($request->all(), [
'current' => 'required|boolean',
'to_date' => 'exclude_if:current,1|date',
]);
This still didn't work because whilst it appears to "exclude it" from the $validator array, it still goes to the 'date' validation and fails. And so then I looked at 'bail' which is supposed to discontinue validation when Validator hits its first fail, however this doesn't work either because exclude_if doesn't fail when it runs, and the script still hits 'date' and fails.
Any help/pointers appreciated.
I am sure I can resolve this by wrapping Validator in an if statement and doing some logic to check whether to include "to_date" in Validator but this seems a bit smelly and not very tidy.
Any ideas? :)
The problem is you are intentionally providing bad data "invalid date".
This will trigger the validation failure because data is present.
exclude_if simply excludes the data from the array after validation.
The best solution is to not send bad data, and do required_if:current,0|date and when to_date is empty and required, it will fail the date validation portion.
Tested with Laravel 6.18.7 and your code works correctly for me. If you're still having issues, you might want to check your input.
Example code I used in tinker:
>>> Validator::make([
'current' => 1,
'to_date' => 'invalid-data',
], [
'current' => 'required|boolean',
'to_date' => 'exclude_if:current,1|date',
])->passes();
=> true

Validating Translated Entities in CakePHP 3

I'm having some difficulties validating a I18N field in CakePHP3.
The translate behavior is setup like this:
$this->addBehavior('Translate', [
'fields' => ['name', 'body', 'slug'],
'validator' => 'default'
]);
Like advertised here: https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#validating-translated-entities
The core validation is working properly. I have a validation rule in validationDefault function that checks if name is not empty and it works fine. Though, I also would like to add some application rules on top of this validation. The "name" field should have a unique value. I don't want to allow multiple entities with the same translated name.
This piece of code apparently doesn't work. CakePHP docs also are quite silent about this matter.
public function buildRules(RulesChecker $rules) {
// prevent duplicate creation
$rules->add($rules->isUnique(['name']));
return $rules;
}
Is this actually possible?
Thanks
What you are doing there is creating a rule for the name field on the main model, this won't affect translations. There is no built-in functionality for that, the behavior only assists with validation rules by making use of the validationTranslated() method in case it exists on your model class, it won't help with application rules.
You'd have to create a custom application rule that checks the translation table, by matching against the field, locale, model and content fields, something along the lines of this:
$rules->add(
function (EntityInterface $entity) {
$behavior = $this->behaviors()->get('Translate');
$association = $this->association($behavior->getConfig('translationTable'));
$result = true;
foreach ($entity->get('_translations') as $locale => $translation) {
$conditions = [
$association->aliasField('field') => 'name',
$association->aliasField('locale') => $locale,
$association->aliasField('content') => $translation->get('name')
];
if ($association->exists($conditions)) {
$translation->setErrors([
'name' => [
'uniqueTranslation' => __d('cake', 'This value is already in use')
]
]);
$result = false;
}
}
return $result;
}
);
Note that this uses the association object rather then the target table, this will ensure that further conditions like the model name are being applied automatically.
Also this requires to set the errors on the entity manually, as they are nested, which isn't supported by the rules checker, currently it can only set errors on the first level entity (see the errorField option).
It should also be noted that it would be possible to modify the rules checker for the translation table association (via the Model.buildRules event), however this would result in the errors being set on new entities that will be put in a separate property (_i18n by default) on the main entity, where the form helper won't find the error, so one would then have to read the error manually, which is a little annoying.
See also
Cookbook > Database Access & ORM > Validation > Applying Application Rules

CakePHP 3 - validate data not tied to a table (validation without any database involvement)

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.

Cakephp 3.0 Form validation

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

CakePHP Model::save allowed partially blank data during record insertion

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.

Categories