Searching multiple columns through traits in Laravel - php

By checking out this tutorial I'm implementing a search url in my Laravel 5.4 application. I've implemented a trait in my model for example if I'm having Company model so in model I'm including my trait something like this:
class Company extends Model
{
use SoftDeletes, DataViewer;
protected $fillable = [
'name', 'address', 'city', 'state', 'country', 'type', 'sub_type',
];
public static $columns = [
'name', 'address', 'city', 'type', 'sub_type'
];
}
Now I'm having this DataViewer trait something like this:
trait DataViewer {
protected $operators = [
'equal' => '=',
'not_equal' => '<>',
'less_than' => '<',
'greater_than' => '>',
'less_than_or_equal_to' => '<=',
'greater_than_or_equal_to' => '>=',
'in' => 'IN',
'like' => 'LIKE'
];
public function scopeSearchPaginateAndOrder($query)
{
$request = app()->make('request');
return $query
->orderBy($request->column, $request->direction)
->where(function($query) use ($request) {
if($request->has('search_input')) {
if($request->search_operator == 'in') {
$query->whereIn($request->search_column, explode(',', $request->search_input));
} else if($request->search_operator == 'like') {
$query->where($request->search_column, 'LIKE', '%'.$request->search_input.'%');
}
else {
$query->where($request->search_column, $this->operators[$request->search_operator], $request->search_input);
}
}
})
->paginate($request->per_page);
}
}
And I'm calling this in controller something like this:
public function getData()
{
$models = Company::searchPaginateAndOrder();
return response()->json([
'model' => $models
]);
}
And to achieve this I'm calling url something like this:
http://stellar.dev/api/companies?column=name&direction=asc&page=1&per_page=50&search_column=name&search_operator=like&search_input=icici
The problem which I'm facing here is that it can search can only through one column, I want to have search with multiple columns in one query. Suggest me what can be done.
Thanks.

You should make your query string as an array for multiple columns like this.
http://stellar.dev/api/companies?column=name&direction=asc&page=1&per_page=50&search_column[]=name&search_operator[]=like&search_input[]=icici&search_column[]=lastname&search_operator[]=like&search_input[]=asd
Then read and use them as an array. Also you need to check their length to prevent unexpected errors. Then you can simple use a simple loop to concatenate your conditions like the following code block.
trait DataViewer {
protected $operators = [
'equal' => '=',
'not_equal' => '<>',
'less_than' => '<',
'greater_than' => '>',
'less_than_or_equal_to' => '<=',
'greater_than_or_equal_to' => '>=',
'in' => 'IN',
'like' => 'LIKE'
];
public function scopeSearchPaginateAndOrder($query)
{
$request = app()->make('request');
$searchInputs = $request->get('search_input', []);
$searchOperators = $request->get('search_operator', []);
$searchColumns = $request->get('search_column', []);
$query->orderBy($request->column, $request->direction);
if(count($searchInputs) == count($searchOperators) && count($searchOperators) == count($searchColumns)) {
$query->where(function($query) use ($searchInputs, $searchOperators, $searchColumns) {
for($x = 0, $l = count($searchColumns); $x < $l; $x++){
if($searchOperators[$x] == 'in') {
$query->whereIn($searchColumns[$x], explode(',', $searchInputs[$x]));
} else if($searchOperators[$x] == 'like') {
$query->where($searchColumns[$x], 'LIKE', "%{$searchInputs[$x]}%");
}
else {
$query->where($searchColumns[$x], $searchColumns, $searchInputs[$x]);
}
}
});
}
return $query->paginate($request->per_page);
}
}
Note: I strongly recommend use post verb for this search page or your data will be html encoded and your url is absolutely not user friendly.
Edited Answer
I have changed the code for multiple columns, single operator and single field.
public function scopeSearchPaginateAndOrder($query)
{
$request = app()->make('request');
$searchInput = $request->get('search_input');
$searchOperator = $request->get('search_operator');
$searchColumns = $request->get('search_column', []);
$query->orderBy($request->column, $request->direction);
if(count($searchColumns) > 0) {
$query->where(function($query) use ($searchInput, $searchOperator, $searchColumns) {
for($x = 0, $l = count($searchColumns); $x < $l; $x++){
if($searchOperator == 'in') {
$query->whereIn($searchColumns[$x], explode(',', $searchInput));
} else if($searchOperator == 'like') {
$query->where($searchColumns[$x], 'LIKE', "%{$searchInput}%");
}
else {
$query->where($searchColumns[$x], $searchOperator, $searchInput);
}
}
});
}
return $query->paginate($request->per_page);
}

Related

what is the correct way to pass 3 where () conditions in the same function, multiple filters | eloquente - laravel

I would like to know if I am doing it right, if my code is semantic and safe.
I need to load a page from the database, but to do this I need to cross some data and all must be compatible, the verification takes place as follows:
url: mysite.com/company/page/code
1 - Check the first parameter to see if company exists or not.
2 - check the second parameter to find out if company X has that page or not
3 - Check the third parameter to find out if page Y has a code and if that code matches what you typed.
4 - If the user arrived here and all the data are correct, please load the page and the corresponding data.
Here he checks if user X has page Y, my doubt is if I can do it like this or is there another way.
$page = Page::where('name', $name)->where('page_name', $page_name)->first();
if ($page === null) {
return view('company.pages.erros.404', compact('name', page));
}
Here is similar to the other, he checks if user X has page Y and if the code of page Y is correct, as in others, my doubt is if it is correct to put several WHERE clauses in the code
$pagecode = Page::where('name', $name)->where('page_name', $pagen_name)->where('code', $pcode)->first();
if ($pagecode === null) {
return view('company.pages.erros.invalid_code', compact('company, name', page, pcode));
}
as I thought you would want the code in general context, here is the full function code
public function loadpage($name, $page_name, $pcode)
{
$company = Company::where('name', $name)->first();
if ($company === null) {
return view('company.not_register', compact('name'));
}
$page = Page::where('name', $name)->where('page_name', $page_name)->first();
if ($page === null) {
return view('company.pages.erros.404', compact('name', page));
}
$pagecode = Page::where('name', $name)->where('page_name', $page_name)->where('code', $pcode)->first();
if ($pagecode === null) {
return view('company.pages.erros.invalid_code', compact('company, name', page, pcode));
}
$personality = DB::table('personalities')->where('name', $name)->first();
return view('company.pages.index', compact('company', 'name', 'personality', 'page', pcode));
}
based on laravel's documentation, I came up with the following result:
public function loadpage($name, $page_name, $pcode)
{
$company = Company::where('name', $name)->first();
if ($company === null) {
return view('company.not_register', compact('name'));
}
$page = Page::where([ ['name', $name],
['page_name', $page_name],])->first();
if ($page === null) {
return view('company.pages.erros.404', compact('name', page));
}
$pagecode = Page::where([ ['name', $name],
['page_name', $page_name], ['code', $pcode],])->first();
if ($pagecode === null) {
return view('company.pages.erros.invalid_code', compact('company, name', page, pcode));
}
$personality = DB::table('personalities')->where('name', $name)->first();
return view('company.pages.index', compact('company', 'name', 'personality', 'page', pcode));
}
now it's up to colleagues more experienced than me to see if that's right or is it possible to improve / simplify
In case you are searching in a single model and you are not sure when which fields occur you might want to use laravel's when() method. It will help you search the database only when the field is available for you. for an example:
$sortBy = null;
$users = DB::table('users')
->when($sortBy, function ($query, $sortBy) {
return $query->orderBy($sortBy);
}, function ($query) {
return $query->orderBy('name');
})
->get();
You will get the idea from here conditional query in laravel
if you have multiple where clause to pass to a where clause, you can have a array containing arrays with the structure as you would have in the where method, like:
$where = [
['name', $name],
['page_name', $page_name],
['code', $pcode],
/*
the arrays should have one of this two structure
["field", "value"], //using = operator as default
["field", "operator", "value"],
*/
];
and than call ->where() with this array as parameter, like:
Page::where($where)->first();
So your code can become
public function loadpage($name, $page_name, $pcode)
{
$name = ['name', $name];
$pageName = ['page_name', $page_name];
$pageCode = [ 'code', $pcode ];
$company = Company::where([$name])->first();
if ($company === null) {
return view('company.not_register', compact('name'));
}
$page = Page::where([$name, $pageName])->first();
if ($page === null) {
return view('company.pages.erros.404', compact('name', page));
}
$pagecode = Page::where([$name, $pageName, $pageCode])->first();
if ($pagecode === null) {
return view('company.pages.erros.invalid_code', compact('company, name', page, pcode));
}
$personality = DB::table('personalities')->where([$name])->first();
return view('company.pages.index', compact('company', 'name', 'personality', 'page', pcode));
}

In an update function during the validation with custom rules ignore current id for duplicates in laravel

I am using Laravel 6. I created a form to update a meeting and also some validation rules in the controller that check if the room is free at that time and if the participants are already occupied in another meeting at that time. Unfortunately when I submit the form and I change for example only the description of the meeting, the validation process doesn't work correctly because it says that the current room is already occupied at that time and also the participants are already occupied in another meeting at that time... that occurs because the validation process doesn't exclude to check the current id of the meeting. So the meeting is a duplicate of itself.
I tried many ways to change the validate method excluding the $id passed as second argument to the function update_meeting but they didn't work.
Controller:
public function update_meeting(Request $request, $id)
{
$this->validate($request, [
'participants' => [ 'required', new CheckParticipant() ],
'description' => 'required',
'room' => [ 'required', new CheckRoom() ],
'date_meeting' => [ 'required', new CheckDateTime() ],
'start' => [ 'required', new CheckTime() ],
'end' => 'required',
]);
$meeting = Meeting::find($id);
$participants = request('participants');
$meeting->id_participants = implode(';', $participants);
$meeting->description = request('description');
$meeting->id_room = request('room');
$meeting->date = request('date_meeting');
$meeting->start_hour = request('start');
$meeting->end_hour = request('end');
$meeting->save();
$message_correct = "The meeting has been correctly updated!";
return redirect()->route('home')->with('success', $message_correct);
}
A Custom Rule (CheckRoom):
public function passes($attribute, $value)
{
$meetings = DB::table('meetings')
->where('id_room', request('room'))
->where('date', request('date_meeting'))
->where(function ($query) {
$query->where(function($sub_q) {
$sub_q->where('start_hour', '>=', request('start'))
->where('start_hour', '<', request('end'));
})
->orWhere(function($sub_q) {
$sub_q->where('start_hour', '<', request('start'))
->where('end_hour', '>=', request('end'));
})
->orWhere(function($sub_q) {
$sub_q->where('end_hour', '>', request('start'))
->where('end_hour', '<=', request('end'));
});
})->get();
if(count($meetings) > 0) {
return false;
} else {
return true;
}
}
Is there a way to ignore current id in the validate method or should I modify the rule excluding the id in the query?
You can use it in this way here in my case my field is username and table name id admins and the id to ignored is $id
'username'=>'required|unique:admins,username,'.$id
Have you tried passing the value in the constructor? new CheckRoom($id)
class CheckRoom implements Rule
{
protected $excludeId = null;
public function __construct($id = null)
{
$this->excludeId = $id;
}
public function passes($attribute, $value)
{
return DB::table('meetings')
...
->when($this->excludeId, function ($query, $exclude) {
$query->where('id', '<>', $exclude);
})->doesntExist();
}
...
}

Sort by relationship first in laravel

I have two tables: admins and log_doctor_infos. admins table has relationship hasOne with log_doctor_infos throught doctor_id like this.
In model Admin:
public function logDoctorInfo() {
return $this->hasOne(LogDoctorInfo::class, 'doctor_id', 'id');
// Model LogDoctorInfo is log_doctor_infos table
}
And in Model LogDoctorInfo:
public function doctor(){
return $this->belongsTo(Admin::class, 'doctor_id', 'id');
// Model Admin is admins table
}
I get all data form admins table and i want to sort record has relationship with log_doctor_infos to top.
Yellow record, which has relationship with log_doctor_infos and i want to sort it in top.
Edit: i use paginate in this query and i really want to get quantity of Yellow record.
Thanks for reading!
In my controller, i have custom filter and paginate. Help me.
public function index(Request $request) {
$fullname = $request->query('fullname', NULL);
$phone = $request->query('phone', NULL);
$status = $request->query('status', NULL);
$doctors = (new Doctor)->newQuery();
if ($fullname != NULL) {
$doctors = $doctors->where('fullname', 'LIKE', '%'.$fullname.'%');
}
if ($phone != NULL) {
$doctors = $doctors->where('phone', 'LIKE', '%'.$phone.'%');
}
if ($status != NULL) {
$doctors = $doctors->where('status', $status);
}
$doctors = $doctors
// ->with(array('logDoctorInfo' => function($query) {
// $query->orderBy('updated_at', 'ASC');
// }))
->latest()
->paginate()
->appends([
'fullname' => $fullname,
'phone' => $phone,
'status' => $status
]);
// dd($doctors);
return view('admin.doctors.index', compact('doctors'));
}
you can use the withCount method.
Admin::withCount('logDoctorInfo')
->orderBy('log_doctor_info_count', 'desc')
->paginate(5);
Your controller will look like this
public function index(Request $request) {
$fullname = $request->input('fullname', NULL);
$phone = $request->input('phone', NULL);
$status = $request->input('status', NULL);
$doctorQuery = Doctor::query();
if ($fullname) {
$doctorQuery->where('fullname', 'LIKE', '%'.$fullname.'%');
}
if ($phone) {
$doctorQuery->where('phone', 'LIKE', '%'.$phone.'%');
}
if ($status) {
$doctorQuery->where('status', $status);
}
$doctorQuery->withCount('logDoctorInfo')
->orderBy('log_doctor_info_count');
$doctors = $doctorQuery->paginate()
->appends([
'fullname' => $fullname,
'phone' => $phone,
'status' => $status
]);
// dd($doctors);
return view('admin.doctors.index', compact('doctors'));
}
Doctor::with('logDoctorInfo')->get()->sortByDesc('logDoctorInfo.id');

Laravel Increase SQL speed

