PageRenderTime 26ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Config/TypedConfigManager.php

https://gitlab.com/reasonat/test8
PHP | 378 lines | 175 code | 25 blank | 178 comment | 36 complexity | dc03a93d2bd10ab639e3b2a74a915d47 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Config;
  3. use Drupal\Component\Utility\NestedArray;
  4. use Drupal\Core\Cache\CacheBackendInterface;
  5. use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
  6. use Drupal\Core\Config\Schema\ConfigSchemaDiscovery;
  7. use Drupal\Core\Extension\ModuleHandlerInterface;
  8. use Drupal\Core\TypedData\TypedDataManager;
  9. /**
  10. * Manages config schema type plugins.
  11. */
  12. class TypedConfigManager extends TypedDataManager implements TypedConfigManagerInterface {
  13. /**
  14. * A storage instance for reading configuration data.
  15. *
  16. * @var \Drupal\Core\Config\StorageInterface
  17. */
  18. protected $configStorage;
  19. /**
  20. * A storage instance for reading configuration schema data.
  21. *
  22. * @var \Drupal\Core\Config\StorageInterface
  23. */
  24. protected $schemaStorage;
  25. /**
  26. * The array of plugin definitions, keyed by plugin id.
  27. *
  28. * @var array
  29. */
  30. protected $definitions;
  31. /**
  32. * Creates a new typed configuration manager.
  33. *
  34. * @param \Drupal\Core\Config\StorageInterface $configStorage
  35. * The storage object to use for reading schema data
  36. * @param \Drupal\Core\Config\StorageInterface $schemaStorage
  37. * The storage object to use for reading schema data
  38. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  39. * The cache backend to use for caching the definitions.
  40. */
  41. public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
  42. $this->configStorage = $configStorage;
  43. $this->schemaStorage = $schemaStorage;
  44. $this->setCacheBackend($cache, 'typed_config_definitions');
  45. $this->alterInfo('config_schema_info');
  46. $this->moduleHandler = $module_handler;
  47. }
  48. /**
  49. * {@inheritdoc}
  50. */
  51. protected function getDiscovery() {
  52. if (!isset($this->discovery)) {
  53. $this->discovery = new ConfigSchemaDiscovery($this->schemaStorage);
  54. }
  55. return $this->discovery;
  56. }
  57. /**
  58. * {@inheritdoc}
  59. */
  60. public function get($name) {
  61. $data = $this->configStorage->read($name);
  62. $type_definition = $this->getDefinition($name);
  63. $data_definition = $this->buildDataDefinition($type_definition, $data);
  64. return $this->create($data_definition, $data);
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function buildDataDefinition(array $definition, $value, $name = NULL, $parent = NULL) {
  70. // Add default values for data type and replace variables.
  71. $definition += array('type' => 'undefined');
  72. $replace = [];
  73. $type = $definition['type'];
  74. if (strpos($type, ']')) {
  75. // Replace variable names in definition.
  76. $replace = is_array($value) ? $value : array();
  77. if (isset($parent)) {
  78. $replace['%parent'] = $parent;
  79. }
  80. if (isset($name)) {
  81. $replace['%key'] = $name;
  82. }
  83. $type = $this->replaceName($type, $replace);
  84. // Remove the type from the definition so that it is replaced with the
  85. // concrete type from schema definitions.
  86. unset($definition['type']);
  87. }
  88. // Add default values from type definition.
  89. $definition += $this->getDefinitionWithReplacements($type, $replace);
  90. $data_definition = $this->createDataDefinition($definition['type']);
  91. // Pass remaining values from definition array to data definition.
  92. foreach ($definition as $key => $value) {
  93. if (!isset($data_definition[$key])) {
  94. $data_definition[$key] = $value;
  95. }
  96. }
  97. return $data_definition;
  98. }
  99. /**
  100. * Determines the typed config type for a plugin ID.
  101. *
  102. * @param string $base_plugin_id
  103. * The plugin ID.
  104. * @param array $definitions
  105. * An array of typed config definitions.
  106. *
  107. * @return string
  108. * The typed config type for the given plugin ID.
  109. */
  110. protected function determineType($base_plugin_id, array $definitions) {
  111. if (isset($definitions[$base_plugin_id])) {
  112. $type = $base_plugin_id;
  113. }
  114. elseif (strpos($base_plugin_id, '.') && $name = $this->getFallbackName($base_plugin_id)) {
  115. // Found a generic name, replacing the last element by '*'.
  116. $type = $name;
  117. }
  118. else {
  119. // If we don't have definition, return the 'undefined' element.
  120. $type = 'undefined';
  121. }
  122. return $type;
  123. }
  124. /**
  125. * Gets a schema definition with replacements for dynamic names.
  126. *
  127. * @param string $base_plugin_id
  128. * A plugin ID.
  129. * @param array $replacements
  130. * An array of replacements for dynamic type names.
  131. * @param bool $exception_on_invalid
  132. * (optional) This parameter is passed along to self::getDefinition().
  133. * However, self::getDefinition() does not respect this parameter, so it is
  134. * effectively useless in this context.
  135. *
  136. * @return array
  137. * A schema definition array.
  138. */
  139. protected function getDefinitionWithReplacements($base_plugin_id, array $replacements, $exception_on_invalid = TRUE) {
  140. $definitions = $this->getDefinitions();
  141. $type = $this->determineType($base_plugin_id, $definitions);
  142. $definition = $definitions[$type];
  143. // Check whether this type is an extension of another one and compile it.
  144. if (isset($definition['type'])) {
  145. $merge = $this->getDefinition($definition['type'], $exception_on_invalid);
  146. // Preserve integer keys on merge, so sequence item types can override
  147. // parent settings as opposed to adding unused second, third, etc. items.
  148. $definition = NestedArray::mergeDeepArray(array($merge, $definition), TRUE);
  149. // Replace dynamic portions of the definition type.
  150. if (!empty($replacements) && strpos($definition['type'], ']')) {
  151. $sub_type = $this->determineType($this->replaceName($definition['type'], $replacements), $definitions);
  152. // Merge the newly determined subtype definition with the original
  153. // definition.
  154. $definition = NestedArray::mergeDeepArray([$definitions[$sub_type], $definition], TRUE);
  155. $type = "$type||$sub_type";
  156. }
  157. // Unset type so we try the merge only once per type.
  158. unset($definition['type']);
  159. $this->definitions[$type] = $definition;
  160. }
  161. // Add type and default definition class.
  162. $definition += array(
  163. 'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
  164. 'type' => $type,
  165. );
  166. return $definition;
  167. }
  168. /**
  169. * {@inheritdoc}
  170. */
  171. public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
  172. return $this->getDefinitionWithReplacements($base_plugin_id, [], $exception_on_invalid);
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function clearCachedDefinitions() {
  178. $this->schemaStorage->reset();
  179. parent::clearCachedDefinitions();
  180. }
  181. /**
  182. * Gets fallback configuration schema name.
  183. *
  184. * @param string $name
  185. * Configuration name or key.
  186. *
  187. * @return null|string
  188. * The resolved schema name for the given configuration name or key. Returns
  189. * null if there is no schema name to fallback to. For example,
  190. * breakpoint.breakpoint.module.toolbar.narrow will check for definitions in
  191. * the following order:
  192. * breakpoint.breakpoint.module.toolbar.*
  193. * breakpoint.breakpoint.module.*.*
  194. * breakpoint.breakpoint.module.*
  195. * breakpoint.breakpoint.*.*.*
  196. * breakpoint.breakpoint.*
  197. * breakpoint.*.*.*.*
  198. * breakpoint.*
  199. * Colons are also used, for example,
  200. * block.settings.system_menu_block:footer will check for definitions in the
  201. * following order:
  202. * block.settings.system_menu_block:*
  203. * block.settings.*:*
  204. * block.settings.*
  205. * block.*.*:*
  206. * block.*
  207. */
  208. protected function getFallbackName($name) {
  209. // Check for definition of $name with filesystem marker.
  210. $replaced = preg_replace('/([^\.:]+)([\.:\*]*)$/', '*\2', $name);
  211. if ($replaced != $name) {
  212. if (isset($this->definitions[$replaced])) {
  213. return $replaced;
  214. }
  215. else {
  216. // No definition for this level. Collapse multiple wildcards to a single
  217. // wildcard to see if there is a greedy match. For example,
  218. // breakpoint.breakpoint.*.* becomes
  219. // breakpoint.breakpoint.*
  220. $one_star = preg_replace('/\.([:\.\*]*)$/', '.*', $replaced);
  221. if ($one_star != $replaced && isset($this->definitions[$one_star])) {
  222. return $one_star;
  223. }
  224. // Check for next level. For example, if breakpoint.breakpoint.* has
  225. // been checked and no match found then check breakpoint.*.*
  226. return $this->getFallbackName($replaced);
  227. }
  228. }
  229. }
  230. /**
  231. * Replaces variables in configuration name.
  232. *
  233. * The configuration name may contain one or more variables to be replaced,
  234. * enclosed in square brackets like '[name]' and will follow the replacement
  235. * rules defined by the replaceVariable() method.
  236. *
  237. * @param string $name
  238. * Configuration name with variables in square brackets.
  239. * @param mixed $data
  240. * Configuration data for the element.
  241. * @return string
  242. * Configuration name with variables replaced.
  243. */
  244. protected function replaceName($name, $data) {
  245. if (preg_match_all("/\[(.*)\]/U", $name, $matches)) {
  246. // Build our list of '[value]' => replacement.
  247. $replace = array();
  248. foreach (array_combine($matches[0], $matches[1]) as $key => $value) {
  249. $replace[$key] = $this->replaceVariable($value, $data);
  250. }
  251. return strtr($name, $replace);
  252. }
  253. else {
  254. return $name;
  255. }
  256. }
  257. /**
  258. * Replaces variable values in included names with configuration data.
  259. *
  260. * Variable values are nested configuration keys that will be replaced by
  261. * their value or some of these special strings:
  262. * - '%key', will be replaced by the element's key.
  263. * - '%parent', to reference the parent element.
  264. * - '%type', to reference the schema definition type. Can only be used in
  265. * combination with %parent.
  266. *
  267. * There may be nested configuration keys separated by dots or more complex
  268. * patterns like '%parent.name' which references the 'name' value of the
  269. * parent element.
  270. *
  271. * Example patterns:
  272. * - 'name.subkey', indicates a nested value of the current element.
  273. * - '%parent.name', will be replaced by the 'name' value of the parent.
  274. * - '%parent.%key', will be replaced by the parent element's key.
  275. * - '%parent.%type', will be replaced by the schema type of the parent.
  276. * - '%parent.%parent.%type', will be replaced by the schema type of the
  277. * parent's parent.
  278. *
  279. * @param string $value
  280. * Variable value to be replaced.
  281. * @param mixed $data
  282. * Configuration data for the element.
  283. *
  284. * @return string
  285. * The replaced value if a replacement found or the original value if not.
  286. */
  287. protected function replaceVariable($value, $data) {
  288. $parts = explode('.', $value);
  289. // Process each value part, one at a time.
  290. while ($name = array_shift($parts)) {
  291. if (!is_array($data) || !isset($data[$name])) {
  292. // Key not found, return original value
  293. return $value;
  294. }
  295. elseif (!$parts) {
  296. $value = $data[$name];
  297. if (is_bool($value)) {
  298. $value = (int) $value;
  299. }
  300. // If no more parts left, this is the final property.
  301. return (string) $value;
  302. }
  303. else {
  304. // Get nested value and continue processing.
  305. if ($name == '%parent') {
  306. /** @var \Drupal\Core\Config\Schema\ArrayElement $parent */
  307. // Switch replacement values with values from the parent.
  308. $parent = $data['%parent'];
  309. $data = $parent->getValue();
  310. $data['%type'] = $parent->getDataDefinition()->getDataType();
  311. // The special %parent and %key values now need to point one level up.
  312. if ($new_parent = $parent->getParent()) {
  313. $data['%parent'] = $new_parent;
  314. $data['%key'] = $new_parent->getName();
  315. }
  316. }
  317. else {
  318. $data = $data[$name];
  319. }
  320. }
  321. }
  322. }
  323. /**
  324. * {@inheritdoc}
  325. */
  326. public function hasConfigSchema($name) {
  327. // The schema system falls back on the Undefined class for unknown types.
  328. $definition = $this->getDefinition($name);
  329. return is_array($definition) && ($definition['class'] != '\Drupal\Core\Config\Schema\Undefined');
  330. }
  331. /**
  332. * {@inheritdoc}
  333. */
  334. protected function alterDefinitions(&$definitions) {
  335. $discovered_schema = array_keys($definitions);
  336. parent::alterDefinitions($definitions);
  337. $altered_schema = array_keys($definitions);
  338. if ($discovered_schema != $altered_schema) {
  339. $added_keys = implode(',', array_diff($altered_schema, $discovered_schema));
  340. $removed_keys = implode(',', array_diff($discovered_schema, $altered_schema));
  341. if (!empty($added_keys) && !empty($removed_keys)) {
  342. $message = "Invoking hook_config_schema_info_alter() has added ($added_keys) and removed ($removed_keys) schema definitions";
  343. }
  344. elseif (!empty($added_keys)) {
  345. $message = "Invoking hook_config_schema_info_alter() has added ($added_keys) schema definitions";
  346. }
  347. else {
  348. $message = "Invoking hook_config_schema_info_alter() has removed ($removed_keys) schema definitions";
  349. }
  350. throw new ConfigSchemaAlterException($message);
  351. }
  352. }
  353. }