I'm trying to get a list of users that are near the authenticated user and also return the distance between them using Laravel's Eloquent. I got it to work using raw SQL, but now I'm almost there making it work properly using mostly eloquent.
So, on the User model I have this method:
public function getUsersAround($coordinates, $radius = 5)
{
return $this->whereHas('location', function ($query) use ($coordinates, $radius) {
$query->distance($coordinates, $radius);
})->paginate(10);
}
Then on the Location model, I have the following scope:
public function scopeDistance($query, $coordinates, $radius)
{
return $query
->join('profiles', 'profiles.id', '=', 'locations.profile_id')
->having('distance', '<', $radius)
->selectRaw("profile_id,
(6371 * ACOS(COS(RADIANS($coordinates->latitude))
* COS(RADIANS(latitude))
* COS(RADIANS(longitude) - RADIANS($coordinates->coordinates))
+ SIN(RADIANS($coordinates->latitude))
* SIN(RADIANS(latitude)))) AS distance")
->orderBy('distance', 'asc');
}
So, all the users in the radius are being returned correctly, but I also want to add the calculated distance to each returned user.
What's strange is that if I dd($query->distance($coordinates, $radius));
I see all the users + a new distance property on each user object (with the correct value), but if I just return the query and dd(auth()->user()->getUsersAround($coordinates, $radius))
inside the controller, the distance property is not there anymore.
Is there a way to return the calculated distance inside the scope query and persist it on the Users object when returning the list of users?
Thanks!
If you want to include the distance in the results, you need the load the location
relation with the distance
attribute. Since you are already selecting the distance
in the query scope on the Location model, you can achieve this quite easily:
public function getUsersAround($coordinates, $radius = 5)
{
return $this->whereHas('location', function ($query) use ($coordinates, $radius) {
$query->distance($coordinates, $radius);
})->with([
'location' => function ($query) use ($coordinates, $radius) {
$query->select('locations.*')->distance($coordinates, $radius);
}
])->paginate(10);
}
Notice the with('location')
, which eager loads the Location model. I've added a select('locations.*')
so that the location
relation will have its own attributes, and the distance($coordinates, $radius)
will also select the profile_id
and the distance
attributes.
So when you are calling auth()->user()->getUsersAround($coordinates, $radius)
inside your controller, you can access the distance
attribute with $user->location->distance
:
$users = auth()->user()->getUsersAround($coordinates, $radius);
foreach ($users as $user) {
$distance = $user->location->distance;
}
That's because you are using get() inside a scope which is not correct. local scopes are intended to define common sets of constraints (like for filtering data). check the documentation on local scopes for more detail.
To add something to a select result, you can use addSelect.