diff --git a/CHANGELOG.md b/CHANGELOG.md index ad1bcd7..3bdf28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. -## 1.1.2 - TBD +## 1.2.0 - 2019-12-27 ### Added +- [#23](https://github.com/zendframework/zend-config-aggregator/pull/23) adds the ability to specify the file mode for the generated cache file, when generating a cache file. The mode can be provided via the `Zend\ConfigAggregator\ConfigAggregator::CACHE_FILEMODE` configuration option. Modes should be expressed as octal values (e.g., `0600`). + - [#21](https://github.com/zendframework/zend-config-aggregator/pull/21) adds support for PHP 7.3. ### Changed diff --git a/composer.json b/composer.json index 60da0af..eb1396f 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ }, "require-dev": { "malukenho/docheader": "^0.1.5", - "mikey179/vfsStream": "^1.6", "phpunit/phpunit": "^5.7.21 || ^6.3", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6 || ^3.0", @@ -47,8 +46,8 @@ }, "extra": { "branch-alias": { - "dev-master": "1.1.x-dev", - "dev-develop": "1.2.x-dev" + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" } }, "scripts": { diff --git a/composer.lock b/composer.lock index 0b4f3db..04741d4 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dab247ec85530a683128f8e9320e9bdd", + "content-hash": "28f8e3b05e43567701044b10c234d6ff", "packages": [ { "name": "zendframework/zend-stdlib", @@ -189,52 +189,6 @@ ], "time": "2017-05-03T05:22:55+00:00" }, - { - "name": "mikey179/vfsStream", - "version": "v1.6.5", - "source": { - "type": "git", - "url": "https://github.com/mikey179/vfsStream.git", - "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", - "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "org\\bovigo\\vfs\\": "src/main/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Frank Kleine", - "homepage": "http://frankkleine.de/", - "role": "Developer" - } - ], - "description": "Virtual file system to mock the real file system in unit tests.", - "homepage": "http://vfs.bovigo.org/", - "time": "2017-08-01T08:02:14+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.7.0", @@ -365,18 +319,18 @@ "authors": [ { "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "role": "Developer", + "email": "arne@blankerts.de" }, { "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpeople.de" }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpunit.de" } ], "description": "Library for handling version information and constraints", @@ -642,8 +596,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "role": "lead", + "email": "sb@sebastian-bergmann.de" } ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", @@ -690,8 +644,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "role": "lead", + "email": "sb@sebastian-bergmann.de" } ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", @@ -781,8 +735,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "role": "lead", + "email": "sb@sebastian-bergmann.de" } ], "description": "Utility class for timing", @@ -912,8 +866,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "The PHP Unit Testing framework.", @@ -982,6 +936,7 @@ "mock", "xunit" ], + "abandoned": true, "time": "2017-08-03T14:08:16+00:00" }, { diff --git a/docs/book/caching.md b/docs/book/caching.md index 842ff52..4e6fec4 100644 --- a/docs/book/caching.md +++ b/docs/book/caching.md @@ -44,3 +44,28 @@ providers. Because of that it is very fast, but after it is enabled, you cannot make any changes to configuration without clearing the cache. **Caching should be used only in a production environment**, and your deployment process should clear the cache. + +You can control the permissions used when creating the cache file by passing +the [file mode](http://php.net/manual/en/function.chmod.php) in the +`ConfigAggregator::CACHE_FILEMODE` configuration. Use this if your config +contains sensitive information such as database passwords: + +```php +use Zend\ConfigAggregator\ArrayProvider; +use Zend\ConfigAggregator\ConfigAggregator; +use Zend\ConfigAggregator\PhpFileProvider; + +$aggregator = new ConfigAggregator( + [ + new ArrayProvider([ + ConfigAggregator::ENABLE_CACHE => true, + ConfigAggregator::CACHE_FILEMODE => 0600 // only owner can read and write + ]), + new PhpFileProvider('*.global.php'), + ], + 'data/config-cache.php' +); +``` + +Note that mode is an octal value. To ensure the expected operation, you need +to prefix the file mode with a zero (e.g. `0644`). diff --git a/src/ConfigAggregator.php b/src/ConfigAggregator.php index 20e53dd..961932c 100644 --- a/src/ConfigAggregator.php +++ b/src/ConfigAggregator.php @@ -14,10 +14,15 @@ use Zend\Stdlib\ArrayUtils\MergeReplaceKeyInterface; use function array_key_exists; +use function chmod; use function class_exists; use function date; +use function fclose; use function file_exists; -use function file_put_contents; +use function flock; +use function fopen; +use function fputs; +use function ftruncate; use function get_class; use function gettype; use function is_array; @@ -35,6 +40,8 @@ class ConfigAggregator { const ENABLE_CACHE = 'config_cache_enabled'; + const CACHE_FILEMODE = 'config_cache_filemode'; + const CACHE_TEMPLATE = <<< 'EOT' cacheFile = $dir . '/cache'; + $this->defaultFile = $dir . '/default'; + $this->lockFile = sys_get_temp_dir() . '/' . basename($this->cacheFile) . '.tmp'; + } + + protected function tearDown() + { + @unlink($this->cacheFile); + @unlink($this->defaultFile); + @unlink($this->lockFile); + @rmdir(dirname($this->cacheFile)); + } + public function testConfigAggregatorRisesExceptionIfProviderClassDoesNotExist() { $this->expectException(InvalidConfigProviderException::class); @@ -67,19 +91,84 @@ function () { public function testConfigAggregatorCanCacheConfig() { - vfsStream::setup(__FUNCTION__); - $cacheFile = vfsStream::url(__FUNCTION__) . '/expressive_config_loader'; new ConfigAggregator([ function () { return ['foo' => 'bar', ConfigAggregator::ENABLE_CACHE => true]; } - ], $cacheFile); - $this->assertTrue(file_exists($cacheFile)); - $cachedConfig = include $cacheFile; + ], $this->cacheFile); + $this->assertTrue(file_exists($this->cacheFile)); + $cachedConfig = include $this->cacheFile; $this->assertInternalType('array', $cachedConfig); $this->assertEquals(['foo' => 'bar', ConfigAggregator::ENABLE_CACHE => true], $cachedConfig); } + public function testConfigAggregatorSetsDefaultModeOnCache() + { + touch($this->defaultFile); + new ConfigAggregator([ + function () { + return ['foo' => 'bar', ConfigAggregator::ENABLE_CACHE => true]; + } + ], $this->cacheFile); + $this->assertEquals(fileperms($this->defaultFile), fileperms($this->cacheFile)); + } + + public function testConfigAggregatorSetsModeOnCache() + { + new ConfigAggregator([ + function () { + return [ + 'foo' => 'bar', + ConfigAggregator::ENABLE_CACHE => true, + ConfigAggregator::CACHE_FILEMODE => 0600 + ]; + } + ], $this->cacheFile); + $this->assertEquals(0600, fileperms($this->cacheFile) & 0777); + } + + public function testConfigAggregatorSetsHandlesUnwritableCache() + { + chmod(dirname($this->cacheFile), 0400); + + $foo = function () { + new ConfigAggregator([ + function () { + return ['foo' => 'bar', ConfigAggregator::ENABLE_CACHE => true]; + } + ], $this->cacheFile); + }; + @$foo(); // suppress warning + + $errors = error_get_last(); + $this->assertNotNull($errors); + $this->assertFalse(file_exists($this->cacheFile)); + } + + public function testConfigAggregatorRespectsCacheLock() + { + $expected = [ + 'cache' => 'locked', + ConfigAggregator::ENABLE_CACHE => true, + ]; + + $fh = fopen($this->lockFile, 'c'); + flock($fh, LOCK_EX); + file_put_contents($this->cacheFile, '<' . '?php return ' . var_export($expected, true) . ';'); + + $method = new ReflectionMethod(ConfigAggregator::class, 'cacheConfig'); + $method->setAccessible(true); + $method->invoke( + new ConfigAggregator(), + ['foo' => 'bar', ConfigAggregator::ENABLE_CACHE => true], + $this->cacheFile + ); + flock($fh, LOCK_UN); + fclose($fh); + + $this->assertEquals($expected, require $this->cacheFile); + } + public function testConfigAggregatorCanLoadConfigFromCache() { $expected = [ @@ -87,13 +176,9 @@ public function testConfigAggregatorCanLoadConfigFromCache() ConfigAggregator::ENABLE_CACHE => true, ]; - $root = vfsStream::setup(__FUNCTION__); - vfsStream::newFile('expressive_config_loader') - ->at($root) - ->setContent('<' . '?php return ' . var_export($expected, true) . ';'); - $cacheFile = vfsStream::url(__FUNCTION__ . '/expressive_config_loader'); + file_put_contents($this->cacheFile, '<' . '?php return ' . var_export($expected, true) . ';'); - $aggregator = new ConfigAggregator([], $cacheFile); + $aggregator = new ConfigAggregator([], $this->cacheFile); $mergedConfig = $aggregator->getMergedConfig(); $this->assertInternalType('array', $mergedConfig);