From 675e61fbb89e719ce30c6c2a982eff5d363d30ae Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Oct 2021 17:20:57 +0200 Subject: [PATCH 01/25] Callback::unwrap can return non-callable [Closes #270] --- src/Utils/Callback.php | 3 +- tests/Utils/Callback.closure.phpt | 65 +++++++++++++++++++------------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index 73d5cecd9..474f806f0 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -161,8 +161,9 @@ public static function isStatic(callable $callable): bool /** * Unwraps closure created by Closure::fromCallable(). + * @return \Closure|array|string */ - public static function unwrap(\Closure $closure): callable + public static function unwrap(\Closure $closure) { $r = new \ReflectionFunction($closure); if (substr($r->name, -1) === '}') { diff --git a/tests/Utils/Callback.closure.phpt b/tests/Utils/Callback.closure.phpt index fb05a74b3..1495b970c 100644 --- a/tests/Utils/Callback.closure.phpt +++ b/tests/Utils/Callback.closure.phpt @@ -45,15 +45,15 @@ class Test } - public function __call($nm, $args) + public function createPrivateClosure(): Closure { - return __METHOD__ . " $nm $args[0]"; + return Closure::fromCallable([$this, 'privateFun']); } - public static function __callStatic($nm, $args) + public static function createPrivateStaticClosure(): Closure { - return __METHOD__ . " $nm $args[0]"; + return Closure::fromCallable([self::class, 'privateStatic']); } @@ -64,6 +64,21 @@ class Test } } + +class TestDynamic +{ + public function __call($nm, $args) + { + return __METHOD__ . " $nm $args[0]"; + } + + + public static function __callStatic($nm, $args) + { + return __METHOD__ . " $nm $args[0]"; + } +} + class TestChild extends Test { } @@ -134,15 +149,16 @@ test('object methods', function () { Assert::same('Test::publicFun*', Closure::fromCallable([$test, 'publicFun'])->__invoke('*')); - Assert::same([$test, 'privateFun'], Callback::unwrap(Closure::fromCallable([$test, 'privateFun']))); + Assert::same([$test, 'privateFun'], Callback::unwrap($test->createPrivateClosure())); Assert::same('Test::privateFun', Callback::toString([$test, 'privateFun'])); - Assert::same('{closure Test::privateFun}', Callback::toString(Closure::fromCallable([$test, 'privateFun']))); + Assert::same('{closure Test::privateFun}', Callback::toString($test->createPrivateClosure())); Assert::same('Test::privateFun', getName(Callback::toReflection([$test, 'privateFun']))); - Assert::same('Test::privateFun', getName(Callback::toReflection(Closure::fromCallable([$test, 'privateFun'])))); + Assert::same('Test::privateFun', getName(Callback::toReflection($test->createPrivateClosure()))); + Assert::same('Test::privateFun', getName(Callback::toReflection((new TestChild)->createPrivateClosure()))); - Assert::same('Test::__call privateFun *', Closure::fromCallable([$test, 'privateFun'])->__invoke('*')); + Assert::same('Test::privateFun*', $test->createPrivateClosure()->__invoke('*')); Assert::same('Test::ref', Closure::fromCallable([$test, 'ref'])(...[&$res])); Assert::same('Test::ref', $res); @@ -168,35 +184,36 @@ test('static methods', function () { Assert::same('Test::publicStatic*', Closure::fromCallable([$test, 'publicStatic'])->__invoke('*')); - Assert::same(['Test', 'privateStatic'], Callback::unwrap(Closure::fromCallable('Test::privateStatic'))); + Assert::same(['Test', 'privateStatic'], Callback::unwrap(Test::createPrivateStaticClosure())); Assert::same('Test::privateStatic', Callback::toString('Test::privateStatic')); - Assert::same('{closure Test::privateStatic}', Callback::toString(Closure::fromCallable('Test::privateStatic'))); + Assert::same('{closure Test::privateStatic}', Callback::toString(Test::createPrivateStaticClosure())); Assert::same('Test::privateStatic', getName(Callback::toReflection('Test::privateStatic'))); - Assert::same('Test::privateStatic', getName(Callback::toReflection(Closure::fromCallable('Test::privateStatic')))); + Assert::same('Test::privateStatic', getName(Callback::toReflection(Test::createPrivateStaticClosure()))); + Assert::same('Test::privateStatic', getName(Callback::toReflection(TestChild::createPrivateStaticClosure()))); - Assert::same('Test::__callStatic privateStatic *', Closure::fromCallable('Test::privateStatic')->__invoke('*')); + Assert::same('Test::privateStatic*', Test::createPrivateStaticClosure()->__invoke('*')); }); test('magic methods', function () { - $test = new Test; + $test = new TestDynamic; Assert::same([$test, 'magic'], Callback::unwrap(Closure::fromCallable([$test, 'magic']))); - Assert::same('Test::magic', Callback::toString([$test, 'magic'])); - Assert::same('{closure Test::magic}', Callback::toString(Closure::fromCallable([$test, 'magic']))); - Assert::same('Test::__call magic *', Closure::fromCallable([$test, 'magic'])->__invoke('*')); + Assert::same('TestDynamic::magic', Callback::toString([$test, 'magic'])); + Assert::same('{closure TestDynamic::magic}', Callback::toString(Closure::fromCallable([$test, 'magic']))); + Assert::same('TestDynamic::__call magic *', Closure::fromCallable([$test, 'magic'])->__invoke('*')); - Assert::same(['Test', 'magic'], Callback::unwrap(Closure::fromCallable('Test::magic'))); - Assert::same('Test::magic', Callback::toString('Test::magic')); - Assert::same('{closure Test::magic}', Callback::toString(Closure::fromCallable('Test::magic'))); - Assert::same('Test::__callStatic magic *', Closure::fromCallable('Test::magic')->__invoke('*')); + Assert::same(['TestDynamic', 'magic'], Callback::unwrap(Closure::fromCallable('TestDynamic::magic'))); + Assert::same('TestDynamic::magic', Callback::toString('TestDynamic::magic')); + Assert::same('{closure TestDynamic::magic}', Callback::toString(Closure::fromCallable('TestDynamic::magic'))); + Assert::same('TestDynamic::__callStatic magic *', Closure::fromCallable('TestDynamic::magic')->__invoke('*')); Assert::exception(function () { - Callback::toReflection([new Test, 'magic']); - }, ReflectionException::class, 'Method Test::magic() does not exist'); + Callback::toReflection([new TestDynamic, 'magic']); + }, ReflectionException::class, 'Method TestDynamic::magic() does not exist'); Assert::exception(function () { - Callback::toReflection(Closure::fromCallable([new Test, 'magic'])); - }, ReflectionException::class, 'Method Test::magic() does not exist'); + Callback::toReflection(Closure::fromCallable([new TestDynamic, 'magic'])); + }, ReflectionException::class, 'Method TestDynamic::magic() does not exist'); }); From 396483be3d5a076bd1f9ca37b1ada0189129dc4d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Oct 2021 15:49:52 +0200 Subject: [PATCH 02/25] Strings: improved things around flags --- .phpstorm.meta.php | 6 +++--- src/Utils/Strings.php | 10 +++++----- tests/Utils/Strings.matchAll().phpt | 20 +++++++++++++------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index b6594ae55..7810b4cc1 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -14,6 +14,6 @@ expectedArguments(\Nette\Utils\Image::calculateSize(), 4, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT); expectedArguments(\Nette\Utils\Json::encode(), 1, \Nette\Utils\Json::PRETTY); expectedArguments(\Nette\Utils\Json::decode(), 1, \Nette\Utils\Json::FORCE_ARRAY); -expectedArguments(\Nette\Utils\Strings::split(), 2, \PREG_SPLIT_NO_EMPTY); -expectedArguments(\Nette\Utils\Strings::match(), 2, \PREG_OFFSET_CAPTURE); -expectedArguments(\Nette\Utils\Strings::matchAll(), 2, \PREG_OFFSET_CAPTURE, \PREG_SET_ORDER); +expectedArguments(\Nette\Utils\Strings::split(), 2, \PREG_SPLIT_NO_EMPTY | \PREG_OFFSET_CAPTURE); +expectedArguments(\Nette\Utils\Strings::match(), 2, \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL); +expectedArguments(\Nette\Utils\Strings::matchAll(), 2, \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL | \PREG_PATTERN_ORDER); diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index e5e626b43..77553a56f 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -467,8 +467,8 @@ private static function pos(string $haystack, string $needle, int $nth = 1): ?in /** - * Splits a string into array by the regular expression. - * Argument $flag takes same arguments as preg_split(), but PREG_SPLIT_DELIM_CAPTURE is set by default. + * Splits a string into array by the regular expression. Parenthesized expression in the delimiter are captured. + * Parameter $flags can be any combination of PREG_SPLIT_NO_EMPTY and PREG_OFFSET_CAPTURE flags. */ public static function split(string $subject, string $pattern, int $flags = 0): array { @@ -478,7 +478,7 @@ public static function split(string $subject, string $pattern, int $flags = 0): /** * Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern. - * Argument $flag takes same arguments as function preg_match(). + * Parameter $flags can be any combination of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL flags. */ public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array { @@ -492,8 +492,8 @@ public static function match(string $subject, string $pattern, int $flags = 0, i /** - * Finds all occurrences matching regular expression pattern and returns a two-dimensional array. - * Argument $flag takes same arguments as function preg_match_all(), but PREG_SET_ORDER is set by default. + * Finds all occurrences matching regular expression pattern and returns a two-dimensional array. Result is array of matches (ie uses by default PREG_SET_ORDER). + * Parameter $flags can be any combination of PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL and PREG_PATTERN_ORDER flags. */ public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array { diff --git a/tests/Utils/Strings.matchAll().phpt b/tests/Utils/Strings.matchAll().phpt index fd461cf1b..384bbc18a 100644 --- a/tests/Utils/Strings.matchAll().phpt +++ b/tests/Utils/Strings.matchAll().phpt @@ -26,13 +26,19 @@ Assert::same([ ], Strings::matchAll('hello world!', '#[e-l]+#')); Assert::same([ - [ - ['hell', 0], - ], - [ - ['l', 9], - ], -], Strings::matchAll('hello world!', '#[e-l]+#', PREG_OFFSET_CAPTURE)); + [['lu', 2], ['l', 2], ['u', 3]], + [['ou', 6], ['o', 6], ['u', 7]], + [['k', 10], ['k', 10], ['', 11]], + [['k', 14], ['k', 14], ['', 15]], +], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', PREG_OFFSET_CAPTURE)); + +Assert::same([ + [['lu', 2], ['ou', 6], ['k', 10], ['k', 14]], + [['l', 2], ['o', 6], ['k', 10], ['k', 14]], + [['u', 3], ['u', 7], ['', 11], ['', 15]], +], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER)); + +Assert::same([['l'], ['k'], ['k']], Strings::matchAll('žluťoučký kůň', '#[e-l]+#u', 0, 2)); Assert::same([['ll', 'l']], Strings::matchAll('hello world!', '#[e-l]+#', PREG_PATTERN_ORDER, 2)); From c5d2b4eb3fac05d9143bd14849b3a5bce9d24c06 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Oct 2021 17:30:49 +0200 Subject: [PATCH 03/25] fixed test --- tests/Utils/Image.drawing.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Utils/Image.drawing.phpt b/tests/Utils/Image.drawing.phpt index 47117ff33..31978da89 100644 --- a/tests/Utils/Image.drawing.phpt +++ b/tests/Utils/Image.drawing.phpt @@ -36,7 +36,7 @@ $image->filledEllipse(187, 125, $radius, $radius, Image::rgb(0, 0, 255, 75)); $image->copyResampled($image, 200, 200, 0, 0, 80, 80, $size, $size); -$file = defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID >= 80100 +$file = defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID >= 70424 ? '/expected/Image.drawing.1b.gd2' : '/expected/Image.drawing.1.gd2'; Assert::same(file_get_contents(__DIR__ . $file), toGd2($image)); From 0c29b5f62d79ac7562b3559577d43ec9be8de7dc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 15:30:47 +0100 Subject: [PATCH 04/25] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fa164d856..cbf355074 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } } } From ebe1de2888badb15a854ac53ced1d6ab3f7f1d56 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 17:40:17 +0100 Subject: [PATCH 05/25] requires PHP 8.0 --- .github/workflows/coding-style.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 4 ++-- composer.json | 2 +- readme.md | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 003ba9265..16e97d13c 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b0692d716..f985b0526 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0c574b8c9..4470d3019 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - php: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['8.0', '8.1'] fail-fast: false @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 extensions: iconv, json, mbstring, xml, gd, intl, tokenizer coverage: none diff --git a/composer.json b/composer.json index cbf355074..12dccc022 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.2" + "php": ">=8.0 <8.2" }, "require-dev": { "nette/tester": "~2.0", diff --git a/readme.md b/readme.md index 81ed0e919..2508385a3 100644 --- a/readme.md +++ b/readme.md @@ -39,6 +39,7 @@ The recommended way to install is via Composer: composer require nette/utils ``` +- Nette Utils 4.0 is compatible with PHP 8.0 to 8.1 - Nette Utils 3.2 is compatible with PHP 7.2 to 8.1 - Nette Utils 3.1 is compatible with PHP 7.1 to 8.0 - Nette Utils 3.0 is compatible with PHP 7.1 to 8.0 From 501a7f33a2ba8a8edf931e85603d09fd24b6b61a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 16:43:25 +0100 Subject: [PATCH 06/25] composer: updated dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 12dccc022..8548e9731 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,8 @@ "php": ">=8.0 <8.2" }, "require-dev": { - "nette/tester": "~2.0", - "tracy/tracy": "^2.3", + "nette/tester": "^2.4", + "tracy/tracy": "^2.8", "phpstan/phpstan": "^0.12" }, "conflict": { From 7bb036bc7f2c9ebbac1963f3cdf81f4657d4dd39 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 12:47:36 +0200 Subject: [PATCH 07/25] removed support for PHP 7 --- src/Utils/Callback.php | 2 - src/Utils/Html.php | 10 +- src/Utils/Image.php | 10 +- src/Utils/Reflection.php | 6 +- src/Utils/Type.php | 4 +- .../Utils/Reflection.getParameterType.80.phpt | 70 --------- tests/Utils/Reflection.getParameterType.phpt | 28 +++- .../Utils/Reflection.getPropertyType.74.phpt | 46 ------ .../Utils/Reflection.getPropertyType.80.phpt | 62 -------- tests/Utils/Reflection.getPropertyType.phpt | 40 ++++- tests/Utils/Reflection.getReturnType.80.phpt | 137 ------------------ tests/Utils/Reflection.getReturnType.phpt | 40 +++++ tests/Utils/SmartObject.events.74.phpt | 26 ---- tests/Utils/SmartObject.events.phpt | 7 + tests/Utils/SmartObject.undeclaredMethod.phpt | 18 +-- 15 files changed, 122 insertions(+), 384 deletions(-) delete mode 100644 tests/Utils/Reflection.getParameterType.80.phpt delete mode 100644 tests/Utils/Reflection.getPropertyType.74.phpt delete mode 100644 tests/Utils/Reflection.getPropertyType.80.phpt delete mode 100644 tests/Utils/Reflection.getReturnType.80.phpt delete mode 100644 tests/Utils/SmartObject.events.74.phpt diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index 474f806f0..b6c003026 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -117,8 +117,6 @@ public static function toString($callable): string if ($callable instanceof \Closure) { $inner = self::unwrap($callable); return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}'); - } elseif (is_string($callable) && $callable[0] === "\0") { - return '{lambda}'; } else { is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual); return $textual; diff --git a/src/Utils/Html.php b/src/Utils/Html.php index cf6289336..c0a82e5c9 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -763,15 +763,7 @@ final public function render(int $indent = null): string final public function __toString(): string { - try { - return $this->render(); - } catch (\Throwable $e) { - if (PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return $this->render(); } diff --git a/src/Utils/Image.php b/src/Utils/Image.php index 1c305714a..08aabbc0a 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -591,15 +591,7 @@ public function toString(int $type = self::JPEG, int $quality = null): string */ public function __toString(): string { - try { - return $this->toString(); - } catch (\Throwable $e) { - if (func_num_args() || PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); - return ''; - } + return $this->toString(); } diff --git a/src/Utils/Reflection.php b/src/Utils/Reflection.php index 818643f11..9215e8520 100644 --- a/src/Utils/Reflection.php +++ b/src/Utils/Reflection.php @@ -98,7 +98,7 @@ public static function getParameterTypes(\ReflectionParameter $param): array */ public static function getPropertyType(\ReflectionProperty $prop): ?string { - return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null); + return self::getType($prop, $prop->getType()); } @@ -319,9 +319,7 @@ private static function parseUseStatements(string $code, string $forClass = null $namespace = $class = $classLevel = $level = null; $res = $uses = []; - $nameTokens = PHP_VERSION_ID < 80000 - ? [T_STRING, T_NS_SEPARATOR] - : [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; + $nameTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; while ($token = current($tokens)) { next($tokens); diff --git a/src/Utils/Type.php b/src/Utils/Type.php index 257cc2968..58faa363f 100644 --- a/src/Utils/Type.php +++ b/src/Utils/Type.php @@ -34,9 +34,7 @@ final class Type */ public static function fromReflection($reflection): ?self { - if ($reflection instanceof \ReflectionProperty && PHP_VERSION_ID < 70400) { - return null; - } elseif ($reflection instanceof \ReflectionMethod) { + if ($reflection instanceof \ReflectionMethod) { $type = $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null); } else { $type = $reflection instanceof \ReflectionFunctionAbstract diff --git a/tests/Utils/Reflection.getParameterType.80.phpt b/tests/Utils/Reflection.getParameterType.80.phpt deleted file mode 100644 index 57261ac51..000000000 --- a/tests/Utils/Reflection.getParameterType.80.phpt +++ /dev/null @@ -1,70 +0,0 @@ -getParameters(); - -Assert::same('Undeclared', Reflection::getParameterType($params[0])); -Assert::same('Test\B', Reflection::getParameterType($params[1])); -Assert::same('array', Reflection::getParameterType($params[2])); -Assert::same('callable', Reflection::getParameterType($params[3])); -Assert::same('A', Reflection::getParameterType($params[4])); -Assert::null(Reflection::getParameterType($params[5])); -Assert::same('Test\B', Reflection::getParameterType($params[6])); -Assert::same(['Test\B', 'null'], Reflection::getParameterTypes($params[6])); -Assert::same('mixed', Reflection::getParameterType($params[7])); -Assert::same(['mixed'], Reflection::getParameterTypes($params[7])); -Assert::same(['A', 'array'], Reflection::getParameterTypes($params[8])); -Assert::same(['A', 'array', 'null'], Reflection::getParameterTypes($params[9])); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[8]); -}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[9]); -}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union or intersection type.'); - - -$method = new ReflectionMethod('AExt', 'methodExt'); -$params = $method->getParameters(); - -Assert::same('A', Reflection::getParameterType($params[0])); diff --git a/tests/Utils/Reflection.getParameterType.phpt b/tests/Utils/Reflection.getParameterType.phpt index 869266982..9ffa52501 100644 --- a/tests/Utils/Reflection.getParameterType.phpt +++ b/tests/Utils/Reflection.getParameterType.phpt @@ -16,8 +16,18 @@ require __DIR__ . '/../bootstrap.php'; class A { - public function method(Undeclared $undeclared, B $b, array $array, callable $callable, self $self, $none, ?B $nullable) - { + public function method( + Undeclared $undeclared, + B $b, + array $array, + callable $callable, + self $self, + $none, + ?B $nullable, + mixed $mixed, + array|self $union, + array|self|null $nullableUnion, + ) { } } @@ -38,6 +48,20 @@ Assert::same('callable', Reflection::getParameterType($params[3])); Assert::same('A', Reflection::getParameterType($params[4])); Assert::null(Reflection::getParameterType($params[5])); Assert::same('Test\B', Reflection::getParameterType($params[6])); +Assert::same(['Test\B', 'null'], Reflection::getParameterTypes($params[6])); +Assert::same('mixed', Reflection::getParameterType($params[7])); +Assert::same(['mixed'], Reflection::getParameterTypes($params[7])); +Assert::same(['A', 'array'], Reflection::getParameterTypes($params[8])); +Assert::same(['A', 'array', 'null'], Reflection::getParameterTypes($params[9])); + +Assert::exception(function () use ($params) { + Reflection::getParameterType($params[8]); +}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union or intersection type.'); + +Assert::exception(function () use ($params) { + Reflection::getParameterType($params[9]); +}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union or intersection type.'); + $method = new ReflectionMethod('AExt', 'methodExt'); $params = $method->getParameters(); diff --git a/tests/Utils/Reflection.getPropertyType.74.phpt b/tests/Utils/Reflection.getPropertyType.74.phpt deleted file mode 100644 index b822be995..000000000 --- a/tests/Utils/Reflection.getPropertyType.74.phpt +++ /dev/null @@ -1,46 +0,0 @@ -getProperties(); - -Assert::same('Undeclared', Reflection::getPropertyType($props[0])); -Assert::same('Test\B', Reflection::getPropertyType($props[1])); -Assert::same('array', Reflection::getPropertyType($props[2])); -Assert::same('A', Reflection::getPropertyType($props[3])); -Assert::null(Reflection::getPropertyType($props[4])); -Assert::same('Test\B', Reflection::getPropertyType($props[5])); - -$class = new ReflectionClass('AExt'); -$props = $class->getProperties(); - -Assert::same('A', Reflection::getPropertyType($props[0])); diff --git a/tests/Utils/Reflection.getPropertyType.80.phpt b/tests/Utils/Reflection.getPropertyType.80.phpt deleted file mode 100644 index 00378ce66..000000000 --- a/tests/Utils/Reflection.getPropertyType.80.phpt +++ /dev/null @@ -1,62 +0,0 @@ -getProperties(); - -Assert::same('Undeclared', Reflection::getPropertyType($props[0])); -Assert::same('Test\B', Reflection::getPropertyType($props[1])); -Assert::same('array', Reflection::getPropertyType($props[2])); -Assert::same('A', Reflection::getPropertyType($props[3])); -Assert::null(Reflection::getPropertyType($props[4])); -Assert::same('Test\B', Reflection::getPropertyType($props[5])); -Assert::same(['Test\B', 'null'], Reflection::getPropertyTypes($props[5])); -Assert::same('mixed', Reflection::getPropertyType($props[6])); -Assert::same(['mixed'], Reflection::getPropertyTypes($props[6])); -Assert::same(['A', 'array'], Reflection::getPropertyTypes($props[7])); -Assert::same(['A', 'array', 'null'], Reflection::getPropertyTypes($props[8])); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[7]); -}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[8]); -}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union or intersection type.'); - -$class = new ReflectionClass('AExt'); -$props = $class->getProperties(); - -Assert::same('A', Reflection::getPropertyType($props[0])); diff --git a/tests/Utils/Reflection.getPropertyType.phpt b/tests/Utils/Reflection.getPropertyType.phpt index 8b15eb85e..e79e0d107 100644 --- a/tests/Utils/Reflection.getPropertyType.phpt +++ b/tests/Utils/Reflection.getPropertyType.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\Utils\Reflection; +use Test\B; // for testing purposes use Tester\Assert; @@ -15,11 +16,46 @@ require __DIR__ . '/../bootstrap.php'; class A { + public Undeclared $undeclared; + public B $b; + public array $array; + public self $self; public $none; + public ?B $nullable; + public mixed $mixed; + public array|self $union; + public array|self|null $nullableUnion; +} + +class AExt extends A +{ + public parent $parent; } $class = new ReflectionClass('A'); $props = $class->getProperties(); -Assert::null(Reflection::getPropertyType($props[0])); -Assert::same([], Reflection::getPropertyTypes($props[0])); +Assert::same('Undeclared', Reflection::getPropertyType($props[0])); +Assert::same('Test\B', Reflection::getPropertyType($props[1])); +Assert::same('array', Reflection::getPropertyType($props[2])); +Assert::same('A', Reflection::getPropertyType($props[3])); +Assert::null(Reflection::getPropertyType($props[4])); +Assert::same('Test\B', Reflection::getPropertyType($props[5])); +Assert::same(['Test\B', 'null'], Reflection::getPropertyTypes($props[5])); +Assert::same('mixed', Reflection::getPropertyType($props[6])); +Assert::same(['mixed'], Reflection::getPropertyTypes($props[6])); +Assert::same(['A', 'array'], Reflection::getPropertyTypes($props[7])); +Assert::same(['A', 'array', 'null'], Reflection::getPropertyTypes($props[8])); + +Assert::exception(function () use ($props) { + Reflection::getPropertyType($props[7]); +}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union or intersection type.'); + +Assert::exception(function () use ($props) { + Reflection::getPropertyType($props[8]); +}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union or intersection type.'); + +$class = new ReflectionClass('AExt'); +$props = $class->getProperties(); + +Assert::same('A', Reflection::getPropertyType($props[0])); diff --git a/tests/Utils/Reflection.getReturnType.80.phpt b/tests/Utils/Reflection.getReturnType.80.phpt deleted file mode 100644 index 96c251ac9..000000000 --- a/tests/Utils/Reflection.getReturnType.80.phpt +++ /dev/null @@ -1,137 +0,0 @@ -onEvent(); -}); diff --git a/tests/Utils/SmartObject.events.phpt b/tests/Utils/SmartObject.events.phpt index 4d3899627..d7952fadf 100644 --- a/tests/Utils/SmartObject.events.phpt +++ b/tests/Utils/SmartObject.events.phpt @@ -19,6 +19,8 @@ class TestClass public static $onPublicStatic; + public array $onEvent; + protected $onProtected; private $onPrivate; @@ -81,3 +83,8 @@ Assert::exception(function () use ($obj) { $obj->onPublic = 'string'; $obj->onPublic(); }, Nette\UnexpectedValueException::class, 'Property TestClass::$onPublic must be iterable or null, string given.'); + +Assert::noError(function () { + $obj = new TestClass; + $obj->onEvent(); +}); diff --git a/tests/Utils/SmartObject.undeclaredMethod.phpt b/tests/Utils/SmartObject.undeclaredMethod.phpt index 60d255cb8..c11e52cd1 100644 --- a/tests/Utils/SmartObject.undeclaredMethod.phpt +++ b/tests/Utils/SmartObject.undeclaredMethod.phpt @@ -120,18 +120,12 @@ Assert::exception(function () { $obj::callMissingParentStatic(); }, Nette\MemberAccessException::class, 'Call to undefined static method InterClass::callMissingParentStatic().'); -Assert::exception( - function () { - $obj = new ChildClass; - $obj->callPrivateParent(); - }, - Nette\MemberAccessException::class, - PHP_VERSION_ID < 70400 - ? 'Call to private method InterClass::callPrivateParent() from scope ChildClass.' - : (PHP_VERSION_ID < 80100 - ? 'Call to undefined static method InterClass::callPrivateParent().' // differs from native error message - : 'Call to undefined method InterClass::callPrivateParent().') -); +Assert::exception(function () { + $obj = new ChildClass; + $obj->callPrivateParent(); +}, Nette\MemberAccessException::class, PHP_VERSION_ID < 80100 + ? 'Call to undefined static method InterClass::callPrivateParent().' // differs from native error message + : 'Call to undefined method InterClass::callPrivateParent().'); Assert::exception(function () { $obj = new ChildClass; From 20874339bac52b1be5d4a7015226dbabc567d699 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Mar 2021 15:50:58 +0100 Subject: [PATCH 08/25] removed deprecated stuff --- src/Utils/Callback.php | 41 ---------------- src/Utils/ObjectMixin.php | 41 ---------------- src/Utils/Reflection.php | 30 ------------ tests/Utils/Callback.invoke.phpt | 49 ------------------- .../Utils/Reflection.getParameterType.81.phpt | 4 -- tests/Utils/Reflection.getParameterType.phpt | 4 -- .../Utils/Reflection.getPropertyType.81.phpt | 4 -- tests/Utils/Reflection.getPropertyType.phpt | 4 -- tests/Utils/Reflection.getReturnType.81.phpt | 7 --- tests/Utils/Reflection.getReturnType.phpt | 6 --- 10 files changed, 190 deletions(-) delete mode 100644 src/Utils/ObjectMixin.php delete mode 100644 tests/Utils/Callback.invoke.phpt diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index b6c003026..56a55393c 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -20,47 +20,6 @@ final class Callback { use Nette\StaticClass; - /** - * @param string|object|callable $callable class, object, callable - * @deprecated use Closure::fromCallable() - */ - public static function closure($callable, string $method = null): \Closure - { - trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED); - try { - return \Closure::fromCallable($method === null ? $callable : [$callable, $method]); - } catch (\TypeError $e) { - throw new Nette\InvalidArgumentException($e->getMessage()); - } - } - - - /** - * Invokes callback. - * @return mixed - * @deprecated - */ - public static function invoke($callable, ...$args) - { - trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED); - self::check($callable); - return $callable(...$args); - } - - - /** - * Invokes callback with an array of parameters. - * @return mixed - * @deprecated - */ - public static function invokeArgs($callable, array $args = []) - { - trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED); - self::check($callable); - return $callable(...$args); - } - - /** * Invokes internal PHP function with own error handler. * @return mixed diff --git a/src/Utils/ObjectMixin.php b/src/Utils/ObjectMixin.php deleted file mode 100644 index 33249509b..000000000 --- a/src/Utils/ObjectMixin.php +++ /dev/null @@ -1,41 +0,0 @@ -getNames() : []; - } - - /** * Returns the type of given parameter and normalizes `self` and `parent` to the actual class names. * If the parameter does not have a type, it returns null. @@ -81,16 +71,6 @@ public static function getParameterType(\ReflectionParameter $param): ?string } - /** - * @deprecated - */ - public static function getParameterTypes(\ReflectionParameter $param): array - { - $type = Type::fromReflection($param); - return $type ? $type->getNames() : []; - } - - /** * Returns the type of given property and normalizes `self` and `parent` to the actual class names. * If the property does not have a type, it returns null. @@ -102,16 +82,6 @@ public static function getPropertyType(\ReflectionProperty $prop): ?string } - /** - * @deprecated - */ - public static function getPropertyTypes(\ReflectionProperty $prop): array - { - $type = Type::fromReflection($prop); - return $type ? $type->getNames() : []; - } - - /** * @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection */ diff --git a/tests/Utils/Callback.invoke.phpt b/tests/Utils/Callback.invoke.phpt deleted file mode 100644 index 9d072f29f..000000000 --- a/tests/Utils/Callback.invoke.phpt +++ /dev/null @@ -1,49 +0,0 @@ - Date: Sun, 19 Sep 2021 19:03:08 +0200 Subject: [PATCH 09/25] coding style --- ecs.php | 2 +- src/Iterators/CachingIterator.php | 2 +- src/Utils/Callback.php | 2 +- src/Utils/DateTime.php | 6 +-- src/Utils/Html.php | 2 +- src/Utils/Image.php | 12 ++--- src/Utils/ObjectHelpers.php | 18 ++++---- src/Utils/Random.php | 9 ++-- src/Utils/Strings.php | 2 +- src/Utils/Type.php | 30 +++++------- src/Utils/Validators.php | 46 +++++++++---------- tests/Iterators/Mapper.phpt | 4 +- tests/Utils/Arrays.associate().phpt | 38 +++++++-------- tests/Utils/Arrays.every().phpt | 14 +++--- tests/Utils/Arrays.invoke.phpt | 4 +- tests/Utils/Arrays.invokeMethod.phpt | 4 +- tests/Utils/Arrays.map().phpt | 8 ++-- tests/Utils/Arrays.normalize.phpt | 4 +- tests/Utils/Arrays.some().phpt | 14 +++--- tests/Utils/Callback.invokeSafe.phpt | 4 +- tests/Utils/DateTime.JSON.phpt | 2 +- tests/Utils/DateTime.from.phpt | 12 ++--- tests/Utils/DateTime.modifyClone.phpt | 2 +- tests/Utils/Json.decode().phpt | 2 +- tests/Utils/Reflection.expandClassName.phpt | 8 ++-- .../Utils/Reflection.groupUseStatements.phpt | 2 +- .../Reflection.nonClassUseStatements.phpt | 2 +- tests/Utils/Strings.replace().phpt | 2 +- .../fixtures.reflection/defaultValue.php | 2 +- 29 files changed, 125 insertions(+), 134 deletions(-) diff --git a/ecs.php b/ecs.php index d655ffcc4..4dd357154 100644 --- a/ecs.php +++ b/ecs.php @@ -9,7 +9,7 @@ return function (Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator): void { - $containerConfigurator->import(PRESET_DIR . '/php71.php'); + $containerConfigurator->import(PRESET_DIR . '/php80.php'); $parameters = $containerConfigurator->parameters(); diff --git a/src/Iterators/CachingIterator.php b/src/Iterators/CachingIterator.php index e2e5e4c1a..cb99645b3 100644 --- a/src/Iterators/CachingIterator.php +++ b/src/Iterators/CachingIterator.php @@ -47,7 +47,7 @@ public function __construct($iterator) } elseif ($iterator instanceof \Traversable) { $iterator = new \IteratorIterator($iterator); } else { - throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? get_class($iterator) : gettype($iterator))); + throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? $iterator::class : gettype($iterator))); } parent::__construct($iterator, 0); diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index 56a55393c..a11cdb3cc 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -60,7 +60,7 @@ public static function check($callable, bool $syntax = false) throw new Nette\InvalidArgumentException( $syntax ? 'Given value is not a callable type.' - : sprintf("Callback '%s' is not callable.", self::toString($callable)) + : sprintf("Callback '%s' is not callable.", self::toString($callable)), ); } return $callable; diff --git a/src/Utils/DateTime.php b/src/Utils/DateTime.php index 320ccc773..661b01c99 100644 --- a/src/Utils/DateTime.php +++ b/src/Utils/DateTime.php @@ -32,10 +32,10 @@ class DateTime extends \DateTime implements \JsonSerializable public const WEEK = 7 * self::DAY; /** average month in seconds */ - public const MONTH = 2629800; + public const MONTH = 2_629_800; /** average year in seconds */ - public const YEAR = 31557600; + public const YEAR = 31_557_600; /** @@ -72,7 +72,7 @@ public static function fromParts( int $day, int $hour = 0, int $minute = 0, - float $second = 0.0 + float $second = 0.0, ) { $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); if ( diff --git a/src/Utils/Html.php b/src/Utils/Html.php index c0a82e5c9..169fc551c 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -844,7 +844,7 @@ final public function attributes(): string . str_replace( ['&', $q, '<'], ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], - $value + $value, ) . (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '') . $q; diff --git a/src/Utils/Image.php b/src/Utils/Image.php index 08aabbc0a..690723daf 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -338,7 +338,7 @@ public function resize($width, $height, int $flags = self::FIT) $newWidth, $newHeight, $this->getWidth(), - $this->getHeight() + $this->getHeight(), ); $this->image = $newImage; } @@ -360,7 +360,7 @@ public static function calculateSize( int $srcHeight, $newWidth, $newHeight, - int $flags = self::FIT + int $flags = self::FIT, ): array { if ($newWidth === null) { } elseif (self::isPercent($newWidth)) { @@ -550,7 +550,7 @@ public function place(self $image, $left = 0, $top = 0, int $opacity = 100) 0, 0, $width, - $height + $height, ); return $this; } @@ -580,7 +580,7 @@ public function save(string $file, int $quality = null, int $type = null): void */ public function toString(int $type = self::JPEG, int $quality = null): string { - return Helpers::capture(function () use ($type, $quality) { + return Helpers::capture(function () use ($type, $quality): void { $this->output($type, $quality); }); } @@ -667,13 +667,13 @@ public function __call(string $name, array $args) $value['red'], $value['green'], $value['blue'], - $value['alpha'] + $value['alpha'], ) ?: imagecolorresolvealpha( $this->image, $value['red'], $value['green'], $value['blue'], - $value['alpha'] + $value['alpha'], ); } } diff --git a/src/Utils/ObjectHelpers.php b/src/Utils/ObjectHelpers.php index c0fda7d8c..8d973c04f 100644 --- a/src/Utils/ObjectHelpers.php +++ b/src/Utils/ObjectHelpers.php @@ -28,8 +28,8 @@ public static function strictGet(string $class, string $name): void { $rc = new \ReflectionClass($class); $hint = self::getSuggestion(array_merge( - array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), - self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), ), $name); throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); } @@ -43,8 +43,8 @@ public static function strictSet(string $class, string $name): void { $rc = new \ReflectionClass($class); $hint = self::getSuggestion(array_merge( - array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), - self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), ), $name); throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); } @@ -76,7 +76,7 @@ public static function strictCall(string $class, string $method, array $addition $hint = self::getSuggestion(array_merge( get_class_methods($class), self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'), - $additionalMethods + $additionalMethods, ), $method); throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); } @@ -107,8 +107,8 @@ public static function strictStaticCall(string $class, string $method): void } else { $hint = self::getSuggestion( - array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }), - $method + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()), + $method, ); throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); } @@ -133,7 +133,7 @@ public static function getMagicProperties(string $class): array '~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', (string) $rc->getDocComment(), $matches, - PREG_SET_ORDER + PREG_SET_ORDER, ); $props = []; @@ -196,7 +196,7 @@ private static function parseFullDoc(\ReflectionClass $rc, string $pattern): arr $traits += $trait->getTraits(); } } while ($rc = $rc->getParentClass()); - return preg_match_all($pattern, implode($doc), $m) ? $m[1] : []; + return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : []; } diff --git a/src/Utils/Random.php b/src/Utils/Random.php index 2403b4d3d..0ac9d152c 100644 --- a/src/Utils/Random.php +++ b/src/Utils/Random.php @@ -25,9 +25,12 @@ final class Random */ public static function generate(int $length = 10, string $charlist = '0-9a-z'): string { - $charlist = count_chars(preg_replace_callback('#.-.#', function (array $m): string { - return implode('', range($m[0][0], $m[0][2])); - }, $charlist), 3); + $charlist = preg_replace_callback( + '#.-.#', + fn(array $m): string => implode('', range($m[0][0], $m[0][2])), + $charlist, + ); + $charlist = count_chars($charlist, mode: 3); $chLen = strlen($charlist); if ($length < 1) { diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index 77553a56f..cf6f52ef9 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -184,7 +184,7 @@ public static function toAscii(string $s): string $s = strtr( $s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", - 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.' + 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.', ); $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); } else { diff --git a/src/Utils/Type.php b/src/Utils/Type.php index 58faa363f..707a1e38c 100644 --- a/src/Utils/Type.php +++ b/src/Utils/Type.php @@ -52,10 +52,10 @@ public static function fromReflection($reflection): ?self } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { return new self( array_map( - function ($t) use ($reflection) { return self::resolve($t->getName(), $reflection); }, - $type->getTypes() + fn($t) => self::resolve($t->getName(), $reflection), + $type->getTypes(), ), - $type instanceof \ReflectionUnionType ? '|' : '&' + $type instanceof \ReflectionUnionType ? '|' : '&', ); } else { @@ -140,7 +140,7 @@ public function getNames(): array */ public function getTypes(): array { - return array_map(function ($name) { return self::fromString($name); }, $this->types); + return array_map(fn($name) => self::fromString($name), $this->types); } @@ -221,27 +221,21 @@ public function allows(string $type): bool $type = self::fromString($type); if ($this->isIntersection()) { - if (!$type->isIntersection()) { - return false; - } - return Arrays::every($this->types, function ($currentType) use ($type) { - $builtin = Reflection::isBuiltinType($currentType); - return Arrays::some($type->types, function ($testedType) use ($currentType, $builtin) { - return $builtin + return $type->isIntersection() + && Arrays::every($this->types, function ($currentType) use ($type) { + $builtin = Reflection::isBuiltinType($currentType); + return Arrays::some($type->types, fn($testedType) => $builtin ? strcasecmp($currentType, $testedType) === 0 - : is_a($testedType, $currentType, true); + : is_a($testedType, $currentType, true)); }); - }); } $method = $type->isIntersection() ? 'some' : 'every'; return Arrays::$method($type->types, function ($testedType) { $builtin = Reflection::isBuiltinType($testedType); - return Arrays::some($this->types, function ($currentType) use ($testedType, $builtin) { - return $builtin - ? strcasecmp($currentType, $testedType) === 0 - : is_a($testedType, $currentType, true); - }); + return Arrays::some($this->types, fn($currentType) => $builtin + ? strcasecmp($currentType, $testedType) === 0 + : is_a($testedType, $currentType, true)); }); } } diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index 60e0eaab3..9b787ad41 100644 --- a/src/Utils/Validators.php +++ b/src/Utils/Validators.php @@ -99,7 +99,7 @@ public static function assert($value, string $expected, string $label = 'variabl if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { $type .= ' ' . var_export($value, true); } elseif (is_object($value)) { - $type .= ' ' . get_class($value); + $type .= ' ' . $value::class; } throw new AssertionException("The $label expects to be $expected, $type given."); } @@ -116,7 +116,7 @@ public static function assertField( array $array, $key, string $expected = null, - string $label = "item '%' in array" + string $label = "item '%' in array", ): void { if (!array_key_exists($key, $array)) { throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.'); @@ -310,14 +310,13 @@ public static function isEmail(string $value): bool $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part $alpha = "a-z\x80-\xFF"; // superset of IDN return (bool) preg_match(<< 'Grudl', ]; -$callback = function ($item, $key) { - return $key . ': ' . $item; -}; +$callback = fn($item, $key) => $key . ': ' . $item; $iterator = new Iterators\Mapper(new \ArrayIterator($arr), $callback); diff --git a/tests/Utils/Arrays.associate().phpt b/tests/Utils/Arrays.associate().phpt index 80e439e8f..d7cde4a53 100644 --- a/tests/Utils/Arrays.associate().phpt +++ b/tests/Utils/Arrays.associate().phpt @@ -28,12 +28,12 @@ Assert::same( 'Mary' => ['name' => 'Mary', 'age' => null], 'Paul' => ['name' => 'Paul', 'age' => 44], ], - Arrays::associate($arr, 'name') + Arrays::associate($arr, 'name'), ); Assert::same( [], - Arrays::associate([], 'name') + Arrays::associate([], 'name'), ); Assert::same( @@ -42,17 +42,17 @@ Assert::same( 'Mary' => ['name' => 'Mary', 'age' => null], 'Paul' => ['name' => 'Paul', 'age' => 44], ], - Arrays::associate($arr, 'name=') + Arrays::associate($arr, 'name='), ); Assert::same( ['John' => 22, 'Mary' => null, 'Paul' => 44], - Arrays::associate($arr, 'name=age') + Arrays::associate($arr, 'name=age'), ); Assert::same(// path as array ['John' => 22, 'Mary' => null, 'Paul' => 44], - Arrays::associate($arr, ['name', '=', 'age']) + Arrays::associate($arr, ['name', '=', 'age']), ); Assert::equal( @@ -70,7 +70,7 @@ Assert::equal( 'age' => 44, ], ], - Arrays::associate($arr, 'name->') + Arrays::associate($arr, 'name->'), ); Assert::equal( @@ -88,7 +88,7 @@ Assert::equal( 'Paul' => ['name' => 'Paul', 'age' => 44], ], ], - Arrays::associate($arr, 'age->name') + Arrays::associate($arr, 'age->name'), ); Assert::equal( @@ -97,12 +97,12 @@ Assert::equal( 'Mary' => ['name' => 'Mary', 'age' => null], 'Paul' => ['name' => 'Paul', 'age' => 44], ], - Arrays::associate($arr, '->name') + Arrays::associate($arr, '->name'), ); Assert::equal( (object) [], - Arrays::associate([], '->name') + Arrays::associate([], '->name'), ); Assert::same( @@ -118,7 +118,7 @@ Assert::same( 44 => ['name' => 'Paul', 'age' => 44], ], ], - Arrays::associate($arr, 'name|age') + Arrays::associate($arr, 'name|age'), ); Assert::same( @@ -127,7 +127,7 @@ Assert::same( 'Mary' => ['name' => 'Mary', 'age' => null], 'Paul' => ['name' => 'Paul', 'age' => 44], ], - Arrays::associate($arr, 'name|') + Arrays::associate($arr, 'name|'), ); Assert::same( @@ -143,7 +143,7 @@ Assert::same( ['name' => 'Paul', 'age' => 44], ], ], - Arrays::associate($arr, 'name[]') + Arrays::associate($arr, 'name[]'), ); Assert::same( @@ -153,12 +153,12 @@ Assert::same( ['Mary' => ['name' => 'Mary', 'age' => null]], ['Paul' => ['name' => 'Paul', 'age' => 44]], ], - Arrays::associate($arr, '[]name') + Arrays::associate($arr, '[]name'), ); Assert::same( ['John', 'John', 'Mary', 'Paul'], - Arrays::associate($arr, '[]=name') + Arrays::associate($arr, '[]=name'), ); Assert::same( @@ -174,12 +174,12 @@ Assert::same( [44 => ['name' => 'Paul', 'age' => 44]], ], ], - Arrays::associate($arr, 'name[]age') + Arrays::associate($arr, 'name[]age'), ); Assert::same( $arr, - Arrays::associate($arr, '[]') + Arrays::associate($arr, '[]'), ); // converts object to array @@ -190,7 +190,7 @@ Assert::same( (object) ['name' => 'John', 'age' => 22], (object) ['name' => 'Mary', 'age' => null], (object) ['name' => 'Paul', 'age' => 44], - ], '[]') + ], '[]'), ); // allowes objects in keys @@ -198,11 +198,11 @@ Assert::equal( ['2014-02-05 00:00:00' => new DateTime('2014-02-05')], Arrays::associate($arr = [ ['date' => new DateTime('2014-02-05')], - ], 'date=date') + ], 'date=date'), ); Assert::equal( (object) ['2014-02-05 00:00:00' => new DateTime('2014-02-05')], Arrays::associate($arr = [ ['date' => new DateTime('2014-02-05')], - ], '->date=date') + ], '->date=date'), ); diff --git a/tests/Utils/Arrays.every().phpt b/tests/Utils/Arrays.every().phpt index eed82ec59..074bf0b1f 100644 --- a/tests/Utils/Arrays.every().phpt +++ b/tests/Utils/Arrays.every().phpt @@ -18,7 +18,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; }, ); Assert::true($res); Assert::same([], $log); @@ -29,7 +29,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::true($res); Assert::same([], $log); @@ -40,7 +40,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; }, ); Assert::false($res); Assert::same([['a', 0, $arr]], $log); @@ -51,7 +51,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::true($res); Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log); @@ -62,7 +62,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; }, ); Assert::false($res); Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log); @@ -73,7 +73,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::true($res); Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); @@ -84,7 +84,7 @@ test('', function () { $log = []; $res = Arrays::every( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::true($res); Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); diff --git a/tests/Utils/Arrays.invoke.phpt b/tests/Utils/Arrays.invoke.phpt index 173106ce4..19d1094f0 100644 --- a/tests/Utils/Arrays.invoke.phpt +++ b/tests/Utils/Arrays.invoke.phpt @@ -30,10 +30,10 @@ $list['key'] = [new Test, 'fn2']; Assert::same( ['Test::fn1 a,b', 'key' => 'Test::fn2 a,b'], - Arrays::invoke($list, 'a', 'b') + Arrays::invoke($list, 'a', 'b'), ); Assert::same( ['Test::fn1 a,b', 'key' => 'Test::fn2 a,b'], - Arrays::invoke(new ArrayIterator($list), 'a', 'b') + Arrays::invoke(new ArrayIterator($list), 'a', 'b'), ); diff --git a/tests/Utils/Arrays.invokeMethod.phpt b/tests/Utils/Arrays.invokeMethod.phpt index 8e1eec51d..8c42d3186 100644 --- a/tests/Utils/Arrays.invokeMethod.phpt +++ b/tests/Utils/Arrays.invokeMethod.phpt @@ -26,10 +26,10 @@ $list = [new Test1, 'key' => new Test2]; Assert::same( ['Test1 a,b', 'key' => 'Test2 a,b'], - Arrays::invokeMethod($list, 'fn', 'a', 'b') + Arrays::invokeMethod($list, 'fn', 'a', 'b'), ); Assert::same( ['Test1 a,b', 'key' => 'Test2 a,b'], - Arrays::invokeMethod(new ArrayIterator($list), 'fn', 'a', 'b') + Arrays::invokeMethod(new ArrayIterator($list), 'fn', 'a', 'b'), ); diff --git a/tests/Utils/Arrays.map().phpt b/tests/Utils/Arrays.map().phpt index 5cdabdedf..2970e9059 100644 --- a/tests/Utils/Arrays.map().phpt +++ b/tests/Utils/Arrays.map().phpt @@ -18,7 +18,7 @@ test('', function () { $log = []; $res = Arrays::map( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::same([], $res); Assert::same([], $log); @@ -29,7 +29,7 @@ test('', function () { $log = []; $res = Arrays::map( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; }, ); Assert::same(['aa', 'bb'], $res); Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log); @@ -40,7 +40,7 @@ test('', function () { $log = []; $res = Arrays::map( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; }, ); Assert::same(['x' => 'aa', 'y' => 'bb'], $res); Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); @@ -51,7 +51,7 @@ test('', function () { $log = []; $res = Arrays::map( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; }, ); Assert::same(['x' => 'aa', 'y' => 'bb'], $res); Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); diff --git a/tests/Utils/Arrays.normalize.phpt b/tests/Utils/Arrays.normalize.phpt index 0ff385a96..44eab09f7 100644 --- a/tests/Utils/Arrays.normalize.phpt +++ b/tests/Utils/Arrays.normalize.phpt @@ -25,7 +25,7 @@ Assert::same( 'a' => 'second', 'd' => ['third'], 7 => 'fourth', - ]) + ]), ); @@ -37,5 +37,5 @@ Assert::same( Arrays::normalize([ 1 => 'first', '' => 'second', - ], true) + ], true), ); diff --git a/tests/Utils/Arrays.some().phpt b/tests/Utils/Arrays.some().phpt index 3cbfadb3a..7a34bc26e 100644 --- a/tests/Utils/Arrays.some().phpt +++ b/tests/Utils/Arrays.some().phpt @@ -18,7 +18,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; }, ); Assert::false($res); Assert::same([], $log); @@ -29,7 +29,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::false($res); Assert::same([], $log); @@ -40,7 +40,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return false; }, ); Assert::false($res); Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log); @@ -51,7 +51,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; }, ); Assert::true($res); Assert::same([['a', 0, $arr]], $log); @@ -62,7 +62,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; }, ); Assert::true($res); Assert::same([['a', 0, $arr]], $log); @@ -73,7 +73,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; }, ); Assert::true($res); Assert::same([['a', 'x', $arr]], $log); @@ -84,7 +84,7 @@ test('', function () { $log = []; $res = Arrays::some( $arr, - function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; } + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; }, ); Assert::true($res); Assert::same([['a', 'x', $arr]], $log); diff --git a/tests/Utils/Callback.invokeSafe.phpt b/tests/Utils/Callback.invokeSafe.phpt index 80df9ff07..4ac046d31 100644 --- a/tests/Utils/Callback.invokeSafe.phpt +++ b/tests/Utils/Callback.invokeSafe.phpt @@ -31,9 +31,7 @@ Assert::same('OK1', $res); // ignored error -Callback::invokeSafe('preg_match', ['ab', 'foo'], function () { - return false; -}); +Callback::invokeSafe('preg_match', ['ab', 'foo'], fn() => false); Assert::same('preg_match(): Delimiter must not be alphanumeric or backslash', $res); diff --git a/tests/Utils/DateTime.JSON.phpt b/tests/Utils/DateTime.JSON.phpt index 90761833a..5683bd981 100644 --- a/tests/Utils/DateTime.JSON.phpt +++ b/tests/Utils/DateTime.JSON.phpt @@ -14,4 +14,4 @@ require __DIR__ . '/../bootstrap.php'; date_default_timezone_set('Europe/Prague'); -Assert::same('"1978-01-23T11:40:00+01:00"', json_encode(DateTime::from(254400000))); +Assert::same('"1978-01-23T11:40:00+01:00"', json_encode(DateTime::from(254_400_000))); diff --git a/tests/Utils/DateTime.from.phpt b/tests/Utils/DateTime.from.phpt index ab7a1e275..c8a6f95cd 100644 --- a/tests/Utils/DateTime.from.phpt +++ b/tests/Utils/DateTime.from.phpt @@ -14,14 +14,14 @@ require __DIR__ . '/../bootstrap.php'; date_default_timezone_set('Europe/Prague'); -Assert::same('1978-01-23 11:40:00', (string) DateTime::from(254400000)); -Assert::same('1978-01-23 11:40:00', (string) (new DateTime)->setTimestamp(254400000)); -Assert::same(254400000, DateTime::from(254400000)->getTimestamp()); +Assert::same('1978-01-23 11:40:00', (string) DateTime::from(254_400_000)); +Assert::same('1978-01-23 11:40:00', (string) (new DateTime)->setTimestamp(254_400_000)); +Assert::same(254_400_000, DateTime::from(254_400_000)->getTimestamp()); Assert::same(time() + 60, (int) DateTime::from(60)->format('U')); -Assert::same(PHP_VERSION_ID < 80100 ? '2050-08-13 11:40:00' : '2050-08-13 12:40:00', (string) DateTime::from(2544000000)); -Assert::same(PHP_VERSION_ID < 80100 ? '2050-08-13 11:40:00' : '2050-08-13 12:40:00', (string) (new DateTime)->setTimestamp(2544000000)); -Assert::same(is_int(2544000000) ? 2544000000 : '2544000000', DateTime::from(2544000000)->getTimestamp()); // 64 bit +Assert::same(PHP_VERSION_ID < 80100 ? '2050-08-13 11:40:00' : '2050-08-13 12:40:00', (string) DateTime::from(2_544_000_000)); +Assert::same(PHP_VERSION_ID < 80100 ? '2050-08-13 11:40:00' : '2050-08-13 12:40:00', (string) (new DateTime)->setTimestamp(2_544_000_000)); +Assert::same(is_int(2_544_000_000) ? 2_544_000_000 : '2544000000', DateTime::from(2_544_000_000)->getTimestamp()); // 64 bit Assert::same('1978-05-05 00:00:00', (string) DateTime::from('1978-05-05')); diff --git a/tests/Utils/DateTime.modifyClone.phpt b/tests/Utils/DateTime.modifyClone.phpt index 7e7fbb34b..b3ed3fbc2 100644 --- a/tests/Utils/DateTime.modifyClone.phpt +++ b/tests/Utils/DateTime.modifyClone.phpt @@ -14,7 +14,7 @@ require __DIR__ . '/../bootstrap.php'; date_default_timezone_set('Europe/Prague'); -$date = DateTime::from(254400000); +$date = DateTime::from(254_400_000); $dolly = $date->modifyClone(); Assert::type(DateTime::class, $dolly); Assert::notSame($date, $dolly); diff --git a/tests/Utils/Json.decode().phpt b/tests/Utils/Json.decode().phpt index e5b48af6a..e9910d26c 100644 --- a/tests/Utils/Json.decode().phpt +++ b/tests/Utils/Json.decode().phpt @@ -66,7 +66,7 @@ Assert::exception(function () { if (defined('JSON_C_VERSION')) { if (PHP_INT_SIZE > 4) { // 64-bit - Assert::same([9223372036854775807], Json::decode('[12345678901234567890]')); // trimmed to max 64-bit integer + Assert::same([9_223_372_036_854_775_807], Json::decode('[12345678901234567890]')); // trimmed to max 64-bit integer } else { // 32-bit Assert::same(['9223372036854775807'], Json::decode('[12345678901234567890]')); // trimmed to max 64-bit integer diff --git a/tests/Utils/Reflection.expandClassName.phpt b/tests/Utils/Reflection.expandClassName.phpt index 904c1b125..389ff39d3 100644 --- a/tests/Utils/Reflection.expandClassName.phpt +++ b/tests/Utils/Reflection.expandClassName.phpt @@ -140,21 +140,21 @@ foreach ($cases as $alias => $fqn) { Assert::same( ['C' => 'A\B'], - Reflection::getUseStatements(new ReflectionClass('Test')) + Reflection::getUseStatements(new ReflectionClass('Test')), ); Assert::same( [], - Reflection::getUseStatements(new ReflectionClass('Test\Space\Foo')) + Reflection::getUseStatements(new ReflectionClass('Test\Space\Foo')), ); Assert::same( ['AAA' => 'AAA', 'B' => 'BBB', 'DDD' => 'CCC\DDD', 'F' => 'EEE\FFF', 'G' => 'GGG'], - Reflection::getUseStatements(new ReflectionClass('Test\Space\Bar')) + Reflection::getUseStatements(new ReflectionClass('Test\Space\Bar')), ); Assert::same( [], - Reflection::getUseStatements(new ReflectionClass('stdClass')) + Reflection::getUseStatements(new ReflectionClass('stdClass')), ); Assert::exception(function () use ($rcTest) { diff --git a/tests/Utils/Reflection.groupUseStatements.phpt b/tests/Utils/Reflection.groupUseStatements.phpt index 7332b69bd..560985937 100644 --- a/tests/Utils/Reflection.groupUseStatements.phpt +++ b/tests/Utils/Reflection.groupUseStatements.phpt @@ -16,5 +16,5 @@ require __DIR__ . '/fixtures.reflection/expandClass.groupUse.php'; Assert::same( ['A' => 'A\B\A', 'C' => 'A\B\B\C', 'D' => 'A\B\C', 'E' => 'D\E'], - Reflection::getUseStatements(new ReflectionClass('GroupUseTest')) + Reflection::getUseStatements(new ReflectionClass('GroupUseTest')), ); diff --git a/tests/Utils/Reflection.nonClassUseStatements.phpt b/tests/Utils/Reflection.nonClassUseStatements.phpt index c6ce04430..bad5b7ace 100644 --- a/tests/Utils/Reflection.nonClassUseStatements.phpt +++ b/tests/Utils/Reflection.nonClassUseStatements.phpt @@ -16,5 +16,5 @@ require __DIR__ . '/fixtures.reflection/expandClass.nonClassUse.php'; Assert::same( [], - Reflection::getUseStatements(new ReflectionClass('NonClassUseTest')) + Reflection::getUseStatements(new ReflectionClass('NonClassUseTest')), ); diff --git a/tests/Utils/Strings.replace().phpt b/tests/Utils/Strings.replace().phpt index c1b94b504..5dda17322 100644 --- a/tests/Utils/Strings.replace().phpt +++ b/tests/Utils/Strings.replace().phpt @@ -24,7 +24,7 @@ class Test Assert::same('hello world!', Strings::replace('hello world!', '#([E-L])+#', '#')); Assert::same('#o wor#d!', Strings::replace('hello world!', ['#([e-l])+#'], '#')); Assert::same('#o wor#d!', Strings::replace('hello world!', '#([e-l])+#', '#')); -Assert::same('@o wor@d!', Strings::replace('hello world!', '#[e-l]+#', function () { return '@'; })); +Assert::same('@o wor@d!', Strings::replace('hello world!', '#[e-l]+#', fn() => '@')); Assert::same('@o wor@d!', Strings::replace('hello world!', '#[e-l]+#', Closure::fromCallable('Test::cb'))); Assert::same('@o wor@d!', Strings::replace('hello world!', ['#[e-l]+#'], Closure::fromCallable('Test::cb'))); Assert::same('@o wor@d!', Strings::replace('hello world!', '#[e-l]+#', ['Test', 'cb'])); diff --git a/tests/Utils/fixtures.reflection/defaultValue.php b/tests/Utils/fixtures.reflection/defaultValue.php index 196ab13de..f2e646ad4 100644 --- a/tests/Utils/fixtures.reflection/defaultValue.php +++ b/tests/Utils/fixtures.reflection/defaultValue.php @@ -41,7 +41,7 @@ public function method( $m = DEFINED, $n = UNDEFINED, $o = NS_DEFINED, - $p = parent::PUBLIC_DEFINED + $p = parent::PUBLIC_DEFINED, ) { } } From a0af6b084e612e271cfe0fba9cfaaa536a3b9405 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 19:15:56 +0100 Subject: [PATCH 10/25] added property typehints --- src/Iterators/CachingIterator.php | 3 +-- src/Utils/ArrayList.php | 3 +-- src/Utils/Html.php | 12 +++++------- src/Utils/Image.php | 3 +-- src/Utils/Paginator.php | 12 ++++-------- src/Utils/Type.php | 11 +++-------- tests/Utils/SmartObject.arrayProperty.phpt | 2 +- 7 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/Iterators/CachingIterator.php b/src/Iterators/CachingIterator.php index cb99645b3..e8e64f3a2 100644 --- a/src/Iterators/CachingIterator.php +++ b/src/Iterators/CachingIterator.php @@ -28,8 +28,7 @@ class CachingIterator extends \CachingIterator implements \Countable { use Nette\SmartObject; - /** @var int */ - private $counter = 0; + private int $counter = 0; public function __construct($iterator) diff --git a/src/Utils/ArrayList.php b/src/Utils/ArrayList.php index 0a694652d..b8c39c7ef 100644 --- a/src/Utils/ArrayList.php +++ b/src/Utils/ArrayList.php @@ -20,8 +20,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate { use Nette\SmartObject; - /** @var mixed[] */ - private $list = []; + private array $list = []; /** diff --git a/src/Utils/Html.php b/src/Utils/Html.php index 169fc551c..152640b23 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -238,10 +238,9 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab /** @var array element's attributes */ public $attrs = []; - /** @var bool use XHTML syntax? */ - public static $xhtml = false; + public static bool $xhtml = false; - /** @var array void elements */ + /** void elements */ public static $emptyElements = [ 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, @@ -251,11 +250,10 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab /** @var array nodes */ protected $children = []; - /** @var string element's name */ - private $name; + /** element's name */ + private string $name = ''; - /** @var bool is element empty? */ - private $isEmpty; + private bool $isEmpty = false; /** diff --git a/src/Utils/Image.php b/src/Utils/Image.php index 690723daf..db6d00557 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -125,8 +125,7 @@ class Image private const FORMATS = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::BMP => 'bmp']; - /** @var resource|\GdImage */ - private $image; + private \GdImage $image; /** diff --git a/src/Utils/Paginator.php b/src/Utils/Paginator.php index 57733bceb..dd42a5af3 100644 --- a/src/Utils/Paginator.php +++ b/src/Utils/Paginator.php @@ -34,17 +34,13 @@ class Paginator { use Nette\SmartObject; - /** @var int */ - private $base = 1; + private int $base = 1; - /** @var int */ - private $itemsPerPage = 1; + private int $itemsPerPage = 1; - /** @var int */ - private $page = 1; + private int $page = 1; - /** @var int|null */ - private $itemCount; + private ?int $itemCount = null; /** diff --git a/src/Utils/Type.php b/src/Utils/Type.php index 707a1e38c..2fa3b085a 100644 --- a/src/Utils/Type.php +++ b/src/Utils/Type.php @@ -17,14 +17,9 @@ */ final class Type { - /** @var array */ - private $types; - - /** @var bool */ - private $single; - - /** @var string |, & */ - private $kind; + private array $types; + private bool $single; + private string $kind; // | & /** diff --git a/tests/Utils/SmartObject.arrayProperty.phpt b/tests/Utils/SmartObject.arrayProperty.phpt index bc6f0ba1c..f935ff943 100644 --- a/tests/Utils/SmartObject.arrayProperty.phpt +++ b/tests/Utils/SmartObject.arrayProperty.phpt @@ -18,7 +18,7 @@ class TestClass { use Nette\SmartObject; - private $items = []; + private array $items = []; public function &getItems() From 8029de82dd9d42470eaceb0f10a99a813f6286f4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 19 Sep 2021 19:04:54 +0200 Subject: [PATCH 11/25] added PHP 8 typehints --- src/Iterators/CachingIterator.php | 6 +- src/Iterators/Mapper.php | 3 +- src/SmartObject.php | 20 ++--- src/StaticClass.php | 3 +- src/Translator.php | 4 +- src/Utils/ArrayHash.php | 6 +- src/Utils/ArrayList.php | 8 +- src/Utils/Arrays.php | 43 ++++------ src/Utils/Callback.php | 13 +-- src/Utils/DateTime.php | 25 ++---- src/Utils/Helpers.php | 10 +-- src/Utils/Html.php | 75 +++++------------ src/Utils/Image.php | 80 +++++++------------ src/Utils/Json.php | 6 +- src/Utils/ObjectHelpers.php | 4 +- src/Utils/Paginator.php | 12 +-- src/Utils/Reflection.php | 10 +-- src/Utils/Strings.php | 10 ++- src/Utils/Type.php | 13 +-- src/Utils/Validators.php | 31 +++---- tests/Utils/Arrays.get().phpt | 2 +- tests/Utils/Arrays.getKeyOffset().phpt | 1 - tests/Utils/Arrays.getRef().phpt | 2 +- tests/Utils/Arrays.pick().phpt | 2 +- tests/Utils/Arrays.renameKey().phpt | 2 +- tests/Utils/DateTime.createFromFormat.phpt | 4 - .../Strings.replace().errors.callback.phpt | 5 -- 27 files changed, 134 insertions(+), 266 deletions(-) diff --git a/src/Iterators/CachingIterator.php b/src/Iterators/CachingIterator.php index e8e64f3a2..d4e09f003 100644 --- a/src/Iterators/CachingIterator.php +++ b/src/Iterators/CachingIterator.php @@ -146,9 +146,8 @@ public function rewind(): void /** * Returns the next key. - * @return mixed */ - public function getNextKey() + public function getNextKey(): mixed { return $this->getInnerIterator()->key(); } @@ -156,9 +155,8 @@ public function getNextKey() /** * Returns the next element. - * @return mixed */ - public function getNextValue() + public function getNextValue(): mixed { return $this->getInnerIterator()->current(); } diff --git a/src/Iterators/Mapper.php b/src/Iterators/Mapper.php index 1a8647c60..87823baf9 100644 --- a/src/Iterators/Mapper.php +++ b/src/Iterators/Mapper.php @@ -27,8 +27,7 @@ public function __construct(\Traversable $iterator, callable $callback) } - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { return ($this->callback)(parent::current(), parent::key()); } diff --git a/src/SmartObject.php b/src/SmartObject.php index 2128fdfd0..5c7735de9 100644 --- a/src/SmartObject.php +++ b/src/SmartObject.php @@ -24,7 +24,7 @@ trait SmartObject /** * @throws MemberAccessException */ - public function __call(string $name, array $args) + public function __call(string $name, array $args): mixed { $class = static::class; @@ -37,27 +37,26 @@ public function __call(string $name, array $args) } elseif ($handlers !== null) { throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.'); } - - } else { - ObjectHelpers::strictCall($class, $name); + return null; } + + ObjectHelpers::strictCall($class, $name); } /** * @throws MemberAccessException */ - public static function __callStatic(string $name, array $args) + public static function __callStatic(string $name, array $args): mixed { ObjectHelpers::strictStaticCall(static::class, $name); } /** - * @return mixed * @throws MemberAccessException if the property is not defined. */ - public function &__get(string $name) + public function &__get(string $name): mixed { $class = static::class; @@ -79,11 +78,9 @@ public function &__get(string $name) /** - * @param mixed $value - * @return void * @throws MemberAccessException if the property is not defined or is read-only */ - public function __set(string $name, $value) + public function __set(string $name, mixed $value): void { $class = static::class; @@ -103,10 +100,9 @@ public function __set(string $name, $value) /** - * @return void * @throws MemberAccessException */ - public function __unset(string $name) + public function __unset(string $name): void { $class = static::class; if (!ObjectHelpers::hasProperty($class, $name)) { diff --git a/src/StaticClass.php b/src/StaticClass.php index 8fed0ef31..c719e3b6f 100644 --- a/src/StaticClass.php +++ b/src/StaticClass.php @@ -27,10 +27,9 @@ final public function __construct() /** * Call to undefined static method. - * @return void * @throws MemberAccessException */ - public static function __callStatic(string $name, array $args) + public static function __callStatic(string $name, array $args): mixed { Utils\ObjectHelpers::strictStaticCall(static::class, $name); } diff --git a/src/Translator.php b/src/Translator.php index 0f343a274..e8c5f7d06 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -17,10 +17,8 @@ interface Translator { /** * Translates the given string. - * @param mixed $message - * @param mixed ...$parameters */ - function translate($message, ...$parameters): string; + function translate(mixed $message, mixed ...$parameters): string; } diff --git a/src/Utils/ArrayHash.php b/src/Utils/ArrayHash.php index 5de178e20..f69ab58bb 100644 --- a/src/Utils/ArrayHash.php +++ b/src/Utils/ArrayHash.php @@ -21,9 +21,8 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator /** * Transforms array to ArrayHash. * @param array $array - * @return static */ - public static function from(array $array, bool $recursive = true) + public static function from(array $array, bool $recursive = true): static { $obj = new static; foreach ($array as $key => $value) { @@ -73,8 +72,7 @@ public function offsetSet($key, $value): void * @param string|int $key * @return T */ - #[\ReturnTypeWillChange] - public function offsetGet($key) + public function offsetGet($key): mixed { return $this->$key; } diff --git a/src/Utils/ArrayList.php b/src/Utils/ArrayList.php index b8c39c7ef..c22c96a46 100644 --- a/src/Utils/ArrayList.php +++ b/src/Utils/ArrayList.php @@ -26,9 +26,8 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate /** * Transforms array to ArrayList. * @param array $array - * @return static */ - public static function from(array $array) + public static function from(array $array): static { if (!Arrays::isList($array)) { throw new Nette\InvalidArgumentException('Array is not valid list.'); @@ -84,8 +83,7 @@ public function offsetSet($index, $value): void * @return T * @throws Nette\OutOfRangeException */ - #[\ReturnTypeWillChange] - public function offsetGet($index) + public function offsetGet($index): mixed { if (!is_int($index) || $index < 0 || $index >= count($this->list)) { throw new Nette\OutOfRangeException('Offset invalid or out of range'); @@ -122,7 +120,7 @@ public function offsetUnset($index): void * Prepends a item. * @param T $value */ - public function prepend($value): void + public function prepend(mixed $value): void { $first = array_slice($this->list, 0, 1); $this->offsetSet(0, $value); diff --git a/src/Utils/Arrays.php b/src/Utils/Arrays.php index 775b6517b..81d030936 100644 --- a/src/Utils/Arrays.php +++ b/src/Utils/Arrays.php @@ -29,7 +29,7 @@ class Arrays * @return ?T * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided */ - public static function get(array $array, $key, $default = null) + public static function get(array $array, string|int|array $key, mixed $default = null): mixed { foreach (is_array($key) ? $key : [$key] as $k) { if (is_array($array) && array_key_exists($k, $array)) { @@ -53,7 +53,7 @@ public static function get(array $array, $key, $default = null) * @return ?T * @throws Nette\InvalidArgumentException if traversed item is not an array */ - public static function &getRef(array &$array, $key) + public static function &getRef(array &$array, string|int|array $key): mixed { foreach (is_array($key) ? $key : [$key] as $k) { if (is_array($array) || $array === null) { @@ -90,10 +90,8 @@ public static function mergeTree(array $array1, array $array2): array /** * Returns zero-indexed position of given array key. Returns null if key is not found. - * @param array-key $key - * @return int|null offset if it is found, null otherwise */ - public static function getKeyOffset(array $array, $key): ?int + public static function getKeyOffset(array $array, string|int $key): ?int { return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true)); } @@ -110,9 +108,8 @@ public static function searchKey(array $array, $key): ?int /** * Tests an array for the presence of value. - * @param mixed $value */ - public static function contains(array $array, $value): bool + public static function contains(array $array, mixed $value): bool { return in_array($value, $array, true); } @@ -124,7 +121,7 @@ public static function contains(array $array, $value): bool * @param array $array * @return ?T */ - public static function first(array $array) + public static function first(array $array): mixed { return count($array) ? reset($array) : null; } @@ -136,7 +133,7 @@ public static function first(array $array) * @param array $array * @return ?T */ - public static function last(array $array) + public static function last(array $array): mixed { return count($array) ? end($array) : null; } @@ -145,9 +142,8 @@ public static function last(array $array) /** * Inserts the contents of the $inserted array into the $array immediately after the $key. * If $key is null (or does not exist), it is inserted at the beginning. - * @param array-key|null $key */ - public static function insertBefore(array &$array, $key, array $inserted): void + public static function insertBefore(array &$array, string|int|null $key, array $inserted): void { $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key); $array = array_slice($array, 0, $offset, true) @@ -159,9 +155,8 @@ public static function insertBefore(array &$array, $key, array $inserted): void /** * Inserts the contents of the $inserted array into the $array before the $key. * If $key is null (or does not exist), it is inserted at the end. - * @param array-key|null $key */ - public static function insertAfter(array &$array, $key, array $inserted): void + public static function insertAfter(array &$array, string|int|null $key, array $inserted): void { if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) { $offset = count($array) - 1; @@ -174,10 +169,8 @@ public static function insertAfter(array &$array, $key, array $inserted): void /** * Renames key in array. - * @param array-key $oldKey - * @param array-key $newKey */ - public static function renameKey(array &$array, $oldKey, $newKey): bool + public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool { $offset = self::getKeyOffset($array, $oldKey); if ($offset === null) { @@ -219,9 +212,8 @@ public static function flatten(array $array, bool $preserveKeys = false): array /** * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. - * @param mixed $value */ - public static function isList($value): bool + public static function isList(mixed $value): bool { return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1)); } @@ -230,9 +222,8 @@ public static function isList($value): bool /** * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. * @param string|string[] $path - * @return array|\stdClass */ - public static function associate(array $array, $path) + public static function associate(array $array, $path): array|\stdClass { $parts = is_array($path) ? $path @@ -285,9 +276,8 @@ public static function associate(array $array, $path) /** * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling. - * @param mixed $filling */ - public static function normalize(array $array, $filling = null): array + public static function normalize(array $array, mixed $filling = null): array { $res = []; foreach ($array as $k => $v) { @@ -302,12 +292,11 @@ public static function normalize(array $array, $filling = null): array * or returns $default, if provided. * @template T * @param array $array - * @param array-key $key * @param ?T $default * @return ?T * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided */ - public static function pick(array &$array, $key, $default = null) + public static function pick(array &$array, string|int $key, mixed $default = null): mixed { if (array_key_exists($key, $array)) { $value = $array[$key]; @@ -401,7 +390,7 @@ public static function invokeMethod(iterable $objects, string $method, ...$args) * @param T $object * @return T */ - public static function toObject(iterable $array, $object) + public static function toObject(iterable $array, object $object): object { foreach ($array as $k => $v) { $object->$k = $v; @@ -412,10 +401,8 @@ public static function toObject(iterable $array, $object) /** * Converts value to array key. - * @param mixed $value - * @return array-key */ - public static function toKey($value) + public static function toKey(mixed $value): int|string { return key([$value => null]); } diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index a11cdb3cc..1d9d835d2 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -22,9 +22,8 @@ final class Callback /** * Invokes internal PHP function with own error handler. - * @return mixed */ - public static function invokeSafe(string $function, array $args, callable $onError) + public static function invokeSafe(string $function, array $args, callable $onError): mixed { $prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool { if ($file === __FILE__) { @@ -50,11 +49,10 @@ public static function invokeSafe(string $function, array $args, callable $onErr /** * Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies * that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists. - * @param mixed $callable * @return callable * @throws Nette\InvalidArgumentException */ - public static function check($callable, bool $syntax = false) + public static function check(mixed $callable, bool $syntax = false) { if (!is_callable($callable, $syntax)) { throw new Nette\InvalidArgumentException( @@ -69,9 +67,8 @@ public static function check($callable, bool $syntax = false) /** * Converts PHP callback to textual form. Class or method may not exists. - * @param mixed $callable */ - public static function toString($callable): string + public static function toString(mixed $callable): string { if ($callable instanceof \Closure) { $inner = self::unwrap($callable); @@ -86,7 +83,6 @@ public static function toString($callable): string /** * Returns reflection for method or function used in PHP callback. * @param callable $callable type check is escalated to ReflectionException - * @return \ReflectionMethod|\ReflectionFunction * @throws \ReflectionException if callback is not valid */ public static function toReflection($callable): \ReflectionFunctionAbstract @@ -118,9 +114,8 @@ public static function isStatic(callable $callable): bool /** * Unwraps closure created by Closure::fromCallable(). - * @return \Closure|array|string */ - public static function unwrap(\Closure $closure) + public static function unwrap(\Closure $closure): \Closure|array|string { $r = new \ReflectionFunction($closure); if (substr($r->name, -1) === '}') { diff --git a/src/Utils/DateTime.php b/src/Utils/DateTime.php index 661b01c99..48e50334a 100644 --- a/src/Utils/DateTime.php +++ b/src/Utils/DateTime.php @@ -40,11 +40,9 @@ class DateTime extends \DateTime implements \JsonSerializable /** * Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object. - * @param string|int|\DateTimeInterface $time - * @return static * @throws \Exception if the date and time are not valid. */ - public static function from($time) + public static function from(string|int|\DateTimeInterface|null $time): static { if ($time instanceof \DateTimeInterface) { return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone()); @@ -63,7 +61,6 @@ public static function from($time) /** * Creates DateTime object. - * @return static * @throws Nette\InvalidArgumentException if the date and time are not valid. */ public static function fromParts( @@ -73,7 +70,7 @@ public static function fromParts( int $hour = 0, int $minute = 0, float $second = 0.0, - ) { + ): static { $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); if ( !checkdate($month, $day, $year) @@ -92,22 +89,17 @@ public static function fromParts( /** * Returns new DateTime object formatted according to the specified format. - * @param string $format The format the $time parameter should be in - * @param string $time - * @param string|\DateTimeZone $timezone (default timezone is used if null is passed) - * @return static|false */ - #[\ReturnTypeWillChange] - public static function createFromFormat($format, $time, $timezone = null) - { + public static function createFromFormat( + string $format, + string $time, + string|\DateTimeZone $timezone = null, + ): static|false { if ($timezone === null) { $timezone = new \DateTimeZone(date_default_timezone_get()); } elseif (is_string($timezone)) { $timezone = new \DateTimeZone($timezone); - - } elseif (!$timezone instanceof \DateTimeZone) { - throw new Nette\InvalidArgumentException('Invalid timezone given'); } $date = parent::createFromFormat($format, $time, $timezone); @@ -135,9 +127,8 @@ public function __toString(): string /** * Creates a copy with a modified time. - * @return static */ - public function modifyClone(string $modify = '') + public function modifyClone(string $modify = ''): static { $dolly = clone $this; return $modify ? $dolly->modify($modify) : $dolly; diff --git a/src/Utils/Helpers.php b/src/Utils/Helpers.php index 5e033fe48..1551e1f19 100644 --- a/src/Utils/Helpers.php +++ b/src/Utils/Helpers.php @@ -43,10 +43,8 @@ public static function getLastError(): string /** * Converts false to null, does not change other values. - * @param mixed $value - * @return mixed */ - public static function falseToNull($value) + public static function falseToNull(mixed $value): mixed { return $value === false ? null : $value; } @@ -54,12 +52,8 @@ public static function falseToNull($value) /** * Returns value clamped to the inclusive range of min and max. - * @param int|float $value - * @param int|float $min - * @param int|float $max - * @return int|float */ - public static function clamp($value, $min, $max) + public static function clamp(int|float $value, int|float $min, int|float $max): int|float { if ($min > $max) { throw new \InvalidArgumentException("Minimum ($min) is not less than maximum ($max)."); diff --git a/src/Utils/Html.php b/src/Utils/Html.php index 152640b23..e18e1c934 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -259,9 +259,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab /** * Constructs new HTML element. * @param array|string $attrs element's attributes or plain text content - * @return static */ - public static function el(string $name = null, $attrs = null) + public static function el(string $name = null, array|string $attrs = null): static { $el = new static; $parts = explode(' ', (string) $name, 2); @@ -287,7 +286,7 @@ public static function el(string $name = null, $attrs = null) /** * Returns an object representing HTML text. */ - public static function fromHtml(string $html): self + public static function fromHtml(string $html): static { return (new static)->setHtml($html); } @@ -296,7 +295,7 @@ public static function fromHtml(string $html): self /** * Returns an object representing plain text. */ - public static function fromText(string $text): self + public static function fromText(string $text): static { return (new static)->setText($text); } @@ -331,9 +330,8 @@ public static function htmlToText(string $html): string /** * Changes element's name. - * @return static */ - final public function setName(string $name, bool $isEmpty = null) + final public function setName(string $name, bool $isEmpty = null): static { $this->name = $name; $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); @@ -361,9 +359,8 @@ final public function isEmpty(): bool /** * Sets multiple attributes. - * @return static */ - public function addAttributes(array $attrs) + public function addAttributes(array $attrs): static { $this->attrs = array_merge($this->attrs, $attrs); return $this; @@ -372,11 +369,8 @@ public function addAttributes(array $attrs) /** * Appends value to element's attribute. - * @param mixed $value - * @param mixed $option - * @return static */ - public function appendAttribute(string $name, $value, $option = true) + public function appendAttribute(string $name, mixed $value, mixed $option = true): static { if (is_array($value)) { $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; @@ -397,10 +391,8 @@ public function appendAttribute(string $name, $value, $option = true) /** * Sets element's attribute. - * @param mixed $value - * @return static */ - public function setAttribute(string $name, $value) + public function setAttribute(string $name, mixed $value): static { $this->attrs[$name] = $value; return $this; @@ -409,9 +401,8 @@ public function setAttribute(string $name, $value) /** * Returns element's attribute. - * @return mixed */ - public function getAttribute(string $name) + public function getAttribute(string $name): mixed { return $this->attrs[$name] ?? null; } @@ -419,9 +410,8 @@ public function getAttribute(string $name) /** * Unsets element's attribute. - * @return static */ - public function removeAttribute(string $name) + public function removeAttribute(string $name): static { unset($this->attrs[$name]); return $this; @@ -430,9 +420,8 @@ public function removeAttribute(string $name) /** * Unsets element's attributes. - * @return static */ - public function removeAttributes(array $attributes) + public function removeAttributes(array $attributes): static { foreach ($attributes as $name) { unset($this->attrs[$name]); @@ -443,9 +432,8 @@ public function removeAttributes(array $attributes) /** * Overloaded setter for element's attribute. - * @param mixed $value */ - final public function __set(string $name, $value): void + final public function __set(string $name, mixed $value): void { $this->attrs[$name] = $value; } @@ -453,9 +441,8 @@ final public function __set(string $name, $value): void /** * Overloaded getter for element's attribute. - * @return mixed */ - final public function &__get(string $name) + final public function &__get(string $name): mixed { return $this->attrs[$name]; } @@ -481,9 +468,8 @@ final public function __unset(string $name): void /** * Overloaded setter for element's attribute. - * @return mixed */ - final public function __call(string $m, array $args) + final public function __call(string $m, array $args): mixed { $p = substr($m, 0, 3); if ($p === 'get' || $p === 'set' || $p === 'add') { @@ -512,9 +498,8 @@ final public function __call(string $m, array $args) /** * Special setter for element's attribute. - * @return static */ - final public function href(string $path, array $query = null) + final public function href(string $path, array $query = null): static { if ($query) { $query = http_build_query($query, '', '&'); @@ -529,10 +514,8 @@ final public function href(string $path, array $query = null) /** * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. - * @param mixed $value - * @return static */ - public function data(string $name, $value = null) + public function data(string $name, mixed $value = null): static { if (func_num_args() === 1) { $this->attrs['data'] = $name; @@ -547,10 +530,8 @@ public function data(string $name, $value = null) /** * Sets element's HTML content. - * @param HtmlStringable|string $html - * @return static */ - final public function setHtml($html) + final public function setHtml(HtmlStringable|string $html): static { $this->children = [(string) $html]; return $this; @@ -568,10 +549,8 @@ final public function getHtml(): string /** * Sets element's textual content. - * @param HtmlStringable|string|int|float $text - * @return static */ - final public function setText($text) + final public function setText(HtmlStringable|string|int|float $text): static { if (!$text instanceof HtmlStringable) { $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); @@ -592,10 +571,8 @@ final public function getText(): string /** * Adds new element's child. - * @param HtmlStringable|string $child Html node or raw HTML string - * @return static */ - final public function addHtml($child) + final public function addHtml(HtmlStringable|string $child): static { return $this->insert(null, $child); } @@ -603,10 +580,8 @@ final public function addHtml($child) /** * Appends plain-text string to element content. - * @param HtmlStringable|string|int|float $text - * @return static */ - public function addText($text) + public function addText(HtmlStringable|string|int|float $text): static { if (!$text instanceof HtmlStringable) { $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); @@ -617,10 +592,8 @@ public function addText($text) /** * Creates and adds a new Html child. - * @param array|string $attrs element's attributes or raw HTML string - * @return static created element */ - final public function create(string $name, $attrs = null) + final public function create(string $name, array|string $attrs = null): static { $this->insert(null, $child = static::el($name, $attrs)); return $child; @@ -629,10 +602,8 @@ final public function create(string $name, $attrs = null) /** * Inserts child node. - * @param HtmlStringable|string $child Html node or raw HTML string - * @return static */ - public function insert(?int $index, $child, bool $replace = false) + public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static { $child = $child instanceof self ? $child : (string) $child; if ($index === null) { // append @@ -660,10 +631,8 @@ final public function offsetSet($index, $child): void /** * Returns child node (\ArrayAccess implementation). * @param int $index - * @return HtmlStringable|string */ - #[\ReturnTypeWillChange] - final public function offsetGet($index) + final public function offsetGet($index): HtmlStringable|string { return $this->children[$index]; } diff --git a/src/Utils/Image.php b/src/Utils/Image.php index db6d00557..650976a84 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -92,7 +92,7 @@ * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text) * @property-read int $width * @property-read int $height - * @property-read resource|\GdImage $imageResource + * @property-read \GdImage $imageResource */ class Image { @@ -146,9 +146,8 @@ public static function rgb(int $red, int $green, int $blue, int $transparency = * Reads an image from a file and returns its type in $type. * @throws Nette\NotSupportedException if gd extension is not loaded * @throws UnknownImageFileException if file not found or file type is not known - * @return static */ - public static function fromFile(string $file, int &$type = null) + public static function fromFile(string $file, int &$type = null): static { if (!extension_loaded('gd')) { throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); @@ -168,11 +167,10 @@ public static function fromFile(string $file, int &$type = null) /** * Reads an image from a string and returns its type in $type. - * @return static * @throws Nette\NotSupportedException if gd extension is not loaded * @throws ImageException */ - public static function fromString(string $s, int &$type = null) + public static function fromString(string $s, int &$type = null): static { if (!extension_loaded('gd')) { throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); @@ -191,10 +189,9 @@ public static function fromString(string $s, int &$type = null) /** * Creates a new true color image of the given dimensions. The default color is black. - * @return static * @throws Nette\NotSupportedException if gd extension is not loaded */ - public static function fromBlank(int $width, int $height, array $color = null) + public static function fromBlank(int $width, int $height, array $color = null): static { if (!extension_loaded('gd')) { throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); @@ -259,9 +256,8 @@ public static function typeToMimeType(int $type): string /** * Wraps GD image. - * @param resource|\GdImage $image */ - public function __construct($image) + public function __construct(\GdImage $image) { $this->setImageResource($image); imagesavealpha($image, true); @@ -288,14 +284,9 @@ public function getHeight(): int /** * Sets image resource. - * @param resource|\GdImage $image - * @return static */ - protected function setImageResource($image) + protected function setImageResource(\GdImage $image): static { - if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) { - throw new Nette\InvalidArgumentException('Image is not valid.'); - } $this->image = $image; return $this; } @@ -303,21 +294,17 @@ protected function setImageResource($image) /** * Returns image GD resource. - * @return resource|\GdImage */ - public function getImageResource() + public function getImageResource(): \GdImage { return $this->image; } /** - * Scales an image. - * @param int|string|null $width in pixels or percent - * @param int|string|null $height in pixels or percent - * @return static + * Scales an image. Width and height accept pixels or percent. */ - public function resize($width, $height, int $flags = self::FIT) + public function resize(int|string|null $width, int|string|null $height, int $flags = self::FIT): static { if ($flags & self::EXACT) { return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height); @@ -350,9 +337,7 @@ public function resize($width, $height, int $flags = self::FIT) /** - * Calculates dimensions of resized image. - * @param int|string|null $newWidth in pixels or percent - * @param int|string|null $newHeight in pixels or percent + * Calculates dimensions of resized image. Width and height accept pixels or percent. */ public static function calculateSize( int $srcWidth, @@ -419,14 +404,9 @@ public static function calculateSize( /** - * Crops image. - * @param int|string $left in pixels or percent - * @param int|string $top in pixels or percent - * @param int|string $width in pixels or percent - * @param int|string $height in pixels or percent - * @return static + * Crops image. Arguments accepts pixels or percent. */ - public function crop($left, $top, $width, $height) + public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static { [$r['x'], $r['y'], $r['width'], $r['height']] = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); @@ -443,14 +423,16 @@ public function crop($left, $top, $width, $height) /** - * Calculates dimensions of cutout in image. - * @param int|string $left in pixels or percent - * @param int|string $top in pixels or percent - * @param int|string $newWidth in pixels or percent - * @param int|string $newHeight in pixels or percent + * Calculates dimensions of cutout in image. Arguments accepts pixels or percent. */ - public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array - { + public static function calculateCutout( + int $srcWidth, + int $srcHeight, + int|string $left, + int|string $top, + int|string $newWidth, + int|string $newHeight, + ): array { if (self::isPercent($newWidth)) { $newWidth = (int) round($srcWidth / 100 * $newWidth); } @@ -479,9 +461,8 @@ public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $to /** * Sharpens image a little bit. - * @return static */ - public function sharpen() + public function sharpen(): static { imageconvolution($this->image, [ // my magic numbers ;) [-1, -1, -1], @@ -493,13 +474,10 @@ public function sharpen() /** - * Puts another image into this image. - * @param int|string $left in pixels or percent - * @param int|string $top in pixels or percent + * Puts another image into this image. Left and top accepts pixels or percent. * @param int $opacity 0..100 - * @return static */ - public function place(self $image, $left = 0, $top = 0, int $opacity = 100) + public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static { $opacity = max(0, min(100, $opacity)); if ($opacity === 0) { @@ -646,10 +624,9 @@ private function output(int $type, ?int $quality, string $file = null): void /** * Call to undefined method. - * @return mixed * @throws Nette\MemberAccessException */ - public function __call(string $name, array $args) + public function __call(string $name, array $args): mixed { $function = 'image' . $name; if (!function_exists($function)) { @@ -677,7 +654,7 @@ public function __call(string $name, array $args) } } $res = $function($this->image, ...$args); - return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd') + return $res instanceof \GdImage ? $this->setImageResource($res) : $res; } @@ -691,10 +668,7 @@ public function __clone() } - /** - * @param int|string $num in pixels or percent - */ - private static function isPercent(&$num): bool + private static function isPercent(int|string &$num): bool { if (is_string($num) && substr($num, -1) === '%') { $num = (float) substr($num, 0, -1); diff --git a/src/Utils/Json.php b/src/Utils/Json.php index 782c1ff2b..9f06a7f05 100644 --- a/src/Utils/Json.php +++ b/src/Utils/Json.php @@ -29,10 +29,9 @@ final class Json /** * Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity, * and Json::ESCAPE_UNICODE for ASCII output. - * @param mixed $value * @throws JsonException */ - public static function encode($value, int $flags = 0): string + public static function encode(mixed $value, int $flags = 0): string { $flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE) | JSON_UNESCAPED_SLASHES @@ -49,10 +48,9 @@ public static function encode($value, int $flags = 0): string /** * Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value. - * @return mixed * @throws JsonException */ - public static function decode(string $json, int $flags = 0) + public static function decode(string $json, int $flags = 0): mixed { $forceArray = (bool) ($flags & self::FORCE_ARRAY); $value = json_decode($json, $forceArray, 512, JSON_BIGINT_AS_STRING); diff --git a/src/Utils/ObjectHelpers.php b/src/Utils/ObjectHelpers.php index 8d973c04f..57bb70bad 100644 --- a/src/Utils/ObjectHelpers.php +++ b/src/Utils/ObjectHelpers.php @@ -202,10 +202,10 @@ private static function parseFullDoc(\ReflectionClass $rc, string $pattern): arr /** * Checks if the public non-static property exists. - * @return bool|string returns 'event' if the property exists and has event like name + * Returns 'event' if the property exists and has event like name * @internal */ - public static function hasProperty(string $class, string $name) + public static function hasProperty(string $class, string $name): bool|string { static $cache; $prop = &$cache[$class][$name]; diff --git a/src/Utils/Paginator.php b/src/Utils/Paginator.php index dd42a5af3..ab3de1582 100644 --- a/src/Utils/Paginator.php +++ b/src/Utils/Paginator.php @@ -45,9 +45,8 @@ class Paginator /** * Sets current page number. - * @return static */ - public function setPage(int $page) + public function setPage(int $page): static { $this->page = $page; return $this; @@ -105,9 +104,8 @@ public function getLastItemOnPage(): int /** * Sets first page (base) number. - * @return static */ - public function setBase(int $base) + public function setBase(int $base): static { $this->base = $base; return $this; @@ -168,9 +166,8 @@ public function getPageCount(): ?int /** * Sets the number of items to display on a single page. - * @return static */ - public function setItemsPerPage(int $itemsPerPage) + public function setItemsPerPage(int $itemsPerPage): static { $this->itemsPerPage = max(1, $itemsPerPage); return $this; @@ -188,9 +185,8 @@ public function getItemsPerPage(): int /** * Sets the total number of items. - * @return static */ - public function setItemCount(int $itemCount = null) + public function setItemCount(int $itemCount = null): static { $this->itemCount = $itemCount === null ? null : max(0, $itemCount); return $this; diff --git a/src/Utils/Reflection.php b/src/Utils/Reflection.php index 6be5b2e8c..1c6aeb642 100644 --- a/src/Utils/Reflection.php +++ b/src/Utils/Reflection.php @@ -82,10 +82,7 @@ public static function getPropertyType(\ReflectionProperty $prop): ?string } - /** - * @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection - */ - private static function getType($reflection, ?\ReflectionType $type): ?string + private static function getType(\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, ?\ReflectionType $type): ?string { if ($type === null) { return null; @@ -104,10 +101,9 @@ private static function getType($reflection, ?\ReflectionType $type): ?string /** * Returns the default value of parameter. If it is a constant, it returns its value. - * @return mixed * @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved */ - public static function getParameterDefaultValue(\ReflectionParameter $param) + public static function getParameterDefaultValue(\ReflectionParameter $param): mixed { if ($param->isDefaultValueConstant()) { $const = $orig = $param->getDefaultValueConstantName(); @@ -362,7 +358,7 @@ private static function parseUseStatements(string $code, string $forClass = null } - private static function fetch(array &$tokens, $take): ?string + private static function fetch(array &$tokens, string|int|array $take): ?string { $res = null; while ($token = current($tokens)) { diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index cf6f52ef9..ebf10440b 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -511,11 +511,13 @@ public static function matchAll(string $subject, string $pattern, int $flags = 0 /** * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`. - * @param string|array $pattern - * @param string|callable $replacement */ - public static function replace(string $subject, $pattern, $replacement = '', int $limit = -1): string - { + public static function replace( + string $subject, + string|array $pattern, + string|callable $replacement = '', + int $limit = -1, + ): string { if (is_object($replacement) || is_array($replacement)) { if (!is_callable($replacement, false, $textual)) { throw new Nette\InvalidStateException("Callback '$textual' is not callable."); diff --git a/src/Utils/Type.php b/src/Utils/Type.php index 2fa3b085a..760e4205d 100644 --- a/src/Utils/Type.php +++ b/src/Utils/Type.php @@ -25,10 +25,10 @@ final class Type /** * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name. * If the subject has no type, it returns null. - * @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection */ - public static function fromReflection($reflection): ?self - { + public static function fromReflection( + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, + ): ?self { if ($reflection instanceof \ReflectionMethod) { $type = $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null); } else { @@ -83,10 +83,11 @@ public static function fromString(string $type): self /** * Resolves 'self', 'static' and 'parent' to the actual class name. - * @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection */ - public static function resolve(string $type, $reflection): string - { + public static function resolve( + string $type, + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, + ): string { $lower = strtolower($type); if ($reflection instanceof \ReflectionFunction) { return $type; diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index 9b787ad41..645211d51 100644 --- a/src/Utils/Validators.php +++ b/src/Utils/Validators.php @@ -87,10 +87,9 @@ class Validators /** * Verifies that the value is of expected types separated by pipe. - * @param mixed $value * @throws AssertionException */ - public static function assert($value, string $expected, string $label = 'variable'): void + public static function assert(mixed $value, string $expected, string $label = 'variable'): void { if (!static::is($value, $expected)) { $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); @@ -109,7 +108,6 @@ public static function assert($value, string $expected, string $label = 'variabl /** * Verifies that element $key in array is of expected types separated by pipe. * @param mixed[] $array - * @param int|string $key * @throws AssertionException */ public static function assertField( @@ -129,9 +127,8 @@ public static function assertField( /** * Verifies that the value is of expected types separated by pipe. - * @param mixed $value */ - public static function is($value, string $expected): bool + public static function is(mixed $value, string $expected): bool { foreach (explode('|', $expected) as $item) { if (substr($item, -2) === '[]') { @@ -200,9 +197,8 @@ public static function everyIs(iterable $values, string $expected): bool /** * Checks if the value is an integer or a float. - * @param mixed $value */ - public static function isNumber($value): bool + public static function isNumber(mixed $value): bool { return is_int($value) || is_float($value); } @@ -210,9 +206,8 @@ public static function isNumber($value): bool /** * Checks if the value is an integer or a integer written in a string. - * @param mixed $value */ - public static function isNumericInt($value): bool + public static function isNumericInt(mixed $value): bool { return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); } @@ -220,9 +215,8 @@ public static function isNumericInt($value): bool /** * Checks if the value is a number or a number written in a string. - * @param mixed $value */ - public static function isNumeric($value): bool + public static function isNumeric(mixed $value): bool { return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]*[.]?[0-9]+$#D', $value)); } @@ -230,9 +224,8 @@ public static function isNumeric($value): bool /** * Checks if the value is a syntactically correct callback. - * @param mixed $value */ - public static function isCallable($value): bool + public static function isCallable(mixed $value): bool { return $value && is_callable($value, true); } @@ -240,9 +233,8 @@ public static function isCallable($value): bool /** * Checks if the value is a valid UTF-8 string. - * @param mixed $value */ - public static function isUnicode($value): bool + public static function isUnicode(mixed $value): bool { return is_string($value) && preg_match('##u', $value); } @@ -250,9 +242,8 @@ public static function isUnicode($value): bool /** * Checks if the value is 0, '', false or null. - * @param mixed $value */ - public static function isNone($value): bool + public static function isNone(mixed $value): bool { return $value == null; // intentionally == } @@ -267,10 +258,9 @@ public static function isMixed(): bool /** * Checks if a variable is a zero-based integer indexed array. - * @param mixed $value * @deprecated use Nette\Utils\Arrays::isList */ - public static function isList($value): bool + public static function isList(mixed $value): bool { return Arrays::isList($value); } @@ -279,9 +269,8 @@ public static function isList($value): bool /** * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null). * Numbers, strings and DateTime objects can be compared. - * @param mixed $value */ - public static function isInRange($value, array $range): bool + public static function isInRange(mixed $value, array $range): bool { if ($value === null || !(isset($range[0]) || isset($range[1]))) { return false; diff --git a/tests/Utils/Arrays.get().phpt b/tests/Utils/Arrays.get().phpt index 277af3bf3..9a3195a30 100644 --- a/tests/Utils/Arrays.get().phpt +++ b/tests/Utils/Arrays.get().phpt @@ -22,7 +22,7 @@ $arr = [ ]; test('Single item', function () use ($arr) { - Assert::same('first', Arrays::get($arr, null)); + Assert::same('first', Arrays::get($arr, '')); Assert::same('second', Arrays::get($arr, 1)); Assert::same('second', Arrays::get($arr, 1, 'x')); Assert::same('x', Arrays::get($arr, 'undefined', 'x')); diff --git a/tests/Utils/Arrays.getKeyOffset().phpt b/tests/Utils/Arrays.getKeyOffset().phpt index 357467755..2a2826f00 100644 --- a/tests/Utils/Arrays.getKeyOffset().phpt +++ b/tests/Utils/Arrays.getKeyOffset().phpt @@ -24,6 +24,5 @@ Assert::same(3, Arrays::getKeyOffset($arr, '1')); Assert::same(3, Arrays::getKeyOffset($arr, 1)); Assert::same(2, Arrays::getKeyOffset($arr, 7)); Assert::same(1, Arrays::getKeyOffset($arr, 0)); -Assert::same(0, Arrays::getKeyOffset($arr, null)); Assert::same(0, Arrays::getKeyOffset($arr, '')); Assert::null(Arrays::getKeyOffset($arr, 'undefined')); diff --git a/tests/Utils/Arrays.getRef().phpt b/tests/Utils/Arrays.getRef().phpt index ad242bb90..d45af2b53 100644 --- a/tests/Utils/Arrays.getRef().phpt +++ b/tests/Utils/Arrays.getRef().phpt @@ -23,7 +23,7 @@ $arr = [ test('Single item', function () use ($arr) { $dolly = $arr; - $item = &Arrays::getRef($dolly, null); + $item = &Arrays::getRef($dolly, ''); $item = 'changed'; Assert::same([ '' => 'changed', diff --git a/tests/Utils/Arrays.pick().phpt b/tests/Utils/Arrays.pick().phpt index b81ea0d89..787400df3 100644 --- a/tests/Utils/Arrays.pick().phpt +++ b/tests/Utils/Arrays.pick().phpt @@ -20,7 +20,7 @@ $arr = [ ]; test('Single item', function () use ($arr) { - Assert::same('null', Arrays::pick($arr, null)); + Assert::same('null', Arrays::pick($arr, '')); Assert::same('first', Arrays::pick($arr, 1)); Assert::same('x', Arrays::pick($arr, 1, 'x')); Assert::exception(function () use ($arr) { diff --git a/tests/Utils/Arrays.renameKey().phpt b/tests/Utils/Arrays.renameKey().phpt index c852df90f..64f58ee35 100644 --- a/tests/Utils/Arrays.renameKey().phpt +++ b/tests/Utils/Arrays.renameKey().phpt @@ -36,7 +36,7 @@ Assert::same([ 'new1' => 'third', ], $arr); -Arrays::renameKey($arr, null, 'new3'); +Arrays::renameKey($arr, '', 'new3'); Assert::same([ 'new3' => 'first', 'new2' => 'second', diff --git a/tests/Utils/DateTime.createFromFormat.phpt b/tests/Utils/DateTime.createFromFormat.phpt index 08ffdd709..321d43f99 100644 --- a/tests/Utils/DateTime.createFromFormat.phpt +++ b/tests/Utils/DateTime.createFromFormat.phpt @@ -22,8 +22,4 @@ Assert::same('2050-08-13 11:40:00.123450', DateTime::createFromFormat('Y-m-d H:i Assert::same('Europe/Prague', DateTime::createFromFormat('Y', '2050')->getTimezone()->getName()); Assert::same('Europe/Bratislava', DateTime::createFromFormat('Y', '2050', 'Europe/Bratislava')->getTimezone()->getName()); -Assert::error(function () { - DateTime::createFromFormat('Y-m-d H:i:s', '2050-08-13 11:40:00', 5); -}, Nette\InvalidArgumentException::class, 'Invalid timezone given'); - Assert::false(DateTime::createFromFormat('Y-m-d', '2014-10')); diff --git a/tests/Utils/Strings.replace().errors.callback.phpt b/tests/Utils/Strings.replace().errors.callback.phpt index d29ead08b..5643940af 100644 --- a/tests/Utils/Strings.replace().errors.callback.phpt +++ b/tests/Utils/Strings.replace().errors.callback.phpt @@ -26,8 +26,3 @@ Assert::same('HELLO', Strings::replace('hello', '#.+#', function ($m) { preg_match('#\d#u', "0123456789\xFF"); // Malformed UTF-8 data return strtoupper($m[0]); })); - - -Assert::exception(function () { - Strings::replace('hello', '#.+#', [stdClass::class, 'foobar']); -}, InvalidStateException::class, "Callback 'stdClass::foobar' is not callable."); From 0d515324bb8da16a26533da15370039656632930 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 11 Mar 2021 21:55:53 +0100 Subject: [PATCH 12/25] removed community health files --- .github/ISSUE_TEMPLATE/Bug_report.md | 19 ------------- .github/ISSUE_TEMPLATE/Feature_request.md | 9 ------ .github/ISSUE_TEMPLATE/Support_question.md | 12 -------- .github/ISSUE_TEMPLATE/Support_us.md | 21 -------------- .github/funding.yml | 2 -- .github/pull_request_template.md | 11 -------- contributing.md | 33 ---------------------- 7 files changed, 107 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/Support_question.md delete mode 100644 .github/ISSUE_TEMPLATE/Support_us.md delete mode 100644 .github/funding.yml delete mode 100644 .github/pull_request_template.md delete mode 100644 contributing.md diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index a4cd12634..000000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: "🐛 Bug Report" -about: "If something isn't working as expected 🤔" - ---- - -Version: ?.?.? - -### Bug Description -... A clear and concise description of what the bug is. A good bug report shouldn't leave others needing to chase you up for more information. - -### Steps To Reproduce -... If possible a minimal demo of the problem ... - -### Expected Behavior -... A clear and concise description of what you expected to happen. - -### Possible Solution -... Only if you have suggestions on a fix for the bug diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index d2e219489..000000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: "🚀 Feature Request" -about: "I have a suggestion (and may want to implement it) 🙂" - ---- - -- Is your feature request related to a problem? Please describe. -- Explain your intentions. -- It's up to you to make a strong case to convince the project's developers of the merits of this feature. diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/Support_question.md deleted file mode 100644 index 75c48b6ed..000000000 --- a/.github/ISSUE_TEMPLATE/Support_question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "🤗 Support Question" -about: "If you have a question 💬, please check out our forum!" - ---- - ---------------^ Click "Preview" for a nicer view! -We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁. - -* Nette Forum: https://forum.nette.org -* Nette Gitter: https://gitter.im/nette/nette -* Slack (czech): https://pehapkari.slack.com/messages/C2R30BLKA diff --git a/.github/ISSUE_TEMPLATE/Support_us.md b/.github/ISSUE_TEMPLATE/Support_us.md deleted file mode 100644 index 92d8a4c3a..000000000 --- a/.github/ISSUE_TEMPLATE/Support_us.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: "❤️ Support us" -about: "If you would like to support our efforts in maintaining this project 🙌" - ---- - ---------------^ Click "Preview" for a nicer view! - -> https://nette.org/donate - -Help support Nette! - -We develop Nette Framework for more than 14 years. In order to make your life more comfortable. Nette cares about the safety of your sites. Nette saves you time. And gives job opportunities. - -Nette earns you money. And is absolutely free. - -To ensure future development and improving the documentation, we need your donation. - -Whether you are chief of IT company which benefits from Nette, or developer who goes for advice on our forum, if you like Nette, [please make a donation now](https://nette.org/donate). - -Thank you! diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 25adc9520..000000000 --- a/.github/funding.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: dg -custom: "https://nette.org/donate" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index f8aa3f408..000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,11 +0,0 @@ -- bug fix / new feature? -- BC break? yes/no -- doc PR: nette/docs#??? - - diff --git a/contributing.md b/contributing.md deleted file mode 100644 index 184152c02..000000000 --- a/contributing.md +++ /dev/null @@ -1,33 +0,0 @@ -How to contribute & use the issue tracker -========================================= - -Nette welcomes your contributions. There are several ways to help out: - -* Create an issue on GitHub, if you have found a bug -* Write test cases for open bug issues -* Write fixes for open bug/feature issues, preferably with test cases included -* Contribute to the [documentation](https://nette.org/en/writing) - -Issues ------- - -Please **do not use the issue tracker to ask questions**. We will be happy to help you -on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. - -**Feature requests** are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong -case to convince the project's developers of the merits of this feature. - -Contributing ------------- - -If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). - -The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. - -Please do not fix whitespace, format code, or make a purely cosmetic patch. - -Thanks! :heart: From 58898425ba3498c5a610af966f06a9dd315cc9bc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 22 Apr 2021 18:17:33 +0200 Subject: [PATCH 13/25] tests: added --- .../SmartObject.undeclaredMethod.native.phpt | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/Utils/SmartObject.undeclaredMethod.native.phpt diff --git a/tests/Utils/SmartObject.undeclaredMethod.native.phpt b/tests/Utils/SmartObject.undeclaredMethod.native.phpt new file mode 100644 index 000000000..d5d62e120 --- /dev/null +++ b/tests/Utils/SmartObject.undeclaredMethod.native.phpt @@ -0,0 +1,149 @@ +privateMethod(); + } + + + public function callPrivateStatic() + { + static::privateStaticMethod(); + } + + + private function callPrivateParent() + { + } +} + + +class InterClass extends ParentClass +{ + public function callParents() + { + parent::callParents(); + } +} + + +class ChildClass extends InterClass +{ + public function callParents() + { + parent::callParents(); + } + + + public function callMissingParent() + { + parent::callMissingParent(); + } + + + public static function callMissingParentStatic() + { + parent::callMissingParentStatic(); + } + + + public function callPrivateParent() + { + parent::callPrivateParent(); + } + + + protected function protectedMethod() + { + } + + + protected static function protectedStaticMethod() + { + } + + + private function privateMethod() + { + } + + + private static function privateStaticMethod() + { + } +} + + + +Assert::exception(function () { + $obj = new ParentClass; + $obj->undef(); +}, Error::class, 'Call to undefined method ParentClass::undef()'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->undef(); +}, Error::class, 'Call to undefined method ChildClass::undef()'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->callParents(); +}, Error::class, 'Call to undefined method ParentClass::callParents()'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->callMissingParent(); +}, Error::class, 'Call to undefined method InterClass::callMissingParent()'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->callMissingParentStatic(); +}, Error::class, 'Call to undefined method InterClass::callMissingParentStatic()'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj::callMissingParentStatic(); +}, Error::class, 'Call to undefined method InterClass::callMissingParentStatic()'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->callPrivateParent(); +}, Error::class, 'Call to private method ParentClass::callPrivateParent() from scope ChildClass'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->protectedMethod(); +}, Error::class, 'Call to protected method ChildClass::protectedMethod() from global scope'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->protectedStaticMethod(); +}, Error::class, 'Call to protected method ChildClass::protectedStaticMethod() from global scope'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj::protectedStaticMethod(); +}, Error::class, 'Call to protected method ChildClass::protectedStaticMethod() from global scope'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->callPrivate(); +}, Error::class, 'Call to private method ChildClass::privateMethod() from scope ParentClass'); + +Assert::exception(function () { + $obj = new ChildClass; + $obj->callPrivateStatic(); +}, Error::class, 'Call to private method ChildClass::privateStaticMethod() from scope ParentClass'); From 9a663e4aeffdfea14b835b428be5bb3f116f8444 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 28 Jul 2021 12:42:14 +0200 Subject: [PATCH 14/25] used native PHP 8 functions --- src/Utils/Callback.php | 4 ++-- src/Utils/Html.php | 4 ++-- src/Utils/Image.php | 2 +- src/Utils/Strings.php | 6 +++--- src/Utils/Validators.php | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index 1d9d835d2..3cea432c7 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -91,7 +91,7 @@ public static function toReflection($callable): \ReflectionFunctionAbstract $callable = self::unwrap($callable); } - if (is_string($callable) && strpos($callable, '::')) { + if (is_string($callable) && str_contains($callable, '::')) { return new \ReflectionMethod($callable); } elseif (is_array($callable)) { return new \ReflectionMethod($callable[0], $callable[1]); @@ -118,7 +118,7 @@ public static function isStatic(callable $callable): bool public static function unwrap(\Closure $closure): \Closure|array|string { $r = new \ReflectionFunction($closure); - if (substr($r->name, -1) === '}') { + if (str_ends_with($r->name, '}')) { return $closure; } elseif ($obj = $r->getClosureThis()) { diff --git a/src/Utils/Html.php b/src/Utils/Html.php index e18e1c934..b5e9cc831 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -806,14 +806,14 @@ final public function attributes(): string $value = (string) $value; } - $q = strpos($value, '"') === false ? '"' : "'"; + $q = str_contains($value, '"') ? "'" : '"'; $s .= ' ' . $key . '=' . $q . str_replace( ['&', $q, '<'], ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], $value, ) - . (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '') + . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '') . $q; } diff --git a/src/Utils/Image.php b/src/Utils/Image.php index 650976a84..8d4238e8c 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -670,7 +670,7 @@ public function __clone() private static function isPercent(int|string &$num): bool { - if (is_string($num) && substr($num, -1) === '%') { + if (is_string($num) && str_ends_with($num, '%')) { $num = (float) substr($num, 0, -1); return true; } elseif (is_int($num) || $num === (string) (int) $num) { diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index ebf10440b..e3832ea48 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -62,7 +62,7 @@ public static function chr(int $code): string */ public static function startsWith(string $haystack, string $needle): bool { - return strncmp($haystack, $needle, strlen($needle)) === 0; + return str_starts_with($haystack, $needle); } @@ -71,7 +71,7 @@ public static function startsWith(string $haystack, string $needle): bool */ public static function endsWith(string $haystack, string $needle): bool { - return $needle === '' || substr($haystack, -strlen($needle)) === $needle; + return str_ends_with($haystack, $needle); } @@ -80,7 +80,7 @@ public static function endsWith(string $haystack, string $needle): bool */ public static function contains(string $haystack, string $needle): bool { - return strpos($haystack, $needle) !== false; + return str_contains($haystack, $needle); } diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index 645211d51..b9ee50b20 100644 --- a/src/Utils/Validators.php +++ b/src/Utils/Validators.php @@ -131,12 +131,12 @@ public static function assertField( public static function is(mixed $value, string $expected): bool { foreach (explode('|', $expected) as $item) { - if (substr($item, -2) === '[]') { + if (str_ends_with($item, '[]')) { if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { return true; } continue; - } elseif (substr($item, 0, 1) === '?') { + } elseif (str_starts_with($item, '?')) { $item = substr($item, 1); if ($value === null) { return true; From 14327ba2e0e5280378e04f45e975943c42b04aac Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Aug 2021 11:40:10 +0200 Subject: [PATCH 15/25] Reflection: uses PhpToken --- src/Utils/Reflection.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Utils/Reflection.php b/src/Utils/Reflection.php index 1c6aeb642..cb1c10beb 100644 --- a/src/Utils/Reflection.php +++ b/src/Utils/Reflection.php @@ -277,7 +277,7 @@ public static function getUseStatements(\ReflectionClass $class): array private static function parseUseStatements(string $code, string $forClass = null): array { try { - $tokens = token_get_all($code, TOKEN_PARSE); + $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); } catch (\ParseError $e) { trigger_error($e->getMessage(), E_USER_NOTICE); $tokens = []; @@ -289,7 +289,7 @@ private static function parseUseStatements(string $code, string $forClass = null while ($token = current($tokens)) { next($tokens); - switch (is_array($token) ? $token[0] : $token) { + switch ($token->id) { case T_NAMESPACE: $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\'); $uses = []; @@ -342,11 +342,11 @@ private static function parseUseStatements(string $code, string $forClass = null case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: - case '{': + case ord('{'): $level++; break; - case '}': + case ord('}'): if ($level === $classLevel) { $class = $classLevel = null; } @@ -362,10 +362,9 @@ private static function fetch(array &$tokens, string|int|array $take): ?string { $res = null; while ($token = current($tokens)) { - [$token, $s] = is_array($token) ? $token : [$token, $token]; - if (in_array($token, (array) $take, true)) { - $res .= $s; - } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) { + if ($token->is($take)) { + $res .= $token->text; + } elseif (!$token->is([T_DOC_COMMENT, T_WHITESPACE, T_COMMENT])) { break; } next($tokens); From 10d0e43f2b41521610d311fe9ec157f5796a6ccf Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Sep 2021 01:23:10 +0200 Subject: [PATCH 16/25] Reflection: getReturnType(), getParameterType(), getPropertyType() return objects Type (BC break) --- src/Utils/Reflection.php | 33 +++------------ .../Utils/Reflection.getParameterType.81.phpt | 31 +++++--------- tests/Utils/Reflection.getParameterType.phpt | 25 ++++------- .../Utils/Reflection.getPropertyType.81.phpt | 29 ++++--------- tests/Utils/Reflection.getPropertyType.phpt | 23 ++++------ tests/Utils/Reflection.getReturnType.81.phpt | 42 +++++++------------ tests/Utils/Reflection.getReturnType.phpt | 32 ++++++-------- 7 files changed, 69 insertions(+), 146 deletions(-) diff --git a/src/Utils/Reflection.php b/src/Utils/Reflection.php index cb1c10beb..bdfa47fc4 100644 --- a/src/Utils/Reflection.php +++ b/src/Utils/Reflection.php @@ -51,51 +51,30 @@ public static function isClassKeyword(string $name): bool /** * Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names. * If the function does not have a return type, it returns null. - * If the function has union or intersection type, it throws Nette\InvalidStateException. */ - public static function getReturnType(\ReflectionFunctionAbstract $func): ?string + public static function getReturnType(\ReflectionFunctionAbstract $func): ?Type { - $type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null); - return self::getType($func, $type); + return Type::fromReflection($func); } /** * Returns the type of given parameter and normalizes `self` and `parent` to the actual class names. * If the parameter does not have a type, it returns null. - * If the parameter has union or intersection type, it throws Nette\InvalidStateException. */ - public static function getParameterType(\ReflectionParameter $param): ?string + public static function getParameterType(\ReflectionParameter $param): ?Type { - return self::getType($param, $param->getType()); + return Type::fromReflection($param); } /** * Returns the type of given property and normalizes `self` and `parent` to the actual class names. * If the property does not have a type, it returns null. - * If the property has union or intersection type, it throws Nette\InvalidStateException. */ - public static function getPropertyType(\ReflectionProperty $prop): ?string + public static function getPropertyType(\ReflectionProperty $prop): ?Type { - return self::getType($prop, $prop->getType()); - } - - - private static function getType(\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, ?\ReflectionType $type): ?string - { - if ($type === null) { - return null; - - } elseif ($type instanceof \ReflectionNamedType) { - return Type::resolve($type->getName(), $reflection); - - } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { - throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.'); - - } else { - throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection)); - } + return Type::fromReflection($prop); } diff --git a/tests/Utils/Reflection.getParameterType.81.phpt b/tests/Utils/Reflection.getParameterType.81.phpt index 15d75986f..5adb2643c 100644 --- a/tests/Utils/Reflection.getParameterType.81.phpt +++ b/tests/Utils/Reflection.getParameterType.81.phpt @@ -43,28 +43,17 @@ class AExt extends A $method = new ReflectionMethod('A', 'method'); $params = $method->getParameters(); -Assert::same('Undeclared', Reflection::getParameterType($params[0])); -Assert::same('Test\B', Reflection::getParameterType($params[1])); -Assert::same('array', Reflection::getParameterType($params[2])); -Assert::same('callable', Reflection::getParameterType($params[3])); -Assert::same('A', Reflection::getParameterType($params[4])); +Assert::same('Undeclared', (string) Reflection::getParameterType($params[0])); +Assert::same('Test\B', (string) Reflection::getParameterType($params[1])); +Assert::same('array', (string) Reflection::getParameterType($params[2])); +Assert::same('callable', (string) Reflection::getParameterType($params[3])); +Assert::same('A', (string) Reflection::getParameterType($params[4])); Assert::null(Reflection::getParameterType($params[5])); -Assert::same('Test\B', Reflection::getParameterType($params[6])); -Assert::same('mixed', Reflection::getParameterType($params[7])); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[8]); -}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[9]); -}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[10]); -}, Nette\InvalidStateException::class, 'The $intersection in A::method() is not expected to have a union or intersection type.'); - +Assert::same('?Test\B', (string) Reflection::getParameterType($params[6])); +Assert::same('mixed', (string) Reflection::getParameterType($params[7])); +Assert::same('A|array', (string) Reflection::getParameterType($params[8], false)); +Assert::same('A|array|null', (string) Reflection::getParameterType($params[9], false)); $method = new ReflectionMethod('AExt', 'methodExt'); $params = $method->getParameters(); -Assert::same('A', Reflection::getParameterType($params[0])); +Assert::same('A', (string) Reflection::getParameterType($params[0])); diff --git a/tests/Utils/Reflection.getParameterType.phpt b/tests/Utils/Reflection.getParameterType.phpt index 9e00ed6d9..616c67325 100644 --- a/tests/Utils/Reflection.getParameterType.phpt +++ b/tests/Utils/Reflection.getParameterType.phpt @@ -41,25 +41,16 @@ class AExt extends A $method = new ReflectionMethod('A', 'method'); $params = $method->getParameters(); -Assert::same('Undeclared', Reflection::getParameterType($params[0])); -Assert::same('Test\B', Reflection::getParameterType($params[1])); -Assert::same('array', Reflection::getParameterType($params[2])); -Assert::same('callable', Reflection::getParameterType($params[3])); -Assert::same('A', Reflection::getParameterType($params[4])); +Assert::same('Undeclared', (string) Reflection::getParameterType($params[0])); +Assert::same('Test\B', (string) Reflection::getParameterType($params[1])); +Assert::same('array', (string) Reflection::getParameterType($params[2])); +Assert::same('callable', (string) Reflection::getParameterType($params[3])); +Assert::same('A', (string) Reflection::getParameterType($params[4])); Assert::null(Reflection::getParameterType($params[5])); -Assert::same('Test\B', Reflection::getParameterType($params[6])); -Assert::same('mixed', Reflection::getParameterType($params[7])); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[8]); -}, Nette\InvalidStateException::class, 'The $union in A::method() is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($params) { - Reflection::getParameterType($params[9]); -}, Nette\InvalidStateException::class, 'The $nullableUnion in A::method() is not expected to have a union or intersection type.'); - +Assert::same('?Test\B', (string) Reflection::getParameterType($params[6])); +Assert::same('mixed', (string) Reflection::getParameterType($params[7])); $method = new ReflectionMethod('AExt', 'methodExt'); $params = $method->getParameters(); -Assert::same('A', Reflection::getParameterType($params[0])); +Assert::same('A', (string) Reflection::getParameterType($params[0])); diff --git a/tests/Utils/Reflection.getPropertyType.81.phpt b/tests/Utils/Reflection.getPropertyType.81.phpt index d7905d6d6..38d42c4e0 100644 --- a/tests/Utils/Reflection.getPropertyType.81.phpt +++ b/tests/Utils/Reflection.getPropertyType.81.phpt @@ -37,27 +37,16 @@ class AExt extends A $class = new ReflectionClass('A'); $props = $class->getProperties(); -Assert::same('Undeclared', Reflection::getPropertyType($props[0])); -Assert::same('Test\B', Reflection::getPropertyType($props[1])); -Assert::same('array', Reflection::getPropertyType($props[2])); -Assert::same('A', Reflection::getPropertyType($props[3])); +Assert::same('Undeclared', (string) Reflection::getPropertyType($props[0])); +Assert::same('Test\B', (string) Reflection::getPropertyType($props[1])); +Assert::same('array', (string) Reflection::getPropertyType($props[2])); +Assert::same('A', (string) Reflection::getPropertyType($props[3])); Assert::null(Reflection::getPropertyType($props[4])); -Assert::same('Test\B', Reflection::getPropertyType($props[5])); -Assert::same('mixed', Reflection::getPropertyType($props[6])); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[7]); -}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[8]); -}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[9]); -}, Nette\InvalidStateException::class, 'The A::$intersection is not expected to have a union or intersection type.'); - +Assert::same('?Test\B', (string) Reflection::getPropertyType($props[5])); +Assert::same('mixed', (string) Reflection::getPropertyType($props[6])); +Assert::same('A|array', (string) Reflection::getPropertyType($props[7], false)); +Assert::same('A|array|null', (string) Reflection::getPropertyType($props[8], false)); $class = new ReflectionClass('AExt'); $props = $class->getProperties(); -Assert::same('A', Reflection::getPropertyType($props[0])); +Assert::same('A', (string) Reflection::getPropertyType($props[0])); diff --git a/tests/Utils/Reflection.getPropertyType.phpt b/tests/Utils/Reflection.getPropertyType.phpt index 161e4ac7e..44abc0630 100644 --- a/tests/Utils/Reflection.getPropertyType.phpt +++ b/tests/Utils/Reflection.getPropertyType.phpt @@ -35,23 +35,14 @@ class AExt extends A $class = new ReflectionClass('A'); $props = $class->getProperties(); -Assert::same('Undeclared', Reflection::getPropertyType($props[0])); -Assert::same('Test\B', Reflection::getPropertyType($props[1])); -Assert::same('array', Reflection::getPropertyType($props[2])); -Assert::same('A', Reflection::getPropertyType($props[3])); +Assert::same('Undeclared', (string) Reflection::getPropertyType($props[0])); +Assert::same('Test\B', (string) Reflection::getPropertyType($props[1])); +Assert::same('array', (string) Reflection::getPropertyType($props[2])); +Assert::same('A', (string) Reflection::getPropertyType($props[3])); Assert::null(Reflection::getPropertyType($props[4])); -Assert::same('Test\B', Reflection::getPropertyType($props[5])); -Assert::same('mixed', Reflection::getPropertyType($props[6])); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[7]); -}, Nette\InvalidStateException::class, 'The A::$union is not expected to have a union or intersection type.'); - -Assert::exception(function () use ($props) { - Reflection::getPropertyType($props[8]); -}, Nette\InvalidStateException::class, 'The A::$nullableUnion is not expected to have a union or intersection type.'); - +Assert::same('?Test\B', (string) Reflection::getPropertyType($props[5])); +Assert::same('mixed', (string) Reflection::getPropertyType($props[6])); $class = new ReflectionClass('AExt'); $props = $class->getProperties(); -Assert::same('A', Reflection::getPropertyType($props[0])); +Assert::same('A', (string) Reflection::getPropertyType($props[0])); diff --git a/tests/Utils/Reflection.getReturnType.81.phpt b/tests/Utils/Reflection.getReturnType.81.phpt index 704c55dff..c0b8cfb57 100644 --- a/tests/Utils/Reflection.getReturnType.81.phpt +++ b/tests/Utils/Reflection.getReturnType.81.phpt @@ -106,48 +106,38 @@ function intersectionType(): AExt&A Assert::null(Reflection::getReturnType(new \ReflectionMethod(A::class, 'noType'))); -Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType'))); +Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType'))); -Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType'))); +Assert::same('string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType'))); +Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType'))); +Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType'))); -Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType'))); +Assert::same('?Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType'))); -Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType'))); +Assert::same('?string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType'))); +Assert::same('?A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType')); -}, Nette\InvalidStateException::class, 'The A::unionType() is not expected to have a union or intersection type.'); +Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType')); -}, Nette\InvalidStateException::class, 'The A::nullableUnionType() is not expected to have a union or intersection type.'); +Assert::same('A|array|null', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionMethod(A::class, 'intersectionType')); -}, Nette\InvalidStateException::class, 'The A::intersectionType() is not expected to have a union or intersection type.'); +Assert::same('AExt&A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'intersectionType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt'))); +Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt'))); Assert::null(Reflection::getReturnType(new \ReflectionFunction('noType'))); -Assert::same('Test\B', Reflection::getReturnType(new \ReflectionFunction('classType'))); +Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionFunction('classType'))); -Assert::same('string', Reflection::getReturnType(new \ReflectionFunction('nativeType'))); +Assert::same('string', (string) Reflection::getReturnType(new \ReflectionFunction('nativeType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionFunction('unionType')); -}, Nette\InvalidStateException::class, 'The unionType() is not expected to have a union or intersection type.'); +Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionFunction('unionType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionFunction('intersectionType')); -}, Nette\InvalidStateException::class, 'The intersectionType() is not expected to have a union or intersection type.'); +Assert::same('AExt&A', (string) Reflection::getReturnType(new \ReflectionFunction('intersectionType'))); // tentative type -Assert::same('int', Reflection::getReturnType(new \ReflectionMethod(\ArrayObject::class, 'count'))); +Assert::same('int', (string) Reflection::getReturnType(new \ReflectionMethod(\ArrayObject::class, 'count'))); diff --git a/tests/Utils/Reflection.getReturnType.phpt b/tests/Utils/Reflection.getReturnType.phpt index 351d098c4..e4002c5dd 100644 --- a/tests/Utils/Reflection.getReturnType.phpt +++ b/tests/Utils/Reflection.getReturnType.phpt @@ -95,36 +95,30 @@ function unionType(): array|A Assert::null(Reflection::getReturnType(new \ReflectionMethod(A::class, 'noType'))); -Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType'))); +Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'classType'))); -Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType'))); +Assert::same('string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nativeType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType'))); +Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'selfType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType'))); +Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'staticType'))); -Assert::same('Test\B', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType'))); +Assert::same('?Test\B', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableClassType'))); -Assert::same('string', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType'))); +Assert::same('?string', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableNativeType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType'))); +Assert::same('?A', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableSelfType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType')); -}, Nette\InvalidStateException::class, 'The A::unionType() is not expected to have a union or intersection type.'); +Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'unionType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType')); -}, Nette\InvalidStateException::class, 'The A::nullableUnionType() is not expected to have a union or intersection type.'); +Assert::same('A|array|null', (string) Reflection::getReturnType(new \ReflectionMethod(A::class, 'nullableUnionType'))); -Assert::same('A', Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt'))); +Assert::same('A', (string) Reflection::getReturnType(new \ReflectionMethod(AExt::class, 'parentTypeExt'))); Assert::null(Reflection::getReturnType(new \ReflectionFunction('noType'))); -Assert::same('Test\B', Reflection::getReturnType(new \ReflectionFunction('classType'))); +Assert::same('Test\B', (string) Reflection::getReturnType(new \ReflectionFunction('classType'))); -Assert::same('string', Reflection::getReturnType(new \ReflectionFunction('nativeType'))); +Assert::same('string', (string) Reflection::getReturnType(new \ReflectionFunction('nativeType'))); -Assert::exception(function () { - Reflection::getReturnType(new \ReflectionFunction('unionType')); -}, Nette\InvalidStateException::class, 'The unionType() is not expected to have a union or intersection type.'); +Assert::same('A|array', (string) Reflection::getReturnType(new \ReflectionFunction('unionType'))); From 694130e676c1d4702aef97c97d4218633780e843 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 26 Sep 2021 12:34:29 +0200 Subject: [PATCH 17/25] Html: removed $xhtml (BC break) --- src/Utils/Html.php | 12 +++------ tests/Utils/Html.basic.phpt | 50 ++++++++++++++----------------------- 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/src/Utils/Html.php b/src/Utils/Html.php index b5e9cc831..81bad0a4e 100644 --- a/src/Utils/Html.php +++ b/src/Utils/Html.php @@ -238,8 +238,6 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab /** @var array element's attributes */ public $attrs = []; - public static bool $xhtml = false; - /** void elements */ public static $emptyElements = [ 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, @@ -740,7 +738,7 @@ final public function __toString(): string final public function startTag(): string { return $this->name - ? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>') + ? '<' . $this->name . $this->attributes() . '>' : ''; } @@ -771,11 +769,7 @@ final public function attributes(): string continue; } elseif ($value === true) { - if (static::$xhtml) { - $s .= ' ' . $key . '="' . $key . '"'; - } else { - $s .= ' ' . $key; - } + $s .= ' ' . $key; continue; } elseif (is_array($value)) { @@ -810,7 +804,7 @@ final public function attributes(): string $s .= ' ' . $key . '=' . $q . str_replace( ['&', $q, '<'], - ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], + ['&', $q === '"' ? '"' : ''', '<'], $value, ) . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '') diff --git a/tests/Utils/Html.basic.phpt b/tests/Utils/Html.basic.phpt index 6e4f5c1a7..064405e5f 100644 --- a/tests/Utils/Html.basic.phpt +++ b/tests/Utils/Html.basic.phpt @@ -14,63 +14,55 @@ require __DIR__ . '/../bootstrap.php'; test('', function () { - Html::$xhtml = true; $el = Html::el('img')->src('image.gif')->alt(''); - Assert::same('', (string) $el); - Assert::same('', $el->toHtml()); - Assert::same('', $el->startTag()); + Assert::same('', (string) $el); + Assert::same('', $el->toHtml()); + Assert::same('', $el->startTag()); Assert::same('', $el->endTag()); }); test('', function () { - Html::$xhtml = true; $el = Html::el('img')->setAttribute('src', 'image.gif')->setAttribute('alt', ''); - Assert::same('', (string) $el); - Assert::same('', $el->startTag()); + Assert::same('', (string) $el); + Assert::same('', $el->startTag()); Assert::same('', $el->endTag()); }); test('', function () { - Html::$xhtml = true; $el = Html::el('img')->accesskey(0, true)->alt('alt', false); - Assert::same('', (string) $el); - Assert::same('', (string) $el->accesskey(1, true)); - Assert::same('', (string) $el->accesskey(1, false)); - Assert::same('', (string) $el->accesskey(0, true)); - Assert::same('', (string) $el->accesskey(0)); + Assert::same('', (string) $el); + Assert::same('', (string) $el->accesskey(1, true)); + Assert::same('', (string) $el->accesskey(1, false)); + Assert::same('', (string) $el->accesskey(0, true)); + Assert::same('', (string) $el->accesskey(0)); unset($el->accesskey); - Assert::same('', (string) $el); + Assert::same('', (string) $el); }); test('', function () { - Html::$xhtml = true; $el = Html::el('img')->appendAttribute('accesskey', 0)->setAttribute('alt', false); - Assert::same('', (string) $el); - Assert::same('', (string) $el->appendAttribute('accesskey', 1)); - Assert::same('', (string) $el->appendAttribute('accesskey', 1, false)); - Assert::same('', (string) $el->appendAttribute('accesskey', 0)); - Assert::same('', (string) $el->setAttribute('accesskey', 0)); - Assert::same('', (string) $el->removeAttribute('accesskey')); + Assert::same('', (string) $el); + Assert::same('', (string) $el->appendAttribute('accesskey', 1)); + Assert::same('', (string) $el->appendAttribute('accesskey', 1, false)); + Assert::same('', (string) $el->appendAttribute('accesskey', 0)); + Assert::same('', (string) $el->setAttribute('accesskey', 0)); + Assert::same('', (string) $el->removeAttribute('accesskey')); }); test('', function () { $el = Html::el('img')->src('image.gif')->alt('')->setText('any content'); - Assert::same('', (string) $el); - Assert::same('', $el->startTag()); - Assert::same('', $el->endTag()); - - Html::$xhtml = false; Assert::same('', (string) $el); + Assert::same('', $el->startTag()); + Assert::same('', $el->endTag()); }); test('', function () { - Html::$xhtml = false; $el = Html::el('img')->setSrc('image.gif')->setAlt('alt1')->setAlt('alt2'); Assert::same('alt2', (string) $el); Assert::same('image.gif', $el->getSrc()); @@ -104,10 +96,6 @@ test('small & big numbers', function () { test('attributes escaping', function () { - Html::$xhtml = true; - Assert::same('', (string) Html::el('a')->one('"')->two("'")->three('<>')->four('&')); - - Html::$xhtml = false; Assert::same('', (string) Html::el('a')->one('"')->two("'")->three('<>')->four('&')); Assert::same('', (string) Html::el('a')->one('``xx')); // mXSS }); From 5c2c477f784f65a4ca08a8de61e519153a15a7b2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Oct 2021 00:25:02 +0200 Subject: [PATCH 18/25] Strings, Arrays, Json, Image: $flags replaced with parameters --- src/Utils/Arrays.php | 3 ++- src/Utils/Image.php | 30 +++++++++++++-------- src/Utils/Json.php | 28 ++++++++++++------- src/Utils/Strings.php | 42 ++++++++++++++++++++++------- tests/Utils/Arrays.grep().phpt | 5 ++++ tests/Utils/Image.resize.phpt | 40 +++++++++++++++++++++++++++ tests/Utils/Json.decode().phpt | 1 + tests/Utils/Json.encode().phpt | 2 ++ tests/Utils/Strings.match().phpt | 10 ++++--- tests/Utils/Strings.matchAll().phpt | 21 +++++++++++++-- tests/Utils/Strings.split().phpt | 16 +++++++++++ 11 files changed, 161 insertions(+), 37 deletions(-) diff --git a/src/Utils/Arrays.php b/src/Utils/Arrays.php index 81d030936..625bec65e 100644 --- a/src/Utils/Arrays.php +++ b/src/Utils/Arrays.php @@ -190,8 +190,9 @@ public static function renameKey(array &$array, string|int $oldKey, string|int $ * @param string[] $array * @return string[] */ - public static function grep(array $array, string $pattern, int $flags = 0): array + public static function grep(array $array, string $pattern, bool|int $invert = false): array { + $flags = $invert ? PREG_GREP_INVERT : 0; return Strings::pcre('preg_grep', [$pattern, $array, $flags]); } diff --git a/src/Utils/Image.php b/src/Utils/Image.php index 8d4238e8c..d472120d4 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -98,7 +98,7 @@ class Image { use Nette\SmartObject; - /** {@link resize()} only shrinks images */ + /** @deprecated */ public const SHRINK_ONLY = 0b0001; /** {@link resize()} will ignore aspect ratio */ @@ -303,14 +303,19 @@ public function getImageResource(): \GdImage /** * Scales an image. Width and height accept pixels or percent. + * @param self::FIT|self::FILL|self::STRETCH|self::EXACT $mode */ - public function resize(int|string|null $width, int|string|null $height, int $flags = self::FIT): static - { - if ($flags & self::EXACT) { + public function resize( + int|string|null $width, + int|string|null $height, + int $mode = self::FIT, + bool $shrinkOnly = false, + ): static { + if ($mode & self::EXACT) { return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height); } - [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags); + [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode, $shrinkOnly); if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize $newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource(); @@ -338,14 +343,17 @@ public function resize(int|string|null $width, int|string|null $height, int $fla /** * Calculates dimensions of resized image. Width and height accept pixels or percent. + * @param self::FIT|self::FILL|self::STRETCH $mode */ public static function calculateSize( int $srcWidth, int $srcHeight, $newWidth, $newHeight, - int $flags = self::FIT, + int $mode = self::FIT, + bool $shrinkOnly = false, ): array { + $shrinkOnly = $shrinkOnly || ($mode & self::SHRINK_ONLY); // back compatibility if ($newWidth === null) { } elseif (self::isPercent($newWidth)) { $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); @@ -357,17 +365,17 @@ public static function calculateSize( if ($newHeight === null) { } elseif (self::isPercent($newHeight)) { $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); - $flags |= empty($percents) ? 0 : self::STRETCH; + $mode |= empty($percents) ? 0 : self::STRETCH; } else { $newHeight = abs($newHeight); } - if ($flags & self::STRETCH) { // non-proportional + if ($mode & self::STRETCH) { // non-proportional if (!$newWidth || !$newHeight) { throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); } - if ($flags & self::SHRINK_ONLY) { + if ($shrinkOnly) { $newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth)); $newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight)); } @@ -386,11 +394,11 @@ public static function calculateSize( $scale[] = $newHeight / $srcHeight; } - if ($flags & self::FILL) { + if ($mode & self::FILL) { $scale = [max($scale)]; } - if ($flags & self::SHRINK_ONLY) { + if ($shrinkOnly) { $scale[] = 1; } diff --git a/src/Utils/Json.php b/src/Utils/Json.php index 9f06a7f05..6c46d5a48 100644 --- a/src/Utils/Json.php +++ b/src/Utils/Json.php @@ -19,23 +19,32 @@ final class Json { use Nette\StaticClass; + /** @deprecated */ public const FORCE_ARRAY = 0b0001; + /** @deprecated */ public const PRETTY = 0b0010; + /** @deprecated */ public const ESCAPE_UNICODE = 0b0100; /** - * Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity, - * and Json::ESCAPE_UNICODE for ASCII output. + * Converts value to JSON format. Use $pretty for easier reading and clarity and $escapeUnicode for ASCII output. * @throws JsonException */ - public static function encode(mixed $value, int $flags = 0): string - { - $flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE) + public static function encode( + mixed $value, + bool|int $pretty = false, + bool $escapeUnicode = false, + ): string { + if (is_int($pretty)) { // back compatibility + $escapeUnicode = $pretty & self::ESCAPE_UNICODE; + $pretty &= self::PRETTY; + } + $flags = ($escapeUnicode ? 0 : JSON_UNESCAPED_UNICODE) | JSON_UNESCAPED_SLASHES - | ($flags & self::PRETTY ? JSON_PRETTY_PRINT : 0) + | ($pretty ? JSON_PRETTY_PRINT : 0) | (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7 $json = json_encode($value, $flags); @@ -47,13 +56,12 @@ public static function encode(mixed $value, int $flags = 0): string /** - * Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value. + * Parses JSON to PHP value. Parameter $forceArray forces an array instead of an object as the return value. * @throws JsonException */ - public static function decode(string $json, int $flags = 0): mixed + public static function decode(string $json, bool|int $forceArray = false): mixed { - $forceArray = (bool) ($flags & self::FORCE_ARRAY); - $value = json_decode($json, $forceArray, 512, JSON_BIGINT_AS_STRING); + $value = json_decode($json, (bool) $forceArray, 512, JSON_BIGINT_AS_STRING); if ($error = json_last_error()) { throw new JsonException(json_last_error_msg(), $error); } diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index e3832ea48..29e0aa9ac 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -468,20 +468,33 @@ private static function pos(string $haystack, string $needle, int $nth = 1): ?in /** * Splits a string into array by the regular expression. Parenthesized expression in the delimiter are captured. - * Parameter $flags can be any combination of PREG_SPLIT_NO_EMPTY and PREG_OFFSET_CAPTURE flags. */ - public static function split(string $subject, string $pattern, int $flags = 0): array - { + public static function split( + string $subject, + string $pattern, + bool|int $captureOffset = false, + bool $skipEmpty = false, + ): array { + $flags = is_int($captureOffset) && $captureOffset // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]); } /** * Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern. - * Parameter $flags can be any combination of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL flags. */ - public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array - { + public static function match( + string $subject, + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + ): ?array { + $flags = is_int($captureOffset) && $captureOffset // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); if ($offset > strlen($subject)) { return null; } @@ -492,11 +505,20 @@ public static function match(string $subject, string $pattern, int $flags = 0, i /** - * Finds all occurrences matching regular expression pattern and returns a two-dimensional array. Result is array of matches (ie uses by default PREG_SET_ORDER). - * Parameter $flags can be any combination of PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL and PREG_PATTERN_ORDER flags. + * Finds all occurrences matching regular expression pattern and returns a two-dimensional array. + * Result is array of matches (ie uses by default PREG_SET_ORDER). */ - public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array - { + public static function matchAll( + string $subject, + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $patternOrder = false, + ): array { + $flags = is_int($captureOffset) && $captureOffset // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0); if ($offset > strlen($subject)) { return []; } diff --git a/tests/Utils/Arrays.grep().phpt b/tests/Utils/Arrays.grep().phpt index f356405d3..f8613b8ae 100644 --- a/tests/Utils/Arrays.grep().phpt +++ b/tests/Utils/Arrays.grep().phpt @@ -21,3 +21,8 @@ Assert::same([ 0 => 'a', 2 => 'c', ], Arrays::grep(['a', '1', 'c'], '#\d#', PREG_GREP_INVERT)); + +Assert::same([ + 0 => 'a', + 2 => 'c', +], Arrays::grep(['a', '1', 'c'], '#\d#', invert: true)); diff --git a/tests/Utils/Image.resize.phpt b/tests/Utils/Image.resize.phpt index ef8a79887..7b3663cce 100644 --- a/tests/Utils/Image.resize.phpt +++ b/tests/Utils/Image.resize.phpt @@ -52,6 +52,14 @@ test('resizing Y shrink', function () use ($main) { }); +test('resizing Y shrink', function () use ($main) { + $image = clone $main; + $image->resize(null, 150, shrinkOnly: true); + Assert::same(176, $image->width); + Assert::same(104, $image->height); +}); + + test('resizing X Y shrink', function () use ($main) { $image = clone $main; $image->resize(300, 150, Image::SHRINK_ONLY); @@ -60,6 +68,14 @@ test('resizing X Y shrink', function () use ($main) { }); +test('resizing X Y shrink', function () use ($main) { + $image = clone $main; + $image->resize(300, 150, shrinkOnly: true); + Assert::same(176, $image->width); + Assert::same(104, $image->height); +}); + + test('resizing X Y', function () use ($main) { $image = clone $main; $image->resize(300, 150); @@ -84,6 +100,14 @@ test('resizing X Y shrink stretch', function () use ($main) { }); +test('resizing X Y shrink stretch', function () use ($main) { + $image = clone $main; + $image->resize(300, 100, Image::STRETCH, shrinkOnly: true); + Assert::same(176, $image->width); + Assert::same(100, $image->height); +}); + + test('resizing X%', function () use ($main) { $image = clone $main; $image->resize('110%', null); @@ -116,6 +140,14 @@ test('flipping Y shrink', function () use ($main) { }); +test('flipping Y shrink', function () use ($main) { + $image = clone $main; + $image->resize(null, -150, shrinkOnly: true); + Assert::same(176, $image->width); + Assert::same(104, $image->height); +}); + + test('flipping X Y shrink', function () use ($main) { $image = clone $main; $image->resize(-300, -150, Image::SHRINK_ONLY); @@ -124,6 +156,14 @@ test('flipping X Y shrink', function () use ($main) { }); +test('flipping X Y shrink', function () use ($main) { + $image = clone $main; + $image->resize(-300, -150, shrinkOnly: true); + Assert::same(176, $image->width); + Assert::same(104, $image->height); +}); + + test('exact resize', function () use ($main) { $image = clone $main; $image->resize(300, 150, Image::EXACT); diff --git a/tests/Utils/Json.decode().phpt b/tests/Utils/Json.decode().phpt index e9910d26c..b8ffabe43 100644 --- a/tests/Utils/Json.decode().phpt +++ b/tests/Utils/Json.decode().phpt @@ -20,6 +20,7 @@ Assert::null(Json::decode(' null')); Assert::equal((object) ['a' => 1], Json::decode('{"a":1}')); Assert::same(['a' => 1], Json::decode('{"a":1}', Json::FORCE_ARRAY)); +Assert::same(['a' => 1], Json::decode('{"a":1}', forceArray: true)); Assert::exception(function () { diff --git a/tests/Utils/Json.encode().phpt b/tests/Utils/Json.encode().phpt index 9ba173388..5f072c366 100644 --- a/tests/Utils/Json.encode().phpt +++ b/tests/Utils/Json.encode().phpt @@ -36,10 +36,12 @@ Assert::same('"\u2028\u2029"', Json::encode("\u{2028}\u{2029}")); // ESCAPE_UNICODE Assert::same('"/I\u00f1t\u00ebrn\u00e2ti\u00f4n\u00e0liz\u00e6ti\u00f8n"', Json::encode("/I\u{F1}t\u{EB}rn\u{E2}ti\u{F4}n\u{E0}liz\u{E6}ti\u{F8}n", Json::ESCAPE_UNICODE)); Assert::same('"\u2028\u2029"', Json::encode("\u{2028}\u{2029}", Json::ESCAPE_UNICODE)); +Assert::same('"\u2028\u2029"', Json::encode("\u{2028}\u{2029}", escapeUnicode: true)); // JSON_PRETTY_PRINT Assert::same("[\n 1,\n 2,\n 3\n]", Json::encode([1, 2, 3], Json::PRETTY)); +Assert::same("[\n 1,\n 2,\n 3\n]", Json::encode([1, 2, 3], pretty: true)); Assert::exception(function () { diff --git a/tests/Utils/Strings.match().phpt b/tests/Utils/Strings.match().phpt index 5814b5753..5b4196180 100644 --- a/tests/Utils/Strings.match().phpt +++ b/tests/Utils/Strings.match().phpt @@ -20,8 +20,12 @@ Assert::same(['hell', 'l'], Strings::match('hello world!', '#([e-l])+#')); Assert::same(['hell'], Strings::match('hello world!', '#[e-l]+#')); Assert::same([['hell', 0]], Strings::match('hello world!', '#[e-l]+#', PREG_OFFSET_CAPTURE)); +Assert::same([['hell', 0]], Strings::match('hello world!', '#[e-l]+#', captureOffset: true)); -Assert::same(['ll'], Strings::match('hello world!', '#[e-l]+#', 0, 2)); +Assert::same(['e', null], Strings::match('hello world!', '#e(x)*#', unmatchedAsNull: true)); +Assert::same(['e', null], Strings::match('hello world!', '#e(x)*#', 0, 0, unmatchedAsNull: true)); // $flags = 0 -Assert::null(Strings::match('hello world!', '', 0, 50)); -Assert::null(Strings::match('', '', 0, 1)); +Assert::same(['ll'], Strings::match('hello world!', '#[e-l]+#', offset: 2)); + +Assert::null(Strings::match('hello world!', '', offset: 50)); +Assert::null(Strings::match('', '', offset: 1)); diff --git a/tests/Utils/Strings.matchAll().phpt b/tests/Utils/Strings.matchAll().phpt index 384bbc18a..257887f95 100644 --- a/tests/Utils/Strings.matchAll().phpt +++ b/tests/Utils/Strings.matchAll().phpt @@ -32,14 +32,31 @@ Assert::same([ [['k', 14], ['k', 14], ['', 15]], ], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', PREG_OFFSET_CAPTURE)); +Assert::same([ + [['lu', 2], ['l', 2], ['u', 3]], + [['ou', 6], ['o', 6], ['u', 7]], + [['k', 10], ['k', 10], ['', 11]], + [['k', 14], ['k', 14], ['', 15]], +], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', captureOffset: true)); + Assert::same([ [['lu', 2], ['ou', 6], ['k', 10], ['k', 14]], [['l', 2], ['o', 6], ['k', 10], ['k', 14]], [['u', 3], ['u', 7], ['', 11], ['', 15]], ], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER)); -Assert::same([['l'], ['k'], ['k']], Strings::matchAll('žluťoučký kůň', '#[e-l]+#u', 0, 2)); +Assert::same([ + [['lu', 2], ['ou', 6], ['k', 10], ['k', 14]], + [['l', 2], ['o', 6], ['k', 10], ['k', 14]], + [['u', 3], ['u', 7], ['', 11], ['', 15]], +], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', captureOffset: true, patternOrder: true)); + +Assert::same([['l'], ['k'], ['k']], Strings::matchAll('žluťoučký kůň', '#[e-l]+#u', offset: 2)); Assert::same([['ll', 'l']], Strings::matchAll('hello world!', '#[e-l]+#', PREG_PATTERN_ORDER, 2)); +Assert::same([['ll', 'l']], Strings::matchAll('hello world!', '#[e-l]+#', offset: 2, patternOrder: true)); + +Assert::same([['e', null]], Strings::matchAll('hello world!', '#e(x)*#', unmatchedAsNull: true)); +Assert::same([['e', null]], Strings::matchAll('hello world!', '#e(x)*#', 0, 0, unmatchedAsNull: true)); // $flags = 0 -Assert::same([], Strings::matchAll('hello world!', '', 0, 50)); +Assert::same([], Strings::matchAll('hello world!', '', offset: 50)); diff --git a/tests/Utils/Strings.split().phpt b/tests/Utils/Strings.split().phpt index b291e9613..3a57a4c4e 100644 --- a/tests/Utils/Strings.split().phpt +++ b/tests/Utils/Strings.split().phpt @@ -29,6 +29,14 @@ Assert::same([ 'c', ], Strings::split('a, b, c', '#(,)\s*#', PREG_SPLIT_NO_EMPTY)); +Assert::same([ + 'a', + ',', + 'b', + ',', + 'c', +], Strings::split('a, b, c', '#(,)\s*#', skipEmpty: true)); + Assert::same([ ['a', 0], [',', 1], @@ -36,3 +44,11 @@ Assert::same([ [',', 4], ['c', 6], ], Strings::split('a, b, c', '#(,)\s*#', PREG_SPLIT_OFFSET_CAPTURE)); + +Assert::same([ + ['a', 0], + [',', 1], + ['b', 3], + [',', 4], + ['c', 6], +], Strings::split('a, b, c', '#(,)\s*#', captureOffset: true)); From a08d1e467c89745b630bceb68014de7e22a6686d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Oct 2021 15:57:10 +0200 Subject: [PATCH 19/25] Strings::replace() added parameters $captureOffset, $unmatchedAsNull --- src/Utils/Strings.php | 5 ++++- tests/Utils/Strings.replace().phpt | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index 29e0aa9ac..164475574 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -539,12 +539,15 @@ public static function replace( string|array $pattern, string|callable $replacement = '', int $limit = -1, + bool $captureOffset = false, + bool $unmatchedAsNull = false, ): string { if (is_object($replacement) || is_array($replacement)) { if (!is_callable($replacement, false, $textual)) { throw new Nette\InvalidStateException("Callback '$textual' is not callable."); } - return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]); + $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]); } elseif (is_array($pattern) && is_string(key($pattern))) { $replacement = array_values($pattern); diff --git a/tests/Utils/Strings.replace().phpt b/tests/Utils/Strings.replace().phpt index 5dda17322..8510afb4a 100644 --- a/tests/Utils/Strings.replace().phpt +++ b/tests/Utils/Strings.replace().phpt @@ -34,3 +34,7 @@ Assert::same('#@ @@@#d!', Strings::replace('hello world!', [ ])); Assert::same(' !', Strings::replace('hello world!', '#\w#')); Assert::same(' !', Strings::replace('hello world!', ['#\w#'])); + +// flags & callback +Assert::same('hell0o worl9d!', Strings::replace('hello world!', '#[e-l]+#', fn($m) => implode($m[0]), captureOffset: true)); +Strings::replace('hello world!', '#e(x)*#', fn($m) => Assert::null($m[1]), unmatchedAsNull: true); From 619eb3e182650ce6553cc7d7c607e5a0c452b2d2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Oct 2021 13:15:19 +0200 Subject: [PATCH 20/25] Strings::split() added parameter $limit --- src/Utils/Strings.php | 3 ++- tests/Utils/Strings.split().phpt | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index 164475574..c7a8f5872 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -474,11 +474,12 @@ public static function split( string $pattern, bool|int $captureOffset = false, bool $skipEmpty = false, + int $limit = -1, ): array { $flags = is_int($captureOffset) && $captureOffset // back compatibility ? $captureOffset : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); - return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]); + return self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); } diff --git a/tests/Utils/Strings.split().phpt b/tests/Utils/Strings.split().phpt index 3a57a4c4e..9638b2710 100644 --- a/tests/Utils/Strings.split().phpt +++ b/tests/Utils/Strings.split().phpt @@ -52,3 +52,5 @@ Assert::same([ [',', 4], ['c', 6], ], Strings::split('a, b, c', '#(,)\s*#', captureOffset: true)); + +Assert::same(['a', ',', 'b, c'], Strings::split('a, b, c', '#(,)\s*#', limit: 2)); From 11747d7934c33326b58820d9b16728977b44be41 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Oct 2021 01:21:23 +0200 Subject: [PATCH 21/25] Strings: added support for UTF8 offsets in regexp --- src/Utils/Strings.php | 60 +++++++++++++++++++++++++++-- tests/Utils/Strings.match().phpt | 13 ++++++- tests/Utils/Strings.matchAll().phpt | 17 ++++++++ tests/Utils/Strings.replace().phpt | 6 +++ tests/Utils/Strings.split().phpt | 30 ++++++++++++--- 5 files changed, 114 insertions(+), 12 deletions(-) diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index c7a8f5872..2a2928293 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -475,11 +475,17 @@ public static function split( bool|int $captureOffset = false, bool $skipEmpty = false, int $limit = -1, + bool $utf8 = false, ): array { $flags = is_int($captureOffset) && $captureOffset // back compatibility ? $captureOffset : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); - return self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); + $pattern .= $utf8 ? 'u' : ''; + $m = self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); + if ($utf8 && ($flags & PREG_SPLIT_OFFSET_CAPTURE)) { + return self::bytesToChars($subject, [$m])[0]; + } + return $m; } @@ -492,16 +498,25 @@ public static function match( bool|int $captureOffset = false, int $offset = 0, bool $unmatchedAsNull = false, + bool $utf8 = false, ): ?array { $flags = is_int($captureOffset) && $captureOffset // back compatibility ? $captureOffset : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } if ($offset > strlen($subject)) { return null; } - return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset]) - ? $m - : null; + if (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) { + return null; + } + if ($utf8 && ($flags & PREG_OFFSET_CAPTURE)) { + return self::bytesToChars($subject, [$m])[0]; + } + return $m; } @@ -516,10 +531,15 @@ public static function matchAll( int $offset = 0, bool $unmatchedAsNull = false, bool $patternOrder = false, + bool $utf8 = false, ): array { $flags = is_int($captureOffset) && $captureOffset // back compatibility ? $captureOffset : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0); + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } if ($offset > strlen($subject)) { return []; } @@ -528,6 +548,9 @@ public static function matchAll( ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), $offset, ]); + if ($utf8 && ($flags & PREG_OFFSET_CAPTURE)) { + return self::bytesToChars($subject, $m); + } return $m; } @@ -542,12 +565,19 @@ public static function replace( int $limit = -1, bool $captureOffset = false, bool $unmatchedAsNull = false, + bool $utf8 = false, ): string { if (is_object($replacement) || is_array($replacement)) { if (!is_callable($replacement, false, $textual)) { throw new Nette\InvalidStateException("Callback '$textual' is not callable."); } $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + if ($utf8) { + $pattern .= 'u'; + if ($captureOffset) { + $replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]); + } + } return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]); } elseif (is_array($pattern) && is_string(key($pattern))) { @@ -555,10 +585,32 @@ public static function replace( $pattern = array_keys($pattern); } + if ($utf8) { + $pattern = array_map(fn($item) => $item . 'u', (array) $pattern); + } + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); } + private static function bytesToChars(string $s, array $groups): array + { + $lastBytes = $lastChars = 0; + foreach ($groups as &$matches) { + foreach ($matches as &$match) { + if ($match[1] > $lastBytes) { + $lastChars += self::length(substr($s, $lastBytes, $match[1] - $lastBytes)); + } elseif ($match[1] < $lastBytes) { + $lastChars -= self::length(substr($s, $match[1], $lastBytes - $match[1])); + } + $lastBytes = $match[1]; + $match[1] = $lastChars; + } + } + return $groups; + } + + /** @internal */ public static function pcre(string $func, array $args) { diff --git a/tests/Utils/Strings.match().phpt b/tests/Utils/Strings.match().phpt index 5b4196180..34799a885 100644 --- a/tests/Utils/Strings.match().phpt +++ b/tests/Utils/Strings.match().phpt @@ -19,13 +19,22 @@ Assert::same(['hell', 'l'], Strings::match('hello world!', '#([e-l])+#')); Assert::same(['hell'], Strings::match('hello world!', '#[e-l]+#')); -Assert::same([['hell', 0]], Strings::match('hello world!', '#[e-l]+#', PREG_OFFSET_CAPTURE)); -Assert::same([['hell', 0]], Strings::match('hello world!', '#[e-l]+#', captureOffset: true)); +Assert::same([['l', 2]], Strings::match('žluťoučký kůň', '#[e-l]+#u', PREG_OFFSET_CAPTURE)); +Assert::same([['l', 2]], Strings::match('žluťoučký kůň', '#[e-l]+#u', captureOffset: true)); +Assert::same([['l', 1]], Strings::match('žluťoučký kůň', '#[e-l]+#u', captureOffset: true, utf8: true)); Assert::same(['e', null], Strings::match('hello world!', '#e(x)*#', unmatchedAsNull: true)); Assert::same(['e', null], Strings::match('hello world!', '#e(x)*#', 0, 0, unmatchedAsNull: true)); // $flags = 0 Assert::same(['ll'], Strings::match('hello world!', '#[e-l]+#', offset: 2)); +Assert::same(['l'], Strings::match('žluťoučký kůň', '#[e-l]+#u', offset: 2)); + +Assert::same(['k'], Strings::match('žluťoučký kůň', '#[e-l]+#u', utf8: true, offset: 2)); + +Assert::same(['žluťoučký'], Strings::match('žluťoučký kůň', '#\w+#', utf8: true)); // without modifier + +Assert::same([['k', 7]], Strings::match('žluťoučký kůň', '#[e-l]+#u', captureOffset: true, utf8: true, offset: 2)); + Assert::null(Strings::match('hello world!', '', offset: 50)); Assert::null(Strings::match('', '', offset: 1)); diff --git a/tests/Utils/Strings.matchAll().phpt b/tests/Utils/Strings.matchAll().phpt index 257887f95..002b43599 100644 --- a/tests/Utils/Strings.matchAll().phpt +++ b/tests/Utils/Strings.matchAll().phpt @@ -45,14 +45,31 @@ Assert::same([ [['u', 3], ['u', 7], ['', 11], ['', 15]], ], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER)); +Assert::same([ + [['lu', 1], ['l', 1], ['u', 2]], + [['ou', 4], ['o', 4], ['u', 5]], + [['k', 7], ['k', 7], ['', 8]], + [['k', 10], ['k', 10], ['', 11]], +], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', captureOffset: true, utf8: true)); + Assert::same([ [['lu', 2], ['ou', 6], ['k', 10], ['k', 14]], [['l', 2], ['o', 6], ['k', 10], ['k', 14]], [['u', 3], ['u', 7], ['', 11], ['', 15]], ], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', captureOffset: true, patternOrder: true)); +Assert::same([ + [['lu', 1], ['ou', 4], ['k', 7], ['k', 10]], + [['l', 1], ['o', 4], ['k', 7], ['k', 10]], + [['u', 2], ['u', 5], ['', 8], ['', 11]], +], Strings::matchAll('žluťoučký kůň!', '#([a-z])([a-z]*)#u', captureOffset: true, patternOrder: true, utf8: true)); + Assert::same([['l'], ['k'], ['k']], Strings::matchAll('žluťoučký kůň', '#[e-l]+#u', offset: 2)); +Assert::same([['k'], ['k']], Strings::matchAll('žluťoučký kůň', '#[e-l]+#u', offset: 2, utf8: true)); + +Assert::same([['žluťoučký'], ['kůň']], Strings::matchAll('žluťoučký kůň', '#\w+#', utf8: true)); // without modifier + Assert::same([['ll', 'l']], Strings::matchAll('hello world!', '#[e-l]+#', PREG_PATTERN_ORDER, 2)); Assert::same([['ll', 'l']], Strings::matchAll('hello world!', '#[e-l]+#', offset: 2, patternOrder: true)); diff --git a/tests/Utils/Strings.replace().phpt b/tests/Utils/Strings.replace().phpt index 8510afb4a..047efeb11 100644 --- a/tests/Utils/Strings.replace().phpt +++ b/tests/Utils/Strings.replace().phpt @@ -37,4 +37,10 @@ Assert::same(' !', Strings::replace('hello world!', ['#\w#'])); // flags & callback Assert::same('hell0o worl9d!', Strings::replace('hello world!', '#[e-l]+#', fn($m) => implode($m[0]), captureOffset: true)); +Assert::same('žl1uťoučk7ý k10ůň!', Strings::replace('žluťoučký kůň!', '#[e-l]+#u', fn($m) => implode($m[0]), captureOffset: true, utf8: true)); Strings::replace('hello world!', '#e(x)*#', fn($m) => Assert::null($m[1]), unmatchedAsNull: true); + +// utf-8 without modifier +Assert::same('* *', Strings::replace('žluťoučký kůň', '#\w+#', fn() => '*', utf8: true)); +Assert::same('* *', Strings::replace('žluťoučký kůň', '#\w+#', '*', utf8: true)); +Assert::same('* *', Strings::replace('žluťoučký kůň', ['#\w+#'], '*', utf8: true)); diff --git a/tests/Utils/Strings.split().phpt b/tests/Utils/Strings.split().phpt index 9638b2710..d888ffca9 100644 --- a/tests/Utils/Strings.split().phpt +++ b/tests/Utils/Strings.split().phpt @@ -46,11 +46,29 @@ Assert::same([ ], Strings::split('a, b, c', '#(,)\s*#', PREG_SPLIT_OFFSET_CAPTURE)); Assert::same([ - ['a', 0], - [',', 1], - ['b', 3], - [',', 4], - ['c', 6], -], Strings::split('a, b, c', '#(,)\s*#', captureOffset: true)); + ['ž', 0], + ['lu', 2], + ['ť', 4], + ['ou', 6], + ['č', 8], + ['k', 10], + ['ý ', 11], + ['k', 14], + ['ůň', 15], +], Strings::split('žluťoučký kůň', '#([a-z]+)\s*#u', captureOffset: true)); + +Assert::same([ + ['ž', 0], + ['lu', 1], + ['ť', 3], + ['ou', 4], + ['č', 6], + ['k', 7], + ['ý ', 8], + ['k', 10], + ['ůň', 11], +], Strings::split('žluťoučký kůň', '#([a-z]+)\s*#u', captureOffset: true, utf8: true)); + +Assert::same(['', ' ', ''], Strings::split('žluťoučký kůň', '#\w+#', utf8: true)); // without modifier Assert::same(['a', ',', 'b, c'], Strings::split('a, b, c', '#(,)\s*#', limit: 2)); From 6cd4758f7c59593b24fd6ec6549104c9f51fe59d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Oct 2021 14:51:53 +0200 Subject: [PATCH 22/25] SmartObject: supports property deprecation --- src/SmartObject.php | 19 +++++++++++++++++-- src/Utils/ObjectHelpers.php | 4 ++-- tests/Utils/SmartObject.property.phpt | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/SmartObject.php b/src/SmartObject.php index 5c7735de9..48f938117 100644 --- a/src/SmartObject.php +++ b/src/SmartObject.php @@ -64,7 +64,14 @@ public function &__get(string $name): mixed if (!($prop & 0b0001)) { throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); } - $m = ($prop & 0b0010 ? 'get' : 'is') . $name; + $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } if ($prop & 0b0100) { // return by reference return $this->$m(); } else { @@ -91,7 +98,15 @@ public function __set(string $name, mixed $value): void if (!($prop & 0b1000)) { throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); } - $this->{'set' . $name}($value); + $m = 'set' . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + $this->$m($value); } else { ObjectHelpers::strictSet($class, $name); diff --git a/src/Utils/ObjectHelpers.php b/src/Utils/ObjectHelpers.php index 57bb70bad..6ec7928d1 100644 --- a/src/Utils/ObjectHelpers.php +++ b/src/Utils/ObjectHelpers.php @@ -130,7 +130,7 @@ public static function getMagicProperties(string $class): array $rc = new \ReflectionClass($class); preg_match_all( - '~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER, @@ -147,7 +147,7 @@ public static function getMagicProperties(string $class): array && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); if ($read || $write) { - $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3; + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4; } } diff --git a/tests/Utils/SmartObject.property.phpt b/tests/Utils/SmartObject.property.phpt index 66237506f..6d258e835 100644 --- a/tests/Utils/SmartObject.property.phpt +++ b/tests/Utils/SmartObject.property.phpt @@ -16,6 +16,7 @@ require __DIR__ . '/../bootstrap.php'; * @property int $bar * @property int $bazz * @property int $s + * @property-deprecated int $depr */ class TestClass { @@ -69,6 +70,16 @@ class TestClass echo __METHOD__; return 'ERROR'; } + + + public function setDepr($value) + { + } + + + public function getDepr() + { + } } @@ -115,3 +126,15 @@ Assert::same('World', $obj->bar); Assert::exception(function () use ($obj) { $val = $obj->bazz; }, Nette\MemberAccessException::class, 'Cannot read a write-only property TestClass::$bazz.'); + + +// deprecated property +Assert::error(function () { + $obj = new TestClass; + $obj->depr = 10; +}, E_USER_DEPRECATED, 'Property TestClass::$depr is deprecated, use TestClass::setDepr() method in %a%SmartObject.property.phpt on line %d%.'); + +Assert::error(function () { + $obj = new TestClass; + $val = $obj->depr; +}, E_USER_DEPRECATED, 'Property TestClass::$depr is deprecated, use TestClass::getDepr() method in %a%SmartObject.property.phpt on line %d%.'); From a828903f85bb513e51ba664b44b61f20d812cf20 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Nov 2021 23:15:10 +0100 Subject: [PATCH 23/25] added Strings::ord() --- src/Utils/Strings.php | 16 ++++++++++++++ tests/Utils/Strings.ord().phpt | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/Utils/Strings.ord().phpt diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index 2a2928293..26dfe932a 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -57,6 +57,22 @@ public static function chr(int $code): string } + /** + * Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF). + */ + public static function ord(string $c): int + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + $tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c); + if (!$tmp) { + throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".'); + } + return unpack('N', $tmp)[1]; + } + + /** * Starts the $haystack string with the prefix $needle? */ diff --git a/tests/Utils/Strings.ord().phpt b/tests/Utils/Strings.ord().phpt new file mode 100644 index 000000000..09f6ca2fd --- /dev/null +++ b/tests/Utils/Strings.ord().phpt @@ -0,0 +1,38 @@ + Date: Tue, 16 Nov 2021 14:45:46 +0100 Subject: [PATCH 24/25] Translator: changed interface, accepts and returns string|Stringable [Closes #231] --- src/Translator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Translator.php b/src/Translator.php index e8c5f7d06..f973f5f15 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -18,7 +18,7 @@ interface Translator /** * Translates the given string. */ - function translate(mixed $message, mixed ...$parameters): string; + function translate(string|\Stringable $message, mixed ...$parameters): string|\Stringable; } From 838ae949876b52f4a327942513ba6ec2df277bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Fri, 19 Nov 2021 14:16:55 +0100 Subject: [PATCH 25/25] Added Nette\Utils\Future for lazy value evaluation --- src/Utils/Future.php | 129 +++++++++++++++++++++++ src/Utils/exceptions.php | 19 ++++ tests/Utils/Future.phpt | 216 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+) create mode 100644 src/Utils/Future.php create mode 100644 tests/Utils/Future.phpt diff --git a/src/Utils/Future.php b/src/Utils/Future.php new file mode 100644 index 000000000..3a2ecc764 --- /dev/null +++ b/src/Utils/Future.php @@ -0,0 +1,129 @@ +resolver = $resolver; + } + + + final public function setDefaultValueFactory(callable $cb): static + { + $this->defaultValueFactory = $cb; + return $this; + } + + + final public function bind(int|string|null $key, &$destination): static + { + if ($key !== null) { + $this->destinations[$key][] = &$destination; + } + return $this; + } + + + final public function bindVar(int|string|null &$v): static + { + return $this->bind($v, $v); + } + + + final public function bindArrayValues(array &$values): static + { + foreach ($values as &$destination) { + $this->bind($destination, $destination); + } + return $this; + } + + + final public function bindArrayKeys(array &$keys): static + { + foreach ($keys as $key => &$destination) { + $this->bind($key, $destination); + } + return $this; + } + + + final public function bindArraysKey(string $key, array &$arrays): static + { + foreach ($arrays as &$item) { + assert(is_array($item)); + $this->bind($item[$key], $item[$key]); + } + return $this; + } + + + final public function bindObjectsProperty(string $property, array $objects): static + { + foreach ($objects as $item) { + assert(is_object($item)); + $this->bind($item->{$property}, $item->{$property}); + } + return $this; + } + + + /** + * @throws Nette\UnexpectedValueException if resolver returns value of unexpected type + * @throws FutureException if resolver does not return all required values + */ + final public function resolve(): void + { + if (count($this->destinations) < 1) { + return; + } + + $values = ($this->resolver)(array_keys($this->destinations)); + if ($values instanceof \Traversable) { + $values = iterator_to_array($values); + + } elseif (!is_array($values)) { + throw new Nette\UnexpectedValueException("Resolver returned '" . get_debug_type($values) . "' but array or Traversable expected."); + } + + if ($this->defaultValueFactory === null && count($diff = array_diff_key($this->destinations, $values))) { + throw (new FutureException('Resolver did not return required items.'))->setMissingKeys(array_keys($diff)); + } + + foreach ($this->destinations as $key => $destinations) { + $value = array_key_exists($key, $values) + ? $values[$key] + : ($this->defaultValueFactory)($key); + + foreach ($destinations as &$destination) { + $destination = $value; + } + + unset($this->destinations[$key]); + } + } +} diff --git a/src/Utils/exceptions.php b/src/Utils/exceptions.php index c45e54306..d04bf5fe2 100644 --- a/src/Utils/exceptions.php +++ b/src/Utils/exceptions.php @@ -56,3 +56,22 @@ class RegexpException extends \Exception class AssertionException extends \Exception { } + + +class FutureException extends \Exception +{ + private array $missingKeys = []; + + + public function setMissingKeys(array $keys): static + { + $this->missingKeys = $keys; + return $this; + } + + + public function getMissingKeys(): array + { + return $this->missingKeys; + } +} diff --git a/tests/Utils/Future.phpt b/tests/Utils/Future.phpt new file mode 100644 index 000000000..2dfeaf437 --- /dev/null +++ b/tests/Utils/Future.phpt @@ -0,0 +1,216 @@ + 'one', + 'a' => 'AAA', + }; + } + return $result; +}; + + +test('bind()', function () use ($resolver) { + (new Future($resolver)) + ->bind(1, $n) + ->bind('a', $a) + ->resolve(); + + Assert::same('one', $n); + Assert::same('AAA', $a); +}); + + +test('bindVar()', function () use ($resolver) { + $n = 1; + $a = 'a'; + + (new Future($resolver)) + ->bindVar($n) + ->bindVar($a) + ->resolve(); + + Assert::same('one', $n); + Assert::same('AAA', $a); +}); + + +test('bindArrayValues()', function () use ($resolver) { + $a = [1, 'a']; + + (new Future($resolver)) + ->bindArrayValues($a) + ->resolve(); + + Assert::same(['one', 'AAA'], $a);; +}); + + +test('bindArrayKeys()', function () use ($resolver) { + $a = [ + 1 => null, + 'a' => null, + ]; + + (new Future($resolver)) + ->bindArrayKeys($a) + ->resolve(); + + Assert::same([ + 1 => 'one', + 'a' => 'AAA', + ], $a); +}); + + +test('bindArraysKey()', function () use ($resolver) { + $a = [ + ['k' => 1], + ['k' => 'a'], + ]; + + (new Future($resolver)) + ->bindArraysKey('k', $a) + ->resolve(); + + Assert::same([ + ['k' => 'one'], + ['k' => 'AAA'], + ], $a); +}); + + +test('bindObjectsProperty()', function () use ($resolver) { + $a = [ + (object) ['p' => 1], + (object) ['p' => 'a'], + ]; + + (new Future($resolver)) + ->bindObjectsProperty('p', $a) + ->resolve(); + + Assert::equal([ + (object) ['p' => 'one'], + (object) ['p' => 'AAA'], + ], $a); +}); + + +test('Resolver returns Traversable.', function () { + (new Future(fn (array $keys) => new \ArrayIterator([1 => 'one']))) + ->bind(1, $n) + ->resolve(); + + Assert::same('one', $n); +}); + + + +test('bindArraysKey() invalid item', function () { + Assert::throws(function () { + $a = [ + 123 # this should be an array + ]; + + (new Future(fn() => null)) + ->bindArraysKey('k', $a) + ->resolve(); + + }, \AssertionError::class, 'assert(is_array($item))'); +}); + + +test('bindObjectsProperty() invalid item', function () { + Assert::throws(function () { + $a = [ + 123 # this should be an object + ]; + + (new Future(fn() => null)) + ->bindObjectsProperty('p', $a) + ->resolve(); + + }, \AssertionError::class, 'assert(is_object($item))'); +}); + + +test('Invalid resolver return type.', function () { + Assert::throws(function () { + (new Future(fn() => null)) + ->bind(1, $n) + ->resolve(); + + }, Nette\UnexpectedValueException::class, "Resolver returned 'null' but array or Traversable expected."); +}); + + +test('Resolver does not return all values.', function () { + $a = 123; + $b = 456; + $c = 789; + + try { + (new Future(fn() => ['a' => 'AAA', 'c' => 'CCC'])) + ->bind('a', $a) + ->bind('b', $b) + ->bind('c', $c) + ->resolve(); + + throw new Exception('Should not be there.'); + + } catch (Nette\Utils\FutureException $e) { + Assert::same($e->getMessage(), 'Resolver did not return required items.'); + } + + Assert::same(['b'], $e->getMissingKeys()); + + # Bound variables stay unchanged + Assert::same(123, $a); + Assert::same(456, $b); + Assert::same(789, $c); +}); + + +test('Default value for missing resolver items.', function () { + $a = 123; + $b = 456; + $c = 789; + + (new Future(fn() => ['a' => 'AAA', 'c' => 'CCC'])) + ->setDefaultValueFactory(fn($key) => "DEFAULT-$key") + ->bind('a', $a) + ->bind('b', $b) + ->bind('c', $c) + ->resolve(); + + + Assert::same('AAA', $a); + Assert::same('DEFAULT-b', $b); + Assert::same('CCC', $c); +}); + + +test('Resolver is not called when no variable is bound.', function () { + $resolver = function () { + throw new Exception('Should not be called.'); + }; + + (new Future($resolver)) + ->resolve(); +});