Migrate als Ersatz für Update-Hooks
Als Entwickler neige ich dazu, faul zu sein. Ich bin immer auf der Suche nach Tools oder Shortcuts, die mir das Leben einfacher machen.
Neulich stolperte ich über einen Tweet von drubb, in dem er die Verwendung von Migrate anstelle eines benutzerdefinierten Update-Skripts zum Aktualisieren bestehender Entitäten zeigt. Da ich zu dem Zeitpunkt noch keine Verwendung dafür hatte, speicherte ich es in dem "vielleicht später hilfreich"-Teil meines Gehirns ... und vergaß es.
Letzte Woche musste ich jedoch in einem Projekt ein bestehendes Feld zur Formularanzeige aller Paragraphs-Typen hinzufügen. Zusätzlich sollte ein neues Feld hinzugefügt und konfiguriert werden. Da ich die Felder nicht für alle Paragraphs-Typen (es gibt etwa 80 davon in diesem Projekt) manuell konfigurieren wollte (wie Du weisst, bin ich ein fauler Entwickler), hätte ich üblicherweise einen Update-Hook für diese Aufgabe geschrieben. Da fiel mir aber der Tweet wieder ein und ich dachte "Wäre es nicht auch möglich, die Konfiguration einfach mit Migrate zu aktualisieren?".
Was brauchen wir?
Der erste Teil der Aufgabe ist einfach: um ein vorhandenes Feld im Formular einer Entität (in diesem Fall Paragraphs) anzuzeigen, wird es einfach aus dem "ausgeblendeten" Bereich in den Inhaltsbereich gezogen.
Nachdem ich das Feld "Published" in den Inhaltsbereich verschoben hatte, exportierte ich die Konfigurationsänderungen, um zu sehen, was geschieht und erhielt das folgende Ergebnis für core.entity_form_display.paragraph.text.default.yml:
In meiner Migration muss ich also genau diese Konfigurationsänderung für alle Paragraphs-Typen replizieren.
Einstellungen für die Formularanzeige migrieren
Bei der Migration benötige ich zunächst ein Quell-Plugin für alle verfügbaren Paragraphs-Typen. Da ich bereits die notwendigen Änderungen an der Formulardarstellung des Paragraphs-Typs "Text" vorgenommen hatte, braucht das Quell-Plugin auch die Möglichkeit, bestimmte Elemente auszuschließen (naja, ich hätte eventuell die bisherigen Konfigurationsänderungen rückgängig machen und mit einer aktuellen Datenbanksicherung neu beginnen können, aber ...).
- <?php
- namespace Drupal\up_migrate\Plugin\migrate\source;
- use ArrayObject;
- use Drupal\Core\StringTranslation\StringTranslationTrait;
- use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
- use Drupal\migrate\Plugin\MigrationInterface;
- use Drupal\paragraphs\Entity\ParagraphsType;
- /**
- * Source plugin for ParagraphsType.
- *
- * @MigrateSource(
- * id = "up_paragraphs_type",
- * source_module = "up_migrate"
- * )
- */
- class UpParagraphsType extends SourcePluginBase {
- use StringTranslationTrait {
- t as t_original;
- }
- /**
- * List of paragraphs types to exclude.
- *
- * @var array
- */
- protected $exclude = [];
- /**
- * List of paragraph types.
- *
- * @var array
- */
- protected $items = [];
- /**
- * {@inheritdoc}
- */
- $options['context'] = 'up_migrate';
- }
- return $this->t_original($string, $args, $options);
- }
- /**
- * {@inheritdoc}
- */
- public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
- parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
- $this->exclude = $configuration['exclude'];
- }
- }
- /**
- * {@inheritdoc}
- */
- public function fields() {
- return [
- 'id' => $this->t('ID'),
- 'label' => $this->t('Label'),
- ];
- }
- /**
- * {@inheritdoc}
- */
- public function getIds() {
- $ids['id']['type'] = 'string';
- return $ids;
- }
- /**
- * Return a comma-separated list of paragraph type ids.
- */
- public function __toString() {
- }
- /**
- * {@inheritdoc}
- */
- protected function initializeIterator() {
- $this->items = [];
- $paragraphs_types = ParagraphsType::loadMultiple();
- /** @var \Drupal\paragraphs\ParagraphsTypeInterface $paragraphs_type */
- foreach ($paragraphs_types as $paragraphs_type) {
- $this->items[$paragraphs_type->id()] = [
- 'id' => $paragraphs_type->id(),
- 'label' => $paragraphs_type->label(),
- ];
- }
- }
- return (new ArrayObject($this->items))->getIterator();
- }
- /**
- * {@inheritdoc}
- */
- }
- }
Wie im obigen Gist deutlich wird, ist das Quell-Plugin sehr einfach. Es holt sich eine Liste aller verfügbaren Paragraphs-Typen und entfernt die Typen, die ausgeschlossen werden sollen.
Der folgende Schritt ist, eine Migration zu schreiben, die die Konfiguration für die Formularanzeige aktualisiert.
- id: paragraphtypes_form_display__status
- label: Add status field to paragraph form display.
- source:
- plugin: up_paragraphs_type
- exclude:
- - text
- constants:
- entity_type: paragraph
- field_name: status
- form_mode: default
- options:
- region: content
- settings:
- display_label: true
- third_party_settings: { }
- type: boolean_checkbox
- weight: 5
- process:
- bundle: id
- entity_type: constants/entity_type
- field_name: constants/field_name
- form_mode: constants/form_mode
- options: constants/options
- destination:
- plugin: component_entity_form_display
- migration_tags:
- - up_paragraphstype
Die Migration verwendet das neue Quell-Plugin "up_paragraphs_type" und schließt den Paragraphs-Typ "Text" aus der zu verarbeitenden Liste aus. In Zeile 11..17 werden genau die gleichen Anzeigeeinstellungen gesetzt wie im Screenshot, der die Konfigurationsänderungen für den Paragraphs-Typ "Text" zeigt.
Im Abschnitt "process" verarbeitet die Migration die Ergebnisse aus dem Quell-Plugin, wobei nur die vom jeweiligen Paragraphs-Typ gelieferte ID verwendet wird, und ansonsten die zuvor definierten Konstanten genutzt werden. Da die Form-Display-Konfiguration aktualisiert werden soll, wird das " component_entity_form_display"-Plugin als Ziel gewählt, das glücklicherweise direkt von Drupal Core bereitgestellt wird.
Nach dem Durchführen der Migration sind alle auf der Website verfügbaren Paragraphs-Typen so konfiguriert, dass sie das Kontrollkästchen "Veröffentlicht" anzeigen. Yeah!
Was ist mit neuen Feldern?
Aber was ist mit dem neuen Feld, das ich erstellen musste? Im Grunde unterscheidet sich die Migration nicht wirklich von der Obigen. Das Einzige, was hinzukommen muss, ist eine zusätzliche Migration, die die Feldkonfiguration für jeden Paragraphs-Typ erstellt.
Sagen wir, wir möchten ein Textfeld mit dem Namen "Kommentar" für alle Paragraphs-Typen erstellen. Dann muss die Field-Storage für dieses Feld etwa wie folgt erstellt werden:
- id: paragraphtypes_field_storage_paragraphs_comment
- label: Define field storage for field_paragraphs_comment.
- source:
- plugin: empty
- constants:
- entity_type: paragraph
- id: paragraph.field_paragraphs_comment
- field_name: field_paragraphs_comment
- type: string
- cardinality: 1
- settings:
- max_length: 255
- langcode: en
- translatable: true
- process:
- entity_type: constants/entity_type
- id: constants/id
- field_name: constants/field_name
- type: constants/type
- cardinality: constants/cardinality
- settings: constants/settings
- langcode: constants/langcode
- translatable: constants/translatable
- destination:
- plugin: entity:field_storage_config
- migration_tags:
- - up_paragraphstype
Hinweis: Wenn das Feld manuell erstellt wurde (wie bei mir für den Absatztyp "Text"), kann diese Migration übersprungen werden, da der Feldspeicher bereits vorhanden ist und es ansonsten zu Fehlern kommt.
Um das neu erstellte Feld hinzuzufügen, muss für jeden Paragraphs-Typ eine Feldinstanz erstellt werden. Dies kann über das Migrationsziel "entity:field_config" durchgeführt werden:
- id: paragraphtypes_field_paragraphs_comment
- label: Adds field_paragraphs_comment to paragraph types.
- source:
- plugin: up_paragraphs_type
- exclude:
- - text
- constants:
- entity_type: paragraph
- field_name: field_paragraphs_comment
- translatable: true
- label: 'Comment'
- process:
- entity_type: constants/entity_type
- field_name: constants/field_name
- bundle: id
- label: constants/label
- translatable: constants/translatable
- destination:
- plugin: entity:field_config
- migration_tags:
- - up_paragraphstype
- migration_dependencies:
- required:
- - paragraphtypes_field_storage_paragraphs_comment
Ziemlich einfach, oder?
Wie geht es weiter?
Nachdem wir gesehen haben, wie einfach das Erstellen und Aktualisieren der Feldkonfiguration mittels Migrate ist, kamen uns gleich mehrere neue Ideen in den Sinn. Es müsste so beispielsweise auch möglich sein, Entity-Typen mit allen erforderlichen Feldern zu erstellen und die Konfiguration über Migrate zu erzeugen. Dies wäre eine großartige Option, um zusätzliche Funktionen auf einer Website einfach per Mausklick zu aktivieren (natürlich muss die Konfiguration exportiert und eventuell noch Weiteres getan werden).
Aber das ist ein Thema für einen weiteren Artikel ...