commit a04d49f27326f47a34e380421adee14bf11ea08b Author: Madeorsk Date: Fri Nov 8 12:45:25 2024 +0100 Initialize Nest configuration library. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dfd9ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# IDEA +.idea/ +*.iml + +# Composer +vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b4d938e --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "version": "1.0", + "name": "nest/configuration", + "description": "Nest configuration service.", + "type": "library", + "authors": [ + { + "name": "Madeorsk", + "email": "madeorsk@protonmail.com" + } + ], + "autoload": { + "psr-4": { + "Nest\\Configuration\\": "src/" + } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://code.zeptotech.net/Nest/Core" + } + ], + "require": { + "php": "^8.3", + "nest/core": "dev-main" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..b4dc290 --- /dev/null +++ b/composer.lock @@ -0,0 +1,56 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "456694be683ba1dd09042acde50b7e2a", + "packages": [ + { + "name": "nest/core", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://code.zeptotech.net/Nest/Core", + "reference": "22296b8d7c8b528089189a8d9500395424cb6468" + }, + "require": { + "php": "^8.3" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Nest\\": "src/" + }, + "files": [ + "src/Utils/Array.php", + "src/Utils/Paths.php", + "src/Utils/Reflection.php", + "src/Utils/String.php" + ] + }, + "authors": [ + { + "name": "Madeorsk", + "email": "madeorsk@protonmail.com" + } + ], + "description": "Nest framework core.", + "time": "2024-11-08T11:12:38+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "nest/core": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.3" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/src/Configuration.php b/src/Configuration.php new file mode 100644 index 0000000..77561d3 --- /dev/null +++ b/src/Configuration.php @@ -0,0 +1,164 @@ + + */ + protected array $config; + + /** + * Create a new configuration manager for the given application. + * @param Application $application The application. + */ + public function __construct(protected Application $application) + { + $this->reload(); + } + + /** + * Scan a configuration directory. + * @param string $path The configuration directory path. + * @param string|null $globalContextIdentifier The global context identifier to look for, if there is one. + * @return void + */ + private function scanDirectory(string $path, ?string $globalContextIdentifier = null): void + { + // Get all files in the config path. + $files = scandir($path); + + foreach ($files as $file) + { + if (is_file(path_join($path, $file)) && str_ends_with($file, ".php")) + // If it looks like a configuration file, we try to load it. + $this->loadedFiles[] = path_join($path, $file); + + if (!empty($globalContextIdentifier) && is_dir(path_join($path, $file)) && $globalContextIdentifier == $file) + { // If it looks like the global context custom configuration directory: we try to load all configuration files in it. + $this->scanDirectory(path_join($path, $file)); + } + } + } + + /** + * Reload all configuration files in the configuration path. + * @return void + */ + public function reload(): void + { + // Initialize loaded files array. + $this->loadedFiles = []; + + // Scan the main configuration directory, searching for config files or global context custom config directory. + $this->scanDirectory($this->application->paths()->getConfigPath(), $this->application->getGlobalContextIdentifier()); + sort($this->loadedFiles); // Sort files alphabetically. + + // Reset configuration. + $this->config = []; + + // Read all sorted configuration files. + foreach ($this->loadedFiles as $configFile) + $this->readConfigFile($configFile); + } + + /** + * Read a configuration file and merge it with the current configuration state. + * @param string $filePath The configuration file path. + * @return $this + */ + public function readConfigFile(string $filePath): static + { + // Read the configuration file. + $config = require $filePath; + // Merge the main configuration with the configuration we just read. + $this->config = array_merge($this->config, $config); + + return $this; + } + + + /** + * Recursive function to get a configuration value. + * @param array $configuration Configuration array where to find the key. + * @param string[] $key Configuration key to find the value. + * @param string $fullKey Full configuration key. + * @return mixed The configuration value for the given key. + * @throws ConfigurationValueNotFoundException + */ + private static function _getValue(array $configuration, array $key, string $fullKey): mixed + { + // Get the currently looked key. + $currentKey = array_shift($key); + + if (array_key_exists($currentKey, $configuration)) + { // Configuration key exists. + if (empty($key)) + { // We reached the value, returning it. + return $configuration[$currentKey]; + } + else + { // We must continue to search for the configuration value. + + if (!is_array($configuration[$currentKey])) + // Cannot continue to search because currently pointed configuration value is not an array. + throw new ConfigurationValueNotFoundException($fullKey); + + // Recursive call to get the requested value. + return static::_getValue($configuration[$currentKey], $key, $fullKey); + } + } + else + { // Configuration key is missing. + throw new ConfigurationValueNotFoundException($fullKey); + } + } + + /** + * Get a value from the loaded configuration. + * Throw an exception if the value is not present in configuration array. + * @param string $key Full configuration key, with "." to explore the configuration tree. + * @return mixed The value corresponding to the given key. + * @throws ConfigurationValueNotFoundException + */ + public function getValue(string $key): mixed + { + // Split the key. + $explodedKey = explode(".", $key); + // Call recursive retrieval of the value. + return static::_getValue($this->config, $explodedKey, $key); + } + + /** + * Get a value from the loaded configuration. + * @param string $key Full configuration key, with "." to explore the configuration tree. + * @param mixed|null $default The default value, if there is none in the loaded configuration. + * @return mixed The value corresponding to the given key. + */ + public function val(string $key, mixed $default = null): mixed + { + try + { // Trying to get the value corresponding to the given key. + return $this->getValue($key); + } + catch (ConfigurationValueNotFoundException $exception) + { // Value could not be found, returning the default value. + return $default; + } + } +} diff --git a/src/ConfigurationService.php b/src/ConfigurationService.php new file mode 100644 index 0000000..9bb4afe --- /dev/null +++ b/src/ConfigurationService.php @@ -0,0 +1,30 @@ +configuration = new Configuration($this); + } + + /** + * Configuration service. + * @return Configuration The configuration manager. + */ + public function configuration(): Configuration + { + return $this->configuration; + } +} diff --git a/src/Exceptions/ConfigurationException.php b/src/Exceptions/ConfigurationException.php new file mode 100644 index 0000000..0c0659c --- /dev/null +++ b/src/Exceptions/ConfigurationException.php @@ -0,0 +1,9 @@ +key\".", $code, $previous); + } +}