Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 10.05.2017 13:43:43

Поиск по нескольким моделям с нормальной пагинацией

Добрый день!

Есть у меня проект, там несколько моделей, статьи, объявления и тп. У всех у них есть поля name и desc, вот хотелось бы как то сделать нормальный поиск по всем этим моделям, вся документация, что по скауту, что по другим решениям описывает, работу только с одной таблицей... По крайней мере я ничего не нашел другого...

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

Спасибо!

Не в сети

#2 10.05.2017 14:38:30

Re: Поиск по нескольким моделям с нормальной пагинацией

Давай начнем с конца.

Что ты хочешь получить — одну коллекцию с объектами разного типа? Или отдельную коллекцию под каждый объект?

P.S. Готового решения скорее всего нет, придется писать самомe. Слежение за моделью можно повесить на обсвервер https://laravel.com/docs/5.0/eloquent#model-observers

Не в сети

#3 10.05.2017 14:47:53

Re: Поиск по нескольким моделям с нормальной пагинацией

Kiran пишет:

Может какое решение уже есть готовое?

Быстрого решения я не знаю, есть поисковые движки, к примеру:
https://laracasts.com/series/search-as- … episodes/1 (платный, если нет доступа к ларакасту, речь там об этом: https://www.algolia.com/)
https://ru.wikipedia.org/wiki/Sphinx_(% … %BD%D0%B0) (бесплатный)

по двум ресурсам есть много туториалов, Sphinx достаточно часто встречается.

Изменено covobo (10.05.2017 14:49:04)

Не в сети

#4 10.05.2017 16:56:19

Re: Поиск по нескольким моделям с нормальной пагинацией

Спасибо за ответы.

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

Algolia я так понял бесплатный сервис.

Сейчас обрисовывается такая картинка, при сохранении записи я создаю клон полей(загловок и текст) например в модели search и далее у меня появляется возможность делать нормальную выборку всех данных разных типов с использование пагинации. Но если честно мне не очень охота зависеть от дополнительных сервисов и тем самым усложнять относительно простой проект. Со Sphinx та же история, если бы база была очень большой все это имело бы смысл но в моем случае наверное излишне, автокомплита там тоже не будет, поэтому запросов к бд минимум.  Может что то попроще можно подтянуть для боле менее корректного поиска теперь уже получается по одной таблице?

Ну или еще какие то варианты может есть интересные?

Не в сети

#5 10.05.2017 17:02:19

Re: Поиск по нескольким моделям с нормальной пагинацией

Как вариант — поля name & desc вынести в отдельную таблицу (и модель), которая будет связана с остальными полиморфной связь.

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

А для основных таблиц можно будет сделать прокси-геттеры для получения нужных данных из полей name & desc.

Понятно? или нужен пример?

Не в сети

#6 10.05.2017 17:56:37

Re: Поиск по нескольким моделям с нормальной пагинацией

DBR пишет:

}%Как вариант - поля name & desc вынести в отдельную таблицу (и модель), которая будет связана с остальными полиморфной связь. 

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

А для основных таблиц можно будет сделать прокси-геттеры для получения нужных данных из полей  name & desc.

Понятно? или нужен пример?

Частично понятно, а вот про прокси-геттеры не ясно, видимо не дочитал smile. С примером оно всегда понятне будет smile.  А возможно оставить поля name и desc в основных таблицах и продублировать их в таблицу для поиска? Так как поиск не так часто нужен будет и структуру менять не охота.
Так старательно хотел уйти от структуры таблиц в вордпресе, где все данные в двух таблицах храняться и опять таки прихожу к этой теме )...

Не в сети

#7 10.05.2017 18:45:15

Re: Поиск по нескольким моделям с нормальной пагинацией

  1. А возможно оставить поля name и desc в основных таблицах и продублировать их в таблицу для поиска? Так как поиск не так часто нужен будет и структуру менять не охота.

Возможно.

Не в сети

#8 10.05.2017 18:47:32

Re: Поиск по нескольким моделям с нормальной пагинацией

Как вариант — поля name & desc вынести в отдельную таблицу (и модель), которая будет связана с остальными полиморфной связь.

