diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..66885cb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +## v2.0.0 + +* Dropping php 5.3 support +* architectural changes + +## v1.2.0 + +* improved overall code quality (scrutinizer suggestions) +* improved code coverage +* reworked __construct() behavior of Config classes + +## v1.1.0 + +* Moved to PSR-4, closed extension points + +## v1.0.0 + +* First stable draft release diff --git a/Config/AbstractConfig.php b/Config/AbstractConfig.php new file mode 100644 index 0000000..30405e2 --- /dev/null +++ b/Config/AbstractConfig.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Asm\Config; + +use Asm\Data\Data; +use Symfony\Component\Yaml\Yaml; + +/** + * Class AbstractConfig + * + * @package Asm\Config + * @author marc aschmann + * @codeCoverageIgnore + * @uses Asm\Data\Data + * @uses Symfony\Component\Yaml\Yaml + */ +abstract class AbstractConfig extends Data +{ + /** + * @var bool + */ + protected $filecheck = true; + + /** + * @var array + */ + protected $imports = []; + + /** + * @var array + */ + protected $default = []; + + /** + * Default constructor. + * + * @param array $param + */ + public function __construct(array $param) + { + if (isset($param['filecheck'])) { + $this->filecheck = (bool)$param['filecheck']; + } + + $this->setConfig($param['file']); + } + + /** + * Add named property to config object + * and insert config as array. + * + * @param string $name name of property + * @param string $file string $file absolute filepath/filename.ending + */ + public function addConfig($name, $file) + { + $this->set($name, $this->readConfig($file)); + } + + /** + * Read config file via YAML parser. + * + * @param string $file absolute filepath/filename.ending + * @return array config array + */ + public function readConfig($file) + { + $config = $this->readFile($file); + $config = $this->extractImports($config); + $config = $this->extractDefault($config); + $this->mergeDefault(); + + return array_replace_recursive( + $this->default, + $config + ); + } + + /** + * Add config to data storage. + * + * @param string $file absolute filepath/filename.ending + */ + public function setConfig($file) + { + $this->setByArray( + array_replace_recursive( + $this->default, + $this->readConfig($file) + ) + ); + } + + /** + * Read yaml files. + * + * @param string $file path/filename + * @return array + */ + private function readFile($file) + { + if ($this->filecheck && !is_file($file)) { + throw new \InvalidArgumentException( + 'Config::Abstract() - Given config file ' . $file . ' does not exist!' + ); + } + + return (array)Yaml::parse($file); + } + + /** + * get all import files from config, if set and remove node. + * + * @param array $config + * @return array + */ + private function extractImports(array $config) + { + if (array_key_exists('imports', $config) && 0 < count($config['imports'])) { + $this->imports = []; + foreach ($config['imports'] as $key => $import) { + if (false === empty($import['resource'])) { + $this->imports = array_replace_recursive( + $this->imports, + $this->readFile($import['resource']) + ); + } + } + + unset($config['imports']); + } + + return $config; + } + + /** + * Get default values if set and remove node from config. + * + * @param array $config + * @return array + */ + private function extractDefault($config) + { + if (array_key_exists('default', $config)) { + $this->default = $config['default']; + unset($config['default']); + } + + return $config; + } + + /** + * Prepare the defaults and replace recursively. + */ + private function mergeDefault() + { + $this->default = array_replace_recursive($this->imports, $this->default); + } +} diff --git a/Config/Config.php b/Config/Config.php index 5855995..6b34112 100644 --- a/Config/Config.php +++ b/Config/Config.php @@ -17,15 +17,6 @@ */ final class Config { - /** - * @var array - */ - private static $whitelist = [ - 'ConfigDefault', - 'ConfigEnv', - 'ConfigTimer', - ]; - /** * Get object of specific class. * @@ -37,11 +28,11 @@ final class Config */ public static function factory(array $param, $class = 'ConfigDefault') { - if (in_array($class, self::$whitelist)) { - if (false === strpos($class, 'Asm')) { - $class = __NAMESPACE__ . '\\' . $class; - } + if (false === strpos($class, 'Asm')) { + $class = __NAMESPACE__ . '\\' . $class; + } + if (class_exists($class)) { // allow config names without ending if (empty($param['file'])) { throw new \InvalidArgumentException('Config::factory() - config filename missing in param array!'); diff --git a/Config/ConfigAbstract.php b/Config/ConfigAbstract.php deleted file mode 100644 index 2474e43..0000000 --- a/Config/ConfigAbstract.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Asm\Config; - -use Asm\Data\Data; -use Symfony\Component\Yaml\Yaml; - -/** - * Class ConfigAbstract - * - * @package Asm\Config - * @author marc aschmann - * @codeCoverageIgnore - * @uses Asm\Data\Data - * @uses Symfony\Component\Yaml\Yaml - */ -abstract class ConfigAbstract extends Data -{ - /** - * @var bool - */ - protected $filecheck = true; - - /** - * Default constructor. - * - * @param array $param - */ - public function __construct(array $param) - { - if (isset($param['filecheck'])) { - $this->filecheck = (bool)$param['filecheck']; - } - - $this->setConfig($param['file']); - } - - /** - * Add named property to config object - * and insert config as array. - * - * @param string $name name of property - * @param string $file string $file absolute filepath/filename.ending - */ - public function addConfig($name, $file) - { - $this->set($name, $this->readConfig($file)); - } - - /** - * Read config file via YAML parser. - * - * @param string $file absolute filepath/filename.ending - * @return array config array - */ - public function readConfig($file) - { - if ($this->filecheck && !is_file($file)) { - throw new \InvalidArgumentException( - 'Config::Abstract() - Given config file ' . $file . ' does not exist!' - ); - } - - return (array)Yaml::parse($file); - } - - /** - * Add config to data storage. - * - * @param string $file absolute filepath/filename.ending - */ - public function setConfig($file) - { - $this->setByArray($this->readConfig($file)); - } -} diff --git a/Config/ConfigDefault.php b/Config/ConfigDefault.php index ae17554..1e886f0 100644 --- a/Config/ConfigDefault.php +++ b/Config/ConfigDefault.php @@ -15,6 +15,6 @@ * @package Asm\Config * @author marc aschmann */ -final class ConfigDefault extends ConfigAbstract implements ConfigInterface +final class ConfigDefault extends AbstractConfig implements ConfigInterface { } diff --git a/Config/ConfigEnv.php b/Config/ConfigEnv.php index afdece6..4fdf8f7 100644 --- a/Config/ConfigEnv.php +++ b/Config/ConfigEnv.php @@ -17,7 +17,7 @@ * @package Asm\Config * @author marc aschmann */ -final class ConfigEnv extends ConfigAbstract implements ConfigInterface +final class ConfigEnv extends AbstractConfig implements ConfigInterface { /** * @var string @@ -66,6 +66,11 @@ private function mergeEnvironments($param) $merged = $config->get($this->defaultEnv); } - $this->setByArray($merged); + $this->setByArray( + array_replace_recursive( + $this->default, + $merged + ) + ); } } diff --git a/Config/ConfigTimer.php b/Config/ConfigTimer.php index bb13ed5..f370f6c 100644 --- a/Config/ConfigTimer.php +++ b/Config/ConfigTimer.php @@ -15,7 +15,7 @@ * @package Asm\Config * @author marc aschmann */ -final class ConfigTimer extends ConfigAbstract implements ConfigInterface +final class ConfigTimer extends AbstractConfig implements ConfigInterface { /** * Convert config date strings to \DateTime objects or \DateIntervals. diff --git a/README.md b/README.md index c9cfd58..4283430 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,16 @@ Available types are: * ConfigEnv - provides the possibility to get an environment-merged config. Based on the currently provided env, you'll have e.g. prod -> dev merged, with prod node as a master. * ConfigTimer is a specialised for of config to provide pre-generated DateTime objects for the Timer class. +Configs now also support the "imports" syntax from symfony configs. + +``` +imports: + - { resource: defaults.yml } +``` +There is the possibility to use a "default" node in all configs, except timers, which will be used as merge-base. +Imported configs will be treated just like the "default" node and also become the base for merging. +The order of merges is import -> default -> prod -> $env. + ### Timer Provides functionality to check if there's a current holiday, has configurable "timers" to check uf e.g. your hotline should be available etc. Extensive examples can be found within both, the TestData and UnitTests :-) diff --git a/Test/BaseConfigTest.php b/Test/BaseConfigTest.php new file mode 100644 index 0000000..cd452c9 --- /dev/null +++ b/Test/BaseConfigTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Asm\Test; + +use org\bovigo\vfs\vfsStream; + +/** + * Class BaseConfigTest + * + * @package Asm\Test + * @author Marc Aschmann + */ +class BaseConfigTest extends \PHPUnit_Framework_TestCase +{ + private $root; + private $configFile; + private $configImportFile; + private $configTimerFile; + + /** + * default setup + */ + public function setUp() + { + parent::setUp(); + + $this->root = vfsStream::setup('configs'); + $this->configFile = vfsStream::newFile('default.yml')->at($this->root); + $this->configFile->setContent(TestData::getYamlConfigFile()); + + $this->configImportFile = vfsStream::newFile('testimport.yml')->at($this->root); + $this->configImportFile->setContent(TestData::getYamlImportFile()); + + $this->configTimerFile = vfsStream::newFile('testTimer.yml')->at($this->root); + $this->configTimerFile->setContent(TestData::getYamlTimerConfigFile()); + } + + /** + * @return mixed + */ + public function getTestYaml() + { + return $this->configFile->url(); + } + + /** + * @return mixed + */ + public function getTimerYaml() + { + return $this->configTimerFile->url(); + } +} diff --git a/Test/TestData.php b/Test/TestData.php index 14c977b..c576435 100644 --- a/Test/TestData.php +++ b/Test/TestData.php @@ -12,6 +12,15 @@ */ class TestData { + public static function getYamlImportFile() + { + return << */ -class ConfigDefaultTest extends \PHPUnit_Framework_TestCase +class ConfigDefaultTest extends BaseConfigTest { /** - * @covers \Asm\Config\ConfigAbstract::readConfig - * @covers \Asm\Config\ConfigAbstract::setConfig + * @covers \Asm\Config\AbstractConfig::readConfig + * @covers \Asm\Config\AbstractConfig::setConfig */ public function testFactory() { $config = Config::factory( [ - 'file' => TestData::getYamlConfigFile(), - 'filecheck' => false, + 'file' => $this->getTestYaml(), ], 'ConfigDefault' ); $this->assertInstanceOf('Asm\Config\ConfigDefault', $config); + + return $config; + } + + /** + * @depends testFactory + * @param ConfigDefault $config + */ + public function testImport(ConfigDefault $config) + { + $this->assertEquals( + [ + 'default' => 'yaddayadda', + 'my_test' => 'is testing hard' + ], + $config->get('testkey_5') + ); } } diff --git a/Tests/Config/ConfigEnvTest.php b/Tests/Config/ConfigEnvTest.php index fabb7b3..1f954b7 100644 --- a/Tests/Config/ConfigEnvTest.php +++ b/Tests/Config/ConfigEnvTest.php @@ -11,6 +11,7 @@ use Asm\Config\Config; use Asm\Config\ConfigEnv; +use Asm\Test\BaseConfigTest; use Asm\Test\TestData; /** @@ -19,7 +20,7 @@ * @package Asm\Tests\Config * @author marc aschmann */ -class ConfigEnvTest extends \PHPUnit_Framework_TestCase +class ConfigEnvTest extends BaseConfigTest { /** * @covers \Asm\Config\ConfigEnv::mergeEnvironments @@ -31,7 +32,27 @@ public function testFactoryProd() // merged environments config $config = Config::factory( [ - 'file' => TestData::getYamlConfigFile(), + 'file' => $this->getTestYaml(), + ], + 'ConfigEnv' + ); + + $this->assertInstanceOf('Asm\Config\ConfigEnv', $config); + + return $config; + } + + /** + * @covers \Asm\Config\ConfigEnv::mergeEnvironments + * @covers \Asm\Config\ConfigEnv::__construct + * @return \Asm\Config\ConfigInterface + */ + public function testFactoryProdWithoutFilecheck() + { + // merged environments config + $config = Config::factory( + [ + 'file' => $this->getTestYaml(), 'filecheck' => false, ], 'ConfigEnv' @@ -51,8 +72,7 @@ public function testFactoryEnv() { $config = Config::factory( [ - 'file' => TestData::getYamlConfigFile(), - 'filecheck' => false, + 'file' => $this->getTestYaml(), 'defaultEnv' => 'prod', 'env' => 'dev', ], @@ -72,4 +92,32 @@ public function testConfigMerge(ConfigEnv $config) { $this->assertEquals(25, $config->get('testkey_2', 0)); } + + + /** + * @depends testFactoryEnv + * @param ConfigEnv $config + */ + public function testConfigInclude(ConfigEnv $config) + { + $this->assertEquals( + [ + 'default' => 'yaddayadda', + 'my_test' => 'is testing hard' + ], + $config->get('testkey_5') + ); + } + + /** + * @depends testFactoryEnv + * @param ConfigEnv $config + */ + public function testConfigDefaultNode(ConfigEnv $config) + { + $this->assertEquals( + 'default test', + $config->get('testkey_4') + ); + } } diff --git a/Tests/Config/ConfigFactoryTest.php b/Tests/Config/ConfigFactoryTest.php index 32c7a99..ff12cbf 100644 --- a/Tests/Config/ConfigFactoryTest.php +++ b/Tests/Config/ConfigFactoryTest.php @@ -10,7 +10,7 @@ namespace Asm\Tests\Config; use Asm\Config\Config; -use Asm\Test\TestData; +use Asm\Test\BaseConfigTest; /** * Class ConfigFactoryTest @@ -18,8 +18,9 @@ * @package Asm\Tests\Config * @author marc aschmann */ -class ConfigFactoryTest extends \PHPUnit_Framework_TestCase +class ConfigFactoryTest extends BaseConfigTest { + /** * @covers \Asm\Config\Config::factory */ @@ -27,7 +28,22 @@ public function testFactory() { $config = Config::factory( [ - 'file' => TestData::getYamlConfigFile(), + 'file' => $this->getTestYaml(), + ], + 'ConfigDefault' + ); + + $this->assertInstanceOf('Asm\Config\ConfigDefault', $config); + } + + /** + * @covers \Asm\Config\Config::factory + */ + public function testFactoryWithoutFilecheck() + { + $config = Config::factory( + [ + 'file' => $this->getTestYaml(), 'filecheck' => false, ], 'ConfigDefault' @@ -44,8 +60,7 @@ public function testFactoryErrorException() { $config = Config::factory( [ - 'file' => TestData::getYamlConfigFile(), - 'filecheck' => false, + 'file' => $this->getTestYaml(), ], 'ConfigXXX' ); diff --git a/Tests/Config/ConfigTimerTest.php b/Tests/Config/ConfigTimerTest.php index 446441a..dcc2e59 100644 --- a/Tests/Config/ConfigTimerTest.php +++ b/Tests/Config/ConfigTimerTest.php @@ -10,15 +10,22 @@ namespace Asm\Tests\Config; use Asm\Config\Config; -use Asm\Test\TestData; +use Asm\Test\BaseConfigTest; -class ConfigTimerTest extends \PHPUnit_Framework_TestCase +/** + * Class ConfigTimerTest + * + * @package Asm\Tests\Config + * @author Marc Aschmann + */ +class ConfigTimerTest extends BaseConfigTest { /** * @covers \Asm\Config\ConfigTimer::setConfig * @covers \Asm\Config\ConfigTimer::handleTimers * @covers \Asm\Config\ConfigTimer::handleHolidays * @covers \Asm\Config\ConfigTimer::handleGeneralHolidays + * @covers \Asm\Test\BaseConfigTest::getTimerYaml * @return ConfigTimer $config * @throws \ErrorException */ @@ -26,8 +33,7 @@ public function testFactory() { $config = Config::factory( [ - 'file' => TestData::getYamlTimerConfigFile(), - 'filecheck' => false, + 'file' => $this->getTimerYaml(), ], 'ConfigTimer' ); diff --git a/Tests/Timer/TimerTest.php b/Tests/Timer/TimerTest.php index d57029f..ce423cd 100644 --- a/Tests/Timer/TimerTest.php +++ b/Tests/Timer/TimerTest.php @@ -75,6 +75,7 @@ public function testIsTimerActive(Timer $timer) * @covers \Asm\Timer\Timer::isHoliday * @covers \Asm\Timer\Timer::getHoliday * @covers \Asm\Timer\Timer::checkHoliday + * @covers \Asm\Timer\Timer::convertDate * @param Timer $timer */ public function testIsHoliday(Timer $timer) diff --git a/composer.json b/composer.json index 4a02d7c..2c89d34 100644 --- a/composer.json +++ b/composer.json @@ -1,26 +1,27 @@ { - "name": "asm/php-utilities", - "description": "php tools with data container, collection objects and timer", - "keywords": [], - "homepage": "https://github.com/maschmann/php-utilities", - "type": "library", - "license": "MIT", - "authors": [ - { - "name": "Marc Aschmann", - "email": "maschmann@gmail.com" - } - ], - "require": { - "php": ">=5.3.9", - "symfony/yaml": "2.*" - }, - "require-dev": { - "phpunit\/phpunit": "~4.0" - }, - "autoload": { - "psr-4": { - "Asm\\": "" - } + "name": "asm/php-utilities", + "description": "php tools with data container, collection objects, yaml config loader and timer", + "keywords": [], + "homepage": "https://github.com/maschmann/php-utilities", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Marc Aschmann", + "email": "maschmann@gmail.com" } + ], + "require": { + "php": ">=5.3.9", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "phpunit\/phpunit": "~4.0", + "mikey179/vfsStream": "~1.0" + }, + "autoload": { + "psr-4": { + "Asm\\": "" + } + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2eea44e..90a4d2f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,6 +23,7 @@ ./Tests ./vendor + ./example.php