Drupal 8: First Impressions for the Back-End Developer
Drupal 8 is in beta now, and recently I’ve had a chance to start working with it. While much of the admin interface is comparable to Drupal 7, there...
Data protection is one of the primary advantages of Drupal, but sometimes there are exceptions to the rule and you might need to modify a field to account for some change in business needs. There are a few rule bends...
This article is one of Metal Toad's Top 20 Drupal Tips. Enjoy!
Data protection is one of the primary advantages of Drupal, but sometimes there are exceptions to the rule and you might need to modify a field to account for some change in business needs. There are a few rule bends (read hacks) that can be done to circumvent Drupal's checks and still maintain data integrity. You should only perform this when extending a field or changing something reasonable that is allowed by the database server. For example, expanding a varchar length or changing unformatted string into a formatted text field.
For a bit of background, I subscribe to the orthodoxy that every Drupal 8 deployment must run through the following command chain without errors:
drush updatedb -y; drush config-import -y; drush entity-updates -y; drush cache-rebuild
And so, in between each code snippet below, I would perform test deployments with this chain on my local, where a failure during the entity schema update lead me to the last "ahHa!" moment.
In this example, I had to convert the Page Header Content field from a plain text area to a WYSIWYG input. This seemed to be an extra simple situation, since the field_page_header_content_value
column was already typed as LONGTEXT in the database and the only change needed was a new column for the filter format value in the tables, paragraph__field_page_header_content
and paragraph_revision__field_page_header_content
. So simple enough that it seemed that migrating to a whole new field would be more excessive than making an exception to Drupal's rules.
My approach started off with comparing the configuration YAMLs between unformatted and formatted text fields. Simply hand-altering the field's files produced no visible change to the site and the files would revert in a following configuration export. So, I continued with a method that had worked in Drupal 7 with Features, which was forcing the database table and configuration changes during an update script. Adding the new column to the database tables was the easy part and I assumed this this would allow the configuration import to take hold.
function mycustom_update_8001() { // Variables to add the format column and its index $table = 'field_page_header_content'; $column = 'format'; $field = [ 'type' => 'varchar_ascii', 'length' => 255, ]; $schema = Database::getConnection()->schema(); // Update the data table $schema->addField('node__' . $table, $table . '_' . $column, $field); $schema->addIndex('node__' . $table, $table . '_' . $column, [$table . '_' . $column], [ 'fields' => [$table . '_' . $column => $field] ]); // The revision table $schema->addField('node_revision__' . $table, $table . '_' . $column, $field); $schema->addIndex('node_revision__' . $table, $table . '_' . $column, [$table . '_' . $column], [ 'fields' => [$table . '_' . $column => $field] ]); ... }
When testing it, the edit form did display a WYSIWYG instead of a text area, but the format did not save and the field's value was still sanitized through the check plain filter. Again, a following configuration export reverted the YAML, and so it seemed that the import was being silently defiant somewhere. But, I was getting a little closer, so next I tried to force the change to the currently installed configuration during the update, before the import is even ran.
function mycustom_update_8001() { ... // Force the current configuration to be exactly like the YAML, // so that the subsequent import does not detect a change $config = \Drupal::configFactory() ->getEditable('field.storage.node.field_page_header_content'); $depends = $config->get('dependencies'); $depends['module'][] = 'text'; $config->set('dependencies', $depends); $config->set('type', 'text_long'); $config->set('settings', []); $config->set('module', 'text'); $config->save(); ... }
After a mock deployment, Drupal now detected that the entity schema needed to be updated and during the entity-update
command, an error was thrown:
The SQL storage cannot change the schema for an existing field (field_page_header_content in node entity) with data
This was interesting to me as I had already modified the database and updated the field configurations, so something else was keeping record and failing the schema update. It turned out that the configuration entity, EntityLastInstalledSchemaRepository
, is used to compare for any new changes that need to be done to the database. So, if it is rewritten to reflect configuration changes that have recently been made, then that error should not be thrown.
function mycustom_update_8001() { ... // Current node field configurations $field_manager = \Drupal::getContainer()->get('entity_field.manager'); // Because the manager was already loaded before the above config was forced, // it will return the old configuration that was cached and so the cache needs clearing $field_manager->clearCachedFieldDefinitions(); $field_storage_configs = $field_manager->getFieldStorageDefinitions('node'); // Get the last installed configuration manager // This is the gatekeeper that determines if an update is needed or can be done $last_installed_repo = \Drupal::getContainer()->get('entity.last_installed_schema.repository'); // Get the last installed configurations for all node fields // These are iterative objects and need to stored as such, not just native arrays, // so reusing the previously set configuration tree is not an option $last_installed_configs = $last_installed_repo->getLastInstalledFieldStorageDefinitions('node'); // Force the last installed config to be the current for the field $last_installed_configs['field_page_header_content'] = $field_storage_configs['field_page_header_content']; $last_installed_repo->setLastInstalledFieldStorageDefinitions('node', $last_installed_configs); }
At this point, all of the field configurations were in agreement that the field had always been this new configuration and, after an error-free deployment, the field acted entirely as desired by allowing users to format their WYSIWYG content. To reiterate, this only works if you modify a Drupal field entity's storage in a way that your database server allows and data is not lost. Also, note that when making some alters on a very large (gigs) table, the server can still bog down or crash depending on its configurations, so YMMV.
mycustom.install
function mycustom_update_8001() { // Set up date to add the format column $table = 'field_page_header_content'; $column = 'format'; $field = [ 'type' => 'varchar_ascii', 'length' => 255, ]; $schema = Database::getConnection()->schema(); // Update the data table $schema->addField('node__' . $table, $table . '_' . $column, $field); $schema->addIndex('node__' . $table, $table . '_' . $column, [$table . '_' . $column], [ 'fields' => [$table . '_' . $column => $field] ]); // The revision table $schema->addField('node_revision__' . $table, $table . '_' . $column, $field); $schema->addIndex('node_revision__' . $table, $table . '_' . $column, [$table . '_' . $column], [ 'fields' => [$table . '_' . $column => $field] ]); // Force the current configuration to be exactly like the YAML, // so that the subsequent import does not detect a change $config = \Drupal::configFactory() ->getEditable('field.storage.node.field_page_header_content'); $depends = $config->get('dependencies'); $depends['module'][] = 'text'; $config->set('dependencies', $depends); $config->set('type', 'text_long'); $config->set('settings', []); $config->set('module', 'text'); $config->save(); // Current node field configurations $field_manager = \Drupal::getContainer()->get('entity_field.manager'); // Because the manager was already loaded before the above config was forced, // it will return the old configuration that was cached $field_manager->clearCachedFieldDefinitions(); $field_storage_configs = $field_manager->getFieldStorageDefinitions('node'); // Get the last installed manager, this is the gatekeeper that determines if // an update is needed or can be done $last_installed_repo = \Drupal::getContainer()->get('entity.last_installed_schema.repository'); // Get the last installed configurations for node fields // These are iterative objects and need to stored as such, not just simple arrays, // so reusing the previously set configs is not an option $last_installed_configs = $last_installed_repo->getLastInstalledFieldStorageDefinitions('node'); // Force the last installed config to be the current for the field $last_installed_configs['field_page_header_content'] = $field_storage_configs['field_page_header_content']; $last_installed_repo->setLastInstalledFieldStorageDefinitions('node', $last_installed_configs); }
Git Diff of Configuration Ymls
From b5e515e907821a228b16174b98fb488554cc41f6 Mon Sep 17 00:00:00 2001 From: Marcus Bernal Date: Mon, 19 Dec 2016 15:06:59 -0800 Subject: [PATCH] configs --- ...tity_form_display.node.landing_page.default.yml | 3 +- ...tity_view_display.node.landing_page.default.yml | 3 +- ...node.landing_page.field_page_header_content.yml | 4 +- ...ield.storage.node.field_page_header_content.yml | 8 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cim/sync/core.entity_form_display.node.landing_page.default.yml b/cim/sync/core.entity_form_display.node.landing_page.default.yml index 0455522..90ff8ae 100644 --- a/cim/sync/core.entity_form_display.node.landing_page.default.yml +++ b/cim/sync/core.entity_form_display.node.landing_page.default.yml @@ -10,6 +10,7 @@ dependencies: module: - paragraphs - path + - text id: node.landing_page.default targetEntityType: node bundle: landing_page @@ -28,7 +29,7 @@ content: rows: 5 placeholder: '' third_party_settings: { } - type: string_textarea + type: text_textarea field_page_sections: type: entity_reference_paragraphs weight: 3 diff --git a/cim/sync/core.entity_view_display.node.landing_page.default.yml b/cim/sync/core.entity_view_display.node.landing_page.default.yml index e650f47..1e69210 100644 --- a/cim/sync/core.entity_view_display.node.landing_page.default.yml +++ b/cim/sync/core.entity_view_display.node.landing_page.default.yml @@ -10,6 +10,7 @@ dependencies: module: - entity_reference_revisions - user + - text id: node.landing_page.default targetEntityType: node bundle: landing_page @@ -27,7 +28,7 @@ content: label: hidden settings: { } third_party_settings: { } - type: basic_string + type: text_default field_page_sections: type: entity_reference_revisions_entity_view weight: 1 diff --git a/cim/sync/field.field.node.landing_page.field_page_header_content.yml b/cim/sync/field.field.node.landing_page.field_page_header_content.yml index 44cf387..6c147c8 100644 --- a/cim/sync/field.field.node.landing_page.field_page_header_content.yml +++ b/cim/sync/field.field.node.landing_page.field_page_header_content.yml @@ -5,6 +5,8 @@ dependencies: config: - field.storage.node.field_page_header_content - node.type.landing_page + module: + - text id: node.landing_page.field_page_header_content field_name: field_page_header_content entity_type: node @@ -16,4 +18,4 @@ translatable: false default_value: { } default_value_callback: '' settings: { } -field_type: string_long +field_type: text_long diff --git a/cim/sync/field.storage.node.field_page_header_content.yml b/cim/sync/field.storage.node.field_page_header_content.yml index 45b669d..13c1369 100644 --- a/cim/sync/field.storage.node.field_page_header_content.yml +++ b/cim/sync/field.storage.node.field_page_header_content.yml @@ -4,13 +4,13 @@ status: true dependencies: module: - node + - text id: node.field_page_header_content field_name: field_page_header_content entity_type: node -type: string_long -settings: - case_sensitive: false -module: core +type: text_long +settings: { } +module: text locked: false cardinality: 1 translatable: true -- 2.8.1
Drupal 8 is in beta now, and recently I’ve had a chance to start working with it. While much of the admin interface is comparable to Drupal 7, there...
I've been playing with D7 forms lately and have found #states to be somewhat challenging due to lack of documentation on Form API page. I've poked...
In this article, we will be building a custom migration which will import users from a Drupal 7 site into a Drupal 8 site. The migration will include...
Be the first to know about new B2B SaaS Marketing insights to build or refine your marketing function with the tools and knowledge of today’s industry.