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: deprecationWarning(
167: get_called_class() . '::config() is deprecated. ' .
168: 'Use setConfig()/getConfig() instead.'
169: );
170:
171: if ($config !== null || is_array($key)) {
172: static::setConfig($key, $config);
173:
174: return null;
175: }
176:
177: return static::getConfig($key);
178: }
179:
180: /**
181: * Drops a constructed adapter.
182: *
183: * If you wish to modify an existing configuration, you should drop it,
184: * change configuration and then re-add it.
185: *
186: * If the implementing objects supports a `$_registry` object the named configuration
187: * will also be unloaded from the registry.
188: *
189: * @param string $config An existing configuration you wish to remove.
190: * @return bool Success of the removal, returns false when the config does not exist.
191: */
192: public static function drop($config)
193: {
194: if (!isset(static::$_config[$config])) {
195: return false;
196: }
197: if (isset(static::$_registry)) {
198: static::$_registry->unload($config);
199: }
200: unset(static::$_config[$config]);
201:
202: return true;
203: }
204:
205: /**
206: * Returns an array containing the named configurations
207: *
208: * @return string[] Array of configurations.
209: */
210: public static function configured()
211: {
212: return array_keys(static::$_config);
213: }
214:
215: /**
216: * Parses a DSN into a valid connection configuration
217: *
218: * This method allows setting a DSN using formatting similar to that used by PEAR::DB.
219: * The following is an example of its usage:
220: *
221: * ```
222: * $dsn = 'mysql://user:pass@localhost/database?';
223: * $config = ConnectionManager::parseDsn($dsn);
224: *
225: * $dsn = 'Cake\Log\Engine\FileLog://?types=notice,info,debug&file=debug&path=LOGS';
226: * $config = Log::parseDsn($dsn);
227: *
228: * $dsn = 'smtp://user:secret@localhost:25?timeout=30&client=null&tls=null';
229: * $config = Email::parseDsn($dsn);
230: *
231: * $dsn = 'file:///?className=\My\Cache\Engine\FileEngine';
232: * $config = Cache::parseDsn($dsn);
233: *
234: * $dsn = 'File://?prefix=myapp_cake_core_&serialize=true&duration=+2 minutes&path=/tmp/persistent/';
235: * $config = Cache::parseDsn($dsn);
236: * ```
237: *
238: * For all classes, the value of `scheme` is set as the value of both the `className`
239: * unless they have been otherwise specified.
240: *
241: * Note that querystring arguments are also parsed and set as values in the returned configuration.
242: *
243: * @param string $dsn The DSN string to convert to a configuration array
244: * @return array The configuration array to be stored after parsing the DSN
245: * @throws \InvalidArgumentException If not passed a string, or passed an invalid string
246: */
247: public static function parseDsn($dsn)
248: {
249: if (empty($dsn)) {
250: return [];
251: }
252:
253: if (!is_string($dsn)) {
254: throw new InvalidArgumentException('Only strings can be passed to parseDsn');
255: }
256:
257: $pattern = <<<'REGEXP'
258: {
259: ^
260: (?P<_scheme>
261: (?P<scheme>[\w\\\\]+)://
262: )
263: (?P<_username>
264: (?P<username>.*?)
265: (?P<_password>
266: :(?P<password>.*?)
267: )?
268: @
269: )?
270: (?P<_host>
271: (?P<host>[^?#/:@]+)
272: (?P<_port>
273: :(?P<port>\d+)
274: )?
275: )?
276: (?P<_path>
277: (?P<path>/[^?#]*)
278: )?
279: (?P<_query>
280: \?(?P<query>[^#]*)
281: )?
282: (?P<_fragment>
283: \#(?P<fragment>.*)
284: )?
285: $
286: }x
287: REGEXP;
288:
289: preg_match($pattern, $dsn, $parsed);
290:
291: if (!$parsed) {
292: throw new InvalidArgumentException("The DSN string '{$dsn}' could not be parsed.");
293: }
294:
295: $exists = [];
296: foreach ($parsed as $k => $v) {
297: if (is_int($k)) {
298: unset($parsed[$k]);
299: } elseif (strpos($k, '_') === 0) {
300: $exists[substr($k, 1)] = ($v !== '');
301: unset($parsed[$k]);
302: } elseif ($v === '' && !$exists[$k]) {
303: unset($parsed[$k]);
304: }
305: }
306:
307: $query = '';
308:
309: if (isset($parsed['query'])) {
310: $query = $parsed['query'];
311: unset($parsed['query']);
312: }
313:
314: parse_str($query, $queryArgs);
315:
316: foreach ($queryArgs as $key => $value) {
317: if ($value === 'true') {
318: $queryArgs[$key] = true;
319: } elseif ($value === 'false') {
320: $queryArgs[$key] = false;
321: } elseif ($value === 'null') {
322: $queryArgs[$key] = null;
323: }
324: }
325:
326: $parsed = $queryArgs + $parsed;
327:
328: if (empty($parsed['className'])) {
329: $classMap = static::getDsnClassMap();
330:
331: $parsed['className'] = $parsed['scheme'];
332: if (isset($classMap[$parsed['scheme']])) {
333: $parsed['className'] = $classMap[$parsed['scheme']];
334: }
335: }
336:
337: return $parsed;
338: }
339:
340: /**
341: * Updates the DSN class map for this class.
342: *
343: * @param array $map Additions/edits to the class map to apply.
344: * @return void
345: */
346: public static function setDsnClassMap(array $map)
347: {
348: static::$_dsnClassMap = $map + static::$_dsnClassMap;
349: }
350:
351: /**
352: * Returns the DSN class map for this class.
353: *
354: * @return array
355: */
356: public static function getDsnClassMap()
357: {
358: return static::$_dsnClassMap;
359: }
360:
361: /**
362: * Returns or updates the DSN class map for this class.
363: *
364: * @deprecated 3.4.0 Use setDsnClassMap()/getDsnClassMap() instead.
365: * @param array|null $map Additions/edits to the class map to apply.
366: * @return array
367: */
368: public static function dsnClassMap(array $map = null)
369: {
370: deprecationWarning(
371: get_called_class() . '::setDsnClassMap() is deprecated. ' .
372: 'Use setDsnClassMap()/getDsnClassMap() instead.'
373: );
374:
375: if ($map !== null) {
376: static::setDsnClassMap($map);
377: }
378:
379: return static::getDsnClassMap();
380: }
381: }
382: