diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml
index a8099a358..60a545e2d 100644
--- a/.github/workflows/coding-style.yml
+++ b/.github/workflows/coding-style.yml
@@ -7,7 +7,7 @@ jobs:
name: Nette Code Checker
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
@@ -21,7 +21,7 @@ jobs:
name: Nette Coding Standard
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 25e44dd05..1b855cdb7 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -7,7 +7,7 @@ jobs:
name: PHPStan
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index f80fe1d1b..8aaab4b13 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -14,7 +14,7 @@ jobs:
name: PHP ${{ matrix.php }} tests on ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
@@ -34,7 +34,7 @@ jobs:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
diff --git a/readme.md b/readme.md
index 530915f8f..d7528c09c 100644
--- a/readme.md
+++ b/readme.md
@@ -1,5 +1,4 @@
-Nette Utility Classes
-=====================
+[![Nette Utils](https://github.com/nette/utils/assets/194960/c33fdb74-0652-4cad-ac6e-c1ce0d29e32a)](https://doc.nette.org/en/utils)
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils)
[![Tests](https://github.com/nette/utils/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/utils/actions)
@@ -11,25 +10,27 @@ Nette Utility Classes
Introduction
------------
-In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use:
-
-- [Arrays](https://doc.nette.org/utils/arrays) - manipulate arrays
-- [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
-- [Date and Time](https://doc.nette.org/utils/datetime) - modify times and dates
-- [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
-- [Finder](https://doc.nette.org/utils/finder) - finds files and directories
-- [Helper Functions](https://doc.nette.org/utils/helpers)
-- [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
-- [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
-- [JSON](https://doc.nette.org/utils/json) - encoding and decoding
-- [Generating Random Strings](https://doc.nette.org/utils/random)
-- [Paginator](https://doc.nette.org/utils/paginator) - pagination math
-- [PHP Reflection](https://doc.nette.org/utils/reflection)
-- [Strings](https://doc.nette.org/utils/strings) - useful text functions
-- [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
-- [Validation](https://doc.nette.org/utils/validators) - validate inputs
-- [Type](https://doc.nette.org/utils/type) - PHP data type
-
+In package nette/utils you will find a set of useful classes for everyday use:
+
+✅ [Arrays](https://doc.nette.org/utils/arrays)
+✅ [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
+✅ [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
+✅ [Finder](https://doc.nette.org/utils/finder) - finds files and directories
+✅ [Floats](https://doc.nette.org/utils/floats) - floating point numbers
+✅ [Helper Functions](https://doc.nette.org/utils/helpers)
+✅ [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
+✅ [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
+✅ [Iterables](https://doc.nette.org/utils/iterables)
+✅ [JSON](https://doc.nette.org/utils/json) - encoding and decoding
+✅ [Generating Random Strings](https://doc.nette.org/utils/random)
+✅ [Paginator](https://doc.nette.org/utils/paginator) - pagination math
+✅ [PHP Reflection](https://doc.nette.org/utils/reflection)
+✅ [Strings](https://doc.nette.org/utils/strings) - useful text functions
+✅ [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
+✅ [Validation](https://doc.nette.org/utils/validators) - validate inputs
+✅ [Type](https://doc.nette.org/utils/type) - PHP data type
+
+
Installation
------------
@@ -40,11 +41,9 @@ The recommended way to install is via Composer:
composer require nette/utils
```
-- Nette Utils 4.0 is compatible with PHP 8.0 to 8.3
-- 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
+Nette Utils 4.0 is compatible with PHP 8.0 to 8.3.
+
+
[Support Me](https://github.com/sponsors/dg)
--------------------------------------------
diff --git a/src/Iterators/CachingIterator.php b/src/Iterators/CachingIterator.php
index 18b1397da..02bd74070 100644
--- a/src/Iterators/CachingIterator.php
+++ b/src/Iterators/CachingIterator.php
@@ -31,26 +31,12 @@ class CachingIterator extends \CachingIterator implements \Countable
private int $counter = 0;
- public function __construct($iterator)
+ public function __construct(iterable|\stdClass $iterable)
{
- if (is_array($iterator) || $iterator instanceof \stdClass) {
- $iterator = new \ArrayIterator($iterator);
-
- } elseif ($iterator instanceof \IteratorAggregate) {
- do {
- $iterator = $iterator->getIterator();
- } while ($iterator instanceof \IteratorAggregate);
-
- assert($iterator instanceof \Iterator);
-
- } elseif ($iterator instanceof \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, get_debug_type($iterator)));
- }
-
- parent::__construct($iterator, 0);
+ $iterable = $iterable instanceof \stdClass
+ ? new \ArrayIterator($iterable)
+ : Nette\Utils\Iterables::toIterator($iterable);
+ parent::__construct($iterable, 0);
}
diff --git a/src/Iterators/Mapper.php b/src/Iterators/Mapper.php
index 87823baf9..284da29da 100644
--- a/src/Iterators/Mapper.php
+++ b/src/Iterators/Mapper.php
@@ -10,9 +10,8 @@
namespace Nette\Iterators;
-
/**
- * Applies the callback to the elements of the inner iterator.
+ * @deprecated use Nette\Utils\Iterables::map()
*/
class Mapper extends \IteratorIterator
{
diff --git a/src/Iterators/MemoizingIterator.php b/src/Iterators/MemoizingIterator.php
new file mode 100644
index 000000000..3d4f937d6
--- /dev/null
+++ b/src/Iterators/MemoizingIterator.php
@@ -0,0 +1,77 @@
+
+ */
+class MemoizingIterator implements \Iterator
+{
+ private array $cache = [];
+ private int $index = 0;
+
+
+ /**
+ * @param \Iterator $inner
+ */
+ public function __construct(
+ private readonly \Iterator $inner,
+ ) {
+ }
+
+
+ public function rewind(): void
+ {
+ if (!$this->cache) {
+ $this->inner->rewind();
+ }
+ $this->index = 0;
+ }
+
+
+ /**
+ * @return V
+ */
+ public function current(): mixed
+ {
+ return $this->cache[$this->index][1] ?? null;
+ }
+
+
+ /**
+ * @return K
+ */
+ public function key(): mixed
+ {
+ return $this->cache[$this->index][0] ?? null;
+ }
+
+
+ public function next(): void
+ {
+ if (!isset($this->cache[++$this->index])) {
+ $this->inner->next();
+ }
+ }
+
+
+ public function valid(): bool
+ {
+ if (!isset($this->cache[$this->index]) && $this->inner->valid()) {
+ $this->cache[$this->index] = [$this->inner->key(), $this->inner->current()];
+ }
+ return isset($this->cache[$this->index]);
+ }
+}
diff --git a/src/Utils/Arrays.php b/src/Utils/Arrays.php
index c33fb9a57..497f5f11b 100644
--- a/src/Utils/Arrays.php
+++ b/src/Utils/Arrays.php
@@ -369,11 +369,11 @@ public static function pick(array &$array, string|int $key, mixed $default = nul
/**
* Tests whether at least one element in the array passes the test implemented by the provided function,
- * which has the signature `function ($value, $key, array $array): bool`.
- * @template K
+ * which has the signature `function (mixed $value, int|string $key, array $array): bool`.
+ * @template K of array-key
* @template V
- * @param iterable $array
- * @param callable(V, K, ($array is array ? array : iterable)): bool $predicate
+ * @param array $array
+ * @param callable(V, K, array): bool $predicate
*/
public static function some(iterable $array, callable $predicate): bool
{
@@ -389,11 +389,11 @@ public static function some(iterable $array, callable $predicate): bool
/**
* Tests whether all elements in the array pass the test implemented by the provided function,
- * which has the signature `function ($value, $key, array $array): bool`.
- * @template K
+ * which has the signature `function (mixed $value, int|string $key, array $array): bool`.
+ * @template K of array-key
* @template V
- * @param iterable $array
- * @param callable(V, K, ($array is array ? array : iterable)): bool $predicate
+ * @param array $array
+ * @param callable(V, K, array): bool $predicate
*/
public static function every(iterable $array, callable $predicate): bool
{
@@ -430,12 +430,12 @@ public static function filter(array $array, callable $predicate): array
/**
* Returns an array containing the original keys and results of applying the given transform function to each element.
- * The function has signature `function ($value, $key, array $array): mixed`.
+ * The function has signature `function (mixed $value, int|string $key, array $array): mixed`.
* @template K of array-key
* @template V
* @template R
- * @param iterable $array
- * @param callable(V, K, ($array is array ? array : iterable)): R $transformer
+ * @param array $array
+ * @param callable(V, K, array): R $transformer
* @return array
*/
public static function map(iterable $array, callable $transformer): array
diff --git a/src/Utils/Cast.php b/src/Utils/Cast.php
new file mode 100644
index 000000000..ff21a6ec4
--- /dev/null
+++ b/src/Utils/Cast.php
@@ -0,0 +1,99 @@
+ $value,
+ is_int($value) => $value !== 0,
+ is_float($value) => $value !== 0.0,
+ is_string($value) => $value !== '' && $value !== '0',
+ default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to bool.'),
+ };
+ }
+
+
+ public static function int(mixed $value): int
+ {
+ return match (true) {
+ is_bool($value) => (int) $value,
+ is_int($value) => $value,
+ is_float($value) => $value === (float) ($tmp = (int) $value)
+ ? $tmp
+ : throw new TypeError('Cannot cast ' . self::string($value) . ' to int.'),
+ is_string($value) => preg_match('~^-?\d+(\.0*)?$~D', $value)
+ ? (int) $value
+ : throw new TypeError("Cannot cast '$value' to int."),
+ default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to int.'),
+ };
+ }
+
+
+ public static function float(mixed $value): float
+ {
+ return match (true) {
+ is_bool($value) => $value ? 1.0 : 0.0,
+ is_int($value) => (float) $value,
+ is_float($value) => $value,
+ is_string($value) => preg_match('~^-?\d+(\.\d*)?$~D', $value)
+ ? (float) $value
+ : throw new TypeError("Cannot cast '$value' to float."),
+ default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to float.'),
+ };
+ }
+
+
+ public static function string(mixed $value): string
+ {
+ return match (true) {
+ is_bool($value) => $value ? '1' : '0',
+ is_int($value) => (string) $value,
+ is_float($value) => str_contains($tmp = (string) $value, '.') ? $tmp : $tmp . '.0',
+ is_string($value) => $value,
+ default => throw new TypeError('Cannot cast ' . get_debug_type($value) . ' to string.'),
+ };
+ }
+
+
+ public static function boolOrNull(mixed $value): ?bool
+ {
+ return $value === null ? null : self::bool($value);
+ }
+
+
+ public static function intOrNull(mixed $value): ?int
+ {
+ return $value === null ? null : self::int($value);
+ }
+
+
+ public static function floatOrNull(mixed $value): ?float
+ {
+ return $value === null ? null : self::float($value);
+ }
+
+
+ public static function stringOrNull(mixed $value): ?string
+ {
+ return $value === null ? null : self::string($value);
+ }
+}
diff --git a/src/Utils/Html.php b/src/Utils/Html.php
index fc0e3ef2a..9a963f0d1 100644
--- a/src/Utils/Html.php
+++ b/src/Utils/Html.php
@@ -577,6 +577,9 @@ final public function getText(): string
*/
final public function addHtml(mixed $child): static
{
+ if (!$child instanceof HtmlStringable) {
+ $child = (string) $child;
+ }
return $this->insert(null, $child);
}
diff --git a/src/Utils/Image.php b/src/Utils/Image.php
index eb728f62b..d2947c723 100644
--- a/src/Utils/Image.php
+++ b/src/Utils/Image.php
@@ -163,10 +163,7 @@ public static function rgb(int $red, int $green, int $blue, int $transparency =
*/
public static function fromFile(string $file, ?int &$type = null): static
{
- if (!extension_loaded('gd')) {
- throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
- }
-
+ self::ensureExtension();
$type = self::detectTypeFromFile($file);
if (!$type) {
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
@@ -183,10 +180,7 @@ public static function fromFile(string $file, ?int &$type = null): static
*/
public static function fromString(string $s, ?int &$type = null): static
{
- if (!extension_loaded('gd')) {
- throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
- }
-
+ self::ensureExtension();
$type = self::detectTypeFromString($s);
if (!$type) {
throw new UnknownImageFileException('Unknown type of image.');
@@ -221,10 +215,7 @@ private static function invokeSafe(string $func, string $arg, string $message, s
*/
public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static
{
- if (!extension_loaded('gd')) {
- throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
- }
-
+ self::ensureExtension();
if ($width < 1 || $height < 1) {
throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
}
@@ -308,6 +299,7 @@ public static function typeToMimeType(int $type): string
*/
public static function isTypeSupported(int $type): bool
{
+ self::ensureExtension();
return (bool) (imagetypes() & match ($type) {
ImageType::JPEG => IMG_JPG,
ImageType::PNG => IMG_PNG,
@@ -323,6 +315,7 @@ public static function isTypeSupported(int $type): bool
/** @return ImageType[] */
public static function getSupportedTypes(): array
{
+ self::ensureExtension();
$flag = imagetypes();
return array_filter([
$flag & IMG_GIF ? ImageType::GIF : null,
@@ -640,6 +633,7 @@ public static function calculateTextBox(
array $options = [],
): array
{
+ self::ensureExtension();
$box = imagettfbbox($size, $angle, $fontFile, $text, $options);
return [
'left' => $minX = min([$box[0], $box[2], $box[4], $box[6]]),
@@ -826,4 +820,12 @@ public function resolveColor(ImageColor|array $color): int
$color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color);
return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color);
}
+
+
+ private static function ensureExtension(): void
+ {
+ if (!extension_loaded('gd')) {
+ throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
+ }
+ }
}
diff --git a/src/Utils/Iterables.php b/src/Utils/Iterables.php
index a2a0b53ec..9e8517df0 100644
--- a/src/Utils/Iterables.php
+++ b/src/Utils/Iterables.php
@@ -156,4 +156,64 @@ public static function map(iterable $iterable, callable $transformer): \Generato
yield $k => $transformer($v, $k, $iterable);
}
}
+
+
+ /**
+ * Wraps around iterator and caches its keys and values during iteration.
+ * This allows the data to be re-iterated multiple times.
+ * @template K
+ * @template V
+ * @param iterable $iterable
+ * @return \IteratorAggregate
+ */
+ public static function memoize(iterable $iterable): iterable
+ {
+ return new class (self::toIterator($iterable)) implements \IteratorAggregate {
+ public function __construct(
+ private iterable $iterable,
+ private array $cache = [],
+ ) {
+ }
+
+
+ public function getIterator(): \Generator
+ {
+ if (!$this->cache){
+ $this->iterable->rewind();
+ }
+ $i = 0;
+ while (true) {
+ if (isset($this->cache[$i])) {
+ [$k, $v] = $this->cache[$i];
+ } elseif ($this->iterable->valid()) {
+ $k = $this->iterable->key();
+ $v = $this->iterable->current();
+ $this->iterable->next();
+ $this->cache[$i] = [$k, $v];
+ } else {
+ break;
+ }
+ yield $k => $v;
+ $i++;
+ }
+ }
+ };
+ }
+
+
+ /**
+ * Creates an iterator from anything that is iterable.
+ * @template K
+ * @template V
+ * @param iterable $iterable
+ * @return \Iterator
+ */
+ public static function toIterator(iterable $iterable): \Iterator
+ {
+ return match (true) {
+ $iterable instanceof \Iterator => $iterable,
+ $iterable instanceof \IteratorAggregate => self::toIterator($iterable->getIterator()),
+ is_array($iterable) => new \ArrayIterator($iterable),
+ };
+ }
}
diff --git a/tests/Iterators/CachingIterator.construct.phpt b/tests/Iterators/CachingIterator.construct.phpt
index a5948c1f9..615d88d54 100644
--- a/tests/Iterators/CachingIterator.construct.phpt
+++ b/tests/Iterators/CachingIterator.construct.phpt
@@ -86,7 +86,7 @@ test('object', function () {
Assert::exception(function () {
$arr = dir('.');
foreach (new Iterators\CachingIterator($arr) as $k => $v);
- }, InvalidArgumentException::class, null);
+ }, TypeError::class, null);
});
diff --git a/tests/Iterators/MemoizingIterator.phpt b/tests/Iterators/MemoizingIterator.phpt
new file mode 100644
index 000000000..7e4e74a58
--- /dev/null
+++ b/tests/Iterators/MemoizingIterator.phpt
@@ -0,0 +1,77 @@
+ 'apple';
+ yield ['b'] => ['banana'];
+ yield 'c' => 'cherry';
+}
+
+
+test('iteration', function () {
+ $iterator = new MemoizingIterator(iterator());
+
+ $pairs = [];
+ foreach ($iterator as $key => $value) {
+ $pairs[] = [$key, $value];
+ }
+ Assert::same(
+ [
+ ['a', 'apple'],
+ [['b'], ['banana']],
+ ['c', 'cherry'],
+ ],
+ $pairs,
+ );
+});
+
+
+test('re-iteration', function () {
+ $iterator = new MemoizingIterator(iterator());
+
+ foreach ($iterator as $value);
+
+ $pairs = [];
+ foreach ($iterator as $key => $value) {
+ $pairs[] = [$key, $value];
+ }
+ Assert::same(
+ [
+ ['a', 'apple'],
+ [['b'], ['banana']],
+ ['c', 'cherry'],
+ ],
+ $pairs,
+ );
+});
+
+
+test('nested re-iteration', function () { // nefunguje
+ $iterator = new MemoizingIterator(iterator());
+
+ $pairs = [];
+ foreach ($iterator as $key => $value) {
+ $pairs[] = [$key, $value];
+ foreach ($iterator as $value);
+ }
+ Assert::same(
+ [
+ ['a', 'apple'],
+ [['b'], ['banana']],
+ ['c', 'cherry'],
+ ],
+ $pairs,
+ );
+});
diff --git a/tests/Utils/Cast.phpt b/tests/Utils/Cast.phpt
new file mode 100644
index 000000000..fa15ca8f6
--- /dev/null
+++ b/tests/Utils/Cast.phpt
@@ -0,0 +1,139 @@
+ Cast::bool([]),
+ TypeError::class,
+ 'Cannot cast array to bool.',
+);
+Assert::exception(
+ fn() => Cast::bool(null),
+ TypeError::class,
+ 'Cannot cast null to bool.',
+);
+
+
+// int
+Assert::same(0, Cast::int(false));
+Assert::same(1, Cast::int(true));
+Assert::same(0, Cast::int(0));
+Assert::same(1, Cast::int(1));
+Assert::exception(
+ fn() => Cast::int(PHP_INT_MAX + 1),
+ TypeError::class,
+ 'Cannot cast 9.2233720368548E+18 to int.',
+);
+Assert::same(0, Cast::int(0.0));
+Assert::same(1, Cast::int(1.0));
+Assert::exception(
+ fn() => Cast::int(0.1),
+ TypeError::class,
+ 'Cannot cast 0.1 to int.',
+);
+Assert::exception(
+ fn() => Cast::int(''),
+ TypeError::class,
+ "Cannot cast '' to int.",
+);
+Assert::same(0, Cast::int('0'));
+Assert::same(1, Cast::int('1'));
+Assert::same(-1, Cast::int('-1.'));
+Assert::same(1, Cast::int('1.0000'));
+Assert::exception(
+ fn() => Cast::int('0.1'),
+ TypeError::class,
+ "Cannot cast '0.1' to int.",
+);
+Assert::exception(
+ fn() => Cast::int([]),
+ TypeError::class,
+ 'Cannot cast array to int.',
+);
+Assert::exception(
+ fn() => Cast::int(null),
+ TypeError::class,
+ 'Cannot cast null to int.',
+);
+
+
+// float
+Assert::same(0.0, Cast::float(false));
+Assert::same(1.0, Cast::float(true));
+Assert::same(0.0, Cast::float(0));
+Assert::same(1.0, Cast::float(1));
+Assert::same(0.0, Cast::float(0.0));
+Assert::same(1.0, Cast::float(1.0));
+Assert::same(0.1, Cast::float(0.1));
+Assert::exception(
+ fn() => Cast::float(''),
+ TypeError::class,
+ "Cannot cast '' to float.",
+);
+Assert::same(0.0, Cast::float('0'));
+Assert::same(1.0, Cast::float('1'));
+Assert::same(-1.0, Cast::float('-1.'));
+Assert::same(1.0, Cast::float('1.0'));
+Assert::same(0.1, Cast::float('0.1'));
+Assert::exception(
+ fn() => Cast::float([]),
+ TypeError::class,
+ 'Cannot cast array to float.',
+);
+Assert::exception(
+ fn() => Cast::float(null),
+ TypeError::class,
+ 'Cannot cast null to float.',
+);
+
+
+// string
+Assert::same('0', Cast::string(false)); // differs from PHP strict casting
+Assert::same('1', Cast::string(true));
+Assert::same('0', Cast::string(0));
+Assert::same('1', Cast::string(1));
+Assert::same('0.0', Cast::string(0.0)); // differs from PHP strict casting
+Assert::same('1.0', Cast::string(1.0)); // differs from PHP strict casting
+Assert::same('-0.1', Cast::string(-0.1));
+Assert::same('9.2233720368548E+18', Cast::string(PHP_INT_MAX + 1));
+Assert::same('', Cast::string(''));
+Assert::same('x', Cast::string('x'));
+Assert::exception(
+ fn() => Cast::string([]),
+ TypeError::class,
+ 'Cannot cast array to string.',
+);
+Assert::exception(
+ fn() => Cast::string(null),
+ TypeError::class,
+ 'Cannot cast null to string.',
+);
+
+
+// OrNull
+Assert::true(Cast::boolOrNull(true));
+Assert::null(Cast::boolOrNull(null));
+Assert::same(0, Cast::intOrNull(0));
+Assert::null(Cast::intOrNull(null));
+Assert::same(0.0, Cast::floatOrNull(0));
+Assert::null(Cast::floatOrNull(null));
+Assert::same('0', Cast::stringOrNull(0));
+Assert::null(Cast::stringOrNull(null));
diff --git a/tests/Utils/Finder.filters.phpt b/tests/Utils/Finder.filters.phpt
index 786bc7104..b5be8d33c 100644
--- a/tests/Utils/Finder.filters.phpt
+++ b/tests/Utils/Finder.filters.phpt
@@ -76,13 +76,13 @@ test('custom filter', function () {
});
-test('custom filter', function () {
- function filter(FileInfo $file)
- {
- return $file->getBaseName() === 'file.txt';
- }
+function filter(FileInfo $file)
+{
+ return $file->getBaseName() === 'file.txt';
+}
+test('custom filter', function () {
$finder = Finder::findFiles('*')
->from('fixtures.finder')
->filter('filter');
diff --git a/tests/Utils/Iterables.memoize().phpt b/tests/Utils/Iterables.memoize().phpt
new file mode 100644
index 000000000..47b2c94e6
--- /dev/null
+++ b/tests/Utils/Iterables.memoize().phpt
@@ -0,0 +1,77 @@
+ 'apple';
+ yield ['b'] => ['banana'];
+ yield 'c' => 'cherry';
+}
+
+
+test('iteration', function () {
+ $iterator = Iterables::memoize(iterator());
+
+ $pairs = [];
+ foreach ($iterator as $key => $value) {
+ $pairs[] = [$key, $value];
+ }
+ Assert::same(
+ [
+ ['a', 'apple'],
+ [['b'], ['banana']],
+ ['c', 'cherry'],
+ ],
+ $pairs,
+ );
+});
+
+
+test('re-iteration', function () {
+ $iterator = Iterables::memoize(iterator());
+
+ foreach ($iterator as $value);
+
+ $pairs = [];
+ foreach ($iterator as $key => $value) {
+ $pairs[] = [$key, $value];
+ }
+ Assert::same(
+ [
+ ['a', 'apple'],
+ [['b'], ['banana']],
+ ['c', 'cherry'],
+ ],
+ $pairs,
+ );
+});
+
+
+test('nested re-iteration', function () {
+ $iterator = Iterables::memoize(iterator());
+
+ $pairs = [];
+ foreach ($iterator as $key => $value) {
+ $pairs[] = [$key, $value];
+ foreach ($iterator as $value);
+ }
+ Assert::same(
+ [
+ ['a', 'apple'],
+ [['b'], ['banana']],
+ ['c', 'cherry'],
+ ],
+ $pairs,
+ );
+});
diff --git a/tests/Utils/Iterables.toIterator().phpt b/tests/Utils/Iterables.toIterator().phpt
new file mode 100644
index 000000000..1b92bef73
--- /dev/null
+++ b/tests/Utils/Iterables.toIterator().phpt
@@ -0,0 +1,56 @@
+ $v) {
+ $tmp[] = "$k => $v";
+ }
+
+ Assert::same([
+ '0 => Nette',
+ '1 => Framework',
+ ], $tmp);
+});
+
+
+test('Iterator', function () {
+ $arr = new ArrayIterator(['Nette', 'Framework']);
+ $tmp = [];
+ foreach (Iterables::toIterator($arr) as $k => $v) {
+ $tmp[] = "$k => $v";
+ }
+
+ Assert::same([
+ '0 => Nette',
+ '1 => Framework',
+ ], $tmp);
+});
+
+
+test('IteratorAggregate', function () {
+ $arr = new ArrayObject(['Nette', 'Framework']);
+ Assert::type(ArrayIterator::class, Iterables::toIterator($arr));
+
+ $tmp = [];
+ foreach (Iterables::toIterator($arr) as $k => $v) {
+ $tmp[] = "$k => $v";
+ }
+
+ Assert::same([
+ '0 => Nette',
+ '1 => Framework',
+ ], $tmp);
+});
diff --git a/tests/Utils/Type.fromReflection.function.82.phpt b/tests/Utils/Type.fromReflection.function.82.phpt
index 5a70e9da6..d1f527edb 100644
--- a/tests/Utils/Type.fromReflection.function.82.phpt
+++ b/tests/Utils/Type.fromReflection.function.82.phpt
@@ -54,5 +54,5 @@ Assert::same('int', (string) $type);
// disjunctive normal form
-$type = Type::fromReflection(new ReflectionFunction(function (): (Bar & Foo)|string|int|null {}));
+$type = Type::fromReflection(new ReflectionFunction(function (): (Bar & Foo) | string | int | null {}));
Assert::same('(Bar&Foo)|string|int|null', (string) $type);