Drupal 11.4: Обзор ключевых изменений

В этом обзоре я собрал изменения в Drupal 11.4, которые показались мне наиболее значимыми или просто интересными. Полный список обновлений вы найдёте на странице релиза1.

Default Admin — новая административная тема на основе Gin

Контрибутная тема Gin2 интегрирована в ядро под названием Default Admin (машинное имя default_admin).345 Тема имеет экспериментальный статус.6 Claro по-прежнему остаётся административной темой по умолчанию, но по первоначальному плану её удалят в Drupal 12 и сделают Default Admin новой стандартной темой.

Видео 1. Форма редактирования материала с демонстрацией светлой и тёмной темы оформления.

Новая тема оформления предлагает значительно улучшенную версию Claro с новыми функциями.

  • Выбор акцентного цвета;
  • Поддержка светлой и тёмной цветовых схем с возможностью ручного выбора или автоматического определения на основе системных настроек;
  • Поддержка режима высокой контрастности;
  • Выбор цвета фокуса;
  • Настройка плотности макета;
  • Возможность расширять сайдбары форм редактирования сущностей, а также переключать их видимость.

Добавлен экспериментальный модуль mailer для интеграции Symfony Mailer

Добавлен экспериментальный модуль mailer для интеграции Symfony Mailer7 в Drupal.8 Он позволяет использовать современные транспорты (SMTP, Sendmail и другие), настраивать параметры отправки через DSN и перехватывать письма в тестах.

Знакомо, не правда ли? Ещё в 2023 году в Drupal 10.29 добавили зависимость symfony/mailer и соответствующий #[Mail]-плагин.10 С тех пор компонент можно было использовать при необходимости, но его возможности были сильно ограничены.

Новый модуль расширяет возможности, предоставляя сервисы для отправки, модификации сообщений и кастомизации процессов:

  • Для тестирования добавлен модуль mailer_capture, который перехватывает отправленные письма.11
  • Основные сервисы для работы с почтой:
    • Symfony\Component\Mailer\MailerInterface — отправка сообщений.
    • Сервис Symfony\Component\Mailer\Transport\TransportInterface — прямая отправка в обход Messenger12.
  • Кастомизация транспортов:
    • Фабрика Drupal\Core\Mailer\TransportServiceFactoryInterface для замены или модификации транспортов.
    • Абстрактный сервис Symfony\Component\Mailer\Transport\AbstractTransportFactory для создания сторонних транспортов через сервисы с метками mailer.transport_factory.
  • События для обработки писем:
    • Symfony\Component\Mailer\Event\MessageEvent13 — изменение содержимого сообщения перед отправкой.
    • Symfony\Component\Mailer\Event\SentMessageEvent14 — получение данных об успешной отправке: оригинал сообщения, отладочная информация, идентификатор.
    • Symfony\Component\Mailer\Event\FailedMessageEvent15 — обработка ошибок с информацией о сообщении и тексте ошибки.
  • Новая настройка mailer_sendmail_commands ограничивает доступные команды sendmail. При попытке использовать неразрешённые команды система выбрасывает исключение о небезопасной отправке сообщения.

Листинг 1. Пример отправки сообщения с использованием нового сервиса Symfony\Component\Mailer\MailerInterface.

use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

final readonly class Foo {

  public function __construct(private MailerInterface $mailer, private LoggerInterface $logger) {}

  public function doSomething(): void {
    // Прочая логика…
    try {
      $this->sendEmail();
    }
    catch (TransportExceptionInterface $exception) {
      $this->logger->error($exception->getMessage());
    }
  }

  private function sendEmail(): void {
    $email = new Email()
      ->subject('Hello, World!')
      ->from('webmaster@example.com')
      ->to('you@example.com')
      ->text('Hello, World! This is a test email.')
      ->attachFromPath('/path/to/file.pdf', 'file.pdf', 'application/pdf');

    $this->mailer->send($email);
  }

}

Листинг 1 демонстрирует, что отправка сообщений существенно отличается от стандартных методов в Drupal. Это вызывает закономерные вопросы. Например, как перехватывать письма определённого модуля или типа? Модуль не даёт ответа на этот вопрос. Похоже, нас подталкивают к созданию собственных типизированных Email-классов. Пример:

use Drupal\commerce_order\Entity\OrderInterface;
use Symfony\Component\Mime\Email;

final class CommerceOrderReceiptEmail extends Email {

  public function __construct(public OrderInterface $order) {}

}

Однако адаптация всех модулей под такую систему потребует много времени. На какое-то время в контрибах и проектах может возникнуть путаница. Но для этого и нужен экспериментальный модуль. Возможно, в него добавят промежуточные слои для текущей системы. Для собственных проектов это вполне подходящее решение, которое определённо удобнее и проще в настройке, чем метод из статьи про отправку писем с использованием ООП и Dependency Injection.

К слову о событиях — приведу небольшой пример их использования:

Листинг 2. Пример использования событий для обработки электронных писем.

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Render\RendererInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Event\FailedMessageEvent;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mailer\Event\SentMessageEvent;
use Symfony\Component\Mime\Email;

final readonly class EmailSubscriber implements EventSubscriberInterface {

  public function __construct(private RendererInterface $renderer, private LoggerInterface $logger) {}

  public static function getSubscribedEvents(): array {
    return [
      MessageEvent::class => 'onMessagePrepare',
      SentMessageEvent::class => 'onMessageSent',
      FailedMessageEvent::class => 'onMessageFailed',
    ];
  }

  public function onMessagePrepare(MessageEvent $event): void {
    $message = $event->getMessage();
    if (!$message instanceof Email) {
      return;
    }

    $email_wrapper = [
      '#theme' => 'email_wrapper',
      '#message' => $message,
    ];
    $email_html = $this->renderer->renderInIsolation($email_wrapper);
    $message->html($email_html);
  }

  public function onMessageSent(SentMessageEvent $event): void {
    if (!$event->getMessage()->getOriginalMessage() instanceof Email) {
      return;
    }

    $this->logger->info('Email was successfully sent.', ['message_id' => $event->getMessage()->getMessageId()]);
  }

  public function onMessageFailed(FailedMessageEvent $event): void {
    if (!$event->getMessage() instanceof Email) {
      return;
    }

    $this->logger->error('Email was not sent.', [
      'message' => $event->getMessage(),
      'error' => $event->getError()->getMessage(),
    ]);
  }

}

Пример с HTML-обёрткой в листинге 2 отправит HTML «как есть» — без инъекции inline-стилей и других преобразований. Для этого потребуются сторонние решения и зависимости16.17

Поддержка #[MapQueryParameter] для маппинга query-параметров в контроллерах

Добавлена поддержка атрибута Symfony #[MapQueryParameter],18 который упрощает работу с query-параметрами в контроллерах. Теперь не нужно вручную извлекать параметры из объекта Request — достаточно объявить типизированные аргументы метода, и Symfony автоматически выполнит маппинг и приведение типов.

Рассмотрим практический пример — контроллер каталога товаров с фильтрацией. URL может выглядеть так: /catalog?min_price=1000&sort=price_asc.

Листинг 3. Традиционный способ получения query-параметров через объект Request.

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

public function catalog(Request $request): JsonResponse {
  $min_price = $request->query->get('min_price');
  $max_price = $request->query->get('max_price');
  $sort = $request->query->get('sort', 'name_asc');

  // Ручная валидация параметров.
  if ($min_price !== null && !is_numeric($min_price)) {
    throw new \InvalidArgumentException('Invalid min_price value');
  }

  // Загрузка и фильтрация товаров…
  $products = $this->loadProducts((int) $min_price, (int) $max_price, $sort);

  return new JsonResponse(['products' => $products]);
}

Листинг 4. Новый способ — каждый query-параметр объявляется отдельным типизированным аргументом через #[MapQueryParameter].

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;

public function catalog(
  #[MapQueryParameter] ?int $min_price = NULL,
  #[MapQueryParameter] ?int $max_price = NULL,
  #[MapQueryParameter] string $sort = 'name_asc',
): JsonResponse {
  // Загрузка и фильтрация товаров…
  $products = $this->loadProducts($min_price, $max_price, $sort);

  return new JsonResponse(['products' => $products]);
}

Атрибут берёт имя параметра запроса из имени аргумента метода. Если нужно использовать другое имя — его можно задать явно: #[MapQueryParameter('min_price')]. Приведение типов выполняется через filter_var(), что позволяет безопасно конвертировать строки в int, float и bool.

Заметка

Drupal регистрирует только QueryParameterValueResolver, поэтому из всего семейства маппинг-атрибутов Symfony поддерживается лишь #[MapQueryParameter] — #[MapQueryString], #[MapRequestPayload] и #[MapUploadedFile] недоступны.

Сеттер‑инъекция через #[Required] в AutowireTrait

AutowireTrait и AutowiredInstanceTrait получили поддержку сеттер‑инъекции с помощью атрибута #[Required].19 Теперь не нужно переопределять конструкторы в подклассах плагинов и контроллеров.

Типичная ситуация: базовый класс плагина принимает несколько зависимостей через конструктор, а подклассу нужна дополнительная зависимость.

Листинг 5. Традиционный подход: подкласс повторяет параметры конструктора родителя, реализует ::create() и вручную извлекает сервисы из контейнера.

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class ExamplePlugin extends PluginBase implements ContainerFactoryPluginInterface {

  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    private readonly Service1Interface $service1,
    private readonly Service2Interface $service2,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
    return new self(
      $configuration, $plugin_id, $plugin_definition,
      $container->get(Service1Interface::class),
      $container->get(Service2Interface::class),
    );
  }

}

С каждой новой зависимостью конструктор и ::create() растут синхронно. Сеттер‑инъекция помогает решить эту проблему:

Листинг 6. Новый подход: зависимости добавляются через сеттер‑методы с атрибутом #[Required], без переопределения конструктора и без ::create().

use Drupal\Core\DependencyInjection\AutowireTrait;
use Symfony\Contracts\Service\Attribute\Required;

class ExamplePlugin extends PluginBase {

  use AutowireTrait;

  protected Service1Interface $service1;
  protected Service2Interface $service2;

  #[Required]
  public function setService1(Service1Interface $service1): void {
    $this->service1 = $service1;
  }

  #[Required]
  public function setService2(Service2Interface $service2): void {
    $this->service2 = $service2;
  }

}

Зависимости в сеттер‑методах разрешаются так же, как в конструкторе — по типу параметра или с помощью атрибута #[Autowire]. В одном классе можно использовать несколько #[Required]‑методов. Методы обязательно должны быть public — трейт обходит только публичные методы.

Ещё одно преимущество такого подхода, особенно в контексте плагинов, — изменения конструктора родительского класса не потребуют рефакторинга вашего кода.

Кроме того, стоит напомнить: конструкторы сервисов, контроллеров (которые тоже лучше регистрировать как сервисы20) и плагинов — это внутренний API, их не должны переопределять подклассы.21

Совет

Если сервис зарегистрирован с autowire: true, сеттер‑инъекция через #[Required] работает и без AutowireTrait — Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass обрабатывает такие методы на этапе компиляции контейнера.

Атрибут #[Bundle] для регистрации бандл-классов сущностей

Бандл-класс — это PHP-класс, назначенный конкретному бандлу сущности. Он позволяет добавлять специфичные для бандла методы и свойства прямо в классе, а не рассыпать логику по хукам и сервисам.

Появился PHP-атрибут #[Bundle], который позволяет связать бандл-класс с бандлом прямо в коде.22 Для типов сущностей с bundle_entity_type (node, media и другие) атрибут назначает PHP-класс уже существующему бандлу — вместо hook_entity_bundle_info_alter(). Раньше для этого требовался хук:

Листинг 7. Традиционный способ назначения бандл-класса через hook_entity_bundle_info_alter().

#[Hook('entity_bundle_info_alter')]
public function entityBundleInfoAlter(array &$bundles): void {
  $bundles['node']['article']['class'] = Article::class;
}

Теперь достаточно добавить атрибут #[Bundle] непосредственно к классу:

Листинг 8. Назначение бандл-класса через атрибут #[Bundle] — хук больше не нужен.

use Drupal\Core\Entity\Attribute\Bundle;
use Drupal\node\Entity\Node;

#[Bundle(
  entityType: 'node',
  bundle: 'article',
)]
class Article extends Node {}

Для типов сущностей без bundle_entity_type атрибут также позволяет объявить сам бандл — достаточно указать параметр label. Это заменяет hook_entity_bundle_info(): определение бандла и его класс оказываются в одном файле:

Листинг 9. Объявление бандла и бандл-класса через #[Bundle] для типа сущности без bundle_entity_type — вместо hook_entity_bundle_info().

use Drupal\Core\Entity\Attribute\Bundle;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\my_module\Entity\MyEntity;

#[Bundle(
  entityType: 'my_entity',
  bundle: 'foo',
  label: new TranslatableMarkup('Foo'),
)]
class Foo extends MyEntity {}

Для автоматического обнаружения класс должен находиться в пространстве имён \Entity модуля и иметь атрибут #[Bundle].

Маршруты через PHP-атрибуты

Добавлена поддержка определения маршрутов прямо в классах контроллеров с помощью PHP-атрибута #[Route].23 Это стандартный подход из Symfony, и больше не нужно дублировать информацию между YAML-файлом и PHP-кодом.

Drupal автоматически обнаруживает классы в пространстве имён Controller каждого модуля (например, Drupal\example\Controller) и регистрирует маршруты из их атрибутов. YAML-маршрутизация (*.routing.yml) продолжает работать — атрибутный подход дополняет существующую систему.

Рассмотрим пример. Раньше маршрут описывался в YAML-файле:

Листинг 10. Определение маршрута в example.routing.yml.

example.hello:
  path: '/example/hello'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::hello'
    _title: 'Hello'
  requirements:
    _permission: 'access content'

Теперь тот же маршрут можно задать атрибутом непосредственно на методе контроллера, а YAML-файл — удалить:

Листинг 11. Определение маршрута через атрибут #[Route] на методе контроллера.

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\Routing\Attribute\Route;

class ExampleController extends ControllerBase {

  #[Route(
    path: '/example/hello',
    name: 'example.hello',
    requirements: ['_permission' => 'access content'],
    defaults: ['_title' => 'Hello'],
  )]
  public function hello(): array {
    return ['#markup' => $this->t('Hello, World!')];
  }

}

Атрибут можно разместить и на уровне класса — он задаёт общий префикс имени и параметры, которые наследуют все методы. Это удобно, когда контроллер содержит несколько маршрутов с общими настройками:

Листинг 12. Атрибут #[Route] на классе задаёт общий префикс имени и требования для всех методов.

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\Routing\Attribute\Route;

#[Route(
  name: 'example.',
  requirements: ['_permission' => 'access content'],
)]
class ExampleController extends ControllerBase {

  #[Route(path: '/example/hello', name: 'hello', defaults: ['_title' => 'Hello'])]
  public function hello(): array {
    return ['#markup' => $this->t('Hello, World!')];
  }

  #[Route(path: '/example/goodbye', name: 'goodbye', defaults: ['_title' => 'Goodbye'])]
  public function goodbye(): array {
    return ['#markup' => $this->t('Goodbye, World!')];
  }

}

В листинге 12 атрибут класса задаёт префикс example. и общее требование _permission для обоих методов. Каждый метод дополняет их своим путём и суффиксом имени — в итоге регистрируются маршруты example.hello и example.goodbye.

Если контроллер реализует единственное действие, можно использовать метод __invoke(). Атрибут на классе задаёт общие настройки, атрибут на методе — суффикс имени маршрута. _controller устанавливается автоматически как имя класса, без ::__invoke:

Листинг 13. Контроллер с единственным действием с __invoke().

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\Routing\Attribute\Route;

#[Route(
  path: '/example/hello',
  name: 'example.',
  requirements: ['_permission' => 'access content'],
  defaults: ['_title' => 'Hello'],
)]
class HelloController extends ControllerBase {

  #[Route(name: 'hello')]
  public function __invoke(): array {
    return ['#markup' => $this->t('Hello, World!')];
  }

}
Работает начиная с 11.4.3

В официальном Change Record23 приведён пример, где #[Route] размещён только на классе, а ::__invoke() — без атрибута. До версии 11.4.3 это не работало из-за бага в AttributeRouteDiscovery24: условие проверки было инвертировано, из-за чего маршруты для контроллеров с __invoke() и атрибутом только на классе никогда не регистрировались. Баг исправлен в #3593939. Начиная с 11.4.3, атрибут на методе не обязателен:

#[Route(
  path: '/example/hello',
  name: 'example.hello',
  requirements: ['_permission' => 'access content'],
  defaults: ['_title' => 'Hello'],
)]
class HelloController extends ControllerBase {

  public function __invoke(): array {
    return ['#markup' => $this->t('Hello, World!')];
  }

}

Поддержка предзагрузки шрифтов в библиотеках

В библиотеках (.libraries.yml) добавлена поддержка ключа fonts для предзагрузки шрифтов.25 Ранее для этого требовалось вручную добавлять ссылки в шаблоны или использовать хуки, теперь же шрифты можно объявлять наравне с CSS и JavaScript.

Листинг 14. Пример объявления предзагружаемого шрифта в библиотеке.

global-styling:
  version: VERSION
  css:
    theme:
      css/global.css: {}
  fonts:
    fonts/metropolis/Metropolis-Regular.woff2:
      preload: true

Для каждого шрифта с preload: true Drupal добавляет в <head> страницы <link>-тег со всеми необходимыми атрибутами:

Листинг 15. Сгенерированный <link>-тег для предзагрузки шрифта.

<link href="/themes/custom/mytheme/fonts/metropolis/Metropolis-Regular.woff2"
      rel="preload" as="font" type="font/woff2" crossorigin="anonymous" />

type определяется автоматически по расширению файла. crossorigin="anonymous" добавляется всегда — без него браузер выполнит два отдельных запроса на один и тот же шрифт: один для preload, второй — при применении CSS-правила @font-face.

Заметка

Если preload: true не указан, шрифт не добавляется в HTML вовсе. Ключ fonts без preload: true ничего не делает.

Аналогичная поддержка ключа fonts добавлена в libraryOverrides внутри определений SDC-компонентов (*.component.yml).26

Листинг 16. Предзагрузка шрифта через переопределение библиотеки в SDC-компоненте.

libraryOverrides:
  fonts:
    component-font.woff2:
      preload: true

Поддержка сжатия Brotli для агрегированных ассетов

Drupal получил поддержку сжатия Brotli для агрегированных CSS- и JS-файлов.27 Brotli сжимает на 15–25 % лучше, чем gzip — меньше данных, быстрее загрузка в поддерживающих его браузерах.

Теперь при включении сжатия агрегатор генерирует сразу оба формата — .gz (gzip) и .br (Brotli). Сервер отдаёт браузеру наиболее подходящий вариант, при отсутствии поддержки — возвращаясь к gzip.

Требования к серверу:

  • Расширение PHP ext-brotli должно быть установлено для генерации .br-файлов.
  • Для Apache достаточно обновлённого файла .htaccess — специальные модули не требуются, так как файлы предварительно сжаты.
  • Для Nginx необходим модуль ngx_brotli и ручная настройка location-блоков.

В конфигурации system.performance ключи css.gzip и js.gzip объявлены устаревшими. Вместо них введены единые boolean-значения:

  • css.compress;
  • js.compress.

При обновлении сайта пост-апдейт хук system_post_update_migrate_compress_setting() автоматически мигрирует существующие значения: если gzip был включён — сжатие останется включённым, если отключён — отключится.

Если ваш модуль или профиль программно управляет сжатием ассетов, обновите код:

Листинг 17. Устаревший способ включения сжатия через настройки gzip.

$config->set('css.gzip', TRUE);
$config->set('js.gzip', TRUE);

Листинг 18. Новый способ включения сжатия через единые настройки compress.

$config->set('css.compress', TRUE);
$config->set('js.compress', TRUE);

Для Nginx добавьте в конфигурацию сервера location-блоки с директивой brotli_static:

Листинг 19. Пример настройки Nginx для раздачи предварительно сжатых Brotli- и gzip-файлов.

