3 namespace Drupal\Core\Config\Development;
5 use Drupal\Component\Utility\Crypt;
6 use Drupal\Component\Utility\SafeMarkup;
7 use Drupal\Core\Config\ConfigCrudEvent;
8 use Drupal\Core\Config\ConfigEvents;
9 use Drupal\Core\Config\Schema\SchemaCheckTrait;
10 use Drupal\Core\Config\Schema\SchemaIncompleteException;
11 use Drupal\Core\Config\StorageInterface;
12 use Drupal\Core\Config\TypedConfigManagerInterface;
13 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16 * Listens to the config save event and validates schema.
18 * If tests have the $strictConfigSchema property set to TRUE this event
19 * listener will be added to the container and throw exceptions if configuration
22 * @see \Drupal\KernelTests\KernelTestBase::register()
23 * @see \Drupal\simpletest\WebTestBase::setUp()
24 * @see \Drupal\simpletest\KernelTestBase::containerBuild()
26 class ConfigSchemaChecker implements EventSubscriberInterface {
30 * The typed config manger.
32 * @var \Drupal\Core\Config\TypedConfigManagerInterface
34 protected $typedManager;
37 * An array of config checked already. Keyed by config name and a checksum.
41 protected $checked = [];
44 * An array of config object names that are excluded from schema checking.
48 protected $exclude = [];
51 * Constructs the ConfigSchemaChecker object.
53 * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_manager
54 * The typed config manager.
55 * @param string[] $exclude
56 * An array of config object names that are excluded from schema checking.
58 public function __construct(TypedConfigManagerInterface $typed_manager, array $exclude = []) {
59 $this->typedManager = $typed_manager;
60 $this->exclude = $exclude;
64 * Checks that configuration complies with its schema on config save.
66 * @param \Drupal\Core\Config\ConfigCrudEvent $event
67 * The configuration event.
69 * @throws \Drupal\Core\Config\Schema\SchemaIncompleteException
70 * Exception thrown when configuration does not match its schema.
72 public function onConfigSave(ConfigCrudEvent $event) {
73 // Only validate configuration if in the default collection. Other
74 // collections may have incomplete configuration (for example language
75 // overrides only). These are not valid in themselves.
76 $saved_config = $event->getConfig();
77 if ($saved_config->getStorage()->getCollectionName() != StorageInterface::DEFAULT_COLLECTION) {
81 $name = $saved_config->getName();
82 $data = $saved_config->get();
83 $checksum = Crypt::hashBase64(serialize($data));
84 if (!in_array($name, $this->exclude) && !isset($this->checked[$name . ':' . $checksum])) {
85 $this->checked[$name . ':' . $checksum] = TRUE;
86 $errors = $this->checkConfigSchema($this->typedManager, $name, $data);
87 if ($errors === FALSE) {
88 throw new SchemaIncompleteException("No schema for $name");
90 elseif (is_array($errors)) {
92 foreach ($errors as $key => $error) {
93 $text_errors[] = SafeMarkup::format('@key @error', ['@key' => $key, '@error' => $error]);
95 throw new SchemaIncompleteException("Schema errors for $name with the following errors: " . implode(', ', $text_errors));
103 public static function getSubscribedEvents() {
104 $events[ConfigEvents::SAVE][] = ['onConfigSave', 255];