Drupal-Tipp: Feldlänge trotz bestehender Daten anpassen
Jeder Drupal-Entwickler steht früher oder später vor folgendem Problem: Ein Kunde oder Redakteur bittet darum, ein Standard-Textfeld (wie eine Headline oder Subheadline) von 255 auf 512 Zeichen zu erweitern. Doch egal, ob du die Änderung in die YAML-Konfiguration einträgst oder die Entity API bemühst: Beim Speichern oder Konfigurations-Import läufst du unweigerlich in einen fatalen Fehler.
Wenn ein Feld bereits Daten enthält, weigert sich die Entity API strikt, das zugrundeliegende Datenbank-Schema zu ändern, um versehentlichen Datenverlust zu verhindern. Dieser Beitrag zeigt dir, wie du diese Einschränkung sicher umgehst, die Datenbanktabellen direkt modifizierst und Drupals internen Schema-Status synchronisierst, ohne eine einzige Zeile deiner Produktionsdaten zu verlieren.
„Das Ändern einer Feldgröße bei vorhandenen Daten wirft eine FieldStorageDefinitionUpdateForbiddenException. Das System verhindert jegliche Schema-Updates über die Standard-API, um die Datenintegrität zu garantieren, was eine manuelle Schema-Manipulation erforderlich macht.“
Um dieses Problem sauber zu lösen, müssen wir die High-Level Entity API umgehen und direkt auf einer niedrigeren Ebene mit der State-Engine interagieren.
Die Probleme mit dem Standard-API-Ansatz:
- Vorhandene Daten führen zu Exception: SqlContentEntityStorageSchema::onFieldStorageDefinitionUpdate() löst sofort eine Exception aus, sobald Daten in der Tabelle existieren.
- Konfiguration ist out-of-sync: Das bloße Ändern der .yml-Feldkonfigurationsdatei und deren Import via Drush schlägt während der Konfigurationssynchronisation fehl, wenn das aktive Datenbank-Schema nicht übereinstimmt.
- Fehler im Status Report: Wenn du Konfigurationswerte erzwingst, meldet Drupals Statusbericht (/admin/reports/status) eine Diskrepanz im Entity-Schema, was Hook-Implementierungen und automatisierte Tests zum Absturz bringen kann.
Der Database-First-Ansatz: Die Lösung des Problems
- Sichere Anpassung: Das Ändern einer Spalte von varchar(255) auf varchar(512) ist eine nicht-destruktive Datenbankoperation. MySQL/MariaDB und PostgreSQL verarbeiten dies, ohne Daten abzuschneiden oder Tabellen dauerhaft zu sperren.
- Manuelle Anpassung der Definitionen: Indem wir die Low-Level Key-Value-Collection entity.storage_schema.sql direkt aktualisieren, signalisieren wir dem Entity Definition Update Manager, dass der Zustand konsistent ist.
- Direkte Konfigurationsmanipulation: Das direkte Ändern der Field-Storage-Konfiguration via Configuration Factory hebelt die API-Validierung aus. So greifen die neuen Zeichenlimits im System sofort, ohne eine Exception auszulösen.
Wie kann das Ganze nun umgesetzt werden?
Die Änderung wird mithilfe eines hook_post_update_NAME()-Hooks in der .post_update.php-Datei eines Moduls implementiert. Post-Update-Hooks laufen nach den regulären hook_update_N-Hooks ab. Das stellt sicher, dass der Service Container vollständig verfügbar und einsatzbereit ist. In unserem Beispiel soll die Feldlänge von field_subheadline_text innerhalb eines Paragraph-Feldtyps angepasst werden.
Folgende Logik muss dafür in einem Custom Module unter folgender Verzeichnisstruktur hinterlegt werden:
web/modules/custom/my_module/
├── my_module.info.yml
├── my_module.module
└── my_module.post_update.php 1<?php
2
3/**
4 * @file
5 * Post update functions for my_module module.
6 */
7
8declare(strict_types=1);
9
10use Drupal\Core\Database\Database;
11use Drupal\field\FieldStorageConfigInterface;
12
13/**
14 * Increase the maximum length of paragraph subheadline field to 512 characters.
15 */
16function my_module_post_update_increase_max_subheadline_length(array &$sandbox): void {
17 $entity_type = 'paragraph';
18 $field_name = 'field_subheadline_text';
19 $new_length = 512;
20 $value_column = "{$field_name}_value";
21 $tables = [
22 "{$entity_type}__{$field_name}",
23 "{$entity_type}_revision__{$field_name}",
24 ];
25
26 // 1. Alter the DB columns directly. Drupal's entity API refuses to change a
27 // field's schema once it holds data
28 // (SqlContentEntityStorageSchema::onFieldStorageDefinitionUpdate() throws
29 // FieldStorageDefinitionUpdateForbiddenException), so we widen the columns
30 // ourselves and then sync the tracked definitions below. Widening a varchar
31 // never truncates existing values.
32 $db_schema = Database::getConnection()->schema();
33 $column_spec = ['type' => 'varchar', 'length' => $new_length, 'not null' => FALSE];
34 foreach ($tables as $table) {
35 if ($db_schema->tableExists($table)) {
36 $db_schema->changeField($table, $value_column, $value_column, $column_spec);
37 }
38 }
39
40 // 2. Sync the installed SQL storage schema so the entity definition update
41 // manager stops reporting a mismatch. This is stored in the
42 // "entity.storage_schema.sql" key-value collection under the key
43 // "{entity_type}.field_schema_data.{field_name}" (see
44 // SqlContentEntityStorageSchema::loadFieldSchemaData()).
45 $kv = \Drupal::keyValue('entity.storage_schema.sql');
46 $schema_key = "{$entity_type}.field_schema_data.{$field_name}";
47 $field_schema = $kv->get($schema_key);
48 if (is_array($field_schema)) {
49 foreach ($field_schema as &$table_schema) {
50 if (is_array($table_schema)
51 && is_array($table_schema['fields'] ?? NULL)
52 && is_array($table_schema['fields'][$value_column] ?? NULL)
53 && array_key_exists('length', $table_schema['fields'][$value_column])
54 ) {
55 $table_schema['fields'][$value_column]['length'] = $new_length;
56 }
57 }
58 unset($table_schema);
59 $kv->set($schema_key, $field_schema);
60 }
61
62 // 3. Update the active field storage config. Use the raw config factory to
63 // bypass the entity API (a FieldStorageConfig::save() would re-trigger the
64 // forbidden-schema-change check from step 1).
65 \Drupal::configFactory()
66 ->getEditable("field.storage.{$entity_type}.{$field_name}")
67 ->set('settings.max_length', $new_length)
68 ->save();
69
70 // 4. Update the last-installed field storage definition so it matches the
71 // active config.
72 $repository = \Drupal::service('entity.last_installed_schema.repository');
73 $definitions = $repository->getLastInstalledFieldStorageDefinitions($entity_type);
74 if (isset($definitions[$field_name]) && $definitions[$field_name] instanceof FieldStorageConfigInterface) {
75 $definitions[$field_name]->setSetting('max_length', $new_length);
76 $repository->setLastInstalledFieldStorageDefinitions($entity_type, $definitions);
77 }
78
79 // 5. Clear cached definitions so the new schema takes effect immediately.
80 \Drupal::service('entity_type.manager')->clearCachedDefinitions();
81 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
82}
83
Testen und überprüfen
Sobald der Code implementiert ist, müssen folgende Kommandos im Terminal ausgeführt werden, um die Schema-Migration anzuwenden und die Container-Caches zu leeren:
# Post-Update-Hooks ausführen, um unsere manuelle Migration zu triggern
drush updatedb
# Caches neu aufbauen, damit der Entity Field Manager die Definitionen frisch einliest
drush cache:rebuild
# Konfiguration exportieren, damit deine field.storage.*.yml dem neuen Datenbankzustand entspricht
drush config:exportAlternativ können diese Schritte auch über die Drupal Benutzeroberfläche durchgeführt werden, was jedoch mit etwas mehr Zeitaufwand verbunden ist. Im Anschluss sollte Drupals Statusbericht (/admin/reports/status) noch einmal geprüft werden, um sicherzustellen, dass keine Fehler bezüglich der Feldkonfiguration mehr vorliegen.
Besonderheiten bei Update & Fallbacks
- Datenbank-Treiber: Diese Strategie nutzt die Standard-Syntax der Drupal Schema API (changeField()). Sie funktioniert nahtlos auf MySQL, MariaDB und PostgreSQL. Falls du SQLite nutzt, verhält sich das Ändern von Spaltenlängen anders, da SQLite keine strikten String-Limits erzwingt – die Metadaten-Synchronisationen sind dennoch zwingend erforderlich.
- Views-Integration: Wenn das Feld aktuell in Views als Argument oder Filter genutzt wird, muss der Views-Cache geleert werden (drush cache:clear views). Die View-Konfiguration selbst trackt die Zeichenlänge nicht direkt, weshalb sie ohne Modifikation weiter funktioniert.
- Form Validierung: Die Anpassung der Feldspeicher-Einstellung (Field Storage) erhöht lediglich das Limit auf Datenbankebene. Falls die Konfiguration der Feldinstanz (Field Instance) die Eingaben an anderer Stelle einschränkt oder überschreibt, müssen diese ebenfalls überprüft werden.