Helping querying relations in eloquent - php

I have this query in my codebase,
$listings = Tag::has('listings')->with(['listings' => function ($query) use ($request) {
$query->where('moderated', 1)
->where('active', 1);
if($request->query('free') == true) {
$query->where('cost', '0.00');
}
if($request->query('type') != "") {
$query->with(['types' => function($q) use ($request) {
$q->whereIn('id', explode(",", $request->query('type')));
}]);
}
$query->with('primaryImage');
}])
->paginate(3);
What I am trying to do is add parts of the query based on what is in the GET request (this bit works), what isnt working is the query on a relation.
Here I am querying Tags that can have many listings, each listing can have many types and I want to only return tags that have listing that match the filter parameters, i.e only show listings that cost "0.00" and then only tags that have listings that match the types in the get request.
So if a user sends type=1,2,3 in the GET request I want to return tags that have listings where the types relationship contains 1 of those IDs, is this possible?
The types relationship on a listing looks like this,
public function types() {
return $this->belongsToMany('App\Type');
}
and the relation from type to listing looks like this,
public function listings() {
return $this->belongsToMany('App\Listing');
}

Instead of using has and with maybe you can use whereHas
$listings = Tag::whereHas('listings', function($query){
$query->where('moderated', 1)
->where('active', 1);
if($request->query('free') == true) {
$query->where('cost', '0.00');
}
if($request->query('type') != "") {
$query->whereHas('types', function($q) use($request){
$q->whereIn('id', explode(",", $request->query('type')));
});
}
$query->with('primaryImage');
});

As you said, "each listing can have many types"
Change this in your Listing::class
public function types() {
return $this->hasMany('App\Type');
}

Related

Laravel: search and filter data

i want to multi filter data in laravel but i show this error:
Too few arguments to function Illuminate\Support\Collection::get()
Please help me to solve this issue.
public function searchLanding(Request $request)
{
$landings = Landing::all();
if(count($landings) && !is_null($request->title)) {
$landings = $landings->where("name", "LIKE", "%{$request->title}%")->get();
}
if (count($landings) && !is_null($request->start_at)) {
$landings = $landings->where('start_at', '>=', $request->start_at)->get();
}
if (count($landings) && !is_null($request->end_at)) {
$landings = $landings->where('end_at', '<=', $request->end_at)->get();
}
}
public function searchLanding(Request $request)
{
$landings = Landing::query();
if(!is_null($request->title)) {
$landings->orWhere("name", "LIKE", "%{$request->title}%");
}
if (!is_null($request->start_at)) {
$landings->orWhere('start_at', '>=', $request->start_at);
}
if (!is_null($request->end_at)) {
$landings->orWhere('end_at', '<=', $request->end_at);
}
return $landings->get();
}
Note:
You shouldn't call all() or get() when you are still building your query, only call them when you want to get the result.
Use where() when you want all conditions to be true,
or use orWhere() when you want one of the conditions to be true.
In the example above, only one of the conditions needs to be true e.g. search found in title or after start_at or before end_at.

laravel retrieving data from database takes too much time

