diff --git a/bin/config-generator.php b/bin/config-generator.php new file mode 100644 index 0000000..d765a22 --- /dev/null +++ b/bin/config-generator.php @@ -0,0 +1,3 @@ +#!/usr/bin/env php +=7.1.0" + "php": ">=7.1.0", + "magicalex/write-ini-file": "v1.2.3" }, "require-dev": { "phpunit/phpunit": "6.4.*" @@ -29,4 +30,4 @@ "Gt\\Config\\Test\\": "./test/unit" } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 02edc48..8bc1f6c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,56 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "3458ad0f96e533420cf160c1fecd0662", - "packages": [], + "content-hash": "5e72bbb2aeae88f5efdd5c244bdc26a2", + "packages": [ + { + "name": "magicalex/write-ini-file", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/Magicalex/WriteiniFile.git", + "reference": "d77f20c9a4ba7e713541539c74d0f14d92283bb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Magicalex/WriteiniFile/zipball/d77f20c9a4ba7e713541539c74d0f14d92283bb6", + "reference": "d77f20c9a4ba7e713541539c74d0f14d92283bb6", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "5.7.*", + "satooshi/php-coveralls": "1.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "WriteiniFile\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0" + ], + "authors": [ + { + "name": "Magicalex", + "email": "magicalex@mondedie.fr" + } + ], + "description": "Write-ini-file php library for create, remove, erase, add, and update ini file", + "homepage": "https://github.com/Magicalex/WriteIniFile", + "keywords": [ + "file", + "ini", + "write", + "write_ini_file" + ], + "time": "2017-03-21T02:45:50+00:00" + } + ], "packages-dev": [ { "name": "doctrine/instantiator", diff --git a/src/Config.php b/src/Config.php index dc44b3c..40603f4 100644 --- a/src/Config.php +++ b/src/Config.php @@ -2,69 +2,12 @@ namespace Gt\Config; class Config { - const INI_EXTENSION = "ini"; - const FILE_OVERRIDE_DEV = "dev"; - const FILE_OVERRIDE_DEPLOY = "deploy"; - const FILE_OVERRIDE_PRODUCTION = "production"; - const FILE_OVERRIDE_ORDER = [ - self::FILE_OVERRIDE_DEV, - self::FILE_OVERRIDE_DEPLOY, - self::FILE_OVERRIDE_PRODUCTION, - ]; + /** @var ConfigSection[] */ + protected $sectionList = []; - protected $projectRoot; - protected $kvp = []; - protected $delimeter; - - public function __construct( - string $projectRoot = "", - string $filename = "config", - string $delimeter = "." - ) { - $this->projectRoot = $projectRoot; - $iniConfig = $this->loadIni($projectRoot, $filename); - $this->kvp = $iniConfig; - $this->delimeter = $delimeter; - } - - public function mergeDefaults( - string $defaultDirectoryPath = null, - string $filename = "config.default", - bool $override = false - ):void { - if(is_null($defaultDirectoryPath)) { - $defaultDirectoryPath = $this->projectRoot; - } - - $defaults = $this->loadIni( - $defaultDirectoryPath, - $filename - ); - - foreach($defaults as $section => $data) { - foreach($data as $key => $value) { - if(!isset($this->kvp[$section])) { - $this->kvp[$section] = []; - } - - if(!isset($this->kvp[$section][$key])) { - $this->kvp[$section][$key] = $value; - } - - if($override) { - $this->kvp[$section][$key] = $value; - } - } - } - } - - public function loadOverrides():void { - foreach(self::FILE_OVERRIDE_ORDER as $override) { - $this->mergeDefaults( - $this->projectRoot, - "config.$override", - true - ); + public function __construct(ConfigSection...$sectionList) { + foreach($sectionList as $section) { + $this->sectionList[$section->getName()] = $section; } } @@ -78,15 +21,11 @@ public function get(string $name):?string { } public function getSection(string $sectionName):?ConfigSection { - if(!isset($this->kvp[$sectionName])) { - return null; - } - - return new ConfigSection($this->kvp[$sectionName]); + return $this->sectionList[$sectionName] ?? null; } protected function getSectionValue(string $name):?string { - $parts = explode($this->delimeter, $name, 2); + $parts = explode(".", $name, 2); $section = $this->getSection($parts[0]); if(is_null($section) @@ -94,22 +33,34 @@ protected function getSectionValue(string $name):?string { return null; } - return $section[$parts[1]]; + return $section->get($parts[1]); } - protected function loadIni(string $directoryPath, string $filename):array { - $kvp = []; - - $iniPath = $directoryPath - . DIRECTORY_SEPARATOR - . $filename - . "." - . self::INI_EXTENSION; + public function getSectionNames():array { + $names = []; - if(is_file($iniPath)) { - $kvp = parse_ini_file($iniPath, true); + foreach($this->sectionList as $section) { + $names []= $section->getName(); } - return $kvp; + return $names; + } + + public function merge(Config $configToOverride):void { + foreach($configToOverride->getSectionNames() as $sectionName) { + if(isset($this->sectionList[$sectionName])) { + foreach($configToOverride->getSection($sectionName) + as $key => $value) { + if(empty($this->sectionList[$sectionName][$key])) { + $this->sectionList[$sectionName][$key] = $value; + } + } + } + else { + $this->sectionList[$sectionName] = $configToOverride->getSection( + $sectionName + ); + } + } } } \ No newline at end of file diff --git a/src/ConfigFactory.php b/src/ConfigFactory.php new file mode 100644 index 0000000..552a738 --- /dev/null +++ b/src/ConfigFactory.php @@ -0,0 +1,63 @@ +merge($previousConfig); + } + + $previousConfig = $config; + } + + return $config; + } + + public static function createFromPathName(string $pathName):?Config { + if(!is_file($pathName)) { + return null; + } + + $parser = new IniParser($pathName); + return $parser->parse(); + } + + public static function createFromArray(array $input):Config { + + } +} \ No newline at end of file diff --git a/src/ConfigSection.php b/src/ConfigSection.php index 2241678..46ca16b 100644 --- a/src/ConfigSection.php +++ b/src/ConfigSection.php @@ -5,10 +5,12 @@ use Iterator; class ConfigSection implements ArrayAccess, Iterator { + protected $name; protected $data; protected $iteratorIndex; - public function __construct(array $data) { + public function __construct(string $name, array $data) { + $this->name = $name; $this->data = $data; } @@ -19,7 +21,7 @@ public function get(string $key):?string { /** * @link http://php.net/manual/en/iterator.current.php */ - public function current():array { + public function current():string { $key = $this->getIteratorKey(); return $this->data[$key]; } @@ -71,14 +73,18 @@ public function offsetGet($offset):?string { * @link http://php.net/manual/en/arrayaccess.offsetset.php */ public function offsetSet($offset, $value):void { - throw new ImmutableObjectMutationException(); + $this->data[$offset] = $value; } /** * @link http://php.net/manual/en/arrayaccess.offsetunset.php */ public function offsetUnset($offset):void { - throw new ImmutableObjectMutationException(); + unset($this->data[$offset]); + } + + public function getName():string { + return $this->name; } protected function getIteratorKey():?string { diff --git a/src/FileWriter.php b/src/FileWriter.php new file mode 100644 index 0000000..d59e29c --- /dev/null +++ b/src/FileWriter.php @@ -0,0 +1,39 @@ +config = $config; + } + + public function writeIni(string $filePath):void { + if(!is_dir(dirname($filePath))) { + mkdir(dirname($filePath), 0775, true); + } + + $writer = new WriteiniFile($filePath); + + foreach($this->config->getSectionNames() as $sectionName) { + foreach($this->config->getSection($sectionName) + as $key => $value) { + $writer->add([ + $sectionName => [ + $key => $value, + ] + ]); + } + } + + try { + $writer->write(); + } + catch(Exception $exception) { + throw new ConfigException("Unable to write to file: $filePath"); + } + } +} \ No newline at end of file diff --git a/src/ImmutableObjectMutationException.php b/src/ImmutableObjectMutationException.php deleted file mode 100644 index b444c29..0000000 --- a/src/ImmutableObjectMutationException.php +++ /dev/null @@ -1,4 +0,0 @@ -pathName = $pathName; + } + + public function parse():Config { + $data = parse_ini_file( + $this->pathName, + true, + INI_SCANNER_TYPED + ); + + $configSectionList = []; + foreach($data as $sectionName => $sectionData) { + $configSectionList []= new ConfigSection( + $sectionName, + $sectionData + ); + } + + return new Config(...$configSectionList); + } +} \ No newline at end of file diff --git a/test/unit/ConfigFactoryTest.php b/test/unit/ConfigFactoryTest.php new file mode 100644 index 0000000..10e45bc --- /dev/null +++ b/test/unit/ConfigFactoryTest.php @@ -0,0 +1,55 @@ +tmp, + "config.ini", + ]); + $filePathDefault = implode(DIRECTORY_SEPARATOR, [ + $this->tmp, + "config.default.ini", + ]); + $filePathDev = implode(DIRECTORY_SEPARATOR, [ + $this->tmp, + "config.dev.ini", + ]); + $filePathProduction = implode(DIRECTORY_SEPARATOR, [ + $this->tmp, + "config.production.ini", + ]); + + file_put_contents($filePathDefault, Helper::INI_DEFAULT); + file_put_contents($filePath, Helper::INI_SIMPLE); + file_put_contents($filePathDev, Helper::INI_OVERRIDE_DEV); + file_put_contents($filePathProduction, Helper::INI_OVERRIDE_PROD); + + $config = ConfigFactory::createForProject($this->tmp); + self::assertEquals("ExampleApp", $config->get("app.namespace")); + self::assertEquals("dev789override", $config->get("block1.value.nested")); + self::assertEquals("this appears by default", $config->get("block1.value.existsByDefault")); + self::assertEquals("my.production.database", $config->get("database.host")); + self::assertEquals("example", $config->get("database.schema")); + } + + public function testCreateFromPathName():void { + $filePath = implode(DIRECTORY_SEPARATOR, [ + $this->tmp, + "config.ini", + ]); + + file_put_contents($filePath, Helper::INI_SIMPLE); + + $config = ConfigFactory::createFromPathName($filePath); + + $sectionNames = $config->getSectionNames(); + self::assertContains("app", $sectionNames); + self::assertContains("block1", $sectionNames); + self::assertContains("database", $sectionNames); + self::assertNotContains("extra", $sectionNames); + } +} \ No newline at end of file diff --git a/test/unit/ConfigSectionTest.php b/test/unit/ConfigSectionTest.php deleted file mode 100644 index b93fabf..0000000 --- a/test/unit/ConfigSectionTest.php +++ /dev/null @@ -1,64 +0,0 @@ - [ - "namespace" => "ExampleApp", - ], - "database" => [ - "host" => "localhost", - "schema" => "example", - "port" => "3306", - ], - ]; - - public function testIterator():void { - $section = new ConfigSection(self::DATA); - - foreach($section as $key => $value) { - self::assertArrayHasKey($key, self::DATA); - self::assertEquals($value, self::DATA[$key]); - } - } - - public function testOffsetExists():void { - $section = new ConfigSection(self::DATA); - - foreach(self::DATA as $key => $value) { - self::assertArrayHasKey($key, $section); - } - } - - public function testOffsetNotExists():void { - $section = new ConfigSection(self::DATA); - for($i = 0; $i < 10; $i++) { - self::assertArrayNotHasKey(uniqid(), $section); - } - } - - public function testOffsetGet():void { - $section = new ConfigSection(self::DATA["app"]); - - foreach(self::DATA["app"] as $key => $value) { - self::assertArrayHasKey($key, $section); - self::assertEquals($value, $section[$key]); - } - } - - public function testOffsetSet():void { - self::expectException(ImmutableObjectMutationException::class); - $section = new ConfigSection(self::DATA); - $section["example"] = "something"; - } - - public function testOffsetUnset():void { - self::expectException(ImmutableObjectMutationException::class); - $section = new ConfigSection(self::DATA); - unset($section["example"]); - } -} \ No newline at end of file diff --git a/test/unit/ConfigTest.php b/test/unit/ConfigTest.php index ca8500f..2bb99f2 100644 --- a/test/unit/ConfigTest.php +++ b/test/unit/ConfigTest.php @@ -2,22 +2,10 @@ namespace Gt\Config\Test; use Gt\Config\Config; +use Gt\Config\ConfigSection; use Gt\Config\Test\Helper\Helper; -use PHPUnit\Framework\TestCase; class ConfigTest extends TestCase { - protected $tmp; - - public function setUp() { - Helper::removeTmpDir(); - $this->tmp = Helper::getTmpDir(); - mkdir($this->tmp, 0775, true); - } - - public function tearDown() { - Helper::removeTmpDir(); - } - public function testNotPresentByDefault() { $config = new Config(); $this->assertNull($config->get(uniqid())); @@ -39,75 +27,29 @@ public function testGet() { self::assertEquals($value, $config->get($key)); } - public function testLoadFromIni() { - $filePath = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.ini", - ]); - file_put_contents($filePath, Helper::INI_SIMPLE); - $config = new Config($this->tmp); - self::assertEquals("ExampleApp", $config->get("app.namespace")); - self::assertNull($config->get("app.nothing")); - self::assertNull($config->get("app")); - } - - public function testLoadWithDefaults() { - $filePath = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.ini", - ]); - file_put_contents($filePath, Helper::INI_SIMPLE); + public function testLoadSection() { + $section = self::createMock(ConfigSection::class); + $section->method("getName") + ->willReturn("test"); + $section->method("get") + ->willReturn("value123"); - $filePathDefault = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.default.ini", - ]); - file_put_contents($filePathDefault, Helper::INI_DEFAULT); - - $config = new Config($this->tmp); - $config->mergeDefaults(); - - self::assertEquals("ExampleApp", $config->get("app.namespace")); - self::assertEquals("789", $config->get("block1.value.nested")); - self::assertEquals("this appears by default", $config->get("block1.value.existsByDefault")); + $config = new Config($section); + self::assertEquals("value123", $config->get("test.example")); } public function testEnvOverride() { putenv("app_namespace=ExampleAppChanged"); putenv("app_nothing=Something"); - $filePath = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.ini", - ]); - file_put_contents($filePath, Helper::INI_SIMPLE); - $config = new Config($this->tmp); + $section = self::createMock(ConfigSection::class); + $section->method("getName") + ->willReturn("app"); + $section->method("get") + ->willReturn("exampleApp"); + + $config = new Config($section); self::assertEquals("ExampleAppChanged", $config->get("app.namespace")); self::assertEquals("Something", $config->get("app.nothing")); } - - public function testFileOverride() { - $filePath = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.ini", - ]); - file_put_contents($filePath, Helper::INI_SIMPLE); - $filePathDev = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.dev.ini", - ]); - file_put_contents($filePathDev, Helper::INI_OVERRIDE_DEV); - $filePathProd = implode(DIRECTORY_SEPARATOR, [ - $this->tmp, - "config.production.ini", - ]); - file_put_contents($filePathProd, Helper::INI_OVERRIDE_PROD); - - $config = new Config($this->tmp); - $config->loadOverrides(); - self::assertEquals("dev789override", $config->get("block1.value.nested")); - self::assertEquals("my.production.database", $config->get("database.host")); - self::assertEquals("secret-key-only-on-production", $config->get("exampleapi.key")); - self::assertEquals("example", $config->get("database.schema")); - } } \ No newline at end of file diff --git a/test/unit/FileWriterTest.php b/test/unit/FileWriterTest.php new file mode 100644 index 0000000..9a789ca --- /dev/null +++ b/test/unit/FileWriterTest.php @@ -0,0 +1,53 @@ + [ + "firstKeyOfOne" => "value", + "secondKeyOfOne" => "another-value", + ], + "two" => [ + "firstKeyOfTwo" => 12345, + "secondKeyOfTwo" => true, + ], + "three" => [ + "fk-of-3" => "this?has!weird charac~'#ters", + "sk-of-3" => false, + ] + ]; + $sectionOne = new ConfigSection("one", $sectionValues["one"]); + $sectionTwo = new ConfigSection("two", $sectionValues["two"]); + $sectionThree = new ConfigSection("three", $sectionValues["three"]); + + $config = self::createMock(Config::class); + $config->method("getSectionNames") + ->willReturn(array_keys($sectionValues)); + $config->method("getSection") + ->willReturn( + $sectionOne, + $sectionTwo, + $sectionThree + ); + + $writer = new FileWriter($config); + $tmpFilePath = Helper::getTmpDir("output.ini"); + $writer->writeIni($tmpFilePath); + + $parsedData = parse_ini_file($tmpFilePath, true); + foreach($sectionValues as $sectionName => $section) { + foreach($section as $key => $value) { + self::assertEquals( + $value, + $parsedData[$sectionName][$key] + ); + } + } + } +} \ No newline at end of file diff --git a/test/unit/Helper/Helper.php b/test/unit/Helper/Helper.php index f90993c..ab7dadd 100644 --- a/test/unit/Helper/Helper.php +++ b/test/unit/Helper/Helper.php @@ -55,11 +55,13 @@ public static function getBaseTmpDir() { ]); } - public static function getTmpDir() { - return implode(DIRECTORY_SEPARATOR, [ + public static function getTmpDir(string...$parts) { + $parts = array_merge([ self::getBaseTmpDir(), uniqid(), - ]); + ], $parts); + + return implode(DIRECTORY_SEPARATOR, $parts); } public static function removeTmpDir() { diff --git a/test/unit/TestCase.php b/test/unit/TestCase.php new file mode 100644 index 0000000..2f83836 --- /dev/null +++ b/test/unit/TestCase.php @@ -0,0 +1,19 @@ +tmp = Helper::getTmpDir(); + mkdir($this->tmp, 0775, true); + } + + public function tearDown() { + Helper::removeTmpDir(); + } +} \ No newline at end of file