Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

whoisthisstud's avatar

Query Builder custom sorting pre-pagination

I have a livewire component that loads all records and then sorts the collection based on whether a query param is present. This works great as intended, where the "active" record becomes the first record listed, but I haven't been able to get pagination working when I need to sort, pre-pagination.

use App\Models\MyResults;

use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Http\Request;

class ResultSets extends Component
{
    public $userColor;
    public $resultSets;
    public $active;
    public $results;

    protected $listeners = [
        'updatedResults' => '$refresh'
    ];

    protected $queryStrings = [
        'active',
    ];

    public function mount($userColor, Request $request)
    {
        $this->userColor = $userColor;
        $this->active = $request->active;
        $this->updateResultSets(); 
    }

    public function boot()
    {
        $this->updateResultSets();  
    }

    public function updateResultSets()
    {
        $this->resultSets = MyResults::orderBy('created_at','desc')->get();

        if( isset($this->active) ) {
            $selected = $this->active;

            $this->resultSets = $this->resultSets->sort(function ($a, $b) use ($selected) {
                return ($selected != $b->id) ? -1 : 1;
            });
        } 
    }

    public function render()
    {
        return view('livewire.my-result-sets');
    }
}

The above works well, with the exception of pagination.

I have attempted to build my own pagination instance, but the results do not update when changing pages. This is my first attempt at building a custom pagination instance, so I guess I could be missing something, here.

use App\Models\MyResults;

use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Http\Request;

class ResultSets extends Component
{
    use WithPagination;

    public $userColor;
    public $active;
    public $results;


    protected $listeners = [
        'updatedResultSets' => '$refresh'
    ];

    protected $queryStrings = [
        'active',
    ];

    public function mount($userColor, Request $request)
    {
        $this->userColor = $userColor;
        $this->active = $request->active;
    }

    public function componentQuery()
    {
        $results = MyResults::orderBy('created_at','desc')->get();

        if( isset($this->active) ) {
            $selected[] = $this->active;

            $results = $results->sort(function ($a, $b) use ($selected) {
                return ($selected[0] != $b->id) ? -1 : 1;
            });
        }

        $this->results = $results;

        return $results;
    }


    public function buildPaginationFrom($collection)
    {
        $perPage = 10;
        $currentPage = \Illuminate\Pagination\Paginator::resolveCurrentPage() ?? 1;
        $itemsOnPage = $collection->skip(10 * ($currentPage-1))->take($perPage);
        $paginatorPath = \Illuminate\Pagination\Paginator::resolveCurrentPath();

        $instance = new \Illuminate\Pagination\LengthAwarePaginator(
                $itemsOnPage,
                $collection->count(),
                $perPage,
                $currentPage,
                ['path' => $paginatorPath]
         );

         return $instance;
    }

    public function render()
    {
        return view('livewire.my-result-sets', [
             'resultSets' => $this->componentQuery(),
             'paginator' => $this->buildPaginationFrom($this->results),
        ]);
    }
}

What am I missing to get this working and is there a better way?

TIA.

0 likes
12 replies
Sinnbeck's avatar

You should do the ordering on the database. Is the idea that if the selected item is in the rows, it always comes first?

2 likes
whoisthisstud's avatar

@Sinnbeck ...basically, yes.

I have a multi-model search where the results link to either Livewire modals, for quick editing of records if an admin, or a respective model record list page and, in that case, want the selected item to be the first record displayed, along with opening associated panels for working with the data, etc.

Are you suggesting that I use a parameterized MySQL stored function and sort the records within that stored function? I'm not sure I've worked with raw MySQL enough. Could you walk me through it or link me to an associated tut?

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@whoisthisstud no I simply meant that you should do the complete logic in this statement (you would probably need a raw query using CASE)

$this->resultSets = MyResults::orderBy('created_at','desc')->paginate();
1 like
Sinnbeck's avatar

A quick example that might work

$this->resultSets = MyResults::orderByRaw('id = ? DESC', $selected[0])->orderBy('created_at','desc')->paginate();
2 likes
jlrdw's avatar

You should be doing database pagination, you are getting all now, what if all grows to tens of thousands.

2 likes
whoisthisstud's avatar

Thanks to both @sinnbeck and @jlrdw, here is my final that is working well.

use App\Models\MyResults;

use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Http\Request;

class MyResultSets extends Component
{
    use WithPagination;

    public $userColor;
    public $active;

    protected $listeners = [
        'updatedResultSets' => '$refresh'
    ];

    protected $queryStrings = [
        'active',
    ];

    public function mount($userColor, Request $request)
    {
        $this->userColor = $userColor;
        $this->active = $request->active;
    }

    public function componentQuery()
    {
        $query = MyResults::query();

        if( isset($this->active) ) {
            $query = $query->orderByRaw('id = ? DESC', $this->active);
        }

        $query = $query->orderBy('created_at','desc');

        return $query->paginate(10);
    }

    public function render()
    {
        return view('livewire.my-result-sets', [
            'resultSets' => $this->componentQuery(),
        ]);
    }
}

Sinnbeck's avatar

@whoisthisstud a trick to clean it up. Also, don't use user input in raw queries. It makes sql injection way to easy

return MyResults::query()->when(isset($this->active), function ($query) {
            $query->orderByRaw("id =? desc", [$this->active] );
        })->orderBy('created_at','desc')
        ->paginate(10); 
1 like
whoisthisstud's avatar

@Sinnbeck I think you and I were viewing this thread at the same time. I had actually found orderByRaw and implemented it before I saw your earlier code suggestion. I noticed that raw input issue the moment I saw it, and updated my code and my reply. Are you saying sql injection is still possible, even with this code change?

whoisthisstud's avatar

@Sinnbeck so, I tried this code. For some reason, the moment that I did, Livewire's protected $queryString no longer worked. Reverting back to the old code didn't fix it, and I haven't made any other updates to this component. I ended up just appending the active param.

But it also had another side effect. Using this obviously shorter code caused an entire page reload when returning to the initial "page 1". The original code does not, and just refreshes the component.

Original completed code

$query = MyResults::query();

if( isset($this->active) ) {
    $query = $query->orderByRaw('id = ? DESC', $this->active);
}

$query = $query->orderBy('created_at','desc');

return $query->paginate(10)->appends(['active' => $this->active]);

Your code with added appends

return MyResults::query()->when(isset($this->active), function ($query) {
                    $query->orderByRaw("id = ? desc", [$this->active] );
                })->orderBy('created_at','desc')
                ->paginate(10)->appends(['active' => $this->active]); 

Please or to participate in this conversation.