такая структура не особо эффективна при большом количестве записей – проверено постами вордпресса и инфоблоками битрикса smile

я собственно не вижу в чём проблема нормализовать структуру записей разного типа под индекс – на сфинксе это можно сделать прямо в индексаторе, просто написав запрос на выборку определённого вида под запись – типа select xyz_foo as title, xyz_bar as description, 'xyz' as type – потом объединить выборки в один общий индекс (сфинкс так умеет) и можно выбирать из него как с фильтрацией по type так и без

Не в сети

#9 10.05.2017 19:01:40

Re: Поиск по нескольким моделям с нормальной пагинацией

  1. Частично понятно, а вот про прокси-геттеры не ясно, видимо не дочитал

В документации про это явно не написано )

Смысл в том, что ты у объекта объявляешь свойства которых у него нет через $appends и через акксесор получаешь данные из связанной таблицы. В результате ты работаешь с desc & name как будто свойство самого объекта, не заморачиваясь что в действительности они в другой таблице. Сделать можно в виде трейта. Текущий код менять не придется. Так что лучше не заморачиваться дублированием данных во вторую таблицу.

PHP
class TextData extends Models {

    public function 
paramable()
    {
        return 
$this->morphTo();
    }
}

class 
Page extends Model {
    protected 
$appends = ['name''desc'];

    public function 
param() {
        return 
$this->morphMany(TextData::class, 'paramable');
    }

    public function 
getNameAttribute()
    {
        return 
$this->param->name;
    }

    public function 
getDescAttribute()
    {
        return 
$this->param->desc;
    }
}

https://laravel.com/docs/5.4/eloquent-relationships#polymorphic-relations
https://laravel.com/docs/5.4/eloquent-mutators

Не в сети

#10 10.05.2017 19:07:27

Re: Поиск по нескольким моделям с нормальной пагинацией

  1. такая структура не особо эффективна при большом количестве записей – проверено постами вордпресса и инфоблоками битрикса ☺

В смысле лишние запросы? Или не удобно использовать?

Изменено DBR (10.05.2017 19:08:00)

Не в сети

#11 10.05.2017 20:40:10

Re: Поиск по нескольким моделям с нормальной пагинацией

в смысле что мета (дополнительные поля в связанной таблице) для записей оказывается «раскидана» по разным блокам в ib_data (страничное хранилище innodb), в то время как данные самих записей хранятся совместно, «кучкой». на небольшом количестве данных ты этого не замечаешь, потому что такая «фрагментация» невелика и большая часть данных всегда живёт либо в буферах innodb (см. innodb-buffer-pool-size) либо в файловом кэше. а вот например когда у тебя интернет-магазин на битриксе с 1.5 млн товарных позиций, которые ещё и постоянно меняются, добавляются и удаляются – а хранится мета в связанной таблице by design – недостатки такой организации данных резко становятся заметны. даже если у тебя поиск сделан через индекс на эластике, сама выборка записей по айдишникам – по первичному ключу вообще-то! – внезапно выполняется совсем не так быстро как должна бы по идее – много операций чтения с диска под нагрузкой, плюс ещё сам битрикс генерит навороченные запросы с проверкой кучи условий

конечно существует множество случаев, когда даже в такой структуре можно добиться приличной производительности, даже на сайте с довольно большим количеством записей. и потом 1.5 млн часто меняющихся товарных позиций – это редкий случай. просто я не сторонник того чтобы усложнять структуру данных без особой необходимости. по крайней мере задача поиска к таким точно не относится – есть множество обходных путей, способов подогнать данные под требуемое ТЗ на поиск

опять же – не бывает абстрактного «какого-то» поиска. всегда есть условия как он должен работать – по каким полям искать, что считать совпадением (иногда – это полнотекстовый поиск с морфологией, иногда – автокомплит по первым буквам), плюс могут быть дополнительные фильтры по тем или иным условиям – соответственно по ним мы определяем что должно присутствовать в индексе, как он должен быть настроен – а уже потом определяем как и откуда мы эти данные возьмём. изменять структуру данных под условия поиска для этого обычно совсем не нужно

