-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConfigReadShell.php
More file actions
414 lines (377 loc) · 12.6 KB
/
Copy pathConfigReadShell.php
File metadata and controls
414 lines (377 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
<?php
/**
* ConfigReadShell
*
* @package ConfigRead\Shell
*/
namespace ConfigRead\Shell;
use Cake\Console\ConsoleOutput;
use Cake\Console\Shell;
use Cake\Core\Configure;
use Cake\Utility\Hash;
/**
* ConfigReadShell class.
*
* Provide a command line interface for fetching the values of keys from
* the `Configure` utility class. Called via
* `./vendor/bin/cake config_read.config_read Key.Name`.
*/
class ConfigReadShell extends Shell {
/**
* Stores command line switch value for whether to use bash variable
* formatting or just echo the raw value. Automatically enabled in
* two cases:
*
* 1. Multiple arguments are provided on the command line.
* 2. The key requested is an array that will be iterated over.
*
* @var bool
*/
public $formatBash = false;
/**
* Stores command line switch value for whether to serialize all output.
*
* When present, it always overrides the --bash option.
*
* @var bool
*/
public $formatSerialize = false;
/**
* Make the shell aware of "unique" key requests.
*
* Cake 3 uses Configure::consume() on a number of Configure keys to
* prime different Cake modules in `config/boostrap.php`.
*
* Cache::setConfig(Configure::consume('Cache'));
* ConnectionManager::setConfig(Configure::consume('Datasources'));
* Email::setConfigTransport(Configure::consume('EmailTransport'));
* Email::setConfig(Configure::consume('Email'));
* Log::setConfig(Configure::consume('Log'));
* Security::setSalt(Configure::consume('Security.salt'));
*
* This removes the configs from Configure, making them inaccessible
* through standard means in this Shell.
*
* This property tracks specific key names and the class::method they
* map to so we can perform the correct logic to fetch the values. For
* example, a request for `Cache._cake_core_.className` would result
* in a call like `\Cake\Cache\Cache::getConfig('_cake_core_')['className']`.
*
* @var array
*/
public $specialKeys = [
'Cache' => '\Cake\Cache\Cache::getConfig',
'Datasources' => '\Cake\Datasource\ConnectionManager::getConfig',
'EmailTransport' => '\Cake\Mailer\Email::getConfigTransport',
'Email' => '\Cake\Mailer\Email::getConfig',
'Log' => '\Cake\Log\Log::getConfig',
'Security.salt' => 'self::securitySaltHelper',
];
/**
* Overrides the default welcome function in order to suppress the
* normal "Welcome to CakePHP" banner.
*
* @return void
*/
public function _welcome() {
// Do nothing.
}
/**
* Sets internal state, validate options/arguments.
*
* @return void
*/
public function startup() {
parent::startup();
$this->formatBash = ($this->params['bash'] || count($this->args) > 1);
$this->formatSerialize = $this->params['serialize'];
if (empty($this->args)) {
$this->_displayHelp('');
$this->abort(__('No Configure keys provided.'));
}
// All other output should not be processed by the Shell.
$this->_io->setOutputAs(ConsoleOutput::RAW);
}
/**
* Main shell execution method.
*
* Coordinates command line arguments and kicks off the output routine.
*
* @return void
*/
public function main() {
if ($this->formatSerialize) {
$this->serializedFetchAndPrint();
} else {
$this->simpleFetchAndPrint();
}
}
/**
* Iterate over provided args, collecting them for serialization.
*
* Used to --serialize formatted output. Returns a single requested
* value as a directly-serialized string. If multiple keys were
* provided on the command line, they are collected into an
* associative array, which is serialized and echoed.
*
* @return void
*/
protected function serializedFetchAndPrint() {
$unserialized = [];
foreach ($this->args as $key) {
$unserialized[$key] = $this->fetchVal($key);
}
if (count($unserialized) === 1) {
$unserialized = array_shift($unserialized);
}
$this->out(serialize($unserialized), 0, Shell::QUIET);
}
/**
* Iterate over provided args, printing them to the console as we go.
*
* Used to handle single scalar values and all --bash formatted output.
*
* @return void
*/
protected function simpleFetchAndPrint() {
foreach ($this->args as $key) {
$val = $this->fetchVal($key);
// One-way switch. Will enable bash variable formatting if the
// value is an array, and it will "stick on" after that.
$this->formatBash = ($this->formatBash || is_array($val));
$this->iterateOnKey($key, $val);
}
}
/**
* Value fetch dispatcher.
*
* Checks if the requested key is from a "special" class (that
* normally has its configs `Configure::consume()`d) and routes the
* request to the correct fetcher.
*
* @param string $key The string name of the key to fetch.
* @return mixed The value as obtained from proper fetcher method.
*/
protected function fetchVal($key) {
$special = $this->specialKey($key);
if ($special) {
return $this->fetchSpecial($special);
} else {
return $this->configRead($key);
}
}
/**
* Value fetcher.
*
* Fetches the requested $key from the Configure class. Also serves as
* a handy way to isolate the static method call to `Configure`.
*
* @param string $key The string name of the key to fetch.
* @return mixed The value as obtained from `Configure::read($key)`.
*/
protected function configRead($key) {
return Configure::read($key);
}
/**
* Determine if the provided key name matches one of our known "special" keys.
*
* If so, return an array of attributes including the callable method
* to fetch values from the class, the config key name to pass (if any)
* and the subkey to extract from the results (if any).
*
* Examples:
*
* - 'Cache' -->
* [
* 'callable' => '\Cake\Cache\Cache::config',
* ]
* // Will return all available Cache configs.
*
* - 'Cache.default' -->
* [
* 'callable' => '\Cake\Cache\Cache::config',
* 'arg' => 'default',
* ]
* // Will return all settings for the `default` Cache.
*
* - 'Cache.default.className' -->
* [
* 'callable' => '\Cake\Cache\Cache::config',
* 'arg' => 'default',
* 'subkey' => 'className',
* ]
* // Will return only the `className` value for the `default` Cache.
*
* Matching results are fed into ::fetchSpecial().
*
* @param string $search The dotted key name to check against our special keys.
* @return array|false An array containing at least a [callable] key, and possibly [arg] and [subkey] keys. False on no match.
* @see ::fetchSpecial()
*/
protected function specialKey($search) {
$callable = false;
foreach ($this->specialKeys as $key => $call) {
if (strpos("{$search}.", "{$key}.") === 0) {
$callable = $call;
}
}
if (!$callable) {
return false;
}
$special = [
'callable' => $callable,
];
$keyParts = explode('.', $search, 3);
if (isset($keyParts[1])) {
$special['arg'] = $keyParts[1];
}
if (isset($keyParts[2])) {
$special['subkey'] = $keyParts[2];
}
return $special;
}
/**
* Performs necessary gymnastics to fetch "special" configs.
*
* There are three cases to handle:
*
* 1. A "deep" subkey like `Cache.default.className`. In this case,
* we need to fetch `Cache::getConfig('default')` and then return
* the ['className'] from the result.
*
* 2. A single config array like `Cache.default. We need to fetch
* `Cache::getConfig('default')` and return the whole thing.
*
* 3. All configs in a module like `Cache`. In this case we need to
* try calling `Cache::configured()` (if it exists), then looping
* over the results using each as a key name for a separate call
* to `Cache::getConfig($name)` and accumulating all of the results
* together to return.
*
* @param array $special An array containing at least a [callable] key and possibly [arg] and [subkey]s.
* @return mixed A single scalar value, or an associative array of sub-values.
*/
protected function fetchSpecial($special) {
if (isset($special['arg'])) {
$set = call_user_func($special['callable'], $special['arg']);
} else {
$allConfigCallable = str_replace(
'::getConfig',
'::configured',
$special['callable'],
$replaceCount
);
$set = null;
if ($replaceCount && is_callable($allConfigCallable)) {
foreach (call_user_func($allConfigCallable) as $configName) {
$set[$configName] = call_user_func($special['callable'], $configName);
}
}
}
if (isset($special['subkey']) && is_array($set)) {
return Hash::get($set, $special['subkey']);
} else {
return $set;
}
}
/**
* Recursive output handler.
*
* Handles keys that are themselves associative arrays by recursively
* calling itself for child values. If the provided $val is not an array,
* output it. Otherwise, loop over the array of values and call itself
* for each element (in case they are _also_ arrays.)
*
* The string key names are "built up" as the depth increases, allowing
* for the output key names to be the compound path through the entire
* depth of the array up to that key.
*
* Side effect: Calls out to ::printVal(), which will (typically) echo
* values to stdout.
*
* @param string $key The string name of the key that was used to fetch $val.
* @param mixed $val The value as obtained from `Configure::read($key)`.
* @return void
*/
protected function iterateOnKey($key, $val) {
// Base case.
if (!is_array($val)) {
$this->printVal($key, $val);
return;
}
// Recursive case.
foreach ($val as $k => $v) {
$this->iterateOnKey("{$key}.{$k}", $v);
}
}
/**
* Output wrapper.
*
* Designed to handle printing a single value to the console. Takes the
* requested output format into account to determine how the data is
* formatted for display.
*
* @param string $key The string name of the key that was used to fetch $val.
* @param mixed $val The value as obtained from `Configure::read($key)`.
* @return void
*/
protected function printVal($key, $val) {
$val = escapeshellarg($val);
$format = '%2$s';
if ($this->formatBash) {
$key = strtoupper(str_replace('.', '_', $key));
$format = '%1$s=%2$s';
}
$this->out(sprintf($format, $key, $val), 1, Shell::QUIET);
}
/**
* Provides a custom helper for fetching the App's Security.salt value.
*
* The call to Security::getSalt() method requires no arguments to get the
* current value but we will have split the command line request for
* `Security.salt` into
* `call_user_func('\Cake\Utility\Security::getSalt', 'salt'). That will
* **set** the salt to `salt` and return "salt" as the new value, which
* isn't what we want. This method exists to be the callable function,
* which itself passes no arguments to getSalt, which in turn will return
* the _actual_ salt value.
*
* @param string $salt Because of how this is implemented, this will always be the literal string "salt" and will be ignored.
* @return string The result of calling `Security::getSalt();`
*/
private function securitySaltHelper($salt) {
return call_user_func('\Cake\Utility\Security::getSalt');
}
/**
* getOptionParser
*
* Processing command line options.
*
* @access public
* @return CosnsoleOptionParser
* @codeCoverageIgnore
*/
public function getOptionParser() {
$parser = parent::getOptionParser();
$parser
->addOption('bash', [
'short' => 'b',
'boolean' => true,
'default' => false,
'help' => __('Always use bash variable deinfition formatting. When enabled, output will be formatted as `KEY_NAME=\'value\'`. This option is auto-enabled if multiple keys are provided on the command line, or if the value for the requested key is itself an array. When multiple values are returned, each will be output on its own line.'),
])
->addOption('serialize', [
'short' => 's',
'boolean' => true,
'default' => false,
'help' => __('Encode all output using PHP\'s `serialize()` method. Makes the Shell\'s output suitable for consumption by other PHP console scripts. Always overrides the --bash option. A single requested key will be serialized directly. Multiple requested keys will be combined into an associative array with the provided arguments as key names and then serialized.'),
])
->setDescription(
__('Provides CLI access to variables defined in the Configure class of the host CakePHP application. Will output the value of any keys passed as arguments. Equivelant to `Configure::read(\'Key.Name\')`. Unrecognized keys will produce empty string or `null` output.')
)
->setEpilog(
__('Provide the Key.name(s) to fetch from Configure::read() as arguments. Multiple keys may be specified, separated by spaces.')
);
return $parser;
}
}