From d42d358ba239add71e3cbff9e95ef6af7cb14d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mor=C3=A1vek?= <petr@pada.cz> Date: Tue, 28 Mar 2023 17:54:07 +0200 Subject: [PATCH 1/4] improved type annotations (#290) --- src/Utils/ArrayHash.php | 10 ++++++---- src/Utils/ArrayList.php | 4 +++- src/Utils/Image.php | 10 +++++++--- src/Utils/Paginator.php | 25 +++++++++++++++++-------- src/Utils/Reflection.php | 2 +- src/Utils/Strings.php | 2 ++ src/Utils/Validators.php | 1 + 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Utils/ArrayHash.php b/src/Utils/ArrayHash.php index 46cad2ba0..95bf73cef 100644 --- a/src/Utils/ArrayHash.php +++ b/src/Utils/ArrayHash.php @@ -15,6 +15,8 @@ /** * Provides objects to work as array. * @template T + * @implements \RecursiveArrayIterator<array-key, T> + * @implements \ArrayAccess<array-key, T> */ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate { @@ -57,7 +59,7 @@ public function count(): int /** * Replaces or appends a item. - * @param string|int $key + * @param array-key $key * @param T $value */ public function offsetSet($key, $value): void @@ -72,7 +74,7 @@ public function offsetSet($key, $value): void /** * Returns a item. - * @param string|int $key + * @param array-key $key * @return T */ #[\ReturnTypeWillChange] @@ -84,7 +86,7 @@ public function offsetGet($key) /** * Determines whether a item exists. - * @param string|int $key + * @param array-key $key */ public function offsetExists($key): bool { @@ -94,7 +96,7 @@ public function offsetExists($key): bool /** * Removes the element from this list. - * @param string|int $key + * @param array-key $key */ public function offsetUnset($key): void { diff --git a/src/Utils/ArrayList.php b/src/Utils/ArrayList.php index de9220922..1c78ef74f 100644 --- a/src/Utils/ArrayList.php +++ b/src/Utils/ArrayList.php @@ -15,6 +15,8 @@ /** * Provides the base class for a generic list (items can be accessed by index). * @template T + * @implements \IteratorAggregate<int, T> + * @implements \ArrayAccess<int, T> */ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate { @@ -26,7 +28,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate /** * Transforms array to ArrayList. - * @param array<T> $array + * @param list<T> $array * @return static */ public static function from(array $array) diff --git a/src/Utils/Image.php b/src/Utils/Image.php index e2f714de5..b354c336b 100644 --- a/src/Utils/Image.php +++ b/src/Utils/Image.php @@ -90,8 +90,8 @@ * @method void stringUp($font, $x, $y, string $s, $col) * @method void trueColorToPalette(bool $dither, $ncolors) * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text) - * @property-read int $width - * @property-read int $height + * @property-read positive-int $width + * @property-read positive-int $height * @property-read resource|\GdImage $imageResource */ class Image @@ -205,6 +205,8 @@ private static function invokeSafe(string $func, string $arg, string $message, s /** * Creates a new true color image of the given dimensions. The default color is black. + * @param positive-int $width + * @param positive-int $height * @return static * @throws Nette\NotSupportedException if gd extension is not loaded */ @@ -301,6 +303,7 @@ public function __construct($image) /** * Returns image width. + * @return positive-int */ public function getWidth(): int { @@ -310,6 +313,7 @@ public function getWidth(): int /** * Returns image height. + * @return positive-int */ public function getHeight(): int { @@ -536,7 +540,7 @@ 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 - * @param int $opacity 0..100 + * @param int<0, 100> $opacity 0..100 * @return static */ public function place(self $image, $left = 0, $top = 0, int $opacity = 100) diff --git a/src/Utils/Paginator.php b/src/Utils/Paginator.php index 4517a8cc3..ded2fcccc 100644 --- a/src/Utils/Paginator.php +++ b/src/Utils/Paginator.php @@ -18,17 +18,17 @@ * @property int $page * @property-read int $firstPage * @property-read int|null $lastPage - * @property-read int $firstItemOnPage - * @property-read int $lastItemOnPage + * @property-read int<0,max> $firstItemOnPage + * @property-read int<0,max> $lastItemOnPage * @property int $base * @property-read bool $first * @property-read bool $last - * @property-read int|null $pageCount - * @property int $itemsPerPage - * @property int|null $itemCount - * @property-read int $offset - * @property-read int|null $countdownOffset - * @property-read int $length + * @property-read int<0,max>|null $pageCount + * @property positive-int $itemsPerPage + * @property int<0,max>|null $itemCount + * @property-read int<0,max> $offset + * @property-read int<0,max>|null $countdownOffset + * @property-read int<0,max> $length */ class Paginator { @@ -89,6 +89,7 @@ public function getLastPage(): ?int /** * Returns the sequence number of the first element on the page + * @return int<0, max> */ public function getFirstItemOnPage(): int { @@ -100,6 +101,7 @@ public function getFirstItemOnPage(): int /** * Returns the sequence number of the last element on the page + * @return int<0, max> */ public function getLastItemOnPage(): int { @@ -129,6 +131,7 @@ public function getBase(): int /** * Returns zero-based page number. + * @return int<0, max> */ protected function getPageIndex(): int { @@ -161,6 +164,7 @@ public function isLast(): bool /** * Returns the total number of pages. + * @return int<0, max>|null */ public function getPageCount(): ?int { @@ -183,6 +187,7 @@ public function setItemsPerPage(int $itemsPerPage) /** * Returns the number of items to display on a single page. + * @return positive-int */ public function getItemsPerPage(): int { @@ -203,6 +208,7 @@ public function setItemCount(?int $itemCount = null) /** * Returns the total number of items. + * @return int<0, max>|null */ public function getItemCount(): ?int { @@ -212,6 +218,7 @@ public function getItemCount(): ?int /** * Returns the absolute index of the first item on current page. + * @return int<0, max> */ public function getOffset(): int { @@ -221,6 +228,7 @@ public function getOffset(): int /** * Returns the absolute index of the first item on current page in countdown paging. + * @return int<0, max>|null */ public function getCountdownOffset(): ?int { @@ -232,6 +240,7 @@ public function getCountdownOffset(): ?int /** * Returns the number of items on current page. + * @return int<0, max> */ public function getLength(): int { diff --git a/src/Utils/Reflection.php b/src/Utils/Reflection.php index dffd87ffa..275775830 100644 --- a/src/Utils/Reflection.php +++ b/src/Utils/Reflection.php @@ -283,7 +283,7 @@ public static function expandClassName(string $name, \ReflectionClass $context): } - /** @return array of [alias => class] */ + /** @return array<string, class-string> of [alias => class] */ public static function getUseStatements(\ReflectionClass $class): array { if ($class->isAnonymous()) { diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index 15c7a147f..302b3f0e0 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -376,6 +376,7 @@ public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS) /** * Pads a UTF-8 string to given length by prepending the $pad string to the beginning. + * @param non-empty-string $pad */ public static function padLeft(string $s, int $length, string $pad = ' '): string { @@ -387,6 +388,7 @@ public static function padLeft(string $s, int $length, string $pad = ' '): strin /** * Pads UTF-8 string to given length by appending the $pad string to the end. + * @param non-empty-string $pad */ public static function padRight(string $s, int $length, string $pad = ' '): string { diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index c71bedc80..a39a8cc52 100644 --- a/src/Utils/Validators.php +++ b/src/Utils/Validators.php @@ -284,6 +284,7 @@ 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 + * @return ($value is list ? true : false) */ public static function isList($value): bool { From 07c000693d08bb34b79dfc8f75c02fb888db6546 Mon Sep 17 00:00:00 2001 From: David Grudl <david@grudl.com> Date: Sun, 30 Jul 2023 14:31:35 +0200 Subject: [PATCH 2/4] Callback::unwrap() returns correct class name for private methods --- src/Utils/Callback.php | 5 +++-- tests/Utils/Callback.closure.phpt | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Utils/Callback.php b/src/Utils/Callback.php index 04e886a6f..1833fa4a1 100644 --- a/src/Utils/Callback.php +++ b/src/Utils/Callback.php @@ -168,13 +168,14 @@ public static function isStatic(callable $callable): bool public static function unwrap(\Closure $closure) { $r = new \ReflectionFunction($closure); + $class = $r->getClosureScopeClass(); if (substr($r->name, -1) === '}') { return $closure; - } elseif ($obj = $r->getClosureThis()) { + } elseif (($obj = $r->getClosureThis()) && $class && get_class($obj) === $class->name) { return [$obj, $r->name]; - } elseif ($class = $r->getClosureScopeClass()) { + } elseif ($class) { return [$class->name, $r->name]; } else { diff --git a/tests/Utils/Callback.closure.phpt b/tests/Utils/Callback.closure.phpt index 1495b970c..937dabcbc 100644 --- a/tests/Utils/Callback.closure.phpt +++ b/tests/Utils/Callback.closure.phpt @@ -156,6 +156,8 @@ test('object methods', function () { Assert::same('Test::privateFun', getName(Callback::toReflection([$test, 'privateFun']))); Assert::same('Test::privateFun', getName(Callback::toReflection($test->createPrivateClosure()))); + + Assert::same(['Test', 'privateFun'], Callback::unwrap((new TestChild)->createPrivateClosure())); Assert::same('Test::privateFun', getName(Callback::toReflection((new TestChild)->createPrivateClosure()))); Assert::same('Test::privateFun*', $test->createPrivateClosure()->__invoke('*')); From a4175c62652f2300c8017fb7e640f9ccb11648d2 Mon Sep 17 00:00:00 2001 From: David Grudl <david@grudl.com> Date: Sun, 30 Jul 2023 17:29:59 +0200 Subject: [PATCH 3/4] support for PHP 8.3 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- src/Utils/Reflection.php | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0cf0e238c..5dd7d837d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] fail-fast: false diff --git a/composer.json b/composer.json index ec4efb0e4..7e881052c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.2 <8.3" + "php": ">=7.2 <8.4" }, "require-dev": { "nette/tester": "~2.0", diff --git a/readme.md b/readme.md index 430cc7359..5317b8e65 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ The recommended way to install is via Composer: composer require nette/utils ``` -- Nette Utils 3.2 is compatible with PHP 7.2 to 8.2 +- Nette Utils 3.2 is compatible with PHP 7.2 to 8.3 - 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 - Nette Utils 2.5 is compatible with PHP 5.6 to 8.0 diff --git a/src/Utils/Reflection.php b/src/Utils/Reflection.php index 275775830..1ee207a4c 100644 --- a/src/Utils/Reflection.php +++ b/src/Utils/Reflection.php @@ -316,7 +316,8 @@ private static function parseUseStatements(string $code, ?string $forClass = nul $tokens = []; } - $namespace = $class = $classLevel = $level = null; + $namespace = $class = null; + $classLevel = $level = 0; $res = $uses = []; $nameTokens = PHP_VERSION_ID < 80000 @@ -387,7 +388,7 @@ private static function parseUseStatements(string $code, ?string $forClass = nul case '}': if ($level === $classLevel) { - $class = $classLevel = null; + $class = $classLevel = 0; } $level--; From b433959cb129b9d4f15d26b9138c7d2b62a3d757 Mon Sep 17 00:00:00 2001 From: David Grudl <david@grudl.com> Date: Tue, 29 Aug 2023 23:55:41 +0200 Subject: [PATCH 4/4] StaticClass: constructor is private [Closes nette/di#292] - ReflectionClass::isInstance() returns false - it is marked as an error in the IDE --- src/StaticClass.php | 6 ++---- tests/Utils/StaticClass.phpt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/StaticClass.php b/src/StaticClass.php index 8fed0ef31..3f48c0c92 100644 --- a/src/StaticClass.php +++ b/src/StaticClass.php @@ -16,12 +16,10 @@ trait StaticClass { /** - * @return never - * @throws \Error + * Class is static and cannot be instantiated. */ - final public function __construct() + private function __construct() { - throw new \Error('Class ' . static::class . ' is static and cannot be instantiated.'); } diff --git a/tests/Utils/StaticClass.phpt b/tests/Utils/StaticClass.phpt index ef119ee29..bcab6f540 100644 --- a/tests/Utils/StaticClass.phpt +++ b/tests/Utils/StaticClass.phpt @@ -22,7 +22,7 @@ class TestClass Assert::exception(function () { new TestClass; -}, Error::class, 'Class TestClass is static and cannot be instantiated.'); +}, Error::class, 'Call to private TestClass::__construct() %a%'); Assert::exception(function () { TestClass::methodA();