location ~ ^/sites/.*/files/css/(.*)\.css$ {
    gzip_static on;
    brotli_static on;
    try_files $uri.br $uri.gz $uri =404;
}

location ~ ^/sites/.*/files/js/(.*)\.js$ {
    gzip_static on;
    brotli_static on;
    try_files $uri.br $uri.gz $uri =404;
}
Brotli не заменяет gzip

Gzip-файлы продолжают генерироваться параллельно. Браузеры без поддержки Brotli автоматически получают gzip-версию. Отключение сжатия отключает оба формата одновременно.

Новый CLI dr с поддержкой команд из модулей

В ядро добавлен новый инструмент командной строки vendor/bin/dr — полноценная замена скрипту core/scripts/drupal, который поддерживал только встроенные команды ядра.28

Главное отличие: теперь модули и темы могут регистрировать собственные консольные команды, и dr обнаружит их автоматически. Чтобы команда была обнаружена, её класс должен находиться в пространстве имён Command: src/Command/ внутри модуля или темы. Команды реализуются как классы Symfony Console с атрибутом #[AsCommand], а зависимости разрешаются через автосвязывание.

Если размещение в src/Command/ неудобно, зарегистрируйте команду в контейнере сервисов с меткой console.command — она станет доступна наравне с остальными.

Минимальный пример — команда, которая читает название сайта через ConfigFactoryInterface и выводит приветствие:

Листинг 20. Команда example:hello — простейший пример регистрации пользовательской команды через #[AsCommand].

<?php

declare(strict_types=1);

namespace Drupal\my_module\Command;

use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
  name: 'example:hello',
  description: 'Greets the site by its name.',
)]
final class HelloCommand extends Command {

  public function __construct(
    private readonly ConfigFactoryInterface $configFactory,
  ) {
    parent::__construct();
  }

  protected function execute(InputInterface $input, OutputInterface $output): int {
    $io = new SymfonyStyle($input, $output);
    $site_name = $this->configFactory->get('system.site')->get('name');
    $io->success("Hello from $site_name!");

    return Command::SUCCESS;
  }

}

Для более сложных сценариев доступен весь арсенал SymfonyStyle: прогресс-бары, таблицы, интерактивные вопросы и цветной вывод.

Листинг 21. Команда example:progress — прогресс-бар и таблица с помощью SymfonyStyle.

<?php

declare(strict_types=1);

namespace Drupal\my_module\Command;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
  name: 'example:progress',
  description: 'Lists all content types with a progress bar.',
)]
final class ProgressCommand extends Command {

  public function __construct(
    private readonly EntityTypeManagerInterface $entityTypeManager,
  ) {
    parent::__construct();
  }

  protected function execute(InputInterface $input, OutputInterface $output): int {
    $io = new SymfonyStyle($input, $output);

    $types = $this->entityTypeManager->getStorage('node_type')->loadMultiple();
    if (!$types) {
      $io->warning('No content types found.');
      return Command::SUCCESS;
    }

    $io->progressStart(count($types));

    $rows = [];
    foreach ($types as $type) {
      $rows[] = [$type->id(), $type->label()];
      $io->progressAdvance();
    }

    $io->progressFinish();
    $io->table(['Machine name', 'Label'], $rows);

    return Command::SUCCESS;
  }

}

Разобравшись, как регистрируются собственные команды, посмотрим на встроенный набор, доступный в ядре на момент релиза:

  • completion — генерирует скрипт автодополнения команд для bash, zsh или fish;
  • generate-theme — создаёт новую тему на основе стандартных шаблонов ядра;
  • help — справка по команде;
  • install — установка Drupal через профиль или рецепт;
  • list — список всех команд;
  • quick-start — установка сайта и запуск локального веб-сервера;
  • server — запуск встроенного веб-сервера;
  • cache:rebuild (псевдонимы: cr, rebuild) — сброс всех кешей;
  • content:export — экспорт контентных сущностей в формат YAML;
  • recipe:apply (псевдоним: recipe) — применение рецепта к сайту;
  • recipe:info — информация о рецепте;
  • system:cron (псевдонимы: cron, core:cron) — запуск cron;
  • system:status (псевдоним: status) — статус системы.

Обратите внимание: сейчас отсутствуют команды для экспорта и импорта конфигураций, а также для запуска hook_update_N() и hook_post_update_NAME(). Вероятно, они появятся в будущих релизах или через сторонние модули. Пока dr — не замена Drush, но в перспективе должен ею стать.

Производительность 11.3 vs 11.4

Теперь сравним производительность Drupal 11.3.12 и 11.4.0-rc2. Вот условия теста:

  • Установочный профиль: Demo Umami — он уже наполнен контентом и имеет готовую вёрстку. Это не просто сайт-пустышка, а вполне себе неплохой референс для тестов.
  • Тестируемые страницы (одинаковы для всех сценариев):
    • / — главная
    • /articles — список статей
    • /articles/give-your-oatmeal-the-ultimate-makeover — страница статьи
    • /recipes — список рецептов
    • /recipes/deep-mediterranean-quiche — страница рецепта
  • Нагрузочный тест: siege 4.1.7, siege -c5 -r100 --no-parser [URL] — 5 потоков по 100 запросов каждый. Перед запуском делается 5 запросов на прогрев каждой из страниц. Сценарии:
    • dev_mode — режим разработки: drush theme:dev on отключает все кеши и включает отладку Twig шаблонов и тем-хуков.
    • no_cache — page_cache и dynamic_page_cache отключены.
    • dynamic_cache — включён только dynamic_page_cache.
    • full_cache — включены page_cache и dynamic_page_cache.
  • Холодный старт — первый запрос сразу после полной очистки кеша (drush cr). 5 повторений (каждое после очистки) на каждую страницу.
  • Окружение: Docker-контейнеры на локальной машине.
    • База данных: MariaDB 11.8.8
    • Веб-сервер: Nginx 1.31.2
    • PHP: 8.5.7 с OPcache

Эти тесты не про абсолютную производительность Drupal — нас интересует только то, как изменились цифры между версиями на одном железе с идентичным окружением, чтобы видеть прогресс.

График сравнения запросов в секунду: Drupal 11.3.12 vs 11.4.0-rc2 по сценариям кеширования

Рисунок 1. Среднее количество запросов в секунду по сценариям кеширования. Больше — лучше.

  • dev_mode — 24,35 → 27,16 req/s (+11,53%)
  • no_cache — 106,91 → 111,87 req/s (+4,63%)
  • dynamic_cache — 152,61 → 149,72 req/s (−1,90%)
  • full_cache — 300,92 → 301,68 req/s (+0,25%)

По нагрузочному тестированию ярко выражено улучшение в сценариях dev_mode и no_cache, что вполне ожидаемо: основная масса улучшений производительности была направлена именно на то, как и что загружает Drupal, если данных ещё нет в кеше. Остальные показатели, я бы сказал, на уровне погрешности — хоть dynamic_cache и просел на -1,90%, я бы, наверное, отнёс это к шуму измерений, так как никаких явных изменений в этом направлении в 11.4 не припоминаю.

График холодного старта: Drupal 11.3.12 vs 11.4.0-rc2 — время первого запроса после очистки кеша

Рисунок 2. Время первого запроса после полной очистки кеша (drush cr), мс. Меньше — лучше.

  • / — 592 → 582 мс (−1,78%)
  • /recipes — 612 → 580 мс (−5,11%)
  • /recipes/deep-mediterranean-quiche — 550 → 576 мс (+4,63%)
  • /articles — 555 → 540 мс (−2,64%)
  • /articles/give-your-oatmeal-the-ultimate-makeover — 539 → 507 мс (−5,84%)
  • Среднее: -2,15%

Для холодного старта результаты в целом ожидаемы: стабильное снижение времени первого запроса в среднем на 2–3%. Исключение — /recipes/deep-mediterranean-quiche, где 11.4.0-rc2 показал незначительную деградацию (+4,63%), но откуда она — непонятно: в остальных сценариях страница показывает стабильные улучшения.

В общем, что 11.4 стал чуточку быстрее, чем 11.3, особенно в сценариях, когда кеш ещё недоступен. Это приятно — сайты будут оживать быстрее после деплоя. Но не стоит ожидать прорыва — достойное минорное улучшение для минорного релиза, не более.

Дополнительные улучшения и изменения

