Розробка кастомних блоків (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

Розробка користувальницьких блоків 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 днів.