From 82b358b3ffb1b8ab451327b72a9f8fdfaa16c5a3 Mon Sep 17 00:00:00 2001 From: jamesgober Date: Thu, 5 Dec 2024 18:50:15 -0500 Subject: [PATCH] Updated --- CHANGELOG.md | 36 + docs/php/classes/JG-Config-Config.html | 716 ++++++++++++++---- .../JG-Config-ConfigParserFactory.html | 34 +- .../JG-Config-Exceptions-ConfigException.html | 12 +- ...onfig-Exceptions-ConfigParseException.html | 12 +- ...fig-Exceptions-InvalidParserException.html | 12 +- .../classes/JG-Config-Parsers-ConfParser.html | 21 +- .../classes/JG-Config-Parsers-IniParser.html | 8 +- .../classes/JG-Config-Parsers-JsonParser.html | 8 +- .../JG-Config-Parsers-ParserInterface.html | 8 +- .../classes/JG-Config-Parsers-PhpParser.html | 8 +- .../classes/JG-Config-Parsers-XmlParser.html | 20 +- .../classes/JG-Config-Parsers-YamlParser.html | 8 +- docs/php/files/src-config.html | 10 +- docs/php/files/src-configparserfactory.html | 13 +- .../files/src-exceptions-configexception.html | 8 + .../src-exceptions-configparseexception.html | 8 + ...src-exceptions-invalidparserexception.html | 8 + docs/php/files/src-parsers-confparser.html | 10 +- docs/php/files/src-parsers-iniparser.html | 8 + docs/php/files/src-parsers-jsonparser.html | 8 + .../files/src-parsers-parserinterface.html | 8 + docs/php/files/src-parsers-phpparser.html | 8 + docs/php/files/src-parsers-xmlparser.html | 8 + docs/php/files/src-parsers-yamlparser.html | 8 + docs/php/files/src/Config.php.txt | 382 ++++++---- .../php/files/src/ConfigParserFactory.php.txt | 41 +- .../src/Exceptions/ConfigException.php.txt | 1 + .../Exceptions/ConfigParseException.php.txt | 1 + .../Exceptions/InvalidParserException.php.txt | 1 + docs/php/files/src/Parsers/ConfParser.php.txt | 64 +- docs/php/files/src/Parsers/IniParser.php.txt | 1 + docs/php/files/src/Parsers/JsonParser.php.txt | 1 + .../files/src/Parsers/ParserInterface.php.txt | 1 + docs/php/files/src/Parsers/PhpParser.php.txt | 1 + docs/php/files/src/Parsers/XmlParser.php.txt | 1 + docs/php/files/src/Parsers/YamlParser.php.txt | 1 + docs/php/js/searchIndex.js | 47 +- src/Parsers/ConfParser.php | 63 +- tests/config/config.conf | 11 +- 40 files changed, 1156 insertions(+), 469 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9397d13..2d91213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,40 @@ This file tracks all notable changes made to the project, including new features --> +  + + +## [1.0.0-RC.5] - 2024-12-05 + +### Added +- **Custom Parser Support**: Introduced the ability to dynamically register and utilize custom parsers, enhancing extensibility. +- **Non-UTF-8 Handling**: Added improved handling for files with non-UTF-8 encodings, ensuring compatibility across varied environments. +- **Performance Tests**: Included benchmarks and stress tests to validate library performance under high-load scenarios. +- **Extended Cache Operations**: Enhanced cache handling with robust invalidation and expiration logic. +- **Enhanced Flattening Control**: Improved options for enabling/disabling key flattening, offering better flexibility for hierarchical configurations. + +### Changed +- **Default Flattening Behavior**: Updated the `Config` constructor to accept an explicit flag for flattening, providing better control and eliminating unexpected defaults. +- **Parsing Enhancements**: + - Optimized performance for **CONF parsing** by leveraging `file()` for efficient line-by-line processing while preserving spacing and comments. + - Improved regex-based parsing to handle edge cases with clean and flexible key-value extraction. + - Enhanced exception messages to offer more descriptive debugging feedback. +- **Error Handling**: Unified error handling across parsers to provide more informative and actionable exceptions. +- **Streamlining Tests**: + - Refactored `ConfigTest` for clarity, expanded coverage, and consistent structure. + - Reworked performance test cases to accurately benchmark operations under diverse conditions. + +### Fixed +- **Custom Parser Registration**: Resolved issues where dynamically registered custom parsers were not recognized during runtime. +- **UTF-8 Validation**: Addressed conflicts where systems forced UTF-8 encoding, bypassing intended checks for invalid encoding. +- **Max Depth Exceeded**: Fixed edge cases in nested configurations exceeding max depth, ensuring the error is thrown correctly. +- **Cache Structure Validation**: Resolved potential inconsistencies in cache format validation and loading. + +### Removed +- **Test Parsers from Production**: Completely removed test parsers from production code to maintain separation of concerns and a cleaner production environment. +   @@ -130,6 +164,8 @@ This file tracks all notable changes made to the project, including new features + +[1.0.0-RC.5]: https://github.com/jamesgober/Config/compare/v1.0.0-RC.4...v1.0.0-RC.5 [1.0.0-RC.4]: https://github.com/jamesgober/Config/compare/v1.0.0-RC3...v1.0.0-RC.4 [1.0.0-RC.3]: https://github.com/jamesgober/Config/compare/v1.0.0-RC.2...v1.0.0-RC3 [1.0.0-RC.2]: https://github.com/jamesgober/Config/compare/v1.0.0-Rc.1...v1.0.0-RC.2 diff --git a/docs/php/classes/JG-Config-Config.html b/docs/php/classes/JG-Config-Config.html index 1511576..49104e6 100644 --- a/docs/php/classes/JG-Config-Config.html +++ b/docs/php/classes/JG-Config-Config.html @@ -125,9 +125,9 @@

Configuration Manager

@@ -162,6 +162,12 @@

+
+ EXPIRE_HALF_DAY + +  = 43200 +
+
EXPIRE_NEVER @@ -175,6 +181,12 @@

 = 86400

+
+ EXPIRE_ONE_HOUR + +  = 3600 +
+
EXPIRE_ONE_MONTH @@ -208,7 +220,8 @@

 : array<string, mixed>

-
Primary configuration storage.
+
Primary configuration storage, including unique keys from +grouped configurations.
$configPath @@ -229,7 +242,8 @@

 : array<string, array<string, string>>

-
Grouped configuration storage.
+
Grouped configuration storage, allowing access by group or +unique key.
$maxDepth @@ -265,7 +279,7 @@

 : void

-
Clears all configuration data.
+
Clears the configuration manager.
delete() @@ -335,14 +349,42 @@

 : bool

-
Loads the configuration and groups from a cache file.
+
Loads configuration and groups from a cache file.
+ +
+ loadFromStream() + +  : bool +
+
Loads configuration data from a stream (e.g., PSR-7 StreamInterface).
+ +
+ loadMultiple() + +  : void +
+
Loads configuration data from multiple files.
+ +
+ mergeConfig() + +  : void +
+
Merges new configuration data into the existing configuration.
+ +
+ resetGroups() + +  : void +
+
Resets the configuration groups to an empty state.
saveCache()  : bool
-
Saves the configuration and groups data to a cache file.
+
Saves the current configuration to a cache file.
setConfigPath() @@ -382,9 +424,9 @@

flattenArray() -  : array<string|int, mixed> +  : array<string, mixed>
-
Flattens a multidimensional array into dot-notated keys and groups.
+
Flattens a nested configuration array.
resolvePath() @@ -393,6 +435,13 @@

Resolves the full file path.
+
+ validateConfigStructure() + +  : void +
+
Validates the structure of configuration and group data.
+

@@ -405,6 +454,38 @@

+

+ EXPIRE_HALF_DAY + + +

+ + + + + + + public + mixed + EXPIRE_HALF_DAY + = 43200 + + + + + + + + + +
+

EXPIRE_NEVER @@ -414,9 +495,9 @@

Common expiration durations in seconds.

@@ -447,9 +528,9 @@

@@ -468,6 +549,38 @@

+

+
+

+ EXPIRE_ONE_HOUR + + +

+ + + + + + + public + mixed + EXPIRE_ONE_HOUR + = 3600 + + + + + + + + +

@@ -479,9 +592,9 @@

@@ -511,9 +624,9 @@

@@ -559,9 +672,9 @@

