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