Laravel Scout 和 TNTSearch 搜索 Trashed

Laravel Scout and TNTSearch search withTrashed

我已经配置了 Laravel Scout,可以在我的模型上使用 ::search()。 相同的模型也使用 SoftDeletes. 如何将 ::search()withTrashed() 结合使用?

下面的代码不起作用。

MyModel::search($request->input('search'))->withTrashed()->paginate(10);

以下确实有效,但不包括已删除的项目。

MyModel::search($request->input('search'))->paginate(10);

更新 1 我在 scout/ModelObserver 中发现已删除的项目无法搜索。这是一个无赖;我希望我的用户能够搜索他们的垃圾。

更新 2 我尝试按照@camelCase 的建议使用 ::withoutSyncingToSearch,,我对此寄予厚望,但这也没有用。

$model = MyModel::withTrashed()->where('slug', $slug)->firstOrFail();

if ($model->deleted_at) {
    $model->forceDelete();
} else {
    MyModel::withoutSyncingToSearch(function () use ($model) {
        $model->delete();
    });
}

这导致在搜索已删除项目时出现未定义的偏移量。顺便说一下,我正在为 Laravel Scout 使用 TNTSearch 驱动程序。我不知道这是 TNTSearch 还是 Laravel Scout 的错误。

我已经为您的问题制定了解决方案。我将为 Scout 提交一个 pull request,希望它能与官方包合并。

这种方法允许您在搜索中包括 软删除模型:

App\User::search('search query string')->withTrashed()->get();

要在您的搜索中仅显示软删除模型:

App\User::search('search query string')->onlyTrashed()->get();

您需要修改3个文件:

Builder.php

laravel\scout\src\Builder.php中添加以下内容:

/**
 * Whether the search should include soft deleted models.
 *
 * @var boolean
 */
public $withTrashed = false;

/**
 * Whether the search should only include soft deleted models.
 *
 * @var boolean
 */
public $onlyTrashed = false;

/**
 * Specify the search should include soft deletes
 * 
 * @return $this
 */
public function withTrashed()
{
    $this->withTrashed = true;

    return $this;
}

/**
 * Specify the search should only include soft deletes
 *
 * @return $this
 */
public function onlyTrashed()
{
    $this->onlyTrashed = true;

    return $this;
}

/**
 * Paginate the given query into a simple paginator.
 *
 * @param  int  $perPage
 * @param  string  $pageName
 * @param  int|null  $page
 * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
 */
public function paginate($perPage = null, $pageName = 'page', $page = null)
{
    $engine = $this->engine();

    $page = $page ?: Paginator::resolveCurrentPage($pageName);

    $perPage = $perPage ?: $this->model->getPerPage();

    $results = Collection::make($engine->map(
        $rawResults = $engine->paginate($this, $perPage, $page), $this->model, $this->withTrashed, $this->onlyTrashed
    )); // $this->withTrashed, $this->onlyTrashed is new

    $paginator = (new LengthAwarePaginator($results, $engine->getTotalCount($rawResults), $perPage, $page, [
        'path' => Paginator::resolveCurrentPath(),
        'pageName' => $pageName,
    ]));

    return $paginator->appends('query', $this->query);
}

Engine.php

laravel\scout\src\Engines\Engine.php中修改如下:

/**
 * Map the given results to instances of the given model.
 *
 * @param  mixed  $results
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  boolean  $withTrashed // New
 * @return \Illuminate\Database\Eloquent\Collection
 */
abstract public function map($results, $model, $withTrashed, $onlyTrashed); // $withTrashed, $onlyTrashed is new

/**
 * Get the results of the given query mapped onto models.
 *
 * @param  \Laravel\Scout\Builder  $builder
 * @return \Illuminate\Database\Eloquent\Collection
 */
public function get(Builder $builder)
{
    return Collection::make($this->map(
        $this->search($builder), $builder->model, $builder->withTrashed, $builder->onlyTrashed // $builder->withTrashed, $builder->onlyTrashed is new
    ));
}

最后,您只需要修改您的相关搜索引擎即可。我正在使用 Algolia,但 map 方法似乎与 TNTSearch.

相同

阿尔戈利亚Engine.php

laravel\scout\src\Engines\AlgoliaEngine.php中修改map方法来匹配我们上面修改的abstractclass:

/**
 * Map the given results to instances of the given model.
 *
 * @param  mixed  $results
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  boolean  $withTrashed // New
 * @return \Illuminate\Database\Eloquent\Collection
 */
public function map($results, $model, $withTrashed, $onlyTrashed) // $withTrashed, $onlyTrashed is new
{
    if (count($results['hits']) === 0) {
        return Collection::make();
    }

    $keys = collect($results['hits'])
                    ->pluck('objectID')->values()->all();

    $modelQuery = $model->whereIn(
        $model->getQualifiedKeyName(), $keys
    );

    if ($withTrashed) $modelQuery->withTrashed(); // This is where the query will include deleted items, if specified
    if ($onlyTrashed) $modelQuery->onlyTrashed(); // This is where the query will only include deleted items, if specified

    $models = $modelQuery->get()->keyBy($model->getKeyName());

    return Collection::make($results['hits'])->map(function ($hit) use ($model, $models) {
        $key = $hit['objectID'];

        if (isset($models[$key])) {
            return $models[$key];
        }
    })->filter();
}

TNTSearchEngine.php

/**
 * Map the given results to instances of the given model.
 *
 * @param mixed                               $results
 * @param \Illuminate\Database\Eloquent\Model $model
 *
 * @return Collection
 */
public function map($results, $model, $withTrashed, $onlyTrashed)
{
    if (count($results['ids']) === 0) {
        return Collection::make();
    }

    $keys = collect($results['ids'])->values()->all();

    $model_query = $model->whereIn(
        $model->getQualifiedKeyName(), $keys
    );

    if ($withTrashed) $model_query->withTrashed();
    if ($onlyTrashed) $model_query->onlyTrashed();

    $models = $model_query->get()->keyBy($model->getKeyName());

    return collect($results['ids'])->map(function ($hit) use ($models) {
        if (isset($models[$hit])) {
            return $models[$hit];
        }
    })->filter();
}

告诉我它是如何工作的。

注意:此方法仍然需要您在删除模型时使用withoutSyncingToSearch方法手动暂停同步;否则搜索条件将更新为 unsearchable().

这是我的解决方案。

// will return matched ids of my model instance
$searchResultIds = MyModel::search($request->input('search'))->raw()['ids'];

MyModel::whereId('id', $searchResultIds)->onlyTrashed()->get();