Skip to content
Advertisement

Laravel use append attirbute inside a query in controller or scope

So I have done the following: I have a table called group that has longitude and latitude which is essentially a location of the group. Then I have an append which calculates the distance from the user.

Group Model:

class Group extends Authenticatable
{

  protected $fillable = [
     'lng',
     'lat'
  ];

  protected $appends = [
     'distance_from_user'
  ];

  public function getDistanceFromUserAttribute(query) {
     
        $ip = Request::ip();
        $coordinates = Location::get($ip);
        $radius = 4000;

        $latFrom = deg2rad($coordinates->latitude); // current user's lat
        $lonFrom = deg2rad($coordinates->longitude); // current user's long
        $latTo = deg2rad($this->lat); // this is coming from the database
        $lonTo = deg2rad($this->lng); // this is coming from the database
      
        $latDelta = $latTo - $latFrom;
        $lonDelta = $lonTo - $lonFrom;
      
        $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) + cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));

        return $angle * 6371;
  }
}

What I want to do now is USE that distance from user in a where clause like below in a scope, which sits in the same model as above.

Scope Inside Group Model:

public function scopeGetAllGroups($query)
{
    return Groups::get()->where('distance_from_user', '<', 25);
}

But the distance_from_user is not available at this point when I use the scope. How can i manage this so that I can use the calculated distance in a query.

Thank you in advance!

Advertisement

Answer

As the comments stated, you cannot use the Query Builder to filter by an accessor. You can however, use the filter() method on a Collection.

// This won't work
Group::where('distance_from_user', '<', 25)->get();
// This should work
$groups = Group::get()->filter(function ($item) {
    return $item->distance_from_user < 25;
});
// Or using PHP 7.4's shorthand Closure
$groups = Group::get()->filter(fn ($item) => $item->distance_from_user < 25);

The problem is the fact you are querying all the records from the database and building a huge Collection of Models. That can really slow down your application. There is a solution for this problem: LazyCollections. Instead of using ->get(), use ->cursor() to return a LazyCollection instance.

$groups = Group::cursor()->filter(function ($item) {
    return $item->distance_from_user < 25;
});
// Or using PHP 7.4's shorthand Closure
$groups = Group::cursor()->filter(fn ($item) => $item->distance_from_user < 25);

This might feel wrong but it uses little to no memory. A cave-at: It doesn’t work well with eager loading relationships. Normal joins are fine though.

At this point, $groups can be iterated over but it’s not a Collection. If you want to turn it into a “normal” collection, just append ->collect() at the end. You can also do some database filtering before using ->cursor()

$example = Group::where(...)->select(...)->join(...)->cursor()->filter(....)->collect();

More on the subject:

https://josephsilber.com/posts/2020/07/29/lazy-collections-in-laravel https://laravel.com/docs/8.x/eloquent#using-cursors

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement