diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5741bf6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,47 @@ +language: php + +sudo: false + +php: + - 5.6 + +# Environment Variables to set +env: + global: + # Contains a $GITHUB_TOKEN env var for use with composer to avoid API limits. + - secure: "JPIIdecDmF2AsgH3b5QWYzr2TunB4tpIBl0/64EGsWZ+5A85Co3NN+eSshgWI5vOjaaJji/S2rKwMRvV9h/YceTL/UH5D2xd/HobRpHAaJSyjp5cplQawokzR/+PrikjmbwTZdeiIaHpUMCqbQvV2+Jq+Vx5vD28+hya1yTdQsk=" + + +# Cache the composer directories, only allowed if using the container based setup +# which depends on setting sudo to false +cache: + directories: + - $HOME/.composer/cache + +# Branches to be built or not +branches: + # Blacklist these branches + except: + - gh-pages + +before_install: + - composer self-update + - mkdir -p build/logs + +install: + - composer config -g github-oauth.github.com $GITHUB_TOKEN + - composer install --no-interaction + +before_script: + - phpenv rehash + - vendor/bin/phpcs --config-set installed_paths vendor/loadsys/loadsys_codesniffer,vendor/cakephp/cakephp-codesniffer + +script: + - vendor/bin/phpcs -np --extensions=php --standard=Loadsys ./src ./tests + - vendor/bin/phpunit --coverage-clover=build/logs/clover.xml + +after_script: + - php vendor/bin/coveralls -v + +notifications: + email: false diff --git a/README.md b/README.md index 3c92a1f..3d7dd63 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ # CakePHP-ConfigReadShell [![Latest Version](https://img.shields.io/github/release/loadsys/CakePHP-ConfigReadShell.svg?style=flat-square)](https://github.com/loadsys/CakePHP-ConfigReadShell/releases) +[![Build Status](https://travis-ci.org/loadsys/CakePHP-ConfigReadShell.svg?branch=master)](https://travis-ci.org/loadsys/CakePHP-ConfigReadShell) +[![Coverage Status](https://coveralls.io/repos/loadsys/CakePHP-ConfigReadShell/badge.svg?branch=master)](https://coveralls.io/r/loadsys/CakePHP-ConfigReadShell?branch=master) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) [![Total Downloads](https://img.shields.io/packagist/dt/loadsys/cakephp-config-read.svg?style=flat-square)](https://packagist.org/packages/loadsys/cakephp-config-read) A CakePHP plugin that provides a Shell to read an app's Configure vars from the command line. - -* This is the Cake 3.x version of the plugin, which exists on the `master` branch and is tracked by the `3.*` semver. -* For the Cake 2.x version of this plugin, please use the repo's `cake-2.x` branch. (semver `2.*`) -* For the Cake 1.3 version, use the `cake-1.3` branch. (semver `1.*`) **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. +* This is the Cake 3.x version of the plugin, which exists on the `master` branch and is tracked by the `~3.0` semver. +* For the Cake 2.x version of this plugin, please use the repo's `cake-2.x` branch. (semver `~2.0`) +* 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. ## Requirements * CakePHP 3.0.0+ -* PHP 5.4.19+ +* PHP 5.6+ ## Installation @@ -50,6 +51,62 @@ $ ./bin/cake ConfigRead -b Key.Name KEY_NAME='foo' ``` +## Gotchas + +### "Consumed" Configure Vars + +CakePHP 3 by default "consumes" some of its configs so as not to confused developers. [`Configure::consume()`](http://book.cakephp.org/3.0/en/development/configuration.html#Cake\Core\Configure::consume) removes the configuration key from Configure, making it unavailable to the rest of the app. At the [time of this writing](https://github.com/cakephp/app/blob/a0f2c4/config/bootstrap.php#L136,L141), it does this for the following keys/classes: + +* Cache/Cache +* Datasources/ConnectionManager +* EmailTransport/Email +* Email/Email +* Log/Log +* Security.salt/Security::salt() + +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).) + +There are two possible workarounds: + +1. Use the `ConsoleShell` instead. For example: + + ```shell + $ echo 'use Cake\Datasource\ConnectionManager; foreach(ConnectionManager::config("default") as $k => $v) { echo "$k=" . escapeshellarg($v) . PHP_EOL; } exit;' | bin/cake Console -q + className='Cake\Database\Connection' + driver='Cake\Database\Driver\Mysql' + persistent='' + host='localhost' + username='my_app' + password='secret' + database='my_app' + encoding='utf8' + timezone='UTC' + cacheMetadata='1' + quoteIdentifiers='' + name='default' + ``` + + 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. + +2. Edit your `config/bootstrap.php` to use `Configure::read()` instead of `Configure::consume()`. + + ```diff + -Cache::config(Configure::consume('Cache')); + -ConnectionManager::config(Configure::consume('Datasources')); + -Email::configTransport(Configure::consume('EmailTransport')); + -Email::config(Configure::consume('Email')); + -Log::config(Configure::consume('Log')); + -Security::salt(Configure::consume('Security.salt')); + +Cache::config(Configure::read('Cache')); + +ConnectionManager::config(Configure::read('Datasources')); + +Email::configTransport(Configure::read('EmailTransport')); + +Email::config(Configure::read('Email')); + +Log::config(Configure::read('Log')); + +Security::salt(Configure::read('Security.salt')); + ``` + + 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. + ## Contributing diff --git a/composer.json b/composer.json index 41c436a..058d9e3 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require-dev": { "phpunit/phpunit": "~4.1", - "squizlabs/php_codesniffer": "~2.0", + "loadsys/loadsys_codesniffer": "~3.0", "satooshi/php-coveralls": "dev-master" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 075ce11..ef6816d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,29 +1,29 @@ - - - ./tests/TestCase - - + + + ./tests/TestCase + + - - - - ./vendor/ - ./vendor/ + + + + ./vendor/ + ./vendor/ - ./tests/ - ./tests/ - - + ./tests/ + ./tests/ + + diff --git a/src/Shell/ConfigReadShell.php b/src/Shell/ConfigReadShell.php index 3e945ff..a737e70 100644 --- a/src/Shell/ConfigReadShell.php +++ b/src/Shell/ConfigReadShell.php @@ -9,7 +9,6 @@ use Cake\Console\Shell; use Cake\Core\Configure; - /** * ConfigReadShell class. * @@ -156,8 +155,8 @@ protected function printVal($key, $val) { * * Processing command line options. * - * @access public - * @return void + * @access public + * @return CosnsoleOptionParser * @codeCoverageIgnore */ public function getOptionParser() { diff --git a/tests/TestCase/Shell/ConfigReadShellTest.php b/tests/TestCase/Shell/ConfigReadShellTest.php index a352e1f..0ab13d7 100644 --- a/tests/TestCase/Shell/ConfigReadShellTest.php +++ b/tests/TestCase/Shell/ConfigReadShellTest.php @@ -1,23 +1,21 @@ output = []; - $this->Shell = $this->initSUT(); - } - - /** - * tearDown method - * - * @return void - */ - public function tearDown() - { - unset($this->io, $this->Shell); - $this->output = []; - - parent::tearDown(); - } +class ConfigReadShellTest extends TestCase { + + /** + * Fixtures used in this test case + * + * @var array + */ + public $fixtures = [ + ]; + + /** + * Acts as an accumulator for output produced by the Shell. + * + * @var array + * @see ::initSUIT() + */ + public $output = [ + ]; + + /** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + + $this->output = []; + $this->Shell = $this->initSUT(); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown() { + unset($this->io, $this->Shell); + $this->output = []; + + parent::tearDown(); + } /** * Helper for accumulating I/O output generated by the Shell. * - * @param string $s The output string being printed. + * @param string $s The output string being printed. * @see ::initSUT() */ - public function outputCollector($s) - { + public function outputCollector($s) { $this->output[] = $s; } @@ -96,21 +90,20 @@ public function outputCollector($s) * Helper for determing the subject class to initialize for testing. * * Methodology: - * - Take the name of this testing class: `SomeObjectTest` - * - If there exists a `TestSomeObject` class (presumed to extend - * SomeObject to expose private/protected methods for testing) - * then return that. - * - Otherwise, guess the namespace of the subject class by - * removing `*\Test\TestCase\*\*Test` from this testing classes - * name and use that. - * - As a side-effect, set a local class property with the - * non-namespaced `SomeObject` name for future reference in tests. + * - Take the name of this testing class: `SomeObjectTest` + * - If there exists a `TestSomeObject` class (presumed to extend + * SomeObject to expose private/protected methods for testing) + * then return that. + * - Otherwise, guess the namespace of the subject class by + * removing `*\Test\TestCase\*\*Test` from this testing classes + * name and use that. + * - As a side-effect, set a local class property with the + * non-namespaced `SomeObject` name for future reference in tests. * - * @return string The fully-namespaced class name to instantiate. + * @return string The fully-namespaced class name to instantiate. * @see ::initSUT() */ - protected function getSUTClassName() - { + protected function getSUTClassName() { $testCaseClass = '\\' . get_class($this); // -> ConfigRead\Test\TestCase\Shell\ConfigReadShellTest @@ -151,14 +144,14 @@ protected function getSUTClassName() * Typically called in ::setUp() or at the beginning * of a test method (if additional mocked methods are necessary.) * - * @return mixed A partially mocked copy of the Shell matching the test class's name. + * @return mixed A partially mocked copy of the Shell matching the test class's name. */ protected function initSUT($additionalMocks = []) { $defaultMocks = [ 'help', //'in', 'out', 'hr', 'error', 'err', '_stop', 'initialize', '_run', 'clear', ]; - $this->io = $this->getMock('\Cake\Console\ConsoleIo', [], [], '', false); + $this->io = $this->getMock('\Cake\Console\ConsoleIo', [], [], '', false); $this->io->expects($this->any()) ->method('out') ->with($this->anything()) @@ -185,70 +178,66 @@ protected function initSUT($additionalMocks = []) { return $shell; } - /** - * Confirm that startup() engages help output when flag is present. - * - * @return void - */ - public function testStartupHelp() - { + /** + * Confirm that startup() engages help output when flag is present. + * + * @return void + */ + public function testStartupHelp() { $this->Shell->params = ['h' => true]; - $this->Shell->expects($this->once()) - ->method('help') - ->will($this->returnValue('canary')); - $this->assertEquals( - 'canary', - $this->Shell->startup(), - 'Shell should return help() when -h is passed.' - ); - } - - /** - * Confirm that startup() engages bash output mode when -b flag is present. - * - * @return void - */ - public function testStartupBashModeFlag() - { + $this->Shell->expects($this->once()) + ->method('help') + ->will($this->returnValue('canary')); + $this->assertEquals( + 'canary', + $this->Shell->startup(), + 'Shell should return help() when -h is passed.' + ); + } + + /** + * Confirm that startup() engages bash output mode when -b flag is present. + * + * @return void + */ + public function testStartupBashModeFlag() { $this->Shell->params = ['b' => 'canary']; - $this->Shell->startup(); - - $this->assertEquals( - ['canary'], - $this->Shell->args, - 'Shell args should contain the value mistakenly captured by the -b option.' - ); - $this->assertTrue( - $this->Shell->formatBash, - 'Bash output formatting should be enabled by the presence of the -b option.' - ); - } - - /** - * Confirm that startup() engages bash output mode when multiple args are present. - * - * @return void - */ - public function testStartupBashModeMultiArgs() - { + $this->Shell->startup(); + + $this->assertEquals( + ['canary'], + $this->Shell->args, + 'Shell args should contain the value mistakenly captured by the -b option.' + ); + $this->assertTrue( + $this->Shell->formatBash, + 'Bash output formatting should be enabled by the presence of the -b option.' + ); + } + + /** + * Confirm that startup() engages bash output mode when multiple args are present. + * + * @return void + */ + public function testStartupBashModeMultiArgs() { $this->Shell->args = ['debug', 'Datasources.default.host']; - $this->Shell->startup(); - - $this->assertTrue( - $this->Shell->formatBash, - 'Bash output formatting should be enabled by the presence of multiple arguments.' - ); - } - - /** - * test main(). - * - * @return void - */ - public function testMain() - { + $this->Shell->startup(); + + $this->assertTrue( + $this->Shell->formatBash, + 'Bash output formatting should be enabled by the presence of multiple arguments.' + ); + } + + /** + * test main(). + * + * @return void + */ + public function testMain() { $expected = [ 'key' => 'val', 'debug' => true, @@ -258,11 +247,11 @@ public function testMain() ], ]; - $shell = $this->initSUT(['fetchVal', 'iterateOnKey']); + $shell = $this->initSUT(['fetchVal', 'iterateOnKey']); $shell->args = array_keys($expected); - $i = 0; - foreach ($expected as $k => $v) { + $i = 0; + foreach ($expected as $k => $v) { $shell->expects($this->at($i++)) ->method('fetchVal') ->with($k) @@ -272,22 +261,21 @@ public function testMain() ->with($k, $v); } - $shell->startup(); - $shell->main(); - - $this->assertTrue( - $shell->formatBash, - 'Bash output should be engaged automatically by presence of multiple command line args.' - ); - } - - /** - * test main(), including associated protected methods. - * - * @return void - */ - public function testMainIntegrationStyle() - { + $shell->startup(); + $shell->main(); + + $this->assertTrue( + $shell->formatBash, + 'Bash output should be engaged automatically by presence of multiple command line args.' + ); + } + + /** + * test main(), including associated protected methods. + * + * @return void + */ + public function testMainIntegrationStyle() { $configure = [ 'key' => 'val', 'debug' => true, @@ -305,17 +293,17 @@ public function testMainIntegrationStyle() Configure::write($configure); $this->Shell->args = array_keys($configure); - $this->Shell->startup(); - $this->Shell->main(); - - $this->assertEquals( - $expected, - $this->output, - 'The output produced from running the shell on the given arguments should match our expected result.' - ); - $this->assertTrue( - $this->Shell->formatBash, - 'Bash output should be engaged automatically by presence of multiple command line args.' - ); - } + $this->Shell->startup(); + $this->Shell->main(); + + $this->assertEquals( + $expected, + $this->output, + 'The output produced from running the shell on the given arguments should match our expected result.' + ); + $this->assertTrue( + $this->Shell->formatBash, + 'Bash output should be engaged automatically by presence of multiple command line args.' + ); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index bcd2369..5b67a7b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,14 +20,14 @@ use Cake\Datasource\ConnectionManager; $findRoot = function ($root) { - do { - $lastRoot = $root; - $root = dirname($root); - if (is_dir($root . '/vendor/cakephp/cakephp')) { - return $root; - } - } while ($root !== $lastRoot); - throw new Exception('Cannot find the root of the application, unable to run tests'); + do { + $lastRoot = $root; + $root = dirname($root); + if (is_dir($root . '/vendor/cakephp/cakephp')) { + return $root; + } + } while ($root !== $lastRoot); + throw new Exception('Cannot find the root of the application, unable to run tests'); }; $root = $findRoot(__FILE__); unset($findRoot); @@ -42,18 +42,18 @@ Configure::write('debug', true); Configure::write('App', [ - 'namespace' => 'App', - 'paths' => [ - 'plugins' => [ROOT . 'Plugin' . DS], - 'templates' => [ROOT . 'App' . DS . 'Template' . DS] - ] + 'namespace' => 'App', + 'paths' => [ + 'plugins' => [ROOT . 'Plugin' . DS], + 'templates' => [ROOT . 'App' . DS . 'Template' . DS] + ] ]); if (!getenv('db_dsn')) { - putenv('db_dsn=sqlite:///:memory:'); + putenv('db_dsn=sqlite:///:memory:'); } ConnectionManager::config('test', ['url' => getenv('db_dsn')]); Plugin::load('ConfigReadShell', [ - 'path' => dirname(dirname(__FILE__)) . DS, + 'path' => dirname(dirname(__FILE__)) . DS, ]);