Entity API в Drupal: чек-лист перед коммитом
Entity API в Drupal связывает типы сущностей, поля, доступ, переводы и кеш. Этот чек-лист помогает проверить код до коммита: от выбора storage до корректной работы с access checking и cache metadata.

Код через Entity API в Drupal может выглядеть рабочим, но ломаться на доступах, переводах или кешировании. Перед коммитом проверьте не только результат load() или save(), но и тип сущности, схему данных, права пользователя, ревизии и способ вывода. Чек-лист ниже рассчитан на Drupal 10.3+ и Drupal 11.x и подходит для кастомных модулей, интеграций и обработчиков очередей.
1. Зафиксируйте модель сущности
Начните не с вызова storage, а с ответа на вопрос: с чем именно работает код. В Drupal есть контентные и конфигурационные сущности. Первые обычно хранят пользовательские данные и могут иметь поля, переводы и ревизии. Вторые описывают настройки сайта: типы материалов, представления, конфигурацию модулей.
- Укажите machine name типа сущности: например,
node,user,taxonomy_termили тип из собственного модуля. - Проверьте, является ли сущность контентной или конфигурационной.
- Для контентной сущности определите bundle: например,
articleдля материала илиpageдля базовой страницы. - Проверьте machine name каждого поля, его кардинальность и тип хранимого значения.
- Зафиксируйте, нужны ли переводы, ревизии и moderation state.
Проверка. В описании задачи и в коде явно указаны тип сущности, bundle и поля. Если разработчик должен угадывать это по названию переменной, модель описана недостаточно точно.
2. Получайте storage через контейнер
Для Drupal 10 и 11 не стоит строить основную бизнес-логику вокруг статических вызовов вроде \Drupal::entityTypeManager(). Такой код допустим в небольших хуках или разовых скриптах, но в сервисе лучше внедрить зависимость через конструктор. Это упрощает тестирование и не прячет зависимости.
<?php
namespace Drupal\example\Service;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\node\NodeInterface;
final class ArticleLoader {
public function __construct(
private readonly EntityTypeManagerInterface $entityTypeManager,
) {}
public function loadPublishedArticle(int $nodeId): ?NodeInterface {
$storage = $this->entityTypeManager->getStorage('node');
$node = $storage->load($nodeId);
if (!$node instanceof NodeInterface || $node->bundle() !== 'article') {
return NULL;
}
if (!$node->isPublished()) {
return NULL;
}
return $node;
}
}
Проверка. Сервис получает только нужную зависимость, проверяет тип объекта и не возвращает материал другого bundle или неопубликованную запись.
Если нужен список сущностей, не загружайте весь набор через loadMultiple() без ограничения. Сначала сформируйте запрос и явно решите, должна ли проверяться доступность результата.
$query = $this->entityTypeManager
->getStorage('node')
->getQuery()
->accessCheck(TRUE)
->condition('type', 'article')
->condition('status', 1)
->sort('created', 'DESC')
->range(0, 20);
$nodeIds = $query->execute();
$articles = $this->entityTypeManager
->getStorage('node')
->loadMultiple($nodeIds);
Вызов accessCheck(TRUE) нельзя оставлять на волю случая. Если запрос используется только во внутренней административной операции, решение может быть другим, но его нужно зафиксировать в коде и документации. Запросы к сущностям без явного решения по доступу — частая причина ошибок после обновления Drupal.
3. Проверьте чтение и запись полей
У поля может быть одно значение или несколько. Кроме того, поле ссылки на сущность хранит не заголовок и не объект, а идентификатор связанной сущности. Проверяйте структуру данных до того, как передавать её в шаблон или внешнее API.
- Для текстового поля используйте значение поля, а не предположение о его типе.
- Для entity reference проверьте
target_idи существование связанной сущности. - Для многозначного поля обработайте все элементы, а не только первый.
- Перед записью проверьте, что поле существует у выбранного bundle.
- Не меняйте значения через прямой SQL: Entity API должна знать об изменении и корректно сохранить его.
$tagIds = [];
if ($node->hasField('field_tags') && !$node->get('field_tags')->isEmpty()) {
foreach ($node->get('field_tags') as $item) {
if ($item->target_id !== NULL) {
$tagIds[] = (int) $item->target_id;
}
}
}
$node->set('field_tags', [
['target_id' => 12],
['target_id' => 18],
]);
$node->save();
Проверка. После сохранения перечитайте сущность и убедитесь, что количество элементов поля и идентификаторы совпадают с ожидаемыми. В интеграции дополнительно проверьте, что связанные термины или материалы доступны и относятся к нужному типу.
4. Не пропустите доступ
Загрузка сущности и право показать её пользователю — разные проверки. Факт, что load() вернул объект, не означает, что его можно вывести на странице. Для единичной сущности используйте проверку доступа, соответствующую операции: обычно это view, update или delete.
$access = $node->access('view', $account, TRUE);
if (!$access->isAllowed()) {
return [];
}
$build = $this->entityTypeManager
->getViewBuilder('node')
->view($node, 'full');
return $build;
Третий аргумент access() просит вернуть объект результата доступа, а не простое логическое значение. Это позволяет сохранить cacheability metadata, если решение зависит от роли, пользователя или контекста маршрута.
Entity API отвечает не только за загрузку записи. Корректный результат — это данные, право их увидеть и кеш, который не покажет их не тому пользователю.
5. Проверка кеша, переводов и ревизий
Если сущность выводится через render array, не превращайте её в строку слишком рано. Рендерер Drupal может добавить cache tags, contexts и max-age. Потеря этих метаданных приводит к устаревшему выводом или утечке персонализированного результата через общий кеш.
- Для вывода используйте entity view builder, когда нужен стандартный режим отображения.
- Не кешируйте готовый HTML без учёта cache contexts и tags.
- Если результат зависит от языка, проверьте текущий language context и перевод сущности.
- Если работа идёт с ревизиями, явно укажите, нужна ли текущая, опубликованная или конкретная ревизия.
- После изменения сущности не пытайтесь вручную чистить весь кеш сайта без необходимости.
Перевод — это не просто другая строка в поле. Проверьте, существует ли перевод, опубликован ли он и какой язык должен использоваться в конкретном контексте. Поведение moderation и revisions также нельзя выводить из одного поля status: у кастомной сущности workflow может работать иначе.
6. Сравните варианты перед коммитом
| Задача | Предпочтительный путь | Риск альтернативы |
|---|---|---|
| Получить одну сущность по ID | Storage и load() | SQL обходит access, поля и особенности типа |
| Найти набор сущностей | Entity Query с явным accessCheck() | Загрузка всех записей создаёт лишний расход памяти |
| Показать сущность пользователю | Проверка доступа и view builder | Ручная сборка HTML теряет кеш и форматирование полей |
| Изменить поле | set() или запись через field API и save() | Прямой SQL не обновляет связанные механизмы Drupal |
| Массово обработать данные | Ограниченные выборки и пакетная обработка | loadMultiple() на весь набор может исчерпать память |
Критерий выбора
Если код показывает данные посетителю, приоритет у access и cache metadata. Если он меняет данные по расписанию или из очереди, важнее идемпотентность, ограничение пакета и журналирование ошибок. Если задача только в миграции или диагностике, прямой доступ к базе может быть частью отдельного инструмента, но не заменой Entity API в рабочем пользовательском потоке.
7. Что ломается чаще всего
- «Call to a member function ... on null».
load()не нашёл сущность. ОбработайтеNULLдо обращения к полям. - «The field ... does not exist». Поле отсутствует у выбранного bundle, либо в коде использован label вместо machine name.
- Запрос возвращает записи, которых пользователь не должен видеть. Проверьте
accessCheck()и последующую проверку доступа. - После сохранения на странице старые данные. Проверьте, не был ли результат превращён в строку до формирования render array и не потеряны ли cache tags.
- В многозначном поле сохраняется только один элемент. Передайте массив элементов поля и отдельно проверьте cardinality.
При разборе ошибки сначала зафиксируйте тип сущности, ID, bundle, текущего пользователя и язык. Без этих данных одинаковое сообщение может иметь разные причины.
Что проверить дальше
После чек-листа добавьте тесты на четыре сценария: сущность существует, сущность отсутствует, пользователь не имеет доступа, поле пустое или многозначное. Для кода, который выводит данные, отдельно проверьте кеш после смены роли и языка. Затем посмотрите в проде на время Entity Query, размер пакетов массовой обработки и количество отказов доступа. Инвалидация кеша и проектирование собственных типов сущностей — отдельные темы; здесь важна проверка уже используемого Entity API-кода.
Сноска. Cacheability metadata — метаданные кешируемости: cache tags, contexts и срок действия, по которым Drupal понимает, когда результат можно переиспользовать, а когда его нужно пересчитать.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: Entity API — официальная документация Drupal, Working with the Entity API — официальная документация Drupal
Частые вопросы
Нужно ли всегда использовать Entity API вместо прямого SQL?
Почему Entity Query требует явно указать accessCheck()?
Что делать, если load() вернул NULL?
Почему после save() сайт показывает старое значение?
Можно ли использовать этот код в Drupal 9?
Как проверить Entity API-код перед выпуском?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.