Hi I am using Request to validate my form.
listingId = 20
categoryType ='listing-category'
I have tried two ways to validate the form. First is
'title' => 'required|min:2|max:255|unique:terms,title,'.$listingId.',id,type,'.$categoryType,
And the second one is
if($this->method() != 'PUT')
{
$uniqueTitleValidation = Rule::unique('terms')->where('type', $categoryType);
}
else
{
$uniqueTitleValidation = [];
$uniqueTitleValidation = Rule::unique('terms')->ignore($listingId)->where('type', $categoryType);
}
and in validation
'title' => [
'required',
'min:2',
'max:255',
$uniqueTitleValidation
],
while creating a new entry it Is working fine. But ignoring the type I guess while updating and throw me already exists error.
This is my DB table
Now as you can see I want to check for listing-category. But I think it also checking for category type.
Note: I am using laravel 5.8
Try below example is cleaner and should work.
$uniqueTitleValidation = Rule::unique('terms')
->where(function ($query) use($categoryType, $listingId){
if($this->method() != 'PUT') {
return $query->where('type', '=', $categoryType);
}
return $query->where([
['type', '=', $categoryType],
['id', '<>', $listingId] // ignore this id and search for other rows
]);
});
Create a rule in your UpdateRequest under App\Http\Requests
Just add the below code.
public function rules()
{
return [
'title' => [
'required',
'string',
Rule::unique('terms', 'title')->whereNull('deleted_at')->ignore($this->listing),
],
];
}
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.',
]
);
In my user table I have fields like firstname and lastname.
then I generated a views using Gii.In my index page now I have Firstname,Lastname,Username etc....
I concatenated my fistname and lastname as Name.
[
'attribute'=>'firstname',
'label' => 'Name',
'format' => 'raw',
'value' => function ($data) {
return Html::a($data->Name);
},
],
In Model
public function getName()
{
return $this->firstname.' '.$this->lastname;
}
unfortunately I am unable to search the name field with last name...
I need to filter the field with both firstname and lastname.
Can anyone please help me....
Thanks in advance.
This is how you setup search model (I did not include your other columns, so dont forget on those)
You can find more here: http://www.yiiframework.com/wiki/621/filter-sort-by-calculated-related-fields-in-gridview-yii-2-0/
class UserSearch extends User
{
public $name;
public function rules()
{
return [
[['name'], 'safe'],
// some other rules ...
];
}
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
public function search($params)
{
$query = User::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->sort->attributes['name'] = [
'asc' => ['lastname' => SORT_ASC, 'firstname' => SORT_ASC],
'desc' => ['lastname' => SORT_DESC, 'firstname' => SORT_DESC],
'label' => 'Name',
'default' => SORT_ASC
];
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
// some other filters ...
$query->andFilterWhere([
'or',
['like', 'lastname', $this->name],
['like', 'firstname', $this->name],
]);
return $dataProvider;
}
}
And in your gridview instead of
[
'attribute'=>'firstname',
// ...
],
just use
[
'attribute'=>'name',
],
Following on from this:
Yii2 how does search() in SearchModel work?
I would like to be able to filter a GridView column of relational data. This is what I mean:
I have two tables, TableA and TableB. Both have corresponding models generated using Gii. TableA has a foreign key to a value in TableB, like this:
TableA
attrA1, attrA2, attrA3, TableB.attrB1
TableB
attrB1, attrB2, attrB3
attrA1 and attrB1 are the primary keys of their corresponding tables.
Now, I have a Yii2 GridView of attrA2, attrA3 and attrB2. I have a working filter on attrA2 and attrA3 so that I can search on column values. I also have a working sort for these two columns too - by just clicking on the column header. I would like to be able to add this filtering and sorting on attrB2 too.
My TableASearch model looks like this:
public function search($params){
$query = TableA::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$this->addCondition($query, 'attrA2');
$this->addCondition($query, 'attrA2', true);
$this->addCondition($query, 'attrA3');
$this->addCondition($query, 'attrA3', true);
return $dataProvider;
}
In my TableA model, I set the related value like this
public $relationalValue;
public function afterFind(){
$b = TableB::find(['attrB1' => $this->attrB1]);
$this->relationalValue = $b->relationalValue;
}
Although it is probably not the best way of doing this. I think I have to use $relationalValue somewhere in my search function but I'm not sure how. Similarly, I would like to be able to sort by this column too - just like I can for attrA2 and AttrA3 by clicking on the header link`. Any help would be appreciated. Thanks.
This is based on the description in the guide. The base code for the SearchModel comes from the Gii code generator. This is also assuming that $this->TableB has been setup using hasOne() or hasMany() relation. See this doc.
1. Setup search model
In TableASearch model add:
public function attributes()
{
// add related fields to searchable attributes
return array_merge(parent::attributes(), ['TableB.attrB1']);
}
public function rules()
{
return [
/* your other rules */
[['TableB.attrB1'], 'safe']
];
}
Then in TableASearch->search() add (before $this->load()):
$dataProvider->sort->attributes['TableB.attrB1'] = [
'asc' => ['TableB.attrB1' => SORT_ASC],
'desc' => ['TableB.attrB1' => SORT_DESC],
];
$query->joinWith(['TableB']);
Then the actual search of your data (below $this->load()):
$query->andFilterWhere([
'like',
'TableB.attrB1',
$this->getAttribute('TableB.attrB1')
]);
2. Configure GridView
Add to your view:
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
/* Other columns */
'TableB1.attrB1',
/* Other columns */
]
]);
Filtering a gridview by a column is damn easy in Yii 2.0. Please add the filter attribute to a gridview column having lookup values, as under:
[
"class" => yii\grid\DataColumn::className(),
"attribute" => "status_id",
'filter' => ArrayHelper::map(Status::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
"value" => function($model){
if ($rel = $model->getStatus()->one()) {
return yii\helpers\Html::a($rel->name,["crud/status/view", 'id' => $rel->id,],["data-pjax"=>0]);
} else {
return '';
}
},
"format" => "raw",
],
I'm stuck with this problem too, and my solution is rather different. I have two simple models:
Book:
class Book extends ActiveRecord
{
....
public static function tableName()
{
return 'books';
}
public function getAuthor()
{
return $this->hasOne(Author::className(), ['id' => 'author_id']);
}
And Author:
class Author extends ActiveRecord
{
public static function tableName()
{
return 'authors';
}
public function getBooks()
{
return $this->hasMany(Book::className(), ['author_id' => 'id']);
}
But my search logic is in different model. And i didn't find how can i implement search without creating additional field author_first_name. So this is my solution:
class BookSearch extends Model
{
public $id;
public $title;
public $author_first_name;
public function rules()
{
return [
[['id', 'author_id'], 'integer'],
[['title', 'author_first_name'], 'safe'],
];
}
public function search($params)
{
$query = Book::find()->joinWith(['author' => function($query) { $query->from(['author' => 'authors']);}]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => array('pageSize' => 50),
'sort'=>[
'attributes'=>[
'author_first_name'=>[
'asc' => ['author.first_name' => SORT_ASC],
'desc' => ['author.first_name' => SORT_DESC],
]
]
]
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
....
$query->andWhere(['like', 'author.first_name', $this->author_first_name]);
return $dataProvider;
}
}
This is for creating table alias: function($query) { $query->from(['author' => 'authors']);}
And GridView code is:
<?php echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'attribute' => 'id',
'filter' => false,
],
[
'attribute' => 'title',
],
[
'attribute' => 'author_first_name',
'value' => function ($model) {
if ($model->author) {
$model->author->getFullName();
} else {
return '';
}
},
'filter' => true,
],
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
I will appreciate any critiques and advice.