API и разработка

  • Объявлен устаревшим метод Drupal\Core\Theme\Registry::getBaseHook(), который не имеет замены из-за отсутствия реальных случаев использования.29
  • Объявлены устаревшими константа (\Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT) и свойство ($recursiveRenderDepth), ограничивающие рекурсивный рендеринг, поскольку введён новый механизм защиты от бесконечных циклов на основе отслеживания состояния рендеринга.30
  • Добавлен новый метод ::getSummary() в интерфейс FieldTypeCategoryInterface, позволяющий отображать развёрнутые описания категорий полей в административном интерфейсе.31
  • AJAX page state теперь передаётся как атрибут запроса вместо query-параметра, что требует обновления кода для его получения.32
  • Добавлены параметры $page_top и $page_bottom к методу HtmlRenderer::buildPageTopAndBottom() для управления соответствующими переменными страницы.33
  • Добавлена возможность встраивать содержимое в верхнюю и нижнюю часть страницы через атрибут #attached в контроллере, как альтернатива хукам.34
  • Объявлены устаревшими все функции в файле locale.translations.inc. Их функциональность перенесена в новые сервисы.35
  • Объявлены устаревшими функции работы с файлами локализации из locale.bulk.inc и locale.batch.inc. Их функциональность перенесена в VO LocaleFile и сервис LocaleFileManager.36
  • Объявлены устаревшими все функции в файле locale.fetch.inc. Их функциональность перенесена в сервис LocaleFetch.37
  • Добавлен метод EntityTypeInterface::hasIntegerId(), который определяет, является ли ID сущности целочисленным. Объявлены устаревшими DefaultHtmlRouteProvider::getEntityTypeIdKeyType(), CommentTypeForm::entityTypeSupportsComments() и _comment_entity_uses_integer_id().38
  • Реализации методов ::getSortedDefinitions() и ::getGroupedDefinitions() интерфейса CategorizingPluginManagerInterface теперь требуют аргумент $label_key.39
  • Реализации метода ExecutableInterface::execute() теперь требуют аргумент $object.40
  • Метод ConfigManager::findConfigEntityDependenciesAsEntities() теперь возвращает конфигурационные сущности без применения переопределений (overrides), используя ::loadMultipleOverrideFree() вместо ::loadMultiple().41
  • Добавлен метод SelectInterface::getRange(), позволяющий получить текущие параметры диапазона (start и length) из объекта Select-запроса.42
  • Изменён порядок поиска временной директории в FileSystem::getOsTemporaryDirectory(): результат sys_get_temp_dir() теперь проверяется раньше /tmp, что может изменить расположение временных файлов.43
  • Метод ::hasRole() перенесён из UserInterface в AccountInterface, что делает его доступным на уровне базового интерфейса сессий.44
  • Outbound path processor теперь получают route_name и route_parameters в массиве $options.45
  • Плагины хранилищ секций Layout Builder теперь должны реализовывать интерфейс SupportAwareSectionStorageInterface.46
  • FormBase теперь включает AutowireTrait, предоставляя автоматический метод ::create() с разрешением зависимостей по типам. Это позволяет убрать шаблонный ::create() из форм.4720
  • Метод Url::createFromRequest() теперь автоматически сохраняет query-параметры из запроса, что устраняет необходимость в ручном вызове ::setOption('query', ...).48
  • AutowireTrait и AutowiredInstanceTrait теперь поддерживают передачу параметров контейнера в конструкторы классов, реализующих ContainerInjectionInterface и ContainerFactoryPluginInterface.4920
  • Добавлен сервис \Drupal\Core\Field\FieldPurger для очистки данных полей. Функции field_purge_batch(), field_purge_field() и field_purge_field_storage() объявлены устаревшими.50
  • Метод ::setMultiple() хранилища ключ-значение на базе БД теперь выполняется в транзакции, что гарантирует атомарность операции — все значения сохраняются либо целиком, либо не сохраняются вовсе.51
  • В интерфейс KeyValueStoreInterface добавлен метод ::getAllKeys(), позволяющий получить все ключи коллекции без загрузки самих значений, что снижает потребление памяти при работе с большими коллекциями.52
  • Добавлен пакет symfony/polyfill-php86, который делает доступными возможности PHP 8.6 — функцию clamp(), константу ARRAY_FILTER_USE_VALUE и перечисляемый тип SortDirection — при работе на более ранних версиях PHP.53
  • В метод ExtensionList::getList() добавлен необязательный аргумент $skip_cache = FALSE. При передаче TRUE обходится как постоянный, так и статический кеш, и список расширений пересчитывается из файловой системы без обновления кеша. Это замена паттерна ->reset()->getList(), который инвалидировал cache_bootstrap на каждом admin-запросе.54

Производительность и кеширование

  • Поля сущностей, у которых разрешён только один элемент значения (лимит = 1), теперь загружаются из базы данных одним запросом — раньше для каждого такого поля выполнялся отдельный запрос.55
  • Теперь загрузка полей с множественным значением стала эффективнее: вместо отдельных запросов для каждого поля формируется один SQL‑запрос.56
  • Улучшена производительность сортировки ленивой коллекции плагинов за счёт исключения ненужного создания экземпляров плагинов.57
  • Оптимизированы операции записи в БД при изменении статуса ревизий контента.58
  • Улучшена производительность перестроения меню за счёт сокращения количества запросов к базе данных: добавлена предзагрузка всех существующих ссылок одним запросом вместо индивидуальных SELECT-запросов для каждой ссылки.59
  • Добавлена новая настройка ($settings['asset_gc_threshold']) — она позволяет управлять сроком хранения агрегированных файлов ассетов.60 По умолчанию этот срок составляет 45 дней.
  • Добавлен статический кеш для определений хранилищ полей (field definitions).61 Это позволяет сократить количество обращений к кеш‑бэкенду и повысить производительность.
  • Добавлено статическое кеширование для конфигурационных сущностей стилей изображений, что уменьшает количество запросов к кешу и базе данных.62
  • Улучшено кеширование определений полей для типов сущностей: теперь определения для всех бандлов одного типа сущности кешируются одной операцией, что уменьшает количество обращений к кеш-бэкенду.63
  • JSON:API больше не проверяет каждый ответ на соответствие схеме по умолчанию. Валидация перенесена в тестовый модуль jsonapi_response_validator, что повышает производительность.64
  • Нормализация JSON:API теперь пропускает кеширование для ResourceObject с max-age: 0, что позволяет избежать накладных расходов на запись и чтение кеша при обработке больших коллекций сущностей.65
  • Метаданные кеширования вычисляемых полей (computed) теперь корректно передаются в ответы JSON:API, если класс списка элементов поля реализует CacheableDependencyInterface.66
  • Оптимизировано количество вызовов FieldDefinition::getColumns() при загрузке сущностей: результаты теперь кешируются в локальные массивы перед обработкой записей из базы данных.67
  • Оптимизирован метод CacheTagsChecksumTrait::calculateChecksum(): замена array_diff() и array_keys() на цикл с isset() сокращает количество вызовов при загрузке большого числа сущностей с холодным кешем.68
  • Из сборщика кеша активного пути меню (ActiveTrailCacheCollector) убрана блокировка при записи, что снижает накладные расходы при параллельных запросах.69
  • Исправлено значение заголовка X-Drupal-Dynamic-Cache для 4xx и 5xx ответов: вместо вводящего в заблуждение UNCACHEABLE (poor cacheability) теперь возвращается UNCACHEABLE (XXX), где XXX — фактический HTTP-статус (например, UNCACHEABLE (403) или UNCACHEABLE (404, sub-request: HIT)).70
  • Улучшена производительность проверки доступа к теме (ThemeAccessCheck). Теперь используется список тем из контейнера вместо загрузки всех данных темы, что сокращает количество обращений к кешу.71
  • Ответы 404 теперь кешируемые: Router::matchRequest() выбрасывает CacheableResourceNotFoundException вместо ResourceNotFoundException, что позволяет кешировать 404-ответы с корректными метаданными.72
  • Добавлено статическое кеширование результата EntityDataDefinition::getDataType(), что сокращает количество вызовов при пакетной обработке сущностей.73
  • Оптимизировано кеширование прототипов в TypedDataManager: ссылка на родительский объект больше не включается в прототип — она присваивается при использовании, что устраняет утечки памяти и повышает эффективность LRU-кеша сущностей.74
  • Удалена ненужная очистка CSS-кеша при установке тем, поскольку кеш CSS не зависит от темы.75
  • MainContentViewSubscriber переведён на использование #[AutowireLocator], что позволяет инстанцировать только нужный рендерер контента вместо загрузки всех доступных.76
  • Метод EntityRepository::loadEntityByUuid() теперь использует статический кеш для хранения пар UUID–ID в памяти, что позволяет избежать повторных SQL-запросов при многократных обращениях к одной и той же сущности в рамках одного запроса.77
  • Свойство EntityBase::$typedData теперь хранится как WeakReference, что устраняет циклическую ссылку и снижает потребление памяти (~3 КБ на сущность).78
  • Добавлен новый кеш-контекст exception_status_code для условия видимости блоков на страницах с HTTP-исключениями (403, 404). Ранее использовался url.path, что приводило к низкому hit rate кеша блоков — теперь кеш варьируется только по коду статуса, а не по полному URL.79
  • Устранено избыточное сканирование файловой системы для локальных po-файлов в модуле locale.80
  • Устранена загрузка форматов дат из конфигурации при получении информации об элементах (element info), что сокращает два лишних обращения к конфигурационным сущностям и кешу на каждый запрос с холодным кешем.81
  • Добавлен новый кеш-бин cache.file_parsing для постоянного кеширования результатов разбора файлов. В отличие от стандартных бинов, он не имеет тега cache.bin и не очищается при drush cr или drupal_flush_all_caches(), что сохраняет кеш между деплоями. Добавлен базовый класс FileParsingCacheCollectorBase с валидацией по mtime; новый YamlCacheCollector уже использует этот бин для разбора libraries.yml и routing.yaml.82
  • Удалён класс RoutePreloader, который при каждом HTML-запросе загружал все публичные маршруты и потреблял значительный объём памяти. Кеш маршрутов переведён на выделенный bin routes с поддержкой APCu через fast-chained механизм.83

