В этом обзоре я собрал изменения в 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!')];
}
}
В официальном 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;
}
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 — нас интересует только то, как изменились цифры между версиями на одном железе с идентичным окружением, чтобы видеть прогресс.
Рисунок 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 не припоминаю.
Рисунок 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. Их функциональность перенесена в VOLocaleFileи сервис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.inc—locale_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 objectCurrentImportState.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()объекта SymfonyResponse.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,MarkupNormalizer—string,NullNormalizer—null.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
Drupal 11.4.0. Релизы Drupal. Не опубликовано на момент выхода материала. ↩
Gin Admin Theme. Проект Gin на Drupal.org. ↩
Drupal core will adopt Gin admin theme to replace Claro. Блог разработчиков Drupal. 2025-06-20. ↩
Merging Gin as Admin theme. Задача на Drupal.org. Дата обращения: 2026-03-03. ↩
Rename Gin-based admin theme. Задача на Drupal.org. Дата обращения: 2026-03-24. ↩
Mark new admin theme as experimental. Задача на Drupal.org. Дата обращения: 2026-03-03. ↩
Sending Emails with Mailer. Официальная документация компонента
symfony/mailer. ↩Experimental Symfony Mailer Module. История изменений Drupal Core. 2025-05-13. ↩
Drupal 10.2 is now available. Блог разработчиков Drupal. 2023-12-15. ↩
Symfony mailer component added as a composer dependency. История изменений Drupal Core. 2023-10-20. ↩
Add a way to capture mails sent through the mailer transport service during tests. Задача на Drupal.org. Дата обращения: 2025-12-02. ↩
Messenger: Sync & Queued Message Handling. Официальная документация компонента
symfony/messenger. ↩MessageEvent. Официальная документация компонента
symfony/mailer. ↩SentMessageEvent. Официальная документация компонента
symfony/mailer. ↩FailedMessageEvent. Официальная документация компонента
symfony/mailer. ↩inline_css — Filters. Документация Twig. ↩
Twig: HTML & CSS. Официальная документация компонента
symfony/mailer. ↩Query parameters can be mapped directly to controller arguments. История изменений Drupal Core. Дата обращения: 2026-02-11. ↩
AutowireTrait supports setter injection with the #[Required] attribute. История изменений Drupal Core. Дата обращения: 2026-02-24. ↩
Вместо того чтобы использовать
AutowireTraitдля контроллеров и форм, просто регистрируйте их как сервис. Так вы получите больше возможностей, сократите объём кода и повысите скорость работы. ↩ ↩ ↩Constructors for service objects, plugins, and controllers. Основные политики и практики разработки Drupal. Дата обращения: 2026-02-24. ↩
Entity bundle classes can be defined and discovered using the Drupal\Core\Entity\Attribute\Bundle attribute. История изменений Drupal Core. Дата обращения: 2026-04-12. ↩
PHP Attributes can be used for route definition and discovery. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩ ↩
Баг актуален на 11.4.0-rc2. ↩
Library definitions now support a fonts key for preloading. История изменений Drupal Core. Дата обращения: 2026-04-23. ↩
SDC library overrides now support a fonts key for preloading. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩
Brotli compression support added for CSS and JavaScript aggregates. История изменений Drupal Core. Дата обращения: 2026-04-29. ↩
dr - Drupal CLI capable of running commands from modules. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩
Deprecation of Drupal\Core\Theme\Registry::getBaseHook(). История изменений Drupal Core. Дата обращения: 2026-01-19. ↩
\Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT and ::$recursiveRenderDepth are deprecated. История изменений Drupal Core. Дата обращения: 2026-01-19. ↩
New method getSummary() added to Drupal\Core\Field\FieldTypeCategoryInterface. История изменений Drupal Core. Дата обращения: 2026-01-22. ↩
AJAX page state is now a request attribute. История изменений Drupal Core. Дата обращения: 2026-02-02. ↩
Drupal\Core\Render\MainContent\HtmlRenderer::buildPageTopAndBottom now has $page_top and $page_bottom parameters. История изменений Drupal Core. Дата обращения: 2026-02-04. ↩
page_top and page_bottom can now be added using attachments on a page's main content. История изменений Drupal Core. Дата обращения: 2026-02-04. ↩
All code in locale.translations.inc has been deprecated.. История изменений Drupal Core. Дата обращения: 2026-02-09. ↩
New LocaleFile and LocaleFileManager. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
All functions in locale.fetch.inc are deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
Entity type helper method to determine if the entity ID is integer. История изменений Drupal Core. Дата обращения: 2026-02-17. ↩
Implementations of CategorizingPluginManagerInterface::getSortedDefinitions() and ::getGroupedDefinitions() require a $labelKey argument. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
Implementations of ExecutableInterface::execute() require an $object argument. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
ConfigManager::findConfigEntityDependenciesAsEntities() returns entities override free. История изменений Drupal Core. Дата обращения: 2026-03-30. ↩
Select query objects now provide getRange() method. История изменений Drupal Core. Дата обращения: 2026-04-13. ↩
\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. ↩
hasRole() has moved from UserInterface to AccountInterface. История изменений Drupal Core. Дата обращения: 2026-03-03. ↩
Outbound path processors miss the route name and parameters. Задача на Drupal.org. Дата обращения: 2026-03-09. ↩
Layout Builder storage plugins must implement SupportAwareSectionStorageInterface. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
FormBase provides create() factory method with autowired parameters. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩
Url::createFromRequest does not ignore query parameters anymore. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩
AutowireTrait and AutowiredInstanceTrait support container parameters. История изменений Drupal Core. Дата обращения: 2026-03-17. ↩
New service for purging field data. История изменений Drupal Core. Дата обращения: 2026-04-29. ↩
Add a transaction around the database key value store setMultiple(). Задача на Drupal.org. Дата обращения: 2026-05-12. ↩
New KeyValueStoreInterface::getAllKeys() method. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
Added symfony/polyfill-php86. История изменений Drupal Core. Дата обращения: 2026-06-01. ↩
ExtensionList::getList() now has an optional $skip_cache argument. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩
Single cardinality entity fields are now loaded from the database at once. История изменений Drupal Core. Дата обращения: 2025-12-18. ↩
Combine multiple cardinality field loading into a single database query. Задача на Drupal.org. Дата обращения: 2026-01-28. ↩
DefaultLazyPluginCollection unnecessarily instantiates plugins when sorting collection. Задача на Drupal.org. Дата обращения: 2026-01-12. ↩
Optimize database writes when re-saving a pending revision as the default one. Задача на Drupal.org. Дата обращения: 2026-01-15. ↩
New asset garbage collection threshold. История изменений Drupal Core. Дата обращения: 2026-01-30. ↩
Static cache field storage definitions. Задача на Drupal.org. Дата обращения: 2026-01-30. ↩
Static cache image style config entities. Задача на Drupal.org. Дата обращения: 2026-02-01. ↩
EntityFieldManager::getFieldDefinitions() per-bundle caching can be expensive. Задача на Drupal.org. Дата обращения: 2026-02-02. ↩
JSON:API no longer validates every response against schema by default. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
JSON:API normalisation not skips cacheing if a ResourceObject has max-age 0. История изменений Drupal Core. Дата обращения: 2026-03-03. ↩
Cache metadata for computed fields is now bubbled for JSON:API responses. История изменений Drupal Core. Дата обращения: 2026-03-18. ↩
Reduce calls to FieldDefinition::getColumns(). Задача на Drupal.org. Дата обращения: 2026-03-22. ↩
Optimise CacheTagsCheckSumTrait::calculateChecksum(). Задача на Drupal.org. Дата обращения: 2026-03-25. ↩
Don't lock on the active trail cache collector cache write. Задача на Drupal.org. Дата обращения: 2026-04-14. ↩
X-Drupal-Dynamic-Cache response header updated for 4xx and 5xx responses. История изменений Drupal Core. Дата обращения: 2026-04-16. ↩
Improve the speed of \Drupal\Core\Theme\ThemeAccessCheck. Задача на Drupal.org. Дата обращения: 2026-04-23. ↩
404 responses are now a CacheableNotFoundHttpException (Router::matchRequest() throws CacheableResourceNotFoundException). История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
Static cache EntityDataDefinition::getDataType(). Задача на Drupal.org. Дата обращения: 2026-03-03. ↩
TypedDataManager prototypes should not include the parent context. Задача на Drupal.org. Дата обращения: 2026-03-11. ↩
Don't clear the CSS cache when installing themes. Задача на Drupal.org. Дата обращения: 2026-03-11. ↩
MainContentViewSubscriber should use a service locator. Задача на Drupal.org. Дата обращения: 2026-03-11. ↩
Add static cache for loadEntityByUuid function to store uuid-id pairs in memory. Задача на Drupal.org. Дата обращения: 2026-05-12. ↩
Use a weak reference for EntityBase::typedData. Задача на Drupal.org. Дата обращения: 2026-03-12. ↩
New Exception status code cache context. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩
Avoid scanning the file system for local po files. Задача на Drupal.org. Дата обращения: 2026-05-19. ↩
Avoid loading date formats in element info. Задача на Drupal.org. Дата обращения: 2026-05-07. ↩
New cache.file_parsing bin and file parsing cache collector. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩
The Migrate Drupal module is deprecated. История изменений Drupal Core. Дата обращения: 2026-01-13. ↩
The History module is deprecated. Задача на Drupal.org. Дата обращения: 2026-01-28. ↩
The Contact module is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-15. ↩
Node search plugin node_search moved to sub-module Search Node in Search. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩
All batch related functions in locale.batch.inc, locale.bulk.inc and locale.compare.inc have been deprecated. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩
Several functions in locale.compare.inc and LocaleProjectStorageInterface are deprecated. История изменений Drupal Core. Дата обращения: 2026-06-01. ↩
locale_translation_get_file_history(), locale_translation_update_file_history(), and locale_translation_file_history_delete() have been deprecated. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩
Locale translation status functions deprecated in favor of the LocaleSource service. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩
Underscore prefixed functions from editor.module are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
The editor_image_upload_settings_form() is deprecated. Its logic is moved to a service. История изменений Drupal Core. Дата обращения: 2026-03-16. ↩
The editor_filter_xss() function is deprecated and functionality is moved to a service. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
Views::pluginManager() and Views::handlerManager() are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-02. ↩
The core/modules/views_ui/admin.inc file is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
Views CachePluginBase::getRowCacheKeys() deprecated, row-level caching removed. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
CachePluginBase::cacheExpire in views module is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
views_ui_contextual_links_suppress(), views_ui_contextual_links_suppress_push(), views_ui_contextual_links_suppress_pop() have been deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
ViewExecutable::getHandler() is deprecated, use ViewExecutable::getHandlerConfiguration() instead. История изменений Drupal Core. Дата обращения: 2026-06-15. ↩
The comment_preview() function is deprecated and the logic has moved to CommentForm. История изменений Drupal Core. Дата обращения: 2026-01-28. ↩
CommentInterface::ANONYMOUS_* constants are deprecated. История изменений Drupal Core. Дата обращения: 2026-01-29. ↩
CommentItemInterface constants FORM_SEPARATE_PAGE and FORM_BELOW are deprecated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
Undocumented User::$password property is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
user_cookie_save() and user_cookie_delete() are deprecated. История изменений Drupal Core. Дата обращения: 2026-05-07. ↩
user_form_process_password_confirm() is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-06. ↩
user_pass_rehash(), user_cancel_url(), user_mail_tokens(), and user_pass_reset_url() are deprecated. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩
node_access_grants has been deprecated. История изменений Drupal Core. Дата обращения: 2026-05-07. ↩
node_access_rebuild functions are deprecated. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩
\Drupal\node\Controller\NodeViewController is deprecated. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩
The node/form library is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
The long format 'filter tips' are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
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. ↩
The check_markup() function is deprecated. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩
text_summary() is deprecated and moved to new TextSummary service. История изменений Drupal Core. Дата обращения: 2026-03-13. ↩
InstallerRouteBuilder is no longer needed. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩
RouteBuilder no longer needs the module handler and controller resolver injected. История изменений Drupal Core. Дата обращения: 2026-04-14. ↩
Migration plugins link_options, link_uri, timezone, and user_langcode are moved to the Migrate module. История изменений Drupal Core. Дата обращения: 2026-04-13. ↩
The _contextual_links_to_id() & _contextual_id_to_links() functions are deprecated. История изменений Drupal Core. Дата обращения: 2026-02-01. ↩
Constraint plugins must use named arguments instead of an options array. История изменений Drupal Core. Дата обращения: 2026-02-02. ↩
Several procedural submit, validation, Ajax callbacks and other functions were converted to methods and deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
Using #item_attributes with image_formatter and responsive_image_formatter is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
\Drupal\FunctionalJavascriptTests\WebDriverCurlService is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
LinkWidget::validateTitleElement() is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
Passing null as $deserialization_target_class to ResourceType is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
'uri_callback' entity key is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
The function _update_cron_notify() has been removed. История изменений Drupal Core. Дата обращения: 2026-05-07. ↩
Deprecated email addresses will no longer pass validation. История изменений Drupal Core. Дата обращения: 2026-04-06. ↩
Accessing the autoload global is deprecated. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
Render control functions hide() and show() are deprecated. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
ToStringTrait is deprecated. История изменений Drupal Core. Дата обращения: 2026-04-20. ↩
Cursor offset and orientation arguments in StatementInterface::fetch() are deprecated. История изменений Drupal Core. Дата обращения: 2026-04-20. ↩
Using a #access value other than a boolean or an AccessResultInterface object is deprecated. История изменений Drupal Core. Дата обращения: 2026-01-29. ↩
block_theme_initialize had been deprecated. История изменений Drupal Core. Дата обращения: 2026-03-03. ↩
SessionManager::delete() is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-09. ↩
The trusted data concept in Config and Config Entities is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩
Return types have changed on some JSON:API Normalizer methods. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩
Return types have changed on some JSON:API Normalizer methods. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩
file_get_file_references() is deprecated in favor of the FileReferenceResolver. История изменений Drupal Core. Дата обращения: 2026-05-18. ↩
Entity query methods no longer implicitly support passing different query objects. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩
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. ↩
Display meaningful error messages according to the link type. Задача на Drupal.org. Дата обращения: 2026-01-13. ↩
Standard profile and recipes no longer use text_with_summary widget. История изменений Drupal Core. Дата обращения: 2026-01-29. ↩
Manual user creation now emails users by default. История изменений Drupal Core. Дата обращения: 2026-02-11. ↩
Site information form now stores unresolved path aliases for front, 403, and 404 pages. История изменений Drupal Core. Дата обращения: 2026-02-11. ↩
Support full-screen editing in CKEditor. Задача на Drupal.org. Дата обращения: 2026-02-15. ↩
Link field widget supports route:{$route_name}. История изменений Drupal Core. Дата обращения: 2026-02-26. ↩
Uninstalling themes in the UI now have a confirmation step. История изменений Drupal Core. Дата обращения: 2026-04-12. ↩
String formatter can now also link to an entity's edit form. История изменений Drupal Core. Дата обращения: 2026-04-20. ↩
HTML5 validation will be disabled in Drupal 12. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
Disabled links are now ignored in active trail. История изменений Drupal Core. Дата обращения: 2026-03-11. ↩
"Manage display" now defaults to a display-builder agnostic overview page. История изменений Drupal Core. Дата обращения: 2026-06-01. ↩
Views table alignment style options now relies on core alignment classes. История изменений Drupal Core. Дата обращения: 2026-01-19. ↩
Update Drupal's default file type icons to use SVG. Задача на Drupal.org. Дата обращения: 2026-01-22. ↩
Redundant WAI-ARIA
roleattributes removed from templates. История изменений Drupal Core. Дата обращения: 2026-02-15. ↩Class Variance Authority (CVA) support added to Twig. История изменений Drupal Core. Дата обращения: 2026-03-24. ↩
Remove support for Book and Forum Module. Задача на Drupal.org. Дата обращения: 2026-03-30. ↩
Inline links in help topics are no longer rendered as absolute. История изменений Drupal Core. Дата обращения: 2026-04-06. ↩
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. ↩
SDC components can now be used as form elements. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
SDCs can now declare expectations and cardinality for slots. История изменений Drupal Core. Дата обращения: 2026-05-04. ↩
Block content attributes are moved to the content wrapper. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
The '#url' property in the responsive_image_formatter theme element is now a Url object. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
ImageToolkit and ImageToolkitOperation plugins are autowirable. История изменений Drupal Core. Дата обращения: 2026-01-22. ↩
The history module has been removed from the standard profile and recipe. История изменений Drupal Core. Дата обращения: 2026-01-19. ↩
The shortcut module has been removed from the standard profile and recipe. История изменений Drupal Core. 2026-02-24. ↩
The Article and Page content types are removed from the Standard profile and recipe. История изменений Drupal Core. Дата обращения: 2026-06-10. ↩
Mark drupal/legacy-project as abandoned. Задача на Drupal.org. Дата обращения: 2026-02-24. ↩
The drupal/core-dev-pinned metapackage is deprecated. История изменений Drupal Core. Дата обращения: 2026-03-18. ↩
Locale now uses file hash instead of mtime to detect translation file changes. История изменений Drupal Core. Дата обращения: 2026-03-23. ↩
The 'version' value in .info.yml files must be a string. История изменений Drupal Core. Дата обращения: 2026-03-30. ↩
Promote justinrainbow/json-schema from dev-dependency to full dependency. ↩
robots.txt blocks search pages with query parameters. История изменений Drupal Core. Дата обращения: 2026-03-16. ↩
Support importing default content in JSON format. Задача на Drupal.org. Дата обращения: 2026-03-16. ↩
Drupal now uses symfony/runtime for bootstrap separation. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
W3C compliant testing. История изменений Drupal Core. Дата обращения: 2026-02-09. ↩
expectDeprecation() is deprecated. История изменений Drupal Core. Дата обращения: 2026-02-17. ↩
[CI] Introduce our own PHPStan ErrorFormatter to avoid multiple PHPStan executions. Задача на Drupal.org. Дата обращения: 2026-02-24. ↩
Kernel tests can make HTTP requests with drupalGet(). История изменений Drupal Core. Дата обращения: 2026-03-30. ↩
Use of uniqid(), md5(), sha1(), crc32() and hash() with weak algorithms is disallowed in Drupal code. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
Reduce container rebuilds in functional tests. Задача на Drupal.org. Дата обращения: 2026-03-12. ↩
Test methods consolidated in EntityResourceTestBase. История изменений Drupal Core. Дата обращения: 2026-03-16. ↩
UUIDs are now validated. История изменений Drupal Core. Дата обращения: 2026-04-28. ↩
New 'resolvable_uri' property is added to link field. История изменений Drupal Core. Дата обращения: 2026-05-12. ↩
'View linked label' operation added to user entity. История изменений Drupal Core. Дата обращения: 2026-02-03. ↩
locale.settings:translation.path config is deprecated in favor of locale_translation_path setting. История изменений Drupal Core. Дата обращения: 2026-03-30. ↩
Password hashing is configurable using kernel parameters. История изменений Drupal Core. Дата обращения: 2026-03-31. ↩
Add clickLink() to HttpKernelUiHelperTrait. Задача на Drupal.org. Дата обращения: 2026-04-03. ↩
AccessResult::allowedIf() and AccessResult::forbiddenIf() now accept a neutral reason. История изменений Drupal Core. Дата обращения: 2026-04-08. ↩
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. ↩
New permission available to view unpublished block content. История изменений Drupal Core. Дата обращения: 2026-06-02. ↩
Комментарии