Розробка користувальницьких блоків Drupal
Блоки у Drupal — це плагіни, що розміщуються в регіонах теми. Два типи: Content blocks (створюються через UI редакторами) та Plugin blocks (код). Користувальницькі плагін-блоки — це те, що пишет розробник, коли потрібна динамічна логіка: вивід останніх записів, віджет форми, баннер з конфігурації.
Анатомія плагін-блока
// src/Plugin/Block/LatestNewsBlock.php
namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @Block(
* id = "my_module_latest_news",
* admin_label = @Translation("Останні Новини"),
* category = @Translation("Мій модуль"),
* context_definitions = {
* "node" = @ContextDefinition("entity:node", required = FALSE, label = @Translation("Current node"))
* }
* )
*/
class LatestNewsBlock extends BlockBase implements ContainerFactoryPluginInterface {
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
private readonly EntityTypeManagerInterface $entityTypeManager,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('entity_type.manager'),
);
}
/**
* Форма конфігурації блока (видна в Layout Builder / Block UI).
*/
public function blockForm($form, FormStateInterface $form_state): array {
$form = parent::blockForm($form, $form_state);
$config = $this->getConfiguration();
$form['count'] = [
'#type' => 'number',
'#title' => $this->t('Кількість елементів'),
'#default_value' => $config['count'] ?? 4,
'#min' => 1,
'#max' => 20,
];
$form['exclude_current'] = [
'#type' => 'checkbox',
'#title' => $this->t('Виключити поточний вузол'),
'#default_value' => $config['exclude_current'] ?? TRUE,
];
return $form;
}
public function blockSubmit($form, FormStateInterface $form_state): void {
$this->configuration['count'] = $form_state->getValue('count');
$this->configuration['exclude_current'] = $form_state->getValue('exclude_current');
}
public function build(): array {
$config = $this->getConfiguration();
$count = $config['count'] ?? 4;
$storage = $this->entityTypeManager->getStorage('node');
$query = $storage->getQuery()
->condition('type', 'news')
->condition('status', 1)
->sort('created', 'DESC')
->range(0, $count)
->accessCheck(TRUE);
// Виключаємо поточну ноду якщо включена опція
if ($config['exclude_current'] ?? TRUE) {
try {
$current_node = $this->getContextValue('node');
if ($current_node && $current_node->id()) {
$query->condition('nid', $current_node->id(), '<>');
}
} catch (\Exception $e) {
// Контекст не доступний — не виключаємо
}
}
$ids = $query->execute();
if (empty($ids)) {
return ['#markup' => ''];
}
$nodes = $storage->loadMultiple($ids);
$view_builder = $this->entityTypeManager->getViewBuilder('node');
return [
'#theme' => 'my_module_news_list',
'#items' => array_map(fn($n) => $view_builder->view($n, 'teaser'), $nodes),
// Кеш: інвалідується при зміні будь-якої ноди типу news
'#cache' => [
'tags' => Cache::mergeTags(['node_list:news'], $this->getCacheTags()),
'contexts' => ['route', 'user.roles'],
'max-age' => Cache::PERMANENT,
],
];
}
public function getCacheTags(): array {
return Cache::mergeTags(parent::getCacheTags(), ['node_list:news']);
}
}
Content Blocks — управління через UI
Content Blocks (Block content entities) створюються редакторами в /admin/content/block. Можна створити користувальницький тип блока в /admin/structure/block-content/types.
Програмне створення типу блока:
use Drupal\block_content\Entity\BlockContentType;
use Drupal\block_content\Entity\BlockContent;
// Тип блока
$block_type = BlockContentType::create([
'id' => 'promo_banner',
'label' => 'Промо-баннер',
'description' => 'Промо-баннер з зображенням та кнопкою',
]);
$block_type->save();
// Додаємо тіло (стандартне body поле)
block_content_add_body_field($block_type->id());
// Програмно створити Content Block
$block = BlockContent::create([
'type' => 'promo_banner',
'info' => 'Літня акція',
'body' => ['value' => '<p>Скидка 20% до 31 липня</p>', 'format' => 'full_html'],
'field_image' => [['target_id' => 42]],
'field_button_text' => 'Дізнатися більше',
'field_button_url' => ['uri' => 'internal:/promo'],
]);
$block->save();
Layout Builder — інтегроване редагування блоків
З включеним layout_builder редактори розміщують блоки безпосередньо на сторінці. Користувальницький плагін-блок автоматично з'являється в списку доступних.
Включити Layout Builder для типу контенту:
drush en layout_builder layout_discovery -y
В /admin/structure/types/manage/{type}/display включаємо Layout Builder.
Блок з формою
Блок, що рендерить форму:
use Drupal\Core\Form\FormBuilderInterface;
/**
* @Block(
* id = "my_module_search_block",
* admin_label = @Translation("Блок пошуку"),
* )
*/
class SearchBlock extends BlockBase implements ContainerFactoryPluginInterface {
public function __construct(
array $configuration, $plugin_id, $plugin_definition,
private readonly FormBuilderInterface $formBuilder,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
public static function create(ContainerInterface $container, ...$args): static {
return new static(...$args, $container->get('form_builder'));
}
public function build(): array {
return $this->formBuilder->getForm(\Drupal\my_module\Form\SearchForm::class);
}
public function getCacheContexts(): array {
return Cache::mergeContexts(parent::getCacheContexts(), ['url.query_args:q']);
}
}
Програмне розміщення блока
use Drupal\block\Entity\Block;
// Створюємо екземпляр блока і розміщуємо його в регіон
$block = Block::create([
'id' => 'latest_news_sidebar',
'plugin' => 'my_module_latest_news',
'theme' => 'my_theme',
'region' => 'sidebar_first',
'weight' => -5,
'settings' => [
'label' => 'Останні новини',
'label_display' => 'visible',
'count' => 3,
'exclude_current' => TRUE,
],
'visibility' => [
'node_type' => [
'id' => 'node_type',
'bundles' => ['article' => 'article', 'news' => 'news'],
'negate' => FALSE,
'context_mapping' => ['node' => '@node.node_route_context:node'],
],
],
]);
$block->save();
Конфігурація в YAML
Після розміщення блоків через UI експортуємо в конфіг:
# block.block.latest_news_sidebar.yml
id: latest_news_sidebar
theme: my_theme
region: sidebar_first
weight: -5
plugin: my_module_latest_news
settings:
id: my_module_latest_news
label: 'Останні новини'
label_display: visible
count: 3
exclude_current: true
visibility:
node_type:
id: node_type
bundles:
article: article
news: news
negate: false
Кешування блоків
Drupal має складну систему кешування. Для блоків:
-
cache_tags— інвалідація по тегам (при зміні даних) -
cache_contexts— варіації кешу (за роллю, URL, мовою) -
max-age— тимчасове обмеження
public function getCacheMaxAge(): int {
// Блок не кешується зовсім — для реального часу
return 0;
// Кеш на час
return 3600;
// Постійний кеш з інвалідацією по тегам
return Cache::PERMANENT;
}
Помилка — повернути 0 для всіх блоків. Це убиває продуктивність. Правильно — використовувати теги та контексти, встановлювати max-age = Cache::PERMANENT.
Терміни
Один користувальницький плагін-блок з конфігурацією: 1 день. Набір блоків з формами, інтеграцією Layout Builder, складним кешуванням: 3–5 днів.







