Table of Contents
Препятствия, которые вы можете встретить, если решите запустить интернет-магазин на WordPress. Не придирки к мелочам или гипотетические ситуации, но реальные проблемы, с которыми столкнулись знакомые владельцы онлайн-магазинов.
1 Предисловие
Создание онлайн-магазина с помощью WordPress и WooCommerce — задача, на первый взгляд, элементарная. Можно не углубляться в дебри программирования, ибо в руки создателю вкладываются плагины, шаблоны и конструкторы страниц, не требующие написания кода.
Хорошо и то, что WP бесплатен для коммерческого использования. Затраты на инфраструктуру всегда будут, но это сравнительно скромные суммы, особенно если проект небольшой. Достаточно арендовать виртуальный сервер у какого-нибудь хостинга, купить домен, установить и настроить шаблон да пару плагинов — магазин готов.
Но так ли всё радужно? Не совсем.
Легкость создания интернет-магазина не гарантирует, что он будет хорошо работать. Недостатки Вордпресса и его плагинов могут помешать развитию проекта.
Я считаю, что данная комбинация решает только одну задачу бизнеса: быстро запустить продажи небольшого количества товара. Ключевое слово — «быстро». Забудьте про «удобство» и «надёжность» — они не приоритетны.
Проблема в том, что WordPress не для интернет-магазинов. Это система управления контентом для блогов. Представьте себе, что WordPress — это паровоз, а рельсы — это ниша сайтов с постами, новостями и прочим креативным контентом. Такой паровоз, пускай и с дымом, шумом, медленно и неторопливо, но доставит вас на нужную станцию. Можно прицепить вагоны (плагины), покрасить (облагородить крутым шаблоном), использовать качественное топливо (задействовать быстрый сервер), но с рельс данный состав не сойдёт. Он не обзаведётся колесами, не превратится в пароход и уж тем более не взлетит. Понимаете, о чём я?
Несмотря на то, что Вордпресс за уйму лет развития обогатился возможностями, он не научился грамотно решать задачи, не связанные непосредственно с блогами. Для создания стабильно работающих интернет-магазинов на данной платформе придётся отдать денежку. Средства уйдут на платные шаблон и плагины, на труд разработчиков для их доработки и создания интеграций с CRM системами и прочими необходимыми сервисами. Удобную систему заказов, классный дизайн и многие «фишки» крупных маркетплейсов сделать бесплатно не получится. Во сколько выйдет такое, зависит от того, что вам нужно и как далеко требуется выйти за рамки базовых возможностей WooCommerce. Некоторые недоработки WP и WC могут оказаться критичны для тех, у кого ограничен бюджет и (или) нет под рукой грамотных разработчиков с готовыми костылями наработками.
Я не утверждаю, что WP и WC плохи и ими нельзя пользоваться. Наоборот — когда сильно ограничен бюджет, это не такой уж и плохой вариант. В конце концов, данная CMS (система управления контентом, в нашем случае WordPress) позволяет не зависеть от каких-то платформ — это важно, когда от проекта требуется самодостаточность. Идеального решения нет в природе, недостатки встречаются у всех платформ.
Что касается других CMS и платформ для создания интернет-магазинов, то в рамках данной статьи я не буду их упоминать. Они существуют, они кому-то нравятся, но это не тема данной публикации. Сейчас — WordPress и подводные камни его применения.
О безопасности
Если вы пользуетесь только CMS и плагином магазина, а также шаблоном от разработчика, который оперативно реагирует на фидбэк — может быть, проблем с безопасностью сайта у вас не будет. Но, конечно же, такая ситуация утопична. Наверняка у вас будут установлены стопятьсот плагинов непонятно от кого и кое-как работающих. Как в таких условиях можно считать сайт защищённым от взлома — понятия не имею.
Если у вас есть какие-то соображения на тему безопасности — милости прошу в комментарии.
2 Проблемы экосистемы плагинов и шаблонов
Под экосистемой я подразумеваю совокупность продуктов, которые должны работать в симбиозе. У WordPress это сама CMS-ка, плагины и шаблоны для неё (в том числе WooCommerce), дополнительные услуги при приобретении платных плагинов (техподдержка, доработка, исправление багов).
За годы работы у меня сложилось впечатление, что Automattic (владельцы WordPress и WooCommerce) живут в информационном пузыре. В их идеальном мире сайты запущены на быстрых серверах, а любые инновации вроде возможности настроить любой элемент сайта моментально начинают поддерживать все шаблоны.
Реальность такова, что шаблоны и плагины не способны предложить качественное решение задач. А иногда и вовсе не способны сделать хоть что-то. А если у какого-нибудь разработчика зажжется искра энтузиазма и он решит переломить ситуацию в лучшую сторону, создав просто обалденный плагин, который решит все проблемы, про него никто не узнает, ибо раздел «Свежее» в каталоге плагинов давным-давно выпилили.
Те, кто отвечает за развитие платформы, словно не любят сопутствующие продукты, молчаливо решив дождаться момента, когда авторы шаблонов и плагинов растеряют остатки желания работать с WP и отправятся писать расширения для других CMS.
Что же у нас сейчас с экосистемой? Перечислю наболевшее.
- В каталоге WP значительная часть плагинов — огрызки, призванные рекламировать платную версию или требующие интеграции со сторонним сервисом (конечно, не бесплатным). К примеру, поищите плагины сокращения ссылок (short link) — из более-менее актуальных найдёте только интеграции со сторонними сервисами. Остальные — старые плагины, чья совместимость под вопросом. Возможно, вы найдёте с виду классный плагин «wp Short URL», умеющий не только генерировать короткие ссылки, но рисовать график переходов по сокращённым ссылкам, вот только работает он… да никак не работает.
- Нет гарантии стабильности. Любой бесплатный плагин может быть продан авторами и площадка к этому относится равнодушно, несмотря на то, что новая версия может оказаться другим продуктом. Например, популярный в своё время плагин «Google Analytics for WordPress» неожиданно сменил концепцию и переименовался в «MonsterInsights — Google Analytics Dashboard for WordPress». До смены владельца это было самодостаточное решение, для работы требующее только авторизацию учётной записи от Google. Т.е. «на сторону» сведения о посещаемости не утекали. Теперь для работы плагину требуется связь со сторонним сервисом MonsterInsights, что принципиально меняет его суть. Иногда после подобных потерь можно подобрать замену или форкнуть, самостоятельно поддерживая совместимость, но подмена того плагина оказалась заметной, ибо альтернативы не нашлось.
- Нет управления зависимостями. Это не позволяет решить другую проблему — для создания плагинов WP предоставляет мало инструментов. Например, страницу настроек в админке создать просто, но сделать в ней вкладки, выбор цвета, вывести список категорий средствами WP не получится. Нужно изобретать свой велосипед или подключать сторонний компонент. И такой компонент нельзя вынести в каталог плагинов так, чтобы он автоматически предлагался к установке вместе с зависимыми плагинами. Есть костыли, не решающие вопрос качественно. Поэтому, как правило, мы получаем плагины весом в несколько мегабайт, ключевая логика работы которых укладывается в десяток строчек. Такой бедлам замедляет отзывчивость сайта, усложняет отладку и вредит стабильности (компоненты не всегда совместимы или вызывают конфликт версий, когда разными плагинами используется один и тот же компонент).
- Многие плагины написаны без соблюдения стандартов (об этом позже) либо работают так, что мешают другим плагинам. Авторы либо изобретают настолько «оригинальный» велосипед, что он работает только в специфичных условиях, либо допускают ошибки, всплывающие при добавлении других плагинов.
- Накрутка рейтинга в официальном каталоге плагинов никак не решается. А значит, сложно найти плагин от действительно хороших авторов. Например, вышеупомянутый плагин аналитики после обновления получил массу негативного фидбека, который позже перекрылся пятизвёздочными восторженными отзывами пользователей с однотипными профилями.
Плагин WooCommerce, являясь дочерним проектом со сложностью, сравнимой с родителем, приносит дополнительные проблемы. Официальный маркетплейс woocommerce.com от Automattic — весьма неоднозначное место.
В Extensions Store продаются плагины и шаблоны как от авторов WC, так и сторонние решения. Казалось бы, что может пойти не так? Оказалось, что многое. Я встретил три серьёзные проблемы этой площадки, о наличии которых не догадывался, пока не столкнулся сам.
- Качество поддержки плагинов никак не контролируется. Вы можете купить плагин, он будет как-то неправильно работать, вы решите уточнить у техподдержки этот момент (её обещают многие авторы) — но ответ будет не адекватным или вас вовсе проигнорируют. Вы можете возмутиться и обратиться напрямую к Automattic — мол, плагин не работает, поддержка не отвечает, на что получите ответ в духе «мы только размещаем плагины, ничего не гарантируя». Это ненормальная позиция.
- Де-факто работа плагинов гарантируется только в том случае, если установлен WooCommerce и только купленный плагин. Допустим, вы купите два дополнения от разных авторов: виджет-фильтр товаров и компонент для подгрузки товаров в каталоге «на лету» через AJAX. Виджет не будет работать с AJAX, даже не надейтесь. Причины могут быть разные: быть может, автор вместо обычного запроса товаров через документированные функции построил свою абстракцию, не совместимую с AJAX. Или плагин «рисует» и сам каталог с товарами, игнорируя стили шаблона. Естественно, переписывать плагин в угоду другим автор не будет. Его-то плагин работает, проблемы для него нет.
- Нет гарантии совместимости с актуальными версиями Ворпдесса. Да, это так. Есть шанс нарваться на плагин, не работающий с современной версией CMS просто потому что автор забил на обновления.
Интернет-магазины работают с персональными данными, с финансами клиентов, к стабильности и надёжности нужно относиться серьёзно. А вышеупомянутые проблемы наблюдаются не первый год.
Есть ещё кое-что, что меня беспокоит: если сказанное выше относится к официальному маркетплейсу, то что говорить о продуктах разработчиков, которые продаются на их собственных сайтах? Где гарантия того, что какой-нибудь плагин интеграции с сервисом вроде MoySklad не окажется забагованным творением без адекватной поддержки?
Сторонние маркетплейсы не лучше. Требования к коду на сайтах вроде Codecanyon не гарантируют корректную работу плагинов и шаблонов, а лишь отфильтровывают откровенно плохие решения.
Отсюда следует вывод: даже если плагины для решения ваших задач существуют, они могут не работать сообща или не работать вовсе.
Совет
Перед тем, как покупать плагин или шаблон, обратите внимание на дату последнего обновления, чтобы понять актуальность продукта. Затем свяжитесь с его автором. Уточните интересующие вас моменты. Наличие обратной связи — уже хорошо.
3 Отзывчивость сайтов
От отзывчивости сайта зависит благосклонность поисковых систем и клиентов.
Сайты на WordPress получают низкие оценки в тестах вроде PageSpeed и GTMetrix по множеству причин. Каждую надо разбирать отдельно, но я осмелюсь обобщить их, выделив две категории проблем.
- Шаблоны и плагины содержат большое количество скриптов и стилей, которые много весят и мешают быстро загрузить и отрисовать страницу. Чтобы разграничить, когда и что загружать, требуется приложить немало усилий — в WP нет штатных средств для такого вида оптимизации.
- Отзывчивость не на высоте из-за внутреннего устройства CMS. Подробнее об этом — в следующих главах.
Проблемы с фронтендовой, т.е. видимой посетителям и админам стороной сайта, сложно обобщить, потому что число комбинаций шаблонов и плагинов стремится к бесконечности. Однако, так как меня спрашивают об этом чаще всего, я постараюсь хотя бы объяснить, почему с этим всё так сложно.
Это относится ко всей экосистеме WordPress
В статье речь о WC, но в целом проблема медленной отзывчивости касается любых сайтов на WP.
Для начала давайте определимся, что именно загружает браузер, чтобы вы увидели страничку. С точки зрения браузера типичная страничка состоит из:
- кода, написанного на языке разметки HTML,
- ссылок на внешние файлы скриптов, написанных на языке программирование JavaScript,
- файлов стилей CSS,
- изображений в форматах JPEG, PNG, WebP, GIF и других,
- других форматов — видео, WebAssembly кода и прочего.
В HTML содержатся ссылки на остальные виды ресурсов, поэтому браузер должен получить этот контент одним из первых (ещё раньше приходят заголовки HTTP от сервера, но для объяснения это не важно). Однако сейчас HTML — лишь верхушка айсберга. Чтобы разобраться, что требуется для быстрой работы сайта в наше время, когда страницы открываются на разных видах устройств, нужно заглянуть в недалёкое прошлое.
Ещё каких-нибудь 10-15 лет назад страницы были устроены просто: из HTML, нескольких файлов стилей и нескольких скриптов. В HTML прописан текст и ссылки на ресурсы (стили, скрипты, фото и т.п.). Браузер видел структуру страницы в коде, смотрел стили и быстро всё это визуализировал. Добившись быстрой отдачи HTML, мы получали шустро работающий сайт, потому что время на отрисовку конечного вида страницы было мизерным. Можно было даже отказаться от показа графики, подгрузив картинки позже. Так как текстовая часть контента преобладала, достаточно было создать иллюзию быстрой загрузки, первым делом показав текст.
Сейчас многое из того, что мы видим на страницах, формируется не столько за счёт прямой «интерпретации» браузером изначально прилетевшего от сервера кода, сколько благодаря выполнению кода на JavaScript, который добавляет в HTML новые элементы и модифицирует имеющиеся. Страницы как бы строятся на устройстве пользователя. Иногда даже для перехода по страницам не требуется постоянная связь с Интернетом — сайт может загрузиться сразу целиком и работать как приложение. Либо, наоборот, значительная часть инфы запрашивается с сервера поздновато. Размер этих вроде как «вспомогательных» ресурсов оказывается настолько велик, что для того, чтобы пользователь не заскучал во время загрузки, разработчики прибегают к хитростям. Например, на любой странице с видео Youtube.com сначала загружается плеер видео и только потом, когда воспроизведение началось, появляется остальное — кнопки лайков и дизлайков, списки рекомендованных видео и так далее.
Недостаток Вордпресса в том, что на каждой странице найдётся хлам, который и весит много, и может что-то поломать в другом хламе. Даже «нужные» ресурсы не всегда полезны. В шаблоне может задействоваться гигантского размера фреймворк, который, пока будет загружаться ради анимации плавного появления заголовка на посадочной странице, успеет тысячу раз показать и скрыть контент. Сначала загрузится текст, затем CSS-стиль с указанием шрифта, затем сам шрифт, затем последует загрузка стилей для отдельных блоков и так далее. Это всё вызовет большое количество перерисовок контента и замедлит открытие страницы.
Или, как пример, на сайте могут использоваться баннеры в формате GIF. Этот архаичный способ показать анимированную смену кадров негативно влияет на отзывчивость браузера, потому что формат GIF не рассчитан на хранение гигантского количества пикселей, привычного для современных устройств (баннеры 32 на 32 пикселя ведь никому сейчас не интересны, не так ли?).
Что с этим делать? Есть две крайности: можно идти по пути минимализма, не используя универсальные шаблоны и не ставя плагины, которые для работы добавляют стили и скрипты, либо заказывать написание шаблона с нуля, в котором будут прописаны все-все функции, выходящие за рамки WordPress и WooCommerce. Можно выбрать что-то среднее, используя минималистичный шаблон и модифицируя плагины под себя, однако результат будет, скорее всего, не удовлетворительным. Также существуют плагины, отключающие загрузку стилей и скриптов других плагинов на отдельных страницах, но на практике это бесполезно: даже платные плагины могут загружать ресурсы нештатными способами, которые не перехватить без редактирования кода.
Некоторые считают, что сайты можно ускорить с помощью кеширования страниц. Это не панацея. Кеширование — хитрость, которая работает не всегда и не для всех. Да, можно сохранять однажды созданную страницу в кеше, сделав HTML-код статичным ресурсом, а затем показывать посетителям вместо того, чтобы заново генерировать, но первый пользователь всё равно будет ждать генерации страницы. А что, если у каждого пользователя своя страничка? И как сайт поймёт, когда нужно отдавать кешированную версию, а когда генерировать заново? Так как эти вопросы не всегда разрешимы, кеширование не стоит рассматривать как способ ускорения.
Теперь, когда я обозначил, насколько всё печально с фронтендом, двинемся вглубь — к устройству CMS и плагина WooCommerce.
Архитектура базы данных не оптимальна
Если кратко: WordPress не подходит для создания онлайн-магазинов из-за того, как именно данные хранятся и выводятся в виде страничек.
Любая CMS — это программа, которая манипулирует данными. От того, как быстро она справляется с обработкой информации, зависит отзывчивость сайта в браузере.
Как правило, для хранения информации используются базы данных. За исключением фотографий товара, всё остальное хранится в MySQL-совместимой базе данных.
Базы данных — это не какой-то файл вроде экселевской таблички, а сложные системы, заботящиеся о целостности информации, быстром её поиске и пополнении. CMS работает посредником между информацией в базе данных MySQL (представленных в виде таблиц) и фронтендом — тем, что увидит пользователь. Скорость реакции сайта на действия посетителя зависит от эффективности работы всей системы.
WordPress — пример того, как не следует работать с БД. Информация о контенте «раскидана» по таблицам не оптимально, из-за чего доступ к ней требует временных и вычислительных затрат. Инфа, нужная для показа постов, раскидана по множеству таблиц, не позволяя быстро отбирать и сортировать данные. Базе данных приходится метаться от табличке к табличке, отбирая нужные строки, а остальные манипуляции проводит уже CMS, лишний раз нагружая процессор сервера.
Возможно, вы никогда не погружались в дебри проектирования баз данных, поэтому постараюсь как можно доходчивее показать на практическом примере, в чём проблема.
Самый простой способ хранить информацию о каком-либо объекте — засунуть всё в одну таблицу, храня свойства объекта (заголовок, текст, дату публикации и т.п.) в ячейках, разделённым колонками. На скриншоте ниже вы увидите таблицу wp_posts Вордпресса с колонками, где каждый пост — это отдельная строка.
Текст, заголовок, дата публикации, статус поста хранятся в таблице wp_posts в виде строк. Благодаря этому мы можем в одном запросе «попросить» базу данных отобрать строки, где статус — «Опубликован», а дата — не больше последних пяти дней, и отсортировать всё это так, чтобы на первом месте был самый свежий пост. Эта «просьба» укладывается в один примитивный запрос. База данных легко и быстро его выполнит, потому что инфа будет у неё под виртуальным носом. И мы получим всю инфу очень-очень быстро, потому что вот она — в этой самой строке, разделённой колонками.
Однако этого достаточно только примитивным блогам, потому что с ростом сложности новости начинают разбегаться по рубрикам, публикуются разными авторами. Нет никакого смысла хранить в каждой строке поста одну и ту же инфу об авторе — его имени, логине, пароле для входа на сайт. Можно завести табличку wp_users, где каждая строка — это информация об авторе, и назначить автору уникальный номер (ID). Затем в таблице wp_posts достаточно завести колонку post_author и прописывать там только ID автора — и всё, инфа не будет дублироваться.
Связь через ID может работать на уровне базы данных. Тогда при удалении аккаунта автора в таблице авторов его посты также удалятся автоматически. Через такую связь обеспечивается целостность — не будет постов-сирот, чьи ID принадлежат удалённым авторам. В Вордпрессе такого вида связи не используются, это не обязательно и в целом нормально. Но что ненормально, так это то, что разработчики WP решили хранить связанную информацию (т.н. «метаполя») объектов в отдельных таблицах не в виде строк со связью через ID, а через значения ячеек в отдельных строках. Другими словами, вместо того, чтобы завести для авторов колонку «nickname» в таблице, чтобы при получении строчки сразу получить и ник, и другую инфу, в этой CMS существует таблицы wp_usermeta, где базе данных нужно найти строку, в которой в колонке user_id будет ID пользователя, а в колонке meta_key будет название инфы — nickname. Только найдя эту строку, в колонке meta_value можно увидеть искомый результат — ник юзера. Такой подход привёл к тому, что базе данных для сбора информации о чём-либо нужно заниматься поиском и группировкой значений из разных таблиц.
На практике это выглядит так: для привязки информации вроде счётчика просмотров в таблице wp_postmeta хранятся ID поста, имя метаполя, значение метаполя. Таких полей — десятки и сотни на каждый пост. Получается, что таблица wp_postmeta всегда в разы больше, чем wp_posts. Это уже сама по себе проблема, ибо чем «раздутее» таблица, тем медленнее поиск по ней. Но ситуация ухудшается тем, что поиск значения в строках всегда дольше, чем отбор инфы в колонках. Из-за разнесения инфы по таблицам сайт может тормозить тогда, когда этого не ожидаешь. Например, неожиданно замедлится открытие каталога онлайн-магазина при добавлении виджета вывода списка популярных товаров. Вроде бы пустяковая функция требует чересчур много действий.
Чтобы вы поняли, насколько эта проблема актуальна и смехотворна по сути, вот список шагов, на которые можно условно разбить элементарнейшую задачу — показ десяти наиболее просматриваемых товаров за всё время существования сайта:
- Отбираются все товары из таблицы wp_posts по статусу, чтобы исключить черновики и удалённые записи. Выбор отрабатывается быстро, потому что тип поста и его статус хранятся в колонках post_status и post_type, да ещё и индекс под рукой. Результатом будет список ID — уникальных номеров товаров.
- Ищутся записи в таблице wp_postmeta по списку ID из предыдущего шага. Базе данных нужно просмотреть всю таблицу wp_postmeta, отбирая те строки, в колонке post_id которых — уникальный номер из гигантского массива ID товаров и одновременно где в колонке meta_key — значение «post_count» (или как там назовет метаполе плагин счётчика). Двойной отбор по двум значениям колонок — это уже относительно долгая операция, именно из-за таких запросов подтормаживают фильтры товаров в магазинах.
- Затем либо на уровне БД, либо в WP все найденные значения счётчиков сортируются по количеству просмотров. Не настолько ресурсоёмкая задача, как предыдущая (можно сказать, элементарная), но время тоже займёт. Особенно если товаров сотни тысяч.
- Из результата сортировки отбирается требуемое количество (десяток) записей.
- CMS снова обращается к таблице wp_posts для взятия названия, цены и прочего контента. Это быстро, потому что обращение происходит с небольшим по количеству списком ID — база данных быстро найдёт искомое.
Что здесь не так? Пожалуй, всё. Если бы в WordPress был удобный способ добавить колонку сразу в wp_posts, все шаги были бы не нужны — достаточно попросить БД отсортировать таблицу по значению в колонке посещаемости и выдать данные с лимитом в 10 строк. Там был бы сразу весь контент первых десяти записей.
Складирование всех свойств поста отдельными записями в wp_postmeta — ужасное архитектурное решение даже для хранения контента для блогов. На каждую запись приходится десяток, а то и больше, метаполей, из-за чего wp_postmeta «раздувается» в разы быстрее, чем wp_posts.
А ведь у нас есть *meta таблицы и для пользователей, и для товаров, и даже для категорий! Учитывая, что скорость получения данных от БД замедляется с ростом количества контента на сайте, концепция использования таблиц с метаполями выглядит вредным решением.
WooCommerce не улучшает концепцию. Недостатки платформы никуда не делись, а усугубились, потому что у товаров много атрибутов, по которым нужно отбирать инфу. Даже простой магазин обуви будет иметь несколько видов свойств товара. Фильтрация может проводиться по виду (кеды, сандалии, бутсы и т.п.), свойствам (размеру, цвету), а также подходящему сезону, бренду и уйме других параметров. Грамотно содержать это в предлагаемой WooCommerce архитектуре невозможно.
Вместо того, чтобы сделать удобный конструктор сущностей, чтобы для каждого свойства создавались отдельные таблицы или колонки, WooCommerce предлагает аж три вида таксономий и свойств. Удивительно, что ни один из них не оптимален.
- Атрибуты (их названия и значения хранятся в таблицах wp_woocommerce_attribute_taxonomies и wp_terms). Фильтрация в каталоге по атрибутам потребует довольно сложных запросов к БД, что негативно скажется на отзывчивости сайта.
- Категории (wp_terms). Отбор по таким таксономиям в целом довольно быстр, но есть шанс, что что-то работать не будет. Например, поиск. Причина в том, что авторы шаблонов и плагинов оригинальничают и по-разному используют возможность создавать таксономии.
- Метаинформация (wp_postmeta). Я писал выше, почему этот вариант — худший.
В идеале свойства, характеризующие товар, должны хранится в одной таблице, где каждая колонка — это свойство. В таком случае фильтры будут работать быстро — можно одним запросом перечислить имена нужных свойств, база данных запросом к одной таблице за один раз «пробежится» по строкам и выдаст отфильтрованный список товаров.
Сейчас авторы WooCommerce только начали думать о том, как решить проблемы отзывчивости. С версии 6.0 появилась штатная возможность использовать дополнительную таблицу с «правильной» организацией данных, чтобы хоть как-то ускорить фильтрацию товаров по атрибутам на страницах каталогов. Поэтому «лучшим» способом хранения свойств товаров можно считать именно атрибуты.
К сожалению, даже если WC начнёт хранить инфу оптимально, ситуацию с отзывчивостью это решит не то чтобы полноценно. Авторы шаблонов и плагинов не знают или не хотят думать о совместимости и правильной архитектуре, поэтому каждый лепит свои костыли, иногда довольно вредные. Например, в популярном платном шаблоне XStore (стоит 39$, купили ~26 тысяч раз на момент написания этого текста) есть «фича» в виде указания бренда для товара. Автор решил сделать брендом пользовательскую таксономию (как будто это категория, но отдельного типа). Такое решение вызвало следующие негативные последствия:
- Для фильтрации по бренду необходимо пользоваться только встроенным в шаблон виджетом.
- Другие плагины виджетов (и не только) не видят бренды у товаров.
- Сломался поиск: если в SKU товара содержится название бренда, поиск по таким товарам не работает.
- Заметно замедляется открытие страниц каталогов, где есть виджет отбора по бренду. Напомню — сторонние виджеты использовать не получится.
Можно ли пользоваться таким шаблоном? Я считаю, что нет. Но он весьма популярен.
Даже если использовать атрибуты строго по документации, они пригодятся далеко не для всего. У товаров есть и косвенные признаки, по которым можно вести сортировку и (или) отбор: количество продаж и добавлений в списки желаемого, наличие скидок, участие в акциях и так далее. В рамках, предлагаемых платформой, нет возможности работать с такими данными быстро.
Решения нет.
Маловероятно, что структуру базы данных WordPress «починят» когда-нибудь. Это та проблема, с которой стоит смириться, если вы решили использовать WooCommerce для создания магазинов.
Кеширование не эффективно
Вспомните предыдущий пример с получением популярных постов. Может ли такое быть, что WordPress, принуждая своей архитектурой таблиц перелопачивать большие объёмы данных, не переиспользует промежуточные результаты запросов к БД? Но это действительно так. В WP нет нормального кеширования.
Прежде чем объяснять, что не так с кешированием в WP, я попытаюсь объяснить, что подразумеваю под «нормальным кешированием», на примере запроса информации о товаре.
Чтобы показать товар на сайте, недостаточно собрать инфу из табличек о том, как называется продукт и какое у него описание. Как минимум цена требует обработки: сайту нужно посмотреть, вводил ли покупатель промокод, приобретал ли какие-то товары ранее, чтобы вычислить — нужно ли применять индивидуальную скидку, отбирать какие-то товары по свойствам (если потенциальный клиент что-то выставил в фильтре товаров или открыл рубрику). Большинство описанных действий — однотипные. Большинство посетителей видят одно и то же. Таким образом, нормальное кеширование — когда все промежуточные результаты переиспользуются, чтобы движок сайта не проводил обработку данных повторно. Тогда странички будут загружаться быстро.
В WordPress из коробки кешируются результаты запросов к метаполям, но этого недостаточно, потому что кеш хранится в рамках одной генерации страницы, и то нужны специфичные условия. То есть если на странице выводится в нескольких местах размер обуви — скорее всего, запрос к базе данных был один, все последующие появления взяты из кеша. Но стоит кликнуть посетителю по любой ссылке или обновить страницу — и это уже новая генерация, старый кеш не используется.
Я считаю, что такой кеш бесполезен. Он не решает проблему тормозов каталога товаров при нашествии толпы посетителей во время рекламных кампаний, ибо для каждого посетителя с нуля строится свой экземпляр страницы.
Фильтрация товаров — наглядный пример того, какие в WooCommerce трудности с переиспользованием данных.
Виджеты с фильтрами — убийцы производительности.
Эти удобные списки и ползунки, позволяющие отобрать интересующий товар, замедляют открытие страниц и (или) заставляют сайт WordPress сильнее нагружать ЦП сервера.
А ведь без фильтров подчас невозможно обойтись.
WC так устроен, что каждый виджет добавляет к списку запросов к базе данных неконтролируемое число новых запросов.
Каждый виджет, каждый список постов — это автономная цепочка запросов, слабо связанная с другими цепочками.
Другими словами, к списку действий того, что должен сделать сайт для показа страницы (отобрать товары выбранной категории, узнать их название и цены и т.д.), добавляются очереди из не менее «тяжелых» новых запросов. Их количество сложно посчитать, потому что виджеты работают по-разному, но у них есть общий признак: они выполняются независимо от запроса на получение содержимого каталога и независимо друг от друга. Переиспользовать результаты, полученные одним фильтром, в другом, как правило, нельзя.
Любой виджет может заметно снизить скорость открытия страниц или вызвать нагрузку на процессор сервера.
К примеру, чем так плох для производительности ползунок цены?
В WooCommerce нигде не хранится инфа о максимальной и минимальной ценах выбранной категории, она даже не кешируется. Поэтому при каждом показе страницы нужно проделать несколько манипуляций, чтобы вывести всего лишь два числа:
1. Запросить список всех доступных для продажи товаров без деления на страницы. То есть для фильтра уже выполняется один запрос, который тяжелее того, что используется для показа нескольких товаров на странице.
2. Запросить цены у каждого товара. Это тоже не быстро, потому что цены — это записи в отдельной таблице wp_postmeta. На каждый товар делается несколько запросов только для того, чтобы узнать цену — это плохо.
3. Отсортировать по цене, взять наименьшую и наибольшую. Чаще всего сортировка выполняется сразу в запросах к БД, но с точки зрения логики сайта это не совсем правильно, ибо цены бывают разные — со скидкой, регулярная, зависимая от текущего пользователя. То есть для корректного показа нужно убедиться в их актуальности, требуется дополнительная обработка результатов запроса.
И это самый простой вариант, когда не учитывается применение других фильтров. А что делать, если на сайте товары делятся не по категориям, а по свойствам? Тогда нельзя «подсмотреть» открытую таксономию и учесть её в запросе, придётся обрабатывать ещё больше данных. Почему в WP нет средств конструирования и переиспользования запросов — загадка.
Замедляют открытие страниц и счётчики товаров, ибо в некоторых реализациях виджетов даже такие данные не кешируются.
Я часто встречал проблемные счётчики в динамических фильтрах, фильтрующих не по таксономии, а по свойству товара — размеру обуви, цвету одежды, марке оборудования. Запросы из-за того, что используют несколько таблиц и перекрестных условий, выполняются неожиданно (для владельца магазина) долго. А если где-то засунута строка поиска — пиши пропало, сайт начнёт «помирать» от нагрузки на сервер, потому что на страницах результатов поиска, куда пойдут посетители, тоже есть виджеты, и они там работают не со всеми товарами, а с найденными, т.е. к обычным запросам добавятся поисковые.
Проблема виджетов актуальна, потому что проблемы начинаются уже при нескольких тысячах товаров. Это, в общем-то, печально. Медленные запросы видны через плагин Query Monitor, но полноценного решения проблемы не существует. Виджеты — это отдельные сущности, их запросы к БД нельзя объединить и переиспользовать просто потому, что нет инструмента для этого на уровне ядра CMS.
Решение (частичное) есть.
Кеширование страниц — не панацея. WP Super Cache, WP-Rocket и другие плагины кеширования неплохо помогают на новостных сайтах, но бесполезны и даже вредны при работе с интернет-магазинами. Дело в том, что они сохраняют конечный результат работы движка сайта — результирующую HTML страницу. Но ведь цены и количество товара постоянно меняются! В результате клиент увидит не то, что в действительности есть на складах, получит ошибку или другой ценник при попытке добавить товар в корзину и уйдёт, недовольный.
Единственное, что можно сделать быстро, без доработки всех виджетов и шаблонов — убрать ограничение объектного кеша на работу в рамках одной генерации страницы. Бесплатный плагин Redis Object Cache от Till Krüss, как и большинство других подобных, меняет поведение объектного кеширования в сторону переиспользуемости. Уже одного этого иногда хватает, чтобы улучшить отзывчивость сайта. Применять стоит с осторожностью: как и при кешировании страниц, актуальность информации на сайте может пострадать.
Другие причины, мешающие сайту работать быстро
Если вы решили, что проблема только в скорости отбора данных, то у меня плохие новости. Есть четыре других недостатка, которые стоит упомянуть, решения которых нет.
Все посты, страницы и другие типы контента свалены в одни и те же таблицы. Об этом я уже упоминал в начале, но есть и другая неприятность — невозможность исправить данную проблему самостоятельно. Вместо того, чтобы позволить на уровне ядра указывать, в каких таблицах хранить записи, CMS предлагает хранить всё в одной кубышке. Собственные костыли, чтобы хоть как-то ускорить работу, слабо помогают — такие решения ограничены собственными типами контента, для интернет-магазинов такое малоприменимо (там уже есть контент предустановленных типов «товар», «заказы»- другие типы не нужны).
Запись данных в БД замедляет сайт. Чем больше покупок в секунду, тем медленнее будут открываться страницы у всех пользователей. Дело в том, что во время записи таблица, в которой добавляются или меняются строки, блокируется. Возникает очередь из запросов, когда совершаются одновременные покупки. После добавления данных происходит переиндексация таблицы для быстрого поиска по ней. Все эти операции нагружают сервер. Это как бы очевидный момент, так работают все MySQL-совместимые БД, но почему авторы WooCommerce это не учли? Почему они не делят таблицы для записей о заказах по месяцам, чтобы старые покупки не влияли на новые? То есть буквально все покупатели магазина на WooCommerce становятся в очередь. Чем больше покупок — тем выше нагрузка на сервер, тем медленнее сайт. Эффект, между прочим, накопительный.
AJAX-запросы — медленные. Это такие запросы, которые выполняются дополнительно к тому запросу, в ответе которого браузер получает код HTML странички. Через AJAX отправляется и подгружается информация, не требующая полной перезагрузки страницы. Когда в админке вы редактируете пост, текст сохраняется как раз через AJAX запросы. Некоторые шаблоны, превращающие сайт в веб-приложение, работают как раз через такого вида связь. Проблема Вордпресса в том, что ему всё равно, какого вида запрос, ресурсы требуются примерно одинаковые.
В WP не используются связи между таблицами. Это полезная особенность БД, помогающая сохранять целостность при удалении и пополнении данными. Не то чтобы отсутствие связей было чем-то критичным, но это бы решило проблему «записей-сирот» в wp_postmeta после удаления постов.
4 Нюансы разработки
Итак, из предыдущей главы понятно, что данные в БД хранятся как-то не так. Авторы шаблонов тоже допускают просчёты, добавляя отсебятину в виджетах-фильтров товаров. Разработчики WordPress и WooCommerce ситуацию не улучшают. Что можно с этим делать самому? И почему сторонние разработчики не могут сделать «как надо» свои плагины?
На мой взгляд, одним из ключевых моментов, мешающих Вордпрессу стать современной гибкой платформой для создания сайтов любого типа, является то, что WP — не фреймворк и никогда им не будет.
WordPress — не фреймворк, а готовый продукт
Данная CMS — не универсальный инструмент для создания оптимально работающих сайтов. Это система управления контентом для блогов.
Следует понимать, что WordPress — это не фреймворк, а CMS для блогов с основой, заложенной более 15 лет назад. Набор решений, который она предлагает, недостаточно стандартизирован и не соответствует современным реалиям. Нет системы зависимостей, нет хаба с решениями. Разработчикам плагинов приходится пользоваться своими велосипедами без оглядки на коллег, чтобы выполнять элементарные операции (вроде того же кеширования).
Возможности языка PHP растут день ото дня, поэтому ничто не мешает создать на основе WP любой продукт — магазин, тикет-систему, видеохостинг и так далее. Вопрос только в том, какой ценой это будет достигнуто.
Как я писал выше, в экосистеме WP не всё в порядке с зависимостями. Разработчикам плагинов приходится выбирать между двух стульев: или с нуля изобретать велосипеды, или засовывать внутрь плагина готовые компоненты, а после следить за их обновлением и совместимостью. И то, и другое трудоёмко, поэтому логично, что за свой труд разработчики хотят денег и рассматривают каталоги плагинов и шаблонов как место для продвижения платных версий своих проектов.
Есть мини-фреймворки, облегчающие разработку, вроде Piklist и Gantry 5 Framework, но они, скажем так, не сравнятся с возможностями Laravel, Yii и других PHP-фреймворков.
Когда разработка идёт на заказ, можно внутрь WordPress можно засунуть какой-нибудь сторонний PHP-фреймворк, позволяющий упросить разработку. Laravel, например, или Symfony. Но зачем тогда там WP? По сути получится сайт внутри сайта. Я знаю только о двух проектах, где использование такой химеры оправдано: сайте с функцией поиска, где записи должны храниться как можно более компактно, и крупном контентном сайте — посты, засунутые в отдельную табличку вне wp_posts, открываются очень быстро. Eloquent позволил на порядок упростить работу с большим массивом данных, и в то же время сохранить возможность быстро конструировать страницы с помощью Elementor.
Тут нужно понимать, что фреймворк внутри CMS — это не один костыль, а склад костылей. Концептуально WP — не фреймворк и работает иначе, её создатели очень своеобразно отнеслись к поддержке возможностей объектно-ориентированного программирования. Это тоже важный момент, поэтому расскажу детали.
Согласно классическому определению ООП — это метод разработки, при котором в программе прописываются объекты и методы взаимодействия с ними. Это интуитивно понятный подход к решению задач.
Три ключевых особенности ОПП:
- Наследование. На основе любого объекта можно создать его потомка, который наследует все свойства и методы родителя.
- Полиморфизм. Всё можно переопределять при наследовании. К примеру, на основе объекта «начальник» можно создать объект «заместитель», изменив наследуемое свойство «должность». Остальные свойства (по велению разработчика) останутся теми же.
- Инкапсуляция. Последняя в списке, но не по важности особенность ОПП — возможность разграничивать доступ к свойствам и методам. Например, объекту «охранник» не обязательно иметь доступ к функциям объекта «касса», а вот у «кассира» доступ есть. При этом и «охранник», и «кассир» — потомки родителя «сотрудник».
Объектно-ориентирование программирование сильно экономит время разработки, позволяя переиспользовать части кода, а значит — снижая стоимость и время разработки. Да и в голове разработчику не приходится держать все свойства всех сущностей, достаточно лишь понимать их иерархию.
У подхода есть и недостатки. Выделю две проблемы, с которыми можно часто столкнуться — риск потери стабильности и сложности тестирования. Если разработчик исходного объекта внесёт изменения, то нововведения автоматически наследуются дочерними объектами, что может привести к непредсказуемому результату. То есть нужно потратить время на продумывание грамотной архитектуры и затем строго следовать ей. Тестировать такое довольно сложно, ибо нужно покрыть все возможные варианты использования дочернего объекта, чтобы потом обнаружить отличающийся результат.
Есть ли в Вордпрессе ООП? И да, и нет.
Плагины для WordPress можно разрабатывать в объектно-ориентированном стиле с момента, когда такая возможность появилась в языке PHP. И в самой CMS с каждой новой версией всё больше компонентов переписываются с учётом ООП. Виджеты, например, можно (и нужно) создавать с помощью наследования класса WP_Widget, что экономит время, потому что не нужно с нуля прописывать взаимодействие виджета с админкой. Есть классы WP_Post для создания собственных типов постов, есть WP_Query для создания запросов к этим самым постам, есть несколько других классов для создания своих объектов через наследование. Всё полезно, но такого слишком мало. На момент написания данной статьи WP мало что предоставляет разработчикам, предпочитающим такой подход.
Существует мнение, что в WP используется разновидность ООП другого типа,который придумал Алан Кэй в начале семидесятых, воплотив в языке программирования Smalltalk: во главу угла ставится возможность «общения» независимых компонентов через хуки (события). Это позволяет модулям (плагинам в терминологии CMS) как бы пропускать данные по цепочке, попутно вызывая события в других модулях. В теории это позволяет независимым командам разработчиков создавать компоненты, легко тестировать их, а затем собирать всё в одном продукте. К сожалению, на практике мы получили несовместимые, криво написанные плагины, где ошибка в одном из них может вызвать сбои на всех страницах сайта. В этом я вижу вину тех, кто за столько лет развития WP не смог сделать нормальный обработчик ошибок, но авторы плагинов также проявили просто феерический пофигизм.
Чтобы показать абсурдность ситуации, достаточно рассказать про стандарты, которые как бы есть, но многим на них плевать.
Дабы разработчикам было легко ориентироваться в чужом проекте (плагине, шаблоне) и не допускать глупые ошибки, создатели CMS предлагают стандарты кодирования WordPress. В них прописаны рекомендации оформления кода: какие кавычки использовать, где ставить отступы и так далее. Однако девелоперы, как правильно, ленятся им следовать. Код расширений для WP, как правило, сложен для понимания, насыщен костылями и ошибками. Даже в платных плагинах из-за наплевательского отношения к стандартам встречаются такие очевидные элементарные ошибки, как обращение к несуществующим переменным. Например, я долгое время переписывался с автором плагина PrivateContent (популярного средства для создания сайтов, предоставляющих что-либо по платной подписке), разбирая каждую его ошибку такого рода, чтобы убедить в том, что если он приведёт свой код к стандарту, то большинство проблем исчезнет, а остальное станут видны как на ладони. Получилось, но не совсем.
И так у многих разработчиков плагинов. Кто-то игнорирует определённый вид ошибок, кто-то использует для самообмана «особенный дебаггер», создающий впечатление, что с кодом всё в порядке, хотя на деле это не так.
Шаблоны — тоже больная тема. Из-за отсутствия стандартизации каждый шаблон — это вещь в себе. Добавили красивый слайдер и привлекающие внимание кнопки? При переключении на другой шаблон они испарятся со страниц, ибо разметка у них уникальная, не совместимая ни с чем.
Таких моментов можно привести много, это тема нескольких статей. Одну я написал ранее: «Недостатки WordPress и как их обойти». Ниже я расскажу о «нюансах» плагина WooCommerce, которые следует принимать в расчёт при планировании и эксплуатации интернет-магазина.
Товары и торговые предложения
Это нужно знать, если планируете сделать витрину с автоматически регулируемым ассортиментом.
В WooCommerce товар — это объект, обладающий описанием и ценой. Другими словами: товар является торговым предложением покупателю и он же упоминается в заказе. Когда складской софт выгружает все SKU как есть, на сайте в качестве торгового предложения появляется или обновляется пост с типом «простой товар». Это подходит только начинающим магазинам, чьи SKU могут продаваться напрямую без изменений.
Неразрывная связь товара и торгового предложения — проблема, с которой столкнётся любой предприниматель, решивший использовать промоакции или выставить на продажу однотипный товар. Простого типа товаров недостаточно, потому что нередки ситуации, когда товар со склада должен выглядеть на сайте как несколько торговых предложений с разной ценой. Например, саундбар и телевизор могут продаваться одновременно как отдельные устройства для домашнего кинотеатра, так и в составе акционного товара «ТВ с саундбаром в подарок». В складском софте саундбар будет заведён как отдельный товар с количеством, которое должно уменьшаться при любой покупке саундбара — с ТВ или без.
Для решения этой проблемы в WooCommerce предлагается групповой тип товара. Упрощённо говоря, это товар, к которому привязано несколько дочерних товаров. Эти дочерние товары можно продавать отдельно, тогда при нулевом остатке одного из них родительский товар («ТВ+саундбар») станет недоступен.
Группы товаров — это костыль, с которым неудобно работать, потому что CRM системы, как правило, при синхронизации остатков понятия не имеют, как там товар на сайте сгруппирован. Они работают с товарами с определённым SKU, сгруппированного товара для них не существует. Это логично, ибо такой объект по сути виртуальный, его нет на складе. Поэтому это работает только в самых примитивных случаях: в рамках промоакций, когда можно создать вручную такой товар и назначить ему правила ценообразования (с помощью сторонних плагинов). В зависимости от шаблона, товар даже не будет выглядеть как единый товар с отдельной картинкой — придётся дорабатывать.
Альтернативой групповым товарам выступают вариации. Если магазин продаёт, допустим, мебель, оная может состоять из различных материалов: дуба, сосны и так далее. Для этого WooCommerce предлагает использовать вариативный тип товара.
Вариации — это товар с наборов свойств (вариаций). Данный тип решает проблему продажи схожих товаров, потому что можно создать одну страницу с переключателями, позволяющими выбрать нужную модель из линейки. Выглядит на первый взгляд удобно: можно создать разные конфигурации, выставив различные цены, габариты и так далее. И это даже будет работать.
Однако есть нюанс: все вариации — это один главный товар в админке и много связанных «недотоваров». Их количество на складе и другие свойства хранятся не так, как у простого товара, это добавляет сложности при синхронизации.
У вариаций есть свой артикул, запутаться при ручной правке остатков нельзя, однако при автоматизации сего процесса нужно проверять, привязано ли SKU к простому товару или свойствам вариативного. Когда складских позиций десятки тысяч, синхронизация с сайтом из-за лишних рысканий по базе данных затягивается. Да и авторы плагинов интеграции, как правило, не покрывают все возможные способы взаимодействия с таким товаром, из-за чего могут плодиться лишние фотографии в медиагалерее, создаваться «лишние» вариации, дублирующие имеющиеся и не учитываться остаток имеющихся.
При продаже процедура отправки статистики в системы аналитики также должна учитывать вариативность, что происходит далеко не всегда (поддерживают не все плагины). Случается, что в системах аналитики видно продажи одного товара, когда на деле его вариаций несколько.
Со свойствами товаров тоже связана одна «особенность», которая может попортить вам нервы. Из-за того, что торговое предложение и товар — одно и то же, свойства товара, связанные с ценами и доступностью на складе, могут сбрасываться при синхронизации со складским ПО. Иными словами, если вам захочется устроить предзаказы по сниженной цене и вы вручную разрешите в свойствах товара предварительный заказ и цену, то нужно удостовериться, что синхронизация не сбивает эти свойства. Они, скорее всего, собьются, потому что разработчики интеграций не предусматривают сохранение вручную изменённых свойств. Да и в WC нет готового способа учитывать такое.
Я не знаю, чем руководствовались разработчики WooCommerce, когда продумывали архитектуру своего творения. Возможно, они не замахивались на лавры самой популярной платформы и решали задачу продажи товаров без группировки и вариаций. Простого решения здесь нет, остаётся лишь понимать ограничения и подстраиваться под них, либо лепить свои костыли поверх костылей платформы и имеющихся интеграций.
Поиск и скрытие недоступных товаров
Я упомянул вариации в предыдущем пункте. С их существованием связаны нюансы, очень меня удивляющие, ибо они игнорируют концепцию «всё есть пост».
Интернет-магазинам важно не допустить продажу отсутствующих товаров. Для работающих в правовом поле России данное условие критично, потому что сделку купли-продажи продавец не может разорвать в одностороннем порядке по причине отсутствия товара (только если это не крупная сеть вроде Эльдорадо-Мвидео, которые могут пойти на нарушение закона — наверное, в расчёте на то, что защищают свои права не все покупатели). Поэтому от WooCommerce подсознательно ожидаешь, что хоть этому моменту будет уделено должное внимание. Увы, это не так.
Проверить, доступен ли для продажи отдельный товар, предельно просто. Для этого WooCommerce предоставляет функцию is_purchasable(). При её вызове сайт проверит несколько свойств: какой статус у товара, включено ли управление остатками в магазине и у товара, какое количество доступно (если управление остатками включено). Поэтому предотвратить продажу отсутствующих товаров довольно легко.
Но что, если нужно спрятать отсутствующий товар со страниц магазина? Удивительно, но сколь-нибудь грамотного способа скрытия недоступного для продажи товара в результатах поиска и на страницах каталоге не существует.
Я просмотрел десятки плагинов и шаблонов, где возможность скрытия заявлена, и нигде не нашёл полноценной реализации. Либо не учитываются вариативные и групповые товары, либо показ страниц требует больших вычислительных ресурсов сервера. Да и есть ли оно, это решение, если архитектурно это невозможно?
Как правило, и для результатов поиска, и для показа каталога сайт выполняет запрос к базе данных через PHP класс WP_Query, который ничего не знает о проверке доступности для продажи. WP_Query — это такая абстракция в движке Вордпресса, которая позволяет указать, какой тип поста нужно запрашивать и с какими свойствами, а затем возвращает список запрошенного с учётом пагинации (т.е. можно запросить 10 постов для пятой страницы блога и получить заданное количество с требуемым сдвигом, если такие посты существуют). Для поиска товаров запрашивается получение постов с типом «product», со статусом «Published» и с частичным совпадением наименования (если поиск-то идёт по названию). В каталоге учитываются другие свойства — категория, цена и т.п. Вызывать is_purchasable() можно только после получения списка товаров, здесь данная функция не поможет.
Заранее «попросить» WP_Query проверять доступность товара невозможно, потому что доступность — это совокупность свойств, не всегда связанных напрямую с товарами. Обходного пути не существует. Если на странице результатов поиска до появления этих самых результатов вызвать поочередно is_purchasable() по списку, то это приведёт к сильной нагрузке на сервер и заметной задержке при открытии страницы.
«Почему Дмитрий так об этом распинается?» — можете подумать вы. Причина в том, что описанное ограничение мешает создать быстро работающий каталог товаров с фильтрацией по статусу доступности заказа, в том числе предзаказа. Помните, я писал выше, что виджеты с посчётом количества товара и слайдеры выбора цены сильно нагружают сервер? Так вот, они тоже не учитывают реальную доступность товара из-за недостатка, описанного выше. Технически, создать можно и виджеты, и страницы каталогов с актуальными товарами, но какой ценой! Из-за большого количества запросов к БД интернет-магазин не сможет держать наплыв посетителей, на недорогом хостинге выдавая 503-ю ошибку при нехватке ресурсов.
Корзина
Если вы планируете маркетинговые акции с возвратом покупателей, следует заранее позаботиться о сохранении брошенных корзин. По умолчанию их содержимое хоть и хранится в БД, но привязывается не к пользователю, а к сессии браузера. Это логично, ибо не все посетители — пользователи, но иногда хочется большей определённости.
В каталоге WP я не нашёл бесплатного плагина, решающего все возможные задачи сразу, поэтому ищите отвечающий вашим целям сами: cart abandonment. Как правило, такие плагины привязывают содержимое корзины к электронному адресу либо анализируют и хранят IP посетителей. Это не всегда этично, поэтому можно пойти другим путём и анализировать только таблицу wp_woocommerce_sessions для составления внутренней статистики. К сожалению, я не нашёл решение, которое показывает статистику добавления товаров в корзины без обращения к внешним сервисам, поэтому вопрос остаётся открытым.
Я могу понять, почему никто так и не сделал бесплатный плагин аналитики брошенных корзин для WooCommerce. Ведь необходимо не просто прикрутить счётчик добавлений и привязать его ко времени, но и отображать в админке график с актуальными данными. Учитывая, как неуклюже хранится информация в базе данных CMS и насколько муторно создавать страницы админки с графиками, это не самая простая задача.
Оформление заказа
Покупка в WooCommerce — череда действий, всегда требующая допиливания, ибо в исходном виде процесс заказа способен достать покупателя так, что он забросит попытки приобрести желанный товар. Нужно учитывать такие моменты и улучшать пользовательский опыт.
Давайте разберёмся, какой путь проходят товары до конечной точки в виде выполненного заказа и что такое вообще за зверь такой — заказ в WC.
Заказ — это запись, хранящая в себе:
- статус,
- список купленных товаров,
- электронный и почтовый адреса покупателя,
- адрес, способ и стоимость доставки,
- стоимость всего заказа, в том числе скидки,
- способ оплаты.
Через админку эти свойства можно увидеть, но не все — отредактировать.
После создания заказ проходит несколько стадий, которые видно по изменяющемуся статусу:
- В ожидании оплаты (Pending) — заказ создан, сайт ожидает ответа от платёжного шлюза об успешной оплате.
- Не удался (Failed) — платёжный шлюз отправил сообщение сайту о неудачной попытке оплаты либо сайт не дождался ответа о статусе оплаты. Время ожидания зависит от плагина платёжного шлюза.
- Обработка (Processing) — платёжный шлюз сообщил об успешной оплате заказа. Не всегда ставится автоматически. Например, при оплате банковским переводом скорее всего менеджеру придётся менять статус вручную, ибо у сайта прямой связи с банком не будет.
- На удержании (On-hold) — что-то в заказе изменилось, сайт по-прежнему ждёт оплату.
- Отменён (Cancelled) — заказ отменён менеджером или заказчиком (если во фронтенде доступна такая возможность).
- Возвращён (Refunded) — заказ отменён, деньги отправлены покупателю обратно.
- Выполнен (Completed) — заказ завершён полностью.
Обратите внимание, что статусы по смыслу связаны с процессом оплаты, не с доставкой. Оная, если на сайте имеется, совершается, как правило, когда у заказа статус «Обработка». После доставки товара, в зависимости от плагина, статус автоматически или вручную с помощью менеджера меняется на «Выполнен». Статусы не способны показать, где сейчас товар — едет ли он к покупателю, находится ли в точке выдачи заказов или вовсе возвращён продавцу.
Если не ставили никакие плагины, кроме WC, заказ сгенерируется только после нажатия кнопки «Подтвердить заказ» на странице оформления заказа, после чего клиента перекинет на страницу оплаты и далее, к странице благодарности за покупку. Есть парочка нюансов, из-за которых продажи могут оказаться ниже ожидаемых из-за сбоев в процессе заказа.
1) Лишние поля при оформлении заказа.
Чем меньше полей для заполнения, тем выше конверсия. Это настолько очевидно и столько раз было доказано экспериментально, что кажется очевидным. Но, видимо, у авторов WooCommerce своё мнение. Вы только посмотрите на это!
К счастью, удалить лишние поля без знаний программирования легко, достаточно поставить один из множества плагинов. Уже даже в некоторые шаблоны встраивают редактор полей заказа, поэтому вопрос сводится к нескольким кликам мышкой. Часто убирают ненужное поле «Название компании», удаляют «Населённый пункт» и «Область», чтобы люди писали адрес только в поле «Адрес».
Здесь спрятаны две ловушки. Первая: многие плагины расчёта доставки смотрят одно из предустановленных полей для вычисления стоимости. Например, официальный плагин Boxberry ориентируется на название города и почтовый индекс. Отключение одного из полей ломает выбор ПВЗ.
Вторая ловушка связана с путаницей групп полей для адресов. В WooCommerce их две: платежа и доставки (shipping и billing). Разные плагины смотрят разные группы полей, поэтому без доработки могут отказываться работать.
Получается парадоксальная ситуация: чтобы улучшить конверсию, нужно убрать поля, но тогда некоторые плагины могут не работать, потому что никто не додумался сделать в настройках выбор поля адреса. Например, плагины сервисов доставки вроде Shiptor изменяют поля, добавляя подсказку с выбором города, сделав процедуру ввода адреса удобнее, но другие плагины служб доставки из-за потери совместимости. Заставить весь этот зоопарк работать вместе — сложно.
Особенно «хорошо» сайтам, которые продают и виртуальные, и физические товары, доставка последних выполняется несколькими поставщиками. Формы оформления должны быть разные, способы доставки — тоже. Готовых решений нет, имеющиеся плагины начнут сыпать ошибками обращения к несуществующим полям в моменты создания заказов, где не окажется обязательных полей.
2) Не интуитивная смена статусов.
Клиент продрался сквозь форму заказа, его перекинуло на страницу оплаты. Какой статус будет после оплаты? «В ожидании оплаты»? А вот и нет!
Надо всегда проверять этот момент, ибо он зависит от плагина, и проводить тестовую оплату перед включением продаж. Может статься, что потребуется доработка. Например, добавить автоматическую отмену заказа с уведомлением клиента после долгого ожидания поступления средств.
3) Способы оплаты
Тут примерно то же самое, что и с добавлением служб доставки. Каждый платежный шлюз предлагает собственный плагин, да и сторонние разработчики любят такое. И все — разные. Какие-то не требуют перехода на сторонний сайт, другие позволяют принимать денежку только на отдельном защищённом ресурсе.
Даже если способ оплаты один, нужно тщательно проверять всю цепочку действий — от оформления заказа до получения покупателем письма с чеком. Следует проверить разные сценарии работы, ибо некоторые платежные шлюзы работают странновато. Например, Юкасса при ошибках в процессе оплаты может молча перекинуть на страницу успешного заказа сайта, что введёт покупателя в заблуждение. Весь процесс следует отладить.
- Если оплата происходит без перехода, то нужно заботиться об интеграции стилей и скриптов платежного шлюза с шаблоном сайта, чтобы всё выглядело бесшовно и таким образом уровень доверия к сайту повышался. Но, как правило, редактировать внешние стили вам никто не даст. Например, Робокасса, хоть и позволяет вывести форму оплаты всплывающим попапом прямо на сайте, не позволяет его настроить.
- Если оплата требует перехода на внешний сайт, то оплативший товар клиент не всегда вернётся на сайт сразу же. Это требует настройки оповещений о том, что товар оплачен. Однако настройка отправка писем в WooCommerce не очень-то и гибкая. Справедливости ради замечу, что плагинов настройки писем — бесчисленное множество.
Можно оставить в стороне вопрос единообразия дизайна, но для магазинов с частыми продажами актуальна проблема сохранения стабильности. Ни один платёжный шлюз не работает абсолютно бесперебойно, ибо форс-мажор случается у всех. Когда такое происходит, требуется подключить альтернативный способ оплаты. Или можно всегда балансировать способы между пользователями из разных регионов (стран), предлагая стабильный. В WooCommerce нет штатного способа такое провернуть. Без доработок тут не обойтись, но это хотя бы не требует значительных усилий.
Шаблоны оформления
С шаблонами WooCommerce дела обстоят неважно. Значительная часть файлов, отвечающих за разметку видимых на сайте компонентов магазина, хранится в каталоге /wp-content/plugins/woocommerce/templates. Изменённые файлы .php требуется хранить в подкаталоге woocommerce шаблона, чтобы они перекрыли имеющиеся. И хотя такая подмена работает, данный способ оформления страниц — безумен. Объясню, почему.
Во-первых, это не шаблоны, а непонятно что. Логика и часть внешнего вида свалены вместе. Нет строгого соответствия файлов страницам. Каждый .php файл может влиять на внешний вид показа контента на совсем разных страницах либо наоборот, не влиять ни на что. Например, в файлах error.php, notice.php и success.php прописана разметка уведомлений, появляющихся на страницах оформления заказа, а уведомления на странице корзины управляются через отдельный скрипт /assets/js/frontend/cart.js. Часть классов у div-ов и других элементов HTML хранятся внутри плагина — правкой шаблона их не переопределить, поэтому может возникнуть путаница со стилями.
Во-вторых, изменения могут нарушить совместимость с плагинами. В файлах прописаны события — «зацепки» для плагинов, чтобы те выводили какие-то компоненты навроде выбора пункта выдачи заказов на странице оформления. Часть HTML разметки выводится внутренними функциями плагина и другие плагины переопределяют её «на лету». Если поменять какие-то классы у div-ов, изменить порядок элементов, то придётся долго и нудно заниматься тестированием изменений и поиском компромиссных решений.
Как будто этих недостатков WooCommerce мало, есть трудности с совместимостью шаблонов плагина, идущих в комплекте с шаблонами сайта. Они могут быть рассчитаны на старую версию WC, это тоже ломает совместимость.
Отлавливание глюков из-за несовместимости разметки — процесс трудоёмкий. Особенно тяжко приходится, если на сайте установлен AJAX-овый шаблон вроде Elessi, по принципу работы близкий к веб-приложению. Для таких авторы шаблона пишут свои замены корзин, страниц оформления и прочего. Выглядят такие шаблоны круто, но расширять их возможности плагинами трудоёмко.
Аналитика
В WooCommerce нельзя смотреть статистику по заказам в реальном времени, если заказов больше нескольких тысяч. Это связано со структурой базы данных: информация о заказах хранится в виде, не пригодном для быстрого извлечения.
К счастью, за последние год-полтора ситуация улучшилась. С версии 5.3 развивается раздел «Аналитика», где можно не только посмотреть динамику заказов по датам, но и сравнить с прошлым периодом. Аналитика работает не в реальном времени — информация периодически актуализируется, поэтому задержка обновления может составлять до нескольких часов. Однако появление данного инструмента не может не радовать, потому что для него авторы плагина решили подойти к вопросу хранения информации в базе данных рациональнее обычного.
Интеграции
Рассказывая, что товары и торговые предложения работают не очень интуитивно, я вскользь упомянул о том, что могут быть проблемы при интеграции со сторонними сервисами. В зависимости от специфики и объёмов продаж, интернет-магазины требуют интеграции со всякими МоимиСкладами, RetailCRM, CarrotQuest, Roistat и так далее.
Уровень абсурда в данной области просто зашкаливает. Например, есть такой плагин — Facebook for WooCommerce. Это официальный плагин от Facebook (Facebook — проект Meta Platforms Inc., деятельность которой в России запрещена), который передаёт товары из WooCommerce в бизнес-аккаунт соцсети.
Подсознательно ожидаешь, что раз плагин официальный, то работать он будет хотя бы… ну хоть как-то! На практике плагин можно считать вредоносным, потому что и с задачей не справляется, и мешает работе админки, и захламляет базу данных. Для того, чтобы синхронизировать товары, Facebook for WooCommerce создаёт фоновые задачи, прописывая их в таблице wp_options. Так как структура данной таблицы не предназначена для job-ов, и статус, и время срабатывания, и цель задачи хранятся в одной-единственной ячейке. Таким образом, чтобы разобраться, нужно ли выполнять задачи, требуется считать их все, распарсить содержимое и принять решение. Это долго, поэтому после начала работы плагина возникают следующие эффекты:
- Раздел админки со списком товаров может перестать открываться, потому что плагин добавляет колонку со статусом синхронизации товара с каталогом Facebook. Пока все задачи прочитаются, может пройти и минута, и две — выполнение скрипта PHP прервётся по тайм-ауту.
- Если во время выполнения задачи что-то пойдёт не так, её запись останется в таблице. Когда в wp_options накопятся десятки тысяч таких вот задач, сайт начнёт подтормаживать.
Знаете, что сделали авторы плагина после того, как я сообщил о проблеме? Добавили ещё одну задачу — ежесуточную очистку выполненных задач… Это вот никак не решило проблему, если задачи не отрабатывают. Например, плагин до сих пор не может корректно синхронизировать вариативные товары. Да и то, что админка начинает тормозить, разработчиков из Facebook как-то не тревожит.
Наверняка в каталоге бесплатных плагинов есть нормально работающие интеграции. И хорошие платные плагины с интеграцией, например, с сервисом МойСклад, также где-то есть. Правда, я их ещё не встречал. Если вы написали такое решение, неважно — на продажу или для бесплатного распространения — пишите мне немедленно, пропиарю вас бесплатно. Просто потому, что о чуде всегда приятно рассказывать.
5 Сильные стороны WooCommerce
Не всё так плохо. Да, платформа имеет недостатки, но они не всегда критичны, потому что стартапы и интернет-магазины бывают разные.
WooCommerce самодостаточен
WooCommerce крут тем, что с его помощью можно инфраструктуру держать «под боком». Ни байта информации о клиентах не уйдёт на сторону. Для аналитики, например, можно поднять Matomo, в качестве CRM — Krayin или Dolibarr. Разве что платежный шлюз будет работать где-то там, на банковских серверах.
Все доработки — по вашему желанию
Не требуется идти на поклон к онлайн-конструкторам интернет-магазинов, где нужные доработки, возможно, будут сделаны никогда. Если чего-то нет, вы можете нанять разработчика и он допилит сайт. Да, по причинам, описанным выше, это не всегда легко, зато возможно.
Всегда есть чем вдохновиться
Не знаете, что нужно вашему магазину? Можно подглядеть интересные фишки на демосайтах платных шаблонов. Виджеты, главные страницы, оформление каталога — разные шаблоны предлагают разные реализации по сути одного и того же. Это всегда интересно изучать и повторять подходящее у себя.
С WP вам можно расти как разработчику
Если вы — начинающий разработчик, то знайте: WP не научит вас правильной архитектуре программного обеспечения, не привьёт вам навыки написания чистого кода. И уж тем более не научит правильной отладке кода, потому что CMS слишком снисходительна к ошибкам. Однако, в WP так мало доступных из коробки решений, что всё добавленное вами будет, скорее всего, написано с нуля. Это могут быть ошибочные решения, корявенькие велосипеды и вам за них когда-нибудь станет стыдно. Но это будут ваши кусочки кода, созданные не благодаря знанию какого-нибудь фреймворка, а через понимание базовых возможностей языка PHP. Знание основ крайне полезно программисту, потому что позволяет думать над решением задач, игнорируя рамки навязанных решений.
Колонки «Цена» и «Наличие» в таблице с товарами, а не в отдельной таблице. При частом изменении цен и остатков переписываются строки с товарами полностью. А снизу ведь лежит SSD с ограниченным количеством циклов записи…
Andrew Lazarev, интересное замечание, спасибо.
Так, а «при частом» — это при каком? Допустим, это магазин где-то с тысячью товаров. Тогда wp_postmeta будет хранить в себе (допустим) около 200 тысяч строк, это примерно 50 мегабайт. Допустим, цены обновляются 1 раз в секунду и 1 раз в секунду файл с данными таблицы полностью перезаписывается. 50 * 60 * 60 * 24 / 1024 = 4218 гигабайт будет записываться данных на диск в сутки. За ресурс SSD возьму 150 терабайт (при гуглении по запросу «ssd lifespan» есть и большие значения, но предположу использование дешёвого накопителя). 150 * 1024 / 4218 = 36,4 суток в режиме такой работы проработает какой-то «средний» SSD. Мда, это печально.
Впрочем, в реальности результат будет менее предсказуем.
С одной стороны, MySQL, особенно если это InnoDB и таблицы не делятся по файлам, не будет перезаписывать все 50 мегабайт таблицы. Более того, можно подкрутить параметр MySQL «innodb_flush_log_at_trx_commit». При значении 2 частота записи сократится. Конечно, надёжность сохранения данных при неожиданном выключении сервера снизится, но тут уж кому что важнее. Есть и другие параметры, которые можно подкрутить, снизив интенсивность обращений к диску для записи: Tuning MySQL/InnoDB Flushing for a Write-Intensive Workload.
С другой стороны, в wp_postmeta и wp_posts много чего пишется. Если заказы идут, то записывается гораздо больше значений. И пусть по отдельности это килобайты, суммарно это может быть много данных.
Так что да, Вордпрессу за архитектуру БД можно добавить ещё один минус.
«Но что, если нужно спрятать отсутствующий товар со страниц магазина? Удивительно, но сколь-нибудь грамотного способа скрытия недоступного для продажи товара в результатах поиска и на страницах каталоге не существует.»
А чем блок «Видимость в магазине» в метабоксе «Опубликовать» не угодил? Там управляйте видимостью товара, как душе угодно..
В целом слишком много букв, чтобы вчитываться и дискутировать. На своём опыте могу сказать, что Woocommerce имеет свою нишу, по меньшей мере как замена сайтам-каталогам b2b-сегмента на Битриксе, коих на просторах рунета валом. И вдолгую на нём проект держать можно, и в плане «удобства и надежности» всё нормально, тут , как и в целом с ВП, главное не увлекаться сторонними решениями, которые завозят с собой вагон тормозящих плюшек вместе с нужным функционалом.
Синхронизация данных это боль, но по мелочам типа «обновить цены\подправить описания из прайса» даже стандартный ручной импорт-экспорт справляется на ура, при желании и на крон что-то подвесить можно.
Игорь, да, как вариант. Проблема в том, что его не используют. Технически изменение данного свойства назначает продукту соответствующую скрытую таксономию, инфа хранится не в wp_posts, а в отдельной табличке wp_term_relationships, id таксономии будет случайным значением (на разных сайтах разный набор категорий, id таксономии exclude-from-catalog назначается в порядке очередности). Проговариваю это потому, что для учета свойства нужно на страницах каталога, в поиске по сайту, в плагинах — везде в запросах указывать требование отсутствия принадлежности к категории скрытых товаров либо использовать стандартный WP_Query, ибо в WC имеется глобальный фильтр на этот случай. К сожалению, авторы сторонних плагинов и даже шаблонов частенько используют MySQL запросы без учета этой таксономии, иногда даже без всяких WP_Query. Поэтому, чтобы использовать свойство показа в каталоге, нужно в каждом плагине и шаблоне проверять и переписывать запросы. Когда плагинов (и сайтов) много, эта задача становится занятием трудоемким.
Так что с «сколь-нибудь грамотного способа скрытия недоступного для продажи товара в результатах поиска и на страницах каталоге не существует» я погорячился, уберу из текста — способ есть, но его использование увеличивает стоимость обслуживания.