Как да накараме Laravel и Elasticsearch да станат приятели

Laravel е любимата ми рамка досега. Харесва ми, че предлага много възможности извън кутията. Екипът на Laravel обмисля всяка част от своя продукт, за да направи инструмента, който решава всички обичайни задачи, с които уеб разработчиците се сблъскват по време на рутината си.

Те дори са направили страхотен инструмент, който ви помага да създадете търсачка на вашия сайт. Казва се Laravel Scout. Единственото неприятно тук е драйверът, който идва с инсталацията по подразбиране на Laravel. Това е водачът на Algolia. Разбира се, Algolia е добър и бърз начин да добавите възможности за търсене към вашите модели. Но аз и, предполагам, много други разработчици биха предпочели да имат Elasticsearch като драйвер по подразбиране.

За щастие екипът на Laravel ни даде възможност да създадем собствени драйвери. Това е доста лесно и можете да намерите цялата необходима информация за това в документите на скаутите. В тази статия ще използвам собственото си решение, което създадох да не съм доволен от съществуващите. Опитах се да следвам идеологията на Laravel, за да създам инструмент, който ви позволява да се справите с Elasticsearch по много лесен и удобен начин.

В този урок ще ви преведа чрез скучни неща, като настройка и конфигуриране на различен софтуер, до интересна част от изпълнението на заявки за търсене. Ще научим как да търсите данни в Elasticsearch, как да филтрирате данни, без да посочвате низ за търсене и как да извършвате заявки, използвайки свързани модели на полета.

чифлик

Предпочитам да използвам Homestead за разработката, виртуална машина, предоставена от екипа на Laravel. За мен това е удобен начин да отделя работна среда от моя личен софтуер и данни, за да предотвратя затрупване на моята машина.

В този урок ще използвам Homestead, така че всички инструкции за конзолата са дадени за Ubuntu.

Ако използвате VM, свържете се с вашата машина чрез SSH и ще започнем с конфигурирането на софтуер.

Инсталиране на ластици

За да изпълним заявка за търсене, първо трябва да инсталираме Elasticsearch. Това може лесно да се направи с мениджъра на пакети:

// изтеглете и инсталирайте ключа за подписване на Elasticsearch
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-ключ добавяне -
// инсталирайте пакета apt-transport-https
sudo apt-get install apt-transport-https
// запишете дефиницията на хранилището
ехо "deb https://artifacts.elastic.co/packages/5.x/apt стабилен главен" | sudo tee -a /etc/apt/sources.list.d/elastic-5.x.list
// инсталирайте пакета elastsearch
sudo apt - вземете актуализация && sudo apt - получите инсталиране на еластични търсения

Но както обикновено, това не е единственото действие, което трябва да направим, за да го накараме да работи.

Отворете файла /etc/elasticsearch/elasticsearch.yml с всеки редактор, например vim, и задайте network.host на localhost:

network.host: localhost

Сега трябва да рестартираме услугата:

судо услуга рестартиране на еластични изследвания

Отнема известно време, за да рестартирате услугата. Веднага щом приключи, проверете дали работи с обикновена http заявка:

къдря http: // localhost: 9200 /

Той трябва да върне отговор, подобен на следното:

{
  "name": "UhEc17r",
  "cluster_name": "elasticsearch",
  "cluster_uuid": "QoNneSUhTG6EJaA4LMqDlA",
  "версия": {
    "число": "5.4.0",
    "build_hash": "780f8c4",
    "build_date": "2017-04-28T17: 43: 27.229Z",
    "build_snapshot": false,
    "lucene_version": "6.5.0"
  }
  "tagline": "Знаеш ли, за търсене"
}

Надявам се, че тук не сте имали проблеми и можем да продължим.

Създаване на нов проект Laravel

Нека създадем нова инсталация на рамката Laravel. Ако използвате Homestead, след това имате инсталатора на Laravel извън кутията, ако не можете да го инсталирате с помощта на Composer.

В Homestead всички проекти се намират в директорията на домашния код. Преместете се в директорията и направете нов проект, използвайки инсталатора на Laravel:

cd ~ / Код /
laravel нов урок за търсене

В този урок ще използваме база данни sqlite, само за да улесним нещата. Отидете до .env файла, задайте опцията DB_CONNECTION за sqlite и коментиране на други опции на базата данни:

DB_CONNECTION = SQLite
# DB_HOST = 127.0.0.1
# DB_PORT = 3306
# DB_DATABASE = Homestead
# DB_USERNAME = Homestead
# DB_PASSWORD = тайна

