Кабанчик
Чаще вызывайте родителей!
Мидл (Джуниору): Чё такой грустный?
Джуниор: Сеньор пристаёт. Требует, чтобы я вызвал родителей. Якобы, они код сделают эффективнее. Как будто я сам не умею программировать!
Мидл: Так вызови, раз он хочет. Какие проблемы?
Джуниор: Кого же вызвать? Мама - она добрая. Зато папа - решительный.
Мидл: Вызывай обоих, не ошибешься.
=========================================
class JuniorClass extends ParentClass
=========================================
Вызываем маму
$Junior -> motherSays();
Сынок, я тебе тут пирожков напекла. Покушай!
=========================================
Вызываем папу
$Junior -> fatherSays();
Опять дурью маешься! Ну ты у меня дождешься!
=========================================
Первоисточник:
--
Интернет-магазин светильников: схема полей elasticsearch для фасетного поиска, фильтра товаров
Понимаю, что пикабу все же развлекательный ресурс, но тем не менее все же, пожалуй, добавлю пост так как, возможно, он может быть полезен некоторой части аудитории.
В этой статье я опущу такие подробности работы с Elasticsearch (далее по тексту просто ES), как:
1) Как установить
2) Как подключаться
3) Раскрывать полную схему mapping для товара интернет-магазина
4) Подробное описание всей структуры и всех запросов для получения страницы товаров с результатами поиска и фильтром.
И что-либо еще.
Здесь, как и написано в заголовке, лишь постараюсь описать схему только для полей характеристик товара и как для них делать запросы агрегации и фильтрации.
Предисловие
К написанию статьи пришел после неудачного опыта разработки интернет-магазина на фреймворке и MySQL с десятками тысяч товаров, которые в свою очередь имели несколько десятков характеристик и множество значений для них. Из-за множества запросов для получения значений фильтра товаров и, возможно, абсолютно неправильного структурирования таблиц или по какой-либо другой причине, сайт ужасно тормозил и долго загружался. Дошло до того, что в вебмастере яндекса получал подобную ошибку:
Скрин из интернета.
Сайт разрабатывал не я. Разбираться с ним в дальнейшем не было ни знаний, ни желания. Решил, что впоследствии буду самостоятельно разрабатывать интернет-магазин, но используя другое и нереляционное хранилище данных, а не Mysql. Выбор пал на ES и при изучении понимание структурирования характеристик товаров и получение для них значений, которые впоследствии безболезненно и не затрагивая код можно было бы менять, отняло много времени. Лично мне очень не хватало в русскоязычном интернете абсолютно простых примеров, какие есть, например, для PHP+Mysql.
Все описанное, лишь основано на моем личном опыте и понимании схемы и структуры документов, ориентированной на использовании для построения фасетного поиска в интернет-магазине к которому я пришел при изучении и разработке. Т.е статья рассчитана больше на новичков, которые начали изучать ES.
По сути дела
“Elasticsearch – это распределенный поисковый и аналитический движок на базе Apache Lucene. Полностью с описанием можно ознакомиться на официальном сайте.
Фасетный поиск (фасетная навигация) – поиск товара в разделе, категории или же на странице полнотекстового поиска по характеристикам: цвет, материал, цена, производитель и т.д. Для конечного пользователя – набор фильтров. Каждый фильтр – характеристика. Значения этого фильтра – все возможные значения характеристики. Для интернет-магазина это основная функция поиска, и пользователи ожидают, что она будет работать достаточно быстро.”
В приведенном ниже примере пользователь находится в категории “Люстры” и отфильтровал дополнительно товары в диапазоне цен от 1394 до 42207 руб. и с цветом черный. Было найдено 198 товаров, а на панели фильтров слева перечислены те характеристики, которые содержатся в результатах поиска, а также количество доступных значений, имеющих этот атрибут (количество фасетов):
Перейдя в интернет-магазине светильников в раздел люстры можно лично опробовать фильтр и повторить действия, описанные выше (на сайте используется ES).
Для создания фасетного поиска в ES есть достаточно мощный инструмент агрегирования. Одной из приятных особенностей агрегации является то, что они могут быть вложенными — другими словами, можно определить агрегации верхнего уровня, которые создают «корзины» (buckets) документов и другие агрегации, которые выполняются внутри этих корзин.
Для упрощения понимания, это в целом похоже на команду SQL GROUP_BY. На основе фильтров обобщаются, группируются документы по какому-то определенному признаку.
Индексирование значений фасета
Перед созданием агрегатов атрибуты документа, которые могут служить фасетами, необходимо проиндексировать в ES. Один из способов индексировать их — перечислить все атрибуты и их значения в одном поле, как в следующем примере:
"facets": {
"color": "Черный",
"style": "Лофт",
"room": "Гостиная",
}
Mapping ES при этом должен выглядеть так:
"facets": {
"type": "nested",
"properties": {
"color": {
"type": "keyword",
},
"style": {
"type": "keyword", }
"room": {
"type": "keyword", }
}
}
Такой подход может подойти, но для фасетирования в таком случае в запросах придется явно перечислять все имена полей, для которых мы хотим создать агрегацию.
"aggs": {
"facets": {
"nested": {
"path": "facets"
},
"aggs": {
"color": {
"terms": {
"field": "facets.color"
}
},
"style": {
"terms": {
"field": "facets.style"
}
},
"room": {
"terms": {
field": "facets.room"
}
},
}
}
}
Очевидно, что это не очень практично и эффективно при большом количестве характеристик товаров, которые со временем могут изменяться, дополняться. И, например, при удалении, изменении или добавлении новой характеристики товара придется вручную изменить mapping, переиндексировать и изменить запрос, добавив в него новое имя поля.
Вместо этого я пришел к следующему.
Разделил имена и значения фасетов, отправляемых в индекс эластика, следующим образом:
"string_facets": {
{
"name": "color",
"value": "Черный"
},
{
"name": "color",
"value": "Белый"
},
{
"name": "style",
"value": "Лофт"
},
{
"name": "style",
"value": "Техно"
},
{
"name": "room",
"value": "Гостиная"
},
{
"name": "room",
"value": "Спальня"
}
}
Mapping:
"string_facets": {
"type": "nested",
"properties": {
"name": {
"type": "keyword",
},
"value": {
"type": "keyword",
}
}
}
Для фильтрации и агрегирования такой структуры требуются вложенные фильтры и вложенные агрегации в запросах.
Агрегация:
"aggs": {
"aggs_text_facets": {
"nested": {
"path": "string_facets"
},
"aggs": {
"name": {
"terms": {
"field": "string_facets.name"
},
"aggs": {
"value": {
"terms": {
"field": "string_facets.value"
}
}
}
}
}
}
}
Фильтрация:
"filter": {
"nested": {
"path": "string_facets",
"filter": {
"bool": {
"must": {
{
"terms": {
"string_facets.name": "color"
}
},
{
"terms": {
"string_facets.value": "Черный"
}
}
}
}
}
}
}
Это касается характеристик, у которых значения текстовые. Характеристики с числовыми значениями необходимо хранить и анализировать отдельно. Это связано с тем, что числовые характеристики (например, размеры: ширина, длина) иногда имеют огромное количество различных значений. И вместо того, чтобы перечислять все возможные значения, достаточно просто получить минимальное и максимальное значения и отобразить их в виде селектора диапазона или ползунка. Это возможно, только если значения хранятся в виде чисел.
В mapping это будет выглядеть следующим образом:
"number_facets": {
"type": "nested",
"properties": {
"name": {
"type": "keyword",
},
"value": {
"type": "double",
}
}
}
Агрегация.
"aggs_number_facet": {
"nested": {
"path": "number_facets"
},
"aggs": {
"name": {
"terms": {
"field": "number_facets.name"
},
"aggs": {
"value": {
"stats": {
"field": "number_facets.value"
}
}
}
}
}
}
Ps. Организовав таким образом схему документов и прописав все необходимые запросы, я столкнулся с еще одной проблемой. При фильтрации оставлялись только товары с выбранным значением в фильтре товаров, соответственно нельзя было выбрать несколько значений одного и того же фильтра, что в моем случае влияло на удобство для пользователей. Для описания решения проблемы требуется отдельная статья.
Мечты сбываются?
Телеграм канал для тех, кто ещё копит техдолг.
Сможете найти на картинке цифру среди букв?
Справились? Тогда попробуйте пройти нашу новую игру на внимательность. Приз — награда в профиль на Пикабу: https://pikabu.ru/link/-oD8sjtmAi
Гайд - импортируем CKEditor 5 в Laravel 10 как модуль node.js
Всем привет!
Уже давно настала эра WYSIWYG - редакторов текстов, и при написании наших веб проектов мы конечно же стараемся их использовать по максимуму.
Один из таких редакторов ну просто с сумасшедше-огромной функциональностью - это небезызвестный CKEditor, который недавно (шучу, давно уже) зарелизился замечательной 5-й версией.
Ее мы сегодня и будем импортировать в наш проект на Laravel 10. Скриншотик (и пасхалочка, кто знает тот поймет) внизу.
Как обычно есть 2 пути - простой и сложный.
Простой - это просто прописать в шаблоне
<script src="https://cdn.ckeditor.com/ckeditor5/40.0.0/classic/ckeditor.js"></script>
И активировать его на textarea через ClassicEditor.create().
Но есть нюанс.... что если авторы решат уйти из РФ и ограничит доступ? Или же РКН решит что "ты не пройдешь!" и ... ты не пройдешь. Нужно любить всех своих пользователей, а не заставлять их расчехлять VPN потому что на сайте не работает редактор.
Да и мы ведь серьезный проект пишем! Нам нужно свое! Да еще и желательно с кастомным билдом под наши задачи!
По этому мы пойдем по второму пути - сложному, который делится еще на два.
Назовем их "следовать инструкциям" и "догадаться самим".
Но перед тем как начать - нам нужно собрать кастомный билд с нужными нами модулями и настройками. Что можно спокойно сделать на сайте. Я использовал билд на базе ClassicEditor, выбрал нужные мне модули в том числе и модуль watchdog на будущее (сейчас активировать его в коде я его конечно-же не буду). В общем, качаем билд и распаковываем его в "Новая папка (3)" на рабочке. Он нам понадобится, но несколько позже.
Распакованный архив с кастомным билдом
Следуем инструкциям.
По инструкции на сайте мы берем скачанный билд, копируем его в папку с проектом в директорию public\assets\ckeditor5, импортируем .js файл в blade шаблон и так-же активируем его через EditorWatchdog() или CreateEditor(). Легко и просто, и в принципе все гайды в интернете по интеграции в Laravel пишут нам что так делать правильно.
Но нас то не обманешь! У нас серьезный проект! И ведь действительно, вдруг нам потом понадобится модуль, который мы не добавили в наш билд? Пересобирать заново? Да ну его! По этому мы пойдем своим путем! Поехали!
Догадываемся сами
К нашему счастью в Laravel встроен замечательнейший инструмент под названием Node.js, который мы и будем использовать. И не только его.
Установка
Идем консолькой в папку с нашим проектом и запускаем (не забудь включить vpn)
npm install --save @ckeditor/ckeditor5-build-classic
Установка
Сразу нам устанавливается множество зависимостей, однако наш любимый Laravel 10 этот модуль сразу не увидит. Для того чтобы это исправить нам нужно сделать еще кое-что. Поехали интегрировать.
Интеграция
Нам нужно чтобы наш Laravel увидел CKEditor, да собирал его под наши нужды "на лету". Для этого мы будем использовать vite, благо он идет в составе Laravel. У авторов CKEditor для него есть отдельный модуль с названием @ckeditor/vite-plugin-ckeditor5, его и будем использовать. Также нам понадобится модуль @ckeditor/ckeditor5-theme-lark.
Снова консоль, пишем:
npm install --save @ckeditor/vite-plugin-ckeditor5
npm install --save @ckeditor/ckeditor5-theme-lark
Пока ждем установку - наливаем кофе.
Далее ищем в корне нашего проекта файл vite.config.js и вносим в него следующие строки как показано на скриншоте (слева - старый файл, справа - внесенные изменения)
import { createRequire } from 'node:module';
const require = createRequire( import.meta.url );import ckeditor5 from '@ckeditor/vite-plugin-ckeditor5';
ckeditor5( { theme: require.resolve( '@ckeditor/ckeditor5-theme-lark' ) } ),
vite.config.js
Конфигурируем
Помните ту конфигурацию, что мы делали на сайте CKE? Она то нам сейчас и понадобится.
Открываем Новую папку (3) и в подпапке src находим файл ckeditor.ts. Это файл с настройками нашего билда. Копируем его в папку с проектом, в директорию resources/js.
Также из папки samples копируем style.css в resources/css.
И дополнительно создаем файлик editor.js - его мы и будем встраивать в наш шаблон.
Содержимое editor.js
import ClassicEditor from './ckeditor';
ClassicEditor.Editor
// Note that you do not have to specify the plugin and toolbar configuration — using defaults from the build.
.create( document.querySelector( '#editor' ), {
language: 'ru',
removePlugins: [
'MediaEmbedToolbar'
]
})
.then( editor => {
console.log( 'Editor was initialized', editor );
} )
.catch( error => {
console.error( error.stack );
} );
Для наблюдательных - я специально не использовал watchdog здесь. Импортировать его я планирую потом.
Дальше нам нужно загрузить недостающие модули.
Открываем редактором ckeditor.ts и видим вверху множество импортов.
Открываем папку "node_modules/@ckeditor" внутри проекта и ищем недостающие модули. Если такие есть - устанавливаем их через npm install --save %имя_модуля%.
В дальнейшем есть шанс получить следующую ошибку от TailWindCSS (при запуске npm run dev)
[vite:css] Nested CSS was detected, but CSS nesting has not been configured correctly.Please enable a CSS nesting plugin *before* Tailwind in your configuration.
Она вызывается из-за того, что мы используем кастомный css для ckeditor
Чтобы этого не происходило, нам необходимо в файле postcss.config.js в корне нашего проекта добавить следующие строки:
'postcss-import': {},
'tailwindcss/nesting': {},
как на скриншоте
postcss.config.js
Почти все
Нам осталось только в нашем шаблоне между тегов <head> добавить
@Vite(['resources/js/editor.js', 'resources/css/editor.css'])
И теперь любой <div> или <textarea> с указанием id="editor" будет вызван с редактором CKEditor5.
Запускаем проект через npm run dev и радуемся полученному результату!
Результат
Результат
А Вы заметили пасхалочку на скриншоте?
Все что вам осталось - это настроить дизайн редактора в css файле.
Ну и как-же без ложки дегтя - почему-то конфигурация "language: ru" не срабатывает и редактор остается английским. Возможно не хватает какого-то модуля или же что-то необходимо донастроить. Пока еще не разобрался, но в целом на данном этапе разработки это не сильно критично. Если у Вас есть идеи - welcome!
Update: С языком разобрался. Действительно его необходимо было имортировать. Для этого в ckeditor.ts необходимо вставить строку после импорта { ClassicEditor }
import '@ckeditor/ckeditor5-build-classic/build/translations/ru';
Update2: Также нашел проблему при вставке видео с внешнего источника, например Youtube. В редакторе оно вставляется, а вот в пост не переносится. Лечится доп конфигом там-же в ckeditor.ts в секцию defaultConfig
mediaEmbed: {
previewsInData: true
}
Исправленная локализация
Надеюсь что данный гайд поможет Вам в проектах.
Если вы хотите немного потрындеть, или быть может знаете Laravel или например умеете верстать, ну или просто у вас есть желание как-нибудь помочь с разработкой проекта, милости прошу в мою потрынделку.
В кратце - да, старый добрый, с блекджеком и шлюпками.
Всем спасибо!