How to attach conditionals to eloquent relation query? - php

I am trying to create the filters and I just want to attach the query to relation by using some conditionals. I don't want to put those conditional in the first block of the code. So how can I get the query instance so I can attach the new queries to the existing relation?
$query = Category::query();
$query->where('category_type', 'xyz')
->with(['products' => function ($query) {
$query->where('condition1', 'value')
->where('condition2', 'value');
}]);
if (isset($queryParams['param1'])) {
$query->with(['products' => function ($query) use ($queryParams) {
$query->getQuery()->where('type', $queryParams['param1']);
}]);
}
Currently, it just overwrites the first query relation condition.

This should work :
$query = Category::query();
$query->where('category_type', 'xyz')
->with(['products' => function ($query) use ($queryParams) {
$query->where('condition1', 'value')
->where('condition2', 'value');
if (isset($queryParams['param1'])) {
$query->where('type', $queryParams['param1']);
}
}]);

Related

Laravel Eloquent do multiple counts on a relationship

I want to do multiple counts on a relationship. I can do 1 using the withCount method and passing it a closure with the condition like so:
$subscribedAudits = Audit::whereHas('users', function ($query) {
$query->whereIn('user_id', [Auth::user()->id])->where('approved', 1);
})->withCount(['users' => function($query) {
$query->where('approved', 0);
}])->with(['users'])->get();
This creates a field on the result called users_count with the count of users where approved is = 0. This is the expected result.
Now I want to do a count on another field on the users table but I'm unsure how to work that into the Eloquent query. I tried the following:
$subscribedAudits = Audit::whereHas('users', function ($query) {
$query->whereIn('user_id', [Auth::user()->id])->where('approved', 1);
})->withCount(['users' => function($query) {
$query->where('approved', 0);
}])->withCount(['users' => function($query) {
$query->where('viewed', 0);
}])->with(['users'])->get();
This won't work as it can only return 1 attribute named users_count.
Is there any way around this without doing another query?
You can pass multiple counts to the withCount method and give them a custom name like so:
Model::withCount([
'relationship as count_1' => function ($query) {
$query->where('active', 1);
},
'relationship as count_2' => function ($query) {
$query->where('active', 0);
}
]);
In your case it would be something like this:
$subscribedAudits = Audit::whereHas('users', function ($query) {
$query->whereIn('user_id', [Auth::user()->id])->where('approved', 1);
})->withCount([
'users as disapproved_count' => function ($query) {
$query->where('approved', 0);
},
'users as not_viewed_count' => function ($query) {
$query->where('viewed', 0);
}
])->with(['users'])->get();
Aggregate functions sometimes treated not as expected by Eloquent. I suggest using selectRaw('count() FILTER (WHERE)') instead.

Filtering in Laravel using regex

I'm trying to filter products based on query string. My goal is to get products from a collection if it's given, otherwise get every product. Could someone help me what's wrong with the following code?
$products = \App\Product::where([
'collection' => (request()->has('collection')) ? request('collection') : '[a-z]+',
'type' => (request()->has('type')) ? request('type') : '[a-z]+'
])->get();
PS.: I've also tried with 'regex:/[a-z]+', it's not working...
$products = \App\Product::where(['collection' => (request()->has('collection')) ? request('collection') : 'regex:/[a-z]+'])->get();
What you can do is use when eloquent clause, so your where clause for collections will be triggered only when the request('collection') exists, same logis applie to type as well.
$products = \App\Product::
when(request()->has('collection'), function ($q) {
return $q->where('collection', request('collection'));
});
->when(request()->has('type'), function ($q) {
return $q->where('type', request('type'));
})
->get();
Or another way if you have your request values assigned to a variable something like:
$collection = request('collection');
$type= request('type');
$products = \App\Product::
when(!empty($collection), function ($q) use ($collection) {
return $q->where('collection', $collection);
});
->when(!empty($type), function ($q) use ($type) {
return $q->where('type', $type);
})
->get();

