├── src ├── Parsers │ ├── ParserException.php │ ├── YamlParser.php │ ├── Extra │ │ └── JsonXMLElement.php │ ├── JsonParser.php │ ├── IniParser.php │ ├── EnvParser.php │ ├── XmlParser.php │ ├── DatabaseParser.php │ └── Parser.php ├── Debug │ ├── ConfigCollection.php │ ├── icons │ │ └── config.svg │ └── ConfigCollector.php └── Config.php ├── README.md ├── LICENSE └── composer.json /src/Parsers/ParserException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | /** 13 | * Class ParserException. 14 | * 15 | * @package config 16 | */ 17 | class ParserException extends \ErrorException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Debug/ConfigCollection.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Debug; 11 | 12 | use Framework\Debug\Collection; 13 | 14 | class ConfigCollection extends Collection 15 | { 16 | protected string $iconPath = __DIR__ . '/icons/config.svg'; 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Aplus Framework Config Library 2 | 3 | # Aplus Framework Config Library 4 | 5 | - [Home](https://aplus-framework.com/packages/config) 6 | - [User Guide](https://docs.aplus-framework.com/guides/libraries/config/index.html) 7 | - [API Documentation](https://docs.aplus-framework.com/packages/config.html) 8 | 9 | [![tests](https://github.com/aplus-framework/config/actions/workflows/tests.yml/badge.svg)](https://github.com/aplus-framework/config/actions/workflows/tests.yml) 10 | [![coverage](https://coveralls.io/repos/github/aplus-framework/config/badge.svg?branch=master)](https://coveralls.io/github/aplus-framework/config?branch=master) 11 | [![packagist](https://img.shields.io/packagist/v/aplus/config)](https://packagist.org/packages/aplus/config) 12 | [![open-source](https://img.shields.io/badge/open--source-sponsor-magenta)](https://aplus-framework.com/sponsor) 13 | -------------------------------------------------------------------------------- /src/Parsers/YamlParser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | /** 13 | * Class YamlParser. 14 | * 15 | * @package config 16 | */ 17 | class YamlParser extends Parser 18 | { 19 | /** 20 | * Parses an YAML file. 21 | * 22 | * @param mixed $config path to the YAML file 23 | * 24 | * @throws ParserException 25 | * 26 | * @return array The YAML parsed data 27 | */ 28 | public static function parse(mixed $config) : array 29 | { 30 | static::checkConfig($config); 31 | return static::parseOrThrow(static function () use ($config) : array { 32 | $data = \yaml_parse_file($config); 33 | return static::ksortRecursive($data); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Natan Felles 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 | -------------------------------------------------------------------------------- /src/Parsers/Extra/JsonXMLElement.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers\Extra; 11 | 12 | /** 13 | * Class JsonXMLElement. 14 | * 15 | * @see \Framework\Config\Parsers\XmlParser 16 | * 17 | * @package config 18 | */ 19 | class JsonXMLElement extends \SimpleXMLElement implements \JsonSerializable 20 | { 21 | /** 22 | * @return array|string 23 | */ 24 | public function jsonSerialize() : array | string 25 | { 26 | $data = []; 27 | foreach ($this as $name => $element) { 28 | $name = (string) $name; 29 | if (!isset($data[$name])) { 30 | $data[$name] = $element; 31 | continue; 32 | } 33 | if (!\is_array($data[$name])) { 34 | $data[$name] = [$data[$name]]; 35 | } 36 | $data[$name][] = $element; 37 | } 38 | $text = \trim((string) $this); 39 | if (($text !== '') && empty($data)) { 40 | $data = $text; 41 | } 42 | return $data; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Parsers/JsonParser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | /** 13 | * Class JsonParser. 14 | * 15 | * @package config 16 | */ 17 | class JsonParser extends Parser 18 | { 19 | /** 20 | * Parses a JSON file. 21 | * 22 | * @param mixed $config path to the JSON file 23 | * 24 | * @throws ParserException 25 | * 26 | * @return array The JSON parsed data 27 | */ 28 | public static function parse(mixed $config) : array 29 | { 30 | static::checkConfig($config); 31 | return static::parseOrThrow(static function () use ($config) : array { 32 | $config = \file_get_contents($config); 33 | try { 34 | $data = \json_decode($config, true, 512, \JSON_THROW_ON_ERROR); // @phpstan-ignore-line 35 | } catch (\Exception $exception) { 36 | throw new ParserException( 37 | static::class . ': ' . $exception->getMessage(), 38 | $exception->getCode(), 39 | \E_ERROR, 40 | $exception->getFile(), 41 | $exception->getLine() 42 | ); 43 | } 44 | return static::ksortRecursive($data); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Parsers/IniParser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | /** 13 | * Class IniParser. 14 | * 15 | * @package config 16 | */ 17 | class IniParser extends Parser 18 | { 19 | /** 20 | * Parses an INI file. 21 | * 22 | * @param mixed $config path to the INI file 23 | * 24 | * @throws ParserException 25 | * 26 | * @return array The INI parsed data 27 | */ 28 | public static function parse(mixed $config) : array 29 | { 30 | static::checkConfig($config); 31 | return static::parseOrThrow(static function () use ($config) : array { 32 | $parsed = \parse_ini_file($config, true, \INI_SCANNER_TYPED); 33 | $data = []; 34 | // @phpstan-ignore-next-line 35 | foreach ($parsed as $section => $values) { 36 | $data[$section] = []; 37 | foreach ($values as $key => $value) { 38 | $key = \explode('.', $key); 39 | $parent = []; 40 | static::addChild($parent, $key, $value); 41 | $data[$section] = \array_replace_recursive( 42 | $data[$section], 43 | $parent 44 | ); 45 | } 46 | } 47 | return static::ksortRecursive($data); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Parsers/EnvParser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | /** 13 | * Class EnvParser. 14 | * 15 | * @package config 16 | */ 17 | class EnvParser extends Parser 18 | { 19 | /** 20 | * Parses an .ENV file. 21 | * 22 | * @param mixed $config Path to the .ENV file. 23 | * 24 | * @throws ParserException 25 | * 26 | * @return array The .ENV parsed data 27 | */ 28 | public static function parse(mixed $config) : array 29 | { 30 | static::checkConfig($config); 31 | return static::parseOrThrow(static function () use ($config) : array { 32 | $contents = \file_get_contents($config); 33 | $contents = \explode(\PHP_EOL, $contents); // @phpstan-ignore-line 34 | $data = []; 35 | foreach ($contents as $line) { 36 | $line = \trim($line); 37 | if ($line === '' || \str_starts_with($line, '#')) { 38 | continue; 39 | } 40 | [$key, $value] = \explode('=', $line, 2); 41 | $key = \trim($key); 42 | $key = \explode('.', $key); 43 | $value = static::getValue($value); 44 | $parent = []; 45 | static::addChild($parent, $key, $value); 46 | $data = \array_replace_recursive($data, $parent); 47 | } 48 | return static::ksortRecursive($data); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Debug/icons/config.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Parsers/XmlParser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | use Framework\Config\Parsers\Extra\JsonXMLElement; 13 | 14 | /** 15 | * Class XmlParser. 16 | * 17 | * @package config 18 | */ 19 | class XmlParser extends Parser 20 | { 21 | /** 22 | * Parses an XML file. 23 | * 24 | * @param mixed $config path to the XML file 25 | * 26 | * @throws ParserException 27 | * 28 | * @return array The XML parsed data 29 | */ 30 | public static function parse(mixed $config) : array 31 | { 32 | static::checkConfig($config); 33 | return static::parseOrThrow(static function () use ($config) : array { 34 | $config = \file_get_contents($config); 35 | $config = \simplexml_load_string($config, JsonXMLElement::class); // @phpstan-ignore-line 36 | $config = \json_encode($config); 37 | $config = \json_decode($config, true); // @phpstan-ignore-line 38 | $data = []; 39 | foreach ($config as $instance => $values) { 40 | foreach ($values as &$value) { 41 | $value = static::parseValue($value); 42 | } 43 | unset($value); 44 | $data[$instance] = $values; 45 | } 46 | return static::ksortRecursive($data); 47 | }); 48 | } 49 | 50 | /** 51 | * @param array|string $value 52 | * 53 | * @return array|bool|float|int|string|null 54 | */ 55 | protected static function parseValue(array | string $value) : array | bool | float | int | string | null 56 | { 57 | if (\is_array($value)) { 58 | foreach ($value as &$val) { 59 | $val = static::parseValue($val); 60 | } 61 | return $value; 62 | } 63 | return static::getValue($value); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aplus/config", 3 | "description": "Aplus Framework Config Library", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "config", 8 | "env", 9 | "ini", 10 | "yaml", 11 | "database" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Natan Felles", 16 | "email": "natanfelles@gmail.com", 17 | "homepage": "https://natanfelles.github.io" 18 | } 19 | ], 20 | "homepage": "https://aplus-framework.com/packages/config", 21 | "support": { 22 | "email": "support@aplus-framework.com", 23 | "issues": "https://github.com/aplus-framework/config/issues", 24 | "forum": "https://aplus-framework.com/forum", 25 | "source": "https://github.com/aplus-framework/config", 26 | "docs": "https://docs.aplus-framework.com/guides/libraries/config/" 27 | }, 28 | "funding": [ 29 | { 30 | "type": "Aplus Sponsor", 31 | "url": "https://aplus-framework.com/sponsor" 32 | } 33 | ], 34 | "require": { 35 | "php": ">=8.3", 36 | "ext-simplexml": "*", 37 | "ext-yaml": "*", 38 | "aplus/database": "^4.0", 39 | "aplus/debug": "^4.3", 40 | "aplus/helpers": "^4.0" 41 | }, 42 | "require-dev": { 43 | "ext-xdebug": "*", 44 | "aplus/coding-standard": "^2.8", 45 | "ergebnis/composer-normalize": "^2.25", 46 | "jetbrains/phpstorm-attributes": "^1.0", 47 | "phpmd/phpmd": "^2.13", 48 | "phpstan/phpstan": "^1.5", 49 | "phpunit/phpunit": "^10.5" 50 | }, 51 | "minimum-stability": "dev", 52 | "prefer-stable": true, 53 | "autoload": { 54 | "psr-4": { 55 | "Framework\\Config\\": "src/" 56 | } 57 | }, 58 | "autoload-dev": { 59 | "psr-4": { 60 | "Tests\\Config\\": "tests/" 61 | } 62 | }, 63 | "config": { 64 | "allow-plugins": { 65 | "ergebnis/composer-normalize": true 66 | }, 67 | "optimize-autoloader": true, 68 | "preferred-install": "dist", 69 | "sort-packages": true 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Parsers/DatabaseParser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | use Framework\Database\Database; 13 | use SensitiveParameter; 14 | 15 | /** 16 | * Class DatabaseParser. 17 | * 18 | * @package config 19 | */ 20 | class DatabaseParser extends Parser 21 | { 22 | /** 23 | * Get config from a database. 24 | * 25 | * @param mixed $config array with configs for database connection: 26 | * host, port, username, password, schema and table 27 | * 28 | * @throws ParserException 29 | * 30 | * @return array The database parsed data 31 | */ 32 | public static function parse(#[SensitiveParameter] mixed $config) : array 33 | { 34 | static::checkConfig($config); 35 | return static::parseOrThrow(static function () use ($config) : array { 36 | try { 37 | $database = new Database($config); 38 | $results = $database->select() 39 | ->from($config['table']) 40 | ->orderBy('key') 41 | ->run() 42 | ->fetchArrayAll(); 43 | } catch (\Exception $exception) { 44 | throw new ParserException( 45 | static::class . ': ' . $exception->getMessage(), 46 | $exception->getCode(), 47 | \E_ERROR, 48 | $exception->getFile(), 49 | $exception->getLine() 50 | ); 51 | } 52 | $data = []; 53 | foreach ($results as $row) { 54 | $key = \explode('.', $row['key']); 55 | $value = static::getValue($row['value']); 56 | $parent = []; 57 | static::addChild($parent, $key, $value); 58 | $data = \array_replace_recursive($data, $parent); 59 | } 60 | return $data; 61 | }); 62 | } 63 | 64 | protected static function checkConfig(#[SensitiveParameter] mixed $config) : void 65 | { 66 | if (!\is_array($config)) { 67 | throw new ParserException(static::class . ' config must be an array'); 68 | } 69 | if (!isset($config['username'])) { 70 | throw new ParserException(static::class . ' config username not set'); 71 | } 72 | if (!isset($config['schema'])) { 73 | throw new ParserException(static::class . ' config schema not set'); 74 | } 75 | if (!isset($config['table'])) { 76 | throw new ParserException(static::class . ' config table not set'); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Parsers/Parser.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Parsers; 11 | 12 | use Closure; 13 | use JetBrains\PhpStorm\Pure; 14 | use SensitiveParameter; 15 | 16 | /** 17 | * Class Parser. 18 | * 19 | * @package config 20 | */ 21 | abstract class Parser 22 | { 23 | /** 24 | * Parse the config output. 25 | * 26 | * @param mixed $config 27 | * 28 | * @throws ParserException If config is invalid or data can not be parsed 29 | * 30 | * @return array The parsed configs 31 | */ 32 | abstract public static function parse(mixed $config) : array; 33 | 34 | /** 35 | * @param Closure $function 36 | * 37 | * @throws ParserException If config data can not be parsed 38 | * 39 | * @return array 40 | */ 41 | protected static function parseOrThrow(Closure $function) : array 42 | { 43 | \set_error_handler(static function ($severity, $message, $file, $line) : void { 44 | $message = static::class . ': ' . $message; 45 | throw new ParserException($message, $severity, $severity, $file, $line); 46 | }); 47 | $result = $function(); 48 | \restore_error_handler(); 49 | return $result; 50 | } 51 | 52 | /** 53 | * Check for config issues. 54 | * 55 | * @param mixed $config The parser configuration 56 | * 57 | * @throws ParserException If config is invalid 58 | */ 59 | protected static function checkConfig(#[SensitiveParameter] mixed $config) : void 60 | { 61 | if (!\is_string($config)) { 62 | throw new ParserException(static::class . ' config must be a string'); 63 | } 64 | $file = \realpath($config); 65 | if ($file === false || !\is_file($file)) { 66 | throw new ParserException('File not found: ' . $config); 67 | } 68 | if (!\is_readable($file)) { 69 | throw new ParserException('File is not readable: ' . $config); 70 | } 71 | } 72 | 73 | /** 74 | * Recursively adds childs to an array tree. 75 | * 76 | * @param array $parent The main array, where the childs will be added 77 | * @param array $childs Childs to add 78 | * @param mixed $value The last child value 79 | */ 80 | protected static function addChild(array &$parent, array $childs, mixed $value) : void 81 | { 82 | $key = \array_shift($childs); 83 | $parent[$key] = []; 84 | if ($childs === []) { 85 | $parent[$key] = $value; 86 | return; 87 | } 88 | static::addChild($parent[$key], $childs, $value); 89 | } 90 | 91 | /** 92 | * Interprets a string value and returns it with a PHP type. 93 | * 94 | * @param string $value The input value 95 | * 96 | * @return array|bool|float|int|string|null The output value 97 | */ 98 | #[Pure] 99 | protected static function getValue(string $value) : array | bool | float | int | string | null 100 | { 101 | $value = \trim($value); 102 | $lowerValue = \strtolower($value); 103 | if ($lowerValue === 'true') { 104 | return true; 105 | } 106 | if ($lowerValue === 'false') { 107 | return false; 108 | } 109 | if ($lowerValue === 'null') { 110 | return null; 111 | } 112 | if (\is_numeric($value) && $value >= \PHP_INT_MIN && $value <= \PHP_INT_MAX) { 113 | return \str_contains($value, '.') ? (float) $value : (int) $value; 114 | } 115 | if (\str_starts_with($value, '"') && \str_ends_with($value, '"')) { 116 | $value = \substr($value, 1, -1); 117 | return \strtr($value, [ 118 | '\"' => '"', 119 | '\\\\' => '\\', 120 | ]); 121 | } 122 | if (\str_starts_with($value, "'") && \str_ends_with($value, "'")) { 123 | return \substr($value, 1, -1); 124 | } 125 | return $value; 126 | } 127 | 128 | /** 129 | * Sort arrays by keys recursively. 130 | * 131 | * @param mixed $value The input value 132 | * 133 | * @return mixed The output value (sorted by keys if the $value is an array) 134 | */ 135 | protected static function ksortRecursive(mixed $value) : mixed 136 | { 137 | if (!\is_array($value)) { 138 | return $value; 139 | } 140 | \ksort($value); 141 | foreach ($value as &$val) { 142 | $val = static::ksortRecursive($val); 143 | } 144 | return $value; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Debug/ConfigCollector.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config\Debug; 11 | 12 | use Framework\Config\Config; 13 | use Framework\Debug\Collector; 14 | use Framework\Debug\Debugger; 15 | use Framework\Helpers\ArraySimple; 16 | 17 | class ConfigCollector extends Collector 18 | { 19 | protected Config $config; 20 | 21 | public function setConfig(Config $config) : static 22 | { 23 | $this->config = $config; 24 | return $this; 25 | } 26 | 27 | public function getActivities() : array 28 | { 29 | $activities = []; 30 | foreach ($this->getData() as $data) { 31 | $activities[] = [ 32 | 'collector' => $this->getName(), 33 | 'class' => static::class, 34 | 'description' => 'Load config file ' . \htmlentities($data['name']), 35 | 'start' => $data['start'], 36 | 'end' => $data['end'], 37 | ]; 38 | } 39 | return $activities; 40 | } 41 | 42 | public function getContents() : string 43 | { 44 | if (!isset($this->config)) { 45 | \ob_start(); 46 | echo '

This collector has not been added to a Config instance.

'; 47 | return \ob_get_clean(); // @phpstan-ignore-line 48 | } 49 | $count = \count($this->getConfigs()); 50 | \ob_start(); 51 | $dir = $this->config->getDir(); 52 | if ($dir !== null): 53 | ?> 54 |

Config directory:

55 | 58 |

configuration have been set.

59 | getTable(); 64 | return \ob_get_clean(); // @phpstan-ignore-line 65 | } 66 | 67 | protected function getTable() : string 68 | { 69 | \ob_start(); 70 | ?> 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | getConfigs() as $config): ?> 82 | 85 | 86 | 87 | 88 | 108 | 123 | 124 | 125 | 126 | 127 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
NameInstancesValuesTime to Load
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | $value): ?> 99 | 100 | 101 | 102 | 103 | 104 | 105 |
KeyType
106 | 107 |
109 | getData() as $value) { 112 | if ($value['name'] === $config['name']) { 113 | echo Debugger::roundSecondsToMilliseconds($value['end'] - $value['start']); 114 | $found = true; 115 | break; 116 | } 117 | } 118 | if (!$found) { 119 | echo 0; 120 | } 121 | ?> 122 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | $value): ?> 138 | 139 | 140 | 141 | 142 | 143 | 144 |
KeyType
145 | 146 |
154 | 160 | */ 161 | protected function getConfigs() : array 162 | { 163 | $result = []; 164 | foreach ($this->config->getAll() as $name => $instances) { 165 | $count = \count($result); 166 | $result[$count]['name'] = $name; 167 | $result[$count]['instances'] = []; 168 | $counter = 0; 169 | foreach ($instances as $instance => $values) { 170 | $result[$count]['instances'][$counter]['name'] = $instance; 171 | $result[$count]['instances'][$counter]['values'] = ArraySimple::convert($values); 172 | foreach ($result[$count]['instances'][$counter]['values'] as &$value) { 173 | $value = \get_debug_type($value); 174 | } 175 | unset($value); 176 | $counter++; 177 | } 178 | } 179 | return $result; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Config; 11 | 12 | use Framework\Config\Debug\ConfigCollector; 13 | use Framework\Helpers\Isolation; 14 | use LogicException; 15 | use SensitiveParameter; 16 | 17 | /** 18 | * Class Config. 19 | * 20 | * @package config 21 | */ 22 | class Config 23 | { 24 | /** 25 | * @var array>> 26 | */ 27 | protected array $configs = []; 28 | protected ?string $configsDir = null; 29 | /** 30 | * @var array>> 31 | */ 32 | protected array $persistence = []; 33 | protected string $suffix; 34 | protected ConfigCollector $debugCollector; 35 | 36 | /** 37 | * Config constructor. 38 | * 39 | * @param array>>|string|null $configs An 40 | * array to set many configs, the config base directory or null 41 | * @param array>> $persistence Configs that 42 | * will always overwrite custom added, loaded or set configs 43 | * @param string $suffix The services filenames suffix used when the config 44 | * directory is set 45 | */ 46 | public function __construct( 47 | #[SensitiveParameter] 48 | array | string | null $configs = null, 49 | #[SensitiveParameter] 50 | array $persistence = [], 51 | string $suffix = '.php' 52 | ) { 53 | if ($configs !== null) { 54 | \is_array($configs) 55 | ? $this->setMany($configs) 56 | : $this->setDir($configs); 57 | } 58 | $this->setPersistence($persistence); 59 | $this->suffix = $suffix; 60 | } 61 | 62 | /** 63 | * Set persistent configs. 64 | * 65 | * @param array> $configs 66 | */ 67 | protected function setPersistence(#[SensitiveParameter] array $configs) : void 68 | { 69 | $this->persistence = $configs; 70 | } 71 | 72 | /** 73 | * Replace configs with persistent configs. 74 | */ 75 | protected function replacePersistence() : void 76 | { 77 | if (empty($this->persistence)) { 78 | return; 79 | } 80 | $this->configs = \array_replace_recursive($this->configs, $this->persistence); 81 | } 82 | 83 | /** 84 | * Get configs with persistence. 85 | * 86 | * @param string $name The service name 87 | * @param string $instance The service instance 88 | * 89 | * @return array The service instance custom configs with 90 | * persistent configs 91 | */ 92 | protected function getPersistentConfigs(string $name, string $instance) : array 93 | { 94 | $this->replacePersistence(); 95 | return $this->configs[$name][$instance] ?? []; 96 | } 97 | 98 | /** 99 | * Set configs to a service instance. 100 | * 101 | * NOTE: These configs will replace an existing instance (except persistence). 102 | * 103 | * @param string $name The service name 104 | * @param array $configs The new configs 105 | * @param string $instance The service instance 106 | * 107 | * @return array The service instance configs 108 | */ 109 | public function set( 110 | string $name, 111 | #[SensitiveParameter] 112 | array $configs, 113 | string $instance = 'default' 114 | ) : array { 115 | $this->configs[$name][$instance] = $configs; 116 | return $this->getPersistentConfigs($name, $instance); 117 | } 118 | 119 | /** 120 | * Get configs by a service instance. 121 | * 122 | * @param string $name The service name 123 | * @param string $instance The service instance 124 | * 125 | * @throws LogicException If the service configs are empty and the Config 126 | * directory is set, and the config file is not found 127 | * 128 | * @return array|null The instance configs as array or null 129 | * if is not set 130 | */ 131 | public function get(string $name, string $instance = 'default') : ?array 132 | { 133 | if (empty($this->configs[$name]) && isset($this->configsDir)) { 134 | $this->load($name); 135 | } 136 | return $this->configs[$name][$instance] ?? null; 137 | } 138 | 139 | /** 140 | * Get service instances configs. 141 | * 142 | * @param string $name The service name 143 | * 144 | * @return array>|null The service instance names as 145 | * keys and its configs as values or null if the service is not set 146 | */ 147 | public function getInstances(string $name) : ?array 148 | { 149 | return $this->configs[$name] ?? null; 150 | } 151 | 152 | /** 153 | * Add configs to a service instance. 154 | * 155 | * NOTE: IF the service instance already exists, the configs will be merged 156 | * 157 | * @param string $name The service name 158 | * @param array $configs The service configs 159 | * @param string $instance The service instance 160 | * 161 | * @return array The service instance configs 162 | */ 163 | public function add( 164 | string $name, 165 | #[SensitiveParameter] 166 | array $configs, 167 | string $instance = 'default' 168 | ) : array { 169 | if (isset($this->configs[$name][$instance])) { 170 | $this->configs[$name][$instance] = \array_replace_recursive( 171 | $this->configs[$name][$instance], 172 | $configs 173 | ); 174 | return $this->getPersistentConfigs($name, $instance); 175 | } 176 | return $this->set($name, $configs, $instance); 177 | } 178 | 179 | /** 180 | * Set many configs in one call. 181 | * 182 | * NOTE: The $configs will replace existing instances (except persistence). 183 | * 184 | * @param array>> $configs The service 185 | * names as keys and its instance configs as values 186 | * 187 | * @return static 188 | */ 189 | public function setMany(#[SensitiveParameter] array $configs) : static 190 | { 191 | foreach ($configs as $name => $values) { 192 | foreach ($values as $instance => $config) { 193 | $this->set($name, $config, $instance); 194 | } 195 | } 196 | return $this; 197 | } 198 | 199 | /** 200 | * Get all configs. 201 | * 202 | * @return array>> All many configs 203 | */ 204 | public function getAll() : array 205 | { 206 | return $this->configs; 207 | } 208 | 209 | /** 210 | * Set the base directory. 211 | * 212 | * @param string $directory Directory path 213 | * 214 | * @throws LogicException If the config directory is not found 215 | * 216 | * @return static 217 | */ 218 | public function setDir(string $directory) : static 219 | { 220 | $dir = \realpath($directory); 221 | if ($dir === false || !\is_dir($dir)) { 222 | throw new LogicException('Config directory not found: ' . $directory); 223 | } 224 | $this->configsDir = $dir . \DIRECTORY_SEPARATOR; 225 | return $this; 226 | } 227 | 228 | /** 229 | * Get the base directory. 230 | * 231 | * @return string|null The directory realpath or null if it was not set 232 | */ 233 | public function getDir() : ?string 234 | { 235 | return $this->configsDir; 236 | } 237 | 238 | /** 239 | * Load a config file. 240 | * 241 | * @param string $name The file name without the directory path and the suffix 242 | * 243 | * @throws LogicException If the config file is not found 244 | * 245 | * @return static 246 | */ 247 | public function load(string $name) : static 248 | { 249 | if (isset($this->debugCollector)) { 250 | $start = \microtime(true); 251 | $this->loadFile($name); 252 | $end = \microtime(true); 253 | $this->debugCollector->addData([ 254 | 'start' => $start, 255 | 'end' => $end, 256 | 'name' => $name, 257 | ]); 258 | return $this; 259 | } 260 | return $this->loadFile($name); 261 | } 262 | 263 | protected function loadFile(string $name) : static 264 | { 265 | $filename = $this->configsDir . $name . $this->suffix; 266 | $filename = \realpath($filename); 267 | if ($filename === false || !\is_file($filename)) { 268 | throw new LogicException('Config file not found: ' . $name); 269 | } 270 | $configs = Isolation::require($filename); 271 | $this->setMany([$name => $configs]); 272 | return $this; 273 | } 274 | 275 | public function setDebugCollector(ConfigCollector $debugCollector) : static 276 | { 277 | $this->debugCollector = $debugCollector; 278 | $this->debugCollector->setConfig($this); 279 | return $this; 280 | } 281 | } 282 | --------------------------------------------------------------------------------