From af7562682b9da99bf87e86139f1141e959b6f2bc Mon Sep 17 00:00:00 2001 From: Cristiano Cinotti Date: Tue, 1 Sep 2026 15:20:43 +0200 Subject: [PATCH] Version 1.0 - Bump to PHP 8.5+ - Remove dependencies - Introduce Phive to safely manage our development tools - Update coding standard to PER 3.x --- .github/workflows/coverage_report.yml | 27 ++- .github/workflows/test.yml | 33 ++- .gitignore | 1 + .phive/phars.xml | 6 + .php-cs-fixer.php | 22 +- CONTRIBUTING.md | 32 ++- composer.json | 22 +- phpstan.neon | 4 + phpunit.xml | 1 - psalm.xml | 17 -- src/Exception/ParamResolverException.php | 10 +- src/ParamResolver.php | 64 +++-- src/Validator.php | 15 +- tests/Datasets/ResolveParams.php | 74 ------ tests/ParamResolverTest.php | 294 +++++++++++++++++++++++ tests/Pest.php | 1 - tests/Unit/ParamResolverTest.php | 225 ----------------- 17 files changed, 455 insertions(+), 393 deletions(-) create mode 100644 .phive/phars.xml create mode 100644 phpstan.neon delete mode 100644 psalm.xml delete mode 100644 tests/Datasets/ResolveParams.php create mode 100644 tests/ParamResolverTest.php delete mode 100644 tests/Pest.php delete mode 100644 tests/Unit/ParamResolverTest.php diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index 9347537..c5bd39b 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -4,25 +4,44 @@ on: push: branches: [master] jobs: + install_tools: + name: Install tools + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v7 + - name: Install tools + uses: ngmy/phive-install-action@master + - name: Upload tools to artifact store + uses: actions/upload-artifact@v7 + with: + name: tools + path: tools coverage: name: coverage + needs: install_tools runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.5 coverage: pcov - name: Install composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@v4 with: composer-options: "--prefer-dist --optimize-autoloader" + - name: Download tools from artifact store + uses: actions/download-artifact@v7 + with: + name: tools + path: tools - name: Build the code coverage report run: composer coverage:clover - name: Upload code coverage report - uses: qltysh/qlty-action/coverage@v1 + uses: qltysh/qlty-action/coverage@v2 with: token: ${{ secrets.QLTY_COVERAGE_TOKEN }} files: clover.xml \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aab9dea..ce1ab00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,28 +2,51 @@ name: Tests on: [push, pull_request] jobs: + install_tools: + name: Install tools + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v7 + - name: Install tools + uses: ngmy/phive-install-action@master + - name: Upload tools to artifact store + uses: actions/upload-artifact@v7 + with: + name: tools + path: tools test: name: test + needs: install_tools strategy: fail-fast: false matrix: - operating-system: [ubuntu-latest, windows-latest, macOS-latest] - php-versions: ['8.2', '8.3', '8.4'] + operating-system: ["ubuntu-latest", "windows-latest", "macOs-latest"] + php-versions: ["8.5"] runs-on: ${{ matrix.operating-system }} steps: - name: Set autocrlf on windows if: matrix.operating-system == 'windows-latest' run: git config --global core.autocrlf false - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} coverage: none - name: Install composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@v4 with: composer-options: "--prefer-dist --optimize-autoloader" + - name: Download tools from artifact store + uses: actions/download-artifact@v7 + with: + name: tools + path: tools + - name: Check coding standard + run: composer cs:check + - name: Static analysis tool + run: composer analytics - name: Test suite - run: composer test + run: composer test \ No newline at end of file diff --git a/.gitignore b/.gitignore index a64fced..94e1ead 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ clover.xml composer.phar composer.lock coverage/ +tools/ vendor/ diff --git a/.phive/phars.xml b/.phive/phars.xml new file mode 100644 index 0000000..b78ac6b --- /dev/null +++ b/.phive/phars.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 81b8ad2..87042fc 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -1,16 +1,18 @@ getFinder() - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') -; +$finder = new PhpCsFixer\Finder()->in(__DIR__ . '/src')->in(__DIR__ . '/tests'); -return $config; +return new PhpCsFixer\Config()->setRules(['@PER-CS3x0' => true])->setFinder($finder); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9f1119..143fa3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,18 +4,29 @@ First of all, many thanks to spend your time on this library! ## Workflow -1. Fork [susina/param-resolver](https://github.com/susina/param-resolver) repository, clone it locally and apply your patches. -2. Run the test suite by `composer test` command and fix all red tests. -3. Run static analysis tool by `composer analytics` command and fix all errors. -4. Fix the coding standard by running `composer cs:fix`. +### Pre-requisites -We provide a __check__ command for all the previously described actions: run `composer check` before submitting a pull request. +1. Have [PHP 8.5](https://php.net) installed. +2. Have [Composer](https://getcomposer.org) installed to manage dependencies and autoloading. +3. Have [Phive](https://phive.io) to install and manage our development tools (PhpUnit, PhpStan etc.) avoiding dependencies conflicts. + +1. Fork [susina/param-resolver](https://github.com/susina/param-resolver) repository. +2. Run `composer install` to install dependencies and create the correct autoloading map. +3. Run `phive install` to safely install our development tools. +4. Apply your patches. +5. Run the test suite by `composer test` command and fix all red tests. +6. Run static analysis tool by `composer analytics` command and fix all errors. +7. Fix the coding standard by running `composer cs:fix`. + +> [!TIP] +> We provide a __check__ command which runs the test suite, analytics tool and coding standard fix. +> So, before submitting a pull request you can simply run `composer check`. ## Running the Test Suite While developing, the test part is very important: if you apply a patch to the existing code, the test suite must run without errors or failures and if you add a new functionality, no one will consider it without tests. -Our test tool is [Pest](https://pestphp.com/) and we provide a script to launch it: +Our test tool is [PhpUnit](https://phpunit.de/) and we provide a script to launch it: ```bash composer test @@ -23,23 +34,22 @@ composer test ## Code Coverage -We provides three commands to print the code coverage report or to generate it in _html_ or _xml_ format: +We provides two commands to generate the code coverage report in _html_ or _xml_ format: -- `composer coverage` to print a coverage summary on your console - `composer coverage:html` command generates a code coverage report in _html_ format, into the directory `coverage/` - `composer coverage:clover` generates the report in _xml_ format, into `clover.xml` file. ## Static Analysis Tool -To prevent as many bugs as possible, we use a static analysis tool called [Psalm](https://psalm.dev/). +To prevent as many bugs as possible, we use a static analysis tool called [PHPStan](https://phpstan.org/). To launch it, run the following command: ```bash composer analytics ``` -After its analysis, Psalm outputs errors and issues with its suggestions on how to fix them. +After its analysis, PHPStan outputs errors and issues with its suggestions on how to fix them. ## Coding Standard @@ -57,4 +67,4 @@ and to show the errors without fixing them, run: composer cs:check ``` -If you want to learn more about our code style, see [https://github.com/susina/coding-standard](https://github.com/susina/coding-standard). +All the repositories inside Susina Project follow [PER 3.x](https://www.php-fig.org/per/coding-style/) coding style. \ No newline at end of file diff --git a/composer.json b/composer.json index cb5d5ea..0123e56 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": ">=8.2" + "php": ">=8.5" }, "autoload": { "psr-4": { @@ -22,38 +22,26 @@ "Susina\\ParamResolver\\Tests\\": "tests/" } }, - "require-dev": { - "psalm/phar": "^6", - "susina/coding-standard": "^2", - "pestphp/pest": "^3" - }, "scripts": { - "analytics": "php vendor/bin/psalm.phar", + "analytics": "php tools/phpstan", "check": [ "@test", "@analytics", "@cs:fix" ], - "coverage": "@test --coverage", "coverage:html": "@test --coverage-html coverage/", "coverage:clover": "@test --coverage-clover clover.xml", - "cs:check": "php-cs-fixer fix -v --diff --dry-run", - "cs:fix": "php-cs-fixer fix -v --diff", - "test": "pest" + "cs:check": "php tools/php-cs-fixer fix -v --diff --dry-run", + "cs:fix": "php tools/php-cs-fixer fix -v --diff", + "test": "php tools/phpunit" }, "scripts-descriptions": { "analytics": "Run static analysis tool", "check": "Perform all tests and analysis, required before submitting a pull request", "cs:check": "Run code style analysis, without fixing errors", "cs:fix": "Run code style analysis and fix errors", - "coverage": "Show coverage report to the console", "coverage:html": "Create a code coverage report in html format, into the `coverage/` directory", "coverage:clover": "Create a code coverage report in xml format, into the `clover.xml` file", "test": "Run the test suite" - }, - "config": { - "allow-plugins": { - "pestphp/pest-plugin": true - } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c308dcf --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 8 + paths: + - src diff --git a/phpunit.xml b/phpunit.xml index 7d0904f..0c12bb9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,7 +11,6 @@ - ./app ./src diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 2d01da2..0000000 --- a/psalm.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - diff --git a/src/Exception/ParamResolverException.php b/src/Exception/ParamResolverException.php index c6b46d4..64e3ddc 100644 --- a/src/Exception/ParamResolverException.php +++ b/src/Exception/ParamResolverException.php @@ -1,6 +1,8 @@ -config = $configuration; $parameters = []; foreach ($configuration as $key => $value) { - $key = $this->resolveValue($key); + $key = $this->resolveKey($key); $value = $this->resolveValue($value); $parameters[$key] = $this->unescapeValue($value); } @@ -72,8 +73,9 @@ public function resolve(array $configuration): array * Replaces parameter placeholders (%name%) by their values. * * @param mixed $value The value to be resolved - * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * + * @throws ParamResolverException If a parameter is not found or if a circular reference is detected. * @return mixed The resolved value */ private function resolveValue(mixed $value, array $resolving = []): mixed @@ -81,7 +83,7 @@ private function resolveValue(mixed $value, array $resolving = []): mixed if (is_array($value)) { $args = []; foreach ($value as $k => $v) { - $args[$this->resolveValue($k, $resolving)] = $this->resolveValue($v, $resolving); + $args[$this->resolveKey($k, $resolving)] = $this->resolveValue($v, $resolving); } return $args; @@ -98,8 +100,9 @@ private function resolveValue(mixed $value, array $resolving = []): mixed * Resolves parameters inside a string * * @param string $value The string to resolve - * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * + * @throws ParamResolverException If a parameter is not found or if a circular reference is detected. * @return mixed The resolved value */ private function resolveString(string $value, array $resolving = []): mixed @@ -112,9 +115,9 @@ private function resolveString(string $value, array $resolving = []): mixed * otherwise, it is replaced with the resolved string or number. */ - /** @var mixed */ - $onlyKey = null; - $replaced = preg_replace_callback('/%([^%\s]*+)%/', function (array $match) use ($resolving, $value, &$onlyKey) { + /** @var string */ + $onlyKey = ''; + $replaced = preg_replace_callback('/%([^%\s]*+)%/', function (array $match) use ($resolving, $value, &$onlyKey): string { $key = $match[1]; $env = $this->parseEnvironmentParams($key); @@ -122,7 +125,7 @@ private function resolveString(string $value, array $resolving = []): mixed $key === '' => '%%', $env !== null => $env, isset($resolving[$key]) => throw new ParamResolverException("Circular reference detected for parameter '$key'."), - default => null + default => null, }; if ($out !== null) { @@ -142,7 +145,7 @@ private function resolveString(string $value, array $resolving = []): mixed return $this->resolveString($resolved, $resolving); }, $value); - if ($setKey = isset($onlyKey)) { + if ($setKey = ($onlyKey !== '')) { $resolving[$onlyKey] = true; } @@ -194,7 +197,7 @@ private function get(int|string $propertyKey): mixed * Scan recursively an array to find a value of a given key. * * @param int|string $propertyKey The array key - * @param array $config The array to scan + * @param mixed[] $config The array to scan * * @return \Generator The value or null if not found */ @@ -228,4 +231,23 @@ private function parseEnvironmentParams(string $value): ?string return Validator::validateEnvParam(substr($value, 4)); } + + /** + * Resolve a value used as an array key. + * + * @param int|string $key + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * @throws ParamResolverException + * @return int|string + */ + private function resolveKey(int|string $key, array $resolving = []): int|string + { + $resolved = $this->resolveValue($key, $resolving); + + if (!is_string($resolved) && !is_int($resolved)) { + throw new ParamResolverException("Resolved key must be a string or an integer, got " . gettype($resolved)); + } + + return $resolved; + } } diff --git a/src/Validator.php b/src/Validator.php index 9b5cabe..ccbe086 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -1,6 +1,8 @@ - 'bar', 'I\'m a %foo%'], - ['foo' => 'bar', 'I\'m a bar'], - ], - [ - ['foo' => 'bar', '%foo%' => '%foo%'], - ['foo' => 'bar', 'bar' => 'bar'], - ], - [ - ['foo' => 'bar', '%foo%' => ['%foo%' => ['%foo%' => '%foo%']]], - ['foo' => 'bar', 'bar' => ['bar' => ['bar' => 'bar']]], - ], - [ - ['foo' => 'bar', 'I\'m a %%foo%%'], - ['foo' => 'bar', 'I\'m a %foo%'], - ], - [ - ['foo' => 'bar', 'I\'m a %foo% %%foo %foo%'], - ['foo' => 'bar', 'I\'m a bar %foo bar'], - ], - [ - ['foo' => ['bar' => ['ding' => 'I\'m a bar %%foo %%bar']]], - ['foo' => ['bar' => ['ding' => 'I\'m a bar %foo %bar']]], - ], - [ - ['foo' => 'bar', 'baz' => '%%%foo% %foo%%% %%foo%% %%%foo%%%'], - ['foo' => 'bar', 'baz' => '%bar bar% %foo% %bar%'], - ], - [ - ['baz' => '%%s?%%s', '%baz%'], - ['baz' => '%s?%s', '%s?%s'], - ], - [ - ['host' => 'foo.bar', 'port' => 1337, '%host%:%port%'], - ['host' => 'foo.bar', 'port' => 1337, 'foo.bar:1337'], - ], - [ - ['foo' => 'bar', '%foo%'], - ['foo' => 'bar', 'bar'], - ], - [ - ['foo' => 'bar', '% foo %'], - ['foo' => 'bar', '% foo %'], - ], - [ - ['foo' => 'bar', '{% set my_template = "foo" %}'], - ['foo' => 'bar', '{% set my_template = "foo" %}'], - ], - [ - ['foo' => 'bar', '50% is less than 100%'], - ['foo' => 'bar', '50% is less than 100%'], - ], - [ - ['foo' => ['bar' => 'baz', '%bar%' => 'babar'], 'babaz' => '%foo%'], - ['foo' => ['bar' => 'baz', 'baz' => 'babar'], 'babaz' => ['bar' => 'baz', 'baz' => 'babar']], - ], - [ - ['foo' => ['bar' => 'baz'], 'babaz' => '%foo%'], - ['foo' => ['bar' => 'baz'], 'babaz' => ['bar' => 'baz']], - ] -]); diff --git a/tests/ParamResolverTest.php b/tests/ParamResolverTest.php new file mode 100644 index 0000000..827990f --- /dev/null +++ b/tests/ParamResolverTest.php @@ -0,0 +1,294 @@ +resolver = new ParamResolver(); + } + + #[DataProvider('providerForResolveParams')] + public function testResolveValues(array $conf, array $expected): void + { + $this->assertSame($expected, $this->resolver->resolve($conf)); + } + + #[DataProvider('providerForResolveParams')] + public function testResolveValuesStatic(array $conf, array $expected): void + { + $this->assertSame($expected, ParamResolver::create()->resolve($conf)); + } + + public function testResolveParametersWithEnvironmentVariables(): void + { + putenv('host=127.0.0.1'); + putenv('user=root'); + + $config = [ + 'HoMe' => 'myHome', + 'project' => 'myProject', + 'subhome' => '%HoMe%/subhome', + 'property1' => 1, + 'property2' => false, + 'directories' => [ + 'project' => '%HoMe%/projects/%project%', + 'conf' => '%project%', + 'schema' => '%project%/schema', + 'template' => '%HoMe%/templates', + 'output%project%' => '/build', + ], + '%HoMe%' => 4, + 'host' => '%env.host%', + 'user' => '%env.user%', + ]; + + $expected = [ + 'HoMe' => 'myHome', + 'project' => 'myProject', + 'subhome' => 'myHome/subhome', + 'property1' => 1, + 'property2' => false, + 'directories' => [ + 'project' => 'myHome/projects/myProject', + 'conf' => 'myProject', + 'schema' => 'myProject/schema', + 'template' => 'myHome/templates', + 'outputmyProject' => '/build', + ], + 'myHome' => 4, + 'host' => '127.0.0.1', + 'user' => 'root', + ]; + + $this->assertSame($expected, $this->resolver->resolve($config)); + + //cleanup environment + putenv('host'); + putenv('user'); + } + + public function testDoesNotCastToStringsTheReplacedValues(): void + { + $conf = $this->resolver->resolve(['foo' => true, 'expfoo' => '%foo%', 'bar' => null, 'expbar' => '%bar%']); + + $this->assertTrue($conf['expfoo']); + $this->assertNull($conf['expbar']); + } + + public function testInvalidPlaceholdersThrowsException(): void + { + $this->expectException(ParamResolverException::class); + $this->expectExceptionMessageIs("Parameter 'baz' not found."); + + $this->resolver->resolve(['foo' => 'bar', '%baz%']); + } + + public function testNotExistentPlaceholderThrowsException(): void + { + $this->expectException(ParamResolverException::class); + $this->expectExceptionMessageIs("Parameter 'foobar' not found."); + + $this->resolver->resolve(['foo %foobar% bar']); + } + + public function testSimpleCircularReferenceThrowsException(): void + { + $this->expectException(ParamResolverException::class); + $this->expectExceptionMessageIs("Circular reference detected for parameter 'bar'."); + + $this->resolver->resolve(['foo' => '%bar%', 'bar' => '%foobar%', 'foobar' => '%foo%']); + } + + public function testComplexCircularReferenceThrowsException(): void + { + $this->expectException(ParamResolverException::class); + $this->expectExceptionMessageIs("Circular reference detected for parameter 'bar'."); + + $this->resolver->resolve(['foo' => 'a %bar%', 'bar' => 'a %foobar%', 'foobar' => 'a %foo%']); + } + + public function testResolveWithEnvironmentVariables(): void + { + putenv('home=myHome'); + putenv('schema=mySchema'); + putenv('isBoolean=true'); + putenv('integer=1'); + + $config = [ + 'home' => '%env.home%', + 'property1' => '%env.integer%', + 'property2' => '%env.isBoolean%', + 'direcories' => [ + 'projects' => '%home%/projects', + 'schema' => '%env.schema%', + 'template' => '%home%/templates', + 'output%env.home%' => '/build', + ], + ]; + + $expected = [ + 'home' => 'myHome', + 'property1' => '1', + 'property2' => 'true', + 'direcories' => [ + 'projects' => 'myHome/projects', + 'schema' => 'mySchema', + 'template' => 'myHome/templates', + 'outputmyHome' => '/build', + ], + ]; + + $this->assertEquals($expected, $this->resolver->resolve($config)); + + //cleanup environment + putenv('home'); + putenv('schema'); + putenv('isBoolean'); + putenv('integer'); + } + + public function testResolveParametersWithEmptyEnvironmentVariable(): void + { + putenv('home='); + + $config = [ + 'home' => '%env.home%', + ]; + + $expected = [ + 'home' => '', + ]; + + $this->assertEquals($expected, $this->resolver->resolve($config)); + + //cleanup environment + putenv('home'); + } + + public function testNotExistentEnvironmentVariablesThrowException(): void + { + $this->expectException(ParamResolverException::class); + $this->expectExceptionMessageIs("Environment variable 'foo' is not defined."); + + $this->resolver->resolve(['home' => '%env.foo%']); + } + + public function testNonStringParameterThrowsException(): void + { + $this->expectException(ParamResolverException::class); + $this->expectExceptionMessageIs('A string value must be composed of strings and/or numbers.'); + + $config = [ + 'foo' => 'a %bar%', + 'bar' => [], + 'baz' => '%foo%', + ]; + + $this->resolver->resolve($config); + } + + public function testResolveParamTwice(): void + { + $config = [ + 'foo' => 'bar', + 'baz' => '%foo%', + ]; + + $this->assertSame(['foo' => 'bar', 'baz' => 'bar'], $this->resolver->resolve($config)); + $this->assertSame([], $this->resolver->resolve($config)); + } + + public static function providerForResolveParams(): array + { + return [ + [ + ['foo'], + ['foo'], + ], + [ + ['foo' => 'bar', 'I\'m a %foo%'], + ['foo' => 'bar', 'I\'m a bar'], + ], + [ + ['foo' => 'bar', '%foo%' => '%foo%'], + ['foo' => 'bar', 'bar' => 'bar'], + ], + [ + ['foo' => 'bar', '%foo%' => ['%foo%' => ['%foo%' => '%foo%']]], + ['foo' => 'bar', 'bar' => ['bar' => ['bar' => 'bar']]], + ], + [ + ['foo' => 'bar', 'I\'m a %%foo%%'], + ['foo' => 'bar', 'I\'m a %foo%'], + ], + [ + ['foo' => 'bar', 'I\'m a %foo% %%foo %foo%'], + ['foo' => 'bar', 'I\'m a bar %foo bar'], + ], + [ + ['foo' => ['bar' => ['ding' => 'I\'m a bar %%foo %%bar']]], + ['foo' => ['bar' => ['ding' => 'I\'m a bar %foo %bar']]], + ], + [ + ['foo' => 'bar', 'baz' => '%%%foo% %foo%%% %%foo%% %%%foo%%%'], + ['foo' => 'bar', 'baz' => '%bar bar% %foo% %bar%'], + ], + [ + ['baz' => '%%s?%%s', '%baz%'], + ['baz' => '%s?%s', '%s?%s'], + ], + [ + ['host' => 'foo.bar', 'port' => 1337, '%host%:%port%'], + ['host' => 'foo.bar', 'port' => 1337, 'foo.bar:1337'], + ], + [ + ['foo' => 'bar', '%foo%'], + ['foo' => 'bar', 'bar'], + ], + [ + ['foo' => 'bar', '% foo %'], + ['foo' => 'bar', '% foo %'], + ], + [ + ['foo' => 'bar', '{% set my_template = "foo" %}'], + ['foo' => 'bar', '{% set my_template = "foo" %}'], + ], + [ + ['foo' => 'bar', '50% is less than 100%'], + ['foo' => 'bar', '50% is less than 100%'], + ], + [ + ['foo' => ['bar' => 'baz', '%bar%' => 'babar'], 'babaz' => '%foo%'], + ['foo' => ['bar' => 'baz', 'baz' => 'babar'], 'babaz' => ['bar' => 'baz', 'baz' => 'babar']], + ], + [ + ['foo' => ['bar' => 'baz'], 'babaz' => '%foo%'], + ['foo' => ['bar' => 'baz'], 'babaz' => ['bar' => 'baz']], + ], + ]; + } +} diff --git a/tests/Pest.php b/tests/Pest.php deleted file mode 100644 index f4165e8..0000000 --- a/tests/Pest.php +++ /dev/null @@ -1 +0,0 @@ -resolver = new ParamResolver(); -}); - -it('resolves parameters', function () { - putenv('host=127.0.0.1'); - putenv('user=root'); - - $config = [ - 'HoMe' => 'myHome', - 'project' => 'myProject', - 'subhome' => '%HoMe%/subhome', - 'property1' => 1, - 'property2' => false, - 'directories' => [ - 'project' => '%HoMe%/projects/%project%', - 'conf' => '%project%', - 'schema' => '%project%/schema', - 'template' => '%HoMe%/templates', - 'output%project%' => '/build', - ], - '%HoMe%' => 4, - 'host' => '%env.host%', - 'user' => '%env.user%', - ]; - - $expected = [ - 'HoMe' => 'myHome', - 'project' => 'myProject', - 'subhome' => 'myHome/subhome', - 'property1' => 1, - 'property2' => false, - 'directories' => [ - 'project' => 'myHome/projects/myProject', - 'conf' => 'myProject', - 'schema' => 'myProject/schema', - 'template' => 'myHome/templates', - 'outputmyProject' => '/build', - ], - 'myHome' => 4, - 'host' => '127.0.0.1', - 'user' => 'root', - ]; - - expect($expected)->toBe($this->resolver->resolve($config)); - - //cleanup environment - putenv('host'); - putenv('user'); -}); - -it('resolves parameters via static instantiation', function () { - putenv('host=127.0.0.1'); - putenv('user=root'); - - $config = [ - 'HoMe' => 'myHome', - 'project' => 'myProject', - 'subhome' => '%HoMe%/subhome', - 'property1' => 1, - 'property2' => false, - 'directories' => [ - 'project' => '%HoMe%/projects/%project%', - 'conf' => '%project%', - 'schema' => '%project%/schema', - 'template' => '%HoMe%/templates', - 'output%project%' => '/build', - ], - '%HoMe%' => 4, - 'host' => '%env.host%', - 'user' => '%env.user%', - ]; - - $expected = [ - 'HoMe' => 'myHome', - 'project' => 'myProject', - 'subhome' => 'myHome/subhome', - 'property1' => 1, - 'property2' => false, - 'directories' => [ - 'project' => 'myHome/projects/myProject', - 'conf' => 'myProject', - 'schema' => 'myProject/schema', - 'template' => 'myHome/templates', - 'outputmyProject' => '/build', - ], - 'myHome' => 4, - 'host' => '127.0.0.1', - 'user' => 'root', - ]; - - expect($expected)->toBe(ParamResolver::create()->resolve($config)); - - //cleanup environment - putenv('host'); - putenv('user'); -}); - - -it('resolves values', function (array $conf, array $expected) { - expect($expected)->toBe($this->resolver->resolve($conf)); -})->with('resolveParams'); - -it('does not cast to strigs the replaced values', function () { - $conf = $this->resolver->resolve(['foo' => true, 'expfoo' => '%foo%', 'bar' => null, 'expbar' => '%bar%']); - - expect($conf['expfoo'])->toBeTrue()->and($conf['expbar'])->toBeNull(); -}); - -it('finds invalid placeholders', fn () => $this->resolver->resolve(['foo' => 'bar', '%baz%'])) - ->throws(ParamResolverException::class, "Parameter 'baz' not found."); - -it('finds not existent placeholder', fn () => $this->resolver->resolve(['foo %foobar% bar'])) - ->throws(ParamResolverException::class, "Parameter 'foobar' not found."); - -it('discovers simple circular reference', fn () => $this->resolver->resolve(['foo' => '%bar%', 'bar' => '%foobar%', 'foobar' => '%foo%'])) - ->throws(ParamResolverException::class, "Circular reference detected for parameter 'bar'."); - -it('discovers complex circular reference', fn () => $this->resolver->resolve(['foo' => 'a %bar%', 'bar' => 'a %foobar%', 'foobar' => 'a %foo%'])) - ->throws(ParamResolverException::class, "Circular reference detected for parameter 'bar'."); - -it('resolves environment variable parameters', function () { - putenv('home=myHome'); - putenv('schema=mySchema'); - putenv('isBoolean=true'); - putenv('integer=1'); - - $config = [ - 'home' => '%env.home%', - 'property1' => '%env.integer%', - 'property2' => '%env.isBoolean%', - 'direcories' => [ - 'projects' => '%home%/projects', - 'schema' => '%env.schema%', - 'template' => '%home%/templates', - 'output%env.home%' => '/build', - ], - ]; - - $expected = [ - 'home' => 'myHome', - 'property1' => '1', - 'property2' => 'true', - 'direcories' => [ - 'projects' => 'myHome/projects', - 'schema' => 'mySchema', - 'template' => 'myHome/templates', - 'outputmyHome' => '/build', - ], - ]; - - expect($this->resolver->resolve($config))->toBe($expected); - - //cleanup environment - putenv('home'); - putenv('schema'); - putenv('isBoolean'); - putenv('integer'); -}); - -it('resolves empty environment variable', function () { - putenv('home='); - - $config = [ - 'home' => '%env.home%', - ]; - - $expected = [ - 'home' => '', - ]; - - expect($expected)->toBe($this->resolver->resolve($config)); - - //cleanup environment - putenv('home'); -}); - -it('finds not existent environment variable', function () { - putenv('home=myHome'); - - $config = [ - 'home' => '%env.home%', - 'property1' => '%env.foo%', - ]; - - $this->resolver->resolve($config); -})->throws(ParamResolverException::class, "Environment variable 'foo' is not defined."); - -it('finds non string or number parameters', function () { - $config = [ - 'foo' => 'a %bar%', - 'bar' => [], - 'baz' => '%foo%', - ]; - - $this->resolver->resolve($config); -})->throws(ParamResolverException::class, 'A string value must be composed of strings and/or numbers.'); - -it('resolve a param twice', function () { - $config = [ - 'foo' => 'bar', - 'baz' => '%foo%', - ]; - - expect(['foo' => 'bar', 'baz' => 'bar'])->toBe($this->resolver->resolve($config)) - ->and([])->toBe($this->resolver->resolve($config)); -});