Drupal 8 Configuration system for coders and site builders
who am I? Kristof De Jaeger @swentel co-founder of eps & kaas co-maintainer of Field API Original creator of Display Suite
Outline What s the problem How did we solve it Simple static settings Configuration entities Deployment - with demo Configuration schema Context, events and overrides
What problems are we trying to solve? Dev admin/config/foo Setting 1 Setting 2 text label node/4 TEST test test test test test test test test test test test test test test Variable soup Save Live admin/config/foo Setting 1 old text Setting 2 label Save node/4 Welcome This is real content on the live site that end users are viewing Database Database
What problems are we trying to solve? Dev Live admin/config/foo node/4 admin/config/foo node/4 Setting 1 Setting 2 text label Save TEST test test test test test test test test test test test test test test Setting 1 Setting 2 old text label Save Welcome This is real content on the live site that end users are viewing Database Database Danger! Want to bring over configuration changes from dev, but not overwrite live content!
What problems are we trying to solve? variable_set()/variable_get() napkins db_select()/db_update()/ db_delete() hook_update_n() drush fu $conf[...]; ctools_export_object()/ ctools_export_load_object() http://www.flickr.com/photos/bean/322616749
The solution Files using the YAML specification Active and staging directory Cached in the database using a standard cache interface Config directory changed via settings.php This is all pluggable
The anatomy of a configuration file
system.site.yml
system.site.yml
system.site.yml
system.site.yml name: 'Configuration management' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: '' 404: '' front: node
The API Drupal::config() ->get() ->set() ->save()
Accessing data
$site_name = Drupal::config('system.site')->get('name'); name: 'Configuration management' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: '' 404: '' front: node
$page_data = Drupal::config('system.site')->get('page'); name: 'Configuration management' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: '' 404: '' front: node
$frontpage = Drupal::config('system.site')- >get('page.front'); name: 'Configuration management' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: '' 404: '' front: node
$all_the_data = Drupal::config('system.site')->get(); name: 'Configuration management' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: '' 404: '' front: node
Saving data
Drupal::config('system.site') ->set('name', 'CMI is good') ->save(); name: 'CMI is good' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: '' 404: '' front: node
Drupal::config('system.site') ->set('name', 'CMI is great') ->set('page', array( 403 => 'access-denied', 404 => 'not-found', front => 'user', )) ->save(); name: 'CMI is great' mail: admin@example.com slogan: 'makes Drupal 8 cex -y' page: 403: access-denied 404: not-found front: user
simple settings system_settings_form is dead create your own submit callback you are responsible for saving configuration ship with default configuration file
{language} {field_config} {date_format_type} {filter_format} {field_config_instance} {date_format_locale} {node_type} {role_permission} {role} {date_formats} {system} {variable} {filter}
Config all the things!
Config entities
/** Saturday 16 * November The 13category ID. use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; /** * Defines the contact category entity. * * @EntityType( * id = "contact_category", * label = @Translation("Category"), * module = "contact", * controllers = { * controller_class = "Drupal\contact\CategoryStorageController", * list = "Drupal\contact\CategoryListController", * form = { * "add" = "Drupal\contact\CategoryFormController" * "edit" = "Drupal\contact\CategoryFormController" * } * } * uri_callback = "contact_category_uri", * config_prefix = "contact.category", * entity_keys = { * "id" = "id", * "label" = "label", * "uuid" = "uuid" * } * ) */ class Category extends ConfigEntityBase implements CategoryInterface {
/** Saturday 16 * November The 13category ID. use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; /** * Defines the contact category entity. * * @EntityType( * id = "contact_category", * label = @Translation("Category"), * module = "contact", * controllers = { * controller_class = "Drupal\contact\CategoryStorageController", * list = "Drupal\contact\CategoryListController", * form = { * "add" = "Drupal\contact\CategoryFormController" * "edit" = "Drupal\contact\CategoryFormController" * } * } * uri_callback = "contact_category_uri", * config_prefix = "contact.category", * entity_keys = { * "id" = "id", * "label" = "label", * "uuid" = "uuid" * } * ) */ class Category extends ConfigEntityBase implements CategoryInterface {
namespace Drupal\contact\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; /** * Defines the contact category entity. * * @Plugin( * id = "contact_category", * label = @Translation("Category"), * module = "contact", * controllers = { * controller_class = "Drupal\Core\Config\Entity\ConfigStorageController", * list = "Drupal\contact\CategoryListController", * form = { * "add" = "Drupal\contact\CategoryFormController" * } * }, * uri_callback = "contact_category_uri", * config_prefix = "contact.category", * entity_keys = { * "id" = "id", * "label" = "label", * "uuid" = "uuid" * } * ) */ class Category extends ConfigEntityBase implements ContactInterface { /** * The category ID.
/** Saturday 16 * November The 13category ID. use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; /** * Defines the contact category entity. * * @EntityType( * id = "contact_category", * label = @Translation("Category"), * module = "contact", * controllers = { * controller_class = "Drupal\Core\Config\Entity\ConfigStorageController", * list = "Drupal\contact\CategoryListController", * form = { * "add" = "Drupal\contact\CategoryFormController" * "edit" = "Drupal\contact\CategoryFormController" * } * } * uri_callback = "contact_category_uri", * config_prefix = "contact.category", * entity_keys = { * "id" = "id", * "label" = "label", * "uuid" = "uuid" * } * ) */ class Category extends ConfigEntityBase implements ContactInterface {
*/ class Category extends ConfigEntityBase { } /** * The category ID. */ public $id; /** * The category UUID. */ public $uuid; /** * The category label. */ public $label; /** * List of recipient e-mail addresses. */ public $recipients = array(); /** * An auto-reply message to send to the message author. */ public $reply = ''; /** * Weight of this category (used for sorting). */ public $weight = 0;
contact.category.feedback.yml id: feedback uuid: de77e4f3-f94b-41a5-ad05-5c32fa08444f label: 'Website feedback' recipients: - '' reply: '' weight: '0' langcode: und
(config) entity API entity_load entity_save $object->any_method()
Deployment
Development environment 1 Database Active Directory
Development environment 1 Database Active Directory
Development environment 1 Database 2 Active Directory
Production environment admin/config/development/sync Database 3 Staging Directory Active Directory
Production environment 4 admin/config/development/sync Database 3 Staging Directory Active Directory
Demo time Full import / export Single import / export No partial imports supported on CLI!
Drush integration
Advanced workflows https://drupal.org/sandbox/dereine/2057465 use git pre-commit hooks with defined config directories in settings.php
Don t hack core
Don t hack active config
State API Only useful for this environment? Use state(). Drupal::state()->set('update.last_check', $now); //... $last_check = Drupal::state()->get('update.last_check')?: 0;
Configuration schema
system.maintenance.yml enabled: '0' message: '@site is currently under maintenance. We should be back shortly. Thank you for your patience.'
system.schema.yml system.maintenance: type: mapping label: 'Maintenance mode' mapping: "enabled": type: boolean label: "Put site into maintenance mode" "message": type: text label: "Message to display when in maintenance mode"
Basic scalar types from typed data boolean: label: 'Boolean' class: '\Drupal\Core\TypedData\Type\Boolean' email: label: 'Email' class: '\Drupal\Core\TypedData\Type\Email' integer: label: 'Integer' class: '\Drupal\Core\TypedData\Type\Integer' string: label: 'String' class: '\Drupal\Core\TypedData\Type\String' uri: label: 'Uri' class: '\Drupal\Core\TypedData\Type\Uri'
Basic data types for configuration undefined: label: 'Undefined' class: '\Drupal\Core\Config\Schema\Property' mapping: label: Mapping class: '\Drupal\Core\Config\Schema\Mapping' sequence: label: Sequence class: '\Drupal\Core\Config\Schema\Sequence'
Simple extended data types # Human readable string that must be plain text and editable with a text field. label: type: string label: 'Label' translatable: true # Internal Drupal path path: type: string label: 'Path' # Human readable string that can contain multiple lines of text or HTML. text: type: string label: 'Text' translatable: true
Complex extended data type # Mail text with subject and body parts. mail: type: mapping label: "Mail" mapping: "subject": type: text label: "Subject" "body": type: text label: "Body"
Config inspector module
Context system, Events & Overrides
Global overrides global $conf; $conf['system.maintenance']['message'] = 'Sorry, our site is down now.';
Global overrides class ConfigGlobalOverrideSubscriber implements EventSubscriberInterface { static function getsubscribedevents() { $events['config.init'][] = array('configinit', 30); return $events; } public function configinit(configevent $event) { global $conf; } } $config = $event->getconfig(); if (isset($conf[$config->getname()])) { $config->setoverride($conf[$config->getname()]); }
Break out of contexts // Enter the override-free context, so we can ensure no overrides are applied. config_context_enter('config.context.free'); // Get system site maintenance message text from the original config. $message = config('system.maintenance')->get('message'); // Leave the override-free context. config_context_leave();
Get into contexts // Enter a user specific context. $context = config_context_enter("drupal\\user\\userconfigcontext"); // Set the account to use on the context. $context->setaccount($account); $mail_config = Drupal::config('user.mail'); // Do stuff... config_context_leave();
Language overrides block.block.bartik.login.yml id: bartik.login uuid: 7012ebfd-7083-47ef-b... weight: '0' status: '1' langcode: en region: sidebar_first plugin: user_login_block settings: label: 'User login' module: user label_display: visible cache: '-1'... locale.hu.block.block.bartik.login.yml settings: label: 'Belépés' locale.nl.block.block.bartik.login.yml settings: label: 'Inloggen'
recap and advice key names/properties should have meaning Use config entities instead of tables Use getters/setters/methods on entities Include config schema (translation!) Upgrade functions available in update.inc
Please try it out! #drupal-cmi - Dedicated IRC channel docs - https://drupal.org/node/1667894 help along - http://drupal.org/core-mentoring-hours
http://groups.drupal.org/cmi - Discussion http://v.gd/cmi_issues - Issues http://groups.drupal.org/core - Core announcements #drupal-cmi - Dedicated IRC channel http://drupal.org/core-mentoring-hours
Questions?
Thanks @heyrocker @webchick @moshe_weitzman @GaborHojtsy @alexpott