как именно и где мы сформируем индекс – дело десятое. будет это отдельная таблица в базе (FULLTEXT-индексы есть в MyISAM, хотя работают они по-моему фигово), или мы проиндексируем в отдельном сервисе (сфинкс, эластик) или вообще выгрузим в облачный сервис (алголия – кстати платная) – в любом случае содержание индекса будет определяться тем что и как будет иметь возможность искать пользователь

Не в сети

#12 11.05.2017 06:30:59

Re: Поиск по нескольким моделям с нормальной пагинацией

Понял. Согласен.

Думаю это болезнь всех универсальных движков. В угоду гибкости теряют в производительности. А вот для небольших и средних проектов такая схема хранения данных вполне пойдет.

Не в сети

#13 11.05.2017 12:10:24

Re: Поиск по нескольким моделям с нормальной пагинацией

Всем спасибо, обмозгую все сказанное и буду потихоньку внедрять...

Не в сети

#14 22.05.2017 17:44:51

Re: Поиск по нескольким моделям с нормальной пагинацией

Фуф, дошли руки до дела таки, решил начать с Sphinx. Все настроил, но вот досада, с пагинацией таки не срастается у меня ничего. Не могу понять как прикрутить страндартную пагинацию к этому движку, так что бы шаблоны все стандартные работали... Совсем нет желание переписывать тот функционал, что есть. Может есть какие наработки уже?

Вот что наклепал, но это тестово, оно толком не работает

        $count = get_object_vars($conn->select(
            \DB::raw("SELECT COUNT(*) FROM gotoaltayIndex WHERE MATCH (:query) OPTION max_matches=100000,field_weights=(name=10, desc=5)"), array(
            'query' => $query,
        ))[0])['count(*)'];

        $results = DB::connection('sphinx')->select("SELECT * FROM gotoaltayIndex WHERE MATCH ('".$query."') OPTION max_matches=100000,field_weights=(name=10, desc=5)" );
   

        $paginator = new Paginator($results, ($count/12), 2);
        echo $paginator->links('vendor.pagination.bootstrap-4');

Не в сети

#15 22.05.2017 18:08:18

Re: Поиск по нескольким моделям с нормальной пагинацией

ну справедливости ради, использовать SphinxQL напрямую для выборок из индекса – это не лучший вариант. есть пакет https://github.com/sngrl/sphinxsearch (он использует gigablah/sphinxphp под капотом). кроме того особая вкусняшка – получив айдишники, он сразу выбирает соответствующие модели из базы и переупорядочивает коллекцию в том же порядке что и результаты из сфинкса – ты на выходе получаешь уже готовые результаты, которые остаётся передать в вид и отобразить пользователю

кроме того, если версия ларавеля достаточно современная и доступен laravel scout, для него есть аж четыре разных реализации драйвера под сфинкса – можно делать поисковые выборки прямо на моделях через стандартное апи!

Не в сети

#16 22.05.2017 18:12:30

Re: Поиск по нескольким моделям с нормальной пагинацией

constb  Спасибо! Но вот что меня смущает, в сфинксе я объединил 5 таблиц в одну, так же пришлось там айдишки править, ибо ему надо уникальные. Вот, и мне кажется sphinxsearch работать будет не идеально с таким положением дел... Про скаута погляжу, не смотрел с ним реализаций...

Не в сети

#17 22.05.2017 18:29:32

Re: Поиск по нескольким моделям с нормальной пагинацией

Но вот что меня смущает, в сфинксе я объединил 5 таблиц в одну, так же пришлось там айдишки править, ибо ему надо уникальные

для сквозного поиска по разным типам записей? для этого не надо айдишники править, просто объединяются несколько индексов в запросе, каждый по своему типу записи. не знаю, можно ли такое провернуть с помощью скаута, но сама возможность выбирать результаты одновременно из нескольких (или всех) индексов в сфинксе предусмотрена

давай приведу пример

примерно так индексируется (код взят из sphinxsearch.conf, проект старый, не на ларавеле):

source template
{
  type    = mysql
  sql_host  = localhost
  sql_user  = xxx
  sql_pass  = yyy
  sql_db    = zzz

  sql_attr_uint = source_id
  sql_attr_uint = date_show

  sql_query_pre = SET NAMES utf8
}

