Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Initial implementation of the INI Configuration Driver for CommonPHP. This commit introduces the core functionality for loading and saving INI file configurations, integrating seamlessly with the CommonPHP Configuration Management system. Features include robust error handling, support for nested structures, and compatibility with the CommonPHP Driver Management framework.
  • Loading branch information
tlmcclatchey committed Feb 18, 2024
0 parents commit 94fdcc3
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 0 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: PHP CI/CD Pipeline

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
build:

runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite
coverage: xdebug

- name: Validate composer.json and composer.lock
run: composer validate

- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: Run tests
run: ./vendor/bin/phpunit tests

# If you have a deployment step, you can include it here.
# This could be pushing to a Docker registry, deploying to a cloud platform, etc.
# - name: Deploy...
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.dev/
.idea/
composer.lock
vendor/
.phpunit.result.cache
5 changes: 5 additions & 0 deletions .run/phpmd.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="phpmd" type="PhpLocalRunConfigurationType" factoryName="PHP Console" path="$PROJECT_DIR$/vendor/bin/phpmd" scriptParameters="../../src ansi cleancode,codesize,controversial,design,naming,unusedcode">
<method v="2" />
</configuration>
</component>
5 changes: 5 additions & 0 deletions .run/phpstan.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="phpstan" type="PhpLocalRunConfigurationType" factoryName="PHP Console" path="$PROJECT_DIR$/vendor/bin/phpstan" scriptParameters="analyse -c ..\..\phpstan.neon">
<method v="2" />
</configuration>
</component>
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.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# INI Configuration Driver for CommonPHP

This library introduces the INI configuration driver, `IniConfigurationDriver`, as part of the CommonPHP Configuration Management ecosystem. It extends the functionality of CommonPHP by allowing applications to seamlessly load and save configurations using INI files.

## Features

- **Load INI Configurations**: Simplifies the process of reading INI files and converting them into associative arrays for easy access within PHP applications.
- **Save Configurations as INI**: Offers the ability to serialize PHP associative arrays back into INI format, preserving the structure and hierarchies.
- **Structured Error Handling**: Incorporates detailed exception handling to manage potential parsing and file operation errors effectively.
- **Support for Nested Structures**: Through a custom implementation, it supports the representation of nested structures within INI files, providing greater flexibility in configuration management.

## Installation

Use Composer to integrate both the Configuration Manager and the INI driver into your project:

```
composer require comphp/config
composer require comphp/config-ini
```

## Usage

To utilize the INI driver with the Configuration Manager, first ensure the `DriverManager` is configured to recognize the INI driver:

```php
use CommonPHP\Drivers\DriverManager;
use CommonPHP\Configuration\Drivers\IniConfigurationDriver\IniConfigurationDriver;

$driverManager = new DriverManager();
$driverManager->enable(IniConfigurationDriver::class);
```

Upon configuration, the `IniConfigurationDriver` will be automatically used for `.ini` file extensions, thanks to the `#[ConfigurationDriverAttribute('ini')]` annotation.

### Loading a Configuration File

```php
$configManager->loadDriver(IniConfigurationDriver::class);
$config = $configManager->get('path/to/configuration.ini');
```

### Saving a Configuration File

After loading the driver as described above, modifications can be saved back to the INI file:

```php
$config->data['newSection'] = ['newKey' => 'newValue'];
$config->save(); // Persists the changes to 'path/to/configuration.ini'
```

## Exception Handling

The driver includes specific exception handling for common issues such as:

- **ConfigurationException**: Thrown for errors related to INI file parsing or when the file format does not meet the expected structure.
- **General Exceptions**: For file read/write operations or parsing failures.
31 changes: 31 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "comphp/config-ini",
"type": "library",
"description": "Provides functionality necessary for working with INI configuration files",
"license": "MIT",
"authors": [
{
"name": "timothy.mcclatchey",
"email": "timothy@commonphp.org"
}
],
"autoload": {
"psr-4": {
"CommonPHP\\Configuration\\Drivers\\IniConfigurationDriver\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"CommonPHP\\Tests\\Drivers\\IniConfigurationDriver\\": "tests/"
}
},
"require": {
"php": "^8.3",
"comphp/config": "^0.1"
},
"require-dev": {
"phpunit/phpunit": "^10.5.9",
"phpstan/phpstan": "^1.10.58",
"phpmd/phpmd": "^2.13"
}
}
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
parameters:
level: 9
paths:
- src
124 changes: 124 additions & 0 deletions src/IniConfigurationDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php /** @noinspection PhpUnused */
namespace CommonPHP\Configuration\Drivers\IniConfigurationDriver;

use CommonPHP\Configuration\Attributes\ConfigurationDriverAttribute;
use CommonPHP\Configuration\Contracts\ConfigurationDriverContract;
use CommonPHP\Configuration\Exceptions\ConfigurationException;
use Override;

