InstanceConfigTrait.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Core;
  17. use Cake\Core\Exception\Exception;
  18. use Cake\Utility\Hash;
  19. use InvalidArgumentException;
  20. /**
  21. * A trait for reading and writing instance config
  22. *
  23. * Implementing objects are expected to declare a `$_defaultConfig` property.
  24. */
  25. trait InstanceConfigTrait
  26. {
  27. /**
  28. * Runtime config
  29. *
  30. * @var array
  31. */
  32. protected $_config = [];
  33. /**
  34. * Whether the config property has already been configured with defaults
  35. *
  36. * @var bool
  37. */
  38. protected $_configInitialized = false;
  39. /**
  40. * Sets the config.
  41. *
  42. * ### Usage
  43. *
  44. * Setting a specific value:
  45. *
  46. * ```
  47. * $this->setConfig('key', $value);
  48. * ```
  49. *
  50. * Setting a nested value:
  51. *
  52. * ```
  53. * $this->setConfig('some.nested.key', $value);
  54. * ```
  55. *
  56. * Updating multiple config settings at the same time:
  57. *
  58. * ```
  59. * $this->setConfig(['one' => 'value', 'another' => 'value']);
  60. * ```
  61. *
  62. * @param string|array $key The key to set, or a complete array of configs.
  63. * @param mixed|null $value The value to set.
  64. * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
  65. * @return $this
  66. * @throws \Cake\Core\Exception\Exception When trying to set a key that is invalid.
  67. */
  68. public function setConfig($key, $value = null, $merge = true)
  69. {
  70. if (!$this->_configInitialized) {
  71. $this->_config = $this->_defaultConfig;
  72. $this->_configInitialized = true;
  73. }
  74. $this->_configWrite($key, $value, $merge);
  75. return $this;
  76. }
  77. /**
  78. * Returns the config.
  79. *
  80. * ### Usage
  81. *
  82. * Reading the whole config:
  83. *
  84. * ```
  85. * $this->getConfig();
  86. * ```
  87. *
  88. * Reading a specific value:
  89. *
  90. * ```
  91. * $this->getConfig('key');
  92. * ```
  93. *
  94. * Reading a nested value:
  95. *
  96. * ```
  97. * $this->getConfig('some.nested.key');
  98. * ```
  99. *
  100. * Reading with default value:
  101. *
  102. * ```
  103. * $this->getConfig('some-key', 'default-value');
  104. * ```
  105. *
  106. * @param string|null $key The key to get or null for the whole config.
  107. * @param mixed|null $default The return value when the key does not exist.
  108. * @return mixed|null Configuration data at the named key or null if the key does not exist.
  109. */
  110. public function getConfig(?string $key = null, $default = null)
  111. {
  112. if (!$this->_configInitialized) {
  113. $this->_config = $this->_defaultConfig;
  114. $this->_configInitialized = true;
  115. }
  116. $return = $this->_configRead($key);
  117. return $return ?? $default;
  118. }
  119. /**
  120. * Returns the config for this specific key.
  121. *
  122. * The config value for this key must exist, it can never be null.
  123. *
  124. * @param string $key The key to get.
  125. * @return mixed Configuration data at the named key
  126. * @throws \InvalidArgumentException
  127. */
  128. public function getConfigOrFail(string $key)
  129. {
  130. $config = $this->getConfig($key);
  131. if ($config === null) {
  132. throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key));
  133. }
  134. return $config;
  135. }
  136. /**
  137. * Merge provided config with existing config. Unlike `config()` which does
  138. * a recursive merge for nested keys, this method does a simple merge.
  139. *
  140. * Setting a specific value:
  141. *
  142. * ```
  143. * $this->configShallow('key', $value);
  144. * ```
  145. *
  146. * Setting a nested value:
  147. *
  148. * ```
  149. * $this->configShallow('some.nested.key', $value);
  150. * ```
  151. *
  152. * Updating multiple config settings at the same time:
  153. *
  154. * ```
  155. * $this->configShallow(['one' => 'value', 'another' => 'value']);
  156. * ```
  157. *
  158. * @param string|array $key The key to set, or a complete array of configs.
  159. * @param mixed|null $value The value to set.
  160. * @return $this
  161. */
  162. public function configShallow($key, $value = null)
  163. {
  164. if (!$this->_configInitialized) {
  165. $this->_config = $this->_defaultConfig;
  166. $this->_configInitialized = true;
  167. }
  168. $this->_configWrite($key, $value, 'shallow');
  169. return $this;
  170. }
  171. /**
  172. * Reads a config key.
  173. *
  174. * @param string|null $key Key to read.
  175. * @return mixed
  176. */
  177. protected function _configRead(?string $key)
  178. {
  179. if ($key === null) {
  180. return $this->_config;
  181. }
  182. if (strpos($key, '.') === false) {
  183. return $this->_config[$key] ?? null;
  184. }
  185. $return = $this->_config;
  186. foreach (explode('.', $key) as $k) {
  187. if (!is_array($return) || !isset($return[$k])) {
  188. $return = null;
  189. break;
  190. }
  191. $return = $return[$k];
  192. }
  193. return $return;
  194. }
  195. /**
  196. * Writes a config key.
  197. *
  198. * @param string|array $key Key to write to.
  199. * @param mixed $value Value to write.
  200. * @param bool|string $merge True to merge recursively, 'shallow' for simple merge,
  201. * false to overwrite, defaults to false.
  202. * @return void
  203. * @throws \Cake\Core\Exception\Exception if attempting to clobber existing config
  204. */
  205. protected function _configWrite($key, $value, $merge = false): void
  206. {
  207. if (is_string($key) && $value === null) {
  208. $this->_configDelete($key);
  209. return;
  210. }
  211. if ($merge) {
  212. $update = is_array($key) ? $key : [$key => $value];
  213. if ($merge === 'shallow') {
  214. $this->_config = array_merge($this->_config, Hash::expand($update));
  215. } else {
  216. $this->_config = Hash::merge($this->_config, Hash::expand($update));
  217. }
  218. return;
  219. }
  220. if (is_array($key)) {
  221. foreach ($key as $k => $val) {
  222. $this->_configWrite($k, $val);
  223. }
  224. return;
  225. }
  226. if (strpos($key, '.') === false) {
  227. $this->_config[$key] = $value;
  228. return;
  229. }
  230. $update =& $this->_config;
  231. $stack = explode('.', $key);
  232. foreach ($stack as $k) {
  233. if (!is_array($update)) {
  234. throw new Exception(sprintf('Cannot set %s value', $key));
  235. }
  236. if (!isset($update[$k])) {
  237. $update[$k] = [];
  238. }
  239. $update =& $update[$k];
  240. }
  241. $update = $value;
  242. }
  243. /**
  244. * Deletes a single config key.
  245. *
  246. * @param string $key Key to delete.
  247. * @return void
  248. * @throws \Cake\Core\Exception\Exception if attempting to clobber existing config
  249. */
  250. protected function _configDelete(string $key): void
  251. {
  252. if (strpos($key, '.') === false) {
  253. unset($this->_config[$key]);
  254. return;
  255. }
  256. $update =& $this->_config;
  257. $stack = explode('.', $key);
  258. $length = count($stack);
  259. foreach ($stack as $i => $k) {
  260. if (!is_array($update)) {
  261. throw new Exception(sprintf('Cannot unset %s value', $key));
  262. }
  263. if (!isset($update[$k])) {
  264. break;
  265. }
  266. if ($i === $length - 1) {
  267. unset($update[$k]);
  268. break;
  269. }
  270. $update =& $update[$k];
  271. }
  272. }
  273. }