Drupal 8 - andyceo/documentation GitHub Wiki
Процедура написания миграции полей:
- source
- process
- destination
- migration
- написать yml-файлик
Как мигрировать мультиязычный контент?
[22:47:24] Andy Postnikov: там ведь должен выываться addTranslation
[23:47:22] Andy Postnikov: <chx> andypost: there's some work standard for multilingual stuf in the sandbox
[23:49:40] Andy Postnikov: https://www.drupal.org/node/2105305/git-instructions/drupal6-i18n/nonmaintainer полагаю оно
Источники:
- Migrating to Drupal 8 - рекомендую, очень подробная и обстоятельная статья.
- IMP
- Migration API in Drupal 8
- Writing D6 to D8 config entities migrations
- chx's sandbox: IMP
- https://docs.google.com/presentation/d/1ZqvUBIo114bPKU4A0Wu4awadf1HIG0YlHJ_AWt6DrKU/edit
- Лучшие ресурсы для начала работы с Drupal 8
- How to create custom entity in Drupal 8
- Creating Drupal 8.x modules
- Drupal 8 isn't Scary, Pt. 1: Introduction & Creating a Hello World Module
- В элементе формы, на изменения которого нужно реагировать аяксом, реализовать свойство
#ajax
. - Написать php-коллбек в классе формы, который будет возвращать тот элемент страницы, который нужно перерисовать. В коллбеке нужно возвращать только тот элемент, который планируется перестроить. Не надо пытаться изменить
$form
и$form_state
. - Вся перестройка формы осуществляется только в конструкторе формы, в зависимости от переданных в форму параметров (
$form_state
) - Примеры:
- форма экспорта ConfigSingleExportForm (
\Drupal\config\Form\ConfigSingleExportForm
)
Последовательность вызовов при срабатывании Ajax в формах:
- build (происходит только когда Ajax сработал первый раз, все последующие срабатывания ajax начинаются с validate)
- validate
- build
- ajax callback
- Нужно написать плагин блока. Он представляет из себя php-файл с классом плагина. Класс описан аннотацией блокового плагина. php-файл нужно положить в папку
yourmodule/src/Plugin/Block/YourModuleBlock.php
. Примеры:-
UserLoginBlock (
\Drupal\user\Plugin\Block\UserLoginBlock
) -
BookNavigationBlock (
\Drupal\book\Plugin\Block\BookNavigationBlock
)
-
UserLoginBlock (
Нужно создать плагин типа FieldType. Также не забыть определить метод isEmpty(). Также, если какое-то свойство (property) поля - вычисляемое, то можно реализовать метод preSave(), в котором это поле будет вычисляться. В аннотации поля, нужно указать default_widget = "SOME_WIDGET"
и default_formatter = "SOME_FORMATTER"
. Можно не указывать дефолтные виджеты и форматтеры, только в этом случае нужно поставить флаг в аннотации no_ui = TRUE
.
Также, если ваше поле может выводиться через стандартные виджеты и форматтеры, то можно указывать их в качестве дефолтных, но при этом нужно в вашем модуле отальтерить эти виджеты, чтобы они могли цепляться к вашему полю. См.
- class YourEntity extends ContentEntityBase implements YourEntityInterface {
- class Slide extends ContentEntityBase { - предпочтительный вариант
- class Slide extends Entity {
-
Через реализацию метода baseFieldDefinitions($entity_type). Все получаемые поля являются типа FieldItemList. Внутри метода пишем что-то вроде:
$fields['your_field'] = FieldDefinition::create('integer') ->setLabel(t('Campaign ID')) ->траливали(); $fields['uuid'] = FieldDefinition::create('uuid') ->setLabel(t('UUID')) ->setDescription(t('The YOUR_ENTITY UUID.')) ->setRequired(TRUE) ->setReadOnly(TRUE);
-
Поле uuid - обязательное для любой сущности. распределенный первичный ключ
-
Все возможные типы полей можно посмотреть здесь:
core/lib/Drupal/Core/Field/Plugin/Field/FieldType
. на момент написания этого текста (30.05.2014) их 17. -
Краткое описание сеттеров поля (которые FieldDefinition):
- setLabel() - задает метку поля (string)
- setDescription() - задает описание поля (string)
- setReadOnly() - объявляет поле только для чтения, неизменяемое поле (обычно для первичных ключей) (boolean)
- setRequired() - объявляет поле обязательным для заполнения (boolean)
- setSetting() - задает настройки поля в терминах поля Drupal 7. Влияет на схему данных (ограничения на хранение поля, схему БД) (array)
- setDisplayOptions(string $display_context = 'view' or 'form', array $options()) - если 'form' - то задает опции отображения поля в режиме формы (ввод значений в поле). Аналогично объекту form_display у сущности (entity). Если 'view',
- setDisplayConfigurable(string $display_context = 'view' or 'form', boolean $flag) - устанавливает флаг, что данный $display_context (который может быть 'view' или 'form') можно (или нельзя) настраивать из админки.
- setPropertyConstraints - задает ограничения при сохранении поля
- setClass
- setComputed
- setConstraints
- setDataType
- setItemDefinition
- setName
- setQueryable
- setTranslatable
CustomBlock.php
controllers = {
"storage" = "Drupal\yourentity\YourEntityStorage",
"view_builder" = "Drupal\yourentity\YourEntityViewBuilder",
"access" = "Drupal\yourentity\YourEntityAccessController",
"list_builder" = "Drupal\yourentity\YourEntityListBuilder",
"form" = {
"default" = "Drupal\yourentity\YourEntityForm",
"delete" = "Drupal\yourentity\Form\YourEntityDeleteForm",
"edit" = "Drupal\yourentity\YourEntityForm"
},
},
-
storage - хранилище сущности. Обязателен. Чтобы выбрать, какой контроллер использовать для вашей сущности, можно руководствоваться следующими правилами:
- для простой сущности без полей - простой контроллер по умолчанию
EntityDatabaseStorage
. При этом сохранение в бд придется писать самому, руками. Пример:Drupal\menu_link\Entity\MenuLink
- для сущности, у которой поля определяются через
baseFieldDefinitions()
, нужно использоватьContentEntityDatabaseStorage
- тогда, если в форме вы поля назвали так же, как в схеме, сохранение произойдет автоматически.
- для простой сущности без полей - простой контроллер по умолчанию
-
view_builder - контроллер, отвечающий за отображение сущности.
-
access - контроллер, отвечающий за доступ к сущности. Позволяет работать с ограничениями доступа к сущности.
-
list_builder - контроллер списка сущности. Он отвечает за генерацию страницы со списком сущностей этого типа. Создается через сервис
EntityManager
, поэтому на его вход (в конструктор) идут 4 параметра:-
EntityTypeInterface $entity_info
-
EntityStorageControllerInterface $storage
-
ModuleHandlerInterface $module_handler
-
QueryFactory $query_factor Конструктор должен быть объявлен так:
public function __construct(EntityTypeInterface $entity_info, EntityStorageInterface $storage, ModuleHandlerInterface $module_handler, QueryFactory $query_factory) { parent::__construct($entity_info, $storage, $module_handler); $this->queryFactory = $query_factory; // enter your specific code here }
Также необходимо реализовать метод
createInstance()
, который создаст экземпляр лист-контроллера.public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) { return new static( $entity_info, $container->get('entity.manager')->getStorage($entity_info->id()), $container->get('module_handler'), $container->get('entity.query') ); }
Как правило, контроллер списка должен содержать следующий набор методов:
- load()
- buildHeader()
- buildRow()
- render()
-
-
form - массив форм. Формы сущности - это класс, реализующий интерфейс
EntityFormInterface
. Этот класс является расширенным вариантом обычной формыFormInterface
. Самое основное отличие от класса обычной формы - это то, что форма, реализуемая черезEntityForm
, обязательно связана с сущностью. Это форма именно для сущности. См. Change notice- default
- delete
- edit
Из всех контроллеров, необходимым для работы с сущностью является только storage-контроллер.
-
Ставим в сущности к полю флаг revionable:
->setRevisionable(TRUE)
-
Затем прогоняем апдейт базы данных
-
Перенести контент из основной таблицы сущности (base_table) в таблицу с ревизиями:
UPDATE `base_table_revision` r JOIN `base_table` bt ON r.id = bt.id SET r.field1 = bt.field1, r.field2 = bt.field2, r.fieldn = bt.fieldn;
Внимание! Все нижеперечисленное, стоит делать, не перемешивая с другими обновлениями схемы этой сущности, особенно, не добавлять новых полей во время этого апдейта; сначала убрать ревизии, а только потом - добавить новое поле. Это связано с тем, что Drupal 8 сделает добавление нового поля только один раз в таблицу, и однажды добавив его, будет думать, что оно там уже есть, даже несмотря на то, что его там нет.
- Удаляем из кода все, связанное с ревизиями (из аннотации, убираем у всех полей флаг
setRevisionable(TRUE)
) - Переименовываем
base_table
в какой-нибудьbase_table1
- Создаем копию таблицы
base_table1
(только структура!) и называем ееbase_table
. Т.е. создаем копию старой таблицыbase_table
, но без контента. Это важно, т.к. Drupal обновляет поля только если нет контента. - Проводим апдейт бд из админки под рутом.
- Затем, вручную удаляем из переименованной таблицы
base_table1
полеrevision_id
. Удаляем (или переименовываем) таблицуbase_table_revision
. (где-то на этом шаге, у меня сама собой удалилась таблицаbase_table_revision
). - Переименовываем таблицу
base_table1
обратно вbase_table
. - Заходим в отчеты - отчет о состоянии и проводим еще одно обновление бд, где будут перечислены все поля.
- После этого все готово. Можно на всякий случай сделать
drush cr
, и продолжать работать.
Если подразумеваются настройки, то нужно:
- Создать папки
yourmodule/config
иyourmodule/config/schema
- Создать схему конфига:
yourmodule/config/schema/yourmodule.schema.yml
. Подробнее о схеме конфига: Configuration schema/metadata - Создать конфиг по умолчанию:
yourmodule/config/yourmodule.settings.yml
, который содержит настройки по умолчанию.
Ищем функцию getFieldTypeMap()
и в классе core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
для MySQL увидим, как и какие типы схемы базы данных Drupal соответвуют типам полей в MySQL. Аналогично можно посмотреть для pgsql
и sqlite
.
Я создал поле типа entity reference, по ошибке оно указывает на неправильную сущность. Можно ли это поменять?
Да. Нужно обновить конфигурацию. Например, нужно поменять ссылку с сущности user
на сущность person
для Field storage
и самого Field
(в данном примере поле называется field_person
, находится оно в сущности user
и ссылается на person
):
drush cset field.storage.user.field_person settings.target_type person
drush cset field.field.user.user.field_person settings.handler 'default:person'
drush cr
После этого, все должно заработать. Старый контент нужно было бы мигрировать вручную.
- The magic behind Drupal’s render elements
- Drupal #Attached
- The Drupal 8 render pipeline - одна из наиболее важных документаций по рендеру.
Если прямой запрос к базе данных, то смотреть класс \Drupal\Core\Database\Query\Select
.
Он имплементирует интерфейсы:
- Drupal\Core\Database\Query\SelectInterface
- Drupal\Core\Database\Query\ConditionInterface
- condition()
- where()
- isNull($field)
- isNotNull($field)
- exists(SelectInterface $select) - Sets a condition that the specified subquery returns values.
- notExists(SelectInterface $select) - Sets a condition that the specified subquery returns no values.
- andConditionGroup()
- orConditionGroup()
Концепция простая: на ноды вешаются замки, а пользователю выдаются ключи. От какого замка ключ подошел - настолько нода и приоткрывается пользователю. Важно помнить, что при сохранении ноды вы не обладаете информацией о том, какой пользователь будет смотреть ноду (нет информации о будущих пользователях). А при выдаче ключей пользователю вы не знаете, какие ноды он соберется смотреть.
Поэтому при сложных случаях, следует все признаки ноды закодировать в один grant id (gid) или realm, а динамические, типа совпадение отдела пользователя с отделом материала - в другой.
Используются два хука:
- hook_node_access_records: навешивает "замки" на ноды
- hook_node_grants: выдает пользователям ключи
Подробнее по ссылкам:
Предположим, что мы уже находимся в сборочной директории, например /tmp/build
.
git clone --branch 8.2.x https://git.drupal.org/project/drupal.git
# Installing contrib modules
cd drupal/modules
mkdir contrib
cd contrib
git clone contrib_module1
git clone contrib_module2
# Installing your modules
cd ..
mkdir custom
cd custom
git clone your_module1
git clone your_module2
# Installing your installation profile
cd ../../profiles
git clone your_profile
# Run composer
cd ..
composer install
# Create needed folders and files and get them write permissions
cd sites/default
mkdir -p files/translations files/simpletest
cp default.settings.php settings.php
chown -R www-data: files/ settings.php
Ссылки: