1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Core;
16:
17: use Cake\Core\Exception\Exception;
18: use Cake\Utility\Hash;
19:
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: /**
29: * Runtime config
30: *
31: * @var array
32: */
33: protected $_config = [];
34:
35: /**
36: * Whether the config property has already been configured with defaults
37: *
38: * @var bool
39: */
40: protected $_configInitialized = false;
41:
42: /**
43: * Sets the config.
44: *
45: * ### Usage
46: *
47: * Setting a specific value:
48: *
49: * ```
50: * $this->setConfig('key', $value);
51: * ```
52: *
53: * Setting a nested value:
54: *
55: * ```
56: * $this->setConfig('some.nested.key', $value);
57: * ```
58: *
59: * Updating multiple config settings at the same time:
60: *
61: * ```
62: * $this->setConfig(['one' => 'value', 'another' => 'value']);
63: * ```
64: *
65: * @param string|array $key The key to set, or a complete array of configs.
66: * @param mixed|null $value The value to set.
67: * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
68: * @return $this
69: * @throws \Cake\Core\Exception\Exception When trying to set a key that is invalid.
70: */
71: public function setConfig($key, $value = null, $merge = true)
72: {
73: if (!$this->_configInitialized) {
74: $this->_config = $this->_defaultConfig;
75: $this->_configInitialized = true;
76: }
77:
78: $this->_configWrite($key, $value, $merge);
79:
80: return $this;
81: }
82:
83: /**
84: * Returns the config.
85: *
86: * ### Usage
87: *
88: * Reading the whole config:
89: *
90: * ```
91: * $this->getConfig();
92: * ```
93: *
94: * Reading a specific value:
95: *
96: * ```
97: * $this->getConfig('key');
98: * ```
99: *
100: * Reading a nested value:
101: *
102: * ```
103: * $this->getConfig('some.nested.key');
104: * ```
105: *
106: * Reading with default value:
107: *
108: * ```
109: * $this->getConfig('some-key', 'default-value');
110: * ```
111: *
112: * @param string|null $key The key to get or null for the whole config.
113: * @param mixed $default The return value when the key does not exist.
114: * @return mixed Config value being read.
115: */
116: public function getConfig($key = null, $default = null)
117: {
118: if (!$this->_configInitialized) {
119: $this->_config = $this->_defaultConfig;
120: $this->_configInitialized = true;
121: }
122:
123: $return = $this->_configRead($key);
124:
125: return $return === null ? $default : $return;
126: }
127:
128: /**
129: * Gets/Sets the config.
130: *
131: * ### Usage
132: *
133: * Reading the whole config:
134: *
135: * ```
136: * $this->config();
137: * ```
138: *
139: * Reading a specific value:
140: *
141: * ```
142: * $this->config('key');
143: * ```
144: *
145: * Reading a nested value:
146: *
147: * ```
148: * $this->config('some.nested.key');
149: * ```
150: *
151: * Setting a specific value:
152: *
153: * ```
154: * $this->config('key', $value);
155: * ```
156: *
157: * Setting a nested value:
158: *
159: * ```
160: * $this->config('some.nested.key', $value);
161: * ```
162: *
163: * Updating multiple config settings at the same time:
164: *
165: * ```
166: * $this->config(['one' => 'value', 'another' => 'value']);
167: * ```
168: *
169: * @deprecated 3.4.0 use setConfig()/getConfig() instead.
170: * @param string|array|null $key The key to get/set, or a complete array of configs.
171: * @param mixed|null $value The value to set.
172: * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true.
173: * @return mixed Config value being read, or the object itself on write operations.
174: * @throws \Cake\Core\Exception\Exception When trying to set a key that is invalid.
175: */
176: public function config($key = null, $value = null, $merge = true)
177: {
178: deprecationWarning(
179: get_called_class() . '::config() is deprecated. ' .
180: 'Use setConfig()/getConfig() instead.'
181: );
182:
183: if (is_array($key) || func_num_args() >= 2) {
184: return $this->setConfig($key, $value, $merge);
185: }
186:
187: return $this->getConfig($key);
188: }
189:
190: /**
191: * Merge provided config with existing config. Unlike `config()` which does
192: * a recursive merge for nested keys, this method does a simple merge.
193: *
194: * Setting a specific value:
195: *
196: * ```
197: * $this->configShallow('key', $value);
198: * ```
199: *
200: * Setting a nested value:
201: *
202: * ```
203: * $this->configShallow('some.nested.key', $value);
204: * ```
205: *
206: * Updating multiple config settings at the same time:
207: *
208: * ```
209: * $this->configShallow(['one' => 'value', 'another' => 'value']);
210: * ```
211: *
212: * @param string|array $key The key to set, or a complete array of configs.
213: * @param mixed|null $value The value to set.
214: * @return $this
215: */
216: public function configShallow($key, $value = null)
217: {
218: if (!$this->_configInitialized) {
219: $this->_config = $this->_defaultConfig;
220: $this->_configInitialized = true;
221: }
222:
223: $this->_configWrite($key, $value, 'shallow');
224:
225: return $this;
226: }
227:
228: /**
229: * Reads a config key.
230: *
231: * @param string|null $key Key to read.
232: * @return mixed
233: */
234: protected function _configRead($key)
235: {
236: if ($key === null) {
237: return $this->_config;
238: }
239:
240: if (strpos($key, '.') === false) {
241: return isset($this->_config[$key]) ? $this->_config[$key] : null;
242: }
243:
244: $return = $this->_config;
245:
246: foreach (explode('.', $key) as $k) {
247: if (!is_array($return) || !isset($return[$k])) {
248: $return = null;
249: break;
250: }
251:
252: $return = $return[$k];
253: }
254:
255: return $return;
256: }
257:
258: /**
259: * Writes a config key.
260: *
261: * @param string|array $key Key to write to.
262: * @param mixed $value Value to write.
263: * @param bool|string $merge True to merge recursively, 'shallow' for simple merge,
264: * false to overwrite, defaults to false.
265: * @return void
266: * @throws \Cake\Core\Exception\Exception if attempting to clobber existing config
267: */
268: protected function _configWrite($key, $value, $merge = false)
269: {
270: if (is_string($key) && $value === null) {
271: $this->_configDelete($key);
272:
273: return;
274: }
275:
276: if ($merge) {
277: $update = is_array($key) ? $key : [$key => $value];
278: if ($merge === 'shallow') {
279: $this->_config = array_merge($this->_config, Hash::expand($update));
280: } else {
281: $this->_config = Hash::merge($this->_config, Hash::expand($update));
282: }
283:
284: return;
285: }
286:
287: if (is_array($key)) {
288: foreach ($key as $k => $val) {
289: $this->_configWrite($k, $val);
290: }
291:
292: return;
293: }
294:
295: if (strpos($key, '.') === false) {
296: $this->_config[$key] = $value;
297:
298: return;
299: }
300:
301: $update =& $this->_config;
302: $stack = explode('.', $key);
303:
304: foreach ($stack as $k) {
305: if (!is_array($update)) {
306: throw new Exception(sprintf('Cannot set %s value', $key));
307: }
308:
309: if (!isset($update[$k])) {
310: $update[$k] = [];
311: }
312:
313: $update =& $update[$k];
314: }
315:
316: $update = $value;
317: }
318:
319: /**
320: * Deletes a single config key.
321: *
322: * @param string $key Key to delete.
323: * @return void
324: * @throws \Cake\Core\Exception\Exception if attempting to clobber existing config
325: */
326: protected function _configDelete($key)
327: {
328: if (strpos($key, '.') === false) {
329: unset($this->_config[$key]);
330:
331: return;
332: }
333:
334: $update =& $this->_config;
335: $stack = explode('.', $key);
336: $length = count($stack);
337:
338: foreach ($stack as $i => $k) {
339: if (!is_array($update)) {
340: throw new Exception(sprintf('Cannot unset %s value', $key));
341: }
342:
343: if (!isset($update[$k])) {
344: break;
345: }
346:
347: if ($i === $length - 1) {
348: unset($update[$k]);
349: break;
350: }
351:
352: $update =& $update[$k];
353: }
354: }
355: }
356: