Yii2: custom validation for multiple attributes using OR not working - php

I am trying to write a rule that validates if attribute_a or attribute_b is set;
one of the following attributes must be set : licitatii_publice or licitatiile_atribuite
The following code does not work;
<?php
namespace common\models;
use yii\base\Model;
class AbonamentValidare extends Model {
public $licitatii_publice;
public $licitatiile_atribuite;
public $zone;
public $judete;
public $tari;
public static $targetAttribute = [];
public function rules() {
return [
[['zone'], 'required'],
[['licitatii_publice', 'licitatiile_atribuite', 'tari', 'judete'], 'safe'],
['licitatii_publice', 'validate_tip_licitatie', 'targetAttribute' => ['licitatii_publice', 'licitatiile_atribuite']],
];
}
function validate_tip_licitatie($attribute, $param) {
print_r($attribute);
$this->addError($attribute, 'eroarea');
}
public function attributeLabels() {
return array(
'licitatii_publice' => 'lp',
'licitatiile_atribite' => 'la',
'tari' => 'tari',
'judete' => 'judete',
'zone' => 'zone',
);
}
public function save() {
return false;
}
}
?>

Just wanted to update this answer for Yii2 case.
In Yii2 the validators have a skipOnEmpty attribute which is by default set to true. This implies custom validators will not be called if the field is empty, which might not be the required behavior, especially in this case where either of one attribute is mandatory. In order to fix this issue we need to set the skipOnEmpty to false as shown below.
[['licitatii_publice, licitatiile_atribuite'], 'validate_tip_licitatie', 'skipOnEmpty'=> false],

Well what I have done in a case like this is to create the validator like this:
................
return [
[['zone'], 'required'],
[['licitatii_publice', 'licitatiile_atribuite', 'tari', 'judete'], 'safe'],
[['licitatii_publice, licitatiile_atribuite'], 'validate_tip_licitatie'],
];
............
function validate_tip_licitatie($attribute, $param) {
if(!$this->licitatii_publice && $this->licitatiile_atribuite)
$this->addError($attribute, 'eroarea');
}
In this way you show both fields with an error.
However I have done this in Yii1, but from what I read Yii2 should be the same. The logic is the same.
If you want to show error only for 1 attribute you can always just use
return [
[['zone'], 'required'],
[['licitatii_publice', 'licitatiile_atribuite', 'tari', 'judete'], 'safe'],
[['licitatii_publice'], 'validate_tip_licitatie'],
];
What you are trying to do is more fancy :), I get that. If you really want to use targetAttribute you might have to do it like this
https://github.com/yiisoft/yii2/blob/master/framework/validators/ExistValidator.php
Just build your own validator class.
Well. After reading about the exist validator i believe that is exactly what you need. It has examples on how to use it.

Related

Yii2 - Validate attribute with keys

