Skip to content

Commit

Permalink
Initial codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
tlmcclatchey committed Feb 17, 2024
0 parents commit 90814b5
Show file tree
Hide file tree
Showing 23 changed files with 684 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
composer.lock
vendor/
.phpunit.result.cache
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 commonphp

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
29 changes: 29 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "comphp/config",
"type": "library",
"description": "Provides read/write access to configuration files",
"license": "MIT",
"authors": [
{
"name": "timothy.mcclatchey",
"email": "[email protected]"
}
],
"autoload": {
"psr-4": {
"CommonPHP\\Configuration\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"CommonPHP\\Tests\\Configuration\\": "tests/"
}
},
"require": {
"php": "^8.3",
"comphp/drivers": "^0.1"
},
"require-dev": {
"phpunit/phpunit": "^10.5.9"
}
}
47 changes: 47 additions & 0 deletions examples/general-usage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

use CommonPHP\Configuration\Attributes\ConfigurationDriverAttribute;
use CommonPHP\Configuration\ConfigurationManager;
use CommonPHP\Configuration\Contracts\ConfigurationDriverContract;
use CommonPHP\Drivers\ServiceProviders\DriverManagerServiceProvider;
use CommonPHP\ServiceManagement\ServiceManager;

require '../vendor/autoload.php';

#[ConfigurationDriverAttribute('php')]
class GeneralUsageExampleDriver implements ConfigurationDriverContract
{

#[\Override] function canSave(): bool
{
return true;
}

#[\Override] function load(string $filename): array
{
echo 'Loading: '.$filename.PHP_EOL;
return [
'foo' => 'bar',
'answerToLifeUniverseEverything' => 42
];
}

#[\Override] function save(string $filename, array $data): void
{
echo 'Saving: '.json_encode($data).' to file: '.$filename.PHP_EOL;
}
}

// Instantiate the ServiceManager and register the DriverManagerServiceProvider for dependency management.
$serviceManager = new ServiceManager();
$serviceManager->providers->registerProvider(DriverManagerServiceProvider::class);
$serviceManager->register(ConfigurationManager::class);

$config = $serviceManager->get(ConfigurationManager::class);

$config->loadDriver(GeneralUsageExampleDriver::class);

$example = $config->get(__FILE__);
var_dump($example->data);

$example->save();
74 changes: 74 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# CommonPHP Configuration Manager

The CommonPHP Configuration Manager (`comphp/config`) is a flexible and robust library designed to simplify the management of configuration files in PHP applications. It leverages the power of dynamic driver loading and custom behaviors to provide a highly adaptable solution for application configuration.

## Features

- **Dynamic Driver Support**: Easily extendable to support various configuration file formats through custom drivers.
- **Behavior Customization**: Control how the system handles duplicate file extensions and unsupported save operations with configurable behaviors.
- **Exception Handling**: Comprehensive exception handling for precise error tracking and management.
- **Attribute and Contract-Based Configuration**: Utilize PHP attributes and interfaces for defining configuration drivers, ensuring a clear and structured approach.

## Installation

Use Composer to install the Configuration Manager:

```
composer require comphp/config
```

## Basic Usage

Refer to the `examples/general-usage.php` file for a detailed example. Here's a quick overview:

1. **Define a Configuration Driver**:
Implement the `ConfigurationDriverContract` in your driver class. Optionally, use the `ConfigurationDriverAttribute` to specify supported file extensions.

2. **Load and Use the Configuration Manager**:
```php
use CommonPHP\Configuration\ConfigurationManager;
use CommonPHP\Drivers\DriverManager;

// Initialize the Driver Manager and Configuration Manager
$driverManager = new DriverManager();
$configurationManager = new ConfigurationManager($driverManager);

// Load a custom driver
$configurationManager->loadDriver(YourCustomDriver::class);

// Get configuration
$config = $configurationManager->get('/path/to/your/config.file');

// Access configuration data
echo $config->data['your_config_key'];
```

3. **Saving Configurations**:
If your driver supports saving, you can persist modifications:
```php
$config->data['new_key'] = 'new_value';
$config->save(); // Make sure your driver implements saving logic
```

## Customizing Behavior

Customize how the Configuration Manager handles certain scenarios through properties:

- **Duplicate Extension Behavior**: Decide how to handle when multiple drivers claim the same file extension.
- **Configuration Cannot Save Behavior**: Define behavior for when a configuration cannot be saved (e.g., driver doesn't support saving).

## Creating Custom Drivers

Implement the `ConfigurationDriverContract` interface in your driver class and optionally use the `ConfigurationDriverAttribute` to specify which file extensions your driver supports.

## Handling Exceptions

The library defines a range of exceptions for fine-grained error handling, from driver load failures to access denied scenarios. Catch these exceptions to handle different error conditions gracefully.

## Contributing

Contributions to the CommonPHP Configuration Manager are welcome. Please follow the repository's contributing guidelines to submit bug reports, feature requests, or pull requests.

## License

The CommonPHP Configuration Manager is open-sourced software licensed under the MIT license.
23 changes: 23 additions & 0 deletions src/Attributes/ConfigurationDriverAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/**
* Marks a class as a configuration driver, specifying supported file extensions.
*
* @package CommonPHP\Configuration\Attributes
*/

namespace CommonPHP\Configuration\Attributes;

use Attribute;
use CommonPHP\Drivers\Contracts\DriverAttributeContract;

#[Attribute(Attribute::TARGET_CLASS)]
readonly class ConfigurationDriverAttribute implements DriverAttributeContract
{
/** @var string[] Supported file extensions. */
public array $extensions;
public function __construct(string ... $extensions)
{
$this->extensions = $extensions;
}
}
155 changes: 155 additions & 0 deletions src/ConfigurationManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php

/**
* Configuration Manager for CommonPHP
*
* Manages configuration loading and saving through various drivers, supporting dynamic driver loading and extension handling.
*
* @package CommonPHP\Configuration
* @author Timothy McClatchey <[email protected]>
* @copyright 2024 CommonPHP.org
* @license http://opensource.org/licenses/MIT MIT License
*/

namespace CommonPHP\Configuration;

use CommonPHP\Configuration\Attributes\ConfigurationDriverAttribute;
use CommonPHP\Configuration\Contracts\ConfigurationDriverContract;
use CommonPHP\Configuration\Exceptions\AccessDeniedException;
use CommonPHP\Configuration\Exceptions\ConfigurationLoadFailedException;
use CommonPHP\Configuration\Exceptions\DriverConfigurationFailedException;
use CommonPHP\Configuration\Exceptions\DriverLoadFailedException;
use CommonPHP\Configuration\Exceptions\DuplicateFileExtensionException;
use CommonPHP\Configuration\Exceptions\ExtensionNotSupportedException;
use CommonPHP\Configuration\Exceptions\InvalidFileExtensionException;
use CommonPHP\Configuration\Support\Configuration;
use CommonPHP\Configuration\Support\ConfigurationCannotSaveBehavior;
use CommonPHP\Configuration\Support\DuplicateExtensionBehavior;
use CommonPHP\DependencyInjection\Exceptions\ClassNotDefinedException;
use CommonPHP\DependencyInjection\Exceptions\ClassNotInstantiableException;
use CommonPHP\DependencyInjection\Exceptions\InstantiateCircularReferenceException;
use CommonPHP\DependencyInjection\Exceptions\InstantiationFailedException;
use CommonPHP\DependencyInjection\Exceptions\ParameterDiscoveryFailedException;
use CommonPHP\DependencyInjection\Exceptions\ParameterTypeRequiredException;
use CommonPHP\DependencyInjection\Exceptions\UnsupportedReflectionTypeException;
use CommonPHP\Drivers\DriverManager;
use CommonPHP\Drivers\Exceptions\DriverException;
use CommonPHP\Drivers\Exceptions\NotConfiguredException;
use CommonPHP\Drivers\Exceptions\NotEnabledException;
use ReflectionClass;
use ReflectionException;

final class ConfigurationManager
{
/** @var DuplicateExtensionBehavior How to handle duplicate file extensions. */
public DuplicateExtensionBehavior $duplicateExtensionBehavior = DuplicateExtensionBehavior::OVERWRITE;

/** @var ConfigurationCannotSaveBehavior Behavior when a configuration cannot be saved. */
public ConfigurationCannotSaveBehavior $configurationCannotSaveBehavior = ConfigurationCannotSaveBehavior::THROW;

/** @var DriverManager The DriverManager instance. */
private DriverManager $driverManager;

/** @var Configuration[] Array of loaded configurations. */
private array $configurations = [];

/** @var class-string<ConfigurationDriverContract>[] Mapping of file extensions to driver classes. */
private array $extensions = [];

/**
* @throws DriverConfigurationFailedException
*/
public function __construct(DriverManager $driverManager)
{
try {
$driverManager->configure(ConfigurationDriverAttribute::class, ConfigurationDriverContract::class);
} catch (DriverException $e) {
throw new DriverConfigurationFailedException($e);
}
$this->driverManager = $driverManager;
}

/**
* @param string $driverClass
* @throws DriverLoadFailedException
* @throws DuplicateFileExtensionException
* @throws InvalidFileExtensionException
*/
public function loadDriver(string $driverClass): void
{
try {
if ($this->driverManager->isEnabled($driverClass)) return;
$this->driverManager->enable($driverClass);
$reflection = new ReflectionClass($driverClass);
} catch (DriverException|ReflectionException $e) {
throw new DriverLoadFailedException($driverClass, $e);
}
/** @var ConfigurationDriverAttribute $attribute */
$attribute = $reflection->getAttributes(ConfigurationDriverAttribute::class)[0]->newInstance();
foreach ($attribute->extensions as $extension)
{
$extension = strtolower($extension);
if (!$this->isValidFileExtension($extension))
{
throw new InvalidFileExtensionException($driverClass, $extension);
}
if (isset($this->extensions[$extension]))
{
if ($this->duplicateExtensionBehavior == DuplicateExtensionBehavior::THROW)
{
throw new DuplicateFileExtensionException($driverClass, $extension, $this->extensions[$extension]);
}
else if ($this->duplicateExtensionBehavior == DuplicateExtensionBehavior::WARN)
{
trigger_error((new DuplicateFileExtensionException($driverClass, $extension, $this->extensions[$extension]))->getMessage(), E_USER_WARNING);
}
else if ($this->duplicateExtensionBehavior == DuplicateExtensionBehavior::NOTICE)
{
trigger_error((new DuplicateFileExtensionException($driverClass, $extension, $this->extensions[$extension]))->getMessage());
}
else if ($this->duplicateExtensionBehavior == DuplicateExtensionBehavior::OVERWRITE)
{
$this->extensions[$extension] = $driverClass;
}
// Do nothing on skip
}
else
{
$this->extensions[$extension] = $driverClass;
}
}
}

/**
* @throws ExtensionNotSupportedException
* @throws ClassNotDefinedException
* @throws InstantiationFailedException
* @throws ParameterDiscoveryFailedException
* @throws InstantiateCircularReferenceException
* @throws ParameterTypeRequiredException
* @throws AccessDeniedException
* @throws UnsupportedReflectionTypeException
* @throws NotEnabledException
* @throws NotConfiguredException
* @throws ConfigurationLoadFailedException
* @throws ClassNotInstantiableException
*/
public function get(string $absolutePath): Configuration
{
if (isset($this->configurations[$absolutePath])) return $this->configurations[$absolutePath];
$extension = pathinfo($absolutePath, PATHINFO_EXTENSION);
$extensionLower = strtolower($extension);
if (!isset($this->extensions[$extensionLower]))
{
throw new ExtensionNotSupportedException($extension);
}
$result = new Configuration($this, $this->driverManager->get($this->extensions[$extensionLower]), $absolutePath);
$this->configurations[$absolutePath] = $result;
return $result;
}

private function isValidFileExtension(string $extension): bool
{
return strlen(trim($extension)) > 0 && !preg_match('/[^a-z0-9._-]/', $extension);
}
}
14 changes: 14 additions & 0 deletions src/Contracts/ConfigurationDriverContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace CommonPHP\Configuration\Contracts;

use CommonPHP\Drivers\Contracts\DriverContract;

interface ConfigurationDriverContract extends DriverContract
{
function canSave(): bool;

function load(string $filename): array;

function save(string $filename, array $data): void;
}
14 changes: 14 additions & 0 deletions src/Exceptions/AccessDeniedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace CommonPHP\Configuration\Exceptions;

use Throwable;

class AccessDeniedException extends ConfigurationException
{
public function __construct(string $path, string $access, ?Throwable $previous = null)
{
parent::__construct('Access denied while trying to '.$access.' file '.$path, $previous);
$this->code = 1611;
}
}
Loading

0 comments on commit 90814b5

Please sign in to comment.