source news : template
{
  sql_query_range = SELECT MIN(id),MAX(id) FROM articles
  sql_range_step = 1024
  sql_ranged_throttle = 1000
  sql_query = \
    SELECT id, 1 as source_id, title, body, announce, date_show \
    FROM articles \
    WHERE title IS NOT NULL AND title != '' AND date_show < UNIX_TIMESTAMP() AND id>=$start AND id<=$end

  sql_query_info = SELECT * FROM articles WHERE id=$id
}

source afisha : template
{
  sql_query_range = SELECT MIN(id),MAX(id) FROM events
  sql_range_step = 1024
  sql_ranged_throttle = 1000
  sql_query = \
    select id, 2 as source_id, title, description as body, announce, premiere_date as date_show \
    FROM events \
    WHERE id>=$start AND id<=$end

  sql_query_info = SELECT * FROM events WHERE id=$id
}

index news
{
  source  = news
  path    = /home/server/sphinx/index/news

  # не делаем различий между е и ё
  charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F, U+401->U+0435, U+451->U+0435
  blend_chars           = +, &, -, U+23

  morphology    = stem_ru
  min_stemming_len  = 2
  min_word_len    = 2

  charset_type    = utf-8

  html_strip    = 1
  html_index_attrs  = img=alt,title; a=title;
}
index afisha : news
{
  source  = afisha
  path  = /home/server/sphinx/index/afisha
}

indexer
{
  mem_limit = 1024M
}

searchd
{
  listen = 127.0.0.1:9312
  log = /home/server/sphinx/logs/searchd.log
  query_log = /home/server/sphinx/logs/query.log
  read_timeout = 5
  client_timeout = 300
  max_children = 30
  pid_file = /home/server/sphinx/logs/searchd.pid
  max_matches = 1000

  seamless_rotate   = 1
  preopen_indexes   = 1
  unlink_old    = 1
  mva_updates_pool  = 1M
  max_packet_size   = 8M
  max_filters   = 256
  max_filter_values = 4096
  max_batch_queries = 32

  workers = threads
  compat_sphinxql_magics = 0
}

на выборках в качестве имени индекса передаётся или один отдельный индекс (news, afisha) либо * – тогда source_id будет содержать тип конкретной записи, результаты выборки из базы приводятся к общему формату. к сожалению в данном конкретном проекте используется другой клиент сфинкса, работающий по бинарному протоколу – не знаю как это будет в SphinxQL

Не в сети

#18 22.05.2017 18:35:34

Re: Поиск по нескольким моделям с нормальной пагинацией

Спасибо, завтра проверю, посмотрим что получится. Но со скаутом получается толком не скрестить, так как он использует какую то одну таблицу. Все равно придется делать выборку и заморачиваться с пагинацией... Или скаут можно натравить на выборку из нескольких моделей?
я там через конфиг объединил, но твой способ крутее полюбому если заработает smile.

Не в сети

#19 22.05.2017 19:36:33

Re: Поиск по нескольким моделям с нормальной пагинацией

Вообщем потестировал я этот метод, если айдишнки совпадают, то он выводит в выборке что то одно. Вот мой конфиг пока такой
source gotoaltay . Это я так понял не вот такая фитча штатная, поэтому всяческие скрипты ее врятли будут поддерживать sad

http://sphinxsearch.com/docs/current/conf-source.html

Вот оно, кстати я конфиг чуть пределал в надежде что получиться но походу, тот вариант что у меня был делает то же самое smile.

Вообщем пока не вижу выхода из готовых решений, Хелп ми плиз smile)

{
        type                    = mysql

        sql_host                = localhost
        sql_user                = 234234
        sql_pass                = 234234
        sql_db                  = 2342344
        sql_port                = 3306  # optional, default is 3306

        sql_query_pre           = SET NAMES utf8
        sql_query_pre           = SET SESSION query_cache_type=OFF

        sql_field_string        = name
        sql_field_string        = desc
        sql_attr_string         = module
        sql_attr_timestamp      = created_at
}

