├── .github └── workflows │ └── continuous-integration.yml ├── .scrutinizer.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── composer.json └── src ├── AbstractConfig.php ├── Config.php ├── ConfigInterface.php ├── ErrorException.php ├── Exception.php ├── Exception ├── EmptyDirectoryException.php ├── FileNotFoundException.php ├── ParseException.php ├── UnsupportedFormatException.php └── WriteException.php ├── Parser ├── AbstractParser.php ├── Ini.php ├── Json.php ├── ParserInterface.php ├── Php.php ├── Properties.php ├── Serialize.php ├── Xml.php └── Yaml.php └── Writer ├── AbstractWriter.php ├── Ini.php ├── Json.php ├── Properties.php ├── Serialize.php ├── WriterInterface.php ├── Xml.php └── Yaml.php /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'src/**' 7 | - 'tests/**' 8 | - composer.json 9 | push: 10 | paths: 11 | - 'src/**' 12 | - 'tests/**' 13 | - composer.json 14 | 15 | jobs: 16 | quality: 17 | runs-on: ${{ matrix.operating-system }} 18 | strategy: 19 | matrix: 20 | operating-system: [ubuntu-latest] 21 | php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] 22 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} 23 | steps: 24 | - uses: actions/checkout@v3 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: Install PHP 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: ${{ matrix.php-versions }} 32 | 33 | - name: Install dependencies 34 | run: composer install --no-interaction --no-progress --prefer-dist 35 | 36 | - name: PHP Unit tests 37 | if: ${{ matrix.php-versions != '7.4' }} 38 | run: vendor/bin/phpunit 39 | 40 | - name: PHP Unit tests generating coverage data 41 | if: ${{ matrix.php-versions == '7.4' }} 42 | run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 43 | 44 | - name: Upload code coverage data 45 | if: ${{ matrix.php-versions == '7.4' }} 46 | run: php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover 47 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | checks: 4 | php: 5 | code_rating: true 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | tools: 20 | external_code_coverage: 21 | timeout: 600 22 | php_analyzer: true 23 | php_code_coverage: false 24 | php_code_sniffer: 25 | config: 26 | standard: PSR2 27 | filter: 28 | paths: ['src'] 29 | php_loc: 30 | enabled: true 31 | excluded_dirs: [vendor, tests] 32 | php_cpd: 33 | enabled: true 34 | excluded_dirs: [vendor, tests] 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `Config` will be documented in this file 4 | 5 | ## 3.2.0 - 2024-12-09 6 | 7 | ### Added 8 | * PHP 8.3 and 8.4 to the build matrix (#164) 9 | 10 | ### Fixed 11 | * Fatal error when root is null in has() method (#159 and #160) 12 | * PHP 8.4 deprecations implicitly marking parameter as nullable is deprecated (#163) 13 | 14 | 15 | ## 3.1.0 - 2022-12-20 16 | 17 | ### Added 18 | * PHP 8.2 to the build matrix (#154) 19 | 20 | ### Fixed 21 | * Use assertEquals instead of assertSame (#153) 22 | * Namespaces in tests (#154) 23 | 24 | ### Removed 25 | * .travis.yml file (#152) 26 | * support for the end of life PHP versions (#144) 27 | 28 | 29 | ## 3.0.1 - 2022-03-26 30 | 31 | ### Added 32 | - Fix merge method (#145 and #147) 33 | 34 | ## 3.0.0 - 2021-12-30 35 | 36 | ### Added 37 | - Move CI to GitHub actions (#132, #136 and #137) 38 | - PHP 8.0 support, adding it to the build matrix (#138 and #139) 39 | - PHP 8.1 support, adding it to the build matrix (#135, #140 and #141) 40 | 41 | ## 2.2.0 - 2020-12-07 42 | 43 | ### Added 44 | - Serialization support (#127) 45 | - Support for Properties files (#128) 46 | 47 | ### Fixed 48 | - Test enhancement (#126) 49 | - Typehint on Xml parser, parse method (#130) 50 | 51 | ## 2.1.0 - 2019-09-01 52 | 53 | ### Added 54 | - Support for writing configuration back to file and string (#122) 55 | 56 | ## 2.0.2 - 2019-04-06 57 | 58 | ### Fixed 59 | - Implementations of `ParserInterface` and cleanup (#120) 60 | - Tests for PHP 7 61 | 62 | ## 2.0.1 - 2019-02-02 63 | 64 | ### Fixed 65 | - Parsing PHP file (#114) 66 | - Parsing PHP string with `$config` variable (#118) 67 | 68 | ## 2.0.0 - 2018-10-03 69 | 70 | ### Added 71 | - Usage of short array syntax (#109) 72 | - Support for string parsers (#111) 73 | 74 | ### Breaking changes 75 | - Changes of interface and parsers 76 | 77 | ## 1.1.0 - 2018-08-22 78 | 79 | ### Added 80 | - Added support for PHP constants in YAML (#112) 81 | 82 | ## 1.0.1 - 2018-03-31 83 | 84 | ### Fixed 85 | - Possibility to use an own file parser (#103) 86 | 87 | ## 1.0.0 - 2018-03-03 88 | 89 | ### Added 90 | - Merge support (#96) 91 | - Set PHP 5.5.9 as minimum required version (#75 and #99) 92 | 93 | ### Fixed 94 | - Fix PHP 5.6 test (#100) 95 | - Edit PHP versions tested on Travis (#101) 96 | - Add more info about the symfony/yaml requirement (#97 and #102) 97 | 98 | ### Breaking changes 99 | - PHP 5.3 and 5.4 are no longer supported. 100 | 101 | ## 0.10.0 - 2016-02-11 102 | 103 | ### Added 104 | - Package-level exceptions so callers can catch exceptions at package-level 105 | - Added support for files suffixed with the `.dist` extension 106 | 107 | ### Fixed 108 | - Rearranged error-handling in `FileParser\Json` for better test coverage 109 | - Project-wide code style fixes to adhere to PSR-2 110 | - Fixes `has()` method returning `false` on `null` values in a config field 111 | 112 | ## 0.9.1 - 2016-01-23 113 | 114 | ### Added 115 | - PHP 7.0 is now tested on Travis 116 | 117 | ## 0.9.0 - 2015-10-22 118 | 119 | ### Added 120 | - Added namespace to example in `README.md` 121 | - Added `has()` method to `ConfigInterface` and implemented in `AbstractConfig` 122 | - Added `all()` method to `ConfigInterface` and implemented in `AbstractConfig` 123 | - Added documentation for new methods 124 | - `AbstractConfig` now implements the `Iterator` interface 125 | 126 | ### Fixed 127 | - PSR-2 compliance 128 | - Give YamlParser file content instead of path 129 | - Updated `AbstractConfig` constructor to only accept arrays 130 | - Removed check to fix loading an empty array 131 | - Fix for #44: Warnings emitted if configuration file is empty 132 | - Fix for #55: Unset cache after a set 133 | 134 | 135 | ## 0.8.2 - 2015-03-21 136 | 137 | ### Fixed 138 | - Some code smells in `Config` 139 | - Updated README.md 140 | 141 | 142 | ## 0.8.1 - 2015-03-21 143 | 144 | ### Fixed 145 | - Various things relating to recent repo transfer 146 | 147 | 148 | ## 0.8.0 - 2015-03-21 149 | 150 | ### Added 151 | - Individual `FileParser` classes for each filetype, and a `FileParserInterface` to type-hint methods with 152 | - Optional paths; you can now prefix a path with '?' and `Config` will skip the file if it doesn't exist 153 | 154 | ### Fixed 155 | - Made the Symfony YAML component a suggested dependency 156 | - Parent constructor was not being called from `Config` 157 | 158 | 159 | ## 0.7.1 - 2015-02-24 160 | 161 | ### Added 162 | - Moved file logic into file-specific loaders 163 | 164 | ### Fixed 165 | - Corrected class name in README.md 166 | 167 | 168 | ## 0.7.0 - 2015-02-23 169 | 170 | ### Fixed 171 | - Removed kludgy hack for YAML/YML 172 | 173 | 174 | ## 0.6.0 - 2015-02-23 175 | 176 | ### Added 177 | - Can now extend `AbstractConfig` to create simple subclasses without any file IO 178 | 179 | 180 | ## 0.5.0 - 2015-02-23 181 | 182 | ### Added 183 | - Moved file logic into file-specific loaders 184 | 185 | ### Fixed 186 | - Cleaned up exception class constructors, PSR-2 compliance 187 | 188 | 189 | ## 0.4.0 - 2015-02-22 190 | 191 | ### Fixed 192 | - Moved file logic into file-specific loaders 193 | 194 | 195 | ## 0.3.0 - 2015-02-22 196 | 197 | ### Fixed 198 | - Created new classes `ConfigInterface` and `AbstractConfig` to simplify code 199 | 200 | 201 | ## 0.2.1 - 2015-02-22 202 | 203 | ### Added 204 | - Array and directory support in constructor 205 | 206 | ### Fixed 207 | - Corrected deprecated usage of `Symfony\Yaml` 208 | 209 | ## 0.2.0 - 2015-02-21 210 | 211 | ### Added 212 | - Array and directory support in constructor 213 | 214 | ### Fixed 215 | - Now can load .YAML and .YML files 216 | 217 | 218 | ## 0.1.0 - 2014-11-27 219 | 220 | ### Added 221 | - Uses PSR-4 for autoloading 222 | - Supports YAML 223 | - Now uses custom exceptions 224 | 225 | 226 | ## 0.0.1 - 2014-11-19 227 | 228 | ### Added 229 | - Tagged first release 230 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [GitHub](https://github.com/noodlehaus/config). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Use Git Flow** - Don't ask us to pull from your master branch. Set up [Git Flow](http://nvie.com/posts/a-successful-git-branching-model/) and create a new feature branch from `develop` 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **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. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ phpunit 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015 Hassan Khan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hassankhan/config", 3 | "type": "library", 4 | "description": "Lightweight configuration file loader that supports PHP, INI, XML, JSON, and YAML files", 5 | "keywords": ["configuration", "config", "json", "yaml", "yml", "ini", "xml", "unframework", "microphp"], 6 | "homepage": "http://hassankhan.me/config/", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Hassan Khan", 11 | "role": "Developer", 12 | "homepage": "http://hassankhan.me/" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=7.4" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^9.5", 20 | "scrutinizer/ocular": "^1.9", 21 | "squizlabs/php_codesniffer": "^3.6", 22 | "symfony/yaml": "^5.4" 23 | }, 24 | "suggest": { 25 | "symfony/yaml": "^5.4" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Noodlehaus\\": "src" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Noodlehaus\\Test\\": "tests" 35 | } 36 | }, 37 | "config": { 38 | "sort-packages": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/AbstractConfig.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Hassan Khan 14 | * @link https://github.com/noodlehaus/config 15 | * @license MIT 16 | */ 17 | abstract class AbstractConfig implements ArrayAccess, ConfigInterface, Iterator 18 | { 19 | /** 20 | * Stores the configuration data 21 | * 22 | * @var array|null 23 | */ 24 | protected $data = null; 25 | 26 | /** 27 | * Caches the configuration data 28 | * 29 | * @var array 30 | */ 31 | protected $cache = []; 32 | 33 | /** 34 | * Constructor method and sets default options, if any 35 | * 36 | * @param array $data 37 | */ 38 | public function __construct(array $data) 39 | { 40 | $this->data = array_merge($this->getDefaults(), $data); 41 | } 42 | 43 | /** 44 | * Override this method in your own subclass to provide an array of default 45 | * options and values 46 | * 47 | * @return array 48 | * 49 | * @codeCoverageIgnore 50 | */ 51 | protected function getDefaults() 52 | { 53 | return []; 54 | } 55 | 56 | /** 57 | * ConfigInterface Methods 58 | */ 59 | 60 | /** 61 | * {@inheritDoc} 62 | */ 63 | public function get($key, $default = null) 64 | { 65 | if ($this->has($key)) { 66 | return $this->cache[$key]; 67 | } 68 | 69 | return $default; 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | */ 75 | public function set($key, $value) 76 | { 77 | $segs = explode('.', $key); 78 | $root = &$this->data; 79 | $cacheKey = ''; 80 | 81 | // Look for the key, creating nested keys if needed 82 | while ($part = array_shift($segs)) { 83 | if ($cacheKey != '') { 84 | $cacheKey .= '.'; 85 | } 86 | $cacheKey .= $part; 87 | if (!isset($root[$part]) && count($segs)) { 88 | $root[$part] = []; 89 | } 90 | $root = &$root[$part]; 91 | 92 | //Unset all old nested cache 93 | if (isset($this->cache[$cacheKey])) { 94 | unset($this->cache[$cacheKey]); 95 | } 96 | 97 | //Unset all old nested cache in case of array 98 | if (count($segs) == 0) { 99 | foreach ($this->cache as $cacheLocalKey => $cacheValue) { 100 | if (substr($cacheLocalKey, 0, strlen($cacheKey)) === $cacheKey) { 101 | unset($this->cache[$cacheLocalKey]); 102 | } 103 | } 104 | } 105 | } 106 | 107 | // Assign value at target node 108 | $this->cache[$key] = $root = $value; 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | public function has($key) 115 | { 116 | // Check if already cached 117 | if (isset($this->cache[$key])) { 118 | return true; 119 | } 120 | 121 | $segments = explode('.', $key); 122 | $root = $this->data; 123 | 124 | // nested case 125 | foreach ($segments as $segment) { 126 | if (is_array($root) && array_key_exists($segment, $root)) { 127 | $root = $root[$segment]; 128 | continue; 129 | } else { 130 | return false; 131 | } 132 | } 133 | 134 | // Set cache for the given key 135 | $this->cache[$key] = $root; 136 | 137 | return true; 138 | } 139 | 140 | /** 141 | * Merge config from another instance 142 | * 143 | * @param ConfigInterface $config 144 | * @return ConfigInterface 145 | */ 146 | public function merge(ConfigInterface $config) 147 | { 148 | $this->data = array_replace_recursive($this->data, $config->all()); 149 | $this->cache = []; 150 | return $this; 151 | } 152 | 153 | /** 154 | * {@inheritDoc} 155 | */ 156 | public function all() 157 | { 158 | return $this->data; 159 | } 160 | 161 | /** 162 | * ArrayAccess Methods 163 | */ 164 | 165 | /** 166 | * Gets a value using the offset as a key 167 | * 168 | * @param string $offset 169 | * 170 | * @return mixed 171 | */ 172 | #[\ReturnTypeWillChange] 173 | public function offsetGet($offset) 174 | { 175 | return $this->get($offset); 176 | } 177 | 178 | /** 179 | * Checks if a key exists 180 | * 181 | * @param string $offset 182 | * 183 | * @return bool 184 | */ 185 | #[\ReturnTypeWillChange] 186 | public function offsetExists($offset) 187 | { 188 | return $this->has($offset); 189 | } 190 | 191 | /** 192 | * Sets a value using the offset as a key 193 | * 194 | * @param string $offset 195 | * @param mixed $value 196 | * 197 | * @return void 198 | */ 199 | #[\ReturnTypeWillChange] 200 | public function offsetSet($offset, $value) 201 | { 202 | $this->set($offset, $value); 203 | } 204 | 205 | /** 206 | * Deletes a key and its value 207 | * 208 | * @param string $offset 209 | * 210 | * @return void 211 | */ 212 | #[\ReturnTypeWillChange] 213 | public function offsetUnset($offset) 214 | { 215 | $this->set($offset, null); 216 | } 217 | 218 | /** 219 | * Iterator Methods 220 | */ 221 | 222 | /** 223 | * Returns the data array element referenced by its internal cursor 224 | * 225 | * @return mixed The element referenced by the data array's internal cursor. 226 | * If the array is empty or there is no element at the cursor, the 227 | * function returns false. If the array is undefined, the function 228 | * returns null 229 | */ 230 | #[\ReturnTypeWillChange] 231 | public function current() 232 | { 233 | return (is_array($this->data) ? current($this->data) : null); 234 | } 235 | 236 | /** 237 | * Returns the data array index referenced by its internal cursor 238 | * 239 | * @return mixed The index referenced by the data array's internal cursor. 240 | * If the array is empty or undefined or there is no element at the 241 | * cursor, the function returns null 242 | */ 243 | #[\ReturnTypeWillChange] 244 | public function key() 245 | { 246 | return (is_array($this->data) ? key($this->data) : null); 247 | } 248 | 249 | /** 250 | * Moves the data array's internal cursor forward one element 251 | * 252 | * @return mixed The element referenced by the data array's internal cursor 253 | * after the move is completed. If there are no more elements in the 254 | * array after the move, the function returns false. If the data array 255 | * is undefined, the function returns null 256 | */ 257 | #[\ReturnTypeWillChange] 258 | public function next() 259 | { 260 | return (is_array($this->data) ? next($this->data) : null); 261 | } 262 | 263 | /** 264 | * Moves the data array's internal cursor to the first element 265 | * 266 | * @return mixed The element referenced by the data array's internal cursor 267 | * after the move is completed. If the data array is empty, the function 268 | * returns false. If the data array is undefined, the function returns 269 | * null 270 | */ 271 | #[\ReturnTypeWillChange] 272 | public function rewind() 273 | { 274 | return (is_array($this->data) ? reset($this->data) : null); 275 | } 276 | 277 | /** 278 | * Tests whether the iterator's current index is valid 279 | * 280 | * @return bool True if the current index is valid; false otherwise 281 | */ 282 | #[\ReturnTypeWillChange] 283 | public function valid() 284 | { 285 | return (is_array($this->data) ? key($this->data) !== null : false); 286 | } 287 | 288 | /** 289 | * Remove a value using the offset as a key 290 | * 291 | * @param string $key 292 | * 293 | * @return void 294 | */ 295 | public function remove($key) 296 | { 297 | $this->offsetUnset($key); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 16 | * @author Hassan Khan 17 | * @author Filip Š 18 | * @link https://github.com/noodlehaus/config 19 | * @license MIT 20 | */ 21 | class Config extends AbstractConfig 22 | { 23 | /** 24 | * All formats supported by Config. 25 | * 26 | * @var array 27 | */ 28 | protected $supportedParsers = [ 29 | 'Noodlehaus\Parser\Php', 30 | 'Noodlehaus\Parser\Ini', 31 | 'Noodlehaus\Parser\Json', 32 | 'Noodlehaus\Parser\Xml', 33 | 'Noodlehaus\Parser\Yaml', 34 | 'Noodlehaus\Parser\Properties', 35 | 'Noodlehaus\Parser\Serialize' 36 | ]; 37 | 38 | /** 39 | * All formats supported by Config. 40 | * 41 | * @var array 42 | */ 43 | protected $supportedWriters = [ 44 | 'Noodlehaus\Writer\Ini', 45 | 'Noodlehaus\Writer\Json', 46 | 'Noodlehaus\Writer\Xml', 47 | 'Noodlehaus\Writer\Yaml', 48 | 'Noodlehaus\Writer\Properties', 49 | 'Noodlehaus\Writer\Serialize' 50 | ]; 51 | 52 | /** 53 | * Static method for loading a Config instance. 54 | * 55 | * @param string|array $values Filenames or string with configuration 56 | * @param ParserInterface $parser Configuration parser 57 | * @param bool $string Enable loading from string 58 | * 59 | * @return Config 60 | */ 61 | public static function load($values, $parser = null, $string = false) 62 | { 63 | return new static($values, $parser, $string); 64 | } 65 | 66 | /** 67 | * Loads a Config instance. 68 | * 69 | * @param string|array $values Filenames or string with configuration 70 | * @param ?ParserInterface $parser Configuration parser 71 | * @param bool $string Enable loading from string 72 | */ 73 | public function __construct($values, ?ParserInterface $parser = null, $string = false) 74 | { 75 | if ($string === true) { 76 | $this->loadFromString($values, $parser); 77 | } else { 78 | $this->loadFromFile($values, $parser); 79 | } 80 | 81 | parent::__construct($this->data); 82 | } 83 | 84 | /** 85 | * Loads configuration from file. 86 | * 87 | * @param string|array $path Filenames or directories with configuration 88 | * @param ?ParserInterface $parser Configuration parser 89 | * 90 | * @throws EmptyDirectoryException If `$path` is an empty directory 91 | */ 92 | protected function loadFromFile($path, ?ParserInterface $parser = null) 93 | { 94 | $paths = $this->getValidPath($path); 95 | $this->data = []; 96 | 97 | foreach ($paths as $path) { 98 | if ($parser === null) { 99 | // Get file information 100 | $info = pathinfo($path); 101 | $parts = explode('.', $info['basename']); 102 | $extension = array_pop($parts); 103 | 104 | // Skip the `dist` extension 105 | if ($extension === 'dist') { 106 | $extension = array_pop($parts); 107 | } 108 | 109 | // Get file parser 110 | $parser = $this->getParser($extension); 111 | 112 | // Try to load file 113 | $this->data = array_replace_recursive($this->data, $parser->parseFile($path)); 114 | 115 | // Clean parser 116 | $parser = null; 117 | } else { 118 | // Try to load file using specified parser 119 | $this->data = array_replace_recursive($this->data, $parser->parseFile($path)); 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * Writes configuration to file. 126 | * 127 | * @param string $filename Filename to save configuration to 128 | * @param ?WriterInterface $writer Configuration writer 129 | * 130 | * @throws WriteException if the data could not be written to the file 131 | */ 132 | public function toFile($filename, ?WriterInterface $writer = null) 133 | { 134 | if ($writer === null) { 135 | // Get file information 136 | $info = pathinfo($filename); 137 | $parts = explode('.', $info['basename']); 138 | $extension = array_pop($parts); 139 | 140 | // Skip the `dist` extension 141 | if ($extension === 'dist') { 142 | $extension = array_pop($parts); 143 | } 144 | 145 | // Get file writer 146 | $writer = $this->getWriter($extension); 147 | 148 | // Try to save file 149 | $writer->toFile($this->all(), $filename); 150 | 151 | // Clean writer 152 | $writer = null; 153 | } else { 154 | // Try to load file using specified writer 155 | $writer->toFile($this->all(), $filename); 156 | } 157 | } 158 | 159 | /** 160 | * Loads configuration from string. 161 | * 162 | * @param string $configuration String with configuration 163 | * @param ParserInterface $parser Configuration parser 164 | */ 165 | protected function loadFromString($configuration, ParserInterface $parser) 166 | { 167 | $this->data = []; 168 | 169 | // Try to parse string 170 | $this->data = array_replace_recursive($this->data, $parser->parseString($configuration)); 171 | } 172 | 173 | /** 174 | * Writes configuration to string. 175 | * 176 | * @param WriterInterface $writer Configuration writer 177 | * @param boolean $pretty Encode pretty 178 | */ 179 | public function toString(WriterInterface $writer, $pretty = true) 180 | { 181 | return $writer->toString($this->all(), $pretty); 182 | } 183 | 184 | /** 185 | * Gets a parser for a given file extension. 186 | * 187 | * @param string $extension 188 | * 189 | * @return Noodlehaus\Parser\ParserInterface 190 | * 191 | * @throws UnsupportedFormatException If `$extension` is an unsupported file format 192 | */ 193 | protected function getParser($extension) 194 | { 195 | foreach ($this->supportedParsers as $parser) { 196 | if (in_array($extension, $parser::getSupportedExtensions())) { 197 | return new $parser(); 198 | } 199 | } 200 | 201 | // If none exist, then throw an exception 202 | throw new UnsupportedFormatException('Unsupported configuration format'); 203 | } 204 | 205 | /** 206 | * Gets a writer for a given file extension. 207 | * 208 | * @param string $extension 209 | * 210 | * @return Noodlehaus\Writer\WriterInterface 211 | * 212 | * @throws UnsupportedFormatException If `$extension` is an unsupported file format 213 | */ 214 | protected function getWriter($extension) 215 | { 216 | foreach ($this->supportedWriters as $writer) { 217 | if (in_array($extension, $writer::getSupportedExtensions())) { 218 | return new $writer(); 219 | } 220 | } 221 | 222 | // If none exist, then throw an exception 223 | throw new UnsupportedFormatException('Unsupported configuration format'.$extension); 224 | } 225 | 226 | /** 227 | * Gets an array of paths 228 | * 229 | * @param array $path 230 | * 231 | * @return array 232 | * 233 | * @throws FileNotFoundException If a file is not found at `$path` 234 | */ 235 | protected function getPathFromArray($path) 236 | { 237 | $paths = []; 238 | 239 | foreach ($path as $unverifiedPath) { 240 | try { 241 | // Check if `$unverifiedPath` is optional 242 | // If it exists, then it's added to the list 243 | // If it doesn't, it throws an exception which we catch 244 | if ($unverifiedPath[0] !== '?') { 245 | $paths = array_merge($paths, $this->getValidPath($unverifiedPath)); 246 | continue; 247 | } 248 | 249 | $optionalPath = ltrim($unverifiedPath, '?'); 250 | $paths = array_merge($paths, $this->getValidPath($optionalPath)); 251 | } catch (FileNotFoundException $e) { 252 | // If `$unverifiedPath` is optional, then skip it 253 | if ($unverifiedPath[0] === '?') { 254 | continue; 255 | } 256 | 257 | // Otherwise rethrow the exception 258 | throw $e; 259 | } 260 | } 261 | 262 | return $paths; 263 | } 264 | 265 | /** 266 | * Checks `$path` to see if it is either an array, a directory, or a file. 267 | * 268 | * @param string|array $path 269 | * 270 | * @return array 271 | * 272 | * @throws EmptyDirectoryException If `$path` is an empty directory 273 | * 274 | * @throws FileNotFoundException If a file is not found at `$path` 275 | */ 276 | protected function getValidPath($path) 277 | { 278 | // If `$path` is array 279 | if (is_array($path)) { 280 | return $this->getPathFromArray($path); 281 | } 282 | 283 | // If `$path` is a directory 284 | if (is_dir($path)) { 285 | $paths = glob($path . '/*.*'); 286 | if (empty($paths)) { 287 | throw new EmptyDirectoryException("Configuration directory: [$path] is empty"); 288 | } 289 | 290 | return $paths; 291 | } 292 | 293 | // If `$path` is not a file, throw an exception 294 | if (!file_exists($path)) { 295 | throw new FileNotFoundException("Configuration file: [$path] cannot be found"); 296 | } 297 | 298 | return [$path]; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Hassan Khan 11 | * @link https://github.com/noodlehaus/config 12 | * @license MIT 13 | */ 14 | interface ConfigInterface 15 | { 16 | /** 17 | * Gets a configuration setting using a simple or nested key. 18 | * Nested keys are similar to JSON paths that use the dot 19 | * dot notation. 20 | * 21 | * @param string $key 22 | * @param mixed $default 23 | * 24 | * @return mixed 25 | */ 26 | public function get($key, $default = null); 27 | 28 | /** 29 | * Function for setting configuration values, using 30 | * either simple or nested keys. 31 | * 32 | * @param string $key 33 | * @param mixed $value 34 | * 35 | * @return void 36 | */ 37 | public function set($key, $value); 38 | 39 | /** 40 | * Function for checking if configuration values exist, using 41 | * either simple or nested keys. 42 | * 43 | * @param string $key 44 | * 45 | * @return boolean 46 | */ 47 | public function has($key); 48 | 49 | /** 50 | * Get all of the configuration items 51 | * 52 | * @return array 53 | */ 54 | public function all(); 55 | } 56 | -------------------------------------------------------------------------------- /src/ErrorException.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Hassan Khan 11 | * @author Filip Š 12 | * @link https://github.com/noodlehaus/config 13 | * @license MIT 14 | */ 15 | abstract class AbstractParser implements ParserInterface 16 | { 17 | 18 | /** 19 | * String with configuration 20 | * 21 | * @var string 22 | */ 23 | protected $config; 24 | 25 | /** 26 | * Sets the string with configuration 27 | * 28 | * @param string $config 29 | * @param string $filename 30 | * 31 | * @codeCoverageIgnore 32 | */ 33 | public function __construct($config, $filename = null) 34 | { 35 | $this->config = $config; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Parser/Ini.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @link https://github.com/noodlehaus/config 15 | * @license MIT 16 | */ 17 | class Ini implements ParserInterface 18 | { 19 | /** 20 | * {@inheritDoc} 21 | * Parses an INI file as an array 22 | * 23 | * @throws ParseException If there is an error parsing the INI file 24 | */ 25 | public function parseFile($filename) 26 | { 27 | $data = @parse_ini_file($filename, true); 28 | return $this->parse($data, $filename); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | * Parses an INI string as an array 34 | * 35 | * @throws ParseException If there is an error parsing the INI string 36 | */ 37 | public function parseString($config) 38 | { 39 | $data = @parse_ini_string($config, true); 40 | return $this->parse($data); 41 | } 42 | 43 | /** 44 | * Completes parsing of INI data 45 | * 46 | * @param array $data 47 | * @param string $filename 48 | * 49 | * @throws ParseException If there is an error parsing the INI data 50 | */ 51 | protected function parse($data = null, $filename = null) 52 | { 53 | if (!$data) { 54 | $error = error_get_last(); 55 | 56 | // Parse functions may return NULL but set no error if the string contains no parsable data 57 | if (!is_array($error)) { 58 | $error["message"] = "No parsable content in data."; 59 | } 60 | 61 | $error["file"] = $filename; 62 | 63 | // if string contains no parsable data, no error is set, resulting in any previous error 64 | // persisting in error_get_last(). in php 7 this can be addressed with error_clear_last() 65 | if (function_exists("error_clear_last")) { 66 | error_clear_last(); 67 | } 68 | 69 | throw new ParseException($error); 70 | } 71 | 72 | return $this->expandDottedKey($data); 73 | } 74 | 75 | /** 76 | * Expand array with dotted keys to multidimensional array 77 | * 78 | * @param array $data 79 | * 80 | * @return array 81 | */ 82 | protected function expandDottedKey($data) 83 | { 84 | foreach ($data as $key => $value) { 85 | if (($found = strpos($key, '.')) !== false) { 86 | $newKey = substr($key, 0, $found); 87 | $remainder = substr($key, $found + 1); 88 | 89 | $expandedValue = $this->expandDottedKey([$remainder => $value]); 90 | if (isset($data[$newKey])) { 91 | $data[$newKey] = array_merge_recursive($data[$newKey], $expandedValue); 92 | } else { 93 | $data[$newKey] = $expandedValue; 94 | } 95 | unset($data[$key]); 96 | } 97 | } 98 | return $data; 99 | } 100 | 101 | /** 102 | * {@inheritDoc} 103 | */ 104 | public static function getSupportedExtensions() 105 | { 106 | return ['ini']; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Parser/Json.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @link https://github.com/noodlehaus/config 15 | * @license MIT 16 | */ 17 | class Json implements ParserInterface 18 | { 19 | /** 20 | * {@inheritDoc} 21 | * Parses an JSON file as an array 22 | * 23 | * @throws ParseException If there is an error parsing the JSON file 24 | */ 25 | public function parseFile($filename) 26 | { 27 | $data = json_decode(file_get_contents($filename), true); 28 | 29 | return (array)$this->parse($data, $filename); 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | * Parses an JSON string as an array 35 | * 36 | * @throws ParseException If there is an error parsing the JSON string 37 | */ 38 | public function parseString($config) 39 | { 40 | $data = json_decode($config, true); 41 | 42 | return (array)$this->parse($data); 43 | } 44 | 45 | /** 46 | * Completes parsing of JSON data 47 | * 48 | * @param array $data 49 | * @param string $filename 50 | * @return array|null 51 | * 52 | * @throws ParseException If there is an error parsing the JSON data 53 | */ 54 | protected function parse($data = null, $filename = null) 55 | { 56 | if (json_last_error() !== JSON_ERROR_NONE) { 57 | $error_message = 'Syntax error'; 58 | if (function_exists('json_last_error_msg')) { 59 | $error_message = json_last_error_msg(); 60 | } 61 | 62 | $error = [ 63 | 'message' => $error_message, 64 | 'type' => json_last_error(), 65 | 'file' => $filename, 66 | ]; 67 | 68 | throw new ParseException($error); 69 | } 70 | 71 | return $data; 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | public static function getSupportedExtensions() 78 | { 79 | return ['json']; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Parser/ParserInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Hassan Khan 11 | * @author Filip Š 12 | * @link https://github.com/noodlehaus/config 13 | * @license MIT 14 | */ 15 | interface ParserInterface 16 | { 17 | /** 18 | * Parses a configuration from file `$filename` and gets its contents as an array 19 | * 20 | * @param string $filename 21 | * 22 | * @return array 23 | */ 24 | public function parseFile($filename); 25 | 26 | /** 27 | * Parses a configuration from string `$config` and gets its contents as an array 28 | * 29 | * @param string $config 30 | * 31 | * @return array 32 | */ 33 | public function parseString($config); 34 | 35 | /** 36 | * Returns an array of allowed file extensions for this parser 37 | * 38 | * @return array 39 | */ 40 | public static function getSupportedExtensions(); 41 | } 42 | -------------------------------------------------------------------------------- /src/Parser/Php.php: -------------------------------------------------------------------------------- 1 | 14 | * @author Hassan Khan 15 | * @author Filip Š 16 | * @link https://github.com/noodlehaus/config 17 | * @license MIT 18 | */ 19 | class Php implements ParserInterface 20 | { 21 | /** 22 | * {@inheritDoc} 23 | * Loads a PHP file and gets its' contents as an array 24 | * 25 | * @throws ParseException If the PHP file throws an exception 26 | * @throws UnsupportedFormatException If the PHP file does not return an array 27 | */ 28 | public function parseFile($filename) 29 | { 30 | // Run the fileEval the string, if it throws an exception, rethrow it 31 | try { 32 | $data = require $filename; 33 | } catch (Exception $exception) { 34 | throw new ParseException( 35 | [ 36 | 'message' => 'PHP file threw an exception', 37 | 'exception' => $exception, 38 | ] 39 | ); 40 | } 41 | 42 | // Complete parsing 43 | return (array)$this->parse($data, $filename); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | * Loads a PHP string and gets its' contents as an array 49 | * 50 | * @throws ParseException If the PHP string throws an exception 51 | * @throws UnsupportedFormatException If the PHP string does not return an array 52 | */ 53 | public function parseString($config) 54 | { 55 | // Handle PHP start tag 56 | $config = trim($config); 57 | if (substr($config, 0, 2) === '' . $config; 59 | } 60 | 61 | // Eval the string, if it throws an exception, rethrow it 62 | try { 63 | $data = $this->isolate($config); 64 | } catch (Exception $exception) { 65 | throw new ParseException( 66 | [ 67 | 'message' => 'PHP string threw an exception', 68 | 'exception' => $exception, 69 | ] 70 | ); 71 | } 72 | 73 | // Complete parsing 74 | return (array)$this->parse($data); 75 | } 76 | 77 | /** 78 | * Completes parsing of PHP data 79 | * 80 | * @param array $data 81 | * @param string $filename 82 | * 83 | * @return array|null 84 | * @throws UnsupportedFormatException 85 | */ 86 | protected function parse($data = null, $filename = null) 87 | { 88 | // If we have a callable, run it and expect an array back 89 | if (is_callable($data)) { 90 | $data = call_user_func($data); 91 | } 92 | 93 | // Check for array, if its anything else, throw an exception 94 | if (!is_array($data)) { 95 | throw new UnsupportedFormatException('PHP data does not return an array'); 96 | } 97 | 98 | return $data; 99 | } 100 | 101 | /** 102 | * Runs PHP string in isolated method 103 | * 104 | * @param string $EGsfKPdue7ahnMTy 105 | * 106 | * @return array 107 | */ 108 | protected function isolate($EGsfKPdue7ahnMTy) 109 | { 110 | return eval($EGsfKPdue7ahnMTy); 111 | } 112 | 113 | /** 114 | * {@inheritDoc} 115 | */ 116 | public static function getSupportedExtensions() 117 | { 118 | return ['php']; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Parser/Properties.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Hassan Khan 11 | * @author Filip Š 12 | * @author Mark de Groot 13 | * @link https://github.com/noodlehaus/config 14 | * @license MIT 15 | */ 16 | class Properties implements ParserInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | * Parses a Properties file as an array. 21 | */ 22 | public function parseFile($filename) 23 | { 24 | return $this->parse(file_get_contents($filename)); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | * Parses a Properties string as an array. 30 | */ 31 | public function parseString($config) 32 | { 33 | return $this->parse($config); 34 | } 35 | 36 | private function parse($txtProperties) 37 | { 38 | $result = []; 39 | 40 | // first remove all escaped whitespace characters: 41 | $txtProperties = preg_replace('/(?parse($data, $filename); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function parseString($config) 29 | { 30 | return (array) $this->parse($config); 31 | } 32 | 33 | 34 | /** 35 | * Completes parsing of JSON data 36 | * 37 | * @param string $data 38 | * @param string $filename 39 | * @return array|null 40 | * 41 | * @throws ParseException If there is an error parsing the serialized data 42 | */ 43 | protected function parse($data = null, $filename = null) 44 | { 45 | $serializedData = @unserialize($data); 46 | if($serializedData === false){ 47 | 48 | throw new ParseException(error_get_last()); 49 | } 50 | 51 | return $serializedData; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public static function getSupportedExtensions() 58 | { 59 | return ['txt']; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Parser/Xml.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @link https://github.com/noodlehaus/config 15 | * @license MIT 16 | */ 17 | class Xml implements ParserInterface 18 | { 19 | /** 20 | * {@inheritDoc} 21 | * Parses an XML file as an array 22 | * 23 | * @throws ParseException If there is an error parsing the XML file 24 | */ 25 | public function parseFile($filename) 26 | { 27 | libxml_use_internal_errors(true); 28 | $data = simplexml_load_file($filename, null, LIBXML_NOERROR); 29 | 30 | return (array)$this->parse($data, $filename); 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | * Parses an XML string as an array 36 | * 37 | * @throws ParseException If there is an error parsing the XML string 38 | */ 39 | public function parseString($config) 40 | { 41 | libxml_use_internal_errors(true); 42 | $data = simplexml_load_string($config, null, LIBXML_NOERROR); 43 | return (array)$this->parse($data); 44 | } 45 | 46 | /** 47 | * Completes parsing of XML data 48 | * 49 | * @param \SimpleXMLElement|null $data 50 | * @param string $filename 51 | * 52 | * @return array|null 53 | * 54 | * @throws ParseException If there is an error parsing the XML data 55 | */ 56 | protected function parse($data = null, $filename = null) 57 | { 58 | if ($data === false) { 59 | $errors = libxml_get_errors(); 60 | $latestError = array_pop($errors); 61 | $error = [ 62 | 'message' => $latestError->message, 63 | 'type' => $latestError->level, 64 | 'code' => $latestError->code, 65 | 'file' => $filename, 66 | 'line' => $latestError->line, 67 | ]; 68 | throw new ParseException($error); 69 | } 70 | 71 | $data = json_decode(json_encode($data), true); 72 | 73 | return $data; 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | public static function getSupportedExtensions() 80 | { 81 | return ['xml']; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Parser/Yaml.php: -------------------------------------------------------------------------------- 1 | 14 | * @author Hassan Khan 15 | * @author Filip Š 16 | * @link https://github.com/noodlehaus/config 17 | * @license MIT 18 | */ 19 | class Yaml implements ParserInterface 20 | { 21 | /** 22 | * {@inheritDoc} 23 | * Loads a YAML/YML file as an array 24 | * 25 | * @throws ParseException If there is an error parsing the YAML file 26 | */ 27 | public function parseFile($filename) 28 | { 29 | try { 30 | $data = YamlParser::parseFile($filename, YamlParser::PARSE_CONSTANT); 31 | } catch (Exception $exception) { 32 | throw new ParseException( 33 | [ 34 | 'message' => 'Error parsing YAML file', 35 | 'exception' => $exception, 36 | ] 37 | ); 38 | } 39 | 40 | return (array)$this->parse($data); 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | * Loads a YAML/YML string as an array 46 | * 47 | * @throws ParseException If If there is an error parsing the YAML string 48 | */ 49 | public function parseString($config) 50 | { 51 | try { 52 | $data = YamlParser::parse($config, YamlParser::PARSE_CONSTANT); 53 | } catch (Exception $exception) { 54 | throw new ParseException( 55 | [ 56 | 'message' => 'Error parsing YAML string', 57 | 'exception' => $exception, 58 | ] 59 | ); 60 | } 61 | 62 | return (array)$this->parse($data); 63 | } 64 | 65 | /** 66 | * Completes parsing of YAML/YML data 67 | * 68 | * @param array $data 69 | * 70 | * @return array|null 71 | */ 72 | protected function parse($data = null) 73 | { 74 | return $data; 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | public static function getSupportedExtensions() 81 | { 82 | return ['yaml', 'yml']; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Writer/AbstractWriter.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @author Mark de Groot 15 | * @link https://github.com/noodlehaus/config 16 | * @license MIT 17 | */ 18 | abstract class AbstractWriter implements WriterInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function toFile($config, $filename) 24 | { 25 | $contents = $this->toString($config); 26 | $success = @file_put_contents($filename, $contents); 27 | if ($success === false) { 28 | throw new WriteException(['file' => $filename]); 29 | } 30 | 31 | return $contents; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Writer/Ini.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Hassan Khan 11 | * @author Filip Š 12 | * @link https://github.com/noodlehaus/config 13 | * @license MIT 14 | */ 15 | class Ini extends AbstractWriter 16 | { 17 | /** 18 | * {@inheritdoc} 19 | * Writes an array to a Ini string. 20 | */ 21 | public function toString($config, $pretty = true) 22 | { 23 | return $this->toINI($config); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public static function getSupportedExtensions() 30 | { 31 | return ['ini']; 32 | } 33 | 34 | /** 35 | * Converts array to INI string. 36 | * @param array $arr Array to be converted 37 | * @param array $parent Parent array 38 | * 39 | * @return string Converted array as INI 40 | * 41 | * @see https://stackoverflow.com/a/17317168/6523409/ 42 | */ 43 | protected function toINI(array $arr, array $parent = []) 44 | { 45 | $converted = ''; 46 | 47 | foreach ($arr as $k => $v) { 48 | if (is_array($v)) { 49 | $sec = array_merge((array) $parent, (array) $k); 50 | $converted .= '['.implode('.', $sec).']'.PHP_EOL; 51 | $converted .= $this->toINI($v, $sec); 52 | } else { 53 | $converted .= $k.'='.$v.PHP_EOL; 54 | } 55 | } 56 | 57 | return $converted; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Writer/Json.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @author Mark de Groot 15 | * @link https://github.com/noodlehaus/config 16 | * @license MIT 17 | */ 18 | class Json extends AbstractWriter 19 | { 20 | /** 21 | * {@inheritdoc} 22 | * Writes an array to a JSON file. 23 | */ 24 | public function toFile($config, $filename) 25 | { 26 | $data = $this->toString($config); 27 | $success = @file_put_contents($filename, $data.PHP_EOL); 28 | if ($success === false) { 29 | throw new WriteException(['file' => $filename]); 30 | } 31 | 32 | return $data; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | * Writes an array to a JSON string. 38 | */ 39 | public function toString($config, $pretty = true) 40 | { 41 | return json_encode($config, $pretty ? (JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : 0); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public static function getSupportedExtensions() 48 | { 49 | return ['json']; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Writer/Properties.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Hassan Khan 11 | * @author Filip Š 12 | * @author Mark de Groot 13 | * @link https://github.com/noodlehaus/config 14 | * @license MIT 15 | */ 16 | class Properties extends AbstractWriter 17 | { 18 | /** 19 | * {@inheritdoc} 20 | * Writes an array to a Properties string. 21 | */ 22 | public function toString($config, $pretty = true) 23 | { 24 | return $this->toProperties($config); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public static function getSupportedExtensions() 31 | { 32 | return ['properties']; 33 | } 34 | 35 | /** 36 | * Converts array to Properties string. 37 | * @param array $arr Array to be converted 38 | * 39 | * @return string Converted array as Properties 40 | */ 41 | protected function toProperties(array $arr) 42 | { 43 | $converted = ''; 44 | 45 | foreach ($arr as $key => $value) { 46 | if (is_array($value)) { 47 | continue; 48 | } 49 | 50 | // Escape all space, ; and = characters in the key: 51 | $key = addcslashes($key, ' :='); 52 | 53 | // Escape all backslashes and newlines in the value: 54 | $value = preg_replace('/([\r\n\t\f\v\\\])/', '\\\$1', $value); 55 | 56 | $converted .= $key.' = '.$value.PHP_EOL; 57 | } 58 | 59 | return $converted; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Writer/Serialize.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @author Mark de Groot 15 | * @link https://github.com/noodlehaus/config 16 | * @license MIT 17 | */ 18 | interface WriterInterface 19 | { 20 | /** 21 | * Writes a configuration from `$config` to `$filename`. 22 | * 23 | * @param array $config 24 | * @param string $filename 25 | * 26 | * @throws WriteException if the data could not be written to the file 27 | * 28 | * @return array 29 | */ 30 | public function toFile($config, $filename); 31 | 32 | /** 33 | * Writes a configuration from `$config` to a string. 34 | * 35 | * @param array $config 36 | * @param bool $pretty 37 | * 38 | * @return array 39 | */ 40 | public function toString($config, $pretty = true); 41 | 42 | /** 43 | * Returns an array of allowed file extensions for this writer. 44 | * 45 | * @return array 46 | */ 47 | public static function getSupportedExtensions(); 48 | } 49 | -------------------------------------------------------------------------------- /src/Writer/Xml.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Hassan Khan 14 | * @author Filip Š 15 | * @author Mark de Groot 16 | * @link https://github.com/noodlehaus/config 17 | * @license MIT 18 | */ 19 | class Xml extends AbstractWriter 20 | { 21 | /** 22 | * {@inheritdoc} 23 | * Writes an array to a Xml string. 24 | */ 25 | public function toString($config, $pretty = true) 26 | { 27 | $xml = $this->toXML($config); 28 | if ($pretty == false) { 29 | return $xml; 30 | } 31 | 32 | $dom = new DOMDocument(); 33 | $dom->preserveWhiteSpace = false; 34 | $dom->formatOutput = true; 35 | $dom->loadXML($xml); 36 | 37 | return $dom->saveXML(); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public static function getSupportedExtensions() 44 | { 45 | return ['xml']; 46 | } 47 | 48 | /** 49 | * Converts array to XML string. 50 | * @param array $arr Array to be converted 51 | * @param string $rootElement I specified will be taken as root element 52 | * @param SimpleXMLElement $xml If specified content will be appended 53 | * 54 | * @return string Converted array as XML 55 | * 56 | * @see https://www.kerstner.at/2011/12/php-array-to-xml-conversion/ 57 | */ 58 | protected function toXML(array $arr, $rootElement = '', $xml = null) 59 | { 60 | if ($xml === null) { 61 | $xml = new SimpleXMLElement($rootElement); 62 | } 63 | foreach ($arr as $k => $v) { 64 | if (is_array($v)) { 65 | $this->toXML($v, $k, $xml->addChild($k)); 66 | } else { 67 | $xml->addChild($k, $v); 68 | } 69 | } 70 | 71 | return $xml->asXML(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Writer/Yaml.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Hassan Khan 13 | * @author Filip Š 14 | * @author Mark de Groot 15 | * @link https://github.com/noodlehaus/config 16 | * @license MIT 17 | */ 18 | class Yaml extends AbstractWriter 19 | { 20 | /** 21 | * {@inheritdoc} 22 | * Writes an array to a Yaml string. 23 | */ 24 | public function toString($config, $pretty = true) 25 | { 26 | return YamlParser::dump($config); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public static function getSupportedExtensions() 33 | { 34 | return ['yaml']; 35 | } 36 | } 37 | --------------------------------------------------------------------------------