Laravel | groupBy returning as object - laravel

I am trying to use a Laravel collection to return a groupBy as an array. However, it always seems to be returned as an object no matter what. I have tried to do $posts->groupBy('category')->toArray() but this seems to still return as an object. I have also tried $posts->groupBy('category')->all() and still it is returning as an object.
I don't know if this is something to do with Laravel returning methods within the routes, but I need this to return as an array.
Here is the code:
public function getFeatures($id)
{
return Feature::query()->get()->groupBy('category')->toArray();
}
The actual code is working fine and I'm getting results back but it just doesn't seem to be converting to an array. Thanks.

When doing a query to get (possibly) several items using Eloquent, Laravel will always return an instance of the Collection class that contains all the model objects. If you need them converted to array to use them in a view you could compact the elements. compact will make an associative array of the elements of the collection:
public function getFeatures($id)
{
$features = Feature::all();
return view('my_cool_view', compact($features));
}
On the other hand, if you need them converted to array to return them through an API, Laravel convert the response to JSON by default:
public function getFeatures($id)
{
return Feature::all();
}
Now, if you somehow need the collection converted to an array, just use toArray() like you indicated:
public function getFeatures($id)
{
$collection_of_features = Feature::all();
$array_of_features = $collection_of_features->toArray();
// use it for wherever you want.
}

