How to use groupBy() to group a Collection object - php

My goal is to retrieve all of a user's 'items' and display then in my view grouped by their 'status'. There are 4 possible statuses, each with their own <div> on the page containing the items' info. After some poking around I believe I need to use the groupBy() method like so:
$items = Item::ownedBy( Auth::id() )->groupBy('status')->get();
This does seem to do some sort of grouping, but when I iterate over the collection I get a max of 4 results, one for each status. This doesn't really solve my problem as I need to display all of a user's items for each status, not just one. I must be missing something here, I'd really like to avoid making a query for each status and displaying them that way. I suppose I could filter the collection by status and create 4 new collections, but isn't this what groupBy() is supposed to do?

You can do that very easy with laravel and Collections. Collections have powerful API and a lot of handy methods, to easily achieve what you want.
Your mistake here is that you are calling groupBy on the QueryBuilder object which returns only groupped by records from the database. Instead you must select all of the records you need, then they will be returned as a collection. After that you can manipulate the collection as you wish. So what you need is:
$items = Item::ownedBy( Auth::id() )->get()->groupBy('status');
You can view all of the Colletion class useful methods here.

Related

Get Laravel collection's item using collection value

Suppose we got a collection from the database like this:
$projects = Projects::all();
Now, for example, I want to get the specific project using the specified column's value. For example, suppose any project has a unique pr_code, Now I want to get an item of collection that pr_code is 1234.
Note = I Know using Projects::where('pr_code', 1234)->first() But I didn't want this. I want use inside collection
How I could do that?
Collections also have where and first (and whereFirst ) functions. So you just need to change the order of functions in your chain: Projects::all()->firstWhere('pr_code', 1234)
Projects::all() queries all records in projects and returns them as a Collection of Projects models, this could be quite a performance hit.
Projects::where('pr_code', 1234)->get() will query for the specific projects and return a Collection of Projects models that have pr_code of 1234.
Projects::where('pr_code', 1234)->first() will do the same, but return the first as a Projects model.
I recommend naming the model Project rather than the plural Projects. The Laravel model is smart enough to know to use the plural name of the database table.

GroupBy data with a specific column using Laravel 8 [duplicate]

My goal is to retrieve all of a user's 'items' and display then in my view grouped by their 'status'. There are 4 possible statuses, each with their own <div> on the page containing the items' info. After some poking around I believe I need to use the groupBy() method like so:
$items = Item::ownedBy( Auth::id() )->groupBy('status')->get();
This does seem to do some sort of grouping, but when I iterate over the collection I get a max of 4 results, one for each status. This doesn't really solve my problem as I need to display all of a user's items for each status, not just one. I must be missing something here, I'd really like to avoid making a query for each status and displaying them that way. I suppose I could filter the collection by status and create 4 new collections, but isn't this what groupBy() is supposed to do?
You can do that very easy with laravel and Collections. Collections have powerful API and a lot of handy methods, to easily achieve what you want.
Your mistake here is that you are calling groupBy on the QueryBuilder object which returns only groupped by records from the database. Instead you must select all of the records you need, then they will be returned as a collection. After that you can manipulate the collection as you wish. So what you need is:
$items = Item::ownedBy( Auth::id() )->get()->groupBy('status');
You can view all of the Colletion class useful methods here.

internal orderBy gets overwritten by external orderBy in Laravel Eloquent

I have a reliationship User hasOne Position and I'm fetching the users, but I want to sort them first by Position->name and then by User->name. I tried the following
<?php
$sorted = Position::where('groupId', $this->groupId)
->whereIn('id', $positions)
->with(['user' => function($query) {
$query->orderBy('user.name'); // internal sort
}])
->orderBy('position.name') // external sort
->get();
This way the results are sorted by the external sort only, or, by Position->name. Different users with the same Position->name are listed unsorted. If I remove the external sort, and leave only the sortBy User->name, it works, BUT only for the names, while positions are random.
I have tried different ways
setting the order in the Position->user relationship, does not work
setting the order in the User->position relationship, does not work
defining only an external orderBy('position.name, user.name'), crashes, saying user table is not in the query.
I also tried following similar questions like
Laravel orderBy on a relationship
Ordering Related Models with Laravel/Eloquent
but they don't seem to be trying to sort the results both by the parent and the relationship. It seems my only solution is to walk the result and sort them in PHP instead of from the DB, but this sounds dumb.
Please advice, thank you.
When you want to sort the parent Position by the relationship User, you need to use a join():
$sorted = Position::where(...)
->whereIn(...)
->with('user')
->join('users', 'users.id', '=', 'positions.user_id') // Or whatever the join logic is
->orderBy('users.name')
->orderBy('positions.name')
->get();
Note: The orderBy() on the user relationship within with() doesn't seem necessary, as by convention, a singular-named relationship should only return 1 record, and sorting on a single record is pointless.
This will return a Collection of Position models, with an attached User model, sorted by the User's name, then the Position's name. You might need to add a select('positions.*') to avoid any ambiguity issues, but this should give you the general idea.

