* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ declare(strict_types=1); namespace OCC\OaiPmh2; use OCC\Basics\Traits\Singleton; use Symfony\Component\Filesystem\Exception\FileNotFoundException; use Symfony\Component\Filesystem\Path; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Exception\ValidationFailedException; use Symfony\Component\Validator\Validation; use Symfony\Component\Yaml\Yaml; /** * Reads, validates and provides configuration settings. * * @author Sebastian Meyer * @package OAIPMH2 * * @property-read string $repositoryName * @property-read string $adminEmail * @property-read string $database * @property-read array $metadataPrefix * @property-read string $deletedRecords * @property-read int $maxRecords * @property-read int $tokenValid * * @template TKey of string * @template TValue of array|int|string */ class Configuration { use Singleton; /** * Fully qualified path to the configuration file. * * @var string */ protected const CONFIG_FILE = __DIR__ . '/../config/config.yml'; /** * The configuration settings. * * @var array */ protected readonly array $settings; /** * Get constraints for configuration array. * * @return Assert\Collection The collection of constraints */ protected function getValidationConstraints(): Assert\Collection { return new Assert\Collection([ 'repositoryName' => [ new Assert\Type('string'), new Assert\NotBlank() ], 'adminEmail' => [ new Assert\Type('string'), new Assert\Email(['mode' => 'html5']), new Assert\NotBlank() ], 'database' => [ new Assert\Type('string'), new Assert\NotBlank() ], 'metadataPrefix' => [ new Assert\Type('array'), new Assert\All([ new Assert\Collection([ 'schema' => [ new Assert\Type('string'), new Assert\Url(), new Assert\NotBlank() ], 'namespace' => [ new Assert\Type('string'), new Assert\Url(), new Assert\NotBlank() ] ]) ]) ], 'deletedRecords' => [ new Assert\Type('string'), new Assert\Choice(['no', 'persistent', 'transient']), new Assert\NotBlank() ], 'maxRecords' => [ new Assert\Type('int'), new Assert\Range([ 'min' => 1, 'max' => 100 ]) ], 'tokenValid' => [ new Assert\Type('int'), new Assert\Range([ 'min' => 300, 'max' => 86400 ]) ] ]); } /** * Read and validate configuration file. * * @return array The configuration array * * @throws FileNotFoundException|ValidationFailedException */ protected function loadConfigFile(): array { $configPath = Path::canonicalize(self::CONFIG_FILE); if (!is_readable($configPath)) { throw new FileNotFoundException( sprintf( 'Configuration file "%s" not found or not readable.', $configPath ), 500, null, $configPath ); } $config = Yaml::parseFile($configPath); $validator = Validation::createValidator(); $violations = $validator->validate($config, $this->getValidationConstraints()); if ($violations->count() > 0) { throw new ValidationFailedException(null, $violations); } /** @var array */ return $config; } /** * Load and validate configuration settings from YAML file. * * @throws FileNotFoundException | ValidationFailedException */ private function __construct() { try { $this->settings = $this->loadConfigFile(); } catch (FileNotFoundException | ValidationFailedException $exception) { throw $exception; } } /** * Magic getter for $this->settings. * * @param TKey $name The setting to retrieve * * @return TValue|null The setting or NULL */ public function __get(string $name): mixed { return $this->settings[$name] ?? null; } }