Устаревшая функциональность и изменения в обратной совместимости

  • Объявлены устаревшими модули Migrate Drupal (инструменты миграции с Drupal 6 и 7)84, History85 и Contact86.
  • Плагин поиска node_search перемещён из модуля node в новый подмодуль search_node модуля search. Классы, расширяющие NodeSearch, необходимо обновить для расширения \Drupal\search_node\Plugin\Search\SearchNode.87
  • Объявлены устаревшими batch-функции из файлов locale.batch.inc, locale.bulk.inc и locale.compare.inc. Их функциональность перенесена в методы сервиса LocaleFetch.88
  • Объявлены устаревшими процедурные функции из locale.compare.inc — locale_translation_flush_projects(), locale_translation_build_projects(), locale_translation_check_projects(), locale_translation_check_projects_local() и другие — а также сервис locale.project и интерфейс LocaleProjectStorageInterface. Их функциональность перенесена в новые сервисы LocaleProjectRepository и LocaleProjectChecker.89
  • Объявлены устаревшими функции locale_translation_get_file_history(), locale_translation_update_file_history() и locale_translation_file_history_delete(). Их функциональность перенесена в новый сервис CurrentImportStateStorage и value object CurrentImportState.90
  • Объявлены устаревшими функции статуса переводов локализации: locale_translation_get_status(), locale_translation_status_save(), locale_translation_status_delete_languages(), locale_translation_status_delete_projects() и locale_translation_clear_status(). Вместо них следует использовать сервис LocaleSource с методами ::loadSources() и ::loadSource().91
  • Объявлены устаревшими функции с префиксом _ из editor.module, их функциональность перемещена в методы класса EditorHooks.92
  • Объявлены устаревшими функция editor_image_upload_settings_form() и файл editor.admin.inc. Логика перенесена в сервис EditorImageUploadSettings и его метод ::getForm().93
  • Объявлена устаревшей функция editor_filter_xss(). Её функциональность перенесена в метод Element::filters().94
  • Объявлены устаревшими статические методы Views::pluginManager() и Views::handlerManager() в пользу Dependency Injection или Service Locator.95
  • Файл views_ui/admin.inc объявлен устаревшим. Процедурные функции перенесены в трейты ViewsFormAjaxHelperTrait и ViewsFormHelperTrait.96
  • Объявлен устаревшим метод CachePluginBase::getRowCacheKeys() и удалено дублирующее кеширование отдельных строк представлений для улучшения производительности.97
  • Объявлен устаревшим метод CachePluginBase::cacheExpire() модуля Views, поскольку система кеширования уже предотвращает возврат просроченных результатов.98
  • Объявлены устаревшими функции views_ui_contextual_links_suppress(), views_ui_contextual_links_suppress_push() и views_ui_contextual_links_suppress_pop() без замены, так как их функциональность давно не работала корректно.99
  • Метод ViewExecutable::getHandler() переименован в ViewExecutable::getHandlerConfiguration() — старое название вводило в заблуждение, так как метод возвращает конфигурацию обработчика, а не его экземпляр. Старый метод объявлен устаревшим и будет удалён в Drupal 13.100
  • Функция comment_preview() объявлена устаревшей — её логика перемещена в метод CommentForm::preview().101
  • Константы для настроек обратной связи анонимных пользователей в CommentInterface объявлены устаревшими. Вместо них добавлен перечисляемый тип AnonymousContact.102
  • Объявлены устаревшими константы CommentItemInterface::FORM_SEPARATE_PAGE и FORM_BELOW. Вместо них следует использовать перечисляемый тип FormLocation со значениями SeparatePage и Below.103
  • Объявлено устаревшим недокументированное свойство User::$password.104
  • Функции user_cookie_save() и user_cookie_delete() объявлены устаревшими. Вместо них следует использовать методы ::setCookie() и ::clearCookie() объекта Symfony Response.105
  • Функция user_form_process_password_confirm() объявлена устаревшей. Вместо неё следует использовать UserThemeHooks::processPasswordConfirm().106
  • Объявлены устаревшими функции однократной аутентификации пользователей: user_pass_rehash(), user_cancel_url(), user_mail_tokens() и user_pass_reset_url(). Их функциональность перенесена в новый сервис OneTimeAuthentication с методами ::generateHmac(), ::generateCancelConfirmUrl(), ::tokens() и другими.107
  • Функция node_access_grants() объявлена устаревшей. Вместо неё следует использовать сервис NodeGrantsHelper и его метод ::nodeAccessGrants(). Классы NodeAccessGrantsCacheContext и NodeGrantDatabaseStorage теперь требуют NodeGrantsHelper в качестве аргумента конструктора.108
  • Функции node_access_rebuild() и node_access_needs_rebuild() объявлены устаревшими. Вместо них следует использовать сервис NodeAccessRebuild с методами ::rebuild(), ::needsRebuild() и ::setNeedsRebuild().109
  • Объявлен устаревшим класс \Drupal\node\Controller\NodeViewController. Он функционально идентичен EntityViewController, и вместо него следует использовать \Drupal\Core\Entity\Controller\EntityViewController.110
  • Объявлена устаревшей библиотека node/form и файл node.module.css, содержавший неиспользуемые стили.111
  • Объявлено устаревшим использование длинного формата подсказок фильтров текста и страницы для их отображения.112
  • Добавлен новый сервис FilterFormatRepositoryInterface для работы с форматами фильтров. Функции filter_formats(), filter_formats_reset(), filter_get_formats_by_role(), filter_default_format() и filter_fallback_format() объявлены устаревшими.113
  • Функция check_markup() объявлена устаревшей без прямой замены. Вместо неё рекомендуется возвращать рендер-массив с типом #type => 'processed_text', который сохраняет метаданные кеширования.114
  • Функция text_summary() объявлена устаревшей. Её функциональность перенесена в сервис TextSummary, метод ::generate() которого принимает те же аргументы.115
  • Класс InstallerRouteBuilder удалён без замены — он стал ненужным после введения обнаружения маршрутов через PHP-атрибуты.116
  • Module handler и controller resolver исключены из конструктора RouteBuilder: PHP-атрибуты для определения маршрутов устранили необходимость в них.117
  • Объявлены устаревшими плагины обработки миграций LinkOptions, LinkUri, Timezone и UserLangcode из модулей menu_link_content, system и user. Их функциональность перенесена в аналогичные классы модуля migrate.118
  • Объявлены устаревшими процедурные функции _contextual_links_to_id() и _contextual_id_to_links() и добавлен новый сервис ContextualLinksSerializer для их замены.119
  • Объявлены устаревшими конструкторы ограничений (констрейнов) с массивом опций и введена поддержка именованных аргументов для улучшения типобезопасности API.120
  • Множество процедурных функций отправки форм, валидации и AJAX‑функций обратного вызова объявлены устаревшими.121
  • Объявлено устаревшим использование свойства #item_attributes в тем-хуках image_formatter и responsive_image_formatter.122 Теперь следует использовать стандартное свойство #attributes.
  • Добавлена настройка для блоков системных меню, позволяющая управлять добавлением CSS-класса текущей страницы.123
  • Объявлен устаревшим класс WebDriverCurlService.124
  • Метод LinkWidget::validateTitleElement() объявлен устаревшим. Валидация перенесена в LinkTitleRequiredConstraintValidator на уровне плагина поля LinkItem.125
  • Передача NULL в качестве параметра $deserialization_target_class конструктора ResourceType объявлена устаревшей. Вместо этого следует использовать stdClass::class.126
  • Ключ uri_callback в аннотации типа сущности объявлен устаревшим. Вместо него следует использовать link templates или outbound path processor.127
  • Удалена функция _update_cron_notify(). Её логика перенесена в UpdateCronHooks, а отправка почты — в сервис MailHandler модуля Update.128
  • Адреса электронной почты в устаревших форматах (например, с пробелом перед символом @) теперь не проходят валидацию форм. Ранее такие адреса принимались, но почтовые серверы обычно отвергали их при отправке.129
  • Объявлено устаревшим обращение к глобальной переменной $autoload / $GLOBALS['autoload'] для получения Composer-автозагрузчика. Вместо этого следует явно подключать автозагрузчик через require '/vendor/autoload.php'.130
  • Объявлены устаревшими функции hide() и show(). Вместо них следует напрямую управлять свойством #printed рендер-массива: $element['#printed'] = TRUE для скрытия и $element['#printed'] = FALSE для отображения.131
  • Объявлены устаревшими все процедурные функции из menu_ui.module. Их функциональность перенесена в сервис MenuUiHelper и методы класса MenuUiHooks.132
  • Объявлен устаревшим трейт ToStringTrait. Вместо него метод __toString() следует реализовывать напрямую, а класс — объявлять реализующим интерфейс \Stringable.133
  • Объявлены устаревшими аргументы смещения курсора и ориентации в методе StatementInterface::fetch(). Они не тестировались и, вероятно, не работали корректно — в Drupal 12 будут удалены.134
  • Объявлен устаревшим защищённый метод SqlContentEntityStorage::loadFromSharedTables() без замены. Его логика объединена с ::loadFromDedicatedTables().135
  • Использование значений, отличных от булевого типа или объекта AccessResultInterface для ключа #access в рендер массивах, объявлено устаревшим.136
  • Объявлены устаревшими функции dblog_filters и _dblog_get_message_types, а также файл dblog.admin.inc. Вместо них следует использовать сервис DbLogFilters.
  • Объявлена устаревшей функция block_theme_initialize(). Логика перенесена в защищённый метод класса BlockHooks без публичной замены.137
  • Объявлен устаревшим метод SessionManager::delete(). Вместо него следует использовать UserSessionRepository::deleteAll().138
  • Объявлена устаревшей концепция «доверенных данных» (trusted data) в конфигурации: метод ConfigEntityInterface::trustData() и параметр $has_trusted_data в Config::save(). Теперь валидация схемы конфигурации, приведение типов и сортировка ключей применяются ко всем сохранениям конфигурации автоматически.139
  • Уточнены типы возвращаемых значений метода ::normalize() в классах нормализаторов сериализации: ComplexDataNormalizer, ConfigEntityNormalizer, ContentEntityNormalizer, EntityReferenceFieldItemNormalizer, ListNormalizer, TimestampItemNormalizer теперь возвращают array, MarkupNormalizer — string, NullNormalizer — null.140141
  • Объявлены устаревшими функции file_get_file_references() и file_field_find_file_reference_column(). Вместо первой следует использовать сервис FileReferenceResolver и его метод ::getReferences(); вторая не имеет замены.142
  • Объявлен устаревшим параметр $sql_query метода Query::getTables() — объект запроса теперь определяется из самого entity query, и передавать его явно больше не нужно. Кроме того, Query::condition() больше не принимает объект Condition, созданный в контексте другого entity query; в Drupal 13 это будет явно запрещено.143
  • Объявлена устаревшей валидация CSRF-токенов по ключу 'rest' в CsrfRequestHeaderAccessCheck. Этот запасной ключ сохранялся с Drupal 8 для совместимости с сессиями, созданными до обновления до Drupal 9, — теперь такие сессии не поддерживаются. В Drupal 12 валидация по ключу 'rest' удалена полностью.144
  • Метод RowPluginBase::render() теперь принимает только экземпляры \Drupal\views\ResultRow. Передача любых других аргументов вызывает предупреждение об устаревании — в Drupal 12 метод получит строгую типизацию.145

Пользовательский интерфейс и UX

  • Улучшена валидация ссылок в виджете LinkWidget: теперь сообщения об ошибках адаптированы под тип ссылки, делая их более понятными для пользователей.146
  • Виджеты и форматеры поля основного текста в стандартном профиле и рецептах изменены: теперь используется text_long вместо text_with_summary.147
  • При ручном создании учётной записи уведомление по электронной почте с инструкциями по установке пароля теперь включено по умолчанию.148
  • Форма настройки информации о сайте теперь сохраняет пути к главной странице, 403 и 404 страницам в том виде, в котором их ввёл пользователь, включая неразрешённые псевдонимы, а не преобразует их во внутренние пути.149
  • Добавлена поддержка полноэкранного режима редактирования в CKEditor 5.150
  • Виджет поля Link теперь поддерживает формат route:{$route_name}, сохраняя префикс route: при повторном сохранении содержимого.151
  • Из модуля Navigation удалены жёстко заданные ссылки на создание пользователей, а также медиа-сущностей «Изображение» и «Документ» из меню «Содержимое». При необходимости ссылки можно добавить обратно вручную через /admin/structure/menu/manage/content.152
  • Модуль Navigation добавлен в стандартный профиль установки и рецепт, заменяя Toolbar в качестве навигации по умолчанию.153
  • При удалении темы через интерфейс теперь отображается страница подтверждения с перечнем конфигураций, которые будут удалены или изменены. Это позволяет отменить удаление до применения изменений.154
  • Форматтер строковых полей теперь поддерживает ссылку не только на страницу просмотра сущности (canonical), но и на форму её редактирования. Тип ссылки выбирается в настройках форматтера.155
  • HTML5-валидация форм отключена по умолчанию из-за проблем с доступностью. Добавлена настройка enable_html5_validation в settings.php для управления поведением, а также предупреждение на странице отчёта о состоянии.156
  • Отключённые ссылки меню теперь игнорируются при построении активного пути (active trail), что устраняет некорректное назначение CSS-класса «active» родительским элементам меню.157
  • Вкладка «Управление отображением» теперь ведёт на новую обзорную страницу режимов отображения /admin/structure/types/manage/{bundle}/display вместо формы редактирования режима по умолчанию. Страница отображает все режимы отображения бандла со статусом и позволяет включать или отключать их напрямую.158

Темы и фронтенд

  • Вместо собственных CSS-правил Views (views-align-*) для выравнивания содержимого столбцов таблиц теперь используются классы из модуля system (align-left, align-center, align-right), обеспечивающие идентичное поведение.159
  • Обновлены стандартные иконки типов файлов: созданы новые высококачественные SVG-файлы, заменяющие устаревшие растровые PNG для улучшенного отображения на экранах с высоким разрешением.160
  • Удалены избыточные WAI-ARIA role-атрибуты из шаблонов, дублирующие семантику современных HTML5-элементов, для улучшения соответствия стандартам.161
  • Добавлена поддержка Twig-функции html_cva() из пакета twig/html-extra для реализации паттерна Class Variance Authority (CVA), упрощающего условное управление CSS-классами в шаблонах. Помимо html_cva(), стали доступны функции html_attr() и html_classes().162
  • Из темы Default Admin удалены шаблоны для модулей Book и Forum, ранее вынесенных из ядра.163
  • Ссылки, создаваемые Twig-функциями help_route_link() и help_topic_link(), больше не формируются абсолютными по умолчанию. Для получения абсолютных URL следует явно передать $options['absolute'] = TRUE.164
  • Добавлен объект расширения темы с методами ::listAllRegions(), ::listVisibleRegions() и ::getDefaultRegion() для работы с регионами темы. Объявлены устаревшими функции system_region_list() и system_default_region(), а также константы REGIONS_VISIBLE и REGIONS_ALL.165
  • SDC-компоненты теперь можно использовать как элементы форм. ComponentElement реализует FormElementInterface и зарегистрирован как #[FormElement('component')], что позволяет использовать #type: component с поддержкой валидации и значений по умолчанию (но без #ajax, #process и других PHP-only свойств).166
  • Для слотов SDC-компонентов добавлена возможность объявлять ожидаемые дочерние компоненты (expected — по ID или тегу) и ограничивать их количество (minItems, maxItems) в *.component.yml.167
  • Тем-хук navigation__message удалён — компонент сообщений модуля Navigation переведён на SDC.168
  • Изменено поведение атрибутов блока: ключ #attributes в рендер-массиве содержимого блока теперь применяется к обёртке содержимого (.content), а не к внешней обёртке блока. Для применения атрибутов ко всему блоку следует использовать #wrapper_attributes.169
  • Свойство #url в тем-элементе responsive_image_formatter теперь является объектом Url вместо строки. В Twig-шаблоне responsive-image-formatter.html.twig для получения строки URL следует использовать url.toString().170

Модули и расширяемость

  • Добавлена поддержка автосвязывания (autowiring) для плагинов ImageToolkit и ImageToolkitOperation.171

Конфигурация и развёртывание

  • Удалён модуль history из стандартного профиля установки и рецепта, подготовка к его переносу в сторонний модуль.172
  • Модуль Shortcut удалён из стандартного профиля установки и рецепта, в рамках подготовки к переносу в контрибутный модуль.173
  • Типы контента «Статья» и «Страница» больше не устанавливаются при использовании стандартного профиля установки или рецепта. Новые сайты должны самостоятельно настраивать необходимые типы контента.174
  • Шаблон проекта drupal/legacy-project объявлен заброшенным (abandoned). Рекомендуется использовать drupal/recommended-project.175
  • Метапакет drupal/core-dev-pinned объявлен устаревшим. Вместо него рекомендуется использовать drupal/core-dev.176
  • Добавлена новая конфигурационная операция (core.menu.static_menu_link_overrides:overrideMenuLinks).177 Она позволяет переопределять статические ссылки меню — например, изменять их свойства (вес и состояние) с помощью рецептов.
  • Модуль Locale теперь использует хеш файла (xxh128) вместо времени модификации (filemtime()) для обнаружения изменений в локальных файлах переводов, что повышает надёжность в современных средах развёртывания (например, Docker, где COPY не сохраняет временные метки файлов).178
  • Значение version в файлах .info.yml теперь должно быть строкой. Числовые значения вроде 1.0 приводили к проблемам парсинга (например, 1.0 упрощалось до 1).179
  • Библиотека justinrainbow/json-schema теперь считается полноценной зависимостью, а не только зависимостью для разработки.180
  • В стандартный robots.txt добавлены правила блокировки индексации страниц поиска с query-параметрами (/search?, /index.php/search?), что предотвращает индексацию динамически генерируемых страниц поисковых результатов.181
  • Импорт содержимого по умолчанию (default content) теперь поддерживает формат JSON наряду с YAML.182
  • Drupal перешёл на компонент symfony/runtime для разделения логики начальной загрузки и обработки запросов. Фронт-контроллеры (index.php, update.php) обновлены под новый паттерн с классом DrupalRuntime. При запуске composer update может потребоваться добавить symfony/runtime в секцию allow-plugins файла composer.json.183
  • Стандартный профиль установки и рецепт standard больше не включают модуль comment. Функционал комментариев теперь необходимо включать явно — сам модуль по-прежнему входит в ядро.184
  • drupal/core-recommended больше не закрепляет минорные версии ряда зависимостей (guzzlehttp/guzzle, guzzlehttp/psr7, twig/twig, symfony/polyfill-* и других). Теперь сайты смогут применять обновления безопасности этих пакетов сразу после релиза, не дожидаясь нового выпуска Drupal Core — это поведение стало необходимым из-за изменений в Composer 2.9.185

Тестирование и качество кода

  • Drupal перешёл на использование W3C-совместимого веб-драйвера для тестирования.186
  • Объявлен устаревшим метод ::expectDeprecation() и трейт ExpectDeprecationTrait. Вместо них следует использовать нативные методы PHPUnit ::expectUserDeprecationMessage() и ::expectUserDeprecationMessageMatches().187
  • Добавлен кастомный ErrorFormatter для PHPStan, позволяющий объединить вывод нескольких форматов (JUnit, GitLab, таблица) в одном запуске вместо многократного выполнения анализа.188
  • Добавлен трейт HttpKernelUiHelperTrait с методом ::drupalGet() для ядерных (kernel) тестов, позволяющий выполнять HTTP-запросы и проверять содержимое ответа с помощью Mink. Это даёт возможность конвертировать многие браузерные тесты в значительно более быстрые kernel-тесты.189
  • Сокращено количество пересборок контейнера при выполнении функциональных тестов, что значительно ускоряет их прохождение.190
  • Публичные методы ::testGet(), ::testPost(), ::testPatch() и ::testDelete() в EntityResourceTestBase переименованы в защищённые ::doTest*() и объединены в один публичный метод ::testCrud().191

Содержимое и структура данных

  • Поле UUID теперь валидирует хранимое значение. Ранее проверялась только длина строки, что позволяло сохранять невалидные UUID.192
  • В поле ссылки добавлено вычисляемое свойство resolvable_uri, содержащее готовый URL с учётом всех URL-опций (query, fragment и др.) — в отличие от uri, которое хранит сырой вид (internal:/, entity:node/5). Свойство доступно в Twig-шаблонах и через JSON:API/REST.193