reduce database query to one and avoid Call to a member function load() on null error

I have this function:
public function show($id)
{
if (count($post = Post::find($id))) {
$post = $post->load(['comments' => function ($q) {
$q->latest();
$q->with(['author' => function ($q) {
$q->select('id', 'username');
}]);
}, 'user' => function ($q) {
$q->select('id', 'username');
}]);
$this->authorize('seePost', $post);
return view('post.show', ['post' => $post]);
} else {
dd('no post');
}
}
I added the if statement as if I try to open a route to a non existent post id I get the error Call to a member function load() on null.
However now I have two queries, one looks for the Post in the DB and if it finds one then I have to load the relations with the second one. What can I do to go back to just one query with all the relations loaded and avoid the error? Any clue?
You can use Constraining Eager Loads do it like this:
https://laravel.com/docs/5.8/eloquent-relationships#constraining-eager-loads
$post = Post::with(["comments" => function ($query) {
// Order by created_at, query comment author & select id, username
$query->latest()->with(["author" => function ($q) {
$q->select("id", "username");
}]);
}, "user" => function ($query) {
// Query post author & select id,username
$query->select("id", "username");
}])
// Fetch post or throw a 404 if post is missing
->findOrFail($id);
// You can also return an empty post instance like this if post is missing
// ->findOrNew([]);
// Or return the post or null if post is missing
// ->find($id);
// Authorize
$this->authorize('seePost', $post);
return view("post.show", ["post" => $post]);
Laravel has an Eager Loading feature that would be helpfull in your case. Eager Loading allows you to autoload relations along with the same query that you use to retrieve your main model info. https://laravel.com/docs/5.8/eloquent-relationships#eager-loading
You could a below codes.
Easiest way is :
$post = Post::with('comments.author', 'user')
->find($id);
Or fine tune query with callback :
$post = Post::with(['comments' => function ($q) {
// if you use comments select, then you need to specify foreign key too
$q->select('id', 'author_id', 'details') // comment fields
->latest(); // Use chaining method
// OR use $q = $q->latest();
},
'comments.author' => function ($q) {
$q->select('id', 'username'); // author fields
},
'user' => function ($) {
$q->select('id', 'username'); // user fields
}])
->find($id);
In some cases you might need some modifications, bu in overall that should avoid you N+1 queries problem.

return view is returning different data

everybody, My code here is returning tasks that have more than 1 Tag and task tags are in $TagArray everything is working good in
return $TaskData;
but When I pass TaskData into view I get different results even get Tasks with one tag
return view ('task', compact('TaskData'));
My Code
$TaskData= Path::with(['pathtags' => function ($q) use ($TagArray) {
$q->with(['Tasks' => function($q) use ($TagArray) {
$q->has('tasktags', '=' , 2)->whereDoesntHave('tasktags',
function ($query) use ($TagArray) {
$query->whereNotIn('name', $TagArray);
}
)->with('tasktags');
}]);
}])->first();

Selecting required fields using relations in Laravel

I have a model Meetings like this:
public function meeting_comments(){
return $this->hasMany('App\MeetingsComments', 'meeting_id', 'id');
}
public function meeting_users() {
return $this->hasMany('App\UserMeetingDetails', 'meeting_id', 'id');
}
The Controller is like this:
$res = Meetings::with('meeting_comments', 'meeting_users')
->select('')->get()->toArray();
I only need comments from meeting_comments and user_id from meeting_users.
What do I put in select to only get the required fields from meeting_comments and meeting_users ??
You do it through a closure in the with call:
$res = Meetings::with(['meeting_comments' => function($query) {
$query->select('comments', 'meeting_id');
}, 'meeting_users' => function($query) {
$query->select('user_id', 'meeting_id');
}])
->get()->toArray();
I'm taking this from memory, so the syntax may be slightly incorrect, but it should work. :)

Categories