Pre-requisites
Configuration is exportable, user-editable state stored as entities
As we previously compared state with configuration, let's compare configuration with state:
- Semi-permanent
- Configuration is Drupal's fundamental instructions from the site administrators and developers for how to run itself. Unlike state, which Drupal itself is broadly at liberty to change, configuration is only likely to ever change at a human's behest. Examples include the site title, or whether the site is in maintenance mode: only a human can determine these settings; it's a rare use case where Drupal would change its own title, or put itself into maintenance mode.
- Complex life cycle
- If configuration is going to hang around for longer, it needs better curation of its life cycle. Configuration can also depend on certain modules, or even be made nonsensical or dangerous (and thus a candidate for uninstallation) when some module is uninstalled. Configuration has its own internal complexity above and beyond just the complexity of whatever data values are being stored.
Configuration uses config objects and config entities to support its persistence and complex behaviours, in contrast to state's simple, cached key/value store. The use of entities also permits configuration to be exported and re-imported, implying that an assembled entity's-worth of configuration (unlike state) can be of use to other sites, not just the one it was configured on.
Config objects versus config entities?
We've introduced a subtle divergence in terminology, which is only important, depending on what you're doing with configuration.
A key aspect of content entities is their countability: if your website has N news items, you can easily envisage it having N+1; moreover, if the most recently created node had ID X, and no other nodes had been created since, then the next node will have ID X+1. If we imagine referring to nodes via their long, unique UUID string instead, we could instead say that they weren't so much countable as sequenceable: put all the nodes in some order, and you can always add a new node at the end of the list.
With sequenceability in mind, here's how the two types of configuration differ:
- Config entities
- These are sequenceable, like content entities. They don't necessarily have a numeric ID, but they do have a unique name and can have a UUID for import and export. Config entities of a particular type must have names that conform to a particular pattern, defined in a
.schema.yml
file provided by the module responsible. Examples of config entities: a blogpost view; an input format; a field configuration. - Config objects
- These are not sequenceable, and (while they can also have a UUID for import and export) are identified solely by their unique name: in this respect they are a kind of singleton. As such, there's no pattern for names that can be extended, although names should begin with the name of the module responsible for the configuration, so they can be uninstalled with the module. Config object structure can also defined in the schema YAML file of the module responsible. Example of config objects: views settings; contact form settings.
Regardless of its type, configuration items all use the same class(es)! Drupal\Core\Config\Config
, or more usually an ImmutableConfig
to avoid altering configuration by mistake. So in much of your day-to-day use of the configuration API, you'll hardly notice the difference.
If your own configuration's type matters to your own code or to possible future extension by others, it must be defined in a .schema.yml
. This file is also used to describe the internal contents of the configuration: for example, the data types of different keys and sub-keys. These provide type hints for the typed data API, which we will discuss later. If you like, you can have a look at the entry for core.date_format.*
in the core.data_types.schema.yml
file, and compare it to the example YAML file below.
If you're only dealing with other modules' configuration, then the key difference you must remember is that you'll usually add config entities, not config objects. You can imagine adding config entities that other modules would be responsible for: e.g. installing a new view or content type (indeed, we did this previously when discussing entities and fields.) However, there is little point in your own module adding config objects for other modules: because they have no naming pattern, and no sequenceability, they have no discoverability; and the new configuration would therefore not be detectable by the other module.
Steps in the configuration lifecycle
1. Create or install configuration
New configuration should always have an "owner" module, to make clear just what it's configuring. This ownership is reflected in naming conventions:
- Configuration created solely for your custom module's own use should have a name beginning with your module's own e.g.
d8api.settings
. You may (and probably should) provide further information about the configuration's structure and type (object vs entity) in e.g.d8api.schema.yml
, but we don't cover that here. - Configuration created to integrate with another pre-existing module should have a name matching a pattern registered in that module's schema file e.g.
views.view.customview
references theviews.view.*
pattern found inviews.schema.yml
. This also implies the configuration must be a config entity, not a config object. - Any other naming convention should be avoided.
At the time of writing, it is possible to create configuration with arbitrary names, as long as no dependencies are declared within the configuration's data structure (see below.) Don't do this; at the very least, it will not be uninstalled along with your own (or anyone else's) module, which is bad practice. At worst, it will be an ongoing maintenance headache, as future developers (including your future self) try to work out just how the configuration is meant to work.
Install configuration with YAML files
Any YAML files found in your module's config/install/
folder will be activated in Drupal at the point of module installation, and this is the preferred method of creating new configuration. The file's name (minus the suffix) will become the configuration name: so d8api.settings
should live in config/install/d8api.settings.yml
.
If your module is already installed, but you want to refresh the configuration, or install some new configuration file you forgot to originally include, you can use the config_devel module to provide a new Drush command for this purpose:
drush cdi1 sites/all/modules/d8api/config/install/d8api.settings.yml
Create configuration using the factory service
You can also create arbitrary new configuration using a Drupal\Core\Config\ConfigFactory
object: just use ->getEditable($newConfigName)
to retrieve a new mutable object, then ->save()
this object with the name provided.
In classes, the factory should always be provided using dependency injection of the config.factory
service, as discussed previously and demonstrated in the example class below; at the command line, you could use \Drupal::service('config.factory')
directly.
Remember to still respect naming conventions for any new configuration names.
2. Read and update configuration
Configuration can be read and updated through the same config.factory
service discussed previously. Note that the simple ConfigFactory::get()
method returns an immutable, or read-only, configuration item, using Drupal\Core\Config\ImmutableConfig
: as you can see in the example immediately above, getEditable()
returns the editable Drupal\Core\Config\Config
instead.
Values within the configuration item can be set using a dotted syntax, so that Config::set('key1.key2', $value)
creates an array containing key2 => $value
, then assigns it to key1
.
3. Import and export configuration
A key aspect of configuration is Configuration Management. The configuration of any website is always exportable, and this export can be restored at a later date to reconfigure the site.
There are many ways of exporting configuration; Drush provides a config-export
command, to export into a directory defined in your site's settings.php
, and the aforementioned config_devel has a command to re-export just the configuration for a particular module.
4. Delete or uninstall configuration
In code, you can explicitly delete configuration items retrieved with the config.factory
service using $config->delete()
. You can also just unset specific sub-arrays within the configuration, leaving the rest intact, using $config->clear($key)->save()
. For sub-subarrays, $key
can follow the same dotted syntax mentioned above e.g. 'key1.key2'
.
However, you probably never need to delete configuration items explicitly, if you've followed the naming conventions discussed above. When a module is uninstalled, all configuration it "owns" will be uninstalled too. In addition, if you need to uninstall another module's config entity, when some other module is uninstalled—for example, a view depending on a field formatter in some other module—then you can include in the YAML file the dependencies: enforced: - d8api
syntax, demonstrated in the example class below.
Examples of working with the configuration life cycle
A. Working with configuration using PHP code
The following PHP class provides examples of how configuration works. You should save it to src/ConfigExample.php
in your d8api
module:
<?php namespace Drupal\d8api; use Drupal\Core\Config\ConfigFactoryInterface; /** * Run some configuration API tests. */ class ConfigExample { /** * @var ConfigFactoryInterface */ protected $configFactory; /** * Implements __construct(). * * @param ConfigInterface $configFactory * Config factory service, injected into the new object. */ public function __construct(ConfigFactoryInterface $configFactory) { $this->configFactory = $configFactory; } /** * Read a configuration value; override it; change it back. * * Config names are stored in the name column of table config. */ public function temporarilyOverride() { $site_info = $this->configFactory->get('system.site'); print "Raw data for system.site:\n"; var_dump($site_info->getRawData()); $old_name = $site_info->get('name'); $editableConfig = $this->configFactory->getEditable('system.site'); $editableConfig->set('name', 'Example for config'); $editableConfig->save(); $site_info = $this->configFactory->get('system.site'); print "Site name has been changed to: " . $site_info->get('name') . "\n"; $editableConfig->set('name', $old_name); $editableConfig->save(); print "Site name has been changed back to $old_name.\n"; } /** * Create a new configuration value; save; clear part of it; delete. */ public function createSaveClearDelete() { $new_config = $this->configFactory->getEditable('d8api.config'); print "New config is empty? " . (empty($new_config->getRawData()) ? 'Y' : 'N') . "\n"; // Save new configuration. $new_config->set('foo', 1)->set('baz.quux', ['fred' => 'barney']); $new_config->save(); // Reload, then clear a value. $reloaded_config = $this->configFactory->getEditable('d8api.config'); $reloaded_config->clear('foo'); // Look at the entire raw data first. print "Config with 'foo' key cleared out: " . var_export($reloaded_config->getRawData(), TRUE) . "\n"; // You can even interrogate partial arrays of content e.g. just "baz". print "Config 'baz' data: " . var_export($reloaded_config->get('baz'), TRUE) . "\n"; // Delete entirely $reloaded_config->delete(); $new_config = $this->configFactory->getEditable('d8api.config'); print "Config has been deleted? " . (empty($new_config->getRawData()) ? 'Y' : 'N') . "\n"; } }
This class consists of three methods:
__construct()
- Provides compatibility with dependency injection, to obtain the config factory service.
temporarilyOverride()
- Trivially demonstrates
$factory->get()
and$factory->getEditable()
to get configuration items, and then each item's->set()
method to modify a value within the configuration. createSaveClearDelete()
- Shows the full life cycle of both a configuration item and also deleting certain data within it using
->clear()
.
We'll show below how to use this class, and what results to expect.
B. Working with configuration using YAML files
Let's create three new YAML files in the config/install/
subfolder. These will in turn install configuration with three names corresponding to the naming convention notes above:
- For this module:
d8api.some.filename.yml
- For the core date formatting:
core.date_format.d8api_date.yml
- Incorrectly namespaced:
notamodule.d8api.yml
Depending on what tutorials you've completed previously, you'll probably have other files in config/install/
already.
d8api.some.filename.yml
This will trivially create some configuration, owned by the module:
some_config: another_key: [1, 2, 3]
It will uninstall along with d8api
module.
views.view.d8api_dependency.yml
This will create a date format, owned by core. Because core defines a core.data_types.schema.yml
for these config entities and others, we do need to provide valid data, not just some test keys and values:
langcode: en status: true dependencies: enforced: module: - d8api id: d8api_date label: 'Example date format' locked: false pattern: 'm/d/Y - H:i'
We also want to demonstrate that this configuration can be uninstalled along with the d8api
module. Hence the YAML includes dependencies: enforced: module: - d8api
: that means, although this configuration "belongs" to core, it will also be uninstalled when d8api
is uninstalled.
Dependency enforcement only makes sense for config entities, because config objects are installed and uninstalled only with the module that owns them i.e. shares their namespace.
notamodule.d8api.yml
Finally, let's do what we shouldn't do. Here's a module with a broken namespace, as the filename above shows:
some_config: another_key: [1, 2, 3]
Once again, the data provided by the configuration is for illustrative purposes only. Also, because that data doesn't specify any dependencies, installation of it does not (at the time of writing) trigger a dependency check on the presence of the notamodule
module. It should be silently installed; as we'll see below, though, it remains even when the module
(By the way, the filename doesn't need to include the snippet .d8api.
: we include that so we can keep track of it and delete it in the examples below.)
What you should see
As previously, you should have Drush available, and we don't cover that here. You should also uninstall the d8api
module, so that the YAML examples included will work correctly.
Begin by exporting all of your site's current configuration into a folder:
drush config-export
You should see the response message:
[success] Configuration successfully exported to some/folder.
Now enable the d8api
module, and re-export the configuration:
drush en -y d8api
drush config-export
The module will be enabled first:
The following extensions will be enabled: d8api Do you really want to continue? (y/n): y [ok] d8api was enabled successfully. d8api defines the following permissions: add contact entity, administer contact entity, delete contact entity, edit contact entity, view contact entity
And then config-export
will recognize that new configuration exists and ask for confirmation:
Differences of the active config to the export directory: Collection Config Operation d8api.contact_type.person create d8api.some.filename create field.storage.contact.field_jobtitle create field.field.contact.person.field_jobtitle create core.entity_view_display.contact.person.default create core.entity_form_display.contact.person.default create notamodule.d8api create core.extension update The .yml files in your export directory (/tmp/sync) will be deleted and replaced with the active config. (y/n):
The only update
you should see will be to the core.extension
configuration: the list of enabled modules now includes d8api
. You can confirm or deny at the "y/n" prompt above: it doesn't matter.
We'll come back to the installed configuration later. Right now, let's look at the code examples. Firstly, run the temporarilyOverride()
method as follows:
drush php-eval '(new \Drupal\d8api\ConfigExample(
\Drupal::service("config.factory")
))->temporarilyOverride();'
This will produce the following output:
Raw data for system.site: array(10) { ["uuid"]=> string(36) "75cc2103-601c-4eeb-aaeb-ac350d5cb945" ["name"]=> string(8) "Tutorial" # ... More details here.... } Site name has been changed to: Example for config Site name has been changed back to Tutorial.
As you can see, system.site
is a config object which happens to have a UUID. The output further shows that, when the config object is saved and then reloaded, its title has been changed; and then changed back, so this example doesn't permanently rename your site!
Secondly, we run the createSaveClearDelete()
example as follows:
drush php-eval '(new \Drupal\d8api\ConfigExample(
\Drupal::service("config.factory")
))->createSaveClearDelete();'
New config is empty? Y Config with 'foo' key cleared out: array ( 'baz' => array ( 'quux' => array ( 'fred' => 'barney', ), ), ) Config 'baz' data: array ( 'quux' => array ( 'fred' => 'barney', ), ) Config has been deleted? Y
The output from this shows respectively that: new, as-yet-unsaved config objects have an empty array as their "raw data"; the ->clear()
method only unsets specific subarrays within the configuration; we can retrieve sub-arrays of configuration using keys; and that the delete method entirely removes the config object from the site.
Finally, let's look at what our configuration YAML files have created. Firstly, check the config
table:
drush sqlq "SELECT name FROM config WHERE name LIKE '%d8api%';"
This should produce output something like:
core.date_format.d8api_date d8api.contact_type.person d8api.some.filename notamodule.d8api
The person
contact type is discussed in our previous tutorial on fields API; you can see the other three YAML files have resulted in three items of configuration.
Now let's uninstall our d8api
module, and re-examine the config
table:
drush pm-uninstall -y d8api drush sqlq "SELECT name FROM config WHERE name LIKE '%d8api%';"
The result of uninstallation should be that all but one file has been removed:
The following extensions will be uninstalled: d8api Do you really want to continue? (y/n): y [ok] d8api was successfully uninstalled. notamodule.d8api
You can see that the badly-namespaced configuration has been "orphaned", although the others have been removed: the contact type, because it depends on a bundle defined in the module; the d8api.some.filename
, because its namespace shows it is owned by the module; and the date format, because its YAML contained an enforced dependency.
Unfortunately, this orphaned configuration will now prevent d8api
from reinstalling itself. Luckily, configuration items all live solely in the config
database table, so (just this once, to fix a broken site), run:
drush sqlq "DELETE FROM config WHERE name LIKE '%d8api%';"
You should then also remove the notamodule.d8api.yml
file from the module, before reinstalling.
If you can see all of this, congratulations! you have learned what configuration is, how it's stored, and how to manipulate it.