← Заметки и статьи о Laravel PHP

3 августа 2015 перевод

Laravel Searchable — лучший пакет для Eloquent

Это первый пост из серии о пакете Eloquence, который позволяет работать с моделями Eloquent в более простой манере. В этой статье мы рассмотрим функциональность, которую пакет добавляет моделям для поиска данных.

Представьте приложение с простой системой друзей:

// Модель User
 
// связь многие-ко-многим
public function friends()
{
   return $this->belongsToMany(static::class, 'friends', 'user_id', 'friend_id');
}
 
// связь один-к-одному
public function profile()
{
   return $this->hasOne(Profile::class);
}

Простая m-m связь между User и его друзьями через pivot-таблицу, для презентации этого достаточно. В таблице пользователей мы храним только E-mail и логин, в таблице профиля — остальные данные пользователей, такие как first_name, last_name и т.д.

Конечно же, нужно ещё поле пароля, метки времени и другие поля, но мы их сознательно опустим для простоты.

Давайте теперь найдём пользователя по комбинации нескольких полей: users.username, users.email, profiles.first_name, profiles.last_name, friends.first_name, friends.last_name.

Вот как это можно сделать с помощью базовых методов Eloquent:

$name = strtolower(Input::get('query'));
 
$matchingUsers = User::where(function ($q) use ($name) {
      $q->where('email', 'like', '%'.str_replace(' ', '', $name).'%')
        ->orWhere('username', 'like', "%{$name}%")
        ->orWhereHas('profile', function ($q) {
          $q->where(function ($q) {
            $q->where('first_name', 'like', "%{$name}%")
              ->orWhere('last_name', 'like', "%{$name}%");
        });
      });
    })->get();

Это достаточно просто, но не учитывает один важный фактор — релевантность полей. И также из-за этого нет смысла искать данные в таблице друзей.

Очевидно, что для построения нужного запроса с учётом релевантности нужно будет произвести некоторое кол-во вычислений, но проектировать движок, как у гугла, здесь не стоит. ;)

В любом случае, в идеале это должно выглядеть так:

$name = 'john doe';
 
$matchingUsers = User::search($name, [
    'profile.last_name' => 20,
    'email' => 10,
    'username' => 10,
    'profile.first_name' => 5,
    'friends.username' => 2,
    'friends.email' => 2,
    'friends.profile.first_name' => 1,
    'friends.profile.last_name' => 1,
  ])->get()
 
// поля для поиска по умолчанию можно определить в модели:
protected $searchableColumns = [
    'profile.last_name' => 20,
    'email' => 10,
    'username' => 10,
    'profile.first_name' => 5,
    'friends.username' => 2,
    'friends.email' => 2,
    'friends.profile.first_name' => 1,
    'friends.profile.last_name' => 1,
];
 
// и затем просто вызвать
User::search($name)->get()

Колонки здесь — это просто отражения связей между моделями. Число возле колонок — это «вес» каждой из них, определяющий их важность при поиске.

Вот как работает EloquenceBuilder — автоматически джоинит связанные таблицы, добавляет некоторое кол-во проверок для производительности и сортирует результаты поиска по релевантности.

Идея этой функциональности базирована на пакете https://github.com/nicolaslopezj/searchable, но в Eloquence это реализовано более гибко и проще для использования, и что самое важное — работает в 4 раза быстрее (или даже лучше, в зависимости от кол-ва дополнительных where в запросах).

Ниже ещё несколько примеров того, как работает поиск. Полнотекстовый поиск не использует индексы и, соответственно, производится медленнее, что может быть заметно в больших таблицах данных или при нескольких джоинах.

В этом случае вы, возможно, захотите отключить полнотекстовый поиск и использовать right-hand wildcard (word*).

// полнотекстовый поиск по умолчанию + указанный «вес» колонок
User::search('john doe', ['email' => 10, 'profile.name' => 20, 'friends.profile.name' => 5])->take(10)->get()
 
// полнотекстовый поиск по колонкам по умолчанию определённым в модели
User::search('"going to LA"')->get()
 
// точный поиск
User::search('"going to LA"', $fulltext = false)->get()
 
// поиск фразы по wildcard
User::search(['going to *', '*LA*', '*NY*'], $fulltext = false)->get()

Попробуйте пакет и пишите свои отзывы в репозитории пакета, указанном в самом начале.

Источник: http://softonsofa.com/laravel-searchable-the-best-package-for-eloquent/

Плюсануть
Поделиться
Отправить