Files
config/src/Config.php

325 lines
9.8 KiB
PHP

<?php
namespace Siteworx\Config;
use Siteworx\Config\Exception\EmptyDirectoryException;
use Siteworx\Config\Exception\FileNotFoundException;
use Siteworx\Config\Exception\UnsupportedFormatException;
use Siteworx\Config\Parser\Ini;
use Siteworx\Config\Parser\Json;
use Siteworx\Config\Parser\ParserInterface;
use Siteworx\Config\Parser\Php;
use Siteworx\Config\Parser\Properties;
use Siteworx\Config\Parser\Serialize;
use Siteworx\Config\Parser\Xml;
use Siteworx\Config\Parser\Yaml;
use Siteworx\Config\Writer\WriterInterface;
/**
* Configuration reader and writer for PHP.
*
* @package Config
* @author Jesus A. Domingo <jesus.domingo@gmail.com>
* @author Hassan Khan <contact@hassankhan.me>
* @author Filip Š <projects@filips.si>
* @link https://github.com/noodlehaus/config
* @license MIT
*/
class Config extends AbstractConfig
{
/**
* All formats supported by Config.
*
* @var array
*/
protected array $supportedParsers = [
Php::class,
Ini::class,
Json::class,
Xml::class,
Yaml::class,
Properties::class,
Serialize::class
];
/**
* All formats supported by Config.
*
* @var array
*/
protected array $supportedWriters = [
Writer\Ini::class,
Writer\Json::class,
Writer\Xml::class,
Writer\Yaml::class,
Writer\Properties::class,
Writer\Serialize::class
];
/**
* Static method for loading a Config instance.
*
* @param string|array $paths Filenames or string with configuration
* @param ParserInterface|null $parser Configuration parser
* @param bool $loadFromString
* @return Config
* @throws EmptyDirectoryException
* @throws FileNotFoundException
* @throws UnsupportedFormatException
*/
public static function load(
string | array $paths,
?ParserInterface $parser = null,
bool $loadFromString = false
): Config {
return new static($paths, $parser, $loadFromString);
}
/**
* Loads a Config instance.
*
* @param string|array $values Filenames or string with configuration
* @param ParserInterface | null $parser Configuration parser
* @throws EmptyDirectoryException
* @throws FileNotFoundException
* @throws UnsupportedFormatException
*/
private function __construct(string | array $values, ?ParserInterface $parser = null, bool $loadFromString = false)
{
if ($loadFromString && !is_array($values) && !file_exists($values)) {
if ($parser === null) {
throw new \InvalidArgumentException('Parser is required to be provided for a string');
}
$this->loadFromString($values, $parser);
} else {
$this->loadFromFile($values, $parser);
}
parent::__construct($this->data);
}
/**
* Loads configuration from file.
*
* @param string|array $path Filenames or directories with configuration
* @param ParserInterface | null $parser Configuration parser
*
* @throws EmptyDirectoryException If `$path` is an empty directory
* @throws FileNotFoundException
* @throws UnsupportedFormatException
*/
protected function loadFromFile(string | array $path, ?ParserInterface $parser = null): void
{
$paths = $this->getValidPaths($path);
$this->data = [];
foreach ($paths as $filePath) {
if ($parser === null) {
// Get file information
$info = pathinfo($filePath);
$parts = explode('.', $info['basename']);
$extension = array_pop($parts);
// Skip the `dist` extension
if ($extension === 'dist') {
$extension = array_pop($parts);
}
// Get file parser
$parser = $this->getParser($extension);
// Try to load file
$newData = $parser->parseFile($filePath);
$oldData = $this->data;
$this->data = array_merge($oldData, $newData);
// Clean parser
$parser = null;
} else {
$newData = $parser->parseFile($filePath);
$oldData = $this->data;
$this->data = array_merge($oldData, $newData);
}
}
}
/**
* Writes configuration to file.
*
* @param string $filename Filename to save configuration to
* @param WriterInterface|null $writer Configuration writer
*
* @throws Exception\WriteException if the data could not be written to the file
* @throws UnsupportedFormatException
*/
public function toFile(string $filename, ?WriterInterface $writer = null): void
{
if ($writer === null) {
// Get file information
$info = pathinfo($filename);
$parts = explode('.', $info['basename']);
$extension = array_pop($parts);
// Skip the `dist` extension
if ($extension === 'dist') {
$extension = array_pop($parts);
}
// Get file writer
$writer = $this->getWriter($extension);
// Try to save file
$writer->toFile($this->all(), $filename);
// Clean writer
$writer = null;
} else {
// Try to load file using specified writer
$writer->toFile($this->all(), $filename);
}
}
/**
* Loads configuration from string.
*
* @param string $configuration String with configuration
* @param ParserInterface $parser Configuration parser
*/
protected function loadFromString(string $configuration, ParserInterface $parser): void
{
$this->data = [];
// Try to parse string
$this->data = array_replace_recursive($this->data, $parser->parseString($configuration));
}
/**
* Writes configuration to string.
*
* @param WriterInterface $writer Configuration writer
* @param boolean $pretty Encode pretty
*/
public function toString(WriterInterface $writer, bool $pretty = true): string
{
return $writer->toString($this->all(), $pretty);
}
/**
* Gets a parser for a given file extension.
*
* @throws UnsupportedFormatException If `$extension` is an unsupported file format
*/
protected function getParser(string $extension): ParserInterface
{
foreach ($this->supportedParsers as $parser) {
if (in_array($extension, $parser::getSupportedExtensions(), true)) {
return new $parser();
}
}
// If none exist, then throw an exception
throw new UnsupportedFormatException('Unsupported configuration format');
}
/**
* Gets a writer for a given file extension.
*
* @throws UnsupportedFormatException If `$extension` is an unsupported file format
*/
protected function getWriter(string $extension): WriterInterface
{
foreach ($this->supportedWriters as $writer) {
if (in_array($extension, $writer::getSupportedExtensions(), true)) {
return new $writer();
}
}
// If none exist, then throw an exception
throw new UnsupportedFormatException('Unsupported configuration format' . $extension);
}
/**
* Gets an array of paths
*
* @param array $path
*
* @return array
*
* @throws FileNotFoundException|EmptyDirectoryException If a file is not found at `$path`
*/
protected function getPathsFromArray(array $path): array
{
$paths = [];
foreach ($path as $unverifiedPath) {
try {
// Check if `$unverifiedPath` is optional
// If it exists, then it's added to the list
// If it doesn't, it throws an exception which we catch
if ($unverifiedPath[0] !== '?') {
$validPaths = $this->getValidPaths($unverifiedPath);
$originalPaths = $paths;
$paths = array_merge($originalPaths, $validPaths);
continue;
}
$optionalPath = ltrim($unverifiedPath, '?');
$validPaths = $this->getValidPaths($optionalPath);
$originalPaths = $paths;
$paths = array_merge($originalPaths, $validPaths);
} catch (FileNotFoundException $e) {
// If `$unverifiedPath` is optional, then skip it
if ($unverifiedPath[0] === '?') {
continue;
}
// Otherwise, rethrow the exception
throw $e;
}
}
return $paths;
}
/**
* Checks `$path` to see if it is either an array, a directory, or a file.
*
* @param string|array $path
*
* @return array
*
* @throws EmptyDirectoryException If `$path` is an empty directory
*
* @throws FileNotFoundException If a file is not found at `$path`
*/
protected function getValidPaths(string | array $path): array
{
if (is_array($path)) {
return $this->getPathsFromArray($path);
}
// If `$path` is a directory
if (is_dir($path)) {
$paths = glob($path . '/*.*');
if (empty($paths)) {
throw new EmptyDirectoryException("Configuration directory: [$path] is empty");
}
return $paths;
}
// If `$path` is not a file, throw an exception
if (!file_exists($path)) {
throw new FileNotFoundException("Configuration file: [$path] cannot be found");
}
return [$path];
}
}