├── .gitignore ├── tests ├── Dflydev │ └── DotAccessConfiguration │ │ ├── fixtures │ │ ├── yamlFileConfigurationBuilderTest-testBuilder-import-level1.yml │ │ ├── yamlFileConfigurationBuilderTest-testBuilder-import-level0.yml │ │ └── yamlFileConfigurationBuilderTest-testBuilder.yml │ │ ├── ConfigurationFactoryTest.php │ │ ├── PlaceholderResolverFactoryTest.php │ │ ├── YamlConfigurationBuilderTest.php │ │ ├── ConfigurationDataSourceTest.php │ │ ├── YamlFileConfigurationBuilderTest.php │ │ ├── AbstractConfigurationBuilderTest.php │ │ └── ConfigurationTest.php └── bootstrap.php ├── .travis.yml ├── phpcs.xml.dist ├── phpunit.xml.dist ├── src └── Dflydev │ └── DotAccessConfiguration │ ├── ConfigurationBuilderInterface.php │ ├── ConfigurationFactoryInterface.php │ ├── ConfigurationFactory.php │ ├── Configuration.php │ ├── PlaceholderResolverFactoryInterface.php │ ├── PlaceholderResolverFactory.php │ ├── AbstractPlaceholderResolverFactory.php │ ├── YamlConfigurationBuilder.php │ ├── ConfigurationDataSource.php │ ├── YamlFileConfigurationBuilder.php │ ├── ConfigurationInterface.php │ ├── AbstractConfigurationBuilder.php │ └── AbstractConfiguration.php ├── LICENSE ├── .github ├── CONTRIBUTING.md └── workflows │ └── tests.yml ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .phpunit.result.cache 2 | .phpcs-cache 3 | composer.lock 4 | vendor 5 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/fixtures/yamlFileConfigurationBuilderTest-testBuilder-import-level1.yml: -------------------------------------------------------------------------------- 1 | a1: 2 | b1: 3 | c1: C1 -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/fixtures/yamlFileConfigurationBuilderTest-testBuilder-import-level0.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - yamlFileConfigurationBuilderTest-testBuilder-import-level1.yml 3 | 4 | a0: 5 | b0: 6 | c0: C0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | 8 | before_script: 9 | - wget -nc http://getcomposer.org/composer.phar 10 | - php composer.phar install --dev 11 | 12 | script: phpunit --coverage-text --verbose 13 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/fixtures/yamlFileConfigurationBuilderTest-testBuilder.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - yamlFileConfigurationBuilderTest-testBuilder-import-level0.yml 3 | - /tmp/testing-this-file-should-not-exist.yml 4 | 5 | a: 6 | b: 7 | c: C -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | src 14 | tests 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src/Dflydev/DotAccessConfiguration/ 6 | 7 | 8 | 9 | 10 | ./tests/Dflydev/DotAccessConfiguration 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/ConfigurationBuilderInterface.php: -------------------------------------------------------------------------------- 1 | importRaw($config); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/ConfigurationFactoryTest.php: -------------------------------------------------------------------------------- 1 | create(); 22 | 23 | $this->assertNotNull($configuration); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/PlaceholderResolverFactoryInterface.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(\Dflydev\DotAccessConfiguration\Configuration::class)->getMock(); 21 | $placeholderResolverFactory = new PlaceholderResolverFactory(); 22 | $placeholderResolver = $placeholderResolverFactory->create($configuration); 23 | 24 | $this->assertNotNull($placeholderResolver); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dragonfly Development Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/AbstractPlaceholderResolverFactory.php: -------------------------------------------------------------------------------- 1 | createInternal($configuration, new ConfigurationDataSource($configuration)); 24 | } 25 | 26 | /** 27 | * Internal create 28 | * 29 | * @param ConfigurationInterface $configuration 30 | * @param DataSourceInterface $dataSource 31 | * 32 | * @return PlaceholderResolverInterface 33 | */ 34 | abstract protected function createInternal(ConfigurationInterface $configuration, DataSourceInterface $dataSource); 35 | } 36 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/YamlConfigurationBuilderTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The Symfony2 YAML library is not available'); 22 | } 23 | 24 | $configurationBuilder = new YamlConfigurationBuilder(); 25 | $configuration = $configurationBuilder->build(); 26 | 27 | $this->assertNotNull($configuration); 28 | } 29 | 30 | public function testBuildWithData() 31 | { 32 | if (!class_exists('Symfony\Component\Yaml\Yaml')) { 33 | $this->markTestSkipped('The Symfony2 YAML library is not available'); 34 | } 35 | 36 | $configurationBuilder = new YamlConfigurationBuilder('foo: bar'); 37 | $configuration = $configurationBuilder->build(); 38 | 39 | $this->assertNotNull($configuration); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/ConfigurationDataSourceTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(\Dflydev\DotAccessConfiguration\Configuration::class)->getMock(); 21 | 22 | $configuration 23 | ->expects($this->any()) 24 | ->method('getRaw') 25 | ->will($this->returnValueMap(array( 26 | array('foo', 'bar'), 27 | array('foo', null, true), 28 | array('foo', 'bar', false), 29 | ))) 30 | ; 31 | 32 | $dataSource = new ConfigurationDataSource($configuration); 33 | 34 | $this->assertEquals('bar', $dataSource->get('foo')); 35 | $this->assertTrue($dataSource->exists('foo')); 36 | $this->assertEquals('bar', $dataSource->get('foo', false)); 37 | $this->assertTrue($dataSource->exists('foo', false)); 38 | $this->assertNull($dataSource->get('foo', true)); 39 | $this->assertFalse($dataSource->exists('foo', true)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/YamlFileConfigurationBuilderTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The Symfony2 YAML library is not available'); 23 | } 24 | 25 | $configurationBuilder = new YamlFileConfigurationBuilder(array(__DIR__ . '/fixtures/yamlFileConfigurationBuilderTest-testBuilder.yml')); 26 | 27 | $configuration = $configurationBuilder->build(); 28 | 29 | $this->assertEquals('C', $configuration->get('a.b.c')); 30 | $this->assertEquals('C0', $configuration->get('a0.b0.c0')); 31 | $this->assertEquals('C1', $configuration->get('a1.b1.c1')); 32 | $this->assertEquals(array( 33 | 'yamlFileConfigurationBuilderTest-testBuilder-import-level0.yml', 34 | '/tmp/testing-this-file-should-not-exist.yml', 35 | 'yamlFileConfigurationBuilderTest-testBuilder-import-level1.yml', 36 | ), $configuration->get('imports')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. We accept contributions via Pull Requests on [GitHub](https://github.com/dflydev/dflydev-dot-access-configuration). 4 | 5 | ## Pull Requests 6 | 7 | - **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - The easiest way to apply the conventions is to run `./vendor/bin/phpcbf`. 8 | 9 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 10 | 11 | - **Tests must pass** - All automated tests, including things like code style checks, but be passing before we'll consider merging the change. 12 | 13 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 14 | 15 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 16 | 17 | - **Create feature branches** - Don't ask us to pull from your default branch. 18 | 19 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 20 | 21 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. 22 | 23 | 24 | ## Running Tests 25 | 26 | ``` bash 27 | $ composer test 28 | ``` 29 | 30 | 31 | **Happy coding**! 32 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/YamlConfigurationBuilder.php: -------------------------------------------------------------------------------- 1 | input = $input; 34 | } 35 | 36 | /** 37 | * {@inheritdocs} 38 | */ 39 | public function internalBuild(ConfigurationInterface $configuration) 40 | { 41 | if (null !== $this->input) { 42 | try { 43 | $yml = Yaml::parse($this->input, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); 44 | } catch (\Exception $e) { 45 | throw new InvalidArgumentException($e->getMessage(), 0, $e); 46 | } 47 | if (is_string($yml)) { 48 | throw(new \InvalidArgumentException('Yaml could not be parsed, parser detected a string.')); 49 | } 50 | $configuration->importRaw($yml); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflydev/dot-access-configuration", 3 | "type": "library", 4 | "description": "Given a deep data structure representing a configuration, access configuration by dot notation.", 5 | "homepage": "https://github.com/dflydev/dflydev-dot-access-configuration", 6 | "keywords": ["config", "configuration"], 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Dragonfly Development Inc.", 11 | "email": "info@dflydev.com", 12 | "homepage": "http://dflydev.com" 13 | }, 14 | { 15 | "name": "Beau Simensen", 16 | "email": "beau@dflydev.com", 17 | "homepage": "http://beausimensen.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.4", 22 | "dflydev/dot-access-data": "^3", 23 | "dflydev/placeholder-resolver": "1.*" 24 | }, 25 | "require-dev": { 26 | "symfony/yaml": "^5.4 || ^6.0", 27 | "phpunit/phpunit": "^9.3", 28 | "squizlabs/php_codesniffer": "^3.5" 29 | }, 30 | "suggest": { 31 | "symfony/yaml": "Required for using the YAML Configuration Builders" 32 | }, 33 | "autoload": { 34 | "psr-0": { 35 | "Dflydev\\DotAccessConfiguration": "src" 36 | } 37 | }, 38 | "scripts": { 39 | "style:fix": "@php ./vendor/bin/phpcsf", 40 | "style:check": "@php ./vendor/bin/phpcs", 41 | "test" : "@php ./vendor/bin/phpunit" 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-master": "2.0-dev" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: ~ 5 | pull_request: ~ 6 | 7 | jobs: 8 | 9 | phpunit: 10 | name: PHPUnit on ${{ matrix.php }} ${{ matrix.composer-flags }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | php: ['7.4', '8.0', '8.1'] 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 1 21 | 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php }} 26 | extensions: curl 27 | coverage: pcov 28 | 29 | - name: Install Composer dependencies 30 | uses: ramsey/composer-install@v2 31 | with: 32 | composer-options: "--no-progress --prefer-dist --optimize-autoloader" 33 | 34 | - name: Configure matchers 35 | uses: mheap/phpunit-matcher-action@v1 36 | 37 | - name: PHPUnit 38 | run: composer run test -- --coverage-text --teamcity 39 | 40 | phpcs: 41 | name: phpcs 42 | runs-on: ubuntu-latest 43 | 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v3 47 | with: 48 | fetch-depth: 1 49 | 50 | - name: Set up PHP 51 | uses: shivammathur/setup-php@v2 52 | with: 53 | php-version: 8.1 54 | tools: cs2pr 55 | 56 | - name: Install Composer dependencies 57 | uses: ramsey/composer-install@v2 58 | with: 59 | composer-options: "--no-progress --prefer-dist --optimize-autoloader" 60 | 61 | - name: phpcs 62 | run: composer run style:check -- -q --report=checkstyle | cs2pr 63 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/ConfigurationDataSource.php: -------------------------------------------------------------------------------- 1 | setConfiguration($configuration); 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function exists($key, $system = false) 34 | { 35 | if ($system) { 36 | return false; 37 | } 38 | 39 | return null !== $this->configuration->getRaw($key); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function get($key, $system = false) 46 | { 47 | if ($system) { 48 | return null; 49 | } 50 | 51 | return $this->configuration->getRaw($key); 52 | } 53 | 54 | /** 55 | * Set Configuration 56 | * 57 | * @param ConfigurationInterface $configuration 58 | * 59 | * @return ConfigurationDataSource 60 | */ 61 | public function setConfiguration(ConfigurationInterface $configuration) 62 | { 63 | $this->configuration = $configuration; 64 | 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/YamlFileConfigurationBuilder.php: -------------------------------------------------------------------------------- 1 | yamlConfigurationFilenames = $yamlConfigurationFilenames; 34 | } 35 | 36 | /** 37 | * {@inheritdocs} 38 | */ 39 | public function internalBuild(ConfigurationInterface $configuration) 40 | { 41 | $config = array(); 42 | $imports = array(); 43 | foreach ($this->yamlConfigurationFilenames as $yamlConfigurationFilename) { 44 | if (file_exists($yamlConfigurationFilename)) { 45 | $config = DotAccessDataUtil::mergeAssocArray($config, Yaml::parse(file_get_contents($yamlConfigurationFilename))); 46 | if (isset($config['imports'])) { 47 | foreach ((array) $config['imports'] as $file) { 48 | if (0 === strpos($file, '/')) { 49 | // Absolute path 50 | $imports[] = $file; 51 | } else { 52 | if ($realpath = realpath(dirname($yamlConfigurationFilename) . '/' . $file)) { 53 | $imports[] = $realpath; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | if ($imports) { 62 | $importsBuilder = new static($imports); 63 | 64 | // We want to reconfigure the imports builder to have the 65 | // same basic configuration as this instance. 66 | $this->reconfigure($importsBuilder); 67 | 68 | $configuration->import($importsBuilder->build()); 69 | 70 | $internalImports = $configuration->get('imports'); 71 | } else { 72 | $internalImports = null; 73 | } 74 | 75 | $configuration->importRaw($config); 76 | 77 | if ($internalImports) { 78 | foreach ((array) $internalImports as $import) { 79 | $configuration->append('imports', $import); 80 | } 81 | } 82 | 83 | return $configuration; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/ConfigurationInterface.php: -------------------------------------------------------------------------------- 1 | configurationFactory = $configurationFactory; 29 | 30 | return $this; 31 | } 32 | 33 | /** 34 | * Configuration Factory 35 | * 36 | * @return ConfigurationFactoryInterface 37 | */ 38 | protected function configurationFactory() 39 | { 40 | if (null === $this->configurationFactory) { 41 | $this->configurationFactory = new ConfigurationFactory(); 42 | } 43 | 44 | return $this->configurationFactory; 45 | } 46 | 47 | /** 48 | * {@inheritdocs} 49 | */ 50 | public function build() 51 | { 52 | $configuration = $this->configurationFactory()->create(); 53 | 54 | if (null !== $this->placeholderResolverFactory) { 55 | $placeholderResolver = $this->placeholderResolverFactory->create($configuration); 56 | $configuration->setPlaceholderResolver($placeholderResolver); 57 | } 58 | 59 | $this->internalBuild($configuration); 60 | 61 | return $configuration; 62 | } 63 | 64 | /** 65 | * Set Placeholder Resolver Factory 66 | * 67 | * @param PlaceholderResolverFactoryInterface $placeholderResolverFactory 68 | */ 69 | public function setPlaceholderResolverFactory(PlaceholderResolverFactoryInterface $placeholderResolverFactory) 70 | { 71 | $this->placeholderResolverFactory = $placeholderResolverFactory; 72 | } 73 | 74 | /** 75 | * Called to reconfigure the specified Configuration Builder to be similar to this instance 76 | * 77 | * @param AbstractConfigurationBuilder $configurationBuilder 78 | */ 79 | public function reconfigure(AbstractConfigurationBuilder $configurationBuilder) 80 | { 81 | if (null !== $this->placeholderResolverFactory) { 82 | $configurationBuilder->setPlaceholderResolverFactory($this->placeholderResolverFactory); 83 | } 84 | 85 | $configurationBuilder->setConfigurationFactory($this->configurationFactory()); 86 | } 87 | 88 | /** 89 | * Internal build 90 | * 91 | * @param ConfigurationInterface $configuration 92 | */ 93 | abstract protected function internalBuild(ConfigurationInterface $configuration); 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dot Access Configuration 2 | 3 | Given a deep data structure representing a configuration, access 4 | configuration by dot notation. 5 | 6 | This library combines [dflydev/dot-access-data](https://github.com/dflydev/dflydev-dot-access-data) 7 | and [dflydev/placeholder-resolver](https://github.com/dflydev/dflydev-placeholder-resolver) 8 | to provide a complete configuration solution. 9 | 10 | ## Requirements 11 | 12 | * PHP (5.3+) 13 | * [dflydev/dot-access-data](https://github.com/dflydev/dflydev-dot-access-data) (^3) 14 | * [dflydev/placeholder-resolver](https://github.com/dflydev/dflydev-placeholder-resolver) (1.x) 15 | * [symfony/yaml](https://github.com/symfony/Yaml) (>2,<2.2) *(suggested)* 16 | 17 | ## Usage 18 | 19 | Generally one will use an implementation of `ConfigurationBuilderInterface` 20 | to build `ConfigurationInterface` instances. For example, to build a Configuration 21 | out of a YAML file, one would use the `YamlFileConfigurationBuilder`: 22 | 23 | ```php 24 | use Dflydev\DotAccessConfiguration\YamlFileConfigurationBuilder; 25 | 26 | $configurationBuilder = new YamlFileConfigurationBuilder('config/config.yml'); 27 | $configuration = $configurationBuilder->build(); 28 | ``` 29 | 30 | Once created, the Configuration instance behaves similarly to a Data 31 | instance from [dflydev/dot-access-data](https://github.com/dflydev/dflydev-dot-access-data). 32 | 33 | ```php 34 | $configuration->set('a.b.c', 'ABC'); 35 | $configuration->get('a.b.c'); 36 | $configuration->set('a.b.e', array('A', 'B', 'C')); 37 | $configuration->append('a.b.e', 'D'); 38 | ``` 39 | 40 | ## Custom Configurations 41 | 42 | Configuration Builders use Configuration Factories and Placeholder Resolver 43 | Factories behind the scenes in order to build a working configuration. 44 | 45 | Under normal circumstances one should not need to do anything with the 46 | Placeholder Resolver Factory. However, one may wish to extend the 47 | default `Configuration` class or use an entirely different implementation 48 | altogether. 49 | 50 | In order to build instances of custom `ConfigurationInterface` implementations 51 | with the standard builders, one would need to implement 52 | `ConfigurationFactoryInterface` and inject it into any 53 | `ConfigurationBuilderInterface`. 54 | 55 | If a Configuration is declared as follows: 56 | ```php 57 | namespace MyProject; 58 | 59 | use Dflydev\DotAccessConfiguration\Configuration; 60 | 61 | class MyConf extends Configuration 62 | { 63 | public function someSpecialMethod() 64 | { 65 | // Whatever you want here. 66 | } 67 | } 68 | ``` 69 | 70 | Create the following factory: 71 | ```php 72 | namespace MyProject; 73 | 74 | use Dflydev\DotAccessConfiguration\ConfigurationFactoryInterface; 75 | 76 | class MyConfFactory implements ConfigurationFactoryInterface 77 | { 78 | /** 79 | * {@inheritdocs} 80 | */ 81 | public function create() 82 | { 83 | return new MyConf; 84 | } 85 | } 86 | ``` 87 | 88 | To use the factory with any builder, inject it as follows: 89 | ```php 90 | use Dflydev\DotAccessConfiguration\YamlFileConfigurationBuilder; 91 | use MyProject\MyConfFactory; 92 | 93 | $configurationBuilder = new YamlFileConfigurationBuilder('config/config.yml'); 94 | 95 | // Inject your custom Configuration Factory 96 | $configurationBuilder->setConfigurationFactory(new MyConfFactory); 97 | 98 | // Will now build instances of MyConfFactory instead of 99 | // the standard Configuration implementation. 100 | $configuration = $configurationBuilder->build(); 101 | ``` 102 | 103 | ## License 104 | 105 | This library is licensed under the New BSD License - see the LICENSE file 106 | for details. 107 | 108 | ## Community 109 | 110 | If you have questions or want to help out, join us in the 111 | [#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net. 112 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/AbstractConfigurationBuilderTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(\Dflydev\PlaceholderResolver\PlaceholderResolverInterface::class)->getMock(); 21 | 22 | $placeholderResolverFactory = $this->getMockBuilder(\Dflydev\DotAccessConfiguration\PlaceholderResolverFactoryInterface::class)->getMock(); 23 | $placeholderResolverFactory 24 | ->expects($this->once()) 25 | ->method('create') 26 | ->will($this->returnValue($placeholderResolver)) 27 | ; 28 | 29 | $configurationBuilder = $this->getMockForAbstractClass(\Dflydev\DotAccessConfiguration\AbstractConfigurationBuilder::class); 30 | $configurationBuilder 31 | ->expects($this->once()) 32 | ->method('internalBuild'); 33 | 34 | $configurationBuilder->setPlaceholderResolverFactory($placeholderResolverFactory); 35 | $configurationBuilder->build(); 36 | } 37 | 38 | public function testReconfigure() 39 | { 40 | $configuration000 = $this->getMockBuilder(\Dflydev\DotAccessConfiguration\ConfigurationInterface::class)->getMock(); 41 | 42 | $configuration000 43 | ->expects($this->exactly(2)) 44 | ->method('get') 45 | ->with($this->equalTo('foo')) 46 | ->will($this->returnValue('FOO')) 47 | ; 48 | 49 | $configuration001 = $this->getMockBuilder(\Dflydev\DotAccessConfiguration\ConfigurationInterface::class)->getMock(); 50 | 51 | $configuration001 52 | ->expects($this->exactly(2)) 53 | ->method('get') 54 | ->with($this->equalTo('bar')) 55 | ->will($this->returnValue('BAR')) 56 | ; 57 | 58 | $placeholderResolver = $this->getMockBuilder(\Dflydev\PlaceholderResolver\PlaceholderResolverInterface::class)->getMock(); 59 | 60 | $placeholderResolverFactory = $this->getMockBuilder(\Dflydev\DotAccessConfiguration\PlaceholderResolverFactoryInterface::class)->getMock(); 61 | $placeholderResolverFactory 62 | ->expects($this->exactly(2)) 63 | ->method('create') 64 | ->will($this->returnValue($placeholderResolver)) 65 | ; 66 | 67 | $configurationFactory = $this->getMockBuilder(\Dflydev\DotAccessConfiguration\ConfigurationFactoryInterface::class)->getMock(); 68 | $configurationFactory 69 | ->expects($this->exactly(2)) 70 | ->method('create') 71 | ->will($this->onConsecutiveCalls($configuration000, $configuration001)); 72 | ; 73 | 74 | $configurationBuilder = $this->getMockForAbstractClass('Dflydev\DotAccessConfiguration\AbstractConfigurationBuilder'); 75 | 76 | $configurationBuilder->setPlaceholderResolverFactory($placeholderResolverFactory); 77 | $configurationBuilder->setConfigurationFactory($configurationFactory); 78 | 79 | $reconfiguredConfigurationBuilder = $this->getMockForAbstractClass('Dflydev\DotAccessConfiguration\AbstractConfigurationBuilder'); 80 | $configurationBuilder->reconfigure($reconfiguredConfigurationBuilder); 81 | 82 | $configurationTest000 = $configurationBuilder->build(); 83 | $configurationTest001 = $reconfiguredConfigurationBuilder->build(); 84 | 85 | $this->assertEquals('FOO', $configuration000->get('foo')); 86 | $this->assertEquals('FOO', $configurationTest000->get('foo')); 87 | $this->assertEquals('BAR', $configuration001->get('bar')); 88 | $this->assertEquals('BAR', $configurationTest001->get('bar')); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Dflydev/DotAccessConfiguration/AbstractConfiguration.php: -------------------------------------------------------------------------------- 1 | data()->get($key); 33 | } catch (MissingPathException $e) { 34 | return null; 35 | } 36 | } 37 | 38 | /** 39 | * {@inheritdocs} 40 | */ 41 | public function get($key) 42 | { 43 | $value = $this->getRaw($key); 44 | if (is_object($value)) { 45 | return $value; 46 | } 47 | $this->resolveValues($value); 48 | 49 | return $value; 50 | } 51 | 52 | /** 53 | * {@inheritdocs} 54 | */ 55 | public function set($key, $value = null) 56 | { 57 | $this->exportIsDirty = true; 58 | 59 | return $this->data()->set($key, $value); 60 | } 61 | 62 | /** 63 | * {@inheritdocs} 64 | */ 65 | public function append($key, $value = null) 66 | { 67 | $this->exportIsDirty = true; 68 | 69 | return $this->data()->append($key, $value); 70 | } 71 | 72 | /** 73 | * {@inheritdocs} 74 | */ 75 | public function exportRaw() 76 | { 77 | return $this->data()->export(); 78 | } 79 | 80 | /** 81 | * {@inheritdocs} 82 | */ 83 | public function export() 84 | { 85 | if ($this->exportIsDirty) { 86 | $this->resolvedExport = $this->data()->export(); 87 | $this->resolveValues($this->resolvedExport); 88 | $this->exportIsDirty = false; 89 | } 90 | 91 | return $this->resolvedExport; 92 | } 93 | 94 | /** 95 | * {@inheritdocs} 96 | */ 97 | public function exportData() 98 | { 99 | return new Data($this->export()); 100 | } 101 | 102 | /** 103 | * {@inheritdocs} 104 | */ 105 | public function importRaw($imported = null, $clobber = true) 106 | { 107 | $this->exportIsDirty = true; 108 | 109 | if (null !== $imported) { 110 | $this->data()->import($imported, $clobber); 111 | } 112 | } 113 | 114 | /** 115 | * {@inheritdocs} 116 | */ 117 | public function import(ConfigurationInterface $imported, $clobber = true) 118 | { 119 | return $this->importRaw($imported->exportRaw(), $clobber); 120 | } 121 | 122 | /** 123 | * {@inheritdocs} 124 | */ 125 | public function resolve($value = null) 126 | { 127 | if (null === $value) { 128 | return null; 129 | } 130 | 131 | return $this->placeholderResolver()->resolvePlaceholder($value); 132 | } 133 | 134 | /** 135 | * {@inheritdocs} 136 | */ 137 | public function setPlaceholderResolver(PlaceholderResolverInterface $placeholderResolver) 138 | { 139 | $this->placeholderResolver = $placeholderResolver; 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * Resolve values 146 | * 147 | * For objects, do nothing. For strings, resolve placeholder. 148 | * For arrays, call resolveValues() on each item. 149 | * 150 | * @param mixed $input 151 | */ 152 | protected function resolveValues(&$input = null) 153 | { 154 | if (is_array($input)) { 155 | foreach ($input as $idx => $value) { 156 | $this->resolveValues($value); 157 | $input[$idx] = $value; 158 | } 159 | } else { 160 | if (!is_object($input)) { 161 | $input = $this->placeholderResolver()->resolvePlaceholder($input); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * Data 168 | * 169 | * @return Data 170 | */ 171 | protected function data() 172 | { 173 | if (null === $this->data) { 174 | $this->data = new Data(); 175 | } 176 | 177 | return $this->data; 178 | } 179 | 180 | /** 181 | * Placeholder Resolver 182 | * 183 | * @return PlaceholderResolverInterface 184 | */ 185 | protected function placeholderResolver() 186 | { 187 | if (null === $this->placeholderResolver) { 188 | $this->placeholderResolver = new RegexPlaceholderResolver(new ConfigurationDataSource($this), '%', '%'); 189 | } 190 | 191 | return $this->placeholderResolver; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /tests/Dflydev/DotAccessConfiguration/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | key = $key; 26 | } 27 | }; 28 | 29 | return array( 30 | 'a' => array( 31 | 'b' => array( 32 | 'c' => 'ABC', 33 | ), 34 | ), 35 | 'abc' => '%a.b.c%', 36 | 'abcd' => '%a.b.c.d%', 37 | 'some' => array( 38 | 'object' => new $configurationTestObjectClass('some.object'), 39 | 'other' => array( 40 | 'object' => new $configurationTestObjectClass('some.other.object'), 41 | ), 42 | ), 43 | 'object' => new $configurationTestObjectClass('object'), 44 | 'an_array' => array('hello'), 45 | ); 46 | } 47 | 48 | protected function runBasicTests($configuration) 49 | { 50 | $this->assertEquals('ABC', $configuration->get('a.b.c'), 'Direct access by dot notation'); 51 | $this->assertEquals('ABC', $configuration->get('abc'), 'Resolved access'); 52 | $this->assertEquals('%a.b.c.d%', $configuration->get('abcd'), 'Unresolved access'); 53 | $this->assertEquals('object', $configuration->get('object')->key); 54 | $this->assertEquals('some.object', $configuration->get('some.object')->key); 55 | $this->assertEquals('some.other.object', $configuration->get('some.other.object')->key); 56 | $this->assertEquals(array('hello'), $configuration->get('an_array')); 57 | $this->assertEquals('This is ABC', $configuration->resolve('This is %a.b.c%')); 58 | $this->assertNull($configuration->resolve()); 59 | } 60 | 61 | public function testGet() 62 | { 63 | $configuration = new Configuration($this->getTestData()); 64 | 65 | $this->runBasicTests($configuration); 66 | } 67 | 68 | public function testAppend() 69 | { 70 | $configuration = new Configuration($this->getTestData()); 71 | 72 | $configuration->append('a.b.c', 'abc'); 73 | $configuration->append('an_array', 'world'); 74 | 75 | $this->assertEquals(array('ABC', 'abc'), $configuration->get('a.b.c')); 76 | $this->assertEquals(array('hello', 'world'), $configuration->get('an_array')); 77 | } 78 | 79 | public function testExportRaw() 80 | { 81 | $configuration = new Configuration($this->getTestData()); 82 | 83 | // Start with "known" expected value. 84 | $expected = $this->getTestData(); 85 | 86 | $this->assertEquals($expected, $configuration->exportRaw()); 87 | 88 | // Simulate change on an object to ensure that objects 89 | // are being handled correctly. 90 | $expected['object']->key = 'object (modified)'; 91 | 92 | // Make the same change in the object that the 93 | // configuration is managing. 94 | $configuration->get('object')->key = 'object (modified)'; 95 | 96 | $this->assertEquals($expected, $configuration->exportRaw()); 97 | } 98 | 99 | public function testExport() 100 | { 101 | $configuration = new Configuration($this->getTestData()); 102 | 103 | // Start with "known" expected value. 104 | $expected = $this->getTestData(); 105 | 106 | // We have one replacement that is expected to happen. 107 | // It should be represented in the export as the 108 | // resolved value! 109 | $expected['abc'] = 'ABC'; 110 | 111 | $this->assertEquals($expected, $configuration->export()); 112 | 113 | // Simulate change on an object to ensure that objects 114 | // are being handled correctly. 115 | $expected['object']->key = 'object (modified)'; 116 | 117 | // Make the same change in the object that the 118 | // configuration is managing. 119 | $configuration->get('object')->key = 'object (modified)'; 120 | 121 | $this->assertEquals($expected, $configuration->export()); 122 | 123 | // Test to make sure that set will result in setting 124 | // a new value and also that export will show this new 125 | // value. (tests "export is dirty" functionality) 126 | $configuration->set('abc', 'ABCD'); 127 | $expected['abc'] = 'ABCD'; 128 | $this->assertEquals($expected, $configuration->export()); 129 | } 130 | 131 | public function testExportData() 132 | { 133 | $configuration = new Configuration($this->getTestData()); 134 | 135 | $data = $configuration->exportData(); 136 | 137 | // The exportData call should return data filled with 138 | // resolved data. 139 | $this->assertEquals('ABC', $data->get('abc')); 140 | } 141 | 142 | public function testImportRaw() 143 | { 144 | $configuration = new Configuration(); 145 | 146 | $configuration->importRaw($this->getTestData()); 147 | 148 | $this->runBasicTests($configuration); 149 | } 150 | 151 | public function testImport() 152 | { 153 | $configuration = new Configuration(); 154 | 155 | $configuration->import(new Configuration($this->getTestData())); 156 | 157 | $this->runBasicTests($configuration); 158 | } 159 | 160 | public function testSetPlaceholderResolver() 161 | { 162 | $placeholderResolver = $this->getMockBuilder(\Dflydev\PlaceholderResolver\PlaceholderResolverInterface::class)->getMock(); 163 | 164 | $placeholderResolver 165 | ->expects($this->once()) 166 | ->method('resolvePlaceholder') 167 | ->with($this->equalTo('foo')) 168 | ->will($this->returnValue('bar')) 169 | ; 170 | 171 | $configuration = new Configuration(); 172 | 173 | $configuration->setPlaceholderResolver($placeholderResolver); 174 | 175 | $this->assertEquals('bar', $configuration->resolve('foo')); 176 | } 177 | } 178 | --------------------------------------------------------------------------------