improve Laravel array validation rule - php

I am searching for a improvement to this Laravel FormRequest rule set:
protected $stopOnFirstFailure = true;
public function rules()
{
return [
'reasons' => ['required', 'array'],
'reasons.*.id' => ['exists:reject_reasons,id'],
'reasons.*.text' => Rule::forEach(function ($value, $attribute, $data) {
// get the key from the array attribute
list(, $key) = explode('.', $attribute);
$reason = RejectReason::find($this->reasons[$key]['id']);
return [
Rule::requiredIf(optional($reason)->with_additional_entry),
'max:255'
];
}),
];
}
The .text element should only be required and checked if the id element exists in the database and the attribute with_additional_entry is true.
Things that could be improved:
If the id is not in the database the validator does not stop after the reasons.*.id rule, so I have to check for the $reason Object -> optional()
Get the key of the form array list(, $key) = explode('.', $attribute);
Does anyone have a better solution to this?

Related

Can i ignore unique value when update record laravel 5.8? [duplicate]

I have a Laravel User model which has a unique validation rule on username and email. In my Repository, when I update the model, I revalidate the fields, so as to not have a problem with required rule validation:
public function update($id, $data) {
$user = $this->findById($id);
$user->fill($data);
$this->validate($user->toArray());
$user->save();
return $user;
}
This fails in testing with:
ValidationException: {"username":["The username has already been
taken."],"email":["The email has already been taken."]}
Is there a way of fixing this elegantly?
Append the id of the instance currently being updated to the validator.
Pass the id of your instance to ignore the unique validator.
In the validator, use a parameter to detect if you are updating or creating the resource.
If updating, force the unique rule to ignore a given id:
//rules
'email' => 'unique:users,email_address,' . $userId,
If creating, proceed as usual:
//rules
'email' => 'unique:users,email_address',
Another elegant way...
In your model, create a static function:
public static function rules ($id=0, $merge=[]) {
return array_merge(
[
'username' => 'required|min:3|max:12|unique:users,username' . ($id ? ",$id" : ''),
'email' => 'required|email|unique:member'. ($id ? ",id,$id" : ''),
'firstname' => 'required|min:2',
'lastname' => 'required|min:2',
...
],
$merge);
}
Validation on create:
$validator = Validator::make($input, User::rules());
Validation on update:
$validator = Validator::make($input, User::rules($id));
Validation on update, with some additional rules:
$extend_rules = [
'password' => 'required|min:6|same:password_again',
'password_again' => 'required'
];
$validator = Validator::make($input, User::rules($id, $extend_rules));
Nice.
Working within my question:
public function update($id, $data) {
$user = $this->findById($id);
$user->fill($data);
$this->validate($user->toArray(), $id);
$user->save();
return $user;
}
public function validate($data, $id=null) {
$rules = User::$rules;
if ($id !== null) {
$rules['username'] .= ",$id";
$rules['email'] .= ",$id";
}
$validation = Validator::make($data, $rules);
if ($validation->fails()) {
throw new ValidationException($validation);
}
return true;
}
is what I did, based on the accepted answer above.
EDIT: With Form Requests, everything is made simpler:
<?php namespace App\Http\Requests;
class UpdateUserRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|unique:users,username,'.$this->id,
'email' => 'required|unique:users,email,'.$this->id,
];
}
}
You just need to pass the UpdateUserRequest to your update method, and be sure to POST the model id.
Unique Validation With Different Column ID In Laravel
'UserEmail'=>"required|email|unique:users,UserEmail,$userID,UserID"
or what you could do in your Form Request is (for Laravel 5.3+)
public function rules()
{
return [
'email' => 'required|email|unique:users,email,'. $this->user
//here user is users/{user} from resource's route url
];
}
i've done it in Laravel 5.6 and it worked.
'email' => [
'required',
Rule::exists('staff')->where(function ($query) {
$query->where('account_id', 1);
}),
],
'email' => [
'required',
Rule::unique('users')->ignore($user->id)->where(function ($query) {
$query->where('account_id', 1);
})
],
Laravel 5 compatible and generic way:
I just had the same problem and solved it in a generic way. If you create an item it uses the default rules, if you update an item it will check your rules for :unique and insert an exclude automatically (if needed).
Create a BaseModel class and let all your models inherit from it:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model {
/**
* The validation rules for this model
*
* #var array
*/
protected static $rules = [];
/**
* Return model validation rules
*
* #return array
*/
public static function getRules() {
return static::$rules;
}
/**
* Return model validation rules for an update
* Add exception to :unique validations where necessary
* That means: enforce unique if a unique field changed.
* But relax unique if a unique field did not change
*
* #return array;
*/
public function getUpdateRules() {
$updateRules = [];
foreach(self::getRules() as $field => $rule) {
$newRule = [];
// Split rule up into parts
$ruleParts = explode('|',$rule);
// Check each part for unique
foreach($ruleParts as $part) {
if(strpos($part,'unique:') === 0) {
// Check if field was unchanged
if ( ! $this->isDirty($field)) {
// Field did not change, make exception for this model
$part = $part . ',' . $field . ',' . $this->getAttribute($field) . ',' . $field;
}
}
// All other go directly back to the newRule Array
$newRule[] = $part;
}
// Add newRule to updateRules
$updateRules[$field] = join('|', $newRule);
}
return $updateRules;
}
}
You now define your rules in your model like you are used to:
protected static $rules = [
'name' => 'required|alpha|unique:roles',
'displayName' => 'required|alpha_dash',
'permissions' => 'array',
];
And validate them in your Controller. If the model does not validate, it will automatically redirect back to the form with the corresponding validation errors. If no validation errors occurred it will continue to execute the code after it.
public function postCreate(Request $request)
{
// Validate
$this->validate($request, Role::getRules());
// Validation successful -> create role
Role::create($request->all());
return redirect()->route('admin.role.index');
}
public function postEdit(Request $request, Role $role)
{
// Validate
$this->validate($request, $role->getUpdateRules());
// Validation successful -> update role
$role->update($request->input());
return redirect()->route('admin.role.index');
}
That's it! :) Note that on creation we call Role::getRules() and on edit we call $role->getUpdateRules().
I have BaseModel class, so I needed something more generic.
//app/BaseModel.php
public function rules()
{
return $rules = [];
}
public function isValid($id = '')
{
$validation = Validator::make($this->attributes, $this->rules($id));
if($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
In user class let's suppose I need only email and name to be validated:
//app/User.php
//User extends BaseModel
public function rules($id = '')
{
$rules = [
'name' => 'required|min:3',
'email' => 'required|email|unique:users,email',
'password' => 'required|alpha_num|between:6,12',
'password_confirmation' => 'same:password|required|alpha_num|between:6,12',
];
if(!empty($id))
{
$rules['email'].= ",$id";
unset($rules['password']);
unset($rules['password_confirmation']);
}
return $rules;
}
I tested this with phpunit and works fine.
//tests/models/UserTest.php
public function testUpdateExistingUser()
{
$user = User::find(1);
$result = $user->id;
$this->assertEquals(true, $result);
$user->name = 'test update';
$user->email = 'ddd#test.si';
$user->save();
$this->assertTrue($user->isValid($user->id), 'Expected to pass');
}
I hope will help someone, even if for getting a better idea. Thanks for sharing yours as well.
(tested on Laravel 5.0)
A simple example for roles update
// model/User.php
class User extends Eloquent
{
public static function rolesUpdate($id)
{
return array(
'username' => 'required|alpha_dash|unique:users,username,' . $id,
'email' => 'required|email|unique:users,email,'. $id,
'password' => 'between:4,11',
);
}
}
.
// controllers/UsersControllers.php
class UsersController extends Controller
{
public function update($id)
{
$user = User::find($id);
$validation = Validator::make($input, User::rolesUpdate($user->id));
if ($validation->passes())
{
$user->update($input);
return Redirect::route('admin.user.show', $id);
}
return Redirect::route('admin.user.edit', $id)->withInput()->withErrors($validation);
}
}
If you have another column which is being used as foreign key or index then you have to specify that as well in the rule like this.
'phone' => [
"required",
"phone",
Rule::unique('shops')->ignore($shopId, 'id')->where(function ($query) {
$query->where('user_id', Auth::id());
}),
],
I am calling different validation classes for Store and Update. In my case I don't want to update every fields, so I have baseRules for common fields for Create and Edit. Add extra validation classes for each. I hope my example is helpful. I am using Laravel 4.
Model:
public static $baseRules = array(
'first_name' => 'required',
'last_name' => 'required',
'description' => 'required',
'description2' => 'required',
'phone' => 'required | numeric',
'video_link' => 'required | url',
'video_title' => 'required | max:87',
'video_description' => 'required',
'sex' => 'in:M,F,B',
'title' => 'required'
);
public static function validate($data)
{
$createRule = static::$baseRules;
$createRule['email'] = 'required | email | unique:musicians';
$createRule['band'] = 'required | unique:musicians';
$createRule['style'] = 'required';
$createRule['instrument'] = 'required';
$createRule['myFile'] = 'required | image';
return Validator::make($data, $createRule);
}
public static function validateUpdate($data, $id)
{
$updateRule = static::$baseRules;
$updateRule['email'] = 'required | email | unique:musicians,email,' . $id;
$updateRule['band'] = 'required | unique:musicians,band,' . $id;
return Validator::make($data, $updateRule);
}
Controller:
Store method:
public function store()
{
$myInput = Input::all();
$validation = Musician::validate($myInput);
if($validation->fails())
{
$key = "errorMusician";
return Redirect::to('musician/create')
->withErrors($validation, 'musicain')
->withInput();
}
}
Update method:
public function update($id)
{
$myInput = Input::all();
$validation = Musician::validateUpdate($myInput, $id);
if($validation->fails())
{
$key = "error";
$message = $validation->messages();
return Redirect::to('musician/' . $id)
->withErrors($validation, 'musicain')
->withInput();
}
}
public static function custom_validation()
{
$rules = array('title' => 'required ','description' => 'required','status' => 'required',);
$messages = array('title.required' => 'The Title must be required','status.required' => 'The Status must be required','description.required' => 'The Description must be required',);
$validation = Validator::make(Input::all(), $rules, $messages);
return $validation;
}
I had the same problem.
What I've done: add in my view hidden field with id of a model and in validator check the unique, only if I've get some id from view.
$this->validate(
$request,
[
'index' => implode('|', ['required', $request->input('id') ? '' : 'unique:members']),
'name' => 'required',
'surname' => 'required',
]
);
You can trying code bellow
return [
'email' => 'required|email|max:255|unique:users,email,' .$this->get('id'),
'username' => 'required|alpha_dash|max:50|unique:users,username,'.$this->get('id'),
'password' => 'required|min:6',
'confirm-password' => 'required|same:password',
];
Laravel 5.8 simple and easy
you can do this all in a form request with quite nicely. . .
first make a field by which you can pass the id (invisible) in the normal edit form. i.e.,
<div class="form-group d-none">
<input class="form-control" name="id" type="text" value="{{ $example->id }}" >
</div>
...
Then be sure to add the Rule class to your form request like so:
use Illuminate\Validation\Rule;
... Add the Unique rule ignoring the current id like so:
public function rules()
{
return [
'example_field_1' => ['required', Rule::unique('example_table')->ignore($this->id)],
'example_field_2' => 'required',
];
... Finally type hint the form request in the update method the same as you would the store method, like so:
public function update(ExampleValidation $request, Examle $example)
{
$example->example_field_1 = $request->example_field_1;
...
$example->save();
$message = "The aircraft was successully updated";
return back()->with('status', $message);
}
This way you won't repeat code unnecessarily :-)
public function rules()
{
if ($this->method() == 'PUT') {
$post_id = $this->segment(3);
$rules = [
'post_title' => 'required|unique:posts,post_title,' . $post_id
];
} else {
$rules = [
'post_title' => 'required|unique:posts,post_title'
];
}
return $rules;
}
For a custom FormRequest and Laravel 5.7+ you can get the id of your updated model like this:
public function rules()
{
return [
'name' => 'required|min:5|max:255|unique:schools,name,'.\Request::instance()->id
];
}
For anyone using a Form request
In my case i tried all of the following none of them worked:
$this->id, $this->user->id, $this->user.
It was because i could not access the model $id nor the $id directly.
So i got the $id from a query using the same unique field i am trying to validate:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$id = YourModel::where('unique_field',$this->request->get('unique_field'))->value('id');
return [
'unique_field' => ['rule1','rule2',Rule::unique('yourTable')->ignore($id)],
];
}
It will work 100%
I have both case implement like One case is same form field in database table products and other is products_name is form field and in table, it's name is name, how we can validate and ignore that id while updating. I have encrypted that so i'm decrypted id, if you are encrypt then you will decrypt otherwise pass it as it's coming from the form.
$request->validate([
'product_code' => 'required|unique:products,product_code,'.decrypt($request->hiddenProductId),
'products_name' => 'required|unique:products,name,'.decrypt($request->hiddenProductId),
]);
there is detailed and straightforward answer to this question, I was looking for too
https://laravel.com/docs/9.x/validation#rule-unique

Laravel form validation: same as field a or b

I have 3 fields: player1, player2 and winner. I want to validate that winner is equal to either player1 or player 2.
I tried looking for a way to add an 'or' clause to the validation but couldn't find anything, only workarounds for different problems.
You can use a custom validation rule to do this. There's nothing built in that allows comparison to one of many other fields. The closest is the same check, but it only checks against one other field.
(I've added dd() to dump data, you can remove them)
The $values will be what comes from your input.
The $validationRules can be adjusted for your needs.
Validator::extend('equals_one_of', function($attribute, $value, $parameters, \Illuminate\Validation\Validator $validator) {
$fields = $validator->getData(); // all posted values
foreach($parameters as $param) { // this is each of the comma separated fields in the validationRules array
if ($value == $fields[$param]) {
dd("matched");
return true;
}
}
dd("no match");
return false;
});
$values = [
'player1' => 'test1',
'player2' => 'test2',
'winner' => 'test1'
];
$validationRules = [
'player1' => 'required',
'player2' => 'required',
'winner' => 'required|equals_one_of:player1,player2'
];
$validate = Validator::make($values, $validationRules);
// use your validator as normal.
dd($validate->validate());
Actually this rule should be registered with extendDependent to correctly resolve parameter names when used with arrays.
Final code:
class OneOfRule
{
public function validate($attribute, $value, $parameters, $validator) {
if(is_null($value)) return true;
foreach($parameters as $param) {
$other = Arr::get($validator->getData(), $param);
if ($value === $other) {
return true;
}
}
return false;
}
}
public function boot()
{
Validator::extendDependent('one_of', 'App\Http\Validation\Rules\OneOfRule#validate');
}

Laravel 5.6 - update unique attribute

I have a Form Request called JuridicoFormRequest with the method "rules":
public function rules() {
$rules = [
'rif'=>'required|max:40|unique:juridico,rif',
'correo'=>'required|max:40|unique:juridico,correo',
'd_social'=>'required|max:50',
'r_social'=>'required|max:50',
'pagina_web'=>'max:40|unique:juridico,pagina_web',
'capital'=>'required|numeric',
'fk_lugar'=>'required|integer',
'fk_lugar_fiscal'=>'required|integer',
'fk_tienda'=>'integer',
'num_carnet'=>'max:50'
];
if ($this->juridico){
$rules['rif'] = 'required|max:40';
$rules['correo'] = 'required|max:40';
$rules['pagina_web'] = 'required|max:40';
}
return $rules;
}
The problem is that in the DataBase, "Correo" attribute must be unique, same for "pagina_web". When I try to update some registry "A" and I write the same "Correo" of another registry "B" or the same "pagina_web" of another registry, throws me an error. This does not happen with the "rif" because is the primary key and I already validated it with:
if ($this->juridico) { /*....*/ }
If I do:
if ($this->juridico) {
$rules['rif'] = 'required|max:40';
$rules['correo'] = 'required|max:40|unique:juridico,correo';
$rules['pagina_web'] = 'required|max:40|unique:juridico,pagina_web';
}
Throws me the validation message (not an exception) "correo must be unique" while that "correo" and that "pagina_web" is already taken by the registry that I'm updating.
Is there a way to solve this?
UPDATE: I solved in this way:
public function rules(){
$rules = [
'rif'=>'required|max:40|unique:juridico,rif',
'correo'=>'required|max:40|unique:juridico,correo',
'd_social'=>'required|max:50',
'r_social'=>'required|max:50',
'pagina_web'=>'max:40|unique:juridico,pagina_web',
'capital'=>'required|numeric',
'fk_lugar'=>'required|integer',
'fk_lugar_fiscal'=>'required|integer',
'fk_tienda'=>'integer',
'num_carnet'=>'max:50'
];
if ($this->juridico){
$rules['rif'] = 'required|max:40';
$rules['correo'] = 'required|max:40|unique:juridico,correo,'.$this->juridico.',rif';
$rules['pagina_web'] = 'required|max:40|unique:juridico,pagina_web,'.$this->juridico.',rif';
}
return $rules;
}
use Illuminate\Validation\Rule;
Validator::make($data, [
'email' => [
'required',
Rule::unique('users')->ignore($user->id),
],
]);
If your table uses a primary key column name other than id, you may specify the name of the column when calling the ignore method:
'email' => Rule::unique('users')->ignore($user->id, 'user_id')
you can customize it!
See documentation https://laravel.com/docs/5.5/validation#rule-unique
$rules['correo'] = 'required|max:40|unique:juridico,correo,pagina_web';
or
$rules['correo'] = [
'required',
'max:40',
Rule::unique('juridico')->where(function ($query) use ($paginaWeb) {
return $query->where('pagina_web', $paginaWeb);
})
];
In AppServiceProvider add this
public function boot()
{
Validator::extend('uniq', function ($attribute, $value, $parameters, $validator) {
$data = $validator->getData();
$tableName = $parameters[0];
unset($parameters[0]);
$primaryKey = 'id';
$query = DB::table($tableName);
// set main uniqueness condition
$query->where($attribute, '=', $value);
// if primary key exists - set to NOT be equal (for updating case)
if (!empty($data[$primaryKey])) {
$query->where($primaryKey, '!=', $data[$primaryKey]);
}
// check conditional columns
if (!empty($parameters)) {
foreach ($parameters as $column) {
if (isset($data[$column])) {
$query->where($column, '=', $data[$column]);
}
}
}
$count = $query->count();
return ($count == 0) ? true : false;
});
}
and usage
$rules['correo'] = 'required|max:40|uniq:juridico,correo,pagina_web';

Validate Arabic Numbers in Laravel 5

I have a form that contains a field for user to enter amount value of certain payment. This field is input of type number.
The validation rule in Laravel for this input is:
'amount' => 'required|numeric'
When I enter the amount in English as: 1500 => The validation passes and everything is OK.
But when I enter the amount in Arabic as: ١٥٠٠ => The validation fails with the following error message:
"validation.numeric"
Should I validate this field manually or is there another solution to this problem?
Maybe you can create your own validation type.
You can add something like this to your boot method in app/Providers/AppServiceProvider.php.
Validator::extend('arabic_numbers', function ($attributes, $value, $parameters, $validation) {
$arabic_numbers = [
'٥',
'١',
// add more
];
$input = $value;
if (!$input) {
return false;
}
$chars = preg_split('//u', $input, -1, PREG_SPLIT_NO_EMPTY);
foreach ($chars as $char) {
if (!in_array($char, $arabic_numbers)) {
return false;
}
}
return true;
});
You can add to your existing rule, e.g. required|arabic_numbers.
Or use something like this:
$input = '١';
$validator = Validator::make([
'user_input' => $input,
], [
'user_input' => 'required|arabic_numbers'
];
if ($validator->fails()) {
//
}
Also you can use in many other ways for example in a custom request:
public function rules()
{
return [
'something' => 'required|arabic_numbers',
];
}
Hope this helps.

Laravel 4 Validation

I use the following rules for validation on creating a new user:
protected $rules= [
'name' => 'required',
'email' => [
'required',
'unique:user',
'email'
]
];
When updating an existing user I use the same ruleset as shown above
but don't want a validation error if the user didn't change his email at all.
I currently resolve this by using the following:
if (!User::changed('email')) {
unset($user->email);
}
It feels like a dirty workaround to me so I was wondering if there are better alternatives.
Also note that the changed method is something I wrote myself. Does anyone know if there
is a native Laravel 4 method for checking whether a model property has changed?
Thanks!
The unique validation rule allows to ignore a given ID, which in your case is the ID of the data set you are updating.
'email' => 'unique:users,email_address,10'
http://four.laravel.com/docs/validation#rule-unique
One approach is to create a validation function in the model and call it with the controller passing in the input, scenario and id (to ignore).
public function validate($input, $scenario, $id = null)
{
$rules = [];
switch($scenario)
{
case 'store':
$rules = [
'name' => 'required|min:5|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|min:4|confirmed'
];
break;
case 'update';
$rules = [
'name' => 'required|min:5|unique:users' .',name,' . $id,
'email' => 'required|email|unique:users' .',email,' . $id,
'password' => 'min:4|confirmed'
];
break;
}
return Validator::make($input, $rules);
}
Then in the controller:
$input = Input::all();
$validation = $user->validate($input, 'update', $user->id);
if ($validation->fails())
{
// Do stuff
}
else
{
// Validation passes
// Do other stuff
}
As others mentioned, the 3rd parameter of the unique rule specifies an id to ignore. You can add other cases, such as 'login' to reuse the validation function.
Alternatively, Jeffrey Way at Tuts Premium has a great series of lessons in "What's New In Laravel 4" which includes a couple of other approaches to handling validation using services and listeners.
See the documentation on http://four.laravel.com/docs/validation#rule-unique
You can exclude the users own id
protected $rules= [
'name' => 'required',
'email' => [
'required',
'unique:user,email,THE_USERS_USER_ID',
'email'
]
];
As of 2014-01-14, you can use sometimes attribute, I believe Taylor added them 2 days ago to Laravel 4.1
$v = Validator::make($data, array(
'email' => 'sometimes|required|email',
));
sometimes only validate input if it exists. this may or may not suit your exact scenario, if you don't have a default value for insert.
http://laravel.com/docs/validation#conditionally-adding-rules
I handle this sort of thing in my validator function. My validators array is setup as a class variable. I then do something like this:
public function validate()
{
//exclude the current user id from 'unqiue' validators
if( $this->id > 0 )
{
$usernameUnique = 'unique:users,username,'.$this->id;
$emailUnique = 'unique:users,email,'.$this->id;
$apiUnique = 'unique:users,api_key,'.$this->id;
}
else
{
$usernameUnique = 'unique:users,username';
$emailUnique = 'unique:users,email';
$apiUnique = 'unique:users,api_key';
}
$this->validators['username'] = array('required', 'max:32', $usernameUnique);
$this->validators['email'] = array('required', 'max:32', $emailUnique);
$this->validators['api_key'] = array('required', 'max:32', $apiUnique);
$val = Validator::make($this->attributes, $this->validators);
if ($val->fails())
{
throw new ValidationException($val);
}
}
I have solved this by having different rules for update and create on models that need to do so, like Users.
I have a Model class that extends Eloquent, where I define the validation, and then all child models that extend the Model can have have both the $rules and $update_rules defined. If you define only $rules, it will be used both for create and update.
class Model extends Eloquent {
protected $errors;
protected static $rules = array();
protected $validator;
public function __construct(array $attributes = array(), Validator $validator = null) {
parent::__construct($attributes);
$this->validator = $validator ?: \App::make('validator');
}
protected static function boot() {
parent::boot();
# call validatie when createing
static::creating(function($model) {
return $model->validate();
});
# call validatie when updating with $is_update = true param
static::updating(function($model) {
return $model->validate(true);
});
}
public function validate($is_update = false) {
# if we have $update_rules defined in the child model, and save is an update
if ($is_update and isset(static::$update_rules)) {
$v = $this->validator->make($this->attributes, static::$update_rules);
}
else {
$v = $this->validator->make($this->attributes, static::$rules);
}
if ($v->passes()) {
return true;
}
$this->setErrors($v->messages());
return false;
}
protected function setErrors($errors) {
$this->errors = $errors;
}
public function getErrors() {
return $this->errors;
}
public function hasErrors() {
return ! empty($this->errors);
}
}

Categories