Skip to content

Commit c8f681e

Browse files
committed
Adds basic special key processing.
Fixes #20 even better.
1 parent 7aa6c66 commit c8f681e

3 files changed

Lines changed: 306 additions & 52 deletions

File tree

README.md

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A CakePHP plugin that provides a Shell to read an app's Configure vars from the
1010

1111
* This is the Cake 3.x version of the plugin, which exists on the `master` branch and is tracked by the `~3.0` semver.
1212
* For the Cake 2.x version of this plugin, please use the repo's `cake-2.x` branch. (semver `~2.0`)
13-
* For the Cake 1.3 version, use the `cake-1.3` branch. (semver `~1.0`) **Note:** we don't expect to actively maintain the 1.3 version. It's here because the project started life as a 1.3 Shell.
13+
* For the Cake 1.3 version, use the `cake-1.3` branch. (semver `~1.0`) **Note:** we don't expect to actively maintain or improve the 1.3 version. It's here because the project started life as a 1.3 Shell.
1414

1515

1616
## Requirements
@@ -50,14 +50,14 @@ To use this plugin, call it from the command line:
5050

5151
```shell
5252
$ cd path/to/app/
53-
$ ./bin/cake ConfigRead Key.Name
53+
$ ./bin/cake ConfigRead.ConfigRead Key.Name
5454
'foo'
5555
```
5656

5757
### Format as a bash variable definition
5858

5959
```shell
60-
$ ./bin/cake ConfigRead Key.Name Second.Key
60+
$ ./bin/cake ConfigRead.ConfigRead Key.Name Second.Key
6161
KEY_NAME='foo'
6262
SECOND_KEY_FIRST='bar'
6363
SECOND_KEY_SECOND='baz'
@@ -67,7 +67,7 @@ SECOND_KEY_THIRD='42'
6767
Note that this format is automatically used whenever more than one key is returned (unless the `--serialize` switch has been used). For example, if you request a key that contains an array, all values in the array will be returned sequentially. Alternatively, if you pass multiple keys on the command line, they will all be returned. The format can also be forced using the `-b` or `--bash` command line switch:
6868

6969
```shell
70-
$ ./bin/cake ConfigRead -b Key.Name
70+
$ ./bin/cake ConfigRead.ConfigRead -b Key.Name
7171
KEY_NAME='foo'
7272
```
7373

@@ -80,10 +80,10 @@ Requesting multiple keys on the command line will produce an array of those keys
8080
This switch always overrides both the `--bash` switch and the Shell's automatic bash formatting.
8181

8282
```shell
83-
$ ./bin/cake ConfigRead -s Key.Name Second.Key
83+
$ ./bin/cake ConfigRead.ConfigRead -s Key.Name Second.Key
8484
a:2:{s:8:"Key.Name";s:3:"foo";s:10:"Second.Key";a:3:{s:5:"First";s:3:"bar";s:6:"Second";s:3:"baz";s:5:"Third";i:42;}}
8585
# Check the result by piping into PHP and unserializing the result.
86-
$ ./bin/cake ConfigRead -s Key.Name Second.Key | php -r 'print_r(unserialize(file_get_contents("php://stdin")));'
86+
$ ./bin/cake ConfigRead.ConfigRead -s Key.Name Second.Key | php -r 'print_r(unserialize(file_get_contents("php://stdin")));'
8787
Array
8888
(
8989
[Key.Name] => foo
@@ -99,7 +99,7 @@ Array
9999
```
100100

101101

102-
## Gotchas
102+
## Potential Gotchas
103103

104104
### "Consumed" Configure Vars
105105

@@ -112,48 +112,9 @@ CakePHP 3 by default "consumes" some of its configs so as not to confused develo
112112
* Log/Log
113113
* Security.salt/Security::salt()
114114

115-
The effect is that you can not use the ConfigReadShell to obtain Configure values for any of these keys since they no longer exist in Configure's store. (This is particularly troublesome if you are using [Environment-Aware Configs](https://github.com/beporter/CakePHP-EnvAwareness/tree/master/slides).)
116-
117-
There are two possible workarounds:
118-
119-
1. Use the `ConsoleShell` instead. For example:
120-
121-
```shell
122-
$ echo 'use Cake\Datasource\ConnectionManager; foreach(ConnectionManager::config("default") as $k => $v) { echo "$k=" . escapeshellarg($v) . PHP_EOL; } exit;' | bin/cake Console -q
123-
className='Cake\Database\Connection'
124-
driver='Cake\Database\Driver\Mysql'
125-
persistent=''
126-
host='localhost'
127-
username='my_app'
128-
password='secret'
129-
database='my_app'
130-
encoding='utf8'
131-
timezone='UTC'
132-
cacheMetadata='1'
133-
quoteIdentifiers=''
134-
name='default'
135-
```
136-
137-
This command is wrapped up in our [loadsys/cakephp-shell-scripts](https://github.com/loadsys/CakePHP-Shell-Scripts) repo as the [`db-credentials`](https://github.com/loadsys/CakePHP-Shell-Scripts/blob/76a24/db-credentials) script.
138-
139-
2. Edit your `config/bootstrap.php` to use `Configure::read()` instead of `Configure::consume()`.
140-
141-
```diff
142-
-Cache::config(Configure::consume('Cache'));
143-
-ConnectionManager::config(Configure::consume('Datasources'));
144-
-Email::configTransport(Configure::consume('EmailTransport'));
145-
-Email::config(Configure::consume('Email'));
146-
-Log::config(Configure::consume('Log'));
147-
-Security::salt(Configure::consume('Security.salt'));
148-
+Cache::config(Configure::read('Cache'));
149-
+ConnectionManager::config(Configure::read('Datasources'));
150-
+Email::configTransport(Configure::read('EmailTransport'));
151-
+Email::config(Configure::read('Email'));
152-
+Log::config(Configure::read('Log'));
153-
+Security::salt(Configure::read('Security.salt'));
154-
```
155-
156-
This will leave the Configure vars in place and allow commands like `bin/cake ConfigRead Datasources.default` to work as expected, but be warned that the values in Configure might not reflect the values actually being used by the various Cake modules.
115+
The ConfigReadShell devotes about half of its codebase dealing with this for you, allowing you to continue to fetch values using the Configure path (`Datasources.default.host` -> `localhost`) while in the background querying `ConnectionManager::config('default')['host']`. (This is particularly helpful if you are using [Environment-Aware Configs](https://github.com/beporter/CakePHP-EnvAwareness/tree/master/slides).)
116+
117+
The "gotcha" here is that ConfigReadShell has to maintain a static list of Configure keys that are consumed, and how to access them in their new container. **If your app consumes a non-standard Configure key during bootstrapping, you will not be able to obtain it from the ConfigReadShell.**
157118

158119

159120
## Contributing

src/Shell/ConfigReadShell.php

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Cake\Console\ConsoleOutput;
1010
use Cake\Console\Shell;
1111
use Cake\Core\Configure;
12+
use Cake\Utility\Hash;
1213

1314
/**
1415
* ConfigReadShell class.
@@ -40,6 +41,38 @@ class ConfigReadShell extends Shell {
4041
*/
4142
public $formatSerialize = false;
4243

44+
/**
45+
* Make the shell aware of "unique" key requests.
46+
*
47+
* Cake 3 uses Configure::consume() on a number of Configure keys to
48+
* prime different Cake modules in `config/boostrap.php`.
49+
*
50+
* Cache::config(Configure::consume('Cache'));
51+
* ConnectionManager::config(Configure::consume('Datasources'));
52+
* Email::configTransport(Configure::consume('EmailTransport'));
53+
* Email::config(Configure::consume('Email'));
54+
* Log::config(Configure::consume('Log'));
55+
* Security::salt(Configure::consume('Security.salt'));
56+
*
57+
* This removes the configs from Configure, making them inaccessible
58+
* through standard means in this Shell.
59+
*
60+
* This property tracks specific key names and the class::method they
61+
* map to so we can perform the correct logic to fetch the values. For
62+
* example, a request for `Cache._cake_core_.className` would result
63+
* in a call like `\Cake\Cache\Cache::config('_cake_core_')['className']`.
64+
*
65+
* @var array
66+
*/
67+
public $specialKeys = [
68+
'Cache' => '\Cake\Cache\Cache::config',
69+
'Datasources' => '\Cake\Datasource\ConnectionManager::config',
70+
'EmailTransport' => '\Cake\Network\Email\Email::configTransport',
71+
'Email' => '\Cake\Network\Email\Email::config',
72+
'Log' => '\Cake\Log\Log::config',
73+
'Security.salt' => 'self::securitySaltHelper',
74+
];
75+
4376
/**
4477
* Overrides the default welcome function in order to suppress the
4578
* normal "Welcome to CakePHP" banner.
@@ -127,6 +160,24 @@ protected function simpleFetchAndPrint() {
127160
}
128161
}
129162

163+
/**
164+
* Value fetch dispatcher.
165+
*
166+
* Checks if the requested key is from a "special" class (that
167+
* normally has its configs `Configure::consume()`d) and routes the
168+
* request to the correct fetcher.
169+
*
170+
* @param string $key The string name of the key to fetch.
171+
* @return mixed The value as obtained from proper fetcher method.
172+
*/
173+
protected function fetchVal($key) {
174+
if ($special = $this->specialKey($key)) {
175+
return $this->fetchSpecial($special);
176+
} else {
177+
return $this->configRead($key);
178+
}
179+
}
180+
130181
/**
131182
* Value fetcher.
132183
*
@@ -136,10 +187,121 @@ protected function simpleFetchAndPrint() {
136187
* @param string $key The string name of the key to fetch.
137188
* @return mixed The value as obtained from `Configure::read($key)`.
138189
*/
139-
protected function fetchVal($key) {
190+
protected function configRead($key) {
140191
return Configure::read($key);
141192
}
142193

194+
/**
195+
* Determine if the provided key name matches one of our known "special" keys.
196+
*
197+
* If so, return an array of attributes including the callable method
198+
* to fetch values from the class, the config key name to pass (if any)
199+
* and the subkey to extract from the results (if any).
200+
*
201+
* Examples:
202+
*
203+
* - 'Cache' -->
204+
* [
205+
* 'callable' => '\Cake\Cache\Cache::config',
206+
* ]
207+
* // Will return all available Cache configs.
208+
*
209+
* - 'Cache.default' -->
210+
* [
211+
* 'callable' => '\Cake\Cache\Cache::config',
212+
* 'arg' => 'default',
213+
* ]
214+
* // Will return all settings for the `default` Cache.
215+
*
216+
* - 'Cache.default.className' -->
217+
* [
218+
* 'callable' => '\Cake\Cache\Cache::config',
219+
* 'arg' => 'default',
220+
* 'subkey' => 'className',
221+
* ]
222+
* // Will return only the `className` value for the `default` Cache.
223+
*
224+
* Matching results are fed into ::fetchSpecial().
225+
*
226+
* @param string $search The dotted key name to check against our special keys.
227+
* @return array|false An array containing at least a [callable] key, and possibly [arg] and [subkey] keys. False on no match.
228+
* @see ::fetchSpecial()
229+
*/
230+
protected function specialKey($search) {
231+
$callable = false;
232+
foreach ($this->specialKeys as $key => $call) {
233+
if (strpos($search, $key) === 0) {
234+
$callable = $call;
235+
}
236+
}
237+
238+
if (!$callable) {
239+
return false;
240+
}
241+
$special = [
242+
'callable' => $callable,
243+
];
244+
245+
$keyParts = explode('.', $search, 3);
246+
if (isset($keyParts[1])) {
247+
$special['arg'] = $keyParts[1];
248+
}
249+
if (isset($keyParts[2])) {
250+
$special['subkey'] = $keyParts[2];
251+
}
252+
253+
return $special;
254+
}
255+
256+
/**
257+
* Performs necessary gymnastics to fetch "special" configs.
258+
*
259+
* There are three cases to handle:
260+
*
261+
* 1. A "deep" subkey like `Cache.default.className`. In this case,
262+
* we need to fetch `Cache::config('default')` and then return
263+
* the ['className'] from the result.
264+
*
265+
* 2. A single config array like `Cache.default. We need to fetch
266+
* `Cache::config('default')` and return the whole thing.
267+
*
268+
* 3. All configs in a module like `Cache`. In this case we need to
269+
* try calling `Cache::configured()` (if it exists), then looping
270+
* over the results using each as a key name for a separate call
271+
* to `Cache::config($name)` and accumulating all of the results
272+
* together to return.
273+
*
274+
* @param array $special An array containing at least a [callable] key and possibly [arg] and [subkey]s.
275+
* @return mixed A single scalar value, or an associative array of sub-values.
276+
*/
277+
protected function fetchSpecial($special) {
278+
if (isset($special['arg'])) {
279+
$set = call_user_func($special['callable'], $special['arg']);
280+
} else {
281+
$allConfigCallable = str_replace(
282+
'::config',
283+
'::configured',
284+
$special['callable'],
285+
$replaceCount
286+
);
287+
debug($allConfigCallable);
288+
debug($replaceCount);
289+
290+
$set = [];
291+
if($replaceCount && is_callable($allConfigCallable)) {
292+
foreach (call_user_func($allConfigCallable) as $configName) {
293+
$set[$configName] = call_user_func($special['callable'], $configName);
294+
}
295+
}
296+
}
297+
298+
if (isset($special['subkey'])) {
299+
return Hash::get($set, $special['subkey']);
300+
} else {
301+
return $set;
302+
}
303+
}
304+
143305
/**
144306
* Recursive output handler.
145307
*
@@ -195,6 +357,25 @@ protected function printVal($key, $val) {
195357
$this->out(sprintf($format, $key, $val), 1, Shell::QUIET);
196358
}
197359

360+
/**
361+
* Provides a custom helper for fetching the App's Security.salt value.
362+
*
363+
* The call to Security::salt() method requires no arguments to get the
364+
* current value but we will have split the command line request for
365+
* `Security.salt` into
366+
* `call_user_func('\Cake\Utility\Security::salt', 'salt'). That will
367+
* **set** the salt to `salt` and return "salt" as the new value, which
368+
* isn't what we want. This method exists to be the callable function,
369+
* which itself passes the proper `null` value as the argument, which
370+
* in turn will return the _actual_ salt value.
371+
*
372+
* @param string $salt Because of how this is implemented, this will always be the literal string "salt" and will be ignored.
373+
@return string The result of calling `Security::salt(null);`
374+
*/
375+
private function securitySaltHelper($salt) {
376+
return call_user_func('\Cake\Utility\Security::salt', null);
377+
}
378+
198379
/**
199380
* getOptionParser
200381
*

0 commit comments

Comments
 (0)