Get property of related/nested object - laravel

I want to get a property of a related/nested object. A question object has many choices, and I want to return a certain choice with a correct field equal to True/1.
So I get the question by ID:
$question = Question::find($request->get('id'));
Then get the related choice which has correct == 1:
$answer = $question->choice->where('correct', 1);
return $answer;
The response is an object within an array:
[
2
{
"id": 3,
"choice": "Non eos architecto ut.",
"question_id": 1,
"correct": 1,
}
]
I wanted to access the choice field:
return $answer->choice;
But get this error:
Undefined property: Illuminate\Database\Eloquent\Collection::$choice
I tried using flatten:
$answer = $question->choice->where('correct', 1)->flatten();
But all this does is remove the question ID 2, but doesn't allow me access to the object properties.

Use the other answer if you want to retrieve just that one value from the database.
But it looks like you already retrieved all the answers as a Collection and want to filter that collection.
The where method returns another Collection. So to get just the one element, use first:
$answer = $question->choice->where('correct', 1)->first();
This gives you the actual Eloquent object
And then you can access any properties as usual.
return $answer->choice;

Use the value method to get a single value from the database:
$choice = $question->choice()->where('correct', 1)->value('choice');
BTW, you should rename your choice method to choices, since it's a hasMany relationship.

Related

Eloquent Collection query - 'WHERE' clause with $value parameter without invoked accessor

I have used some accessors for Model attributes.
And as my knowledge about Accessor in Laravel, it can use to create a virtual attribute based on another attribute value or modify the return value of the existing attribute.
So I create an Accessor with the public method getStatusAttribute to convert the status from integer to string value. For example, my Model has an attribute named is 'status'. In DB, this column is stored with integers like 0,1,2,...
In this method, the return value is a string based on the current status attribute value.
public function getStatusAttribute(int $value): string
{
$statuses = [
1 => 'New',
2 => 'Pending',
3 => 'Canceled'
];
return $statuses[$value];
}
Everything work fined when I retrieve attribute value from the model object entity.
But when I want to retrieve an Eloquent Collection with a specific 'status' attribute value, the $value parameter in the 'where' method expects a string value that is returned from the accessor method, not is an integer value which stored in a database. So the $value parameter would be 'New' or 'Pending' or 'Canceled' instead of 1,2,3.
I want to retrieve a collection that contains an Eloquent Model Object so that I won't use an Illuminate\Support\Facades\DB.
I know I still can retrieve the exact collection with the $value parameter as a string, but I was just wondering if we have another way to skip the accessor in the query of Eloquent Collection?
Please see an example below for more detail about what I expect.
For example, some records in the database look like the below, and the model was named 'DemoModel' :
id
status
1
1
2
3
3
1
4
3
5
2
public function getCanceledEntities($entityId)
{
$canceledEntities = DemoModel::all()->where('status', '=', 3);
if($canceledEntities->isNotEmpty()) {
return $canceledEntities;
}
}
Without an accessor, this query will return a collection containing two entities with id [ 2, 4 ].
But with an accessor (as defined in the model), it can't find a matched entity because the WHERE clause expects that the 3rd parameter is 'Canceled' instead of 3.
My alternative method is to use a string 'Canceled' in the 'where' clause. However, I still want to use the value stored in a database for the query, and I think maybe the framework has another way to write an Eloquent Collection query without invoking an accessor.
Any suggestions are always welcome.

How to properly send a sorted model from my controller to the Laravel API Resource collection?

This is my controller:
public function branchesNearby($lat, $lng)
{
$branches = Branch::all();
//Calculate distance between each branch and client
//Radius of earth is 6371 KM so multiply the result with radius of earth 6371*pi/180
foreach ($branches as $branch){
$branch['distance'] = sqrt(pow(($branch->lat - $lat), 2) + pow(($branch->lng - $lng), 2)) * 108;
}
//Sort by nearest first
$sortedBranches = $branches->sortBy('distance');
return BranchResource::collection($sortedBranches);
}
You can see i created an extra attribute to calculate distance between user location and the branch location. Then, I sort the branch model by distance. However, The api response that i get is:
API response
You can see it is an object. I don't need the keys "2", "0" and "1". I need to remove this extra wrapping and i need it to be an array of objects like this:
Correct API but without sorting
Surely, It is sorting which causes it to become an object? I tried many other ways, one of them being:
$sortedBranches = $collection->sortBy('distance');
$final = $sortedBranches->values()->toJson();
and sending this $final to the resource collection. That gives me the error: " call to a member function first() on string in file api resources". This must be something small but i really need help.
Updating:
I did not post my Resource before, this is how it is:
public function toArray($request)
{
return [
'id' => $this->id,
'shop' => $this->shop->name,
'shop_image' => asset('api/images/' . $this->shop->image_file),
'lat' => $this->lat,
'lng' => $this->lng,
'shop_logo' => asset('api/images/' . $this->shop->logo_file),
'distance' => $this->distance . " KM"
];
The error that i get if i use:
$sortedBranches = $branches->sortBy('distance')->values()->all();
return BranchResource::collection($sortedBranches);
is:
The error
Update 3:
If i don't call the resource collection and simply output the $sortedBranches like this:
return response()->json($sortedBranches, 200);
Here, the format of the api response is correct but the data is not correct. This is how it looks:
$sortedBranches
Is there a way i can manipulate $sortedBranches and show the output like with the BranchResource?
Based on our troubleshoot, the resulting solution should be:
return BranchResource::collection($sortedBranches)->values()->all();
When you pass a collection into the collect method of the BranchResource class, it re-creates the collection. Laravel sees you are returning an (collection)object directly from your controller, and intervenes by casting it to json( i believe this is default). If the resulting json is not converted the way you want, you need to modify this collection. Therfor we needed to modify the BranchResource::collection() collection, not the $sortedBranches collection.
Edit:
Modified collect method into collection;

Laravel | groupBy returning as object

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.

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