How to filter collection rows while using .each() in laravel? - laravel

I want to filter this $authors collection.
$authors = Author::get();
In my view, I need to show how many books an author is related.
I use .each() to merge the count to $authors and return it to where it was called.
return $authors->each(function($author, $key){
$author->count = Author::findOrFail($author->id)->books()->count();
});
Question is "How can I remove/filter if an author have not wrriten any of a book(count <= 0)?"
I tried this is and it failed.
return $authors->each(function($author, $key){
$author->count = Author::findOrFail($author->id)->books()->count();
$author->filter(function($author, $key){
return $author->count <= 0;
})
});

You should use withCount() to avoid N+1 and performance problems:
$authors = Author::withCount('books')->get();
In a Blade view, you'll be able to do:
$author->books_count
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models.
https://laravel.com/docs/5.4/eloquent-relationships#counting-related-models

Related

Laravel query on Many to Many relationship

I have an API to keep tracked photos and tags. Each photo can have N tags and one tag can be linked to N photos. That's done using 3 tables:
Photo's table.
Tag's table.
photo_tag relation table.
Now I'm working to get all photos tagged with a set of tags the idea is to make requests to my API with a list of tags and get a list of photos that has at least all the tags.
I've been trying with the whereIn operator.
This is my code (now it's all hardcoded):
$photos = Photo::whereHas('tags', function (Builder $query) {
$query->whereIn('tag', ['a5', 'mate', 'Brillante']);
})->get();
return response()->json($photos, 200);
When I execute it it return all that photos that match one tag and I need only photos that hast all the requested tags (in this example a5, mate).
I'm working on Laravel 9.
Edit:
As Tim Lewis suggested I've tried looping:
$tags = array("a5", "mate", "Brilante");
$photoQuery = Photo::query();
foreach($tags as $tag) {
\Log::debug($tag);
$photoQuery->whereHas('tags', function($query) use ($tag) {
return $query->where('tag', $tag);
});
}
$photos = $photoQuery->get();
Now it's returning an empty list I think because is looking for Photos that only have the 3 tags I hardcoded on the array.
Edit 2:
It seems that those changes were right, but for some reason Postman was not showing me any results of those changes are the solutions to my issue.
Since the whereIn() method matches against any of the values provided, and not all, you'll need to modify this. Specificying a number of whereHas() clauses, 1 for each Tag, should work:
$photoQuery = Photo::query();
foreach ($request->input('tags') as $tag) {
$photoQuery = $photoQuery->whereHas('tags', function ($query) use ($tag) {
return $query->where('tag', $tag);
});
}
$photos = $photoQuery->get();
Now, depending on the tags being sent to your API (assuming through the $request variable as a 'tags' => [] array), this query will include a whereHas() clause for each Tag, and only return Photo records that have all specified Tags.

Exclude empty or null column with Laravel Eloquent

How to exclude empty or null columns when getting collections with Laravel Eloquent ?
I tried this but unsuccessfully:
User::where('last_name', 'foo')->get()->filter()
In addition to #pr1nc3 answer, there is a method ->reject() for this specific purpose. It rejects/excludes the items that match the condition. For your case, use it like this:
User::where('last_name', 'foo')->get()->reject(function ($value) { return empty($value); });
All the values that meet the condition empty($value) i.e. the values that are null/empty will be rejected.
You can do the filter in 2 steps
$users = User::where('last_name', 'foo')->get(); //returns your collection
Then you can use filter for your collection like:
$myFilteredCollection = $users->filter(function ($value) { return !empty($value); });
If you still need it in one line then you can do:
Of course you can merge it into one, get() actually outputs the collections but looks a bit ugly i think. Keep your actions separate.
$users = User::where('last_name', 'foo')->get()->filter(function ($value) { return !empty($value); });

Eloquent how to pass parameters to relationship

The code I'm trying to fix looks like this. I have an Hotel class which is used in a query to get all hotels in an area but it doesn't discard those which are not available. There's a method inside which should be an accessor but it's not written the way I expected it to be:
public function isAvailableInRanges($start_date,$end_date){
$days = max(1,floor((strtotime($end_date) - strtotime($start_date)) / DAY_IN_SECONDS));
if($this->default_state)
{
$notAvailableDates = $this->hotelDateClass::query()->where([
['start_date','>=',$start_date],
['end_date','<=',$end_date],
['active','0']
])->count('id');
if($notAvailableDates) return false;
}else{
$availableDates = $this->hotelDateClass::query()->where([
['start_date','>=',$start_date],
['end_date','<=',$end_date],
['active','=',1]
])->count('id');
if($availableDates <= $days) return false;
}
// Check Order
$bookingInRanges = $this->bookingClass::getAcceptedBookingQuery($this->id,$this->type)->where([
['end_date','>=',$start_date],
['start_date','<=',$end_date],
])->count('id');
if($bookingInRanges){
return false;
}
return true;
}
I wanted to filter out hotels using this query. So this is the query from the controller:
$list = $model_hotel->with(['location','hasWishList','translations','termsByAttributeInListingPage'])->get();
Is it possible to pass the range of days to the function?
By the way the first thing I tried was to use the collection after the query and pass a filter function through the collection and after that paginate manually but although it does filter, but apparently it loses
the "Eloquent" result set collection properties and it ends up as a regular collection, thus it doesn't work for me that way.
Maybe the best approach for that is to create a query scope (source) and put all your logic inside of this function.
after that you can call this scope and pass the dates. Example you will create a query scope and paste your code inside of it.
public function scopeisAvailableInRanges($query, $start_date, $end_date) {
}
then you will invoke this query scope in your controller like this.
$list = $model_hotel::isavailableinranges($start_date, $end_date)->with(['location','hasWishList','translations','termsByAttributeInListingPage'])->get();
keep in mind that inside of your query scope you will return a collection. A collection of all your available hotels.

sortBy not working (Nothings happen) in my case in laravel5.1

I'm trying to use the sortBy() of laravel but it has no effect for my situation. I want to sort the question_number field in the question_numbers table. I'm using belongsToMany relationship here. Please see my code for reference:
$undeleted = function($query){
return $query->where('deleted', 0);
};
$quiz = Quiz::with([
'multiple_choices.question_numbers' => $undeleted
])->find($id);
$quiz_items = collect($quiz->multiple_choices);
$quiz_items->sortBy(function($quiz_item){
return $quiz_item['question_numbers.question_number'];
});
dd($quiz_items);
The result of the code above is just list of multiple choices without sorting.

How to re-order an Eloquent collection?

I've got a collection of records retrieved via a relationship, and I'd like to order them by the created_at field. Is this possible in Eloquent?
Here is how I am retrieving the collection:
$skills = $employee->skills;
I'd like to order this $skills collection by their creation. I've tried $skills->orderBy('created_at', 'desc'); but the Collection class does not have an orderBy method.
I guess this problem is very simple and I'm missing something..
You can do this in two ways. Either you can orderBy your results while query, as in
$employee->skills()->orderBy('created_at', 'desc')->get();
OR
You can use sortBy and sortByDesc on your collection
The reason this is failing is that orderBy is a query method not a collection method.
If you used $skills = $employee->skills()->orderBy('created_at', 'desc')->get();, this would query the skills in the order you want.
Alternatively, if you already had a collection that you wanted to re-order, you could use the sortBy or sortByDesc methods.
You need to add the orderBy constraint on the query instead of the relationship.
For e.g,
$employees = Employee::where('salary', '>', '50000') // just an example
->with('skills') // eager loading the relationship
->orderBy('created_at', 'desc')
->get();
and then:
foreach($employees as $employee)
{
var_dump($employee->skill);
}
If you want the results to always be ordered by a field, you can specify that on the relationship:
Employee.php
public function skills() {
return $this->hasMany(Skills::class)->orderBy('created_at');
}
If you just want to order them sometimes, you can use orderBy(), but on the relationship, not the property:
$skills = $employee->skills()->orderBy('created_at')->get();
Collection has sortBy and sortByDesc
$skills = $skills->sortBy('created_at');
$skills = $skills->sortByDesc('created_at');
This Stackoverflow question askes how to order an Eloquent collection. However, I would like to propose a different solution to use instead given the example in the question. I would like to recommend to use an ordering on the query itself for performance reasons.
Like #Don't Panic proposes you can specify a default ordering on the relationship for great reusability convenience:
app/Models/Employee.php
public function skills() {
return $this->hasMany(Skills::class)->orderBy('created_at');
}
However, if you have already set an ordering on your query like we do in the code above, any additional orderings will be ignored. So that is a bummer if you want to use a different sorting in another situation. To overwrite this default ordering and re-order the query with a new ordering, one needs to use the reorder() method. For example:
// Get a Collections of Skill-models ordered by the oldest skill first.
$skills = $employee->skills()->reorder()->orderByDesc('created_at')->get();
// Same result as the previous example, but different syntax.
$skills = $employee->skills()->reorder()->oldest()->get();
// Or just give some arguments to the reorder() method directly:
$skills = $employee->skills()->reorder('created_at', 'desc')->get();

Resources