It seems like the ConfigEntityInterface::isSyncing()
method only returns TRUE during a (single) config import (started from the UI) or during a full config synchronization.
However, becasue isSyncing() returns FALSE during the configuration import while installing a module, we can potentially create the same config twice, leading to unexpected behavior in hooks that act upon the creation.
A simple example
Imagine a module that creates a config entity foo.node_type_meta.NODE_TYPE.yml
whenever a node type is created. In the code, it's only created if $node_type->isSyncing()
returns FALSE, because we do not want to create it during a configuration sync.
- CORRECT: When I create a node type through the API or UI, isSyncing() is FALSE and the setting is created
- CORRECT: When I import node.type.bar.yml, isSyncing() is TRUE and the setting is not created. If I want the setting too, it needs to be part of my import.
- PROBLEM: When I install a module that has a
config/install/node.type.baz.yml
, isSyncing() is FALSE and the setting is created. See below why this is bad.
Where it breaks
Now suppose my module has both node.type.baz.yml
and foo.node_type_meta.baz.yml
. What happens is that the node type is installed first because the meta config should depend on it.
During the node type saving process, my meta config is already created with the default values only to be overwritten right after that with the config from the Yaml file. This leads to my meta config being saved twice.
The reason this is allowed, is because ModuleInstaller
calls $config_installer->checkConfigurationToInstall('module', $module);
at the start of the installation at which point the meta config entity did not exist yet. Later on, the config is saved using the trusted data flag ($entity->trustData()->save()
or $new_config->save(TRUE)
), meaning Drupal won't complain about it existing already.
Why it's hard to solve
We do not want to set isSyncing() to TRUE during module install because we would then require a module that provides a node type to know about all the modules that could possibly create config based on node type creation. After all, if they don't provide said additional configuration in config/install, said config would never be created because isSyncing() is TRUE.
Additionally, we can't easily inform $config_installer->checkConfigurationToInstall('module', $module);
that certain config will be created while other config entities are being saved. So we can't fix it there either.
Possible solution
Provide an easy way to check whether the config you want to create exists in Yaml but has not yet been imported. That is, if there isn't already an easy way to do so. Then document this clearly, perhaps in ConfigEntityInterface::isSyncing()
, so that module maintainers know they should use it to prevent their config from being saved twice.
It could look like this:
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
if (!$update && !$this->isSyncing()) {
$foo_id = $this->id() . '-foo';
// Only create a foo entity if it won't be imported.
if (!$some_service->willBeImported($foo_id)) {
Foo::create([
'id' => $foo_id,
'node_type' => $this->id(),
])->save();
}
}
}