Filter Illuminate collection based on emptiness of child collection

Orders implements a HasManyThrough relationship to Items. I want to filter the Orders collection to ones just the orders that have zero items. I tried:
$orders->where('items', '!=', []);
This of course doesn't work as it's only possible to do discrete comparisons (equal, not equal, less/greater than, etc) against concrete values. There is no "isEmpty" property. Something like a whereCallback would be super.
What would be the easiest way to perform this? The same $orders collection is already used in multiple ways in the same request, so I'd rather not do another roundtrip to the database.
It would be easier if you include the items_count to the orders collection while fetching it from the database.
$orders = Order::withCount('items')->restOfTheQuery()->get();
this will add another property called items_count to each and every order object in the $orders collection
and then, when you want only the orders which doesn't have items
// using collections where function
$orderWithoutItems = $orders->where('items_count', 0);
I think filter() is kind of like the whereCallback you're looking for.
$ordersWithoutItems = $orders->filter(function($order) {
return $order->items()->doesntExist();
});

Laravel model object retrieving relation model with where condition without "get" method

I'm working on octoberCMS(laravel framework), I have a doubt on retrieving relation model on where clause.
I have fetched a clothing_type record from "clothing type" model based on its primary key "id".
$clothing_type = ClothingType::where('id',post('clothingtype_id'))->where('status','Active')->first();
This "clothing type" model is related with "products" model, the relation is => each clothing type hasMany products.
Every thing works fine; Now my business logic has two cases, one is to get all the products of the clothing type and another is to get the first product of the clothing type. So I have used the $clothing_type->products to get all the products and $clothing_type->products->first() to get the first product.
Now I have to apply a condition for both the cases. The condition is that only the product whose status is "Active" should be fetched, hence
$products = $clothing_type->products->where('status','Active'); and$first_product_detail = $products->first();.
Every thing works as expected but how come the products are fetched without "get()" method. $clothing_type->products->where('status','Active')->get(); Since I'm new to relation I want to know how this works or is this a bad way to get records or improper assumption. But every thing works good.
$clothing_type = ClothingType::where('id',post('clothingtype_id'))->where('status','Active')->first();
if(count($clothing_type->products))
{
$products = $clothing_type->products->where('status','Active');
$first_product_detail = $products->first();
}
You are doing it the correct way. When you access the relationship as an attribute Eloquent automatically retrieves the records.
However, if you access the relationship as a method, you get the query itself, to which you can add your filters:
if(count($clothing_type->products))
{
$products = $clothing_type->products()->where('status','Active')->get();
$first_product_detail = $products->first();
}
This would solve your problems
(documentation is over here (see the first item))
Edit: Also note that the first method is not a method of Eloquent, but from Collection, which is pretty powerful!
Edit2:
I misread the part of your question where you want to know HOW this is possible. Both Eloquent and Collections have a where method. I assume you understand the working of the Eloquent one, but the one from Collection is pretty much the same (see documentation on the Collection where here)
I prefer the Eloquent one myself, because that limits the amount of records that is retrieved from the database. But if you need all the products (even the inactive ones) later on in your code, just use the Collection method to filter the active ones out
There is nothing to be afraid of...
first() and where()
are functions of both Illuminate\Database\Query\Builder as well as Illuminate\Support\Collection and all first does is limit the records to take 1 and then give you the first record. When you use Builder a query is made to get 1 record and 1 you use it on a collection, all records are first get() and then the first of those records is returned.
Here,
When you do,
$clothing_type->products, Laravel gives you a collection of products...
So...
$products is an object of Illuminate\Support\Collection
and
$products->first() calls for the first() function in that class.
Documentation on where and first methods of a collection...

Categories