SchemaCheckTrait.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. <?php
  2. namespace Drupal\Core\Config\Schema;
  3. use Drupal\Core\Config\TypedConfigManagerInterface;
  4. use Drupal\Core\TypedData\PrimitiveInterface;
  5. use Drupal\Core\TypedData\TraversableTypedDataInterface;
  6. use Drupal\Core\TypedData\Type\BooleanInterface;
  7. use Drupal\Core\TypedData\Type\StringInterface;
  8. use Drupal\Core\TypedData\Type\FloatInterface;
  9. use Drupal\Core\TypedData\Type\IntegerInterface;
  10. /**
  11. * Provides a trait for checking configuration schema.
  12. */
  13. trait SchemaCheckTrait {
  14. /**
  15. * The config schema wrapper object for the configuration object under test.
  16. *
  17. * @var \Drupal\Core\Config\Schema\Element
  18. */
  19. protected $schema;
  20. /**
  21. * The configuration object name under test.
  22. *
  23. * @var string
  24. */
  25. protected $configName;
  26. /**
  27. * Checks the TypedConfigManager has a valid schema for the configuration.
  28. *
  29. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
  30. * The TypedConfigManager.
  31. * @param string $config_name
  32. * The configuration name.
  33. * @param array $config_data
  34. * The configuration data, assumed to be data for a top-level config object.
  35. *
  36. * @return array|bool
  37. * FALSE if no schema found. List of errors if any found. TRUE if fully
  38. * valid.
  39. */
  40. public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data) {
  41. // We'd like to verify that the top-level type is either config_base,
  42. // config_entity, or a derivative. The only thing we can really test though
  43. // is that the schema supports having langcode in it. So add 'langcode' to
  44. // the data if it doesn't already exist.
  45. if (!isset($config_data['langcode'])) {
  46. $config_data['langcode'] = 'en';
  47. }
  48. $this->configName = $config_name;
  49. if (!$typed_config->hasConfigSchema($config_name)) {
  50. return FALSE;
  51. }
  52. $this->schema = $typed_config->createFromNameAndData($config_name, $config_data);
  53. $errors = [];
  54. foreach ($config_data as $key => $value) {
  55. $errors = array_merge($errors, $this->checkValue($key, $value));
  56. }
  57. if (empty($errors)) {
  58. return TRUE;
  59. }
  60. return $errors;
  61. }
  62. /**
  63. * Helper method to check data type.
  64. *
  65. * @param string $key
  66. * A string of configuration key.
  67. * @param mixed $value
  68. * Value of given key.
  69. *
  70. * @return array
  71. * List of errors found while checking with the corresponding schema.
  72. */
  73. protected function checkValue($key, $value) {
  74. $error_key = $this->configName . ':' . $key;
  75. $element = $this->schema->get($key);
  76. if ($element instanceof Undefined) {
  77. return [$error_key => 'missing schema'];
  78. }
  79. // Do not check value if it is defined to be ignored.
  80. if ($element && $element instanceof Ignore) {
  81. return [];
  82. }
  83. if ($element && is_scalar($value) || $value === NULL) {
  84. $success = FALSE;
  85. $type = gettype($value);
  86. if ($element instanceof PrimitiveInterface) {
  87. $success =
  88. ($type == 'integer' && $element instanceof IntegerInterface) ||
  89. // Allow integer values in a float field.
  90. (($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) ||
  91. ($type == 'boolean' && $element instanceof BooleanInterface) ||
  92. ($type == 'string' && $element instanceof StringInterface) ||
  93. // Null values are allowed for all primitive types.
  94. ($value === NULL);
  95. }
  96. // Array elements can also opt-in for allowing a NULL value.
  97. elseif ($element instanceof ArrayElement && $element->isNullable() && $value === NULL) {
  98. $success = TRUE;
  99. }
  100. $class = get_class($element);
  101. if (!$success) {
  102. return [$error_key => "variable type is $type but applied schema class is $class"];
  103. }
  104. }
  105. else {
  106. $errors = [];
  107. if (!$element instanceof TraversableTypedDataInterface) {
  108. $errors[$error_key] = 'non-scalar value but not defined as an array (such as mapping or sequence)';
  109. }
  110. // Go on processing so we can get errors on all levels. Any non-scalar
  111. // value must be an array so cast to an array.
  112. if (!is_array($value)) {
  113. $value = (array) $value;
  114. }
  115. // Recurse into any nested keys.
  116. foreach ($value as $nested_value_key => $nested_value) {
  117. $errors = array_merge($errors, $this->checkValue($key . '.' . $nested_value_key, $nested_value));
  118. }
  119. return $errors;
  120. }
  121. // No errors found.
  122. return [];
  123. }
  124. }