Разработка кастомных Views Drupal
Views — самый мощный инструмент в Drupal для отображения списков контента. Фильтры, сортировки, связанные сущности, разные форматы вывода, ajax-пагинация, exposed filters — всё настраивается без кода. Кастомный код нужен там, где UI не достаёт: кастомные фильтры, кастомные форматтеры полей, программная манипуляция с query.
Создание View через UI
Путь: /admin/structure/views/add. Основные параметры:
- View name — машинное имя, используется в коде
- Show — тип сущности (Content, Users, Taxonomy terms...)
- Tagged with — фильтр по тегу при создании
- Display — Page (URL), Block, REST Export, Feed, Attachment
Конфигурация View в YAML
После настройки в UI экспортируем:
drush config:export
# создаётся views.view.{machine_name}.yml
Фрагмент конфигурации сложного View:
# views.view.news_list.yml
id: news_list
label: 'Список новостей'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
default:
display_options:
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
link_to_entity: true
label: ''
element_label_colon: false
field_image:
id: field_image
table: node__field_image
field: field_image
image_style: news_teaser
image_link: content
created:
id: created
date_format: custom
custom_date_format: 'd.m.Y'
filters:
status:
value: '1'
table: node_field_data
field: status
type:
value: { news: news }
table: node_field_data
field: type
field_category_target_id:
id: field_category_target_id
exposed: true
expose:
label: 'Категория'
identifier: category
sorts:
created:
order: DESC
pager:
type: full
options:
items_per_page: 12
style:
type: html_list
row:
type: 'fields'
use_ajax: true
page_1:
display_plugin: page
path: /news
display_options:
menu:
type: normal
title: Новости
Exposed Filters (фильтры для пользователя)
Exposed filters рендерятся как форма — пользователь может фильтровать список без перезагрузки (с AJAX) или с перезагрузкой. Настраиваются в UI для каждого фильтра галочкой «Expose this filter to visitors».
Кастомный обработчик exposed filter через hook_views_query_alter:
// my_module.module
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query\QueryPluginBase;
/**
* Implements hook_views_query_alter().
*/
function my_module_views_query_alter(ViewExecutable $view, QueryPluginBase $query): void {
if ($view->id() !== 'news_list') return;
// Добавляем дополнительное условие на основе GET-параметра
$price_from = \Drupal::request()->query->get('price_from');
if ($price_from && is_numeric($price_from)) {
$query->addWhereExpression(
0,
"node__field_price.field_price_value >= :price_from",
[':price_from' => (float) $price_from]
);
}
}
Кастомный плагин стиля строки
Стандартные стили: Fields, Entity, HTML list, Table. Для кастомного рендера — плагин:
// src/Plugin/views/row/ArticleCardRow.php
namespace Drupal\my_module\Plugin\views\row;
use Drupal\views\Plugin\views\row\RowPluginBase;
use Drupal\views\ResultRow;
/**
* @ViewsRow(
* id = "article_card",
* title = @Translation("Article card"),
* help = @Translation("Renders articles as cards"),
* display_types = {"normal"},
* )
*/
class ArticleCardRow extends RowPluginBase {
public function render(ResultRow $row): array {
$node = $row->_entity;
$view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
return [
'#theme' => 'article_card',
'#node' => $node,
'#title' => $node->getTitle(),
'#image' => $node->hasField('field_image')
? $view_builder->viewField($node->get('field_image'), 'card')
: NULL,
'#url' => $node->toUrl()->toString(),
'#cache' => [
'tags' => $node->getCacheTags(),
'contexts' => ['user.roles'],
],
];
}
}
Программное использование Views
use Drupal\views\Views;
// Получить View программно и выполнить
$view = Views::getView('news_list');
$view->setDisplay('block_1');
$view->setArguments([42]); // contextual filter аргументы
$view->setExposedInput(['category' => 'tech']);
$view->execute();
// Получить результаты
$results = $view->result;
foreach ($results as $row) {
$node = $row->_entity;
// ...
}
// Рендер блока View
$view->preExecute();
$view->execute();
$rendered = $view->buildRenderable('block_1');
Views и кастомные таблицы
Если нужно показывать данные из кастомной таблицы БД, регистрируем её для Views:
// my_module.views.inc
/**
* Implements hook_views_data().
*/
function my_module_views_data(): array {
$data = [];
$data['my_module_stats']['table']['group'] = t('My Module');
$data['my_module_stats']['table']['base'] = [
'field' => 'id',
'title' => t('My Module Stats'),
'help' => t('Statistics data from my module'),
];
$data['my_module_stats']['node_id'] = [
'title' => t('Node ID'),
'help' => t('Related node'),
'relationship' => [
'base' => 'node_field_data',
'base field' => 'nid',
'id' => 'standard',
'label' => t('Node'),
],
'filter' => ['id' => 'numeric'],
'sort' => ['id' => 'standard'],
'field' => ['id' => 'numeric'],
];
$data['my_module_stats']['view_count'] = [
'title' => t('View count'),
'field' => ['id' => 'numeric'],
'filter' => ['id' => 'numeric'],
'sort' => ['id' => 'standard'],
];
return $data;
}
REST Export Display
Views умеет экспортировать данные в JSON через REST Export display:
/api/news.json?page=0&items_per_page=20&category=tech
Конфигурируется в UI или YAML. Заголовки CORS настраиваются в services.yml:
# web/sites/default/services.yml
cors.config:
enabled: true
allowedHeaders: ['*']
allowedMethods: []
allowedOrigins: ['https://frontend.example.com']
exposedHeaders: false
maxAge: false
supportsCredentials: false
Сроки
Простой View с exposed filters через UI + экспорт конфигурации: полдня. Кастомный плагин (row, filter, sort), кастомная таблица данных, REST export: 2–3 дня.







