Laravel unique rule validator breaking during Validator::make - php

I am trying to check to see if (a) column(s) is/are unique by using the Rule::unique('table')->where(function($query) use($x) {...}); functionality but when I pass this into my validator I am getting a strange error. What I think is happening is that it is trying to check if a value is equal in the where the statement that I provided but also a column that it THINKS is the unique ID column for the table but it is not so it is breaking.
protected function validator(array $data)
{
$uid = 660000000;
$rule = Rule::unique('member_record')->where(function ($query) use ($uid) {
return $query->where('uniqueID', $uid);
});
return Validator::make($data, [
'fullName' => ['required', 'string', 'min:2'],
'member_id' => [
'bail', 'required', 'Numeric', $rule,
'exists:new_benefits_member,member_id'
],
'email' => ['bail', 'required', 'email', 'confirmed', 'unique:user,email'],
'password' => [
'required', 'string', 'min:8', 'confirmed',
'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$/'
],
'terms' => ['required']
]);
}
However, then I am getting an error that looks like the following.
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'member_id' in 'where clause' (SQL: select count(*) as aggregate from member_record where member_id = 660000000 and (uniqueID = 660000000))
What my only assumption is that when I am passing data into the Validator::make($data... it is trying to compare the $rule with the $data array and it is messing it up. Let me know if you have any fixes that I can try out.

The problem here is that the Rule::unique() function can take 2 parameters as shown below
public static function unique($table, $column = 'NULL')
{
return new Rules\Unique($table, $column);
}
if column is left as 'NULL' then this will default to the name of the key in the validator::make($x, [] <--- array
as shown in this example.
protected function validator(array $data)
{
$uid = 660000000;
$rule = Rule::unique('member_record')->where(function ($query) use ($uid) {
return $query->where('uniqueID', $uid)->orwhere('client_member_id', $uid);
});
$data['foo'] = 0;
$validator = Validator::make($data, [
'foo' => [$rule]
]);
return $validator;
}
results in this response
Column not found: 1054 Unknown column 'foo' in 'where clause' (SQL: select count(*) as aggregate from member_record where foo = 0 and (uniqueID = 660000000 or client_member_id = 660000000))
If you would like to exclude "is equal to" in the first part of the where clause you would perform a unique check like this
'member_id' => ['unique:member_record,foo']
If you would like to add additional where clauses then you would want to do something like this
'member_id' => ['unique:member_record,foo,NULL,id,bar,' . $uid]
This will return SQL looking like this
select count(*) as aggregate from member_record where foo = 660000000 and bar = 660000000

Related

Ignoring the user result input when updating data goes wrong

I'm working on an :
edit.blade.php
where users can edit data and the update method of Controller goes here:
public function update(Request $request, $id)
{
$discountCode = DiscountCode::find($id);
$request->validate([
'code' => 'unique:discount_codes,code'.$discountCode->id,
'started_at' => 'required_if:timespan,TRUE',
'ended_at' => 'required_if:timespan,TRUE',
],
...
}
So as you can see the code must be uique in discount_codes table.
So I tried adding :
$discountCode->id
in order to ignore unique validation rule for the current data but it does not work out and returns this error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'code15' in
'where clause' (SQL: select count(*) as aggregate from
`discount_codes` where `code15` = GIGvJjp4PM)
15 is my data row id.
So what's going wrong here? How can I solve this issue?
Use Rule::class to create the rule and ignore specific id
use Illuminate\Validation\Rule;
//...
public function update(Request $request, $id)
{
$discountCode = DiscountCode::findOrFail($id);
$request->validate([
'code' => [Rule::unique('discount_codes')->ignore($id)],
'started_at' => 'required_if:timespan,TRUE',
'ended_at' => 'required_if:timespan,TRUE',
],
...
}

Using models relationships in Laravel Validation Rule 'Exists'

Db schema
users
-id
-name
-email
...
roles
-id
-name
User may have multiple roles and vice-versa (i already defined
relationships in models)
pivot table
role_user
-id
-user_id
-role_id
Validation rule trying to make: user_id must exist in users table and has role id = 4
//...
'user_id' => ['nullable', Rule::exists('users')->where(
function ($query) {
$query->whereHas('roles',
function ($q) {
$q->where('id', 4);
}
);
}
)],
//...
Error message : "SQLSTATE[42S22]: Column not found: 1054 Unknown
column 'has' in 'where clause' (SQL: select count(*) as aggregate from
users where user_id = 0 and (has = roles))"
I would use this. This will solve your problem, but i don't know either is the best way or not.
use Validator; // on the top
$validator = Validator::make($request->all(), [
'user_id' => 'nullable|numeric|exists:users,id',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$user = User::find($request->user_id);
if(!$user || !$user->roles->first() || $user->roles->first()->id != 4) {
return response()->json(['user_id'=>['You dont have permission.']], 422);
}
Another way you can try
'user_id' => [
'nullable',
'numeric',
'exists:users,id',
function ($attribute, $value, $fail) {
$editorsIDs = User::whereHas('roles', function ($q) {
$q->where('id', 4);
})->pluck('id');
if(! $editorsIDs->contains($value)) {
$fail('You dont have permission.');
}}
]
I resolve it using
$usersWhereHasEditorRole = User::whereHas('roles', function ($q) {
$q->where('id', 4);
})->pluck('id')->unique('id')->toArray();
$validator = Validator::make($request->all(), [
'name' => 'required|alpha', // commun
'email' => 'required|email|unique:users', // commun
'password' => 'required|min:8|regex:/[a-z]/|regex:/[A-Z]/|regex:/[0-9]/|regex:/[#$!%*#?&]/', // commun
'c_password' => 'required|same:password', // commun
//...
'user_id' => ['nullable', Rule::in($usersWhereHasEditorRole)],
//...
]);
if ($validator->fails()) {
return $this->sendError('Validation Error.', $validator->errors(), 400);
}
dd('passes');

updateOrCreate() gives Column not found: 1054 Unknown column '0' in 'where clause'

I'm trying to assign role to user when they attempt. But it gives me this error. I'm using updateOrCreate() because I want to use that method later to change user role
SQLSTATE[42S22]: Column not found: 1054 Unknown column '0' in 'where
clause' (SQL: select * from roles where (0 = user_id and 1 = =
and 2 = 10) limit 1)
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger("user_id")->index();
$table->string("role_name");
$table->foreign("user_id")->references("id")->on("users")->onDelete("cascade");
});
RegisterController
protected function create(array $data)
{
$user = user::create([
'name' => $data['name'],
'email' => $data['email'],
"username" => $data["username"],
'password' => Hash::make($data['password']),
]);
if ($user) {
$model = new Role();
$model->assignNewbieRole($user->id);
}
return $user;
}
Role model
public function assignNewbieRole($id)
{
$this->updateOrCreate(["user_id","=",$id],["role_name","=","newbie"]);
}
How do I fix this ?
You need to pass an associated array instead of individual values:
$this->updateOrCreate(["user_id" => $id], ["role_name" => "newbie"]);
The values have to be set using a associative array where the column name is the key and the value is the value you want to find/insert.
$this->updateOrCreate(
["user_id" => $id],
["role_name" => "newbie"]
);

Laravel unique validation on multiple columns

I have 2 columns in table servers.
I have columns ip and hostname.
I have validation:
'data.ip' => ['required', 'unique:servers,ip,'.$this->id]
This working only for column ip. But how to do that it would work and for column hostname?
I want validate data.ip with columns ip and hostname.
Because can be duplicates in columns ip and hostname, when user write ip.
You can use Rule::unique to achieve your validation rule
$messages = [
'data.ip.unique' => 'Given ip and hostname are not unique',
];
Validator::make($data, [
'data.ip' => [
'required',
Rule::unique('servers')->where(function ($query) use($ip,$hostname) {
return $query->where('ip', $ip)
->where('hostname', $hostname);
}),
],
],
$messages
);
edit: Fixed message assignation
The following will work on the create
'data.ip' => ['required', 'unique:servers,ip,'.$this->id.',NULL,id,hostname,'.$request->input('hostname')]
and the following for the update
'data.ip' => ['required', 'unique:servers,ip,'.$this->id.','.$request->input('id').',id,hostname,'.$request->input('hostname')]
I'm presuming that id is your primary key in the table. Substitute it for your environment.
The (undocumented) format for the unique rule is:
table[,column[,ignore value[,ignore column[,where column,where value]...]]]
Multiple "where" conditions can be specified, but only equality can be checked. A closure (as in the accepted answer) is needed for any other comparisons.
Laravel 5.6 and above
Validation in the controller
The primary key (in my case) is a combination of two columns (name, guard_name)
I validate their uniqueness by using the Rule class both on create and on update method of my controller (PermissionsController)
PermissionsController.php
<?php
namespace App\Http\Controllers;
use App\Permission;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Http\Controllers\Controller;
class PermissionsController extends Controller
{
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
request()->validate([
'name' => 'required|max:255',
'guard_name' => [
'required',
Rule::unique('permissions')->where(function ($query) use ($request) {
return $query
->whereName($request->name)
->whereGuardName($request->guard_name);
}),
],
],
[
'guard_name.unique' => __('messages.permission.error.unique', [
'name' => $request->name,
'guard_name' => $request->guard_name
]),
]);
Permission::create($request->all());
flash(__('messages.permission.flash.created'))->success();
return redirect()->route('permission.index');
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Permission $permission)
{
request()->validate([
'name' => 'required|max:255',
'guard_name' => [
'required',
Rule::unique('permissions')->where(function ($query) use ($request, $permission) {
return $query
->whereName($request->name)
->whereGuardName($request->guard_name)
->whereNotIn('id', [$permission->id]);
}),
],
],
[
'guard_name.unique' => __('messages.permission.error.unique', [
'name' => $request->name,
'guard_name' => $request->guard_name
]),
]);
$permission->update($request->all());
flash(__('messages.permission.flash.updated'))->success();
return redirect()->route('permission.index');
}
}
Notice in the update method i added an additional query constraint [ whereNotIn('id', [$permission->id]) ] to ignore the current model.
resources/lang/en/messages.php
<?php
return [
'permission' => [
'error' => [
'unique' => 'The combination [":name", ":guard_name"] already exists',
],
'flash' => [
'updated' => '...',
'created' => '...',
],
]
]
The flash() method is from the laracasts/flash package.
Table
server
Field
id primary key
ip should be unique with hostname
hostname should be unique with ip
Here I validate for Ip and the hostname should be unique.
use Illuminate\Validation\Rule;
$ip = '192.168.0.1';
$host = 'localhost';
While Create
Validator::make($data, [
'ip' => [
'required',
Rule::unique('server')->where(function ($query) use($ip,$host) {
return $query->where('ip', $ip)->where('hostname', $host);
});
],
]);
While Update
Add ignore after RULE
Validator::make($data, [
'ip' => [
'required',
Rule::unique('server')->where(function ($query) use($ip,$host) {
return $query->where('ip', $ip)->where('hostname', $host);
})->ignore($serverid);
],
]);
This works for me for both create and update.
[
'column_1' => 'required|unique:TableName,column_1,' . $this->id . ',id,colum_2,' . $this->column_2
]
Note: tested in Laravel 6.
Try this rule:
'data.ip' => 'required|unique:servers,ip,'.$this>id.'|unique:servers,hostname,'.$this->id
With Form Requests:
In StoreServerRequest (for Create)
public function rules() {
'ip' => [
'required',
Rule::unique('server')->where(function ($query) {
$query->where('ip', $this->ip)
->where('hostname', $this->host);
})
],
}
public function messages() {
return [
'ip.unique' => 'Combination of IP & Hostname is not unique',
];
}
In UpdateServerRequest (for Update)
Just Add ignore at the end
public function rules() {
'ip' => [
'required',
Rule::unique('server')->where(function ($query) {
$query->where('ip', $this->ip)
->where('hostname', $this->host);
})->ignore($this->server->id)
],
}
This is the demo code. It would help you much better. I tried covering both insert and update scenarios.
Inside app/Http/Providers/AppServiceProvider.php
Validator::extend('uniqueOfMultiple', function ($attribute, $value, $parameters, $validator)
{
$whereData = [
[$attribute, $value]
];
foreach ($parameters as $key => $parameter) {
//At 0th index, we have table name
if(!$key) continue;
$arr = explode('-', $parameter);
if($arr[0] == 'except') {
$column = $arr[1];
$data = $arr[2];
$whereData[] = [$column, '<>', $data];
} else {
$column = $arr[0];
$data = $arr[1];
$whereData[] = [$column, $data];
}
}
$count = DB::table($parameters[0])->where($whereData)->count();
return $count === 0;
});
Inside app/Http/Requests/Something/StoreSometing.php
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|max:225|uniqueOfMultiple:menus,location_id-' . $this->get('location_id', 'NULL') . ',language_id-' . $this->get('language_id', 1),
'location_id' => 'required|exists:menu_location,id',
'order' => 'digits_between:0,10'
];
}
Inside app/Http/Requests/Something/UpdateSomething.php
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|max:225|uniqueOfMultiple:menus,location_id-' . $this->get('location_id', 'NULL') . ',language_id-' . $this->get('language_id', 'NULL') . ',except-id-' . $this->route('id', 'NULL'),
'location_id' => 'required|exists:menu_location,id',
'order' => 'digits_between:0,10'
];
}
Inside resources/lang/en/validation.php
'unique_of_multiple' => 'The :attribute has already been taken under it\'s parent.',
Here in this code, the custom validation used is uniqueOfMultiple. The first argument passed is the table_name i.e menus and all other arguments are column_name and are comma-separated. The columns are used here, name (primary column), location_id, language_id and one except-for column for the update case, except-id. The value passed for all three is - separated.
This works for me for both create and update.
in your ServerUpdateRequest or ServerCreateRequest class
public function rules()
{
return [
'column_1' => 'required|unique:TableName,column_1,' . $this->id . ',id,colum_2,' . $this->column_2 . ',colum_3,' . $this->column_3,
];
}
This command run background a aggregate Sql like this
select
count(*) as aggregate
from
`TableName`
where
`column_1` = <postedColumn1Value>
and `id` <> idValue
and `column_2` = <postedColumn2Value>
and `column_3` = <postedColumn3Value>
tested in Laravel 9. and it works
Note: if you want to see background sql for debugging (For example, to check if the request values are empty[$this->]) , especially you have to write wrong code, For example, you may enter a filed name incorrectly.
for me laravel 8 this works
$req->validate([
'house_no' => [
Rule::unique('house')
->where('house_no', $req->input('house_no'))
->where('ward_no', $req->input('ward_no'))
],
]);
The following code worked nicely for me at Laravel 8
Create:
'required|unique:TableName,column_1,' . $this->column_1 . ',id,colum_2,' . $this->column_2,
Example:
public function store(Request $request)
{
$union = auth()->user()->union_id;
$request->validate([
'holding_no' => 'required|integer|unique:holding_taxes,holding_no,' . $request->holding_no . ',id,union_id,' . $union,
]);
}
Update:
'required|unique:TableName,column_1,' . $this->id . ',id,colum_2,' . $this->column_2,
Example:
public function update(Request $request, $id)
{
$union = auth()->user()->union_id;
$request->validate([
'holding_no' => 'required|unique:holding_taxes,holding_no,' . $id . ',id,union_id,'.$union,
]);
}
Simple solution with call back query
Rule::unique('users')->where(fn ($query) => $query->where(['project_id'=> request()->project_id, 'code'=> request()->code ])),
public function store(Request $request)
{
$this->validate($request, [
'first_name' => 'required|regex:/^[\pL\s\-]+$/u|max:255|unique:contacts,first_name, NULL,id,first_name,'.$request->input('last_name','id'),
'last_name'=>'required|regex:/^[\pL\s\-]+$/u|max:255|unique:contacts,last_name',
'email' => 'required|email|max:255|unique:contacts,email',
'job_title'=>'required',
'city'=>'required',
'country'=>'required'],
[
'first_name.regex'=>'Use Alphabets Only',
'email.unique'=>'Email is Already Taken.Use Another Email',
'last_name.unique'=>'Contact Already Exist!. Try Again.',
]
);

How to pass a variable to custom validator

I've created a custom validator that resides in the AppServiceProvider. The boot method contains a DB method that should accept the first patameter passed into the validator as the table name. When I manually fill out the table name, it works but when the first parameter is passed, I run into this error:
QueryException in Connection.php line 729:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'server1.{users}'
doesn't exist (SQL: select count(*) as aggregate from `{users}` where `email` =
mail#mail.com and `STORE_ID` = 2)
Here's my service provider code:
public function boot()
{
Validator::extend('uniqueForStore', function ($attribute, $value, $parameters, $validator) {
$count = DB::table($parameters[0])->where($attribute, $value)->where('STORE_ID', config('constants.STORE_ID'))->count();
return $count === 0;
});
}
This is where the problem lies:
DB::table($parameters[0])
Here's my register user form request code:
public function rules()
{
return [
'first_name' => 'required',
'last_name' => 'required',
'email' => "uniqueForStore:{users}",
'password' => 'required|min:6'
];
}
Set your validation rules as below - just remove brackets for unique value (users):
public function rules()
{
return [
'first_name' => 'required',
'last_name' => 'required',
'email' => "uniqueForStore:users",
'password' => 'required|min:6'
];
}

Categories