The configuration system can already store language specific override data. If locale module is not enabled, this data is not accessible (not a problem). If locale module is enabled, only the data localized to the page's interface language is accessible (which is the problem). No other language version is accessible on the page. This is a regression, because we cannot even send user emails in various languages on a page that is a basic feature of user and contact modules in prior versions.
The language overrides are implemented with a general system of configuration listeners and overrides that - though only used for localization and global $conf overrides in core -, allow modules to override configuration values depending on different request conditions (in contrib). Possible examples are organic group specific values or domain specific values (groups and domain modules in contrib). The core listener/override system needs more information about the config override values needed so it can pull in specific data as appropriate. These two use cases apply to core:
- For localizing configuration to different languages during the same page request we need to add some contextual value (language) to the configuration API or values that can be used to derive language (such as a user account).
- For administering configuration objects we need to get the default configuration values without any overrides so the values edited in base settings forms are consistent and don't depend on the current interface language.
(The question of how translation of configuration would be possible is evident. That has various usability / workflow / permissions considerations that will most likely not be possible to resolve in core in Drupal 8. A configuration translation user interface will need to live in contrib.)
- Create a configuration context system (objects based on \Drupal\Core\Config\ContextInterface). These objects contain all the parameters we need to load/save configuration objects.
- Introduce a context factory (\Drupal\Core\Config\Context\ConfigContextFactory) that can be used to create context object instances.
- Set a default configuration context for the page request, and use different configuration context objects for different purposes (administration, send emails to users, etc).
- Extend the configuration API to make the config factory context aware, so config() calls would use the context set prior, until the context is left.
API changes, API usage
We have a number of configuration context objects which will determine how the configuration is loaded and overridden for each different purpose for which we are using it.
- GlobalConfigContext, registered as (config.context with the DIC) which is used as the default configuration context for the page request. It takes values from global $conf as configuration overrides (Then used by ConfigOverrideSubscriber when loading configuration objects).
- ConfigContext (without futher arguments, registered with the DIC as config.context.free), used for administration (settings forms), configuration install, import and export. The configuration listeners should 'keep hands off' configuration objects loaded for this context unless they need to act upon configuration updates. I.e. these configuration objects won't be overridden with global $conf nor with translations.
- UserConfigContext, which is specific to user account based contexts (registered with the DIC as config.context.user).
An example of a user context is creation of user accounts. We need to send an email to the user using their language preference instead of the system default or the page request language. Since mail text is stored in the configuration, we use the context system when retrieving the mail text for that user. A user context is created and used to retrieve the mail text from the configuration system. The context is set on the config factory.
The new config_context_enter() and config_context_leave() API functions were added in system.module to enter and leave contexts. These are simplified versions of invocations of certain methods on the ContextFactory. For example this code is used in config import:
// Use the override free context for config importing so that any overrides do // not change the data on import. config_context_enter('config.context.free'); // ... actual import // Exit the override free context. config_context_leave();
A more complex example with entering a user context for user specific values:
// Enter a user specific context. $user_config_context = config_context_enter("Drupal\\user\\UserConfigContext"); // Set the account to use on the context. $user_config_context->setAccount($account); // Now the configuration retrieved (and any subsequent config() calls in // API functions) will work with the context on the top of the context // stack (that we most recently entered). $mail_config = config('user.mail'); // Use token_replace(), etc. here on the mail configuration to replace // with values proper for the context we entered (eg. the site name or // slogan properly translated to the language we use). // Reset config context to the prior value on the stack before leaving // this operation. This ensures any wrapping code will return to the // context that was set prior and our user specific context will not // persist. config_context_leave();
The locale module's LocaleConfigSubscriber is intelligent to recognize a user account's presence in the context and it sets the language according to the value derived from the user's preference. Other use cases may be a 'groups' module or a 'domain' module changing some configuration data depending on the user.
The point here is that a higher level context is provided and it is the responsibility of the underlying context system with the enabled modules to figure out configuration data for this context. The override/listener system in the config system is generic enough to allow for all kinds of overrides, so the context system is designed to be generic enough and not limit it to one or two specific types of contexts.
New classes introduced, classes changed
Example of where this should be used (once converted to CMI):
PASSED: [[SimpleTest]]: [MySQL] 52,556 pass(es). View
PASSED: [[SimpleTest]]: [MySQL] 52,551 pass(es). View