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
i am very new in Laravel and this is my first project in Laravel.As usual, first of all i am developing a full user authentication system.I can registered an single user,can send an user verification email and after clicking that link i can activate a new user account, can login and can logout.But after that whenever i am trying to registered another new user and after clicking the verification link , i am facing an exception which is,
Illuminate \ Database \ QueryException
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '' for key 'users_code_unique' (SQL: update `users` set `code` = , `active` = 1, `updated_at` = 2014-07- 25 04:26:06 where `id` = 41)
now this is my route.php,
<?php
Route::get('/',array(
'as' =>'home',
'uses' =>'HomeController#index'
));
Route::get('/signin',array(
'as' =>'signin',
'uses' =>'AccountController#signinGet'
));
Route::get('/signup',array(
'as' => 'signup',
'uses' => 'AccountController#signupGet'
));
/*
/*
/Authenticated Group
*/
Route::group(array('before' => 'auth'),function(){
/*
/Sign Out(GET)
*/
Route::get('/signout',array
(
'as' => 'signout',
'uses' => 'AccountController#signoutGet'
));
});
/*
/UnAuthenticated Group
*/
Route::group(array('before' => 'guest'),function(){
/* CSRF Protect*/
Route::group(array('before' => 'csrf'),function(){
/*
/ Create Account(POST)
*/
Route::post('/signup',array(
'as'=> 'signup',
'uses'=>'AccountController#signupPost'
));
/*
/ Sign In(POST)
*/
Route::post('/signin',array(
'as' => 'signin-post',
'uses' => 'AccountController#signinPost'
));
});
/*
/ Sign In (GET)
*/
Route::get('/signin',array(
'as' => 'signin',
'uses' => 'AccountController#signinGet'
));
/*
/Create Account(GET)
*/
Route::get('/signup',array(
'as' => 'signup',
'uses'=> 'AccountController#signupGet'
));
Route::get('signup/account/activate/{code}',array(
'as' =>'activate-account',
'uses' =>'AccountController#activatePost'
));
});
?>
and this is my AccountController
<?php
class AccountController extends \BaseController {
public function signinGet()
{
return View::make('account.signin');
}
public function signinPost(){
$validator = Validator::make(Input::all(),array(
'email' => 'required|email',
'password' => 'required'
));
if($validator->fails()){
//redirect to the signin page
return Redirect::route('signin')
->withErrors($validator)
->withInput();
}else{
//Attempt user singin
$auth = Auth::attempt(array
(
'email' => Input::get('email'),
'password' => Input::get('password'),
'active' => 1
));
if($auth){
//Redirect To intented URL
return Redirect::intended('/');
}
else
{
return Redirect::route('signin')
->with('global','The username or password you provided is wrong or account not activated!');
}
}
return Redirect::route('signin')
->with('global','There is a problem Signing You in.');
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function signupGet()
{
return View::make('account.signup');
}
public function signupPost()
{
$validator = Validator::make(Input::all(), array(
'email' => 'required|max:255|email|unique:users',
'username' => 'required|min:3|unique:users',
'password' => 'required|min:6',
'password_again' => 'required|same:password'
)
);
if($validator->fails())
{
return Redirect::route('signup')
->withErrors($validator)
->withInput();
}else
{
$email = Input::get('email');
$username = Input::get('username');
$password = Input::get('password');
//Activation Code
$code = str_random(60);
$user = User::create(array(
'email' => $email,
'username' => $username,
'password' => Hash::make($password),
'code' => $code,
'active' => 0
)
);
if($user){
//User Activation Code Creation
Mail::send('emails.auth.activate', array('link' => URL::route('activate-account',$code), 'username' => $username),function($message) use ($user)
{
$message->to($user->email,$user->username)->subject('Activate Your Account');
});
return Redirect::route('signup')
->with('global','Your Account has been created! We have sent you an email to activate your account.Please Check the both the Inbox and Spam Folder.');
}
}
//return 'This is a Post Result';
}
public function activatePost($code){
$user = User::where('code','=',$code)->where('active','=',0);
if($user->count()){
$user = $user->first();
$user->active = 1;
$user->code = '';
if($user->save()){
return Redirect::route('home')
->with('global','Activated!.You can sign in now!');
}
}
else{
return Redirect::route('signup')
->with('global','Sorry!We could not activate your acount,please try again later.');
}
}
public function signoutGet(){
Auth::logout();
return Redirect::route('home');
}
}
?>
and this is my create user migration file
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration {
public function up()
{
Schema::create('users', function(Blueprint $table)
{
$table->increments('id');
$table->string('username',255)->unique();
$table->string('email',255)->unique();
$table->string('password',60);
$table->string('password_temp',60);
$table->string('code',60)->unique();
$table->integer('active');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('users');
}
}
?>
and this is my user.php
<?php
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
public function getRememberToken()
{
return $this->remember_token;
}
public function setRememberToken($value)
{
$this->remember_token = $value;
}
public function getRememberTokenName()
{
return 'remember_token';
}
protected $fillable = array('email','username','password','password_temp','code','active');
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
}
?>
now whats the problem?
Make sure your code field is nullable, then instead of setting it's value to empty string, make it null:
$code = null;
Then you will be able to save it as NULL (MySQL) while it remains unique.
Also change this one:
$user = User::where('code','=',$code)->where('active','=',0);
if($user->count()){
$user = $user->first();
To:
$user = User::where('code','=',$code)->where('active','=',0)->first();
if(count($user)){
You don't need to call db twice, just check if returned result is not null (count will do), meaning it returned a User object.
your problem can use validator to check. simply use as this:
use Validator;
use Request;
//...
//unique will pre check the key code weather if unique in tbl_name
public function yourfunc(Request $request) {
// set the rules to check
$rules = ['code'=>'required|unique:tbl_name'];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
// handler errors
$erros = $validator->errors();
}
//... everything is ok here
}
you can explore more at laravel validation
I found it. You've set code column as unique, though you're setting it to empty string after user click an activation link. And there already is a row in table with code=''; so it throws an error. The problem is here (activatePost):
$user->code = '';
So either don't empty it, set it to something else or set db colums as not unique.
I would leave the code without emptying it and additionally I would check if user was activated - a simple if in activatePost. Maybe it's a good idea to verify user not only according to code, but also with a hashed id in link.
There a few things you need to do to improve your code. But duplicate entry usually happens when you set a column to unique and tries to re inset the same data into another row. The most time it get confusing is when you check your table and and find the column empty. Whoop! When a column is set to unique and empty, it means no other column can contain empty data.
In simple terms the form of the column can not be duplicated, either null or with data.
Integrity constraint violation Duplicate entry problems are due to database schema inconsistence being detected by laravel.
Integrity constraint violation Duplicate entry problems are likely to occur when the database schema was preserved by laravel migration, and then the db scheme is updated somehow outside of laravel migration's control (eg. developers manually (without via performing a laravel migration) alter the db schema in a db client OR the db scheme is updated by importing a piece of SQL dump that is inconsistent with the db scheme -- as a result of that, the importing unexpectedly updates the laravel-migration-preserved db schema.
To avoid the second kind of harm, be careful when importing SQL dump data into db: ensure the SQL dump data is consistent with the current db schema before your SQL dump is imported.
Overall, when doing laravel development, the db schema should always be preserved by laravel migration to avoid db schema inconsistence.