И накрая, за да съхраняваме данни, трябва да създадем файл:

докоснете базата данни / database.sqlite

Скаут шофьор

Вече говорихме малко за Laravel Scout. Той добавя пълнотекстово търсене към вашите Красноречиви модели. Освен това споменах, че ще използваме персонализиран скаутски двигател, тъй като няма драйвер за Elasticsearch.

Нека инсталираме пакета с Composer:

композиторът изисква babenkoivan / разузнавач-еластичен търсач

Както можете да се досетите, ние ще направим някои конфигурации. Първо, отидете на config / app.php и добавете два низа в секцията с доставчиците:

'доставчици' => [
   // ...
   // самия пакет Scout
   Laravel \ Скаут \ ScoutServiceProvider :: клас,
   
   // драйверът за Elasticsearch
   ScoutElastic \ ScoutElasticServiceProvider :: клас,
   // ...
]

Второ, ние трябва да публикуваме настройките на пакетите, за да конфигурираме Scout. Изпълнете следните команди в конзолата:

php занаятчийски доставчик: публикувай --provider = "Laravel \ Scout \ ScoutServiceProvider"
php занаятчийски доставчик: публикуване --provider = "ScoutElastic \ ScoutElasticServiceProvider"

И накрая, добавете SCOUT_DRIVER = ластик в края на .env файла.

Конфигуратор на индекса

Elasticsearch Index е като база данни в традиционния релационен модел, докато Elasticsearch Type е като таблица. Трябва да създадем и двете, преди да можем да изпратим каквито и да било данни на Elasticsearch. Ще започнем от създаването на индекс.

В началото ще направим специален клас - Index Configurator:

php artisan make: index-configurator TutorialIndexConfigurator

Този клас описва индекса на урока. Ние сме свободни да посочим или настройки (например анализатори) или картографиране по подразбиране в тях. Но за нашите цели не се нуждаем от специални настройки, ние сме добри с тези по подразбиране.

Накрая, нека създадем индексна единица в ElasticSearch според конфигуратора на индекса:

php занаятчийски ластик: създаване на индекс "App \ TutorialIndexConfigurator"

Търсаеми модели

Този урок е свързан с добавяне на пълнотекстово търсене към вашите Красноречиви модели, но все още не сме създали нито един от тях. В този раздел ще запълня празнината и ще направя две цялости: книга и автор.

Нека да кажем, че всяка книга има заглавие, описание, година на издаване и автор. Авторът има само име. Нека установим и следното опростяване: автор може да напише няколко книги, но книга може да бъде написана само от един автор.

Можем да създаваме модели с помощта на командата Artisan make:

php artisan make: searchable-model Book --index-configurator = "TutorialIndexConfigurator" - миграция
php artisan make: searchable-model Author --index-configurator = "TutorialIndexConfigurator" - миграция

Търсените модели са разширени версии на обичайните, но с някакво предварително определено поведение, което им позволява да изпълняват заявки за търсене в Elasticsearch.

Можете да забележите, че и ние сме направили миграции и сме посочили индекси-конфигуратора за всеки модел. Това е важно, защото трябва да бъдем изрични по отношение на това, което Elasticsearch Index използва за тип Elasticsearch.

Можете да намерите и двете (Book.php и Author.php) създадени файлове в папката на приложението на проекта. Ще ги редактираме, за да посочим картографиране за всеки тип Ealsticsearch. Жизненоважно е да зададете картографиране, защото това казва на Elasticsearch как да обработва полета.

Ето пълния код на класа Book след редактиране:

Приложение за пространство на имена;
използвайте ScoutElastic \ SearchableModel;
клас Book разширява SearchableModel
{
    // Не искаме да използваме времеви отметки в този урок
    public $ timestamps = невярно;
    защитен $ indexConfigurator = TutorialIndexConfigurator :: class;
// Ние не анализираме числата, целият текст е на английски
    защитен $ mapping = [
        'свойства' => [
            'id' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ],
            'заглавие' => [
                'type' => 'string',
                'analyzer' => 'английски'
            ],
            'описание' => [
                'type' => 'string',
                'analyzer' => 'английски'
            ],
            'година' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ],
            'author_id' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ]
        ]
    ];
    // Всяка книга принадлежи на един автор
    автор на публична функция ()
    {
        върнете $ this-> pripadaTo (Автор :: клас);
    }
}

Кодът на модела Автор е по-долу:

