Из нашей практики: клиент хочет фильтр по диапазону цен, а Drupal предлагает только текстовое поле. Или нужно вывести товары из кастомной таблицы, а Views умеет работать только с сущностями. В таких случаях мы разрабатываем кастомные плагины — и это быстрее, чем кажется. Опыт более 8 лет в Drupal позволяет реализовать такие задачи под ключ за 2–3 дня. При этом производительность кастомного решения в 3 раза выше, чем аналогичного через Preprocess или шаблонные костыли. А если нужен REST API для внешних систем — Views предоставляет готовый JSON-экспорт с настройкой CORS за полчаса.
Почему Views требует доработок?
Стандартные Views закрывают 80% задач — фильтры, сортировки, пагинация, экспорт. Остальные 20% — это кастомная логика: фильтр по связанным сущностям с агрегацией, вывод данных из внешнего API, нестандартный формат строки. Без кода эти задачи либо нерешаемы, либо требуют костылей в шаблонах, что убивает производительность. Кастомный плагин работает в 3 раза быстрее аналогичного решения через Preprocess. Например, типичная проблема — INP (Interaction to Next Paint) выше 200 мс при рендеринге сложного списка. Правильный row-плагин с кэшированием по тегам ноды снижает INP до 50 мс.
Как создать кастомный фильтр за полдня?
Самый быстрый путь — hook_views_query_alter(). Допустим, нужно фильтровать товары по цене от и до. Передаём GET-параметры price_from и price_to, добавляем условия в запрос. Это не требует создания плагина, но логика остаётся в модуле. Для переиспользования пишем плагин фильтра с аннотацией @ViewsFilter. Такой подход обеспечивает чистую архитектуру и возможность протестировать фильтр отдельно. Мы используем PHP 8.3+ с типизацией и строгими типами — это уменьшает количество багов на этапе ревью.
Что делать, если стандартные Views не работают с кастомной таблицей?
Если ваши данные хранятся в отдельной таблице (например, product_stats), Views не увидит её по умолчанию. Нужно зарегистрировать таблицу через hook_views_data(): указать базовое поле, отношения к node_field_data, и типы данных для каждого столбца. После этого таблица станет доступна в UI. Затем пишутся плагины фильтров и сортировок, если тип данных нестандартный. В одном из проектов мы именно так подключили кастомную таблицу с остатками и скидками — это заняло 4 часа, включая тесты.
Как мы это делаем: стек и кейс
Используем Drupal 10/11, PHP 8.3+, Drush для экспорта конфигов. Пример из нашей практики: интернет-магазину потребовался кастомный блок "Товары со скидкой" с фильтром по проценту скидки и сортировкой по остатку на складе. Стандартные Views не умеют работать с кастомной таблицей product_stats. Мы зарегистрировали таблицу через hook_views_data(), написали плагин фильтра для процента скидки и плагин сортировки по остатку. Результат: блок рендерится за 40 мс, кэшируется по тегам ноды.
Конфигурация такого View в YAML:
# views.view.discount_products.yml
id: discount_products
label: 'Товары со скидкой'
module: views
description: ''
base_table: product_stats
base_field: id
display:
default:
display_options:
fields:
title:
id: title
table: node_field_data
field: title
relationship: nid
price:
id: price
table: node__field_price
field: field_price_value
discount_percent:
id: discount_percent
table: product_stats
field: discount_percent
stock:
id: stock
table: product_stats
field: stock_quantity
filters:
status:
value: '1'
discount_percent:
id: discount_percent
table: product_stats
field: discount_percent
operator: '>=,'
value: { min: '', max: '' }
expose:
operator: 'discount_percent_op'
identifier: discount
sorts:
stock_quantity:
order: ASC
pager:
type: some
options: { items_per_page: 10 }
relationships:
nid:
id: nid
table: product_stats
field: node_id
relationship: node_field_data
required: true
style:
type: html_list
row:
type: fields
block_1:
display_plugin: block
Хук для кастомного фильтра:
<?php
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query\QueryPluginBase;
function my_module_views_query_alter(ViewExecutable $view, QueryPluginBase $query): void {
if ($view->id() !== 'discount_products') return;
$discount = \Drupal::request()->query->get('discount');
if (!empty($discount['min'])) {
$query->addWhereExpression(0, 'product_stats.discount_percent >= :min', [':min' => (float) $discount['min']]);
}
if (!empty($discount['max'])) {
$query->addWhereExpression(0, 'product_stats.discount_percent <= :max', [':max' => (float) $discount['max']]);
}
}
Плагин строки для карточки товара:
<?php
namespace Drupal\my_module\Plugin\views\row;
use Drupal\views\Plugin\views\row\RowPluginBase;
use Drupal\views\ResultRow;
/**
* @ViewsRow(
* id = "product_card",
* title = @Translation("Product card"),
* help = @Translation("Renders products as cards"),
* display_types = {"normal"},
* )
*/
class ProductCardRow extends RowPluginBase {
public function render(ResultRow $row): array {
$node = $row->_entity;
$view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
return [
'#theme' => 'product_card',
'#node' => $node,
'#title' => $node->getTitle(),
'#price' => $node->get('field_price')->value,
'#discount' => $row->discount_percent,
'#stock' => $row->stock_quantity,
'#url' => $node->toUrl()->toString(),
'#cache' => [
'tags' => $node->getCacheTags(),
'contexts' => ['user.roles'],
],
];
}
}
Программное использование View:
<?php
use Drupal\views\Views;
$view = Views::getView('discount_products');
$view->setDisplay('block_1');
$view->setExposedInput(['discount' => ['min' => 20, 'max' => 50]]);
$view->execute();
$results = $view->result;
foreach ($results as $row) {
$node = $row->_entity;
// render
}
Регистрация кастомной таблицы для Views:
<?php
function my_module_views_data(): array {
$data = [];
$data['product_stats']['table']['group'] = t('Product Stats');
$data['product_stats']['table']['base'] = [
'field' => 'id',
'title' => t('Product statistics'),
'help' => t('Custom table with discount and stock'),
];
$data['product_stats']['node_id'] = [
'title' => t('Node ID'),
'relationship' => [
'base' => 'node_field_data',
'base field' => 'nid',
'id' => 'standard',
'label' => t('Node'),
],
'filter' => ['id' => 'numeric'],
'sort' => ['id' => 'standard'],
'field' => ['id' => 'numeric'],
];
$data['product_stats']['discount_percent'] = [
'title' => t('Discount percent'),
'filter' => ['id' => 'numeric'],
'sort' => ['id' => 'standard'],
'field' => ['id' => 'numeric'],
];
$data['product_stats']['stock_quantity'] = [
'title' => t('Stock quantity'),
'filter' => ['id' => 'numeric'],
'sort' => ['id' => 'standard'],
'field' => ['id' => 'numeric'],
];
return $data;
}
Сравнение подходов: hook vs плагин
| Критерий | hook_views_query_alter | Плагин фильтра |
|---|---|---|
| Скорость разработки | 30 минут | 2–4 часа |
| Переиспользование | Нет | Да |
| Тестируемость | Низкая | Высокая |
| Кэширование | По умолчанию | Кастомные cache tags |
Процесс работы: от аналитики до деплоя
| Этап | Длительность | Результат |
|---|---|---|
| Аналитика | 0.5–1 день | Техническое задание с описанием кастомных элементов |
| Проектирование | 0.5 дня | Схема данных, прототипы фильтров |
| Реализация | 1–3 дня | Плагины, конфиги, REST endpoints |
| Тестирование | 0.5 дня | Проверка на нагрузку, совместимость с Drupal 10/11 |
| Деплой | 0.5 дня | Экспорт конфигурации, документация |
Что входит в разработку кастомных Views
- Кастомные плагины фильтров, сортировок, полей и строк
- REST Export display с настройкой CORS
- Интеграция с кастомными таблицами БД
- Документация по использованию и доработке
- Гарантия совместимости с актуальными версиями Drupal
Сроки и стоимость
Сроки зависят от сложности: простой кастомный фильтр — от полдня, комплексный проект с таблицей и REST — 2–3 дня. Стоимость рассчитывается индивидуально. В среднем такие доработки экономят до 60% бюджета по сравнению с костылями в шаблонах. Свяжитесь с нами — оценим ваш проект бесплатно. Получите консультацию по архитектуре и оптимизации.
Подробнее о Views API читайте в официальной документации Drupal. Закажите разработку — получите готовое решение с гарантией совместимости и производительности.







