├── .cs.php
├── .github
└── workflows
│ └── build.yml
├── LICENSE
├── README.md
├── composer.json
├── phpcs.xml
├── phpstan.neon
└── src
└── Configuration.php
/.cs.php:
--------------------------------------------------------------------------------
1 | setUsingCache(false)
7 | ->setRiskyAllowed(true)
8 | ->setRules(
9 | [
10 | '@PSR1' => true,
11 | '@PSR2' => true,
12 | '@Symfony' => true,
13 | 'psr_autoloading' => true,
14 | // custom rules
15 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], // psr-5
16 | 'phpdoc_to_comment' => false,
17 | 'no_superfluous_phpdoc_tags' => false,
18 | 'array_indentation' => true,
19 | 'array_syntax' => ['syntax' => 'short'],
20 | 'cast_spaces' => ['space' => 'none'],
21 | 'concat_space' => ['spacing' => 'one'],
22 | 'compact_nullable_typehint' => true,
23 | 'declare_equal_normalize' => ['space' => 'single'],
24 | 'general_phpdoc_annotation_remove' => [
25 | 'annotations' => [
26 | 'author',
27 | 'package',
28 | ],
29 | ],
30 | 'increment_style' => ['style' => 'post'],
31 | 'list_syntax' => ['syntax' => 'short'],
32 | 'echo_tag_syntax' => ['format' => 'long'],
33 | 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
34 | 'phpdoc_align' => false,
35 | 'phpdoc_no_empty_return' => false,
36 | 'phpdoc_order' => true, // psr-5
37 | 'phpdoc_no_useless_inheritdoc' => false,
38 | 'protected_to_private' => false,
39 | 'yoda_style' => false,
40 | 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
41 | 'ordered_imports' => [
42 | 'sort_algorithm' => 'alpha',
43 | 'imports_order' => ['class', 'const', 'function'],
44 | ],
45 | 'single_line_throw' => false,
46 | 'declare_strict_types' => false,
47 | 'blank_line_between_import_groups' => true,
48 | 'fully_qualified_strict_types' => true,
49 | 'no_null_property_initialization' => false,
50 | 'operator_linebreak' => [
51 | 'only_booleans' => true,
52 | 'position' => 'beginning',
53 | ],
54 | 'global_namespace_import' => [
55 | 'import_classes' => true,
56 | 'import_constants' => null,
57 | 'import_functions' => null
58 | ]
59 | ]
60 | )
61 | ->setFinder(
62 | PhpCsFixer\Finder::create()
63 | ->in(__DIR__ . '/src')
64 | ->in(__DIR__ . '/tests')
65 | ->name('*.php')
66 | ->ignoreDotFiles(true)
67 | ->ignoreVCS(true)
68 | );
69 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: [ push, pull_request ]
4 |
5 | jobs:
6 | run:
7 | runs-on: ${{ matrix.operating-system }}
8 | strategy:
9 | matrix:
10 | operating-system: [ ubuntu-latest ]
11 | php-versions: [ '8.1', '8.2' ]
12 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v1
17 |
18 | - name: Setup PHP
19 | uses: shivammathur/setup-php@v2
20 | with:
21 | php-version: ${{ matrix.php-versions }}
22 | extensions: mbstring, intl, zip
23 | coverage: none
24 |
25 | - name: Check PHP Version
26 | run: php -v
27 |
28 | - name: Check Composer Version
29 | run: composer -V
30 |
31 | - name: Check PHP Extensions
32 | run: php -m
33 |
34 | - name: Validate composer.json and composer.lock
35 | run: composer validate
36 |
37 | - name: Install dependencies
38 | run: composer install --prefer-dist --no-progress --no-suggest
39 |
40 | - name: Run test suite
41 | run: composer test:all
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 odan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # selective/config
2 |
3 | A strictly typed configuration component for PHP. Inspired by [Apache Commons Configuration](https://commons.apache.org/proper/commons-configuration/).
4 |
5 | [](https://packagist.org/packages/selective/config)
6 | [](LICENSE)
7 | [](https://github.com/selective-php/config/actions)
8 | [](https://scrutinizer-ci.com/g/selective-php/config/code-structure)
9 | [](https://scrutinizer-ci.com/g/selective-php/config/?branch=master)
10 | [](https://packagist.org/packages/selective/config/stats)
11 |
12 |
13 | ## Requirements
14 |
15 | * PHP 8.1+
16 |
17 | ## Installation
18 |
19 | ```bash
20 | composer require selective/config
21 | ```
22 |
23 | ## Theory of Operation
24 |
25 | You can use the `Configuration` to read single values from a multidimensional
26 | array by passing the path to one of the `get{type}()` and `find{type}()` methods.
27 |
28 | Each `get*() / find*()` method takes a default value as second argument.
29 | If the path cannot be found in the original array, the default is used as return value.
30 |
31 | A `get*()` method returns only the declared return type.
32 | If the default value is not given and the element cannot be found, an exception is thrown.
33 |
34 | A `find*()` method returns only the declared return type or `null`.
35 | No exception is thrown if the element cannot be found.
36 |
37 | ## Usage
38 |
39 | ```php
40 | [
46 | 'key2' => [
47 | 'key3' => 'value1',
48 | ]
49 | ]
50 | ]);
51 |
52 | // Output: value1
53 | echo $config->getString('key1.key2.key3');
54 | ```
55 |
56 | ## Slim 4 integration
57 |
58 | Add this dependency injection container definition:
59 |
60 | ```php
61 | use Selective\Config\Configuration;
62 |
63 | // ...
64 |
65 | return [
66 | // Application settings
67 | Configuration::class => function () {
68 | return new Configuration(require __DIR__ . '/settings.php');
69 | },
70 |
71 | // ...
72 | ];
73 | ```
74 |
75 | ## Examples
76 |
77 | ### Configuring a database connection
78 |
79 | The settings:
80 |
81 | ```php
82 | // Database settings
83 | $settings['db'] = [
84 | 'driver' => 'mysql',
85 | 'host' => 'localhost',
86 | 'username' => 'root',
87 | 'database' => 'test',
88 | 'password' => '',
89 | 'charset' => 'utf8mb4',
90 | 'collation' => 'utf8mb4_unicode_ci',
91 | 'flags' => [
92 | PDO::ATTR_PERSISTENT => false,
93 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
94 | ],
95 | ];
96 | ```
97 |
98 | The container definition:
99 |
100 | ```php
101 | use Selective\Config\Configuration;
102 | use PDO;
103 |
104 | return [
105 | // ...
106 |
107 | PDO::class => static function (ContainerInterface $container) {
108 | $config = $container->get(Configuration::class);
109 |
110 | $host = $config->getString('db.host');
111 | $dbname = $config->getString('db.database');
112 | $username = $config->getString('db.username');
113 | $password = $config->getString('db.password');
114 | $charset = $config->getString('db.charset');
115 | $flags = $config->getArray('db.flags');
116 | $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
117 |
118 | return new PDO($dsn, $username, $password, $flags);
119 | },
120 |
121 | // ...
122 |
123 | ];
124 | ```
125 |
126 | ### Injecting the configuration
127 |
128 | The settings:
129 |
130 | ```php
131 | $settings['module'] = [
132 | 'key1' => 'my-value',
133 | ];
134 | ```
135 |
136 | The consumer class:
137 |
138 | ```php
139 | config = $config;
152 | }
153 |
154 | public function bar()
155 | {
156 | $myKey1 = $this->config->getString('module.key1');
157 |
158 | // ...
159 | }
160 | }
161 |
162 | ```
163 |
164 | ## Similar libraries
165 |
166 | * https://github.com/laminas/laminas-config
167 | * https://github.com/hassankhan/config
168 |
169 | ## License
170 |
171 | The MIT License (MIT). Please see [License File](LICENSE) for more information.
172 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "selective/config",
3 | "description": "Config component, strictly typed",
4 | "license": "MIT",
5 | "type": "library",
6 | "keywords": [
7 | "config",
8 | "configuration",
9 | "setting",
10 | "settings",
11 | "properties",
12 | "strict"
13 | ],
14 | "homepage": "https://github.com/selective-php/config",
15 | "require": {
16 | "php": "^8.1",
17 | "cakephp/chronos": "^2 || ^3"
18 | },
19 | "require-dev": {
20 | "friendsofphp/php-cs-fixer": "^3",
21 | "phpstan/phpstan": "^1",
22 | "phpunit/phpunit": "^10",
23 | "squizlabs/php_codesniffer": "^3"
24 | },
25 | "autoload": {
26 | "psr-4": {
27 | "Selective\\Config\\": "src/"
28 | }
29 | },
30 | "autoload-dev": {
31 | "psr-4": {
32 | "Selective\\Config\\Test\\": "tests/"
33 | }
34 | },
35 | "config": {
36 | "sort-packages": true
37 | },
38 | "scripts": {
39 | "cs:check": [
40 | "@putenv PHP_CS_FIXER_IGNORE_ENV=1",
41 | "php-cs-fixer fix --dry-run --format=txt --verbose --diff --config=.cs.php --ansi"
42 | ],
43 | "cs:fix": [
44 | "@putenv PHP_CS_FIXER_IGNORE_ENV=1",
45 | "php-cs-fixer fix --config=.cs.php --ansi --verbose"
46 | ],
47 | "sniffer:check": "phpcs --standard=phpcs.xml",
48 | "sniffer:fix": "phpcbf --standard=phpcs.xml",
49 | "stan": "phpstan analyse -c phpstan.neon --no-progress --ansi",
50 | "test": "phpunit --configuration phpunit.xml --do-not-cache-result --colors=always",
51 | "test:all": [
52 | "@cs:check",
53 | "@sniffer:check",
54 | "@stan",
55 | "@test"
56 | ],
57 | "test:coverage": "php -d xdebug.mode=coverage -r \"require 'vendor/bin/phpunit';\" -- --configuration phpunit.xml --do-not-cache-result --colors=always --coverage-clover build/logs/clover.xml --coverage-html build/coverage"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ./src
10 | ./tests
11 |
12 |
13 |
14 |
15 | warning
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 8
3 | reportUnmatchedIgnoredErrors: false
4 | paths:
5 | - src
6 | ignoreErrors:
7 | - '#Cannot cast mixed to int.#'
8 | - '#Cannot cast mixed to string.#'
9 | - '#Cannot cast mixed to float.#'
10 | - '#Cannot access offset string on mixed.#'
11 | - '#Parameter \#1 \$time of class Cake\\Chronos\\Chronos constructor expects DateTimeInterface\|int\|string\|null, mixed given.#'
12 | - '#Parameter \#1 \$data of class Selective\\Config\\Configuration constructor expects DateTimeInterface\|int\|string\|null, mixed given.#'
13 | - '#Parameter \#1 \$data of class Selective\\Config\\Configuration constructor expects array, mixed given.#'
14 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::findString\(\) expects string\|null, mixed given.#'
15 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::getString\(\) expects string\|null, mixed given.#'
16 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::findInt\(\) expects int\|null, mixed given.#'
17 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::getInt\(\) expects int\|null, mixed given.#'
18 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::findBool\(\) expects bool\|null, mixed given.#'
19 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::getBool\(\) expects bool\|null, mixed given.#'
20 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::findFloat\(\) expects float\|null, mixed given.#'
21 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::getFloat\(\) expects float\|null, mixed given.#'
22 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::findArray\(\) expects array\|null, mixed given.#'
23 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::getArray\(\) expects array\|null, mixed given.#'
24 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::findChronos\(\) expects Cake\\Chronos\\Chronos\|null, mixed given.#'
25 | - '#Parameter \#2 \$default of method Selective\\Config\\Configuration::getChronos\(\) expects Cake\\Chronos\\Chronos\|null, mixed given.#'
26 |
--------------------------------------------------------------------------------
/src/Configuration.php:
--------------------------------------------------------------------------------
1 | The data
15 | */
16 | private $data;
17 |
18 | /**
19 | * Constructor.
20 | *
21 | * @param array $data Data
22 | */
23 | public function __construct(array $data = [])
24 | {
25 | $this->data = $data;
26 | }
27 |
28 | /**
29 | * Get value as integer.
30 | *
31 | * @param string $key The key
32 | * @param int|null $default The default value
33 | *
34 | * @throws InvalidArgumentException
35 | *
36 | * @return int The value
37 | */
38 | public function getInt(string $key, ?int $default = null): int
39 | {
40 | $value = $this->find($key, $default);
41 |
42 | if ($this->isNullOrBlank($value)) {
43 | throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key));
44 | }
45 |
46 | return (int)$value;
47 | }
48 |
49 | /**
50 | * Get value as integer or null.
51 | *
52 | * @param string $key The key
53 | * @param int|null $default The default value
54 | *
55 | * @return int|null The value
56 | */
57 | public function findInt(string $key, ?int $default = null)
58 | {
59 | $value = $this->find($key, $default);
60 |
61 | if ($this->isNullOrBlank($value)) {
62 | return null;
63 | }
64 |
65 | return (int)$value;
66 | }
67 |
68 | /**
69 | * Get value as string.
70 | *
71 | * @param string $key The key
72 | * @param string|null $default The default value
73 | *
74 | * @throws InvalidArgumentException
75 | *
76 | * @return string The value
77 | */
78 | public function getString(string $key, ?string $default = null): string
79 | {
80 | $value = $this->find($key, $default);
81 |
82 | if ($value === null) {
83 | throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key));
84 | }
85 |
86 | return (string)$value;
87 | }
88 |
89 | /**
90 | * Get value as string or null.
91 | *
92 | * @param string $key The key
93 | * @param string $default The default value
94 | *
95 | * @return string|null The value
96 | */
97 | public function findString(string $key, ?string $default = null)
98 | {
99 | $value = $this->find($key, $default);
100 |
101 | if ($value === null) {
102 | return null;
103 | }
104 |
105 | return (string)$value;
106 | }
107 |
108 | /**
109 | * Get value as array.
110 | *
111 | * @param string $key The key
112 | * @param array|null $default The default value
113 | *
114 | * @throws InvalidArgumentException
115 | *
116 | * @return array The value
117 | */
118 | public function getArray(string $key, ?array $default = null): array
119 | {
120 | $value = $this->find($key, $default);
121 |
122 | if ($this->isNullOrBlank($value)) {
123 | throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key));
124 | }
125 |
126 | return (array)$value;
127 | }
128 |
129 | /**
130 | * Get value as array or null.
131 | *
132 | * @param string $key The key
133 | * @param array $default The default value
134 | *
135 | * @return array|null The value
136 | */
137 | public function findArray(string $key, ?array $default = null)
138 | {
139 | $value = $this->find($key, $default);
140 |
141 | if ($this->isNullOrBlank($value)) {
142 | return null;
143 | }
144 |
145 | return (array)$value;
146 | }
147 |
148 | /**
149 | * Get value as float.
150 | *
151 | * @param string $key The key
152 | * @param float|null $default The default value
153 | *
154 | * @throws InvalidArgumentException
155 | *
156 | * @return float The value
157 | */
158 | public function getFloat(string $key, ?float $default = null): float
159 | {
160 | $value = $this->find($key, $default);
161 |
162 | if ($this->isNullOrBlank($value)) {
163 | throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key));
164 | }
165 |
166 | return (float)$value;
167 | }
168 |
169 | /**
170 | * Get value as float or null.
171 | *
172 | * @param string $key The key
173 | * @param float $default The default value
174 | *
175 | * @return float|null The value
176 | */
177 | public function findFloat(string $key, ?float $default = null)
178 | {
179 | $value = $this->find($key, $default);
180 |
181 | if ($this->isNullOrBlank($value)) {
182 | return null;
183 | }
184 |
185 | return (float)$value;
186 | }
187 |
188 | /**
189 | * Get value as boolean.
190 | *
191 | * @param string $key The key
192 | * @param bool|null $default The default value
193 | *
194 | * @throws InvalidArgumentException
195 | *
196 | * @return bool The value
197 | */
198 | public function getBool(string $key, ?bool $default = null): bool
199 | {
200 | $value = $this->find($key, $default);
201 |
202 | if ($this->isNullOrBlank($value)) {
203 | throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key));
204 | }
205 |
206 | return (bool)$value;
207 | }
208 |
209 | /**
210 | * Get value as boolean or null.
211 | *
212 | * @param string $key The key
213 | * @param bool $default The default value
214 | *
215 | * @return bool|null The value
216 | */
217 | public function findBool(string $key, ?bool $default = null)
218 | {
219 | $value = $this->find($key, $default);
220 |
221 | if ($this->isNullOrBlank($value)) {
222 | return null;
223 | }
224 |
225 | return (bool)$value;
226 | }
227 |
228 | /**
229 | * Get value as Chronos.
230 | *
231 | * @param string $key The key
232 | * @param Chronos|null $default The default value
233 | *
234 | * @throws InvalidArgumentException
235 | *
236 | * @return Chronos The value
237 | */
238 | public function getChronos(string $key, ?Chronos $default = null): Chronos
239 | {
240 | $value = $this->find($key, $default);
241 |
242 | if ($this->isNullOrBlank($value)) {
243 | throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key));
244 | }
245 |
246 | if ($value instanceof Chronos) {
247 | return $value;
248 | }
249 |
250 | return new Chronos($value);
251 | }
252 |
253 | /**
254 | * Get value as Chronos or null.
255 | *
256 | * @param string $key The key
257 | * @param Chronos|null $default The default value
258 | *
259 | * @return Chronos|null The value
260 | */
261 | public function findChronos(string $key, ?Chronos $default = null)
262 | {
263 | $value = $this->find($key, $default);
264 |
265 | if ($this->isNullOrBlank($value)) {
266 | return null;
267 | }
268 |
269 | if ($value instanceof Chronos) {
270 | return $value;
271 | }
272 |
273 | return new Chronos($value);
274 | }
275 |
276 | /**
277 | * Find mixed value.
278 | *
279 | * @param string $path The path
280 | * @param mixed|null $default The default value
281 | *
282 | * @return mixed|null The value
283 | */
284 | public function find(string $path, $default = null)
285 | {
286 | if (array_key_exists($path, $this->data)) {
287 | return $this->data[$path] ?? $default;
288 | }
289 |
290 | if (strpos($path, '.') === false) {
291 | return $default;
292 | }
293 |
294 | $pathKeys = explode('.', $path);
295 |
296 | $arrayCopyOrValue = $this->data;
297 |
298 | foreach ($pathKeys as $pathKey) {
299 | if (!isset($arrayCopyOrValue[$pathKey])) {
300 | return $default;
301 | }
302 | $arrayCopyOrValue = $arrayCopyOrValue[$pathKey];
303 | }
304 |
305 | return $arrayCopyOrValue;
306 | }
307 |
308 | /**
309 | * Return all settings as array.
310 | *
311 | * @return array The data
312 | */
313 | public function all(): array
314 | {
315 | return $this->data;
316 | }
317 |
318 | /**
319 | * Is null or blank.
320 | *
321 | * @param mixed $value The value
322 | *
323 | * @return bool The status
324 | */
325 | private function isNullOrBlank($value): bool
326 | {
327 | return $value === null || $value === '';
328 | }
329 | }
330 |
--------------------------------------------------------------------------------