Проблема: готовых блоков недостаточно
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 с большим опытом. Закажите разработку кастомных блоков, и мы реализуем любую логику с максимальной производительностью.