Indicates whether the configuration cache has been loaded.

@@ -597,12 +710,13 @@

-

Primary configuration storage.

+

Primary configuration storage, including unique keys from +grouped configurations.

@@ -611,10 +725,7 @@

$config = [] -

Stores all configuration key-value pairs, including unique keys -generated from grouped configurations.

-
- + @@ -638,9 +749,9 @@

Default configuration directory path.

@@ -676,9 +787,9 @@

Whether configuration keys should be flattened.

@@ -714,12 +825,13 @@

-

Grouped configuration storage.

+

Grouped configuration storage, allowing access by group or +unique key.

@@ -728,10 +840,7 @@

$groups = [] -

Stores configurations by groups, allowing access by original -source filename or unique key.

-
- + @@ -755,9 +864,9 @@

Maximum allowed depth for nested configurations.

@@ -767,19 +876,17 @@

private int $maxDepth - = 10 - -

This property sets a limit on how deeply nested configurations can be -processed by the flattening logic. Exceeding this depth will result in -a ConfigException being thrown to prevent performance degradation and -manageability issues.

-

Default: 10

-
+ = 3 -

Maximum depth for nested configurations.

+

This property sets a limit on how deeply nested configurations +can be processed by the flattening logic. Exceeding this depth +will result ina ConfigException being thrown to prevent +performance degradation and manageability issues.

+

Default: 3

+ @@ -806,9 +913,9 @@

Constructor.

@@ -820,7 +927,9 @@

- +

Initializes the configuration manager with optional defaults.

+
+

Parameters
@@ -828,7 +937,7 @@
Parameters
: string|null = null
-

Default path for config files (optional).

+

Optional path for configuration files.

@@ -863,9 +972,9 @@

Adds or updates a configuration value.

@@ -877,8 +986,7 @@

-

If the key contains dots, indicating a grouped or flattened list, -the method ensures the group and config data are updated accordingly.

+

Optimized to ensure group structures are maintained efficiently.

Parameters
@@ -923,12 +1031,12 @@

-

Clears all configuration data.

+

Clears the configuration manager.

public @@ -959,9 +1067,9 @@

Deletes a configuration key or group.

@@ -973,9 +1081,8 @@

-

If the provided key corresponds to a group, all keys in that group -will be removed from the configuration array before the group itself -is deleted.

+

Removes a specific key or group from the configuration. If the key represents +a group, all associated keys within the group will also be removed.

Parameters
@@ -992,6 +1099,23 @@
Parameters

+
+ Tags + + +
+
+
+ throws +
+
+ ConfigException + +

If the group structure is invalid.

+
+ +
+
@@ -1011,9 +1135,9 @@

Deletes a cache file.

@@ -1033,7 +1157,7 @@

Parameters
: string
-

The file path of the cache to delete.

+

Path to cache.

@@ -1047,7 +1171,7 @@
Parameters
Return values
bool — -

True if the cache was deleted successfully, false otherwise.

+

True if deleted.

@@ -1067,9 +1191,9 @@

Retrieves configuration from a file.

@@ -1123,9 +1247,9 @@

Retrieves a configuration value.

@@ -1188,9 +1312,9 @@

Retrieves the entire configuration array.

@@ -1232,9 +1356,9 @@

Retrieves all configuration groups.

@@ -1276,9 +1400,9 @@

Checks if a specific configuration key exists.

@@ -1290,10 +1414,7 @@

-

This method first checks if the key exists in the main configuration. -If not, it then checks whether the key corresponds to a group in the grouped configurations.

-
- +

Parameters
@@ -1301,7 +1422,7 @@
Parameters
: string
-

The configuration key to check.

+

The key to check.

@@ -1315,7 +1436,7 @@
Parameters
Return values
bool — -

True if the key or group exists; otherwise, false.

+

True if the key exists, false otherwise.

@@ -1335,9 +1456,9 @@

Checks if the configuration cache has been loaded.

@@ -1359,7 +1480,7 @@

Return values
bool — -

True if the cache has been loaded, false otherwise.

+

True if the cache has been loaded; otherwise, false.

@@ -1379,9 +1500,9 @@

Loads configuration data from a file.

@@ -1393,10 +1514,9 @@

-

Reads the specified file, parses its contents, and merges -the data into the existing configuration. When flattening is enabled, -keys are grouped by the file's base name to create unique keys. If a key -already exists, it will be overwritten.

+

Reads the specified file, parses its contents, and merges the data +into the existing configuration. If the cache is already loaded, +this method skips further processing.

Parameters
@@ -1406,7 +1526,7 @@
Parameters
: string|null = null
-

Absolute or relative path to the file.

+

The file path or name (optional).

@@ -1457,12 +1577,12 @@

-

Loads the configuration and groups from a cache file.

+

Loads configuration and groups from a cache file.

public @@ -1479,13 +1599,103 @@
Parameters
: string
-

The file path of the cache.

+

Path to the cache file.

+
+ +
+

+ + +
+ Tags + + +
+
+
+ throws +
+
+ ConfigException + +

If the cache format is invalid.

+
+ +
+
+ + + +
+
Return values
+ bool + — +

True if the cache was loaded successfully; otherwise, false.

+
+ +
+ +

+
+

+ loadFromStream() + + +

+ + +

Loads configuration data from a stream (e.g., PSR-7 StreamInterface).

+ + + public + loadFromStream(StreamInterface $stream) : bool + +
+
+ + +
Parameters
+
+
+ $stream + : StreamInterface +
+
+

A PSR-7 compatible stream.

+
+ Tags + + +
+
+
+ throws +
+
+ ConfigException + +

If the stream cannot be parsed.

+
+ +
+
@@ -1493,11 +1703,160 @@
Parameters
Return values
bool — -

True if the cache was loaded successfully, false otherwise.

+

True if the configuration was successfully loaded.

+
+
+

+ loadMultiple() + + +

+ + +

Loads configuration data from multiple files.

+ + + public + loadMultiple(array<string|int, string> $filePaths) : void + +
+
+ + +
Parameters
+
+
+ $filePaths + : array<string|int, string> +
+
+

List of file paths.

+
+ +
+
+ + +
+ Tags + + +
+
+
+ throws +
+
+ ConfigException + +

If any file cannot be loaded or parsed.

+
+ +
+
+ + + + +
+
+

+ mergeConfig() + + +

+ + +

Merges new configuration data into the existing configuration.

+ + + public + mergeConfig(array<string, mixed> $newConfig) : void + +
+
+ + +
Parameters
+
+
+ $newConfig + : array<string, mixed> +
+
+

The new configuration data.

+
+ +
+
+ + + + + + +
+
+

+ resetGroups() + + +

+ + +

Resets the configuration groups to an empty state.

+ + + public + resetGroups() : void + +
+
+ + + + + + + +
-

Saves the configuration and groups data to a cache file.

+

Saves the current configuration to a cache file.

public @@ -1535,7 +1894,7 @@
Parameters
: string
-

The file path to save the cache.

+

File path to save cache.

@@ -1544,7 +1903,7 @@
Parameters
: int = 0
-

Expiration time in seconds (0 for no expiration).

+

Expiration time (0 for no expiration).

@@ -1558,7 +1917,7 @@
Parameters
Return values
bool — -

True if the cache was saved successfully, false otherwise.

+

True if successful; otherwise, false.

@@ -1578,9 +1937,9 @@

Sets the configuration directory path.

@@ -1619,7 +1978,7 @@

ConfigException -

If the path is invalid.

+

If the provided path is invalid.

@@ -1643,9 +2002,9 @@

Enables or disables key flattening.

@@ -1691,9 +2050,9 @@

Sets the maximum depth for nested configurations.

@@ -1756,9 +2115,9 @@

Inserts configuration data into the manager.

@@ -1813,9 +2172,9 @@

Parses a configuration file.

@@ -1886,31 +2245,28 @@

-

Flattens a multidimensional array into dot-notated keys and groups.

+

Flattens a nested configuration array.

private - flattenArray(array<string|int, mixed> $data, string $baseKey[, int $currentDepth = 0 ]) : array<string|int, mixed> + flattenArray(array<string, mixed> $data, string $baseKey) : array<string, mixed>
-

This method recursively processes a nested array, converting its keys -into dot-notated strings for flattened storage. It tracks the current -depth to ensure the nesting does not exceed the configured $maxDepth. -If the maximum depth is exceeded, a ConfigException is thrown.

