StorableConfigBase.php 7.4 KB

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