* @author Hassan Khan * @author Filip Š * @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]; } }