+

Converts nested arrays into dot-notated keys.

Parameters
$data - : array<string|int, mixed> + : array<string, mixed>

The input array to flatten.

@@ -1922,18 +2278,7 @@
Parameters
: string
-

The base key for flattening, used for constructing -dot-notated keys.

-
- -
-
- $currentDepth - : int - = 0
-
-

The current depth of the recursion, used to enforce -the maximum depth constraint (default: 0).

+

The base key for flattening.

@@ -1952,7 +2297,7 @@
ConfigException -

If the maximum depth for nested configurations is exceeded.

+

If the maximum depth is exceeded.

@@ -1962,15 +2307,7 @@
Return values
- array<string|int, mixed> - — -

An array containing two elements:

-
    -
  • The first element is an associative array of flattened key-value pairs.
  • -
  • The second element is an associative array of flattened key mappings to original keys.
  • -
-
- + array<string, mixed>

@@ -1988,9 +2325,9 @@

Resolves the full file path.

@@ -2029,6 +2366,80 @@

Return values
+ +
+

+ validateConfigStructure() + + +

+ + +

Validates the structure of configuration and group data.

+ + + private + validateConfigStructure(array<string|int, mixed> $config, array<string|int, mixed> $groups) : void + +
+
+ + +
Parameters
+
+
+ $config + : array<string|int, mixed> +
+
+

The configuration data.

+
+ +
+
+ $groups + : array<string|int, mixed> +
+
+

The groups data.

+
+ +
+
+ + +
+ Tags + + +
+
+
+ throws +
+
+ ConfigException + +

If the structures are invalid.