I have a problem with attribute key validation.
In model I have property like:
public $_fileds = [];
and My view
<?= $form->field($model, '_fileds[name]') ?>
<?= $form->field($model, '_fileds[type]') ?>
I want to set "_fields[name]" as requierd field. For example I tried add to rules in model something like this:
public function rules()
{
return [
[['_fields[name]'], 'required']
];
}
but this not working :(
Does anyone have any solution?
Sorry for my not good English and thanks in advance for all solutions.
You can write your own validation.
Remove: public $_fileds = [];
public function rules()
{
return [
[['_fields[name]'], 'validateName']
];
}
In same model class:
public function validateName():bool
{
... your checks(if fails return false) ...
return true;
}
But there is one issue: if some of checks fails you, in normal cases, need to set error message using $this->addError() which will correctly work with attributes which are not array elements because as first element of $this->addError() you need to pass attribute name as string where '_fileds' is ok but '_fileds[name]' won't work. So you also need to find a way to return errors if they appear. Same issue with attribute labels and hints.

How to avoid duplication in Laravel validation rules

For validating form validation rules I currently stored them in User Model and use it in Register Controller, User controller in admin panel, User Controller in APIs and some other places, but currently it's very hard to maintain because each controller needs a slightly different set of rules and when I change the rules in User Model other controllers will not work anymore. So how to avoid duplication in rules and still keep the code maintainable?
Approach I often use is to write a HasRules trait for my models, it looks something like this:
trait HasRules
{
public static function getValidationRules(): array
{
if (! property_exists(static::class, 'rules')) {
return [];
}
if (func_num_args() === 0) {
return static::$rules;
}
if (func_num_args() === 1 && is_string(func_get_arg(0))) {
return array_get(static::$rules, func_get_arg(0), []);
}
$attributes = func_num_args() === 1 && is_array(func_get_arg(0))
? func_get_arg(0)
: func_get_args();
return array_only(static::$rules, $attributes);
}
}
Looks messy, but what it does is allows you to retrieve your rules (from a static field if such exists) in a variety of ways. So in your model you can:
class User extends Model
{
use HasRules;
public static $rules = [
'name' => ['required'],
'age' => ['min:16']
];
...
}
Then in your validation (for example, in your FormRequest's rules() method or in your controllers when preparing rules array) you can call this getValidationRules() in variety of ways:
$allRules = User::getValidationRules(); // if called with no parameters all rules will be returned.
$onlySomeRules = [
'controller_specific_field' => ['required'],
'name' => User::getValidationRules('name'); // if called with one string parameter only rules for that attribute will be returned.
];
$multipleSomeRules = User::getValidationRules('name', 'age'); // will return array of rules for specified attributes.
// You can also call it with array as first parameter:
$multipleSomeRules2 = User::getValidationRules(['name', 'age']);
Don't be afraid to write some code for generating your custom controller specific rules. Use array_merge and other helpers, implement your own (for example, a helper that adds 'required' value to array if it's not there or removes it etc). I strongly encourage you to use FormRequest classes to encapsulate that logic though.
You can try using laravel's validation laravel documentation
it is really easy to use and maintain just follow these steps:
run artisan command: php artisan make:request StoreYourModelName
which will create a file in App/Http/Requests
in the authorize function set it to:
public function authorize()
{
return true;
}
then write your validation logic in the rules function:
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
Custom error messages add this below your rules function:
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
Lastly to use this in your controller just add it as a parameter in your function.
public function create(Request $request, StoreYourModelName $storeYourModelName)
{
//
}
and that's all you need to do this will validate on form submission if validation passes it will go to your controller, keep in mind your validation logic does not have to be like mine thought i would show you one way that it can be done..

Laravel - limit FormRequest to certain parameters

In a form request class I use a method like this to validate input data.
class SignupRequest extends FormRequest
{
...
public function rules()
{
return [
'user.email' => 'required_with:user|email',
'user.domain_name' => 'required_with:user|string',
'user.password' => 'required_with:user|string|min:8',
'user.username' => 'required_with:user',
];
}
...
}
Later in a controller I use something like this
$data = $request->get('user', []);
return $this->response($this->userService->create($data, false), 201);
I want somehow to write to my SignupRequest which fields it should allow to be passed. So when later I get $data = $request->get('user', []); I'm sure there are only allowed fields in it.
Is this possible inside the FormRequest?
P.S. I'm aware of $request->only(['field1', 'field2', 'field3']) way, but if I want to limit the fields in SignupRequest extends FormRequest. Because if I use $request->only([...]) in my code several times, I would have to change it several times later. I want to keep it in one place.
You wouldn't need to do this with the request.
One option would be to do something like:
$user = $request->input('user', []);
$data = array_only($user, ['email', 'domain_name', 'password', 'username']);
Or you could even inline it:
$data = array_only($request->input('user', []), ['email', 'domain_name', 'password', 'username']);
Hope this helps!
FormRequest is meant to validate your request data, not control them. You could always extract the inputs you need by doing so.
$data = $request->only(['user.name', 'user.password']);
Edit : Based on your comment, you can do something like this. This allows you to store all the field names within a single request to keep them organised and easier to update.
Add this to your SignupRequest
public function loginData()
{
return array_only($this->input('user', []), ['username', 'password']);
}
Use it in the controller like so
$request->loginData();
return $this->response($this->userService->create($request->loginData(), false), 201);

How can I get the list of required parameters of a yii 2.0 action as an array?

When a user visits a particular url in my yii 2.0 application without required parameters, I want to present a form to collect the required missing parameters.
for this purpose, I need the names of missing parameters, e.g. I have a function
public function actionBlast ($bomb, $building) {
}
I expect the results as an array like this
$args = [0=>'bomb', 1=>'building'];
I tried func_get_args() but it returns null, and the undocumented ReflectionFunctionAbstract::getParameters ( void ) etc. Any other way out?
I think the best way to achieve what you want is to override the default ErrorAction.
Inside your controllers directory, create:
controllers
actions
ErrorAction.php
In ErrorAction.php, add:
<?php
namespace frontend\controllers\actions;
use Yii;
use yii\web\ErrorAction as DefaultErrorAction;
class ErrorAction extends DefaultErrorAction
{
public function run()
{
$missing_msg = 'Missing required parameters:';
$exception = Yii::$app->getErrorHandler()->exception;
if (substr($exception->getMessage(), 0, strlen($missing_msg)) === $missing_msg) {
$parameters = explode(',', substr($exception->getMessage(), strlen($missing_msg)));
return $this->controller->render('missing_params_form' ?: $this->id, [
'parameters' => $parameters,
]);
}
return parent::run();
}
}
In your controller add:
public function actions()
{
return [
'error' => [
'class' => 'frontend\controllers\actions\ErrorAction',
],
];
}
and create a view "missing_params_form.php" in your controller `s view directory, where you can generate your form fields.
I believe this to be your best option, though you may need to update it in case a Yii update changes the error message.

Check one attribute from a list is set

I have a model like so
class Person extends yii\db\ActiveRecord
{
public $desk_no;
public $mobl_no;
public $faxx_no;
}
I want to add a validation rule which would read something like this in English
One of the *_no attributes is required; I don't care which one.
How do I go about that in yii2?
A bit of a hack but you can use the required validator with conditional validation:
public function rules() {
$oneOfUs = ['desk_no', 'mobl_no', 'faxx_no'];
return [
... //Your other rules here
[$oneOfUs, 'required', 'when' => function($model, $attribute) use ($oneOfUs) {
foreach (array_diff($oneOfUs, [$attribute]) as $f) {
return !!($model->$f);
}
return false;
}]
];
}
The above code works since the condition function will return true if any attribute except the current one is set. This will in turn run therequired validator on the current attribute.
you could use such validation for example
['desk_no', 'required', 'when' => function($model) {
return is_null($model->mobl_no)&&is_null($model->faxx_no);
}, message => "One of the *_no attributes is required; I don't care which one." ],
but if this logic can change, i prefer custom validator

Categories