By reading your comment on other answer, I realized what you mean.
Hi #HCK, thanks for the answer. The ->toArray() method doesn't seem to work and still returns it like { "category_1": [], "category_2": [] } whereas I need it to do ["category_1": [], "category_2": []]
First, this answer is based on a guess that you are doing something like this on your controller (you didn't posted the controller code):
return reponse()->json($theResponseFromGetFeaturesMethod);
Since inside php the $theResponseFromGetFeaturesMethod variable contains an dictionary array (something like ["key"=>"value]), when you convert it to a JSON, you will notice that this "conversion" happens.
This happens because Javascript arrays (and JSON) doesn't support dictionary arrays. See this sample on javascript console:
> var a = [];
undefined
> a['key'] = "value"
"value"
> a
> key: "value"
length: 0
__proto__: Array(0)
Note that the a still have a length of zero, but it now have an key property.
This happens because almost everything on javascript is actually an object. So the array is a special kind of object that have push, pop and many other array methods. Doing array[] = 'somevalue' is actually a shortcut to array.push('somevalue').
So, the behavior that you are observing is right, the toArray() method work as expected, and the JSON conversion too.
Another weird behavior is when you try to convert this PHP array to an JSON:
[
0 => 'a'
1 => 'b'
9 => 'c'
]
You will note that in this case, PHP will convert this array to an object too. The result in JSON will be:
{
"0": "a",
"1": "b",
"2": "c"
}
This is also the expected behavior, since the JSON syntax doesn't support defining the index for a value.

Related

DB::get is Array in Laravel but it says it is not array

I thought the data which is from DB::get() is Array.
However , the console says it is not array.
$fruitList = Food::where('id' => 300)->get(['id']);
shuffle($fruitList);
ErrorException: shuffle() expects parameter 1 to be array, object given in file
The return value of get() is not an array. it's Laravel array collection you can convert it to an array or use shuffle of array collection:
$fruitList = Food::where('id' => 300)->get(['id'])->toArray();
shuffle($fruitList);
with array collection:
$fruitList = Food::where('id' => 300)->get(['id'])->shuffle();
Just like #A.seddighi mentioned, using get() or all() gives you a collection. It may seem like an array when you output it using return or print but it is different.
Collections can be filtered, queried and so on. e.g
$fruitList->has('price')
etc.
To get an array simply called the toArray() method on it, you may also use flatMap(), mapWithKeys() etc. Make sure you follow the documentation that is suitable for your version of laravel.

select certain columns from eloquent collection after the query has executed

Using Laravel 5.3, I have a model with the following function
public function myData() {
return $this->hasMany(MyData::class);
}
and in my collection I have the following
$my_data = MyModel->myData()->get();
All good so far. If I return $my_data I get an eloquent collection with three items.
What I need now though is to create a duplicate of that collection but containing only three of the fields.
I have tried several different things, each of which return an error. The following is the closest I have got, but this returns an empty array - I assume because the fields are located one level deeper than the collection object.
$new_collection = $my_data->only(['field_1', 'field_2', 'field_3']);
What would be the correct way to create a new collection containing all three items, each with only the three selected fields?
Thanks for your help
You could use map:
$slimmed_down = $collection->map(function ($item, $key) {
return [
'field_1' => $item->field_1,
'field_2' => $item->field_2,
'field_3' => $item->field_3
];
});
This will return a new Collection with just the values you want. As far as I know there isn't any other method that does what you want, so iterating over every item and selecting the fields this way is one of the few solutions.
The advantage of using map instead of a standard foreach loop is that when you use map it returns a new instance of Collection.
Edit:
After some thoughts and research about this, the problem you'll have created is that the all the values in the Collection aren't instances of anything anymore. If you don't mind this effect, an even prettier and faster way would be to do this:
$slimmed_down = $collection->toArray()->only(['field_1', 'field_2', 'field_3']);
This basically has the same result.
Using Laravel 9, I just had the same issue :
$my_data->only(['field_1', 'field_2', 'field_3']);
returning an empty array.
I solved it with :
$my_data->map->only(['field_1', 'field_2', 'field_3']);

Laravel 'where' collection method modifies the collection

Does the Laravel wherecollection method modify the collection?
On the Laravel documentation you can read this:
almost every method returns a new Collection instance, allowing you to preserve the original copy of the collection
And the only methods which have a warning about modifying the collection are transformand forget
But I have this code:
$descriptions = Description::where('description', $request->description);
if ($descriptions->count()) {
$descriptionsWithSameUnit = $descriptions->where('unit_id', $request->unit);
if ($descriptionsWithSameUnit->count()==0) {
$descriptionsWithAnotherUnit = $descriptions->where('unit_id', '!=', $request->unit);
if ($descriptionsWithAnotherUnit->count()) {
...
And the collection IS modified after the first where so $descriptionsWithAnotherUnit is always empty, because on that point the collection only has the records where unit_id == $request->unit. Is this a bug in the framework or in the documentation?
And the question that arises from here: What can I do to keep a copy of the original object without retrieving again from the DB? I tried this:
$descriptions = Description::where('description', $request->description);
if ($descriptions->count()) {
$descriptionsWithSameUnit = $descriptions;
$descriptionsWithSameUnit->where('unit_id', $request->unit);
...
But the $descriptions object is also modified when I apply the where method to the $descriptionsWithSameUnit object
The first thing is that to get collection you need to use get, so if you want to get collection, you should do:
$descriptions = Description::where('description', $request->description)->get();
and not only:
$descriptions = Description::where('description', $request->description);
Second thing is that there's no possibility to use operator using where method on collection, so:
$descriptionsWithAnotherUnit = $descriptions->where('unit_id', '!=', $request->unit);
is completely incorrect. You should use filtermethod here.

Eloquent Collections Where Condition

I want to get alternative products pictures.
dd($alternativeProduct->pictures);
When die and dump i get this result.
I need to get only the picture which is main. If main equals to 1. It is main picture.
When I write
dd($alternativeProduct->pictures->where('main', 1))
I got an empty array.
Here is my relation with Product and Picture relation
public function pictures(){
return $this->hasMany('App\Models\ProductPicture');
}
What can i do ?
The where method in a collection has three parameters: $key, $value and $strict, the last one defaults to true if not passed when calling the method. When $strict is true the comparison is done via === which does not do type coercion, meaning "1" === 1 is false.
From your dump data, I can see that "main" => "1" which means it's a string and you're passing an integer 1 to the where method, resulting in what I described above as a false condition and returning an empty result set. You can fix that by disabling strict comparison:
$alternativeProduct->pictures->where('main', 1, false);
// or the equivalent helper whereLoose
$alternativeProduct->pictures->whereLoose('main', 1);
Or passing a string as the value:
$alternativeProduct->pictures->where('main', "1");
That being said, if that's the only place you're using that collection in that request's context, I suggest that you filter the results at the database level, not after they are fetched, like so:
$alternativeProduct->pictures()->where('main', 1)->get();
Accessing the relations as a method ->pictures(), instead of as a property ->pictures, will return a Query Builder instance that allows you to add conditions to the database query and only fetch the actual items you need from the database, instead of fetching them all and filtering them out afterwards.
You may want to use whereLoose instead:
dd($alternativeProduct->pictures->whereLoose('main', 1))
From the Laravel docs:
The where method uses strict comparisons when checking item values. Use the whereLoose method to filter using "loose" comparisons.

Eloquent Collection: Counting and Detect Empty

This may be a trivial question but I am wondering if Laravel recommends a certain way to check whether an Eloquent collection returned from $result = Model::where(...)->get() is empty, as well as counting the number of elements.
We are currently using !$result to detect empty result, is that sufficient? As for count($result), does it actually cover all cases, including empty result?
When using ->get() you cannot simply use any of the below:
if (empty($result)) { }
if (!$result) { }
if ($result) { }
Because if you dd($result); you'll notice an instance of Illuminate\Support\Collection is always returned, even when there are no results. Essentially what you're checking is $a = new stdClass; if ($a) { ... } which will always return true.
To determine if there are any results you can do any of the following:
if ($result->first()) { }
if (!$result->isEmpty()) { }
if ($result->count()) { }
if (count($result)) { }
You could also use ->first() instead of ->get() on the query builder which will return an instance of the first found model, or null otherwise. This is useful if you need or are expecting only one result from the database.
$result = Model::where(...)->first();
if ($result) { ... }
Notes / References
->first() http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Collection.html#method_first
isEmpty() http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Collection.html#method_isEmpty
->count() http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Collection.html#method_count
count($result) works because the Collection implements Countable and an internal count() method: http://laravel.com/api/4.2/Illuminate/Database/Eloquent/Collection.html#method_count
Bonus Information
The Collection and the Query Builder differences can be a bit confusing to newcomers of Laravel because the method names are often the same between the two. For that reason it can be confusing to know what one you’re working on. The Query Builder essentially builds a query until you call a method where it will execute the query and hit the database (e.g. when you call certain methods like ->all() ->first() ->lists() and others). Those methods also exist on the Collection object, which can get returned from the Query Builder if there are multiple results. If you're not sure what class you're actually working with, try doing var_dump(User::all()) and experimenting to see what classes it's actually returning (with help of get_class(...)). I highly recommend you check out the source code for the Collection class, it's pretty simple. Then check out the Query Builder and see the similarities in function names and find out when it actually hits the database.
Laravel 5.2 Collection Class
Laravel 5.2 Query Builder
I think you are looking for:
$result->isEmpty()
This is different from empty($result), which will not be true because the result will be an empty collection. Your suggestion of count($result) is also a good solution. I cannot find any reference in the docs
I agree the above approved answer. But usually I use $results->isNotEmpty() method as given below.
if($results->isNotEmpty())
{
//do something
}
It's more verbose than if(!results->isEmpty()) because sometimes we forget to add '!' in front which may result in unwanted error.
Note that this method exists from version 5.3 onwards.
There are several methods given in Laravel for checking results count/check empty/not empty:
$result->isNotEmpty(); // True if result is not empty.
$result->isEmpty(); // True if result is empty.
$result->count(); // Return count of records in result.
I think better to used
$result->isEmpty();
The isEmpty method returns true if the collection is empty; otherwise,
false is returned.
According to Laravel Documentation states you can use this way:
$result->isEmpty();
The isEmpty method returns true if the collection is empty; otherwise, false is returned.
I think you try something like
#if(!$result->isEmpty())
// $result is not empty
#else
// $result is empty
#endif
or also use
if (!$result) { }
if ($result) { }
You can do
$result = Model::where(...)->count();
to count the results.
You can also use
if ($result->isEmpty()){}
to check whether or not the result is empty.
so Laravel actually returns a collection when just using Model::all();
you don't want a collection you want an array so you can type set it.
(array)Model::all(); then you can use array_filter to return the results
$models = (array)Model::all()
$models = array_filter($models);
if(empty($models))
{
do something
}
this will also allow you to do things like count().
You can use: $counter = count($datas);
The in_array() checks if a value exists in an array.
public function isAbsolutelyEmpty($value)
{
return in_array($value, ["", "0", null, 0, 0.0], true);
}
You want to check these two cases of count().
#1
If the result contains only a single row (one record) from the database by using ->first().
if(count($result)) {
// record is exist true...
}
#2
If result contain set of multiple row (multiple records) by using ->get() or ->all().
if($result->count()) {
//record is exist true...
}

Resources