Model rule variable required without scenario (Yii2) - validation

How can I create a model rule that's only required when a certain value from the Database is 1?
I tried using a 'required', 'when' rule but that doesn't seem to update the client-side JavaScript.
I also tried a custom inline validator but that doesn't seem to post an empty field.
Scenario's aren't an option I think as I have 6 fields and can have any combination of required/not required.
EDIT
At the moment I just never add the required rules, instead of directly returning the rules I store them in a variable. $rules = []
Then before I return the variable I add the required options to the array.
if($x->x_required)
$rules[] = ['your-field', 'required', 'on' => 'your-scenario'];
This is a quickfix and I don't really like it, but it works. I'm not sure if there is a better way of doing this.

You need to use combination required with when, but for client side validation you need additionally specify whenClient property.
Example (add this to your rules()):
[
'attributeName',
'required',
'when' => function ($model) {
return $model->country == Country::USA;
},
'whenClient' => function (attribute, value) {
return $('#country').value == 'USA';
},
],
Official docs:
RequiredValidator
Validator $when
Validator $whenClient

Related

Yii2 unique validator as adhoc validator

How can I convert this:
public function rules() {
return [
[['attr1', 'attr2'], 'unique', 'skipOnEmpty' => false, 'skipOnError' => false, targetAttribute' => ['attr1', 'attr2']],
into an inline validator, or what would be the equivalent of this as an inline validator? Thank you.
Is not possible to use Unique validator as ad hoc as explained in https://www.yiiframework.com/doc/guide/2.0/en/input-validation#ad-hoc-validation:
Note: Not all validators support this type of validation. An example is the unique core validator which is designed to work with a model only.
You will have to build it by yourself.
Other way could be to wrap the query into a try catch, assuming that you have a unique key on the db. The db will complain about the query and you can catch the error.
Edit for inline validation:
This is a very specific validator, something you will not reuse so, lets write it inline as an anonymous function:
public function rules(){
[['attr1'], function(){
//we know the names of the attribute so we use them here directly instead of pass as a parameter
if(self::find()->where([
'attr1' => $this->attr1,
'attr2' => $this->attr2
])->exists()){
$this->addError('attr1', 'Error message');
$this->addError('attr2', 'Error message');
}
}]
}
Notice I just registered validation for attr1. If you register also attr2, you will end with 2 errors per each attribute.

How can I validate GET controller params in CakePHP 2?

Given this on the model:
public $validate = [
'amount' => array(
'rule' => array('comparison', '>=', 0),
'message' => 'You must buy over 0 of this item!'
)
];
How can I validate param #2 of the below?
public function buy(int $item, int $amount) {
Validation seems to be built only for POST, which I'd like to opt out of here.
First things first, modifying the database with GET requests is an anti-pattern for many different reasons. Even if you assume a friendly user agent (which you never should!), browsers can behave quirky and do unexpected stuff like for example sending GET request multiple times (that is perfectly valid as GET is not ment to modify data), which they usually won't for POST/PUT/DELETE.
I would strongly suggest to change your endpoint to handle POST requests instead.
That being said, you can generally validate whatever you want, the validation mechanisms first and foremost just validate data, they don't know or care where it stems from. You can hand over whatever data you want to your model, and let it validate it:
$data = array(
'item' => $item,
'amount' => $amount,
);
$this->ModelName->set($data);
if ($this->ModelName->validates()) {
// data is valid
} else {
// data is invalid
$errors = $this->ModelName->validationErrors;
}
Moreover you can use CakePHP's validation methods completely manually too:
App::uses('Utility', 'Validation');
$isValid = Validation::comparison($amount, '>' 0);
This example of course doesn't make too much sense, given that $isValid = $amount > 0 would do the same, however it should just show that you can validate anything everywhere without models being involved.
See also
Cookbook > Models > Data Validation > Validating Data from the Controller
Cookbook > Models > Data Validation > Core Validation Rules

Laravel - form validation to pass only if certain field is empty

So I'm building a form and I need specific fields to be empty.
They return an empty string and from other similar questions, I looked for
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class
in Kernel.php which is commented out by default, I believe.
I don't want to change its behavior since it's a global middleware.
I have tried making them nullable, string|sometimes, present|max:0 yet none of these give me the desired result. I want the validation to pass only if the fields are empty.
Any help will be deeply appreciated.
So as I understood, you want for specific field to be required, if the other field in form is empty? To achieve that, you can use required_without property in Request validation like this:
public function rules()
{
return [
'filed_name' => 'required_without:other_field_name',
];
}
public function messages()
{
return [
'filed_name.required_without' => 'filed_name is required.',
];
}
More on validation on official documentation.

Validation check field not empty

We're trying to have one or another field validated, the second field only shows up when they choose to not fill in the first. So we only need the second field to validate if the first is left empty from them skipping over it.
For context its to checek the make of an appliance, we have a list of brands/makes known to the system but an option to write it manually if yours doesnt show up. But we need to validate that the manual entry field isn't empty, but only if they've skipped over the first list.
'single_item_make' => 'required_if:policy_type,single|required_if:single_item_make_other,',
'single_item_make_other' => 'required_if:policy_type,single|required_if:single_item_make,'
We tried the above and it didnt work, we cant seem to find anything in the docs about checking fields for being empty.
Only one of these two fields will be submitted at a time.
You can not combine the required_if with the required_without in this case, because it conflicts.
In your current code, the first rule on both is:
required_if:policy_type,single
Which requires both fields if policy_type === 'single', if 1 of the fields is empty this validation will fail.
A solution might be to use complex conditional validation, like so:
$v = Validator::make($data, [
'policy_type' => [
'required',
'in:single,x,y', // ?
],
// some other static validation rules you have
]);
// conditional validation based on policy_type === 'single';
$v->sometimes('single_item_make', 'required_without:single_item_make_other', function ($input) {
return $input->policy_type === 'single';
});
$v->sometimes('single_item_make_other', 'required_without:single_item_make', function ($input) {
return $input->policy_type === 'single';
});
This will only check that both can't be empty at the same time and that one field is required when the other one is empty.
However, this will leave the option for the user to fill in both.
If you would want to validate that both can't be empty, but only 1 can be set at the same time (xor), you would have to extend your validator as this does not exist in Laravel.
Put this in your AppServiceProvider's boot() method:
Validator::extendImplicit('xor', function ($attribute, $value, $parameters, $validator) {
return empty($value) || empty(data_get($validator->getData(), $parameters[0]));
});
Then you can use:
$v->sometimes('single_item_make', 'required_without:single_item_make_other|xor:single_item_make_other', function ($input) {
return $input->policy_type === 'single';
});
$v->sometimes('single_item_make_other', 'required_without:single_item_make|xor:single_item_make', function ($input) {
return $input->policy_type === 'single';
});
In this case, required_without makes sure that if 1 is empty the other 1 is required and the xor validation makes sure that if 1 is set, the other 1 can not have a value.
You can add custom error messages in your validation or use a custom validator and pass those validation messages there.
More info: https://laravel.com/docs/5.7/validation#conditionally-adding-rules
I have not tested both pieces of code, but they should work.
As the required_without docs suggest, you need to use it as below:
'single_item_make' => 'required_if:policy_type,single|required_without:single_item_make_other,',
'single_item_make_other' => 'required_if:policy_type,single|required_without:single_item_make,'

How to create scenario in Yii2 with no validation rules active?

I have MyEntity.php model. As a part of the model script, there are some rules and some scenarios defined:
public function rules()
{
return [
[['myentity_id', 'myentity_title', 'myentity_content', 'myentity_date'], 'required'],
[['myentity_id'], 'integer'],
[['myentity_title', 'myentity_content'], 'string', 'max' => 120],
[['myentity_date'], 'safe'],
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['scenario_one'] = ['myentity_id', 'myentity_title'];
$scenarios['scenario_two'] = ['myentity_id', 'myentity_content'];
return $scenarios;
}
I need to be able to have different scenarios, and for different actions only certain validations (by params) to be active. For example, scenario_one for actionOne, scenario_two for actionTwo etc.
Here is some small part of code from the controller:
public function actionOne($id)
{
$modelMyEntity = $this->findModel($id);
$modelMyEntity->scenario = 'scenario_one';
.
.
.
}
public function actionTwo($id)
{
$modelMyEntity = $this->findModel($id);
$modelMyEntity->scenario = 'scenario_two';
.
.
.
}
Now I want to have a scenario_three where there should NOT be any validations at all. I'll have additional checks in code that will prevent failing while storing in database. I'll just need to make sure that no validations are applied because it's preventing my form from submitting. If I don't apply any scenario, then the default scenario is applied (all listed validations will be active, which is totally opposite of the scenario I need).
To be able to do this, you need to do a few things (including the ones you almost did yourself):
In your controller, write $modelMyEntity->scenario = 'scenario_three';
In model, add an additional scenario array 'scenario_three' in scenarios() method:
Like this:
$scenarios['scenario_three'] = ['myentity_id', 'myentity_content'];
Finally, most changes will be required in rules() as you will need to add where to include or exclude specific scenarios.
Basically, in each rule you can now write except conditional and point which attributes will not comply to which scenario. So in your example, let's say, let's exclude all attributes for scenario_three:
[['myentity_id', 'myentity_title', 'myentity_content', 'myentity_date'], 'required', 'except' => 'scenario_three'],
[['myentity_id'], 'integer', 'except' => 'scenario_three'],
[['myentity_title', 'myentity_content'], 'string', 'max' => 120, 'except' => 'scenario_three'],
[['myentity_date'], 'safe'],
This is a little different solution to how to ignore rules but I find this more attractive because in future it would be easier to add/remove specific attributes for this scenario and will also be easier for other developers (if there are more than just you) to understand what you're trying to do.
But I think #iStranger's solution works too (much simpler).
If I correctly understood your question, you can assign scenario_three as current scenario: model will not find matched rules and will skip validation checks.
public function actionThree($id)
{
$modelMyEntity = $this->findModel($id);
$modelMyEntity->scenario = 'scenario_three';
.
.
.
}
UPD:
However I strictly recommend to define explicitly all scenarios and corresponding active attributes (in scenario method) and remove $scenarios = parent::scenarios();, because it can cause unnecessary effects. Parent implementation is mostly developed to backward compatibility with Yii1, that has no scenarios() methods. And it is assumed usually if you override scenarios() method, you should not merge your explicitly defined scenarios with parent implementation.

Resources