Разработка кастомных блоков (Blocks) Drupal

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомных блоков (Blocks) Drupal
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомных блоков (Blocks) 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("Latest News"),
 *   category = @Translation("My Module"),
 *   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('Number of items'),
      '#default_value' => $config['count'] ?? 4,
      '#min' => 1,
      '#max' => 20,
    ];

    $form['exclude_current'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Exclude current node'),
      '#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' => 'Promo Banner',
  '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 — блоки в inline-редактировании

С включённым 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("Search Block"),
 * )
 */
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 дней.