I am trying to increase the speed of my queries in Laravel 5.7 and I have the call down to ~2.5 seconds. I am trying to figure out more ways to make it faster and if I could get some help I'd greatly appreciate it.
Thanks
How my data is structured:
Function(Controller):
public function getUserDataTmp(Request $request) {
$input = file_get_contents("php://input");
$request = json_decode($input);
if ($this->authTokenAccess($request) == true) {
$bottomWords = bottom_exterior_word::select('word','sentence','sequence','id','group_id')->where('user_id','=', $request->id)->get();
$emergencyWords = left_exterior_word::select('word','sentence','sequence','id')->where('user_id','=', $request->id)->get();
foreach($bottomWords as $tmp => $key) {
$group_id = $key->group_id;
$bottomWords->user_id = $request->id;
$bottomWords[$tmp]->words = $key->getMainWords($group_id, $request->id);
}
foreach($emergencyWords as $key => $word) {
$emergencyWords[$key]->image = imageModel::select('base64','id')->where('emergency_id','=', $word->id)->first();
}
$data = [
'data' => [
'return' => 'success',
'code' => 'VEDC001',
'response' => 'Successfully Gathered Words',
'main_categories' => $bottomWords,
'emergency_words' => $emergencyWords
]
];
return(json_encode($data));
}
}
getMainWords Function(bottom_exterior_word model):
public function getMainWords($group_id, $id)
{
// return("TEST");
$words = \App\main_word::select('id','group_id','sentence','sequence','word')->where('group_id','=', $group_id)->where('user_id','=', $id)->get();
foreach ($words as $key => $word) {
$words[$key]->image = Image::select('base64','id')->where('word_id','=', $word->id)->first();
}
return $words;
}
Start by refactoring so that you dont query inside a foreach loop
foreach($bottomWords as $tmp => $key) {
$group_id = $key->group_id;
$bottomWords->user_id = $request->id;
$bottomWords[$tmp]->words = $key->getMainWords($group_id, $request->id);
}
I would change the getMainWords function to accepts an array of group id's and use the whereIn clause:
The whereIn method verifies that a given column's value is contained
within the given array:
$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();
Same treatment for this loop.
foreach($emergencyWords as $key => $word) {
$emergencyWords[$key]->image = imageModel::select('base64','id')->where('emergency_id','=', $word->id)->first();
}
In general minimizing the NUMBER of queries will improve response time.
Old post, would just like to update it though. Since I have first posted this, I have learned a lot more about Laravel and am a lot more experienced with it.
Here is my new function and solution:
Controller:
public function data(Request $request)
{
return response()->success(
[
'emergencywords' => EmergencyWord::with('image')->whereUserId($request->user()->id)->get(),
'categorywords' => CategoryWord::with(['image','words.image'])->whereUserId($request->user()->id)->get(),
]
);
}
Category Word Relationships:
public function image()
{
return $this->hasOne('App\Image','id','image_id');
}
public function words()
{
return $this->hasMany('App\MainWord','category_words_id','sequence');
}
Emergency Word Relationships:
public function image()
{
return $this->hasOne('App\Image','id','image_id');
}
Main Word Relationships:
public function image()
{
return $this->hasOne('App\Image','id','image_id');
}

How to use "With" in subqueries laravel eloquent with condition?

I am trying to add new functionality to my project to be able to add tweet anonymously so I need to check if the public field = 0 , return the tweets without user or null user object !
How I can use "with" with OrWhere or something like that?
I have done that by merging the collections but I need more efficient query to do that
when I try OrWhere()->with() that return user data for all collection I need the data in with() comes only with condition ? how to do that ?
Thanks
$tweets = Tweet::where(function ($query) {
$query->where('public',1);
$query->where('hashtag_id', request('hashtag_id'));
})->with([
'user' => function ($query) {
$query->select('id', 'name', 'username', 'photo', 'verified')->withTrashed();
},
'hashtag' => function ($query) {
$query->withTrashed();
},
])-> where(function ($query) {
$query->whereRaw('DATE_ADD(created_at,INTERVAL expiration_time SECOND) >= "' . Carbon::now()->toDateTimeString() . '" or expiration_time = 0');})
->withCount(['replies'])->orderBy('created_at', 'desc')->get();
$tweets2 = Tweet::where(function ($query) {
$query->where('public',0);
$query->where('hashtag_id', request('hashtag_id'));
})->with(['hashtag' => function ($query) {
$query->withTrashed();
},
])-> where(function ($query) {
$query->whereRaw('DATE_ADD(created_at,INTERVAL expiration_time SECOND) >= "' . Carbon::now()->toDateTimeString() . '" or expiration_time = 0');})
->withCount(['replies'])->orderBy('created_at', 'desc')->get();
$tweets = $tweets->merge($tweets2);
I have figured this solution by adding method in the model check this condition and return depends on this condition
$tweets = Tweet::Where(function ($query) {
$query->where('hashtag_id', request('hashtag_id'));
})->with([
'hashtag' => function ($query) {
$query->withTrashed();
},
])->where(function ($query) {
$query->whereRaw('DATE_ADD(created_at,INTERVAL expiration_time SECOND) >= "' . Carbon::now()->toDateTimeString() . '" or expiration_time = 0');})
->withCount(['replies'])->orderBy('created_at', 'desc')->get();
foreach($tweets as $tweet){
$tweet->user = $tweet->GetPublic();
}
That is the method in the model
public function GetPublic()
{
if($this->public == 1)
{ $user = $this->user;
return $user;
}
else{
return null;
}
}

Categories