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

Formularanzeige eines Paragraphs-Typen.

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:

Konfigurationsänderungen in der Anzeige des Formulars für Paragraphs

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} */ protected function t($string, array $args = [], array $options = []) { if (empty($options['context'])) { $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); if (isset($configuration['exclude'])) { $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() { return implode(', ', array_column($this->items, 'id')); } /** * {@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(), ]; } if (!empty($this->exclude)) { $this->items = array_diff_key($this->items, array_flip($this->exclude)); } return (new ArrayObject($this->items))->getIterator(); } /** * {@inheritdoc} */ public function count($refresh = FALSE) { parent::count($this->items); } }

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

Stefan Borchert
  • Geschäftsführung

Stefan ist Co-Geschäftsführer und zuständig für die Qualitätssicherung bei undpaul. Er beherrscht ver­schiedenste Programmiersprachen und hat ein Auge für das User Interface. Er ist Maintainer diverser Module, Mitarbeiter am Drupal Core und Mitglied der ersten Stunde der Drupal User Group Hannover. Acquia Certified Developer seit Juli 2014. Entgegen aller gängigen Vorurteile über Programmierer, zieht er Bewegung an der frischen Luft dem Sofa vor.