Доступ и безопасность

  • Добавлена новая операция контроля доступа 'view linked label' для сущности пользователя, определяющая возможность отображения имени как ссылки на профиль.194
  • Объявлена устаревшей конфигурация locale.settings:translation.path. Вместо неё следует использовать настройку $settings['locale_translation_path'] в settings.php.195
  • Добавлена возможность настройки алгоритма и параметров хеширования паролей через параметры ядра password.algorithm и password.options в services.yml.196
  • В трейт HttpKernelUiHelperTrait добавлен метод ::clickLink(), позволяющий имитировать клик по ссылке в kernel-тестах без необходимости использовать браузерные тесты.197
  • Методы AccessResult::allowedIf() и AccessResult::forbiddenIf() теперь принимают необязательный аргумент с причиной нейтрального результата доступа.198
  • Маршруты HTTP-аутентификации user.login.http, user.pass.http, user.login_status.http и user.logout.http перенесены в модуль REST и переименованы (rest.login, rest.pass, rest.login_status, rest.logout). Контроллер UserAuthenticationController заменён на RestAuthenticationController.199
  • Добавлено новое разрешение 'view unpublished block content', позволяющее редакторам просматривать неопубликованные блок-контент-сущности без необходимости иметь разрешения «administer block content» или «access block library».200

  1. Drupal 11.4.0. Релизы Drupal. Не опубликовано на момент выхода материала. ↩

  2. Gin Admin Theme. Проект Gin на Drupal.org. ↩

  3. Drupal core will adopt Gin admin theme to replace Claro. Блог разработчиков Drupal. 2025-06-20. ↩

  4. Merging Gin as Admin theme. Задача на Drupal.org. Дата обращения: 2026-03-03. ↩

  5. Rename Gin-based admin theme. Задача на Drupal.org. Дата обращения: 2026-03-24. ↩

  6. Mark new admin theme as experimental. Задача на Drupal.org. Дата обращения: 2026-03-03. ↩

  7. Sending Emails with Mailer. Официальная документация компонента symfony/mailer. ↩

  8. Experimental Symfony Mailer Module. История изменений Drupal Core. 2025-05-13. ↩

  9. Drupal 10.2 is now available. Блог разработчиков Drupal. 2023-12-15. ↩

  10. Symfony mailer component added as a composer dependency. История изменений Drupal Core. 2023-10-20. ↩

  11. Add a way to capture mails sent through the mailer transport service during tests. Задача на Drupal.org. Дата обращения: 2025-12-02. ↩

  12. Messenger: Sync & Queued Message Handling. Официальная документация компонента symfony/messenger. ↩

  13. MessageEvent. Официальная документация компонента symfony/mailer. ↩

  14. SentMessageEvent. Официальная документация компонента symfony/mailer. ↩

  15. FailedMessageEvent. Официальная документация компонента symfony/mailer. ↩

  16. inline_css — Filters. Документация Twig. ↩

  17. Twig: HTML & CSS. Официальная документация компонента symfony/mailer. ↩

  18. Query parameters can be mapped directly to controller arguments. История изменений Drupal Core. Дата обращения: 2026-02-11. ↩

  19. AutowireTrait supports setter injection with the #[Required] attribute. История изменений Drupal Core. Дата обращения: 2026-02-24. ↩

  20. Вместо того чтобы использовать AutowireTrait для контроллеров и форм, просто регистрируйте их как сервис. Так вы получите больше возможностей, сократите объём кода и повысите скорость работы. ↩ ↩ ↩

  21. Constructors for service objects, plugins, and controllers. Основные политики и практики разработки Drupal. Дата обращения: 2026-02-24. ↩

  22. Entity bundle classes can be defined and discovered using the Drupal\Core\Entity\Attribute\Bundle attribute. История изменений Drupal Core. Дата обращения: 2026-04-12. ↩

  23. PHP Attributes can be used for route definition and discovery. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩ ↩

  24. Баг присутствовал в 11.4.0–11.4.2. Исправлен в #3593939 — коммит попал в 11.4.3. ↩

  25. Library definitions now support a fonts key for preloading. История изменений Drupal Core. Дата обращения: 2026-04-23. ↩

  26. SDC library overrides now support a fonts key for preloading. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩

  27. Brotli compression support added for CSS and JavaScript aggregates. История изменений Drupal Core. Дата обращения: 2026-04-29. ↩

  28. dr - Drupal CLI capable of running commands from modules. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩

  29. Deprecation of Drupal\Core\Theme\Registry::getBaseHook(). История изменений Drupal Core. Дата обращения: 2026-01-19. ↩

  30. \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT and ::$recursiveRenderDepth are deprecated. История изменений Drupal Core. Дата обращения: 2026-01-19. ↩

  31. New method getSummary() added to Drupal\Core\Field\FieldTypeCategoryInterface. История изменений Drupal Core. Дата обращения: 2026-01-22. ↩

  32. AJAX page state is now a request attribute. История изменений Drupal Core. Дата обращения: 2026-02-02. ↩

  33. Drupal\Core\Render\MainContent\HtmlRenderer::buildPageTopAndBottom now has $page_top and $page_bottom parameters. История изменений Drupal Core. Дата обращения: 2026-02-04. ↩

  34. page_top and page_bottom can now be added using attachments on a page's main content. История изменений Drupal Core. Дата обращения: 2026-02-04. ↩

  35. All code in locale.translations.inc has been deprecated.. История изменений Drupal Core. Дата обращения: 2026-02-09. ↩

  36. New LocaleFile and LocaleFileManager. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  37. All functions in locale.fetch.inc are deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  38. Entity type helper method to determine if the entity ID is integer. История изменений Drupal Core. Дата обращения: 2026-02-17. ↩

  39. Implementations of CategorizingPluginManagerInterface::getSortedDefinitions() and ::getGroupedDefinitions() require a $labelKey argument. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩

  40. Implementations of ExecutableInterface::execute() require an $object argument. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩

  41. ConfigManager::findConfigEntityDependenciesAsEntities() returns entities override free. История изменений Drupal Core. Дата обращения: 2026-03-30. ↩

  42. Select query objects now provide getRange() method. История изменений Drupal Core. Дата обращения: 2026-04-13. ↩

  43. \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory() checks the directory returned by sys_get_temp_dir() before /tmp and windows specific directories. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  44. hasRole() has moved from UserInterface to AccountInterface. История изменений Drupal Core. Дата обращения: 2026-03-03. ↩

  45. Outbound path processors miss the route name and parameters. Задача на Drupal.org. Дата обращения: 2026-03-09. ↩

  46. Layout Builder storage plugins must implement SupportAwareSectionStorageInterface. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  47. FormBase provides create() factory method with autowired parameters. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩

  48. Url::createFromRequest does not ignore query parameters anymore. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩

  49. AutowireTrait and AutowiredInstanceTrait support container parameters. История изменений Drupal Core. Дата обращения: 2026-03-17. ↩

  50. New service for purging field data. История изменений Drupal Core. Дата обращения: 2026-04-29. ↩

  51. Add a transaction around the database key value store setMultiple(). Задача на Drupal.org. Дата обращения: 2026-05-12. ↩

  52. New KeyValueStoreInterface::getAllKeys() method. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩

  53. Added symfony/polyfill-php86. История изменений Drupal Core. Дата обращения: 2026-06-01. ↩

  54. ExtensionList::getList() now has an optional $skip_cache argument. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩

  55. Single cardinality entity fields are now loaded from the database at once. История изменений Drupal Core. Дата обращения: 2025-12-18. ↩

  56. Combine multiple cardinality field loading into a single database query. Задача на Drupal.org. Дата обращения: 2026-01-28. ↩

  57. DefaultLazyPluginCollection unnecessarily instantiates plugins when sorting collection. Задача на Drupal.org. Дата обращения: 2026-01-12. ↩

  58. Optimize database writes when re-saving a pending revision as the default one. Задача на Drupal.org. Дата обращения: 2026-01-15. ↩

  59. Try to reduce the number of database queries in MenuTreeStorage::rebuild(). Задача на Drupal.org. Дата обращения: 2026-01-26. ↩

  60. New asset garbage collection threshold. История изменений Drupal Core. Дата обращения: 2026-01-30. ↩

  61. Static cache field storage definitions. Задача на Drupal.org. Дата обращения: 2026-01-30. ↩

  62. Static cache image style config entities. Задача на Drupal.org. Дата обращения: 2026-02-01. ↩

  63. EntityFieldManager::getFieldDefinitions() per-bundle caching can be expensive. Задача на Drupal.org. Дата обращения: 2026-02-02. ↩

  64. JSON:API no longer validates every response against schema by default. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩

  65. JSON:API normalisation not skips cacheing if a ResourceObject has max-age 0. История изменений Drupal Core. Дата обращения: 2026-03-03. ↩

  66. Cache metadata for computed fields is now bubbled for JSON:API responses. История изменений Drupal Core. Дата обращения: 2026-03-18. ↩

  67. Reduce calls to FieldDefinition::getColumns(). Задача на Drupal.org. Дата обращения: 2026-03-22. ↩

  68. Optimise CacheTagsCheckSumTrait::calculateChecksum(). Задача на Drupal.org. Дата обращения: 2026-03-25. ↩

  69. Don't lock on the active trail cache collector cache write. Задача на Drupal.org. Дата обращения: 2026-04-14. ↩

  70. X-Drupal-Dynamic-Cache response header updated for 4xx and 5xx responses. История изменений Drupal Core. Дата обращения: 2026-04-16. ↩

  71. Improve the speed of \Drupal\Core\Theme\ThemeAccessCheck. Задача на Drupal.org. Дата обращения: 2026-04-23. ↩

  72. 404 responses are now a CacheableNotFoundHttpException (Router::matchRequest() throws CacheableResourceNotFoundException). История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  73. Static cache EntityDataDefinition::getDataType(). Задача на Drupal.org. Дата обращения: 2026-03-03. ↩

  74. TypedDataManager prototypes should not include the parent context. Задача на Drupal.org. Дата обращения: 2026-03-11. ↩

  75. Don't clear the CSS cache when installing themes. Задача на Drupal.org. Дата обращения: 2026-03-11. ↩

  76. MainContentViewSubscriber should use a service locator. Задача на Drupal.org. Дата обращения: 2026-03-11. ↩

  77. Add static cache for loadEntityByUuid function to store uuid-id pairs in memory. Задача на Drupal.org. Дата обращения: 2026-05-12. ↩

  78. Use a weak reference for EntityBase::typedData. Задача на Drupal.org. Дата обращения: 2026-03-12. ↩

  79. New Exception status code cache context. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩

  80. Avoid scanning the file system for local po files. Задача на Drupal.org. Дата обращения: 2026-05-19. ↩

  81. Avoid loading date formats in element info. Задача на Drupal.org. Дата обращения: 2026-05-07. ↩

  82. New cache.file_parsing bin and file parsing cache collector. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩

  83. Route preloading/caching changes. История изменений Drupal Core. Дата обращения: 2026-06-29. ↩

  84. The Migrate Drupal module is deprecated. История изменений Drupal Core. Дата обращения: 2026-01-13. ↩

  85. The History module is deprecated. Задача на Drupal.org. Дата обращения: 2026-01-28. ↩

  86. The Contact module is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-15. ↩

  87. Node search plugin node_search moved to sub-module Search Node in Search. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩

  88. All batch related functions in locale.batch.inc, locale.bulk.inc and locale.compare.inc have been deprecated. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩

  89. Several functions in locale.compare.inc and LocaleProjectStorageInterface are deprecated. История изменений Drupal Core. Дата обращения: 2026-06-01. ↩

  90. locale_translation_get_file_history(), locale_translation_update_file_history(), and locale_translation_file_history_delete() have been deprecated. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩

  91. Locale translation status functions deprecated in favor of the LocaleSource service. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩

  92. Underscore prefixed functions from editor.module are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  93. The editor_image_upload_settings_form() is deprecated. Its logic is moved to a service. История изменений Drupal Core. Дата обращения: 2026-03-16. ↩

  94. The editor_filter_xss() function is deprecated and functionality is moved to a service. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  95. Views::pluginManager() and Views::handlerManager() are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-02. ↩

  96. The core/modules/views_ui/admin.inc file is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  97. Views CachePluginBase::getRowCacheKeys() deprecated, row-level caching removed. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  98. CachePluginBase::cacheExpire in views module is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  99. ViewExecutable::getHandler() is deprecated, use ViewExecutable::getHandlerConfiguration() instead. История изменений Drupal Core. Дата обращения: 2026-06-15. ↩

  100. The comment_preview() function is deprecated and the logic has moved to CommentForm. История изменений Drupal Core. Дата обращения: 2026-01-28. ↩

  101. CommentInterface::ANONYMOUS_* constants are deprecated. История изменений Drupal Core. Дата обращения: 2026-01-29. ↩

  102. CommentItemInterface constants FORM_SEPARATE_PAGE and FORM_BELOW are deprecated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  103. Undocumented User::$password property is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  104. user_form_process_password_confirm() is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-06. ↩

  105. user_pass_rehash(), user_cancel_url(), user_mail_tokens(), and user_pass_reset_url() are deprecated. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩

  106. node_access_grants has been deprecated. История изменений Drupal Core. Дата обращения: 2026-05-07. ↩

  107. node_access_rebuild functions are deprecated. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩

  108. \Drupal\node\Controller\NodeViewController is deprecated. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩

  109. The node/form library is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  110. The long format 'filter tips' are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  111. New repository service for filter formats. filter_formats(), filter_formats_reset(), filter_get_formats_by_role(), filter_default_format() & filter_fallback_format() are deprecated. История изменений Drupal Core. Дата обращения: 2026-04-06. ↩

  112. The check_markup() function is deprecated. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩

  113. text_summary() is deprecated and moved to new TextSummary service. История изменений Drupal Core. Дата обращения: 2026-03-13. ↩

  114. InstallerRouteBuilder is no longer needed. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩

  115. RouteBuilder no longer needs the module handler and controller resolver injected. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩

  116. Migration plugins link_options, link_uri, timezone, and user_langcode are moved to the Migrate module. История изменений Drupal Core. Дата обращения: 2026-04-13. ↩

  117. The _contextual_links_to_id() & _contextual_id_to_links() functions are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-01. ↩

  118. Constraint plugins must use named arguments instead of an options array. История изменений Drupal Core. Дата обращения: 2026-02-02. ↩

  119. Several procedural submit, validation, Ajax callbacks and other functions were converted to methods and deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  120. Using #item_attributes with image_formatter and responsive_image_formatter is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  121. System menu blocks have configuration option for "Add a CSS class to ancestors of the current page". История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  122. \Drupal\FunctionalJavascriptTests\WebDriverCurlService is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  123. Passing null as $deserialization_target_class to ResourceType is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩

  124. 'uri_callback' entity key is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩

  125. The function _update_cron_notify() has been removed. История изменений Drupal Core. Дата обращения: 2026-05-07. ↩

  126. Deprecated email addresses will no longer pass validation. История изменений Drupal Core. Дата обращения: 2026-04-06. ↩

  127. Accessing the autoload global is deprecated. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩

  128. Render control functions hide() and show() are deprecated. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩

  129. Functions in menu_ui.module are deprecated and move to hooks. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩

  130. ToStringTrait is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-20. ↩

  131. Cursor offset and orientation arguments in StatementInterface::fetch() are deprecated. История изменений Drupal Core. Дата обращения: 2026-04-20. ↩

  132. SqlContentEntityStorage::loadFromSharedTables() is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  133. Using a #access value other than a boolean or an AccessResultInterface object is deprecated. История изменений Drupal Core. Дата обращения: 2026-01-29. ↩

  134. block_theme_initialize had been deprecated. История изменений Drupal Core. Дата обращения: 2026-03-03. ↩

  135. SessionManager::delete() is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩

  136. The trusted data concept in Config and Config Entities is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩

  137. Return types have changed on some JSON:API Normalizer methods. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩

  138. Return types have changed on some JSON:API Normalizer methods. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩

  139. file_get_file_references() is deprecated in favor of the FileReferenceResolver. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩

  140. Entity query methods no longer implicitly support passing different query objects. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩

  141. CSRF token validation with the 'rest' key is deprecated in Drupal 11.4.0 and removed in Drupal 12.0.0. История изменений Drupal Core. Дата обращения: 2026-06-15. ↩

  142. RowPluginBase::render() only accepts \Drupal\views\ResultRow. История изменений Drupal Core. Дата обращения: 2026-06-29. ↩

  143. Standard profile and recipes no longer use text_with_summary widget. История изменений Drupal Core. Дата обращения: 2026-01-29. ↩

  144. Manual user creation now emails users by default. История изменений Drupal Core. Дата обращения: 2026-02-11. ↩

  145. Site information form now stores unresolved path aliases for front, 403, and 404 pages. История изменений Drupal Core. Дата обращения: 2026-02-11. ↩

  146. Support full-screen editing in CKEditor. Задача на Drupal.org. Дата обращения: 2026-02-15. ↩

  147. Add Navigation to the Standard profile and recipes. Задача на Drupal.org. Дата обращения: 2026-03-03. ↩

  148. Uninstalling themes in the UI now have a confirmation step. История изменений Drupal Core. Дата обращения: 2026-04-12. ↩

  149. HTML5 validation will be disabled in Drupal 12. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  150. "Manage display" now defaults to a display-builder agnostic overview page. История изменений Drupal Core. Дата обращения: 2026-06-01. ↩

  151. Views table alignment style options now relies on core alignment classes. История изменений Drupal Core. Дата обращения: 2026-01-19. ↩

  152. Update Drupal's default file type icons to use SVG. Задача на Drupal.org. Дата обращения: 2026-01-22. ↩

  153. Redundant WAI-ARIA role attributes removed from templates. История изменений Drupal Core. Дата обращения: 2026-02-15. ↩

  154. Class Variance Authority (CVA) support added to Twig. История изменений Drupal Core. Дата обращения: 2026-03-24. ↩

  155. Remove support for Book and Forum Module. Задача на Drupal.org. Дата обращения: 2026-03-30. ↩

  156. There is a new Theme extension object. system_region_list() and system_default_region() and region related constants are deprecated. История изменений Drupal Core. Дата обращения: 2026-04-23. ↩

  157. SDC components can now be used as form elements. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  158. SDCs can now declare expectations and cardinality for slots. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩

  159. navigation__message theme hook deleted. История изменений Drupal Core. Дата обращения: 2026-04-29. ↩

  160. Block content attributes are moved to the content wrapper. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩

  161. The '#url' property in the responsive_image_formatter theme element is now a Url object. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩

  162. ImageToolkit and ImageToolkitOperation plugins are autowirable. История изменений Drupal Core. Дата обращения: 2026-01-22. ↩

  163. The history module has been removed from the standard profile and recipe. История изменений Drupal Core. Дата обращения: 2026-01-19. ↩

  164. The shortcut module has been removed from the standard profile and recipe. История изменений Drupal Core. 2026-02-24. ↩

  165. The Article and Page content types are removed from the Standard profile and recipe. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩

  166. Mark drupal/legacy-project as abandoned. Задача на Drupal.org. Дата обращения: 2026-02-24. ↩

  167. The drupal/core-dev-pinned metapackage is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-18. ↩

  168. Locale now uses file hash instead of mtime to detect translation file changes. История изменений Drupal Core. Дата обращения: 2026-03-23. ↩

  169. The 'version' value in .info.yml files must be a string. История изменений Drupal Core. Дата обращения: 2026-03-30. ↩

  170. Promote justinrainbow/json-schema from dev-dependency to full dependency. ↩

  171. robots.txt blocks search pages with query parameters. История изменений Drupal Core. Дата обращения: 2026-03-16. ↩

  172. Support importing default content in JSON format. Задача на Drupal.org. Дата обращения: 2026-03-16. ↩

  173. Drupal now uses symfony/runtime for bootstrap separation. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩

  174. Commenting is not enabled in Drupal by default anymore. История изменений Drupal Core. Дата обращения: 2026-06-29. ↩

  175. W3C compliant testing. История изменений Drupal Core. Дата обращения: 2026-02-09. ↩

  176. expectDeprecation() is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-17. ↩

  177. [CI] Introduce our own PHPStan ErrorFormatter to avoid multiple PHPStan executions. Задача на Drupal.org. Дата обращения: 2026-02-24. ↩

  178. Kernel tests can make HTTP requests with drupalGet(). История изменений Drupal Core. Дата обращения: 2026-03-30. ↩

  179. Reduce container rebuilds in functional tests. Задача на Drupal.org. Дата обращения: 2026-03-12. ↩

  180. Test methods consolidated in EntityResourceTestBase. История изменений Drupal Core. Дата обращения: 2026-03-16. ↩

  181. UUIDs are now validated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩

  182. 'View linked label' operation added to user entity. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩

  183. locale.settings:translation.path config is deprecated in favor of locale_translation_path setting. История изменений Drupal Core. Дата обращения: 2026-03-30. ↩

  184. Password hashing is configurable using kernel parameters. История изменений Drupal Core. Дата обращения: 2026-03-31. ↩

  185. AccessResult::allowedIf() and AccessResult::forbiddenIf() now accept a neutral reason. История изменений Drupal Core. Дата обращения: 2026-04-08. ↩

  186. user.pass.http, user.login.http, user.login_status.http and user.logout.http routes moved to the rest module. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩

  187. New permission available to view unpublished block content. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩

Комментарии

Добавить комментарий

Поддерживается Markdown
Поделиться
Обсудить с AI
Прочитай эту страницу: https://niklan.net/blog/drupal-11-4-overview?_format=llms и ответь на вопросы по её содержимому.
Просмотреть как Markdown