3 namespace Drupal\Core\Config\Schema;
5 use Drupal\Core\Config\TypedConfigManagerInterface;
6 use Drupal\Core\TypedData\PrimitiveInterface;
7 use Drupal\Core\TypedData\TraversableTypedDataInterface;
8 use Drupal\Core\TypedData\Type\BooleanInterface;
9 use Drupal\Core\TypedData\Type\StringInterface;
10 use Drupal\Core\TypedData\Type\FloatInterface;
11 use Drupal\Core\TypedData\Type\IntegerInterface;
14 * Provides a trait for checking configuration schema.
16 trait SchemaCheckTrait {
19 * The config schema wrapper object for the configuration object under test.
21 * @var \Drupal\Core\Config\Schema\Element
26 * The configuration object name under test.
30 protected $configName;
33 * Checks the TypedConfigManager has a valid schema for the configuration.
35 * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
36 * The TypedConfigManager.
37 * @param string $config_name
38 * The configuration name.
39 * @param array $config_data
40 * The configuration data, assumed to be data for a top-level config object.
43 * FALSE if no schema found. List of errors if any found. TRUE if fully
46 public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data) {
47 // We'd like to verify that the top-level type is either config_base,
48 // config_entity, or a derivative. The only thing we can really test though
49 // is that the schema supports having langcode in it. So add 'langcode' to
50 // the data if it doesn't already exist.
51 if (!isset($config_data['langcode'])) {
52 $config_data['langcode'] = 'en';
54 $this->configName = $config_name;
55 if (!$typed_config->hasConfigSchema($config_name)) {
58 $this->schema = $typed_config->createFromNameAndData($config_name, $config_data);
60 foreach ($config_data as $key => $value) {
61 $errors = array_merge($errors, $this->checkValue($key, $value));
70 * Helper method to check data type.
73 * A string of configuration key.
78 * List of errors found while checking with the corresponding schema.
80 protected function checkValue($key, $value) {
81 $error_key = $this->configName . ':' . $key;
82 $element = $this->schema->get($key);
83 if ($element instanceof Undefined) {
84 return [$error_key => 'missing schema'];
87 // Do not check value if it is defined to be ignored.
88 if ($element && $element instanceof Ignore) {
92 if ($element && is_scalar($value) || $value === NULL) {
94 $type = gettype($value);
95 if ($element instanceof PrimitiveInterface) {
97 ($type == 'integer' && $element instanceof IntegerInterface) ||
98 // Allow integer values in a float field.
99 (($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) ||
100 ($type == 'boolean' && $element instanceof BooleanInterface) ||
101 ($type == 'string' && $element instanceof StringInterface) ||
102 // Null values are allowed for all primitive types.
105 // Array elements can also opt-in for allowing a NULL value.
106 elseif ($element instanceof ArrayElement && $element->isNullable() && $value === NULL) {
109 $class = get_class($element);
111 return [$error_key => "variable type is $type but applied schema class is $class"];
116 if (!$element instanceof TraversableTypedDataInterface) {
117 $errors[$error_key] = 'non-scalar value but not defined as an array (such as mapping or sequence)';
120 // Go on processing so we can get errors on all levels. Any non-scalar
121 // value must be an array so cast to an array.
122 if (!is_array($value)) {
123 $value = (array) $value;
125 // Recurse into any nested keys.
126 foreach ($value as $nested_value_key => $nested_value) {
127 $errors = array_merge($errors, $this->checkValue($key . '.' . $nested_value_key, $nested_value));