source gotoaltayProperties : gotoaltay
{
        sql_query               = \
                SELECT id, UNIX_TIMESTAMP(created_at) AS created_at, name, `desc`, 'property' as 'module' FROM properties
}

source gotoaltayExcursions : gotoaltay
{
        sql_query               = \
                SELECT id, UNIX_TIMESTAMP(created_at) AS created_at, name, `desc`, 'excursion' as 'module' FROM excursions
}
source gotoaltayRealties : gotoaltay
{
        sql_query               = \
                SELECT id, UNIX_TIMESTAMP(created_at) AS created_at, name, `desc`, 'realty' as 'module' FROM realties
}


source gotoaltayNews : gotoaltay
{
        sql_query               = \
                SELECT (id+4000000) as id, UNIX_TIMESTAMP(created_at) AS created_at, name, `text` as 'desc', 'news' as 'module' FROM news
}

source gotoaltayPosts : gotoaltay
{
        sql_query               = \
                SELECT (id+5000000) as id, UNIX_TIMESTAMP(created_at) AS created_at, name, `text` as 'desc', 'post' as 'module' FROM posts
}

index gotoaltayIndexPosts
{
        source                  = gotoaltayPosts
        path                    = /var/lib/sphinx/posts
        morphology              = stem_enru
        docinfo                 = extern
        min_word_len            = 3
        charset_table           = 0..9, A..Z->a..z, _, a..z, U+0401->U+0435, U+0451->U+0435, U+410..U+42F->U+430..U+44F, U+430..U+44F
        html_strip              = 1
}
index gotoaltayIndexProperies : gotoaltayIndexPosts
{
        source                  = gotoaltayProperties
#       source                  = gotoaltayExcursions
#       source                  = gotoaltayRealties
#       source                  = gotoaltayNews

        path                    = /var/lib/sphinx/gotoaltay-properies
}

index gotoaltayIndexExcursions : gotoaltayIndexPosts
{
        source                  = gotoaltayExcursions
        path                    = /var/lib/sphinx/gotoaltay-excursions
}

index gotoaltayIndexRealties : gotoaltayIndexPosts
{
        source                  = gotoaltayRealties
        path                    = /var/lib/sphinx/gotoaltay-realties
}



indexer
{
        mem_limit               = 128M
}

Не в сети

#20 23.05.2017 09:52:19

Re: Поиск по нескольким моделям с нормальной пагинацией

Kiran, у тебя уже почти получилось. теперь посмотри на конфиг в моём посте и особое внимание обрати на добавление source_id в sql-запросах – эти source_id нужно выбирать из индекса вместе с айдишниками – тогда у тебя будут и айдишники и типы записей, которым они соответствуют, в результатах поисковой выборки – по ним и определишь что и из какой модели выбирать для отображения результатов

Не в сети

#21 23.05.2017 15:04:34

Re: Поиск по нескольким моделям с нормальной пагинацией

Constb спасибо, но либо я ничего не понял, либо уже это реализовал,только у меня называется поле module. А вот с айдишниками приходится плюсовать к ним значение, иначе при выборке, часть записей отсутсвует у которых одинаковые id. Вот. По сути я реализовал пагинацию таки, надо было другой класс использовать...

use Illuminate\Pagination\LengthAwarePaginator;

$paginator = new LengthAwarePaginator($resultNew, $count, 12);
$paginator->setPath("search")->appends(Request::All());

Щас вот с генерацией уролов еще разберусь smile .

Еще такой вопрос, может есть решение, вот сделал я выборку, а как мне можно теперь выделить слова найденые? Что то ума не приложу, по идее это должен бы сфинкс сделать, но тоже не ясно как его уговорить на такой подвиг smile

Не в сети

#22 23.05.2017 18:13:46

Re: Поиск по нескольким моделям с нормальной пагинацией

он это делает. но опять же у меня это реализовано только в старом проекте и через бинарный протокол – выдаёт фрагмент текста, содержащего найденные слова. как в sphinxql это – надо гуглить.

по поводу айдишников – я не вижу твоих запросов кроме старого сообщения – но там выборка делается только из одного индекса

Не в сети

Подвал раздела