ConfigSchemaChecker.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. <?php
  2. namespace Drupal\Core\Config\Development;
  3. use Drupal\Component\Utility\Crypt;
  4. use Drupal\Component\Render\FormattableMarkup;
  5. use Drupal\Core\Config\ConfigCrudEvent;
  6. use Drupal\Core\Config\ConfigEvents;
  7. use Drupal\Core\Config\Schema\SchemaCheckTrait;
  8. use Drupal\Core\Config\Schema\SchemaIncompleteException;
  9. use Drupal\Core\Config\StorageInterface;
  10. use Drupal\Core\Config\TypedConfigManagerInterface;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. /**
  13. * Listens to the config save event and validates schema.
  14. *
  15. * If tests have the $strictConfigSchema property set to TRUE this event
  16. * listener will be added to the container and throw exceptions if configuration
  17. * is invalid.
  18. *
  19. * @see \Drupal\KernelTests\KernelTestBase::register()
  20. * @see \Drupal\simpletest\WebTestBase::setUp()
  21. * @see \Drupal\simpletest\KernelTestBase::containerBuild()
  22. */
  23. class ConfigSchemaChecker implements EventSubscriberInterface {
  24. use SchemaCheckTrait;
  25. /**
  26. * The typed config manger.
  27. *
  28. * @var \Drupal\Core\Config\TypedConfigManagerInterface
  29. */
  30. protected $typedManager;
  31. /**
  32. * An array of config checked already. Keyed by config name and a checksum.
  33. *
  34. * @var array
  35. */
  36. protected $checked = [];
  37. /**
  38. * An array of config object names that are excluded from schema checking.
  39. *
  40. * @var string[]
  41. */
  42. protected $exclude = [];
  43. /**
  44. * Constructs the ConfigSchemaChecker object.
  45. *
  46. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_manager
  47. * The typed config manager.
  48. * @param string[] $exclude
  49. * An array of config object names that are excluded from schema checking.
  50. */
  51. public function __construct(TypedConfigManagerInterface $typed_manager, array $exclude = []) {
  52. $this->typedManager = $typed_manager;
  53. $this->exclude = $exclude;
  54. }
  55. /**
  56. * Checks that configuration complies with its schema on config save.
  57. *
  58. * @param \Drupal\Core\Config\ConfigCrudEvent $event
  59. * The configuration event.
  60. *
  61. * @throws \Drupal\Core\Config\Schema\SchemaIncompleteException
  62. * Exception thrown when configuration does not match its schema.
  63. */
  64. public function onConfigSave(ConfigCrudEvent $event) {
  65. // Only validate configuration if in the default collection. Other
  66. // collections may have incomplete configuration (for example language
  67. // overrides only). These are not valid in themselves.
  68. $saved_config = $event->getConfig();
  69. if ($saved_config->getStorage()->getCollectionName() != StorageInterface::DEFAULT_COLLECTION) {
  70. return;
  71. }
  72. $name = $saved_config->getName();
  73. $data = $saved_config->get();
  74. $checksum = Crypt::hashBase64(serialize($data));
  75. if (!in_array($name, $this->exclude) && !isset($this->checked[$name . ':' . $checksum])) {
  76. $this->checked[$name . ':' . $checksum] = TRUE;
  77. $errors = $this->checkConfigSchema($this->typedManager, $name, $data);
  78. if ($errors === FALSE) {
  79. throw new SchemaIncompleteException("No schema for $name");
  80. }
  81. elseif (is_array($errors)) {
  82. $text_errors = [];
  83. foreach ($errors as $key => $error) {
  84. $text_errors[] = new FormattableMarkup('@key @error', ['@key' => $key, '@error' => $error]);
  85. }
  86. throw new SchemaIncompleteException("Schema errors for $name with the following errors: " . implode(', ', $text_errors));
  87. }
  88. }
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. public static function getSubscribedEvents() {
  94. $events[ConfigEvents::SAVE][] = ['onConfigSave', 255];
  95. return $events;
  96. }
  97. }