From 51f8436e9d935dfa556d6f5f60098946920fcbc3 Mon Sep 17 00:00:00 2001 From: Jens True Date: Tue, 4 Jul 2023 09:43:27 +0000 Subject: [PATCH] Configuration refactoring with schema support. --- composer.json | 3 +- composer.lock | 308 +++++++++++++++++++++++++++++++++++++++++- src/App.php | 52 +++++-- src/CommandBackup.php | 10 +- src/CommandShow.php | 2 +- 5 files changed, 358 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 53415c1..c3d718a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "twig/twig": "^3.6", "symfony/process": "^6.3", "monolog/monolog": "^3.3", - "verifiedjoseph/ntfy-php-library": "^4.2" + "verifiedjoseph/ntfy-php-library": "^4.2", + "league/config": "^1.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index e0cdf49..0976451 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,83 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bb7231593eb9e5ee2dd36b699ea157c7", + "content-hash": "7c0718c0e4c6eabc3fec9cc0dbf816e2", "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "f41715465d65213d644d3141a6a93081be5d3549" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", + "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + }, + "time": "2022-10-27T11:44:00+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.7.0", @@ -331,6 +406,88 @@ ], "time": "2023-04-17T16:11:26+00:00" }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, { "name": "monolog/monolog", "version": "3.4.0", @@ -432,6 +589,155 @@ ], "time": "2023-06-21T08:46:11+00:00" }, + { + "name": "nette/schema", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.3" + }, + "time": "2022-10-13T01:24:26+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "shasum": "" + }, + "require": { + "php": ">=8.0 <8.3" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.4", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.0" + }, + "time": "2023-02-02T10:41:53+00:00" + }, { "name": "psr/container", "version": "2.0.2", diff --git a/src/App.php b/src/App.php index 9687018..0b9e4a3 100644 --- a/src/App.php +++ b/src/App.php @@ -7,6 +7,8 @@ use Symfony\Component\Yaml\Exception\ParseException; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Psr\Log\NullLogger; +use League\Config\Configuration; +use Nette\Schema\Expect; /** * Application class @@ -15,25 +17,56 @@ use Psr\Log\NullLogger; * * @author Jens True * @license https://opensource.org/licenses/gpl-license.php GNU Public License - * @link https://jcktrue.dks + * @link https://jcktrue.dk */ class App { protected Logger $logger; - protected mixed $config; + protected Configuration $config; /** * Create a new instance providing a config file * * @param string $configFile Relative or full path to YML config. + * @SuppressWarnings(PHPMD.StaticAccess) */ public function __construct(string $configFile) { + + // Define your configuration schema + $this->config = new Configuration([ + 'rclone' => Expect::structure([ + 'path' => Expect::string()->default('rclone'), + 'options' => Expect::arrayOf('string', 'string') + ]), + 'backup' => Expect::arrayOf(Expect::structure([ + 'title' => Expect::string(), + 'source' => Expect::string(), + 'destination' => Expect::string(), + ])), + 'notification' => Expect::arrayOf(Expect::structure([ + 'type' => Expect::string(), + 'domain' => Expect::string(), + 'topic' => Expect::string(), + ])), + 'log' => Expect::string()->assert( + function (string $path): bool { + return is_writable($path); + } + )->required(), + 'templates' => Expect::structure(['notify' => Expect::string()]) + ]); + $parser = new Yaml(); - $this->config = $parser->parseFile($configFile); + $parsedConfig = $parser->parseFile($configFile); + + + // Merge those values into your configuration schema: + $this->config->merge($parsedConfig); + $logger = new Logger('app'); - if (isset($this->config['log'])) { - $logger->pushHandler(new StreamHandler($this->getConfig()['log'])); + if ($this->config->get('log')) { + $logger->pushHandler(new StreamHandler($this->config->get('log'))); } $logger->info("Initialization complete"); @@ -41,13 +74,14 @@ class App } /** - * Get the full configuration + * Get configuration from key * - * @return mixed Full configuration structure + * @param non-empty-string $key Key to fetch + * @return mixed Configuration value */ - public function getConfig(): mixed + public function getConfig(string $key): mixed { - return $this->config; + return $this->config->get($key); } /** diff --git a/src/CommandBackup.php b/src/CommandBackup.php index f090f35..ba275a7 100644 --- a/src/CommandBackup.php +++ b/src/CommandBackup.php @@ -40,15 +40,15 @@ class CommandBackup extends Command return Command::FAILURE; } - $rclone = new Rclone(); + $rclone = new Rclone($app->getConfig('rclone.path')); $rclone->setLogger($app->getLogger()->withName('rclone')); $notification = new Notification(); - $notification->loadMany($app->getConfig()['notification']); + $notification->loadMany($app->getConfig('notification')); - $render = new Twig($app->getConfig()['templates']); + $render = new Twig($app->getConfig('templates')); - foreach ($sio->progressIterate($app->getConfig()['backup']) as $conf) { + foreach ($sio->progressIterate($app->getConfig('backup')) as $conf) { $title = $conf['title']; try { $template = array(); @@ -58,7 +58,7 @@ class CommandBackup extends Command $template['rclone_version'] = $rclone->getVersion(); $template['destination_size_before'] = $rclone->getSize((string)$conf['destination']); - $rclone->copy((string)$conf['source'], (string)$conf['destination'], $app->getConfig()['rclone']['options']); + $rclone->copy((string)$conf['source'], (string)$conf['destination'], $app->getConfig('rclone.options')); $template['destination_size_after'] = $rclone->getSize((string)$conf['destination']); $template['end'] = new DateTime(); diff --git a/src/CommandShow.php b/src/CommandShow.php index 287ba19..b63f765 100644 --- a/src/CommandShow.php +++ b/src/CommandShow.php @@ -38,7 +38,7 @@ class CommandShow extends Command $sio->table( ['Description', 'Source', 'Destination'], - $app->getConfig()['backup'] + $app->getConfig('backup') ); $sio->success("Done");