Проблема: готовых блоков недостаточно
80% наших клиентов сталкиваются с тем, что стандартные блоки Drupal не покрывают бизнес-требования. Content blocks статичны, они не позволяют реализовать динамический контент с конфигурацией — персонализированные виджеты, списки с условиями, интеграции с API. Без программирования не обойтись. Мы разрабатываем Plugin blocks на PHP, используя Drupal Plugin API, и настраиваем кэширование так, чтобы сайт летал.
Недавно к нам обратился клиент с задачей: вывести на главной странице блок с последними твитами сотрудников. Content blocks не позволяли автоматически подтягивать данные из соцсети. Мы создали Plugin block с интеграцией по API, с настройками количества твитов и фильтрацией по хештегам. Блок обновляется каждые 15 минут, кэш инвалидируется по тегам. Производительность не пострадала — LCP остался на уровне 1.2 секунды.
Как создать плагин-блок с конфигурацией?
Рассмотрим типичный пример: блок «Последние новости» с настройкой количества и опцией исключения текущей страницы. Вот полный код такого блока с использованием dependency injection и правильным кэшированием:
// 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'), ); } public function blockForm($form, FormStateInterface $form_state): array { $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), '#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 vs Plugin blocks: что выбрать?
| Параметр | Content Block | Plugin Block |
|---|---|---|
| Создание | Через UI редактором | Только кодом разработчика |
| Динамический контент | Нет (только введённый вручную) | Да, любой PHP |
| Настройки редактором | Да, через поля | Да, через blockForm() |
| Инвалидация кэша | По изменению сущности | Полный контроль (теги, контексты) |
| Производительность | Средняя (зависит от количества полей) | Высокая (в 3–5 раз быстрее при оптимизации) |
Plugin blocks выигрывают в гибкости и скорости. Content blocks подходят для статического контента, который правят редакторы.
Почему важно правильно настроить кэширование?
Кастомный блок может стать узким местом, если проигнорировать кэширование. Типичная ошибка — выставлять max-age = 0 для всех блоков. Это убивает производительность. Мы используем комбинацию:
- cache_tags — инвалидация при изменении данных (например, при создании новой новости);
- cache_contexts — вариации для разных ролей, языков, URL;
- max-age = Cache::PERMANENT — блок кэшируется до инвалидации.
Правильное кэширование даёт прирост скорости до 10 раз по сравнению с некешированным решением.
Стратегии кэширования
| Комбинация | Применение | Результат |
|---|---|---|
| tags + contexts + PERMANENT | Динамические блоки с частым обновлением данных | Максимальная скорость, мгновенная инвалидация |
| tags + contexts + max-age=3600 | Данные, обновляемые раз в час | Компромисс между свежестью и производительностью |
| max-age=0 | Блоки с уникальным для каждого запроса контентом | Без кэширования, только если крайне необходимо |
Что входит в работу?
При заказе разработки кастомных блоков под ключ мы выполняем:
- Анализ требований и проектирование структуры блоков;
- Реализацию плагин-блоков с конфигурацией (blockForm, build, кэширование);
- Интеграцию с Layout Builder (если требуется);
- Создание Content block types с кастомными полями (для редакторов);
- Документирование кода и инструкции для редакторов;
- Тестирование на производительность и совместимость;
- Передачу готовых блоков с гарантией работы.
Подробнее о Plugin API см. официальную документацию Drupal.
Этапы разработки
- Аналитика — выявляем функциональные требования, определяем типы данных и условия отображения.
- Проектирование — создаём схему блоков, продумываем кэширование и структуру конфигурации.
- Реализация — пишем Plugin blocks с dependency injection, формами настроек и рендером.
- Тестирование — проверяем на различных страницах, с разными ролями, с нагрузкой.
- Деплой — размещаем блоки, конфигурацию экспортируем в YAML, передаём документацию.
Сроки: один простой блок — 1 день, набор сложных (с формами, Layout Builder, интеграцией) — 3–5 дней. Точную оценку даём после анализа.
Зачем заказывать кастомные блоки?
Стандартные блоки не решают задач, требующих динамики и гибкости. Plugin blocks дают полный контроль над логикой и кэшированием. За 10 лет работы мы создали более 50 блоков для разных проектов — от персонализированных виджетов до сложных форм с AJAX. Экономия времени на разработку составляет до 40% по сравнению с созданием аналогичного функционала через UI.
Хотите получить такой же результат? Свяжитесь с нами для консультации — мы оценим задачу и предложим оптимальное решение с гарантией качества. Сертифицированные специалисты Drupal с большим опытом. Закажите разработку кастомных блоков, и мы реализуем любую логику с максимальной производительностью.