+
+ +
+
+ + + +
@@ -2143,8 +2554,10 @@
Return values
  • Constants
  • @@ -2178,6 +2591,10 @@
    Return values
  • isCacheLoaded()
  • load()
  • loadCache()
  • +
  • loadFromStream()
  • +
  • loadMultiple()
  • +
  • mergeConfig()
  • +
  • resetGroups()
  • saveCache()
  • setConfigPath()
  • setFlatten()
  • @@ -2186,6 +2603,7 @@
    Return values
  • parse()
  • flattenArray()
  • resolvePath()
  • +
  • validateConfigStructure()
  • diff --git a/docs/php/classes/JG-Config-ConfigParserFactory.html b/docs/php/classes/JG-Config-ConfigParserFactory.html index 2ae5a8f..3355e18 100644 --- a/docs/php/classes/JG-Config-ConfigParserFactory.html +++ b/docs/php/classes/JG-Config-ConfigParserFactory.html @@ -125,16 +125,18 @@

    ConfigParserFactory

    -

    Factory class for creating and managing configuration parsers. Supports default parsers -for common file formats such as JSON, PHP, and XML, and allows custom parsers to be registered.

    +

    Factory class for creating and managing configuration parsers. +Supports default parsers for common file formats such as conf, +ini, json, PHP, xml, yaml/yml, and allows custom parsers to be +registered.

    @@ -243,9 +245,9 @@

    Default mapping of file extensions to parser class names.

    @@ -286,9 +288,9 @@

    Creates a parser instance based on the file's extension.

    @@ -359,9 +361,9 @@

    Retrieves the list of all registered parsers.

    @@ -403,9 +405,9 @@

    Bulk registers parsers from an associative array.

    @@ -468,9 +470,9 @@

    Registers a custom parser, allowing users to extend or override default parsers.

    @@ -543,9 +545,9 @@

    Unregisters a parser for a specific file extension.

    diff --git a/docs/php/classes/JG-Config-Exceptions-ConfigException.html b/docs/php/classes/JG-Config-Exceptions-ConfigException.html index 1a921fb..778fdde 100644 --- a/docs/php/classes/JG-Config-Exceptions-ConfigException.html +++ b/docs/php/classes/JG-Config-Exceptions-ConfigException.html @@ -130,9 +130,9 @@

    ConfigException

    @@ -213,9 +213,9 @@

    Constructs a new ConfigException instance.

    @@ -279,9 +279,9 @@

    Returns a string representation of the exception.

    diff --git a/docs/php/classes/JG-Config-Exceptions-ConfigParseException.html b/docs/php/classes/JG-Config-Exceptions-ConfigParseException.html index 2d19ca7..32d4ed0 100644 --- a/docs/php/classes/JG-Config-Exceptions-ConfigParseException.html +++ b/docs/php/classes/JG-Config-Exceptions-ConfigParseException.html @@ -130,9 +130,9 @@

    ConfigParseException

    @@ -210,9 +210,9 @@

    Constructs a new ConfigParseException instance.

    @@ -276,9 +276,9 @@

    Provides a string representation of the exception.

    diff --git a/docs/php/classes/JG-Config-Exceptions-InvalidParserException.html b/docs/php/classes/JG-Config-Exceptions-InvalidParserException.html index 3719a6e..7e8c97a 100644 --- a/docs/php/classes/JG-Config-Exceptions-InvalidParserException.html +++ b/docs/php/classes/JG-Config-Exceptions-InvalidParserException.html @@ -130,9 +130,9 @@

    InvalidParserException

    @@ -211,9 +211,9 @@

    Constructs a new InvalidParserException.

    @@ -277,9 +277,9 @@

    Provides a string representation of the exception.

    diff --git a/docs/php/classes/JG-Config-Parsers-ConfParser.html b/docs/php/classes/JG-Config-Parsers-ConfParser.html index f09df6d..3e3facb 100644 --- a/docs/php/classes/JG-Config-Parsers-ConfParser.html +++ b/docs/php/classes/JG-Config-Parsers-ConfParser.html @@ -130,16 +130,17 @@

    ConfParser

    Parses key-value pairs from CONF configuration files. This parser supports -flexible key-value syntax with either : or = as separators.

    +clean formatting with = as the separator. It skips comments and empty lines +and trims whitespace while preserving inner spaces.

    @@ -218,9 +219,9 @@

    Parses a CONF configuration file into an associative array.

    @@ -232,10 +233,8 @@

    -

    The method reads key-value pairs from the file, where keys and values are separated -by either : or =. Leading and trailing whitespace is trimmed for both keys and values.

    -

    Example:

    -
    host: localhost
    +        

    Example:

    +
    host = localhost
     user = root
     
    @@ -298,9 +297,9 @@

    Converts string values to their respective PHP scalar types where applicable.

    diff --git a/docs/php/classes/JG-Config-Parsers-IniParser.html b/docs/php/classes/JG-Config-Parsers-IniParser.html index 2ba2cb8..93eb6fb 100644 --- a/docs/php/classes/JG-Config-Parsers-IniParser.html +++ b/docs/php/classes/JG-Config-Parsers-IniParser.html @@ -130,9 +130,9 @@

    IniParser

    @@ -211,9 +211,9 @@

    Parses an INI file and returns its contents as an associative array.

    diff --git a/docs/php/classes/JG-Config-Parsers-JsonParser.html b/docs/php/classes/JG-Config-Parsers-JsonParser.html index a1a08d1..728f547 100644 --- a/docs/php/classes/JG-Config-Parsers-JsonParser.html +++ b/docs/php/classes/JG-Config-Parsers-JsonParser.html @@ -130,9 +130,9 @@

    JsonParser

    @@ -211,9 +211,9 @@

    Parses a JSON configuration file and returns its contents as an associative array.

    diff --git a/docs/php/classes/JG-Config-Parsers-ParserInterface.html b/docs/php/classes/JG-Config-Parsers-ParserInterface.html index 4218d3c..8310e7e 100644 --- a/docs/php/classes/JG-Config-Parsers-ParserInterface.html +++ b/docs/php/classes/JG-Config-Parsers-ParserInterface.html @@ -118,9 +118,9 @@

    ParserInterface

    @@ -193,9 +193,9 @@

    Parses a configuration file and returns its contents as an associative array.

    diff --git a/docs/php/classes/JG-Config-Parsers-PhpParser.html b/docs/php/classes/JG-Config-Parsers-PhpParser.html index 0887cff..6c10df0 100644 --- a/docs/php/classes/JG-Config-Parsers-PhpParser.html +++ b/docs/php/classes/JG-Config-Parsers-PhpParser.html @@ -130,9 +130,9 @@

    PhpParser

    @@ -211,9 +211,9 @@

    Parses a PHP configuration file and returns its contents as an array.

    diff --git a/docs/php/classes/JG-Config-Parsers-XmlParser.html b/docs/php/classes/JG-Config-Parsers-XmlParser.html index 08e9ee5..6ca9b40 100644 --- a/docs/php/classes/JG-Config-Parsers-XmlParser.html +++ b/docs/php/classes/JG-Config-Parsers-XmlParser.html @@ -130,9 +130,9 @@

    XmlParser

    @@ -232,9 +232,9 @@

    Parses an XML configuration file and returns its contents as an associative array.

    @@ -305,9 +305,9 @@

    Converts a `SimpleXMLElement` object into an associative array or scalar value.

    @@ -361,9 +361,9 @@

    Handles XML parsing errors by throwing an exception with detailed information.

    @@ -426,9 +426,9 @@

    Converts string values to their respective PHP scalar types where applicable.

    diff --git a/docs/php/classes/JG-Config-Parsers-YamlParser.html b/docs/php/classes/JG-Config-Parsers-YamlParser.html index a994771..4f8f9a6 100644 --- a/docs/php/classes/JG-Config-Parsers-YamlParser.html +++ b/docs/php/classes/JG-Config-Parsers-YamlParser.html @@ -130,9 +130,9 @@

    YamlParser

    @@ -234,9 +234,9 @@

    Parses a YAML configuration file and returns its contents as an associative array.

    diff --git a/docs/php/files/src-config.html b/docs/php/files/src-config.html index 1f8a7a8..8c67ed5 100644 --- a/docs/php/files/src-config.html +++ b/docs/php/files/src-config.html @@ -107,7 +107,7 @@

    Config.php

    The core configuration management class. Provides methods to load, parse, and manage configuration data from -various sources, including JSON, YAML, XML, and more.

    +various sources, including conf, ini, json, yaml/yml, xml, and more.

    PHP version 8.2

    @@ -125,6 +125,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-configparserfactory.html b/docs/php/files/src-configparserfactory.html index a3b617a..f4cd2ef 100644 --- a/docs/php/files/src-configparserfactory.html +++ b/docs/php/files/src-configparserfactory.html @@ -105,8 +105,9 @@

    ConfigParserFactory.php

    ConfigParserFactory.php

    -

    Factory class for creating and managing configuration parsers based on file extensions. -Allows registration of custom parsers and overrides for default parsers.

    +

    Factory class for creating and managing configuration parsers +based on file extensions. Allows registration of custom parsers +and overrides for default parsers.

    PHP version 8.2

    @@ -124,6 +125,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-exceptions-configexception.html b/docs/php/files/src-exceptions-configexception.html index 471aa78..03c8bc1 100644 --- a/docs/php/files/src-exceptions-configexception.html +++ b/docs/php/files/src-exceptions-configexception.html @@ -124,6 +124,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-exceptions-configparseexception.html b/docs/php/files/src-exceptions-configparseexception.html index 9f547ee..d96da64 100644 --- a/docs/php/files/src-exceptions-configparseexception.html +++ b/docs/php/files/src-exceptions-configparseexception.html @@ -123,6 +123,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-exceptions-invalidparserexception.html b/docs/php/files/src-exceptions-invalidparserexception.html index b47c54d..be5c19f 100644 --- a/docs/php/files/src-exceptions-invalidparserexception.html +++ b/docs/php/files/src-exceptions-invalidparserexception.html @@ -123,6 +123,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-confparser.html b/docs/php/files/src-parsers-confparser.html index cda16be..056315e 100644 --- a/docs/php/files/src-parsers-confparser.html +++ b/docs/php/files/src-parsers-confparser.html @@ -106,7 +106,7 @@

    ConfParser.php

    Parses key-value pairs from custom CONF configuration files. These files -typically use : or = as separators for key-value pairs.

    +typically use = as the separator for key-value pairs.

    PHP version 8.2

    @@ -124,6 +124,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-iniparser.html b/docs/php/files/src-parsers-iniparser.html index dbb4611..c895f81 100644 --- a/docs/php/files/src-parsers-iniparser.html +++ b/docs/php/files/src-parsers-iniparser.html @@ -123,6 +123,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-jsonparser.html b/docs/php/files/src-parsers-jsonparser.html index 21fa05a..b92cf48 100644 --- a/docs/php/files/src-parsers-jsonparser.html +++ b/docs/php/files/src-parsers-jsonparser.html @@ -124,6 +124,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-parserinterface.html b/docs/php/files/src-parsers-parserinterface.html index 682b4cb..8434517 100644 --- a/docs/php/files/src-parsers-parserinterface.html +++ b/docs/php/files/src-parsers-parserinterface.html @@ -125,6 +125,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-phpparser.html b/docs/php/files/src-parsers-phpparser.html index 8671d82..2b673d5 100644 --- a/docs/php/files/src-parsers-phpparser.html +++ b/docs/php/files/src-parsers-phpparser.html @@ -124,6 +124,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-xmlparser.html b/docs/php/files/src-parsers-xmlparser.html index 80bc1e7..735d068 100644 --- a/docs/php/files/src-parsers-xmlparser.html +++ b/docs/php/files/src-parsers-xmlparser.html @@ -124,6 +124,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src-parsers-yamlparser.html b/docs/php/files/src-parsers-yamlparser.html index 898938b..6f158a8 100644 --- a/docs/php/files/src-parsers-yamlparser.html +++ b/docs/php/files/src-parsers-yamlparser.html @@ -123,6 +123,14 @@
    1.0.0 + +
    + since +
    +
    + 1.0.0 + +
    author diff --git a/docs/php/files/src/Config.php.txt b/docs/php/files/src/Config.php.txt index 6666c23..eab7da6 100644 --- a/docs/php/files/src/Config.php.txt +++ b/docs/php/files/src/Config.php.txt @@ -4,36 +4,42 @@ * * The core configuration management class. * Provides methods to load, parse, and manage configuration data from - * various sources, including JSON, YAML, XML, and more. + * various sources, including conf, ini, json, yaml/yml, xml, and more. * * PHP version 8.2 * * @package JG\Config * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License * @copyright 2024 James Gober (https://jamesgober.com) - */ +*/ declare(strict_types=1); namespace JG\Config; use \ltrim; +use \mkdir; use \rtrim; use \is_dir; -use \strtok; -use \explode; -use \implode; +use \unlink; use \is_file; +use \is_array; use \strtolower; use \array_merge; use \array_shift; +use \json_decode; +use \json_encode; use \preg_replace; -use \str_contains; use \array_key_exists; -use \JG\Config\ConfigParserFactory; -use \JG\Config\Exceptions\ConfigException; +use \file_put_contents; +use \JSON_PRETTY_PRINT; +use \JSON_THROW_ON_ERROR; +use JG\Config\ConfigParserFactory; +use Psr\Http\Message\StreamInterface; +use JG\Config\Exceptions\ConfigException; /** * Configuration Manager @@ -46,73 +52,71 @@ use \JG\Config\Exceptions\ConfigException; */ class Config { - /** - * Common expiration durations in seconds. - */ - public const EXPIRE_NEVER = 0; - public const EXPIRE_ONE_DAY = 86400; - public const EXPIRE_ONE_WEEK = 604800; + /** Common expiration durations in seconds. */ + public const EXPIRE_NEVER = 0; + public const EXPIRE_ONE_HOUR = 3600; + public const EXPIRE_HALF_DAY = 43200; + public const EXPIRE_ONE_DAY = 86400; + public const EXPIRE_ONE_WEEK = 604800; public const EXPIRE_ONE_MONTH = 2592000; /** * Default configuration directory path. * - * @var string|null + * @var string|null $configPath */ private ?string $configPath = null; /** * Whether configuration keys should be flattened. * - * @var bool + * @var bool $flatten */ private bool $flatten = true; /** * Maximum allowed depth for nested configurations. * - * This property sets a limit on how deeply nested configurations can be - * processed by the flattening logic. Exceeding this depth will result in - * a `ConfigException` being thrown to prevent performance degradation and - * manageability issues. + * This property sets a limit on how deeply nested configurations + * can be processed by the flattening logic. Exceeding this depth + * will result ina `ConfigException` being thrown to prevent + * performance degradation and manageability issues. * - * Default: 10 + * Default: 3 * - * @var int Maximum depth for nested configurations. + * @var int $maxDepth */ - private int $maxDepth = 10; + private int $maxDepth = 3; /** * Indicates whether the configuration cache has been loaded. * - * @var bool + * @var bool$cacheLoaded */ private bool $cacheLoaded = false; /** - * Grouped configuration storage. - * - * Stores configurations by groups, allowing access by original - * source filename or unique key. + * Grouped configuration storage, allowing access by group or + * unique key. * - * @var array> + * @var array> $groups */ private array $groups = []; /** - * Primary configuration storage. + * Primary configuration storage, including unique keys from + * grouped configurations. * - * Stores all configuration key-value pairs, including unique keys - * generated from grouped configurations. - * - * @var array + * @var array $config */ private array $config = []; /** * Constructor. * - * @param string|null $configPath Default path for config files (optional). + * Initializes the configuration manager with optional defaults. + * + * @param string|null $configPath Optional path for configuration files. * @param bool $flattenConfig Whether to enable key flattening (default: true). */ public function __construct(?string $configPath = null, bool $flattenConfig = true) @@ -127,7 +131,7 @@ class Config * @param string|null $path Absolute or relative path to the config directory. * @return void * - * @throws ConfigException If the path is invalid. + * @throws ConfigException If the provided path is invalid. */ public function setConfigPath(?string $path = null): void { @@ -153,6 +157,7 @@ class Config * * @param int $depth Maximum depth to allow. * @return void + * * @throws ConfigException If the depth is less than 1. */ public function setMaxDepth(int $depth): void @@ -166,7 +171,7 @@ class Config /** * Checks if the configuration cache has been loaded. * - * @return bool True if the cache has been loaded, false otherwise. + * @return bool True if the cache has been loaded; otherwise, false. */ public function isCacheLoaded(): bool { @@ -181,15 +186,11 @@ class Config */ private function resolvePath(?string $filePath): ?string { - if (!$filePath) { + if (!$filePath || (!is_file($filePath) && !$this->configPath)) { return null; } - if (is_file($filePath)) { - return $filePath; - } - - return $this->configPath ? $this->configPath . ltrim($filePath, '/') : null; + return is_file($filePath) ? $filePath : $this->configPath . ltrim($filePath, '/'); } /** @@ -206,46 +207,53 @@ class Config } /** - * Flattens a multidimensional array into dot-notated keys and groups. + * Merges new configuration data into the existing configuration. * - * This method recursively processes a nested array, converting its keys - * into dot-notated strings for flattened storage. It tracks the current - * depth to ensure the nesting does not exceed the configured `$maxDepth`. - * If the maximum depth is exceeded, a `ConfigException` is thrown. + * @param array $newConfig The new configuration data. + * @return void + */ + public function mergeConfig(array $newConfig): void + { + $this->config = array_merge($this->config, $newConfig); + } + + /** + * Flattens a nested configuration array. * - * @param array $data The input array to flatten. - * @param string $baseKey The base key for flattening, used for constructing - * dot-notated keys. - * @param int $currentDepth The current depth of the recursion, used to enforce - * the maximum depth constraint (default: 0). - * - * @return array An array containing two elements: - * - The first element is an associative array of flattened key-value pairs. - * - The second element is an associative array of flattened key mappings to original keys. + * Converts nested arrays into dot-notated keys. + * + * @param array $data The input array to flatten. + * @param string $baseKey The base key for flattening. + * @param int $depth Current depth of the flattening process. + * @return array * - * @throws ConfigException If the maximum depth for nested configurations is exceeded. + * @throws ConfigException If the maximum depth is exceeded. */ - private function flattenArray(array $data, string $baseKey, int $currentDepth = 0): array + private function flattenArray(array $data, string $baseKey): array { - if ($currentDepth > $this->maxDepth) { - throw new ConfigException( - "Maximum depth of {$this->maxDepth} exceeded while processing configuration." - ); - } - $flattenedData = []; $groups = []; + $stack = [['data' => $data, 'key' => $baseKey, 'depth' => 0]]; + + while ($stack) { + $current = array_pop($stack); + $currentData = $current['data']; + $currentKey = $current['key']; + $currentDepth = $current['depth']; + + if ($currentDepth >= $this->maxDepth) { // Correctly handle "max depth exceeded" + throw new ConfigException("Maximum depth of {$this->maxDepth} exceeded at key: {$currentKey}"); + } - foreach ($data as $key => $value) { - $fullKey = "{$baseKey}.{$key}"; + foreach ($currentData as $key => $value) { + $fullKey = "{$currentKey}.{$key}"; - if (is_array($value)) { - [$nestedFlattened, $nestedGroups] = $this->flattenArray($value, $fullKey, $currentDepth + 1); - $flattenedData = array_merge($flattenedData, $nestedFlattened); - $groups = array_merge($groups, $nestedGroups); - } else { - $flattenedData[$fullKey] = $value; - $groups[$fullKey] = $key; + if (is_array($value)) { + $stack[] = ['data' => $value, 'key' => $fullKey, 'depth' => $currentDepth + 1]; + } else { + $flattenedData[$fullKey] = $value; + $groups[$fullKey] = $key; + } } } @@ -255,40 +263,75 @@ class Config /** * Loads configuration data from a file. * - * Reads the specified file, parses its contents, and merges - * the data into the existing configuration. When flattening is enabled, - * keys are grouped by the file's base name to create unique keys. If a key - * already exists, it will be overwritten. + * Reads the specified file, parses its contents, and merges the data + * into the existing configuration. If the cache is already loaded, + * this method skips further processing. * - * @param string|null $filePath Absolute or relative path to the file. + * @param string|null $filePath The file path or name (optional). * @return bool True if the configuration was successfully loaded. + * * @throws ConfigException If the file cannot be found or parsed. */ public function load(?string $filePath = null): bool { + if ($this->isCacheLoaded()) { + return true; + } + $filePath = $this->resolvePath($filePath); + if (!$filePath || !is_file($filePath)) { throw new ConfigException("Configuration file not found: {$filePath}"); } - - // Parse the configuration file + $parsedData = $this->parse($filePath); - if ($parsedData === null) { - throw new ConfigException("Failed to parse configuration file: {$filePath}"); + if (!is_array($parsedData)) { + throw new ConfigException("Invalid configuration format in file: {$filePath}. Expected an array."); } - + if ($this->flatten) { - // Flatten the configuration data - $baseName = strtolower(preg_replace('/\.[^.]+$/', '', basename($filePath))); + $baseName = strtolower(pathinfo($filePath, PATHINFO_FILENAME)); [$flattenedData, $groups] = $this->flattenArray($parsedData, $baseName); - - // Insert the flattened data $this->insert($flattenedData, [$baseName => $groups]); - return true; + } else { + $this->insert($parsedData); } - // Insert non-flattened data - $this->insert($parsedData); + return true; + } + + /** + * Loads configuration data from multiple files. + * + * @param array $filePaths List of file paths. + * @return void + * + * @throws ConfigException If any file cannot be loaded or parsed. + */ + public function loadMultiple(array $filePaths): void + { + foreach ($filePaths as $filePath) { + $this->load($filePath); + } + } + + /** + * Loads configuration data from a stream (e.g., PSR-7 StreamInterface). + * + * @param StreamInterface $stream A PSR-7 compatible stream. + * @return bool True if the configuration was successfully loaded. + * + * @throws ConfigException If the stream cannot be parsed. + */ + public function loadFromStream(StreamInterface $stream): bool + { + $data = json_decode($stream->getContents(), true, 512, JSON_THROW_ON_ERROR); + + if (!is_array($data)) { + throw new ConfigException("Failed to parse configuration stream."); + } + + $this->insert($data); return true; } @@ -303,16 +346,26 @@ class Config protected function parse(?string $filePath = null): ?array { $filePath = $this->resolvePath($filePath); - + if (!$filePath || !is_file($filePath)) { throw new ConfigException("Configuration file not found: {$filePath}"); } - + + $contents = file_get_contents($filePath); + if ($contents === false) { + throw new ConfigException("Failed to read configuration file: {$filePath}"); + } + + // Validate UTF-8 encoding + if (!mb_check_encoding($contents, 'UTF-8')) { + throw new ConfigException("File encoding must be UTF-8: {$filePath}"); + } + $parser = ConfigParserFactory::createParser($filePath); if (!$parser) { throw new ConfigException("No suitable parser found for: {$filePath}"); } - + return $parser->parse($filePath); } @@ -328,30 +381,36 @@ class Config } /** - * Checks if a specific configuration key exists. + * Validates the structure of configuration and group data. * - * This method first checks if the key exists in the main configuration. - * If not, it then checks whether the key corresponds to a group in the grouped configurations. - * - * @param string $key The configuration key to check. - * @return bool True if the key or group exists; otherwise, false. + * @param array $config The configuration data. + * @param array $groups The groups data. + * @return void + * + * @throws ConfigException If the structures are invalid. */ - public function has(string $key): bool + private function validateConfigStructure(array $config, array $groups): void { - // Check if the key exists in the main configuration array - if (array_key_exists($key, $this->config)) { - return true; + if (!is_array($config) || !is_array($groups)) { + throw new ConfigException("Invalid configuration structure: Both 'config' and 'groups' must be arrays."); } + } - // Check if the key exists as a group - return array_key_exists($key, $this->groups); + /** + * Checks if a specific configuration key exists. + * + * @param string $key The key to check. + * @return bool True if the key exists, false otherwise. + */ + public function has(string $key): bool + { + return array_key_exists($key, $this->config) || array_key_exists($key, $this->groups); } /** * Adds or updates a configuration value. * - * If the key contains dots, indicating a grouped or flattened list, - * the method ensures the group and config data are updated accordingly. + * Optimized to ensure group structures are maintained efficiently. * * @param string $key The configuration key (supports dot notation for groups). * @param mixed $value The configuration value. @@ -359,17 +418,19 @@ class Config */ public function add(string $key, mixed $value): void { - // Add to config $this->config[$key] = $value; // If the key is dot-notated, update the group if (str_contains($key, '.')) { $parts = explode('.', $key); $group = array_shift($parts); - $subKey = implode('.', $parts); - // Ensure the group exists - $this->groups[$group][$key] = $subKey; + // Avoid redundant structure initialization + if (!isset($this->groups[$group])) { + $this->groups[$group] = []; + } + + $this->groups[$group][$key] = implode('.', $parts); } } @@ -388,25 +449,32 @@ class Config /** * Deletes a configuration key or group. * - * If the provided key corresponds to a group, all keys in that group - * will be removed from the configuration array before the group itself - * is deleted. + * Removes a specific key or group from the configuration. If the key represents + * a group, all associated keys within the group will also be removed. * * @param string $key The key or group to delete. * @return void + * + * @throws ConfigException If the group structure is invalid. */ public function delete(string $key): void { + // Validate group structure if the key is in groups if (isset($this->groups[$key])) { - // Delete all group keys from config + if (!is_array($this->groups[$key])) { + throw new ConfigException("Invalid group structure for key: {$key}. Expected an array."); + } + + // Remove all keys in the group foreach ($this->groups[$key] as $fullKey => $subKey) { unset($this->config[$fullKey]); } + unset($this->groups[$key]); return; } - - // Delete single key + + // Remove single key unset($this->config[$key]); } @@ -431,73 +499,89 @@ class Config } /** - * Saves the configuration and groups data to a cache file. + * Resets the configuration groups to an empty state. + * + * @return void + */ + public function resetGroups(): void + { + $this->groups = []; + } + + /** + * Saves the current configuration to a cache file. * - * @param string $filePath The file path to save the cache. - * @param int $expires Expiration time in seconds (0 for no expiration). - * @return bool True if the cache was saved successfully, false otherwise. + * @param string $filePath File path to save cache. + * @param int $expires Expiration time (0 for no expiration). + * @return bool True if successful; otherwise, false. */ public function saveCache(string $filePath, int $expires = 0): bool { - // Check if the file is writable or can be created + if (!is_array($this->config) || !is_array($this->groups)) { + throw new ConfigException("Cannot save cache: 'config' and 'groups' must be arrays."); + } + $dir = dirname($filePath); if (!is_dir($dir) && !mkdir($dir, 0755, true)) { return false; } - if (is_file($filePath) && !is_writable($filePath)) { - return false; - } - $cache = [ + $data = [ 'config' => $this->config, 'groups' => $this->groups, - 'expires' => $expires > 0 ? time() + $expires : false, + 'expires' => $expires ? time() + $expires : 0, ]; - $json = json_encode($cache, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); - - return file_put_contents($filePath, $json) !== false; + return file_put_contents($filePath, json_encode($data, JSON_PRETTY_PRINT)) !== false; } /** - * Loads the configuration and groups from a cache file. + * Loads configuration and groups from a cache file. * - * @param string $filePath The file path of the cache. - * @return bool True if the cache was loaded successfully, false otherwise. + * @param string $filePath Path to the cache file. + * @return bool True if the cache was loaded successfully; otherwise, false. + * + * @throws ConfigException If the cache format is invalid. */ public function loadCache(string $filePath): bool { if ($this->cacheLoaded) { return true; } - + if (!is_file($filePath) || !is_readable($filePath)) { return false; } - - $data = json_decode(file_get_contents($filePath), true, 512, JSON_THROW_ON_ERROR); - - if (!is_array($data) || !isset($data['config'], $data['groups'], $data['expires'])) { + + try { + $data = json_decode(file_get_contents($filePath), true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + unlink($filePath); // Cleanup malformed cache return false; } - - if ($data['expires'] && time() > $data['expires']) { + + if (!isset($data['config'], $data['groups']) || !is_array($data['config']) || !is_array($data['groups'])) { + unlink($filePath); // Cleanup malformed cache + throw new ConfigException("Invalid cache format: 'config' and 'groups' must be arrays."); + } + + if (isset($data['expires']) && $data['expires'] > 0 && time() > $data['expires']) { $this->deleteCache($filePath); return false; } - + $this->config = $data['config']; $this->groups = $data['groups']; $this->cacheLoaded = true; - + return true; } /** * Deletes a cache file. * - * @param string $filePath The file path of the cache to delete. - * @return bool True if the cache was deleted successfully, false otherwise. + * @param string $filePath Path to cache. + * @return bool True if deleted. */ public function deleteCache(string $filePath): bool { @@ -505,13 +589,11 @@ class Config } /** - * Clears all configuration data. - * - * @return void + * Clears the configuration manager. */ public function clear(): void { - $this->config = []; - $this->groups = []; + $this->groups = $this->config = []; + $this->cacheLoaded = false; } } \ No newline at end of file diff --git a/docs/php/files/src/ConfigParserFactory.php.txt b/docs/php/files/src/ConfigParserFactory.php.txt index 0b50fd7..842f25f 100644 --- a/docs/php/files/src/ConfigParserFactory.php.txt +++ b/docs/php/files/src/ConfigParserFactory.php.txt @@ -2,13 +2,15 @@ /** * ConfigParserFactory.php * - * Factory class for creating and managing configuration parsers based on file extensions. - * Allows registration of custom parsers and overrides for default parsers. + * Factory class for creating and managing configuration parsers + * based on file extensions. Allows registration of custom parsers + * and overrides for default parsers. * * PHP version 8.2 * * @package JG\Config * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License @@ -29,8 +31,10 @@ use JG\Config\Exceptions\InvalidParserException; /** * ConfigParserFactory * - * Factory class for creating and managing configuration parsers. Supports default parsers - * for common file formats such as JSON, PHP, and XML, and allows custom parsers to be registered. + * Factory class for creating and managing configuration parsers. + * Supports default parsers for common file formats such as conf, + * ini, json, PHP, xml, yaml/yml, and allows custom parsers to be + * registered. * * @package JG\Config */ @@ -72,7 +76,7 @@ class ConfigParserFactory } throw new InvalidParserException( - "No suitable parser found for file extension: .$extension" + "No suitable parser found for file extension: .$extension. File: $filePath" ); } @@ -81,21 +85,34 @@ class ConfigParserFactory * * @param string $extension The file extension for the parser (e.g., 'yaml'). * @param class-string $parserClass Fully qualified class name of the parser, - * must implement ParserInterface. + * must implement ParserInterface. * @return void * @throws InvalidParserException If the class does not implement the required interface. */ public static function registerParser(string $extension, string $parserClass): void { - // Validate that the class implements ParserInterface + $extension = strtolower(ltrim($extension, '.')); + + if (empty($extension)) { + throw new InvalidParserException("File extension cannot be empty."); + } + // Validate and instantiate the parser class + if ($parserClass && is_a($parserClass, ParserInterface::class, true)) { + self::$configParsers[$extension] = $parserClass; + return; + } + if (!is_a($parserClass, ParserInterface::class, true)) { throw new InvalidParserException( "Parser class '{$parserClass}' must implement ParserInterface." ); } - // Sanitize the extension and register the parser - $extension = strtolower(ltrim($extension, '.')); + // Optional: prevent duplicate registration unless overwriting + if (isset(self::$configParsers[$extension])) { + throw new InvalidParserException("Parser for extension '{$extension}' is already registered."); + } + self::$configParsers[$extension] = $parserClass; } @@ -133,10 +150,16 @@ class ConfigParserFactory public static function unregisterParser(string $extension): bool { $extension = strtolower(ltrim($extension, '.')); + + if (empty($extension)) { + return false; + } + if (isset(self::$configParsers[$extension])) { unset(self::$configParsers[$extension]); return true; } + return false; } } \ No newline at end of file diff --git a/docs/php/files/src/Exceptions/ConfigException.php.txt b/docs/php/files/src/Exceptions/ConfigException.php.txt index e74f864..5cf3e7e 100644 --- a/docs/php/files/src/Exceptions/ConfigException.php.txt +++ b/docs/php/files/src/Exceptions/ConfigException.php.txt @@ -9,6 +9,7 @@ * * @package JG\Config\Exceptions * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Exceptions/ConfigParseException.php.txt b/docs/php/files/src/Exceptions/ConfigParseException.php.txt index df7065f..18aa9a1 100644 --- a/docs/php/files/src/Exceptions/ConfigParseException.php.txt +++ b/docs/php/files/src/Exceptions/ConfigParseException.php.txt @@ -8,6 +8,7 @@ * * @package JG\Config\Exceptions * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Exceptions/InvalidParserException.php.txt b/docs/php/files/src/Exceptions/InvalidParserException.php.txt index ca2dcdc..0fc660a 100644 --- a/docs/php/files/src/Exceptions/InvalidParserException.php.txt +++ b/docs/php/files/src/Exceptions/InvalidParserException.php.txt @@ -8,6 +8,7 @@ * * @package JG\Config\Exceptions * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Parsers/ConfParser.php.txt b/docs/php/files/src/Parsers/ConfParser.php.txt index 3b42dd8..cc04973 100644 --- a/docs/php/files/src/Parsers/ConfParser.php.txt +++ b/docs/php/files/src/Parsers/ConfParser.php.txt @@ -3,12 +3,13 @@ * ConfParser.php * * Parses key-value pairs from custom CONF configuration files. These files - * typically use `:` or `=` as separators for key-value pairs. + * typically use `=` as the separator for key-value pairs. * * PHP version 8.2 * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License @@ -18,19 +19,20 @@ declare(strict_types=1); namespace JG\Config\Parsers; +use \file; use \trim; -use \sprintf; -use \is_numeric; -use \strtolower; -use \preg_match_all; -use \file_get_contents; +use \explode; +use \is_file; +use \preg_match; +use \is_readable; use \JG\Config\Exceptions\ConfigParseException; /** * ConfParser * * Parses key-value pairs from CONF configuration files. This parser supports - * flexible key-value syntax with either `:` or `=` as separators. + * clean formatting with `=` as the separator. It skips comments and empty lines + * and trims whitespace while preserving inner spaces. * * @package JG\Config\Parsers */ @@ -39,12 +41,9 @@ class ConfParser implements ParserInterface /** * Parses a CONF configuration file into an associative array. * - * The method reads key-value pairs from the file, where keys and values are separated - * by either `:` or `=`. Leading and trailing whitespace is trimmed for both keys and values. - * * Example: * ``` - * host: localhost + * host = localhost * user = root * ``` * @@ -58,40 +57,33 @@ class ConfParser implements ParserInterface throw new ConfigParseException("File not found or unreadable: {$filePath}"); } - // Attempt to retrieve the file contents - $data = file_get_contents($filePath); - - if ($data === false) { - throw new ConfigParseException( - sprintf("Failed to read CONF configuration file: %s", $filePath) - ); + $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($lines === false) { + throw new ConfigParseException("Failed to read the CONF file: {$filePath}"); } - $out = []; + $output = []; - // Define regex for capturing key-value pairs with `:` or `=` as separator - $regex = '/^(?P[a-zA-Z._\-]+[a-zA-Z0-9]*)\s*[:=]\s*(?P.*)$/m'; + foreach ($lines as $line) { + $line = trim($line); - // Process matches using the defined regex pattern - if (preg_match_all($regex, $data, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $key = trim($match['key']); - $value = trim($match['value']); + // Skip comments + if (str_starts_with($line, '#') || str_starts_with($line, ';')) { + continue; + } - // Convert scalar values (e.g., true, false, null, numbers) if applicable - $value = $this->convertValue($value); + // Parse key=value pairs + $parts = explode('=', $line, 2); + if (count($parts) === 2) { + $key = trim($parts[0]); + $value = trim($parts[1]); - if ($key !== '' && $value !== '') { - $out[$key] = $value; - } + // Convert scalar values if applicable + $output[$key] = $this->convertValue($value); } - } else { - throw new ConfigParseException( - sprintf("No valid key-value pairs found in CONF file: %s", $filePath) - ); } - return $out; + return $output; } /** diff --git a/docs/php/files/src/Parsers/IniParser.php.txt b/docs/php/files/src/Parsers/IniParser.php.txt index 04edfb2..7f2c0e0 100644 --- a/docs/php/files/src/Parsers/IniParser.php.txt +++ b/docs/php/files/src/Parsers/IniParser.php.txt @@ -8,6 +8,7 @@ * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Parsers/JsonParser.php.txt b/docs/php/files/src/Parsers/JsonParser.php.txt index 993287d..7a7743d 100644 --- a/docs/php/files/src/Parsers/JsonParser.php.txt +++ b/docs/php/files/src/Parsers/JsonParser.php.txt @@ -9,6 +9,7 @@ * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Parsers/ParserInterface.php.txt b/docs/php/files/src/Parsers/ParserInterface.php.txt index 835f093..de4a750 100644 --- a/docs/php/files/src/Parsers/ParserInterface.php.txt +++ b/docs/php/files/src/Parsers/ParserInterface.php.txt @@ -10,6 +10,7 @@ * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Parsers/PhpParser.php.txt b/docs/php/files/src/Parsers/PhpParser.php.txt index e56b88b..0f999cd 100644 --- a/docs/php/files/src/Parsers/PhpParser.php.txt +++ b/docs/php/files/src/Parsers/PhpParser.php.txt @@ -9,6 +9,7 @@ * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Parsers/XmlParser.php.txt b/docs/php/files/src/Parsers/XmlParser.php.txt index ff40ec9..02549e1 100644 --- a/docs/php/files/src/Parsers/XmlParser.php.txt +++ b/docs/php/files/src/Parsers/XmlParser.php.txt @@ -9,6 +9,7 @@ * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/files/src/Parsers/YamlParser.php.txt b/docs/php/files/src/Parsers/YamlParser.php.txt index 8553ab7..5d71ea0 100644 --- a/docs/php/files/src/Parsers/YamlParser.php.txt +++ b/docs/php/files/src/Parsers/YamlParser.php.txt @@ -8,6 +8,7 @@ * * @package JG\Config\Parsers * @version 1.0.0 + * @since 1.0.0 * @author James Gober * @link https://github.com/jamesgober/Config * @license MIT License diff --git a/docs/php/js/searchIndex.js b/docs/php/js/searchIndex.js index 951d39a..5c3f746 100644 --- a/docs/php/js/searchIndex.js +++ b/docs/php/js/searchIndex.js @@ -40,16 +40,31 @@ Search.appendIndex( "name": "insert", "summary": "Inserts\u0020configuration\u0020data\u0020into\u0020the\u0020manager.", "url": "classes/JG-Config-Config.html#method_insert" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AmergeConfig\u0028\u0029", + "name": "mergeConfig", + "summary": "Merges\u0020new\u0020configuration\u0020data\u0020into\u0020the\u0020existing\u0020configuration.", + "url": "classes/JG-Config-Config.html#method_mergeConfig" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003AflattenArray\u0028\u0029", "name": "flattenArray", - "summary": "Flattens\u0020a\u0020multidimensional\u0020array\u0020into\u0020dot\u002Dnotated\u0020keys\u0020and\u0020groups.", + "summary": "Flattens\u0020a\u0020nested\u0020configuration\u0020array.", "url": "classes/JG-Config-Config.html#method_flattenArray" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003Aload\u0028\u0029", "name": "load", "summary": "Loads\u0020configuration\u0020data\u0020from\u0020a\u0020file.", "url": "classes/JG-Config-Config.html#method_load" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AloadMultiple\u0028\u0029", + "name": "loadMultiple", + "summary": "Loads\u0020configuration\u0020data\u0020from\u0020multiple\u0020files.", + "url": "classes/JG-Config-Config.html#method_loadMultiple" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AloadFromStream\u0028\u0029", + "name": "loadFromStream", + "summary": "Loads\u0020configuration\u0020data\u0020from\u0020a\u0020stream\u0020\u0028e.g.,\u0020PSR\u002D7\u0020StreamInterface\u0029.", + "url": "classes/JG-Config-Config.html#method_loadFromStream" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003Aparse\u0028\u0029", "name": "parse", @@ -60,6 +75,11 @@ Search.appendIndex( "name": "fetch", "summary": "Retrieves\u0020configuration\u0020from\u0020a\u0020file.", "url": "classes/JG-Config-Config.html#method_fetch" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AvalidateConfigStructure\u0028\u0029", + "name": "validateConfigStructure", + "summary": "Validates\u0020the\u0020structure\u0020of\u0020configuration\u0020and\u0020group\u0020data.", + "url": "classes/JG-Config-Config.html#method_validateConfigStructure" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003Ahas\u0028\u0029", "name": "has", @@ -90,15 +110,20 @@ Search.appendIndex( "name": "getGroups", "summary": "Retrieves\u0020all\u0020configuration\u0020groups.", "url": "classes/JG-Config-Config.html#method_getGroups" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AresetGroups\u0028\u0029", + "name": "resetGroups", + "summary": "Resets\u0020the\u0020configuration\u0020groups\u0020to\u0020an\u0020empty\u0020state.", + "url": "classes/JG-Config-Config.html#method_resetGroups" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003AsaveCache\u0028\u0029", "name": "saveCache", - "summary": "Saves\u0020the\u0020configuration\u0020and\u0020groups\u0020data\u0020to\u0020a\u0020cache\u0020file.", + "summary": "Saves\u0020the\u0020current\u0020configuration\u0020to\u0020a\u0020cache\u0020file.", "url": "classes/JG-Config-Config.html#method_saveCache" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003AloadCache\u0028\u0029", "name": "loadCache", - "summary": "Loads\u0020the\u0020configuration\u0020and\u0020groups\u0020from\u0020a\u0020cache\u0020file.", + "summary": "Loads\u0020configuration\u0020and\u0020groups\u0020from\u0020a\u0020cache\u0020file.", "url": "classes/JG-Config-Config.html#method_loadCache" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003AdeleteCache\u0028\u0029", @@ -108,13 +133,23 @@ Search.appendIndex( }, { "fqsen": "\\JG\\Config\\Config\u003A\u003Aclear\u0028\u0029", "name": "clear", - "summary": "Clears\u0020all\u0020configuration\u0020data.", + "summary": "Clears\u0020the\u0020configuration\u0020manager.", "url": "classes/JG-Config-Config.html#method_clear" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003AEXPIRE_NEVER", "name": "EXPIRE_NEVER", "summary": "Common\u0020expiration\u0020durations\u0020in\u0020seconds.", "url": "classes/JG-Config-Config.html#constant_EXPIRE_NEVER" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AEXPIRE_ONE_HOUR", + "name": "EXPIRE_ONE_HOUR", + "summary": "", + "url": "classes/JG-Config-Config.html#constant_EXPIRE_ONE_HOUR" + }, { + "fqsen": "\\JG\\Config\\Config\u003A\u003AEXPIRE_HALF_DAY", + "name": "EXPIRE_HALF_DAY", + "summary": "", + "url": "classes/JG-Config-Config.html#constant_EXPIRE_HALF_DAY" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003AEXPIRE_ONE_DAY", "name": "EXPIRE_ONE_DAY", @@ -153,12 +188,12 @@ Search.appendIndex( }, { "fqsen": "\\JG\\Config\\Config\u003A\u003A\u0024groups", "name": "groups", - "summary": "Grouped\u0020configuration\u0020storage.", + "summary": "Grouped\u0020configuration\u0020storage,\u0020allowing\u0020access\u0020by\u0020group\u0020or\nunique\u0020key.", "url": "classes/JG-Config-Config.html#property_groups" }, { "fqsen": "\\JG\\Config\\Config\u003A\u003A\u0024config", "name": "config", - "summary": "Primary\u0020configuration\u0020storage.", + "summary": "Primary\u0020configuration\u0020storage,\u0020including\u0020unique\u0020keys\u0020from\ngrouped\u0020configurations.", "url": "classes/JG-Config-Config.html#property_config" }, { "fqsen": "\\JG\\Config\\ConfigParserFactory", diff --git a/src/Parsers/ConfParser.php b/src/Parsers/ConfParser.php index 7af452e..cc04973 100644 --- a/src/Parsers/ConfParser.php +++ b/src/Parsers/ConfParser.php @@ -3,7 +3,7 @@ * ConfParser.php * * Parses key-value pairs from custom CONF configuration files. These files - * typically use `:` or `=` as separators for key-value pairs. + * typically use `=` as the separator for key-value pairs. * * PHP version 8.2 * @@ -19,19 +19,20 @@ namespace JG\Config\Parsers; +use \file; use \trim; -use \sprintf; -use \is_numeric; -use \strtolower; -use \preg_match_all; -use \file_get_contents; +use \explode; +use \is_file; +use \preg_match; +use \is_readable; use \JG\Config\Exceptions\ConfigParseException; /** * ConfParser * * Parses key-value pairs from CONF configuration files. This parser supports - * flexible key-value syntax with either `:` or `=` as separators. + * clean formatting with `=` as the separator. It skips comments and empty lines + * and trims whitespace while preserving inner spaces. * * @package JG\Config\Parsers */ @@ -40,12 +41,9 @@ class ConfParser implements ParserInterface /** * Parses a CONF configuration file into an associative array. * - * The method reads key-value pairs from the file, where keys and values are separated - * by either `:` or `=`. Leading and trailing whitespace is trimmed for both keys and values. - * * Example: * ``` - * host: localhost + * host = localhost * user = root * ``` * @@ -59,40 +57,33 @@ public function parse(string $filePath): array throw new ConfigParseException("File not found or unreadable: {$filePath}"); } - // Attempt to retrieve the file contents - $data = file_get_contents($filePath); - - if ($data === false) { - throw new ConfigParseException( - sprintf("Failed to read CONF configuration file: %s", $filePath) - ); + $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($lines === false) { + throw new ConfigParseException("Failed to read the CONF file: {$filePath}"); } - $out = []; + $output = []; - // Define regex for capturing key-value pairs with `:` or `=` as separator - $regex = '/^(?P[a-zA-Z._\-]+[a-zA-Z0-9]*)\s*[:=]\s*(?P.*)$/m'; + foreach ($lines as $line) { + $line = trim($line); - // Process matches using the defined regex pattern - if (preg_match_all($regex, $data, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $key = trim($match['key']); - $value = trim($match['value']); + // Skip comments + if (str_starts_with($line, '#') || str_starts_with($line, ';')) { + continue; + } - // Convert scalar values (e.g., true, false, null, numbers) if applicable - $value = $this->convertValue($value); + // Parse key=value pairs + $parts = explode('=', $line, 2); + if (count($parts) === 2) { + $key = trim($parts[0]); + $value = trim($parts[1]); - if ($key !== '' && $value !== '') { - $out[$key] = $value; - } + // Convert scalar values if applicable + $output[$key] = $this->convertValue($value); } - } else { - throw new ConfigParseException( - sprintf("No valid key-value pairs found in CONF file: %s", $filePath) - ); } - return $out; + return $output; } /** diff --git a/tests/config/config.conf b/tests/config/config.conf index fe5699d..fe40de1 100644 --- a/tests/config/config.conf +++ b/tests/config/config.conf @@ -1,8 +1,11 @@ # CONFIG COMMENT -host: localhost -port = 3306 -user: root -password = secret +# +# TESTING WITH SPACES +###########WWWWWWWWWWWWWWW +host = localhost +port = 3306 +user = root +password = secret [app] debug = true