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] работает и без AutowireTraitSymfony\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!')];
  }

}
Заметка

В официальном Change Record23 приведён пример, где #[Route] размещён только на классе, а ::__invoke() — без атрибута. Но работать оно не будет из-за бага в AttributeRouteDiscovery24: условие $class->hasMethod('__invoke') === 0 всегда FALSE, поскольку hasMethod() возвращает bool, а не int. Атрибут на методе обязателен, пока баг не будет исправлен (раз, два), впрочем, это уже другая история.

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

В библиотеках (.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_cachepage_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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Добавлена новая операция контроля доступа 'view linked label' для сущности пользователя, определяющая возможность отображения имени как ссылки на профиль.191
  • Объявлена устаревшей конфигурация locale.settings:translation.path. Вместо неё следует использовать настройку $settings['locale_translation_path'] в settings.php.192
  • Добавлена возможность настройки алгоритма и параметров хеширования паролей через параметры ядра password.algorithm и password.options в services.yml.193
  • В трейт HttpKernelUiHelperTrait добавлен метод ::clickLink(), позволяющий имитировать клик по ссылке в kernel-тестах без необходимости использовать браузерные тесты.194
  • Методы AccessResult::allowedIf() и AccessResult::forbiddenIf() теперь принимают необязательный аргумент с причиной нейтрального результата доступа.195
  • Маршруты 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.196
  • Добавлено новое разрешение 'view unpublished block content', позволяющее редакторам просматривать неопубликованные блок-контент-сущности без необходимости иметь разрешения «administer block content» или «access block library».197

  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-rc2

  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. The Migrate Drupal module is deprecated. История изменений Drupal Core. Дата обращения: 2026-01-13. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  110. 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. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  140. 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. 

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

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

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

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

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

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

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

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

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

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

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

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

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

  154. 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. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  176. Use of uniqid(), md5(), sha1(), crc32() and hash() with weak algorithms is disallowed in Drupal code. История изменений Drupal Core. Дата обращения: 2026-04-28. 

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

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

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

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

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

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

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

  184. 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. 

  185. 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