4

I used this old code, and start refactor it with use of laravel/scout. This code is a simply search function thet search in posts and pages too, and show result mixed paginate.

Old code:

public function search($term) {
   $post = Post::where('title', 'LIKE', $term);
   $page = Page::where('content', 'LIKE', $term);
   $result = $page->union($post)->orderBy('created_at')->paginate();
   return $result;
}

New Code not working:

public function search($term) {
   $post = Post::search($term);
   $page = Page::search($term);
   $result = $page->union($post)->orderBy('created_at')->paginate(); 
   return $result;
}

get error: Method Laravel\Scout\Builder::union does not exist.

What is the best syntax for this problem?

Bálint Bakos
  • 474
  • 2
  • 5
  • 21
  • 1
    Scout's search functionality is implemented in engines and is specific to how the engines manage data. Scout's `Builder` doesn't use `QueryBuiler` directly due to the same reason. Implementing `union` to work across all engines may not be possible or atleast not trivial to do. If sure to use a particular engine (say database engine) then you may try to implement your custom version with `union` functionality taking currently used engine as starting point to extend it. And then extend the `Scout\Builder` with a `macro` to use the extended functionality. – Donkarnash Jun 01 '22 at 06:11
  • Just a thought - wouldn't it be easier to merge the paginated results for individual models and send merged results to frontend? – Donkarnash Jun 01 '22 at 06:13

1 Answers1

2

Since Scout\Builder doesn't support union. And it would be non trivial to implement union functionality for all possible search engines supported by Scout.

However, Scout\Builder provides a query() function to customise the eloquent results query.

This provides kind of an escape hatch, where by scout can be leveraged on one model (out of the two)

public function search($term)
{
    $postQuery = Post::query()
        ->where('some_column', 'like', $term . "%");
    $results = Page::search($term)
        ->query(
            fn($query) => $query->union($postQuery)
                ->orderBy('created_at', 'desc')
        )
        ->paginate();
}

Laravel Scout - Customizing the Eloquent Results Query

For Algolia Engine

If using Algolia as search engine, there's Algolia Scout Extended which supports search with multiple models.

For other Engines

Another approach to search multiple models with Scout and get paginated results would be:

  • Get search results for individual models using Scout (using Model::search('query')->get();)
  • Concat the resulting collections
  • Paginate the collection

Define a macro for Illuminate\Support\Collection in the boot method of a Service Provider (for eg in App\Providers\AppServiceProvider)

<?php

namespace App\Providers;

use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\LengthAwarePaginator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Collection::macro('paginate', function ($perPage, $total = null, $page = null, $pageName = 'page') {
            $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);

            return new LengthAwarePaginator(
                $total ? $this : $this->forPage($page, $perPage)->values(),
                $total ?: $this->count(),
                $perPage,
                $page,
                [
                    'path'     => LengthAwarePaginator::resolveCurrentPath(),
                    'pageName' => $pageName,
                ]
            );
        });
    }
}

Then in the controller

public function search($term) {
   $post = Post::where('title', 'LIKE', $term)->get();
   $page = Page::where('content', 'LIKE', $term)->get();
   $result = $page->concat($post)->sortBy('created_at')->paginate(5);
   return $result;
}
Donkarnash
  • 12,433
  • 5
  • 26
  • 37