From 0a409957156092e9745013d27da47694a88f00d2 Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Sun, 7 Apr 2024 16:15:45 +0200 Subject: [PATCH] Add support for Laravel 10 + 11 --- .github/workflows/check-code.yml | 20 ++--- composer.json | 9 +-- phpstan-baseline.neon | 4 - src/Log/Channels/ConsoleOutputChannel.php | 19 ++--- src/Log/Handlers/ConsoleOutputHandler.php | 33 ++++---- src/Validation/Rules/ArrayOrStringRule.php | 15 ++-- src/Validation/Rules/BooleanRule.php | 19 +++-- src/Validation/Rules/NumberRule.php | 19 +++-- src/Validation/Rules/PerPageRule.php | 15 ++-- src/Validation/Rules/RemoteUrlRule.php | 19 +++-- .../Validation/Rules/AbstractRuleTest.php | 31 +++++--- .../Unit/Validation/Rules/NumberRuleTest.php | 6 +- .../Validation/Rules/RemoteUrlRuleTest.php | 76 ++++++++----------- 13 files changed, 140 insertions(+), 145 deletions(-) diff --git a/.github/workflows/check-code.yml b/.github/workflows/check-code.yml index d957bc21..532607de 100644 --- a/.github/workflows/check-code.yml +++ b/.github/workflows/check-code.yml @@ -20,23 +20,13 @@ concurrency: cancel-in-progress: true jobs: - code_l9: - name: "Code check - Laravel 9" - strategy: - matrix: - phpVersion: [ "8.1", "8.2", "8.3" ] - uses: wrk-flow/reusable-workflows/.github/workflows/php-check.yml@59e60b0bf283c13b2a5e1dc70641e62730d81bc0 - with: - composerRequireDev: "laravel/framework:^9" - phpVersion: "${{ matrix.phpVersion }}" - secrets: inherit code_l10: name: "Code check - Laravel 10" strategy: matrix: phpVersion: [ "8.1", "8.2", "8.3" ] - uses: wrk-flow/reusable-workflows/.github/workflows/php-check.yml@59e60b0bf283c13b2a5e1dc70641e62730d81bc0 + uses: wrk-flow/reusable-workflows/.github/workflows/php-check.yml@09ffc469f54061cac1e9cebab2c5896676852b5d with: composerRequireDev: "laravel/framework:^10" phpVersion: "${{ matrix.phpVersion }}" @@ -47,7 +37,7 @@ jobs: strategy: matrix: phpVersion: [ "8.2", "8.3" ] - uses: wrk-flow/reusable-workflows/.github/workflows/php-check.yml@59e60b0bf283c13b2a5e1dc70641e62730d81bc0 + uses: wrk-flow/reusable-workflows/.github/workflows/php-check.yml@09ffc469f54061cac1e9cebab2c5896676852b5d with: composerRequireDev: "laravel/framework:^11" phpVersion: "${{ matrix.phpVersion }}" @@ -58,7 +48,7 @@ jobs: strategy: matrix: phpVersion: [ "8.1", "8.2", "8.3" ] - uses: wrk-flow/reusable-workflows/.github/workflows/php-tests.yml@59e60b0bf283c13b2a5e1dc70641e62730d81bc0 + uses: wrk-flow/reusable-workflows/.github/workflows/php-tests.yml@09ffc469f54061cac1e9cebab2c5896676852b5d with: composerRequireDev: "laravel/framework:^9" phpVersion: "${{ matrix.phpVersion }}" @@ -69,7 +59,7 @@ jobs: strategy: matrix: phpVersion: [ "8.1", "8.2", "8.3" ] - uses: wrk-flow/reusable-workflows/.github/workflows/php-tests.yml@59e60b0bf283c13b2a5e1dc70641e62730d81bc0 + uses: wrk-flow/reusable-workflows/.github/workflows/php-tests.yml@09ffc469f54061cac1e9cebab2c5896676852b5d with: composerRequireDev: "laravel/framework:^10" phpVersion: "${{ matrix.phpVersion }}" @@ -80,7 +70,7 @@ jobs: strategy: matrix: phpVersion: [ "8.2", "8.3" ] - uses: wrk-flow/reusable-workflows/.github/workflows/php-tests.yml@59e60b0bf283c13b2a5e1dc70641e62730d81bc0 + uses: wrk-flow/reusable-workflows/.github/workflows/php-tests.yml@09ffc469f54061cac1e9cebab2c5896676852b5d with: composerRequireDev: "laravel/framework:^11" phpVersion: "${{ matrix.phpVersion }}" diff --git a/composer.json b/composer.json index 7799c376..9557a57b 100644 --- a/composer.json +++ b/composer.json @@ -19,15 +19,15 @@ ], "require": { "php": ">=8.1", - "laravel/framework": "^v9.52.16 | ^v10.48.4 | ^v11.2.0", - "psr/log": "^2 | ^3", + "laravel/framework": "^v10.48.4 | ^v11.2.0", + "psr/log": "^3", "psr/simple-cache": "^3.0" }, "require-dev": { "larastrict/conventions": "v0.1.1", "mockery/mockery": "^1.6", "nette/php-generator": "^v4.1.4", - "orchestra/testbench": "^v7.41 | ^v8.22.2 | ^v9.0.3", + "orchestra/testbench": "^v8.22.2 | ^v9.0.3", "phpstan/phpstan-mockery": "^1.1.0", "phpstan/phpstan-phpunit": "^v1.3.16" }, @@ -36,6 +36,7 @@ "@php ./vendor/bin/testbench package:discover --ansi" ], "analyse": "./vendor/bin/phpstan", + "analyse:b": "./vendor/bin/phpstan -b", "check": [ "@lint", "@test", @@ -47,10 +48,8 @@ ], "lint:check": "./vendor/bin/ecs", "lint:fix": "./vendor/bin/ecs --fix", - "lint:stan": "./vendor/bin/phpstan", "lint:upgrade": "vendor/bin/rector process", "lint:upgrade:check": "vendor/bin/rector process --dry-run", - "lint:stan:b": "./vendor/bin/phpstan -b", "test": "./vendor/bin/phpunit", "test:stubs": "STUBS_GENERATE=true ./vendor/bin/phpunit", "test:coverage": "./vendor/bin/phpunit --coverage-text" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c9967170..1490ee4e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -35,10 +35,6 @@ parameters: count: 1 path: src/Http/Resources/JsonResourceCollection.php - - - message: "#^Method LaraStrict\\\\Log\\\\Channels\\\\ConsoleOutputChannel\\:\\:getLevel\\(\\) should return 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning' but returns int\\|string\\.$#" - count: 1 - path: src/Log/Channels/ConsoleOutputChannel.php - message: "#^Parameter \\$getValue of method LaraStrict\\\\Cache\\\\Contracts\\\\CacheMeServiceContract\\:\\:get\\(\\) expects Closure\\(mixed, mixed, mixed, mixed, mixed, mixed\\)\\: LaraStrict\\\\Providers\\\\Entities\\\\AppServiceProviderEntity, Closure\\(Illuminate\\\\Contracts\\\\Foundation\\\\Application\\)\\: LaraStrict\\\\Providers\\\\Entities\\\\AppServiceProviderEntity given\\.$#" diff --git a/src/Log/Channels/ConsoleOutputChannel.php b/src/Log/Channels/ConsoleOutputChannel.php index e48bcaf2..0fd18b1a 100644 --- a/src/Log/Channels/ConsoleOutputChannel.php +++ b/src/Log/Channels/ConsoleOutputChannel.php @@ -7,13 +7,11 @@ use Illuminate\Log\LogManager; use LaraStrict\Log\Handlers\ConsoleOutputHandler; use LaraStrict\Log\Managers\ConsoleOutputManager; +use Monolog\Level; use Monolog\Logger; -use Psr\Log\LogLevel; /** * @internal - * @phpstan-import-type LevelName from Logger - * @phpstan-import-type Level from Logger */ final class ConsoleOutputChannel extends LogManager { @@ -22,7 +20,6 @@ final class ConsoleOutputChannel extends LogManager */ public function __invoke(array $config): Logger { - $handler = new ConsoleOutputHandler( manager: $this->app->make(ConsoleOutputManager::class), level: self::getLevel($config), @@ -36,21 +33,21 @@ public function __invoke(array $config): Logger } /** - * @param array $config - * - * @phpstan-return Level|LevelName|LogLevel::* + * @param array $config */ - private static function getLevel(array $config): int|string + private static function getLevel(array $config): Level { if (array_key_exists('level', $config)) { $level = $config['level']; - if (is_string($level) || is_int($level)) { - return $level; + if (is_string($level) || is_int($level) || $level instanceof Level) { + // TODO: I have no clue hot to make PHPStan happy here + // @phpstan-ignore-next-line + return Logger::toMonologLevel($level); } } - return Logger::DEBUG; + return Level::Debug; } /** diff --git a/src/Log/Handlers/ConsoleOutputHandler.php b/src/Log/Handlers/ConsoleOutputHandler.php index 4cb6e26c..bdd8796a 100644 --- a/src/Log/Handlers/ConsoleOutputHandler.php +++ b/src/Log/Handlers/ConsoleOutputHandler.php @@ -8,29 +8,30 @@ use Illuminate\Console\View\Components\Factory; use LaraStrict\Log\Managers\ConsoleOutputManager; use Monolog\Handler\AbstractProcessingHandler; -use Monolog\Logger; +use Monolog\Level; +use Monolog\LogRecord; use Psr\Log\LogLevel; use Symfony\Component\Console\Output\OutputInterface; /** * @internal - * @phpstan-import-type LevelName from Logger - * @phpstan-import-type Level from Logger */ final class ConsoleOutputHandler extends AbstractProcessingHandler { /** - * @phpstan-param Level|LevelName|LogLevel::* $level + * @param int|string|Level|LogLevel::* $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level */ public function __construct( private readonly ConsoleOutputManager $manager, - string|int $level = Logger::DEBUG, + string|int|Level $level = Level::Debug, bool $bubble = true ) { parent::__construct($level, $bubble); } - protected function write(array $record): void + protected function write(LogRecord $record): void { $consoleOutputFactory = $this->manager->getOutputFactory(); $outputStyle = $this->manager->getOutputStyle(); @@ -43,27 +44,27 @@ protected function write(array $record): void return; } - $context = $record['context']; + $context = $record->context; $hasContext = $context !== []; - $message = $record['message']; + $message = $record->message; switch ($record['level']) { - case Logger::NOTICE: - case Logger::INFO: + case Level::Notice: + case Level::Info: $consoleOutputFactory->info($message); break; - case Logger::WARNING: + case Level::Warning: $consoleOutputFactory->warn($message); break; - case Logger::ALERT: - case Logger::EMERGENCY: - case Logger::CRITICAL: + case Level::Alert: + case Level::Emergency: + case Level::Critical: $consoleOutputFactory->alert($message); break; - case Logger::ERROR: + case Level::Error: $consoleOutputFactory->error($message); break; - case Logger::DEBUG: + case Level::Debug: if ($outputStyle->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) { return; } diff --git a/src/Validation/Rules/ArrayOrStringRule.php b/src/Validation/Rules/ArrayOrStringRule.php index 5392797f..045d5ae2 100644 --- a/src/Validation/Rules/ArrayOrStringRule.php +++ b/src/Validation/Rules/ArrayOrStringRule.php @@ -4,17 +4,20 @@ namespace LaraStrict\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; -class ArrayOrStringRule implements Rule +final class ArrayOrStringRule implements ValidationRule { - public function passes($attribute, $value) + public function validate(string $attribute, mixed $value, Closure $fail): void { - return is_array($value) || is_string($value); + if (self::passes($value) === false) { + $fail('Given :attribute must by array or string'); + } } - public function message(): string + private function passes(mixed $value): bool { - return 'Given :attribute must by array or string'; + return is_array($value) || is_string($value); } } diff --git a/src/Validation/Rules/BooleanRule.php b/src/Validation/Rules/BooleanRule.php index 924b376e..f3093f0d 100644 --- a/src/Validation/Rules/BooleanRule.php +++ b/src/Validation/Rules/BooleanRule.php @@ -4,17 +4,24 @@ namespace LaraStrict\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; -class BooleanRule implements Rule +final class BooleanRule implements ValidationRule { - public function passes($attribute, $value): bool + public function validate(string $attribute, mixed $value, Closure $fail): void { - return in_array($value, [true, false, 'true', 'false', 0, 1, '0', '1'], true); + if (self::passes($value) === false) { + $fail('Given :attribute is not a valid boolean: true, false, 0, 1 (can be string or numeric value).'); + } } - public function message(): string + private function passes(mixed $value): bool { - return 'Given :attribute is not a valid boolean: true, false, 0, 1 (can be string or numeric value).'; + if (is_array($value) === false) { + return false; + } + + return in_array($value, [true, false, 'true', 'false', 0, 1, '0', '1'], true); } } diff --git a/src/Validation/Rules/NumberRule.php b/src/Validation/Rules/NumberRule.php index e0aab636..2d785999 100644 --- a/src/Validation/Rules/NumberRule.php +++ b/src/Validation/Rules/NumberRule.php @@ -4,12 +4,20 @@ namespace LaraStrict\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; use LaraStrict\Core\Helpers\Value; -class NumberRule implements Rule +final class NumberRule implements ValidationRule { - public function passes($attribute, $value): bool + public function validate(string $attribute, mixed $value, Closure $fail): void + { + if (self::passes($value) === false) { + $fail('Given :attribute is not a valid number or it exceeds int/float limits.'); + } + } + + private static function passes(mixed $value): bool { if (is_string($value) === false && is_numeric($value) === false) { return false; @@ -29,11 +37,6 @@ public function passes($attribute, $value): bool return str_contains((string) $value, 'E+') === false; } - public function message(): string - { - return 'Given :attribute is not a valid number or it exceeds int/float limits.'; - } - /** * @return ($value is non-empty-string ? bool : ($value is int ? true : false)) */ diff --git a/src/Validation/Rules/PerPageRule.php b/src/Validation/Rules/PerPageRule.php index 9a7c984b..ab1f51db 100644 --- a/src/Validation/Rules/PerPageRule.php +++ b/src/Validation/Rules/PerPageRule.php @@ -4,22 +4,25 @@ namespace LaraStrict\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; -class PerPageRule implements Rule +final class PerPageRule implements ValidationRule { public function __construct( private readonly int $max = 100 ) { } - public function passes($attribute, $value) + public function validate(string $attribute, mixed $value, Closure $fail): void { - return is_numeric($value) && $value > 0 && $value <= $this->max; + if ($this->passes($value) === false) { + $fail('Not a valid :attribute. Must be between 1 - ' . $this->max); + } } - public function message() + private function passes(mixed $value): bool { - return 'Not a valid :attribute. Must be between 1 - ' . $this->max; + return is_numeric($value) && $value > 0 && $value <= $this->max; } } diff --git a/src/Validation/Rules/RemoteUrlRule.php b/src/Validation/Rules/RemoteUrlRule.php index 212000fd..fb1d6817 100644 --- a/src/Validation/Rules/RemoteUrlRule.php +++ b/src/Validation/Rules/RemoteUrlRule.php @@ -4,11 +4,19 @@ namespace LaraStrict\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Closure; +use Illuminate\Contracts\Validation\ValidationRule; -class RemoteUrlRule implements Rule +final class RemoteUrlRule implements ValidationRule { - public function passes($attribute, $value): bool + public function validate(string $attribute, mixed $value, Closure $fail): void + { + if ($this->passes($value) === false) { + $fail('Given :attribute is not a valid url (public IP or domain on http/s protocol)'); + } + } + + private function passes(mixed $value): bool { if (is_string($value) === false) { return false; @@ -37,9 +45,4 @@ public function passes($attribute, $value): bool // Must contain top level domain return preg_match('#^[\w\d\-.]{1,63}\.[a-z]{2,6}$#', $host) !== 0; } - - public function message(): string - { - return 'Given :attribute is not a valid url (public IP or domain on http/s protocol)'; - } } diff --git a/tests/Unit/Validation/Rules/AbstractRuleTest.php b/tests/Unit/Validation/Rules/AbstractRuleTest.php index 2bd15715..7d84eed0 100644 --- a/tests/Unit/Validation/Rules/AbstractRuleTest.php +++ b/tests/Unit/Validation/Rules/AbstractRuleTest.php @@ -4,41 +4,48 @@ namespace Tests\LaraStrict\Unit\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Translation\PotentiallyTranslatedString; +use LaraStrict\Testing\Laravel\Contracts\Translation\TranslatorAssert; use PHPUnit\Framework\TestCase; +/** + * @todo move to testing package + */ abstract class AbstractRuleTest extends TestCase { /** - * @dataProvider testPassesData + * @dataProvider passesData */ public function testPasses(RuleExpectation $expectation): void { $rule = $this->createRule(); - $this->assertEquals($expectation->expectedIsValid, $rule->passes('test', $expectation->value)); - } + $failed = false; + $fail = function (string $message) use (&$failed): PotentiallyTranslatedString { + $failed = true; + $this->assertEquals($this->getExpectedMessage(), str_replace(':attribute', 'test', $message)); + return new PotentiallyTranslatedString($message, new TranslatorAssert()); + }; - public function testMessage(): void - { - $rule = $this->createRule(); + $rule->validate('test', $expectation->value, $fail); - $this->assertEquals($this->getExpectedMessage(), str_replace(':attribute', 'test', $rule->message())); + $this->assertEquals($expectation->expectedIsValid, $failed === false); } - abstract public function createRule(): Rule; + abstract public function createRule(): ValidationRule; /** * @return array */ - abstract protected function testData(): array; + abstract protected function data(): array; abstract protected function getExpectedMessage(): string; - protected function testPassesData(): array + protected function passesData(): array { $data = []; - foreach ($this->testData() as $index => $entity) { + foreach ($this->data() as $index => $entity) { $data[$index . ' with value: ' . $entity->value] = [$entity]; } diff --git a/tests/Unit/Validation/Rules/NumberRuleTest.php b/tests/Unit/Validation/Rules/NumberRuleTest.php index 65cecd35..3cce3631 100644 --- a/tests/Unit/Validation/Rules/NumberRuleTest.php +++ b/tests/Unit/Validation/Rules/NumberRuleTest.php @@ -4,17 +4,17 @@ namespace Tests\LaraStrict\Unit\Validation\Rules; -use Illuminate\Contracts\Validation\Rule; +use Illuminate\Contracts\Validation\ValidationRule; use LaraStrict\Validation\Rules\NumberRule; class NumberRuleTest extends AbstractRuleTest { - public function createRule(): Rule + public function createRule(): ValidationRule { return new NumberRule(); } - protected function testData(): array + protected function data(): array { return [ new RuleExpectation('test', false), diff --git a/tests/Unit/Validation/Rules/RemoteUrlRuleTest.php b/tests/Unit/Validation/Rules/RemoteUrlRuleTest.php index d3e731ed..6722840e 100644 --- a/tests/Unit/Validation/Rules/RemoteUrlRuleTest.php +++ b/tests/Unit/Validation/Rules/RemoteUrlRuleTest.php @@ -4,61 +4,47 @@ namespace Tests\LaraStrict\Unit\Validation\Rules; +use Illuminate\Contracts\Validation\ValidationRule; use LaraStrict\Validation\Rules\RemoteUrlRule; -use PHPUnit\Framework\TestCase; -class RemoteUrlRuleTest extends TestCase +final class RemoteUrlRuleTest extends AbstractRuleTest { - private RemoteUrlRule $rule; - - protected function setUp(): void - { - parent::setUp(); - - $this->rule = new RemoteUrlRule(); - } - - public function testPassesIPButOnlyPublicNotPrivate(): void + public function createRule(): ValidationRule { - $matrix = [ - ['https://194.145.181.176', true], - ['https://194.145.181.176/logo.png', true], - ['http://0.0.0.0', false], - ['https://0.0.0.0', false], - ['http://0.0.0.0', false], - ['http://127.0.0.1', false], - ['http://192.168.0.1', false], - ]; - $this->assertMatrix($matrix); + return new RemoteUrlRule(); } - public function testDoamains(): void + protected function data(): array { - $matrix = [ - ['git://test', false], - ['https://azzurro.cz', true], - ['https://www.azzurro.cz', true], - ['https://beta-xx.azzurro.cz', true], - ['https://beta-s.beta.azzurro.cz', true], - ['https://beta-é.beta.azzurro.cz', false], - ['https://redtag.studio', true], - ['https://google.de', true], - ['http://azzurro.cz/logo.test', true], - ['http://google.com/image.png', true], - ['http://localhost', false], - ['https://localhost', false], - ['ftp://test', false], - ['https://redis', false], - ['https://mariadb', false], + return [ + // Domains + new RuleExpectation('git://test', false), + new RuleExpectation('https://azzurro.cz', true), + new RuleExpectation('https://www.azzurro.cz', true), + new RuleExpectation('https://beta-xx.azzurro.cz', true), + new RuleExpectation('https://beta-s.beta.azzurro.cz', true), + new RuleExpectation('https://beta-é.beta.azzurro.cz', false), + new RuleExpectation('https://redtag.studio', true), + new RuleExpectation('https://google.de', true), + new RuleExpectation('http://azzurro.cz/logo.test', true), + new RuleExpectation('http://google.com/image.png', true), + new RuleExpectation('http://localhost', false), + new RuleExpectation('https://localhost', false), + new RuleExpectation('ftp://test', false), + new RuleExpectation('https://redis', false), + new RuleExpectation('https://mariadb', false), + // Ips + new RuleExpectation('https://194.145.181.176', true), + new RuleExpectation('https://194.145.181.176/logo.png', true), + new RuleExpectation('http://0.0.0.0', false), + new RuleExpectation('https://0.0.0.0', false), + new RuleExpectation('http://127.0.0.1', false), + new RuleExpectation('http://192.168.0.1', false), ]; - $this->assertMatrix($matrix); } - protected function assertMatrix(array $matrix): void + protected function getExpectedMessage(): string { - foreach ($matrix as $values) { - [$ip, $check] = $values; - $this->assertEquals($check, $this->rule->passes('test', $ip), 'Not valid url ' . $ip); - } + return 'Given test is not a valid url (public IP or domain on http/s protocol)'; } }