I am working with laravel project which uses a mysql database.It has some tables which has over 5 million data.it takes too much time to get these data to frontend. following is the function i use to get data. i have some filters used such as date range (from,to) search by name($seach) pagination amount(records_number) etc. but when i try to get all records it takes too much time. is there any solutions/optimizations for this matter?
Thanks.
public function transactionListBetween($from, $to, $sort, $search, $records_number, $filter, $previous_sort) {
$query = $this->accountTransactions
->with('transactionType', 'giver', 'recipient')
->leftJoin('tbdb_users as recipient', 'recipient.id', '=', 'tbdb_account_transaction.recipient_id')
->leftJoin('tbdb_users as giver', 'giver.id', '=', 'tbdb_account_transaction.giver_id')
->leftJoin('tbdb_account_transaction_type', 'tbdb_account_transaction.account_transaction_type_id', '=', 'tbdb_account_transaction_type.id')
->where('recipient.name', 'like', "%$search%")
->select('tbdb_account_transaction.*');
if($filter) {
if($filter == -1) {
$transactionTypeModel = \App::make('App\Models\AccountTransactionTypeModel');
$depositTypeIds = $transactionTypeModel->whereIn('name', ['PayPal Deposit', 'Eway Deposit', 'Bank Deposit', 'BPay Deposit', 'Poli Deposit'])->lists('id');
$query = $query->whereIn('tbdb_account_transaction.account_transaction_type_id', $depositTypeIds);
} else {
$query = $query->where('tbdb_account_transaction.account_transaction_type_id', $filter);
}
}
if($from) {
$query = $query->where('tbdb_account_transaction.created_date', '>=', $from);
}
if($to) {
$query = $query->where('tbdb_account_transaction.created_date', '<=', $to->endOfDay());
}
if($records_number == 'no_paginate') {
return $query->orderBy($sort, $previous_sort)
->get();
} else {
$totalAmount = $query->sum('amount');
$collection = $query->orderBy($sort, $previous_sort)->paginate($records_number);
$collection->totalAmount = number_format($totalAmount / 100, 2);
return $collection;
}
}
You seem to be doing 3 left joins and getting the same data as you did with the with.
You can drop them:
public function transactionListBetween($from, $to, $sort, $search, $records_number, $filter, $previous_sort) {
$query = $this->accountTransactions->->with('transactionType', 'giver', 'recipient')
->whereHas('recipient', function ($query) use ($search) { //Query the related model
$query->where('name', 'LIKE', "%$search%"); //Note this will not use an index
});
if ($filter) {
if ($filter == -1) {
$query->whereHas('transactionType', function ($query) {
$query->whereIn('id', \DB::raw("(SELECT id FROM tbdb_account_transaction_type WHERE name IN ('PayPal Deposit', 'Eway Deposit', 'Bank Deposit', 'BPay Deposit', 'Poli Deposit')");
});
} else {
$query->whereHas('transactionType', function ($query) use ($filter) {
$query->where('id', $filter);
});
}
}
if($from) {
$query = $query->where('created_date', '>=', $from);
}
if($to) {
$query = $query->where('created_date', '<=', $to->endOfDay());
}
if($records_number == 'no_paginate') {
return $query->orderBy($sort, $previous_sort)
->get();
} else {
$totalAmount = $query->sum('amount');
$collection = $query->orderBy($sort, $previous_sort)->paginate($records_number);
$collection->totalAmount = number_format($totalAmount / 100, 2);
return $collection;
}
}
This will reduce the main result set greatly making it easier to work with.
In my case i have also having this problem when using mysql database. These much records in table with relationship takes too much time. You have to use raw query to get result. You have to use pagination to show the data. because fetching all the data will slow down.
But i highly recommended you to normalize your table structure. In my case i did normalize of my tables by split it with yearly.
Found out why this is happening. it is because of pagination. it takes lot of time to paginate. there are more than 50000 pages so it takes time. i used simplePaginate() instead of paginate(). then it loads faster than usual

Laravel Eloquent split large query in to chunk and reuse it

I am having really a very big query formation like below. i want to split this and need to re-use for many other ajax call's
$buildquery=Hotel::has('room');
$buildquery->whereHas('room', function($query) use ($request) {
// If amenities is there add it to query
if($request->filled('amenities')){
$amenities = $request->amenities;
$count = count($amenities);
$query->withCount(['amenities' => function($query) use ($amenities, $count){
$query->whereIn('amenities_id', $amenities);
}])
->having('amenities_count', $count);
}
/* filter based on guest */
if($request->filled('guestsCount')){
$memberCount = $request->guestsCount + $request->childCount;
$query->Where('capacity', '>=', $memberCount);
}else{
$query->Where('capacity', '>=', 1);
}
});
$buildquery->with(['room' => function ($query) use ($request) {
// If amenities is there add it to query
if($request->filled('amenities')){
$amenities = $request->amenities;
$count = count($amenities);
$query->withCount(['amenities' => function($query) use ($amenities, $count){
$query->whereIn('amenities_id', $amenities);
}])
->having('amenities_count', $count);
}
/* filter based on guest */
if($request->filled('guestsCount')){
$memberCount = $request->guestsCount + $request->childCount;
$query->Where('capacity', '>=', $memberCount);
}else{
$query->Where('capacity', '>=', 1);
}
$query->with('roomtype')->with('floorroomcount')->with('image')->with('amenities');
$query->OrderBy('price');
$query->Where('astatus', 1)->Where('status', 0);
}]);
/* client must be active */
$buildquery->whereHas('client', function($query) {
$query->Where('status', 1);
});
/* search based on rating */
if ($request->filled('rating')) {
if($request->rating > 0){
$rating = $request->rating;
$buildquery->where('star', $rating);
}
}
/* search based on hotel */
if ($request->filled('location_id')) {
$buildquery->Where('city', $request->location_id);
}
#include('roomlist.area');
$buildquery->Where('astatus', 1)->where('status', 0); //actually its hotel
$hotels = $buildquery->simplePaginate(20);
$hotels = $this->addRates($hotels, $request->checkin_date, $request->checkout_date);
$hotels = $this->addAvailableCount($hotels, $request->checkin_date, $request->checkout_date);
$hotels = $hotels->transform(function (Hotel $hotel){
$hotel->setRelation('room', $hotel->room->sortBy('price')->flatten());
return $hotel;
});
return view('roomlist.loadmore', compact('hotels'));
please see this line #include('roomlist.area'); in that roomlist/area.blade.php file i am having the following code
<?php
if($request->filled('type')){
if($request->type == "Area"){
//get the area first
$hotel = Hotel::select('area')->where('city', $request->location_id)->first();
if(isset($hotel)){
if($hotel->area != null){
$buildquery->where('area', $hotel->area);
}
}
}
}
?>
Is there any way that i can include this code from a blade or in any other manner.
Note: i need to re-use many things like this.
First thing you could do is break some of this functionality into scopes: https://laravel.com/docs/5.6/eloquent#query-scopes
For instance, you could change this:
/* search based on hotel */
if ($request->filled('location_id')) {
$buildquery->Where('city', $request->location_id);
}
Into this:
if ($request->filled('location_id')) {
$buildquery->inCity($request->location_id);
}
Or this:
/* client must be active */
$buildquery->whereHas('client', function($query) {
$query->Where('status', 1);
});
into this:
$buildquery->withActiveClient();
This is a small change but it allows you to use inCity in other places without re-writing as much, and for the other scopes it might be more code you can extract.
You could also make a Transformer class to change this:
$hotels = $hotels->transform(function (Hotel $hotel){
$hotel->setRelation('room', $hotel->room->sortBy('price')->flatten());
return $hotel;
});
To this:
$hotels = (new HotelRoomTransformer())->transform($hotels);
This type of extracting code could make this file much more readable, and that way if you need to reuse parts of it you have them in separate, reusable files.
Lastly, this type of functionality can all be extracted into a repository if you want to entirely remove Eloquent from your controllers. Here is a short guide on the repository pattern: https://medium.com/#connorleech/use-the-repository-design-pattern-in-a-laravel-application-13f0b46a3dce

How to use Laravel's attach() on filtered query result?

I would like to use attach() on a filtered Builder result:
$users = User::whereIn('type', array(1, 2))->get();
$usersType_1 = $users->filter(function($item) {
return $item->type == 1;
});
$usersType_2 = $users->filter(function($item) {
return $item->type == 2;
});
$usersType_1->role()->attach(3);
$usersType_2->role()->attach(4);
So, I need to attach the role based on the user type. The role() method is specified on the User model
The attach() part from the code above throws the following error: Method role() doesn't exist - which I assume happens because filter() returns a Collection.
Is there a working way to attach pivot entries on filtered Builder result? Or do I need to run 2 separate queries and run attach() on them respectively?
You couldn't use role method no collection instead of User model. Try by:
$users = User::whereIn('type', array(1, 2))->get();
$usersType_1 = $users->filter(function($item) {
if($item->type == 1) {
return $item->role()->attach(3);
}
})
->all();
$usersType_2 = $users->filter(function($item) {
if($item->type == 2) {
return $item->role()->attach(4);
}
})
->all();
Alternative :
$users = User::whereIn('type', array(1, 2))->get();
$usersType_1 = $users->filter(function($item) {
$item->type == 1;
})
->all();
$usersType_2 = $users->filter(function($item) {
return $item->type == 2;
})
->all();
$role1 = Role::find(3);
$role2 = Role::find(4);
$role1->users()->attach($usersType_1->puck('id'));
$role2->users()->attach($usersType_2->puck('id'));
Why not just do:
$usersType_1 = $users->where('type', 1); // or whereType(1)
$usersType_1->role()->attach(3);
$usersType_2 = $users->where('type', 2); // or whereType(1)
$usersType_2->role()->attach(4);
I think the "Method role() doesn't exist" exception occurs because you're filtering an Eloquent Collection.
EDIT: I see why this doesn't work. It's because you're trying to attach an entire collection to a role. It would work if you used find() and then attached a role.
So you should loop over all the users with type 1 and attach the role
foreach ($usersType_1 as $user) {
$user->role()->attach(3);
}

Input returns empty query in error

I have a movies website that I want to allow people to search by genre
movies.com/people/action/genre
The Route
Route::get('people/{genre}/genre', array('uses' => 'ActorController#genre', 'as' => 'people.genre'));
The ActorController#genre
public function genre()
{
$genre=Input::get('genre');
$actors = $this->actor->allgenre($genre);
return View::make('Actor.All')->withActors($actors);
}
This grabs all the actors from the db
function allGenre($genre)
{
return $this->actor->where('genre', 'like', '$genre')->orderBy('views', 'desc')->paginate(24);
}
This is returning no results, when it should be returning results because if I go
function allGenre($genre)
{
return $this->actor->where('genre', 'like', 'action')->orderBy('views', 'desc')->paginate(24);
}
Results show up
When you use that routing, the $genre variable would be bound the the controller:
public function genre($genre)
{
$actors = $this->actor->allgenre($genre);
return View::make('Actor.All')->withActors($actors);
}
While in your previous question you used query strings, so you needed Input::get('key'), now you changed the url, and you don't use resource controllers anymore, so you must go back to the "usual" way
I got it to work
function allGenre($genre)
{
return $this->actor->where('genre', 'like', $genre)->orderBy('views', 'desc')->paginate(24);
}
Apparently Laravel doesn't accept '$genre' with the ' around the variable

Categories