9230281f55d8eb7c71dea6458893f9ffbd0a3539
[yaffs-website] / web / core / lib / Drupal / Core / Config / Schema / SchemaCheckTrait.php
1 <?php
2
3 namespace Drupal\Core\Config\Schema;
4
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;
12
13 /**
14  * Provides a trait for checking configuration schema.
15  */
16 trait SchemaCheckTrait {
17
18   /**
19    * The config schema wrapper object for the configuration object under test.
20    *
21    * @var \Drupal\Core\Config\Schema\Element
22    */
23   protected $schema;
24
25   /**
26    * The configuration object name under test.
27    *
28    * @var string
29    */
30   protected $configName;
31
32   /**
33    * Checks the TypedConfigManager has a valid schema for the configuration.
34    *
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.
41    *
42    * @return array|bool
43    *   FALSE if no schema found. List of errors if any found. TRUE if fully
44    *   valid.
45    */
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';
53     }
54     $this->configName = $config_name;
55     if (!$typed_config->hasConfigSchema($config_name)) {
56       return FALSE;
57     }
58     $definition = $typed_config->getDefinition($config_name);
59     $data_definition = $typed_config->buildDataDefinition($definition, $config_data);
60     $this->schema = $typed_config->create($data_definition, $config_data);
61     $errors = [];
62     foreach ($config_data as $key => $value) {
63       $errors = array_merge($errors, $this->checkValue($key, $value));
64     }
65     if (empty($errors)) {
66       return TRUE;
67     }
68     return $errors;
69   }
70
71   /**
72    * Helper method to check data type.
73    *
74    * @param string $key
75    *   A string of configuration key.
76    * @param mixed $value
77    *   Value of given key.
78    *
79    * @return array
80    *   List of errors found while checking with the corresponding schema.
81    */
82   protected function checkValue($key, $value) {
83     $error_key = $this->configName . ':' . $key;
84     $element = $this->schema->get($key);
85     if ($element instanceof Undefined) {
86       return [$error_key => 'missing schema'];
87     }
88
89     // Do not check value if it is defined to be ignored.
90     if ($element && $element instanceof Ignore) {
91       return [];
92     }
93
94     if ($element && is_scalar($value) || $value === NULL) {
95       $success = FALSE;
96       $type = gettype($value);
97       if ($element instanceof PrimitiveInterface) {
98         $success =
99           ($type == 'integer' && $element instanceof IntegerInterface) ||
100           // Allow integer values in a float field.
101           (($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) ||
102           ($type == 'boolean' && $element instanceof BooleanInterface) ||
103           ($type == 'string' && $element instanceof StringInterface) ||
104           // Null values are allowed for all primitive types.
105           ($value === NULL);
106       }
107       // Array elements can also opt-in for allowing a NULL value.
108       elseif ($element instanceof ArrayElement && $element->isNullable() && $value === NULL) {
109         $success = TRUE;
110       }
111       $class = get_class($element);
112       if (!$success) {
113         return [$error_key => "variable type is $type but applied schema class is $class"];
114       }
115     }
116     else {
117       $errors = [];
118       if (!$element instanceof TraversableTypedDataInterface) {
119         $errors[$error_key] = 'non-scalar value but not defined as an array (such as mapping or sequence)';
120       }
121
122       // Go on processing so we can get errors on all levels. Any non-scalar
123       // value must be an array so cast to an array.
124       if (!is_array($value)) {
125         $value = (array) $value;
126       }
127       // Recurse into any nested keys.
128       foreach ($value as $nested_value_key => $nested_value) {
129         $errors = array_merge($errors, $this->checkValue($key . '.' . $nested_value_key, $nested_value));
130       }
131       return $errors;
132     }
133     // No errors found.
134     return [];
135   }
136
137 }