325 lines
9.8 KiB
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];
|
|
}
|
|
}
|