First, let's see how to do this with the basic query builder. Then we discuss how to fulfill this query using Eloquent models:
function paginateDishesFromPoint(Point $point, $pageSize) { $distanceField = "ST_Distance_Sphere(locations.coordinates, " . "ST_GeomFromText('{$point->toWKT()}') AS distance"; return DB::table('dishes') ->select('dishes.*', DB::raw($distanceField)) ->join('dish_locations', 'dish_locations.dish_id', '=', 'dishes.id') ->join('locations', 'locations.id', '=', 'dish_locations.location_id') ->orderBy('distance') ->paginate($pageSize); }
The ST_Distance_Sphere() function calculates the distance by which we can sort the results. The Laravel paginate() method performs automatic pagination using the page parameter passed through the request URL. Read the pagination documents for more information. Using the function above, we can get a broken set of results as follows:
$point = new Point($latitude, $longitude); $sortedDishes = paginateDishesFromPoint($point, 15);
... where Point is the Grimzy\LaravelMysqlSpatial\Types\Point class of the package we use , and 15 is the number of results per page.
Now try to do this with Eloquent models. We will use the local query area to encapsulate the logic needed to create the part of the query that performs the ordering:
class Dish extends Model { ... public function locations() { return $this->belongsToMany(App\Location::class); } public function scopeOrderByDistanceFrom($query, Point $point) { $relation = $this->locations(); $locationsTable = $relation->getRelated()->getTable(); $distanceField = "ST_Distance_Sphere($locationsTable.coordinates, " . "ST_GeomFromText('{$point->toWKT()}') AS distance"; return $query ->select($this->getTable() . '.*', DB::raw($distanceField)) ->join( $relation->getTable(), $relation->getQualifiedForeignKeyName(), '=', $relation->getQualifiedParentKeyName() ) ->join( $locationsTable, $relation->getRelated()->getQualifiedKeyName(), '=', $relation->getQualifiedRelatedKeyName() ) ->orderBy('distance'); } }
This implementation uses metadata on models to add table and field names to the query, so we do not need to update this method if they change. Now we can get the ordered set using the model:
$point = new Point($latitude, $longitude); $sortedDishes = Dish::orderByDistanceFrom($point)->paginate($pageSize);
$sortedDishes is an instance of the Laravel LengthAwarePaginator that wraps the Collection models. If we pass the results in a view, here's how to display them in a Blade template:
<ul> @foreach($sortedDishes as $dish) <li>{{ $dish->name }} is {{ $dish->distance }} meters away.</li> @endforeach </ul> <a href="{{ $sortedDishes->nextPageUrl() }}">Load more...</a>
As shown above, paginator provides convenience methods that we can use to easily navigate between calculated results.
Alternatively, we could use AJAX queries to load the results. Just remember to pass the current page + 1 to the page parameter of the request data.