bc81ab7cc1709367e514712fecd65aab8daf485d
[yaffs-website] / web / core / lib / Drupal / Core / Config / StorableConfigBase.php
1 <?php
2
3 namespace Drupal\Core\Config;
4
5 use Drupal\Core\Config\Schema\Ignore;
6 use Drupal\Core\Config\Schema\Sequence;
7 use Drupal\Core\Config\Schema\SequenceDataDefinition;
8 use Drupal\Core\TypedData\PrimitiveInterface;
9 use Drupal\Core\TypedData\Type\FloatInterface;
10 use Drupal\Core\TypedData\Type\IntegerInterface;
11 use Drupal\Core\Config\Schema\Undefined;
12
13 /**
14  * Provides a base class for configuration objects with storage support.
15  *
16  * Encapsulates all capabilities needed for configuration handling for a
17  * specific configuration object, including storage and data type casting.
18  *
19  * The default implementation in \Drupal\Core\Config\Config adds support for
20  * runtime overrides. Extend from StorableConfigBase directly to manage
21  * configuration with a storage backend that does not support overrides.
22  *
23  * @see \Drupal\Core\Config\Config
24  */
25 abstract class StorableConfigBase extends ConfigBase {
26
27   /**
28    * The storage used to load and save this configuration object.
29    *
30    * @var \Drupal\Core\Config\StorageInterface
31    */
32   protected $storage;
33
34   /**
35    * The config schema wrapper object for this configuration object.
36    *
37    * @var \Drupal\Core\Config\Schema\Element
38    */
39   protected $schemaWrapper;
40
41   /**
42    * The typed config manager.
43    *
44    * @var \Drupal\Core\Config\TypedConfigManagerInterface
45    */
46   protected $typedConfigManager;
47
48   /**
49    * Whether the configuration object is new or has been saved to the storage.
50    *
51    * @var bool
52    */
53   protected $isNew = TRUE;
54
55   /**
56    * The data of the configuration object.
57    *
58    * @var array
59    */
60   protected $originalData = [];
61
62   /**
63    * Saves the configuration object.
64    *
65    * Must invalidate the cache tags associated with the configuration object.
66    *
67    * @param bool $has_trusted_data
68    *   Set to TRUE if the configuration data has already been checked to ensure
69    *   it conforms to schema. Generally this is only used during module and
70    *   theme installation.
71    *
72    * @return $this
73    *
74    * @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
75    */
76   abstract public function save($has_trusted_data = FALSE);
77
78   /**
79    * Deletes the configuration object.
80    *
81    * Must invalidate the cache tags associated with the configuration object.
82    *
83    * @return $this
84    */
85   abstract public function delete();
86
87   /**
88    * Initializes a configuration object with pre-loaded data.
89    *
90    * @param array $data
91    *   Array of loaded data for this configuration object.
92    *
93    * @return $this
94    *   The configuration object.
95    */
96   public function initWithData(array $data) {
97     $this->isNew = FALSE;
98     $this->data = $data;
99     $this->originalData = $this->data;
100     return $this;
101   }
102
103   /**
104    * Returns whether this configuration object is new.
105    *
106    * @return bool
107    *   TRUE if this configuration object does not exist in storage.
108    */
109   public function isNew() {
110     return $this->isNew;
111   }
112
113   /**
114    * Retrieves the storage used to load and save this configuration object.
115    *
116    * @return \Drupal\Core\Config\StorageInterface
117    *   The configuration storage object.
118    */
119   public function getStorage() {
120     return $this->storage;
121   }
122
123   /**
124    * Gets the schema wrapper for the whole configuration object.
125    *
126    * The schema wrapper is dependent on the configuration name and the whole
127    * data structure, so if the name or the data changes in any way, the wrapper
128    * should be reset.
129    *
130    * @return \Drupal\Core\Config\Schema\Element
131    */
132   protected function getSchemaWrapper() {
133     if (!isset($this->schemaWrapper)) {
134       $this->schemaWrapper = $this->typedConfigManager->createFromNameAndData($this->name, $this->data);
135     }
136     return $this->schemaWrapper;
137   }
138
139   /**
140    * Validate the values are allowed data types.
141    *
142    * @param string $key
143    *   A string that maps to a key within the configuration data.
144    * @param string $value
145    *   Value to associate with the key.
146    *
147    * @return null
148    *
149    * @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
150    *   If the value is unsupported in configuration.
151    */
152   protected function validateValue($key, $value) {
153     // Minimal validation. Should not try to serialize resources or non-arrays.
154     if (is_array($value)) {
155       foreach ($value as $nested_value_key => $nested_value) {
156         $this->validateValue($key . '.' . $nested_value_key, $nested_value);
157       }
158     }
159     elseif ($value !== NULL && !is_scalar($value)) {
160       throw new UnsupportedDataTypeConfigException("Invalid data type for config element {$this->getName()}:$key");
161     }
162   }
163
164   /**
165    * Casts the value to correct data type using the configuration schema.
166    *
167    * @param string $key
168    *   A string that maps to a key within the configuration data.
169    * @param string $value
170    *   Value to associate with the key.
171    *
172    * @return mixed
173    *   The value cast to the type indicated in the schema.
174    *
175    * @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
176    *   If the value is unsupported in configuration.
177    */
178   protected function castValue($key, $value) {
179     $element = $this->getSchemaWrapper()->get($key);
180     // Do not cast value if it is unknown or defined to be ignored.
181     if ($element && ($element instanceof Undefined || $element instanceof Ignore)) {
182       // Do validate the value (may throw UnsupportedDataTypeConfigException)
183       // to ensure unsupported types are not supported in this case either.
184       $this->validateValue($key, $value);
185       return $value;
186     }
187     if (is_scalar($value) || $value === NULL) {
188       if ($element && $element instanceof PrimitiveInterface) {
189         // Special handling for integers and floats since the configuration
190         // system is primarily concerned with saving values from the Form API
191         // we have to special case the meaning of an empty string for numeric
192         // types. In PHP this would be casted to a 0 but for the purposes of
193         // configuration we need to treat this as a NULL.
194         $empty_value = $value === '' && ($element instanceof IntegerInterface || $element instanceof FloatInterface);
195
196         if ($value === NULL || $empty_value) {
197           $value = NULL;
198         }
199         else {
200           $value = $element->getCastedValue();
201         }
202       }
203     }
204     else {
205       // Throw exception on any non-scalar or non-array value.
206       if (!is_array($value)) {
207         throw new UnsupportedDataTypeConfigException("Invalid data type for config element {$this->getName()}:$key");
208       }
209       // Recurse into any nested keys.
210       foreach ($value as $nested_value_key => $nested_value) {
211         $value[$nested_value_key] = $this->castValue($key . '.' . $nested_value_key, $nested_value);
212       }
213
214       if ($element instanceof Sequence) {
215         $data_definition = $element->getDataDefinition();
216         if ($data_definition instanceof SequenceDataDefinition) {
217           // Apply any sorting defined on the schema.
218           switch ($data_definition->getOrderBy()) {
219             case 'key':
220               ksort($value);
221               break;
222
223             case 'value':
224               // The PHP documentation notes that "Be careful when sorting
225               // arrays with mixed types values because sort() can produce
226               // unpredictable results". There is no risk here because
227               // \Drupal\Core\Config\StorableConfigBase::castValue() has
228               // already cast all values to the same type using the
229               // configuration schema.
230               sort($value);
231               break;
232
233           }
234         }
235       }
236     }
237     return $value;
238   }
239
240 }