InfluxOW/laravel_ddd_ecommerce

View on GitHub
app/Domains/Common/Traits/Models/Searchable.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace App\Domains\Common\Traits\Models;

use Closure;
use Elastic\ScoutDriverPlus\Builders\BoolQueryBuilder;
use Elastic\ScoutDriverPlus\Searchable as BaseSearchable;
use Elastic\ScoutDriverPlus\Support\Query;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

trait Searchable
{
    use BaseSearchable {
        search as baseSearch;
    }

    protected static int $maxSearchResultsCount = 100;

    public static function search(string $query = '', ?Closure $callback = null): Collection
    {
        /** @var string $query */
        $query = preg_replace('/[^a-zA-Z0-9\-\s]+/', '', $query);

        if (config('scout.driver') === 'elastic') {
            return static::elasticSearch($query);
        }

        return static::baseSearch($query)->take(static::$maxSearchResultsCount)->get();
    }

    public function scopeSearch(Builder $query, string $searchable, bool $orderByScore): void
    {
        /** @phpstan-ignore-next-line */
        $ids = static::search($searchable)->pluck('id');
        $table = $this->getTable();

        $query->whereIntegerInRaw("{$table}.id", $ids);

        if ($orderByScore && count($ids) > 1) {
            // TODO: No comments
            $orderQuery = 'CASE' . PHP_EOL;
            foreach ($ids as $i => $id) {
                $orderQuery = "{$orderQuery} WHEN {$table}.id={$id} THEN {$i}" . PHP_EOL;
            }
            $orderQuery = "{$orderQuery} END";

            $query->orderByRaw($orderQuery);
        }
    }

    protected static function elasticSearch(string $searchable): Collection
    {
        $query = Query::bool();

        self::shouldFuzzyMatch($query, $searchable);
        self::shouldWildcardMatch($query, $searchable);
        self::shouldExactMatch($query, $searchable);

        $query->minimumShouldMatch(1);

        return static::searchQuery($query)->size(static::$maxSearchResultsCount)->execute()->models();
    }

    private static function shouldFuzzyMatch(BoolQueryBuilder $query, string $searchable): void
    {
        $query->should(
            Query::multiMatch()
                ->query($searchable)
                ->fields(['*'])
                ->fuzziness((string) static::getFuzziness($searchable))
                ->operator('AND')
                ->boost(2.00)
        );
    }

    private static function shouldWildcardMatch(BoolQueryBuilder $query, string $searchable): void
    {
        $query->should([
            'query_string' => [
                'query' => "*{$searchable}*",
                'boost' => 5.0,
                'default_operator' => 'AND',
                'analyze_wildcard' => true,
                'allow_leading_wildcard' => true,
            ],
        ]);
    }

    private static function shouldExactMatch(BoolQueryBuilder $query, string $searchable): void
    {
        $query->should([
            'query_string' => [
                'query' => $searchable,
                'boost' => 10.0,
                'default_operator' => 'AND',
                'analyze_wildcard' => false,
                'allow_leading_wildcard' => false,
            ],
        ]);
    }

    protected static function getFuzziness(string $searchable): int
    {
        $strlen = strlen($searchable);

        return match (true) {
            $strlen > 7 => 2,
            $strlen > 4 => 1,
            default => 0,
        };
    }
}