Laravel: Get column value from first() result of relationship query - php

I'm trying to get a single column value from the first result of a Model's belongsToMany relationship query, as i'm returning the ->first() result of the relationship I was hoping $code->reward->title would work but it doesn't.
I get an Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation error
What I'm trying to do is the get the title of the current reward that is linked to a specific code - the code_reward pivot table has a valid_from and expires_at date as the reward linked to a code will change as time goes by, hence the need to get the currently active reward for that code.
Here's my code:
Model: Code
public function rewards()
{
return $this->belongsToMany('App\Reward')->withPivot('valid_from', 'expires_at')->withTimestamps();
}
public function reward()
{
$now = Carbon::now();
return $this->rewards()
->wherePivot('valid_from', '<', $now)
->wherePivot('expires_at', '>', $now)
->first();
}
Controller: CodeController
public function index()
{
$codes = Code::all();
return view('codes/index')->with('codes', $codes);
}
View: Codes/index
#foreach ($codes as $code)
{{$code->id}}
{{$code->reward->title}}
#endforeach
Any help is really appreciated!
Update
Unfortunately both suggestions below ($code->reward()->title and getRewardAttribute() return an Trying to get property of non-object error.
If I remove ->first() from the Code->reward() method and replace $code->reward->title with $code->reward->first() in the view it echoes out the whole reward model as json, however $code->reward->first()->title still returns the Trying to get property of non-object error
Update 2
If I do {{dd($code->reward->title)}} in the view I get the reward title but if I just do {{$code->reward->title}}, I don't!
AND the $code->reward->title works as expected in a #Show view, so could it be that the collection of codes supplied by the controller's #index method isn't passing the necessary data or not passing it in a necessary format??
SOLVED
The issue was caused by one of the $code->rewards in the foreach loop in the index view returning null! The first one didn't, hence the dd() working but as soon as the loop hit a null it crashed.
Once I wiped and refreshed the db (and made sure my seeds where adding only valid data!) it worked. Doing {{$code->reward ? $code->reward->title : ''}} fixed the issue. Grrr.

Your statement is failing because $code->reward->title tells Laravel that you have defined a relationship on your Code model in a method called reward(). However, your relationship is actually defined in the method rewards(). Instead, reward() is a custom method on the model that you have made up. Calling it as a method and not a relation is the quickest way to get what you want.
{{$code->reward()->title}}
As #andrewtweber points out below, you could also make your custom reward() method into an attribute accessor. To do that, just rename the function to getRewardAttribute() and then you can call it in your view like you originally did.
Alternatively, you could get rid of that reward() method entirely and move all of that logic to the controller, where it probably makes more sense. You'd have to use constrained eager loading to pull that off. So in your controller you'd have something like this:
$codes = App\Code::with(['rewards' => function ($query) {
$query->wherePivot('valid_from', '<', $now)
->wherePivot('expires_at', '>', $now);
])->get();
Of course, this would return all of your filtered codes. This is because you cannot apply a sql limit inside a nested eager relationship as outlined here. So in your view, you would then have to do something like this:
{{$code->rewards->first()->title}}
However, it will be simpler to go with my first solution, so that's entirely up to you.

Try to set this method in Code Model, because query builder treats valid_from and expired_at as string, not date?
public function getDates()
{
return ['valid_from','expired_at'];
}

Related

laravel #if or #isset show nothing or gives an exeption

I have made in laravel v.8. a database query (eloquent) that returns several columns.
$wgName = Auth::user()
->join('wg_groups', 'users.wg_group_id', '=', 'wg_groups.id')
->get();
Now I want in my html view that the wg_name is displayed if it is set.
I have tried the following four things:
#if(isset($wgName->wg_name))
<h1>{{$wgName->wg_name}}</h1>
#endif
Here simply nothing is displayed
#if(isset($wgName))
<h1>{{$wgName->wg_name}}</h1>
#endif
Exception: Property [wg_name] does not exist on this collection instance.
#isset($wgName)
<h1>{{$wgName->wg_name}}</h1>
#endisset
Exception: Property [wg_name] does not exist on this collection instance.
#isset($wgName->wg_name)
<h1>{{ $wgName->wg_name }}</h1>
#endisset
Here simply nothing is displayed
I have no idea why it doesn't work and I didn't find anything in the documentation.
https://laravel.com/docs/8.x/eloquent
Calling ->get() on Query builder will give you a Collection instance containing multiple users. And there is definitely no wg_name on Collection instance so the result is always false.
Try using first():
$wgName = Auth::user()
->join('wg_groups', 'users.wg_group_id', '=', 'wg_groups.id')
->first();
Currently you are selecting all users from the database. This happens due to Eloquent redirecting non existing methods to a new query builder instance.
By changing the query to use the first() method instead of the get() method you'll receive only one record, but this would probably not be the correct record as it does not take the current user into account.
There are multiple ways to solve this issue
1: Just use a simple query to receive the wg_name.
$wgName = DB::table('wg_groups')->find(Auth::user()->wg_group_id);
2: Add a global scope to your User model to always join the wg_group data when looking up a user.
You could add the following method to your User model to always join this table when querying the users table.
protected static function booted()
{
self::addGlobalScope('include_wg_group', function (\Illuminate\Database\Eloquent\Builder $query) {
$query->join('wg_groups', 'users.wg_group_id', '=', 'wg_groups.id');
});
}
Now everytime you receive an instance of a User it will have all the info of wg_groups joined into it. This allows you to just grab the name like this:
$wgName = Auth::user()->wg_name;

Laravel 7 not finding with scopes

I have a model called Shift on my application, and I've defined my relationships and scopes on it like this:
Relationship:
public function status()
{
return $this->belongsTo(ShiftStatus::class);
}
With scope:
public function scopeWithStatus($query)
{
$query->with('status');
}
Now, when I retrieve shifts and try to call these scopes, I do the following:
$shifts = Shift::all()
->withStatus()
->withProfession()
...
->get();
return response([
'message' => 'Shifts retrieved.',
'shifts' => $shifts
]);
However, I get this error...
"message": "Method Illuminate\\Database\\Eloquent\\Collection::withStatus does not exist.",
I'm not sure why this is happening? It should pick up the scope shouldn't it?
It is not possible to use a query scope in a Collection, since query scope is a concept used in Eloquent to add constraints to a database query while Collections are just a collection of things (data, objects, etc).
So, remove all(). Also i think you are missing return statement in scope (not a part of this question, but you need to update the code )
when you call Shift::all() you get all the shifts table record from db as a collection , then you load the relation on that collection which makes that error.
you should not be loading the result from db unless your query is ready, you should tell the query builder to load the relation then call the result:
$shifts = Shift::withStatus()
->withProfession()
...
->get();

Laravel Where clause, paginate and sortByDesc

I am working a project and I would want to use a where clause, paginate and then sort in the collection in specific order. I have tried the result below but keeps throwing the errors below Method:
Illuminate\Database\Eloquent\Collection::links does not exist. (View:
/Applications/XAMPP/xamppfiles/htdocs/vermex/resources/views/equipments.blade.php)
The Product model is where I am getting the data and store in a variable called $equipment. If there is a better way of doing this, please help.
public function equipments()
{
$equipments = Product::where('product_category_id', 3)->paginate(2)-
>sortByDesc('id');
return view('equipments', compact('equipments'));
}
Try putting the orderBy before the paginate
$equipments = Product::where('product_category_id', 3)->orderBy('id', 'desc')->paginate(2);
sortByDesc is a collection method.
paginate will need to be last for links to be available in the blade view.

How to filter laravel collection

I am trying to make a filter in laravel. This following filter works
$posts= Post::where('category',$request->category)->orderBy('id','desc')->paginate(10);
But when I try to do something like this
public function index(Request $request)
{
$posts= Post::where('category',$request->category)->get();
$posts->latest()->paginate(10);
dd($posts);
It doesn't work. Can someone explain why is this and provide me the code that works. My project have multiple filter.
Error
Because $posts = Post::all(); already execute a query.
Post::where('category',$request->category)->latest()->paginate(10)->get();
would be what you want.
A note:latest requires the created_at column
You should go
$posts = Post::where('category',$request->category)->latest()->paginate(10);
the get request is unnecessary as the paginate will execute the query.
The first one makes the query by pagination i.e fetch 10 records per constructed page
For the second one, based on observation, you most likely have encountered at least 2 errors:
The first, on the line that used the get method because that method requires at least one parameter.
Type error: Too few arguments to function Illuminate\Support\Collection::get()
The other since its a collection, and since there is nothing like paginate or latest method on collection therefore throws other errors. You should check Collection's Available methods to have a glimpse of the methods allowed on collection.
One of the best solutions is to simply order the result when making the query:
Blog::where('category',$request->category)
->orderBy('created_at', 'desc') //you may use also 'updated_at' also depends on your need
->paginate(10);
This way you have the latest coming first int the pagination and also having not worrying about paginating a collection

GroupBy and OrderBy Laravel Eloquent

Building a chat application with a dashboard and am trying to get a notification of the last message the that other user sent.
Here is my Model relationships:
public function messages() {
return $this->hasMany('App\Message', 'author_id');
}
public function lastMessage() {
return $this->hasMany('App\Message', 'recipient_id')->orderBy('created_at', 'DESC')->groupBy('author_id');
}
On thing I cant figure out is instead of returning the last message as it should be sorted by using orderBY, it returns the first record of that group that exists in the database.
Looked around online but cant seem to find any info on this. The only thing I found is a post by someone who said that orderBy and groupBy in laravel don't play well together.
Any help is appreciated!
Instead of redefining the relationship in lastMessage, you might try calling messages from it and then running your query from that.
Without seeing more of your model schema (ie: where are these relationships defined??), this might not be perfect, but it's a start:
public function lastMessage()
{
return $this->messages() // <-- Notice the ()...this creates a query instead of immediately returning the relationship
->where('recipient_id', $this->id) // Not sure if this is correct, might need to adjust according to how you have defined the tables
->orderBy('created_at', 'desc')
->first();
}
It will query the messages relationship with the chained constraints that are listed. And by returning first() it returns only one record as opposed to a collection.

Categories