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 BadMethodCallException;
18: use InvalidArgumentException;
19: use LogicException;
20:
21: /**
22: * A trait that provides a set of static methods to manage configuration
23: * for classes that provide an adapter facade or need to have sets of
24: * configuration data registered and manipulated.
25: *
26: * Implementing objects are expected to declare a static `$_dsnClassMap` property.
27: */
28: trait StaticConfigTrait
29: {
30:
31: /**
32: * Configuration sets.
33: *
34: * @var array
35: */
36: protected static $_config = [];
37:
38: /**
39: * This method can be used to define configuration adapters for an application.
40: *
41: * To change an adapter's configuration at runtime, first drop the adapter and then
42: * reconfigure it.
43: *
44: * Adapters will not be constructed until the first operation is done.
45: *
46: * ### Usage
47: *
48: * Assuming that the class' name is `Cache` the following scenarios
49: * are supported:
50: *
51: * Setting a cache engine up.
52: *
53: * ```
54: * Cache::setConfig('default', $settings);
55: * ```
56: *
57: * Injecting a constructed adapter in:
58: *
59: * ```
60: * Cache::setConfig('default', $instance);
61: * ```
62: *
63: * Configure multiple adapters at once:
64: *
65: * ```
66: * Cache::setConfig($arrayOfConfig);
67: * ```
68: *
69: * @param string|array $key The name of the configuration, or an array of multiple configs.
70: * @param array $config An array of name => configuration data for adapter.
71: * @throws \BadMethodCallException When trying to modify an existing config.
72: * @throws \LogicException When trying to store an invalid structured config array.
73: * @return void
74: */
75: public static function setConfig($key, $config = null)
76: {
77: if ($config === null) {
78: if (!is_array($key)) {
79: throw new LogicException('If config is null, key must be an array.');
80: }
81: foreach ($key as $name => $settings) {
82: static::setConfig($name, $settings);
83: }
84:
85: return;
86: }
87:
88: if (isset(static::$_config[$key])) {
89: throw new BadMethodCallException(sprintf('Cannot reconfigure existing key "%s"', $key));
90: }
91:
92: if (is_object($config)) {
93: $config = ['className' => $config];
94: }
95:
96: if (isset($config['url'])) {
97: $parsed = static::parseDsn($config['url']);
98: unset($config['url']);
99: $config = $parsed + $config;
100: }
101:
102: if (isset($config['engine']) && empty($config['className'])) {
103: $config['className'] = $config['engine'];
104: unset($config['engine']);
105: }
106: static::$_config[$key] = $config;
107: }
108:
109: /**
110: * Reads existing configuration.
111: *
112: * @param string $key The name of the configuration.
113: * @return array|null Array of configuration data.
114: */
115: public static function getConfig($key)
116: {
117: return isset(static::$_config[$key]) ? static::$_config[$key] : null;
118: }
119:
120: /**
121: * This method can be used to define configuration adapters for an application
122: * or read existing configuration.
123: *
124: * To change an adapter's configuration at runtime, first drop the adapter and then
125: * reconfigure it.
126: *
127: * Adapters will not be constructed until the first operation is done.
128: *
129: * ### Usage
130: *
131: * Assuming that the class' name is `Cache` the following scenarios
132: * are supported:
133: *
134: * Reading config data back:
135: *
136: * ```
137: * Cache::config('default');
138: * ```
139: *
140: * Setting a cache engine up.
141: *
142: * ```
143: * Cache::config('default', $settings);
144: * ```
145: *
146: * Injecting a constructed adapter in:
147: *
148: * ```
149: * Cache::config('default', $instance);
150: * ```
151: *
152: * Configure multiple adapters at once:
153: *
154: * ```
155: * Cache::config($arrayOfConfig);
156: * ```
157: *
158: * @deprecated 3.4.0 Use setConfig()/getConfig() instead.
159: * @param string|array $key The name of the configuration, or an array of multiple configs.
160: * @param array|null $config An array of name => configuration data for adapter.
161: * @return array|null Null when adding configuration or an array of configuration data when reading.
162: * @throws \BadMethodCallException When trying to modify an existing config.
163: */
164: public static function config($key, $config = null)
165: {
166: if ($config !== null || is_array($key)) {
167: static::setConfig($key, $config);
168:
169: return null;
170: }
171:
172: return static::getConfig($key);
173: }
174:
175: /**
176: * Drops a constructed adapter.
177: *
178: * If you wish to modify an existing configuration, you should drop it,
179: * change configuration and then re-add it.
180: *
181: * If the implementing objects supports a `$_registry` object the named configuration
182: * will also be unloaded from the registry.
183: *
184: * @param string $config An existing configuration you wish to remove.
185: * @return bool Success of the removal, returns false when the config does not exist.
186: */
187: public static function drop($config)
188: {
189: if (!isset(static::$_config[$config])) {
190: return false;
191: }
192: if (isset(static::$_registry)) {
193: static::$_registry->unload($config);
194: }
195: unset(static::$_config[$config]);
196:
197: return true;
198: }
199:
200: /**
201: * Returns an array containing the named configurations
202: *
203: * @return array Array of configurations.
204: */
205: public static function configured()
206: {
207: return array_keys(static::$_config);
208: }
209:
210: /**
211: * Parses a DSN into a valid connection configuration
212: *
213: * This method allows setting a DSN using formatting similar to that used by PEAR::DB.
214: * The following is an example of its usage:
215: *
216: * ```
217: * $dsn = 'mysql://user:pass@localhost/database?';
218: * $config = ConnectionManager::parseDsn($dsn);
219: *
220: * $dsn = 'Cake\Log\Engine\FileLog://?types=notice,info,debug&file=debug&path=LOGS';
221: * $config = Log::parseDsn($dsn);
222: *
223: * $dsn = 'smtp://user:secret@localhost:25?timeout=30&client=null&tls=null';
224: * $config = Email::parseDsn($dsn);
225: *
226: * $dsn = 'file:///?className=\My\Cache\Engine\FileEngine';
227: * $config = Cache::parseDsn($dsn);
228: *
229: * $dsn = 'File://?prefix=myapp_cake_core_&serialize=true&duration=+2 minutes&path=/tmp/persistent/';
230: * $config = Cache::parseDsn($dsn);
231: * ```
232: *
233: * For all classes, the value of `scheme` is set as the value of both the `className`
234: * unless they have been otherwise specified.
235: *
236: * Note that querystring arguments are also parsed and set as values in the returned configuration.
237: *
238: * @param string $dsn The DSN string to convert to a configuration array
239: * @return array The configuration array to be stored after parsing the DSN
240: * @throws \InvalidArgumentException If not passed a string, or passed an invalid string
241: */
242: public static function parseDsn($dsn)
243: {
244: if (empty($dsn)) {
245: return [];
246: }
247:
248: if (!is_string($dsn)) {
249: throw new InvalidArgumentException('Only strings can be passed to parseDsn');
250: }
251:
252: $pattern = '/^(?P<scheme>[\w\\\\]+):\/\/((?P<user>.*?)(:(?P<password>.*?))?@)?' .
253: '((?P<host>[.\w-\\\\]+)(:(?P<port>\d+))?)?' .
254: '(?P<path>\/[^?]*)?(\?(?P<query>.*))?$/';
255: preg_match($pattern, $dsn, $parsed);
256:
257: if (!$parsed) {
258: throw new InvalidArgumentException("The DSN string '{$dsn}' could not be parsed.");
259: }
260: foreach ($parsed as $k => $v) {
261: if (is_int($k)) {
262: unset($parsed[$k]);
263: }
264: if ($v === '') {
265: unset($parsed[$k]);
266: }
267: }
268:
269: $query = '';
270:
271: if (isset($parsed['query'])) {
272: $query = $parsed['query'];
273: unset($parsed['query']);
274: }
275:
276: parse_str($query, $queryArgs);
277:
278: foreach ($queryArgs as $key => $value) {
279: if ($value === 'true') {
280: $queryArgs[$key] = true;
281: } elseif ($value === 'false') {
282: $queryArgs[$key] = false;
283: } elseif ($value === 'null') {
284: $queryArgs[$key] = null;
285: }
286: }
287:
288: if (isset($parsed['user'])) {
289: $parsed['username'] = $parsed['user'];
290: }
291:
292: if (isset($parsed['pass'])) {
293: $parsed['password'] = $parsed['pass'];
294: }
295:
296: unset($parsed['pass'], $parsed['user']);
297: $parsed = $queryArgs + $parsed;
298:
299: if (empty($parsed['className'])) {
300: $classMap = static::getDsnClassMap();
301:
302: $parsed['className'] = $parsed['scheme'];
303: if (isset($classMap[$parsed['scheme']])) {
304: $parsed['className'] = $classMap[$parsed['scheme']];
305: }
306: }
307:
308: return $parsed;
309: }
310:
311: /**
312: * Updates the DSN class map for this class.
313: *
314: * @param array $map Additions/edits to the class map to apply.
315: * @return void
316: */
317: public static function setDsnClassMap(array $map)
318: {
319: static::$_dsnClassMap = $map + static::$_dsnClassMap;
320: }
321:
322: /**
323: * Returns the DSN class map for this class.
324: *
325: * @return array
326: */
327: public static function getDsnClassMap()
328: {
329: return static::$_dsnClassMap;
330: }
331:
332: /**
333: * Returns or updates the DSN class map for this class.
334: *
335: * @deprecated 3.4.0 Use setDsnClassMap()/getDsnClassMap() instead.
336: * @param array|null $map Additions/edits to the class map to apply.
337: * @return array
338: */
339: public static function dsnClassMap(array $map = null)
340: {
341: if ($map !== null) {
342: static::setDsnClassMap($map);
343: }
344:
345: return static::getDsnClassMap();
346: }
347: }
348: