In CakePHP3 how do I create a custom model rule which validates that a time is after another time in the same table? - validation

Columns (both datatype time):
Start
End
End cannot be before start.
$validator
->requirePresence('end', 'create')
->notEmpty('end')
->add('end', [
'time' => [
'rule' => 'time',
'message' => 'end can only accept times.'
],
'dependency' => [
'rule' => [$this, 'endBeforeStart'],
'message' => 'end can not be before start.'
],
]);
If it is a PUT request which only contains end, the model will need to query the existing record to compare against start. If it is a PUT which contains both then it need to validate against the intended new parameter.
How does cakePHP3 do this?
private function endBeforeStart($fieldValueToBeValidated, $dataRelatedToTheValidationProcess)
{
//What goes here?
}
I can't seem to find any examples of doing this online.

I'm not quite sure and haven't tested it, but maybe this gives you some hints:
$validator
->add('end', [
'endBeforeStart' => [
'rule' => function ($value, $context) {
// If it's a POST (new entry):
if ( $context['newRecord'] == '1' ) {
// Do your comparison here
// Input values are e.g. in $context['data']['starttime']
// If end is before start:
return false;
}
// If it's a PUT (update):
else {
// If starttime is not in $context['data']['starttime']
// check for the old value in $getOldEntry
$getOldEntry = $this->getOldEntry( $context['data']['id'] );
// And do your comparison here...
// If end is before start:
return false;
}
return true;
},
'message' => 'end can not be before start.' ],
])
public function getOldEntry($id = null) {
return $this->get($id);
}
I'm also not sure if the last function has to be private or public...

Related

Laravel array key validation

I have custom request data:
{
"data": {
"checkThisKeyForExists": [
{
"value": "Array key Validation"
}
]
}
}
And this validation rules:
$rules = [
'data' => ['required','array'],
'data.*' => ['exists:table,id']
];
How I can validate array key using Laravel?
maybe it will helpful for you
$rules = ([
'name' => 'required|string', //your field
'children.*.name' => 'required|string', //your 1st nested field
'children.*.children.*.name => 'required|string' //your 2nd nested field
]);
The right way
This isn't possible in Laravel out of the box, but you can add a new validation rule to validate array keys:
php artisan make:rule KeysIn
The rule should look roughly like the following:
class KeysIn implements Rule
{
public function __construct(protected array $values)
{
}
public function message(): string
{
return ':attribute contains invalid fields';
}
public function passes($attribute, $value): bool
{
// Swap keys with their values in our field list, so we
// get ['foo' => 0, 'bar' => 1] instead of ['foo', 'bar']
$allowedKeys = array_flip($this->values);
// Compare the value's array *keys* with the flipped fields
$unknownKeys = array_diff_key($value, $allowedKeys);
// The validation only passes if there are no unknown keys
return count($unknownKeys) === 0;
}
}
You can use this rule like so:
$rules = [
'data' => ['required','array', new KeysIn(['foo', 'bar'])],
'data.*' => ['exists:table,id']
];
The quick way
If you only need to do this once, you can do it the quick-and-dirty way, too:
$rules = [
'data' => [
'required',
'array',
fn(attribute, $value, $fail) => count(array_diff_key($value, $array_flip([
'foo',
'bar'
]))) > 0 ? $fail("{$attribute} contains invalid fields") : null
],
'data.*' => ['exists:table,id']
];
I think this is what you are looking:
$rules = [
'data.checkThisKeyForExists.value' => ['exists:table,id']
];

How can i skip unique field in Yii2?

I have a unique field that I check when editing or adding a new training course. But, for some reason, when I enter a value in a field, it does not show me a hint that the field is already taken.
In addition, I need to do this: when I change the values and did not change this unique field, but left it as it is, then the validor should not swear that the field is already taken.
Thank.
InfCourses Model:
public function rules()
{
return [
[['name', 'short_description', 'price', 'favorite', 'active', 'course_order', 'link'], 'required'],
[['price', 'active'], 'integer'],
[['favorite'], 'string'],
[['name', 'short_description', 'link'], 'string', 'max' => 255],
[['active'], 'exist', 'skipOnError' => true, 'targetClass' => InfStatuses::className(), 'targetAttribute' => ['active' => 'id']],
[['course_order'], 'integer', 'min' => 1],
[
['course_order'], 'unique',
'targetAttribute' => ['course_order'],
'filter' => ['!=', 'id', Yii::$app->request->get('id')],
],
];
}
Validator in InfCoursesController:
public function actionValidate()
{
$model = new InfCourses();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
Part of form code:
<?php $form = ActiveForm::begin([
'enableAjaxValidation' => true,
'validationUrl' => 'validate',
'options' => [
'data-pjax' => true,
]
]); ?>
Your validation is simply incorrect. You're using Yii::$app->request->get('id') in your rules, which is probably main source of your problems. Model should not access request or web user component directly - it breaks MVC pattern. Also putting values directly in rules in this way may give you unexpected results. You should check what query is generated by this validator, because it is hard to guess what is happening with such twisted rule.
But it may be easier to fix actionValidate() and distinguish between validating of new record and validating existing record:
public function actionValidate($id = null) {
if (empty($id)) {
$model = new InfCourses();
} else {
$model = $this->findModel($id);
}
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
}
Then you can limit your unique rule to:
[['course_order'], 'unique'],
Validator will be smart enough to detect that it is validating existing record and will not report unchanged fields values as duplicates. You just need to provide record ID in this action URL.
Well... I cut code below to each action: create/update.
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
Then remove validationUrl from form component. Inside model i make this rule [['course_order'],'unique']... Working fine...

Cakephp-3 conditional notEmpty validation behaviour

I need to do a conditional validation on a field: if other_field = 1 then this_field = notBlank. I can not find a way to do this.
My validator in table class:
public function validationDefault(Validator $validator) {
$validator->allowEmpty('inst_name');
$validator->add('inst_name', [
'notEmpty' => [
'rule' => 'checkInstName',
'provider' => 'table',
'message' => 'Please entar a name.'
],
'maxLength' => [
'rule' => ['maxLength', 120],
'message' => 'Name must not exceed 120 character length.'
]
]);
return $validator;
}
public function checkInstName($value, array $context) {
if ($context['data']['named_inst'] == 1) {
if ($value !== '' && $value !== null) {
return true;
} else {
return false;
}
} else {
return true;
}
}
Trouble here is, if i note, in the start of the method, that the field is allowed to be empty, when the entered value is empty, Cake doesn't run through any of my validations, because it's empty and allowed to be such. If i do not note that the field can be empty, Cake just runs "notEmpty" validation before my custom validation and outputs "This field cannot be left empty" at all time when it's empty.
How do i make Cake go through my conditional "notEmpty" validation?
I did try validation rule with 'on' condition with the same results.
Successfully tested, this might help you and others. CakePHP 3.*
$validator->notEmpty('event_date', 'Please enter event date', function ($context) {
if (!empty($context['data']['position'])) {
return $context['data']['position'] == 1; // <-- this means event date cannot be empty if position value is 1
}
});
In this example Event Date cannot be empty if position = 1 . You must put this condition if (!empty($context['data']['position'])) because the $context['data']['position'] value only will exist after the user click submit button. Otherwise you will get notice error.

Validating third variable on difference between 2 other variables in cakephp

I can't get my head around this one. I want to validate whether a third variable on a cakephp form is less than or equal to the difference between two other variables on the form. Has anyone tackled this or similar?
Paul
here is some code to show what I have done:
public function lessThanEqualTo($check, $otherfield) {
$value = array_values($check);
$compareTo = $this->data[$this->name][$otherfield];
if (!Validation::comparison($value[0], 'lessorequal', $compareTo)) {
return false;
}
else {
return true;
}
}
public function numDifference($startnumber, $usednumber) {
$sn = int($this->data[$this->name][$startnumber]);
$un = int($this->data[$this->name][$usednumber]);
return ($sn - $un);
}
Model contains validation: but the second rule is plain wrong, I have tried a number of things, but I am just coming up with rubbish:
public $validate = array(
'ag1_compl_dist_num'=>array(
'rule' => array('lessThanEqualTo','ag1_compl_start_number'),
'message' => 'Value must be less than starting number',
'allowEmpty' => true,
'required' => false
),
'ag1_compl_remain' => array(
'rule' => array('lessThanEqualTo','numDifference'),
'message' => 'Value must be less than difference between numbers',
'allowEmpty' => true,
'required' => false
)
);
Clearly, the call to numDifference should have something that identifies which two numbers to work out the difference between..
When you do:
'rule' => array('lessThanEqualTo','numDifference')
And then in your lessThanEqualTo function $compareTo = $this->data[$this->name][$otherfield];, you will look for numDifference field in your data object, which does not exist.
One way would be to create a lessThanEqualToDiff method like this:
public function lessThanEqualToDiff ($check, $startfield, $usedfield) {
$value = array_values($check);
$sn = intval($this->data[$this->name][$startfield]);
$un = intval($this->data[$this->name][$usedfield]);
return Validation::comparison($value[0], 'lessorequal', $sn - $un) ;
}
Then your rule:
public $validate = array(
'ag1_compl_remain' => array(
'rule' => array('lessThanEqualToDiff', 'ag1_compl_start_number', 'ag1_compl_used_number'),
'message' => 'Value must be less than difference between numbers',
'allowEmpty' => true,
'required' => false
)
);
So, the validation method should be like this:
public function lessThanEqualToDiff ($check, $startfield, $usedfield) {
$value = array_values($check);
$sn = (int)$this->data[$this->name][$startfield];
$un = (int)$this->data[$this->name][$usedfield]
return Validation::comparison($value[0], 'lessorequal', $sn - $un) ;
}
to use the php int feature correctly. Thanks to Holt and marian0 for their help.

CakePHP custom Validation rule checks unique field combination only on create

I have a Database with a User model. These Users should be unique by their name and birthday.
So I wrote a custom validation function called checkUnique
public function checkUnique($check){
$condition = array(
"User.name" => $this->data["User"]["name"],
"User.lastname" => $this->data["User"]["lastname"],
"User.birthday" => $this->data["User"]["birthday"]
);
$result = $this->find("count", array("conditions" => $condition));
return ($result == 0);
}
The validation rule in the model:
"name" => array(
"checkUnique" => array(
"rule" => array("checkUnique"),
"message" => "This User already exists.",
"on" => "create"
),
)
I have two problems.
The first: This validation rule also triggers on update action, implemented as
public function edit($id = null) {
if (!$this->User->exists($id)) {
throw new NotFoundException(__('Invalid User'));
}
if ($this->request->is(array('post', 'put'))) {
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('Update done.'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The user can't be saved.'));
}
} else {
$options = array('conditions' => array('User.' . $this->User->primaryKey => $id));
$this->request->data = $this->User->find('first', $options);
}
}
But I wrote "on" => "create", so why it triggers also on update?
The second problem:
If the validation rule only triggers on create, how can I manage, to trigger an validation error, if someone change the name, lastname and birthday like an other user in the Database? Then the unique validation rule should be triggered.
Remove the 'on' => 'create'. (You want to validate in both events).
Modify your custom validation rule to this
public function checkUnique() {
$condition = array(
"User.name" => $this->data["User"]["name"],
"User.lastname" => $this->data["User"]["lastname"],
"User.birthday" => $this->data["User"]["birthday"]
);
if (isset($this->data["User"]["id"])) {
$condition["User.id <>"] = $this->data["User"]["id"];
//your query will be against id different than this one when
//updating
}
$result = $this->find("count", array("conditions" => $condition));
return ($result == 0);
}

Resources