Приложение за пространство на имена;
използвайте ScoutElastic \ SearchableModel;
клас Автор разширява SearchableModel
{
   public $ timestamps = невярно;
   защитен $ indexConfigurator = TutorialIndexConfigurator :: class;
   защитен $ mapping = [
        'свойства' => [
            'id' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ],
            'име' => [
                'type' => 'string',
                'analyzer' => 'английски'
            ]
        ]
    ];
    // Всеки автор може да напише няколко книги
    публикации с публични функции ()
    {
        върнете $ this-> hasMany (Книга :: клас);
    }
}

миграции

По време на създаването на модел направихме и два файла за миграция. Можете да ги намерите в папката база данни / миграции. Намерете файла, който завършва с books_table.php и опишете полетата на таблицата, както следва:

използвайте осветена \ поддръжка \ фасади \ схема;
използвайте Illuminate \ Database \ Schema \ Blueprint;
използвайте илюминация \ база данни \ миграции \ миграция;
клас CreateBooksTable разширява миграцията
{
    публична функция нагоре ()
    {
        Схема :: create ('книги', функция (Blueprint $ table) {
            $ Трапезен> стъпки ( "идентификатор");
            $ Трапезен> низ ( "заглавието");
            $ Трапезен> текст ( "описание");
            $ Трапезен> число ( "година");
            $ Трапезен> число ( "author_id ');
        });
    }
    публична функция надолу ()
    {
        Схема :: dropIfExists ( "книги");
    }
}

Сега намерете файла author_table.php и опишете полетата на таблицата с автори:

използвайте осветена \ поддръжка \ фасади \ схема;
използвайте Illuminate \ Database \ Schema \ Blueprint;
използвайте илюминация \ база данни \ миграции \ миграция;
клас CreateAuthorsTable разширява миграцията
{
    публична функция нагоре ()
    {
        Схема :: create ('автори', функция (Blueprint $ table) {
            $ Трапезен> стъпки ( "идентификатор");
            $ Трапезен> низ ( "име");
        });
    }
    публична функция надолу ()
    {
        Схема :: dropIfExists ( "автори");
    }
}

За да създадете таблици, изпълнете командата migrate в конзолата:

php занаятчията мигрира

Данъчни данни

За да играем с търсачката, ни трябват някои данни. Най-лесният начин да го получите е да създадете някои фалшиви данни. Нека добавим фабрики за модела Book и автора в базата данни / фабрики / файла ModelFactory.php:

$ factory-> define (Приложение \ Автор :: клас, функция (Faker \ Generator $ faker) {
    връщане [
        'name' => "{$ faker-> firstName} {$ faker-> lastName}"
    ];
});
$ factory-> define (Приложение \ Книга :: клас, функция (Faker \ Generator $ faker) {
    връщане [
        'title' => ucfirst ($ faker-> realText (15)),
        'description' => $ faker-> realText (200),
        'година' => $ faker-> година,
        'author_id' => функция () {
        // Взимаме първия случаен автор от таблицата
            връщане App \ Author :: inRandomOrder () -> first () -> id;
        }
    ];
});

Laravel има готина PHP конзола, наречена Tinker. Можете да го стартирате с помощта на artisan:

php занаятчийски калайджия

Нека създадем 50 автори:

фабрика (App \ Автор: клас, 50) -> create ()

и 200 книги:

фабрика (App \ Book :: клас, 200) -> create ()

Страхотен! Сега имаме данни, с които да играем.

Заявки за търсене

Всички подготовки са готови, готови сме да направим първата си заявка за търсене. Да намерим всички книги, които имат думата Алиса:

// В тази заявка казваме Elasticsearch - дайте ни 10 записа, които съдържат думата Alice във всяко поле
App \ Book :: търсене ( "Алиса") -> приемане (20) -> получите ()

Сигурен съм, че ще получите някои резултати, защото методът Faker \ Generator :: realText връща произволен текст от книгата на Алиса в страната на чудесата.

Не казахме на Elasticsearch кои полета имат по-голям приоритет, затова можем да видим бъркотия в резултата. Много по-хубаво би било да има записи с Алиса в заглавие в горната част и записи с Алис в описание в долната част. Но как можем да постигнем това? Отговорът е правило за търсене.

За ваше удобство има команда за създаване на правила за търсене:

php artisan make: search-rule BookSearchRule

Правилото за търсене е клас, който описва как ще се изпълняват заявките за търсене. Трябва да внедрите само един метод - buildQueryPayload. Този метод трябва да върне заявка за bool.

Засега искаме да търсим по заглавие и описание, така че нека модифицираме класа BookSearchRule според нашите нужди:

Приложение за пространство на имена;
използвайте ScoutElastic \ SearchRule;
клас BookSearchRule разширява SearchRule
{
    изграждане на обществена функцияQueryPayload ()
    {
        $ query = $ this-> builder-> query;
        връщане [
            'трябва' => [
                [
                    'съвпадение' => [
                        'заглавие' => [
                            'заявка' => $ заявка,
                            'boost' => 2
                        ]
                    ]
                ],
                [
                    'съвпадение' => [
                        'описание' => [
                            'заявка' => $ заявка,
                            'boost' => 1
                        ]
                    ]
                ]
            ]
        ];
    }
}

Сега нека да кажем на модела Книга да използва по правило стандартното правило за търсене:

// ...
клас Book разширява SearchableModel
{
    защитени $ searchRules = [
        BookSearchRule :: клас
    ];
    / ...
}

Нека направим едно и също запитване още веднъж:

App \ Book :: търсене ( "Алиса") -> приемане (20) -> получите ()

Вече можете да видите различен резултат: записите с Алис в заглавие са в горната част. И точно това искахме.

Добре, да преминем към следващия пример. Ами ако искаме да вземем всички книги, които са написани след 2010 г.? Няма проблем! Можем да го направим така:

// Казваме Elasticsearch - намерете всичко, което беше пуснато след 2010 г.
Приложение \ Книга :: търсене ('*') -> къде ('година', '>', 2010) -> вземе (10) -> получи ()

Нека направим нещо по-трудно. Сега искам да търся книги по име на автор. Как мога да направя това? Има няколко варианта. Първият е да се намери автор по име с помощта на модела Author и след това да се предаде идентификатор на автора до клаузата където на заявката за търсене. Второто е да накараме модела Book да намира записи според името на автора.

Можем да определим метода toSearchableArray, за да определим кои полета трябва да бъдат индексирани от Elasticsearch:

// ...
клас Book разширява SearchableModel
{
    // ...
    обществена функция доSearchableArray ()
    {
        връщане array_merge (
            // По подразбиране всички полета на модела ще бъдат индексирани
            родител :: toSearchableArray (),
            ['author_name' => $ this-> author-> name]
        );
    }
}

Сега трябва да добавим име на автора към картографирането на модела:

// ...
клас Book разширява SearchableModel
{
    // ...
    защитен $ mapping = [
        'свойства' => [
            'id' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ],
            'заглавие' => [
                'type' => 'string',
                'analyzer' => 'английски'
            ],
            'описание' => [
                'type' => 'string',
                'analyzer' => 'английски'
            ],
            'година' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ],
            'author_id' => [
                'type' => 'integer',
                'index' => 'not_analyzed'
            ],
            'автор_име' => [
                'type' => 'string',
                'analyzer' => 'английски'
            ]
        ]
    ];
    // ...
}

