From 862fb3ed74e42bb219aa293abcc655c98f5071f8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 May 2024 13:02:23 +0200 Subject: [PATCH 01/11] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) 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 From d3d796c4976bb026e611527be24810d7adc72d71 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 27 Mar 2024 19:08:12 +0100 Subject: [PATCH 02/11] Image: GD extension detection added --- src/Utils/Image.php | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) 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.'); + } + } } From fd325ea8e0b79ae0bbbfdea58e400fa67071abaa Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 30 Nov 2023 12:20:02 +0100 Subject: [PATCH 03/11] Arrays::some(), every(), map() for iteratos & Mapper should be replaced by Iterators --- src/Iterators/Mapper.php | 3 +-- src/Utils/Arrays.php | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) 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/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 From cf73271a474f6482641ced3ddaf78c3d44d9e6dc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 May 2024 13:10:58 +0200 Subject: [PATCH 04/11] cs --- tests/Utils/Finder.filters.phpt | 10 +++++----- tests/Utils/Type.fromReflection.function.82.phpt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) 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/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); From a6a81d8eac8b1874c5f43346da3fff1d0c2e903d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 May 2024 12:47:40 +0200 Subject: [PATCH 05/11] added Iterables::toIterator() --- src/Utils/Iterables.php | 17 ++++++++ tests/Utils/Iterables.toIterator().phpt | 56 +++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 tests/Utils/Iterables.toIterator().phpt diff --git a/src/Utils/Iterables.php b/src/Utils/Iterables.php index a2a0b53ec..e64f8f134 100644 --- a/src/Utils/Iterables.php +++ b/src/Utils/Iterables.php @@ -156,4 +156,21 @@ public static function map(iterable $iterable, callable $transformer): \Generato yield $k => $transformer($v, $k, $iterable); } } + + + /** + * 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/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); +}); From 75fd4b1fc0ac7745e12ad14597b4e08bf05ef660 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 May 2024 12:47:40 +0200 Subject: [PATCH 06/11] added Iterables::memoize() --- src/Utils/Iterables.php | 43 ++++++++++++++++ tests/Utils/Iterables.memoize().phpt | 77 ++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tests/Utils/Iterables.memoize().phpt diff --git a/src/Utils/Iterables.php b/src/Utils/Iterables.php index e64f8f134..9e8517df0 100644 --- a/src/Utils/Iterables.php +++ b/src/Utils/Iterables.php @@ -158,6 +158,49 @@ public static function map(iterable $iterable, callable $transformer): \Generato } + /** + * 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 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, + ); +}); From 838a3cb33451206b2bcd51c1f01fc54cb93c5e03 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 May 2024 13:19:44 +0200 Subject: [PATCH 07/11] CachingIterator: refactoring --- src/Iterators/CachingIterator.php | 24 ++++--------------- .../Iterators/CachingIterator.construct.phpt | 2 +- 2 files changed, 6 insertions(+), 20 deletions(-) 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/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); }); From a9ae49f30ffed978191c16c49f558c20489a6f07 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Oct 2022 21:52:55 +0200 Subject: [PATCH 08/11] added Cast [WIP] --- src/Utils/Cast.php | 99 ++++++++++++++++++++++++++++++ tests/Utils/Cast.phpt | 139 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 src/Utils/Cast.php create mode 100644 tests/Utils/Cast.phpt 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/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)); From d668e02bf5cc786b07abbb5658c97abbb4a00850 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 10 Dec 2023 16:22:17 +0100 Subject: [PATCH 09/11] added MemoizingIterator [WIP] --- src/Iterators/MemoizingIterator.php | 77 ++++++++++++++++++++++++++ tests/Iterators/MemoizingIterator.phpt | 77 ++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/Iterators/MemoizingIterator.php create mode 100644 tests/Iterators/MemoizingIterator.phpt 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/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, + ); +}); From d6a99617b62820837a1535c5508ac17ed7866640 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 May 2024 21:54:31 +0200 Subject: [PATCH 10/11] readme: added jumbo --- readme.md | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) 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) -------------------------------------------- From ef8d7b988c2b1aa2b09da63ac04dad36a84c8730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Sko=C4=8D=C3=ADk?= Date: Thu, 13 Jun 2024 13:28:07 +0200 Subject: [PATCH 11/11] Ensure valid type is passed into insert() --- src/Utils/Html.php | 3 +++ 1 file changed, 3 insertions(+) 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); }