/**
* Class IniConfigurationDriver
*
* This class implements the ConfigurationDriverContract interface and provides methods to load and save configuration data
* from/to INI files.
*
* @package CommonPHP\Configuration\Drivers\IniConfigurationDriver
*/
#[ConfigurationDriverAttribute('ini')]
class IniConfigurationDriver implements ConfigurationDriverContract
{

/**
* Checks whether the object can be saved.
*
* @return bool Returns true if the object can be saved, false otherwise.
*/
#[Override] function canSave(): bool
{
return true;
}

/**
* Loads an INI file and returns its contents as an associative array.
*
* @param string $filename The path to the INI file to load.
* @return array The contents of the INI file as an associative array.
* @throws ConfigurationException If the file does not appear to be a properly formatted INI file.
*/
#[Override] function load(string $filename): array
{
$result = parse_ini_file($filename);
if ($result === false)
{
throw new ConfigurationException('File does not appear to be a properly formatted INI file: '.$filename);
}
return $result;
}

/**
* Saves an array of data to an INI file.
*
* @param string $filename The path to the INI file to be saved.
* @param array $data The data to be saved to the INI file.
*
* @return void
* @throws ConfigurationException
*/
#[Override] function save(string $filename, array $data): void
{
$result = $this->generateIniLines($data);
$this->writeToFile($filename, $result);
}

/**
* Generate INI lines based on the provided data.
*
* @param array $data The data to generate INI lines from.
* @return array The generated INI lines.
*/
private function generateIniLines(array $data): array
{
$result = [];
foreach ($data as $key => $val) {
if (is_array($val)) {
$result[] = "[$key]";
$result = array_merge($result, $this->generateSubLines($val));
continue;
}
$result[] = $this->generateLine($key, $val);
}
return $result;
}

/**
* Generates an array of sublines based on the given subData array.
*
* @param array $subData The array containing subData.
* @return array The array of generated sublines.
*/
private function generateSubLines(array $subData): array
{
$result = [];
foreach ($subData as $sKey => $sVal) {
$result[] = $this->generateLine($sKey, $sVal);
}
return $result;
}

/**
* Generates a formatted line for a key-value pair.
*
* @param string $key The key of the pair.
* @param string|int $value The value of the pair.
* @return string The formatted line.
*/
private function generateLine(string $key, string|int $value): string
{
return "$key = " . (is_numeric($value) ? $value : '"' . $value . '"');
}

/**
* Writes an array of lines to a file.
*
* @param string $filename The path to the file to write to.
* @param array $lines The lines to be written to the file.
* @throws ConfigurationException if an unexpected error occurs while trying to save the file.
*/
private function writeToFile(string $filename, array $lines): void
{
$success = file_put_contents($filename, implode("\r\n", $lines));
if ($success === false) {
throw new ConfigurationException('An unexpected error occurred while trying to save INI file: ' . $filename);
}
}
}
72 changes: 72 additions & 0 deletions tests/IniConfigurationDriverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace CommonPHP\Tests\Drivers\IniConfigurationDriver;

use CommonPHP\Configuration\Drivers\IniConfigurationDriver\IniConfigurationDriver;
use CommonPHP\Configuration\Exceptions\ConfigurationException;
use PHPUnit\Framework\TestCase;

/**
* Class IniConfigurationDriverTest
*
* Tests for the IniConfigurationDriver class.
*/
class IniConfigurationDriverTest extends TestCase
{
/**
* @var IniConfigurationDriver
*/
protected IniConfigurationDriver $driver;

/**
* Setting up for test case
*/
protected function setUp(): void
{
$this->driver = new IniConfigurationDriver();
}

/**
* Test for the `load` method
* @throws ConfigurationException
*/
public function testLoad()
{
// Test case: when the file exists and is valid INI format
$filename = rtrim(__DIR__, '\\/').DIRECTORY_SEPARATOR.'test.ini';
$data = $this->driver->load($filename);


$this->assertEquals(['answerToLifeUniverseEverything' => 42], $data);

// Test case: when the file does not exist or is invalid INI format
$this->expectException(ConfigurationException::class);
$filename = 'path/to/invalid/test.ini';
$this->driver->load($filename);
}

/**
* Test to verify that save method writes correctly to an ini file.
*/
public function testSuccessfulSave(): void
{
$filename = rtrim(__DIR__, '\\/').DIRECTORY_SEPARATOR.'test.ini';
$data = ['answerToLifeUniverseEverything' => 42];
$this->driver->save($filename, $data);

$this->assertFileExists($filename);
$this->assertIsArray(parse_ini_file($filename, true));
}

/**
* Test to verify that save method throws exception when write operation fails.
*/
public function testSavingToFileFailure(): void
{
$this->expectException(ConfigurationException::class);

$filename = '/invalid/directory/test.ini';
$data = ['app' => ['name' => 'ApplicationName', 'version' => 1]];
$this->driver->save($filename, $data);
}
}
1 change: 1 addition & 0 deletions tests/test.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
answerToLifeUniverseEverything = 42

0 comments on commit 94fdcc3

Please sign in to comment.