Friday, March 3, 2017

Laravel Eloquent - haversine formula and pagination

I'm using haversine formula to count distance from selected post-code to desired destination.

App\Models\Business.php

public function scopeDistance($query, $latitude, $longitude, $radius)
{
    $query->getQuery()->orders = [];
    return $query->select('*')
                 ->selectRaw("( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) )  * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin(radians(latitude)) ) ) AS distance")
                 ->having('distance', '<=', $radius)
                 ->orderBy('distance');
}

Everything seems pretty ok, except when i use paginate() method instead of get()

I have been looking for solution, there are lots of topic describing this problem, but seems that people didn't come up with good solution for it.

Some solutions are using clone $builder and trying to get item_count separately, but in my case it caused incorrect values (always got number of all, not filtered by formula), maybe i did something wrong - not sure...

However i came up with mine solution and i would like to hear any feedback if it's ok in terms of speed and memory usage.

function custom_paginator($builder, $per_page)
    {
        $path = current_route();
        $curPage = \Illuminate\Pagination\Paginator::resolveCurrentPage();
        $haversine  = isset($builder->getQuery()->columns[1]) ? $builder->getQuery()->columns[1]->getValue() : null;

        $count = $haversine ?
                 \DB::select(\DB::raw(sprintf("SELECT COUNT(*) as count FROM (SELECT %s FROM `businesses` HAVING `distance` <= %d) AS harversine", $haversine, $builder->getQuery()->havings[0]['value'])))[0]->count :
                 $builder->count();

        return new \Illuminate\Pagination\LengthAwarePaginator(
            $builder->forPage($curPage, $per_page)->get()->all(),
            $count, $per_page, null, compact('path')
        );
    }

I haven't got any more ideas how to improve it, at least it works and i dont need to call something like

count = count($results = $builder->get());
$items = $results->forPage($curPage, $perPage);

feedback really appreciated.



via rk.dev

Advertisement