И още нещо, трябва да променим правилото за търсене:

Приложение за пространство на имена;
използвайте ScoutElastic \ SearchRule;
клас BookSearchRule разширява SearchRule
{
    изграждане на обществена функцияQueryPayload ()
    {
        $ query = $ this-> builder-> query;
        връщане [
            'трябва' => [
                [
                    'съвпадение' => [
                        'заглавие' => [
                            'заявка' => $ заявка,
                            'boost' => 3
                        ]
                    ]
                ],
                [
                    'съвпадение' => [
                        'автор_име' => [
                            'заявка' => $ заявка,
                            'boost' => 2
                        ]
                    ]
                ],
                [
                    'съвпадение' => [
                        'описание' => [
                            'заявка' => $ заявка,
                            'boost' => 1
                        ]
                    ]
                ]
            ]
        ];
    }
}

Сега сме готови да търсим по ново поле, но не е индексирано: Elasticsearch не знаеше нищо за това поле, когато създавахме фалшиви данни. За щастие можем да пренастроим данни с командата за импортиране:

php занаятчийски скаут: импортиране на "App \ Book"

Вземете първото име на автора от базата данни:

първо App \ Автор :: () -> име

В моя случай това е Roxanne Boehm Нека се опитаме да намерим книгите на Роксан:

Приложение \ Резервирай :: търсене ('Roxanne Boehm') -> get ()

Работи!

послеслов

Ако искате да получите повече информация за възможностите на Laravel Scout, проверете официалната документация и страницата на драйвера на Elasticsearch в GitHub.

Ако имате въпроси или имате нужда от помощ, не се колебайте да оставите коментар по-долу.

Благодаря на всички за отзивите! Промених инсталационната част на Elasticsearch, за да ви осигуря стъпките за инсталиране на последната версия. Също така поправих някои неточности в статията и пуснах новата версия на скаутския драйвер с фиксирана заявка match_all.