@vituum/vite-plugin-latte 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +6 -6
  2. package/vendor/autoload.php +1 -1
  3. package/vendor/composer/autoload_real.php +4 -4
  4. package/vendor/composer/autoload_static.php +2 -2
  5. package/vendor/composer/installed.json +21 -20
  6. package/vendor/composer/installed.php +8 -8
  7. package/vendor/latte/latte/composer.json +6 -5
  8. package/vendor/latte/latte/readme.md +27 -9
  9. package/vendor/latte/latte/src/Bridges/Tracy/templates/LattePanel.panel.phtml +4 -1
  10. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +1 -1
  11. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +129 -129
  12. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +1 -1
  13. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +1 -1
  14. package/vendor/latte/latte/src/Latte/Engine.php +22 -2
  15. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +2 -3
  16. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +9 -1
  17. package/vendor/latte/latte/src/Latte/Essential/Filters.php +110 -10
  18. package/vendor/latte/latte/src/Latte/Essential/Nodes/ImportNode.php +8 -2
  19. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarNode.php +14 -18
  20. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +1 -1
  21. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +5 -4
  22. package/vendor/latte/latte/src/Latte/Runtime/Template.php +1 -1
  23. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +0 -1
  24. package/vendor/nette/utils/composer.json +1 -1
  25. package/vendor/nette/utils/readme.md +25 -26
  26. package/vendor/nette/utils/src/Iterators/CachingIterator.php +5 -19
  27. package/vendor/nette/utils/src/Iterators/Mapper.php +1 -2
  28. package/vendor/nette/utils/src/Utils/Arrays.php +59 -28
  29. package/vendor/nette/utils/src/Utils/Callback.php +1 -1
  30. package/vendor/nette/utils/src/Utils/Image.php +14 -12
  31. package/vendor/nette/utils/src/Utils/Iterables.php +99 -20
  32. package/vendor/nette/utils/src/Utils/Reflection.php +5 -3
  33. package/vendor/nette/utils/src/Utils/Strings.php +26 -6
@@ -23,6 +23,9 @@ use function is_array, is_string, count, strlen;
23
23
  */
24
24
  final class Filters
25
25
  {
26
+ public ?string $locale = null;
27
+
28
+
26
29
  /**
27
30
  * Converts HTML to plain text.
28
31
  */
@@ -35,7 +38,7 @@ final class Filters
35
38
 
36
39
 
37
40
  /**
38
- * Removes tags from HTML (but remains HTML entites).
41
+ * Removes tags from HTML (but remains HTML entities).
39
42
  */
40
43
  public static function stripTags(FilterInfo $info, $s): string
41
44
  {
@@ -168,14 +171,11 @@ final class Filters
168
171
  */
169
172
  public static function date(string|int|\DateTimeInterface|\DateInterval|null $time, ?string $format = null): ?string
170
173
  {
174
+ $format ??= Latte\Runtime\Filters::$dateFormat;
171
175
  if ($time == null) { // intentionally ==
172
176
  return null;
173
- }
174
-
175
- $format ??= Latte\Runtime\Filters::$dateFormat;
176
- if ($time instanceof \DateInterval) {
177
+ } elseif ($time instanceof \DateInterval) {
177
178
  return $time->format($format);
178
-
179
179
  } elseif (is_numeric($time)) {
180
180
  $time = (new \DateTime)->setTimestamp((int) $time);
181
181
  } elseif (!$time instanceof \DateTimeInterface) {
@@ -194,10 +194,75 @@ final class Filters
194
194
  }
195
195
 
196
196
 
197
+ /**
198
+ * Date/time formatting according to locale.
199
+ */
200
+ public function localDate(
201
+ string|int|\DateTimeInterface|null $value,
202
+ ?string $format = null,
203
+ ?string $date = null,
204
+ ?string $time = null,
205
+ ): ?string
206
+ {
207
+ if ($this->locale === null) {
208
+ throw new Latte\RuntimeException('Filter |localDate requires the locale to be set using Engine::setLocale()');
209
+ } elseif ($value == null) { // intentionally ==
210
+ return null;
211
+ } elseif (is_numeric($value)) {
212
+ $value = (new \DateTime)->setTimestamp((int) $value);
213
+ } elseif (!$value instanceof \DateTimeInterface) {
214
+ $value = new \DateTime($value);
215
+ $errors = \DateTime::getLastErrors();
216
+ if (!empty($errors['warnings'])) {
217
+ throw new \InvalidArgumentException(reset($errors['warnings']));
218
+ }
219
+ }
220
+
221
+ if ($format === null) {
222
+ $xlt = ['' => \IntlDateFormatter::NONE, 'full' => \IntlDateFormatter::FULL, 'long' => \IntlDateFormatter::LONG, 'medium' => \IntlDateFormatter::MEDIUM, 'short' => \IntlDateFormatter::SHORT,
223
+ 'relative-full' => \IntlDateFormatter::RELATIVE_FULL, 'relative-long' => \IntlDateFormatter::RELATIVE_LONG, 'relative-medium' => \IntlDateFormatter::RELATIVE_MEDIUM, 'relative-short' => \IntlDateFormatter::RELATIVE_SHORT];
224
+ $date ??= $time === null ? 'long' : null;
225
+ $formatter = new \IntlDateFormatter($this->locale, $xlt[$date], $xlt[$time]);
226
+ } else {
227
+ $formatter = new \IntlDateFormatter($this->locale, pattern: (new \IntlDatePatternGenerator($this->locale))->getBestPattern($format));
228
+ }
229
+
230
+ $res = $formatter->format($value);
231
+ $res = preg_replace('~(\d\.) ~', "\$1\u{a0}", $res);
232
+ return $res;
233
+ }
234
+
235
+
236
+ /**
237
+ * Formats a number with grouped thousands and optionally decimal digits according to locale.
238
+ */
239
+ public function number(
240
+ float $number,
241
+ string|int $patternOrDecimals = 0,
242
+ string $decimalSeparator = '.',
243
+ string $thousandsSeparator = ',',
244
+ ): string
245
+ {
246
+ if (is_int($patternOrDecimals) && $patternOrDecimals < 0) {
247
+ throw new Latte\RuntimeException('Filter |number: the number of decimal must not be negative');
248
+ } elseif ($this->locale === null || func_num_args() > 2) {
249
+ return number_format($number, $patternOrDecimals, $decimalSeparator, $thousandsSeparator);
250
+ }
251
+
252
+ $formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
253
+ if (is_string($patternOrDecimals)) {
254
+ $formatter->setPattern($patternOrDecimals);
255
+ } else {
256
+ $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $patternOrDecimals);
257
+ }
258
+ return $formatter->format($number);
259
+ }
260
+
261
+
197
262
  /**
198
263
  * Converts to human-readable file size.
199
264
  */
200
- public static function bytes(float $bytes, int $precision = 2): string
265
+ public function bytes(float $bytes, int $precision = 2): string
201
266
  {
202
267
  $bytes = round($bytes);
203
268
  $units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
@@ -209,7 +274,15 @@ final class Filters
209
274
  $bytes /= 1024;
210
275
  }
211
276
 
212
- return round($bytes, $precision) . ' ' . $unit;
277
+ if ($this->locale === null) {
278
+ $bytes = (string) round($bytes, $precision);
279
+ } else {
280
+ $formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
281
+ $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $precision);
282
+ $bytes = $formatter->format($bytes);
283
+ }
284
+
285
+ return $bytes . ' ' . $unit;
213
286
  }
214
287
 
215
288
 
@@ -455,7 +528,7 @@ final class Filters
455
528
  * @param iterable<K, V> $data
456
529
  * @return iterable<K, V>
457
530
  */
458
- public static function sort(
531
+ public function sort(
459
532
  iterable $data,
460
533
  ?\Closure $comparison = null,
461
534
  string|int|\Closure|null $by = null,
@@ -469,7 +542,16 @@ final class Filters
469
542
  $by = $byKey === true ? null : $byKey;
470
543
  }
471
544
 
472
- $comparison ??= fn($a, $b) => $a <=> $b;
545
+ if ($comparison) {
546
+ } elseif ($this->locale === null) {
547
+ $comparison = fn($a, $b) => $a <=> $b;
548
+ } else {
549
+ $collator = new \Collator($this->locale);
550
+ $comparison = fn($a, $b) => is_string($a) && is_string($b)
551
+ ? $collator->compare($a, $b)
552
+ : $a <=> $b;
553
+ }
554
+
473
555
  $comparison = match (true) {
474
556
  $by === null => $comparison,
475
557
  $by instanceof \Closure => fn($a, $b) => $comparison($by($a), $by($b)),
@@ -524,6 +606,24 @@ final class Filters
524
606
  }
525
607
 
526
608
 
609
+ /**
610
+ * Filters elements according to a given $predicate. Maintains original keys.
611
+ * @template K
612
+ * @template V
613
+ * @param iterable<K, V> $iterable
614
+ * @param callable(V, K, iterable<K, V>): bool $predicate
615
+ * @return iterable<K, V>
616
+ */
617
+ public static function filter(iterable $iterable, callable $predicate): iterable
618
+ {
619
+ foreach ($iterable as $k => $v) {
620
+ if ($predicate($v, $k, $iterable)) {
621
+ yield $k => $v;
622
+ }
623
+ }
624
+ }
625
+
626
+
527
627
  /**
528
628
  * Returns value clamped to the inclusive range of min and max.
529
629
  */
@@ -9,6 +9,7 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Essential\Nodes;
11
11
 
12
+ use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
12
13
  use Latte\Compiler\Nodes\Php\ExpressionNode;
13
14
  use Latte\Compiler\Nodes\StatementNode;
14
15
  use Latte\Compiler\PrintContext;
@@ -16,11 +17,12 @@ use Latte\Compiler\Tag;
16
17
 
17
18
 
18
19
  /**
19
- * {import "file"}
20
+ * {import "file"[, args]}
20
21
  */
21
22
  class ImportNode extends StatementNode
22
23
  {
23
24
  public ExpressionNode $file;
25
+ public ArrayNode $args;
24
26
 
25
27
 
26
28
  public static function create(Tag $tag): static
@@ -28,6 +30,8 @@ class ImportNode extends StatementNode
28
30
  $tag->expectArguments();
29
31
  $node = new static;
30
32
  $node->file = $tag->parser->parseUnquotedStringOrExpression();
33
+ $tag->parser->stream->tryConsume(',');
34
+ $node->args = $tag->parser->parseArguments();
31
35
  return $node;
32
36
  }
33
37
 
@@ -35,8 +39,9 @@ class ImportNode extends StatementNode
35
39
  public function print(PrintContext $context): string
36
40
  {
37
41
  return $context->format(
38
- '$this->createTemplate(%node, $this->params, "import")->render() %line;',
42
+ '$this->createTemplate(%node, %node? + $this->params, "import")->render() %line;',
39
43
  $this->file,
44
+ $this->args,
40
45
  $this->position,
41
46
  );
42
47
  }
@@ -45,5 +50,6 @@ class ImportNode extends StatementNode
45
50
  public function &getIterator(): \Generator
46
51
  {
47
52
  yield $this->file;
53
+ yield $this->args;
48
54
  }
49
55
  }
@@ -10,8 +10,10 @@ declare(strict_types=1);
10
10
  namespace Latte\Essential\Nodes;
11
11
 
12
12
  use Latte\Compiler\Nodes\Php\Expression\AssignNode;
13
+ use Latte\Compiler\Nodes\Php\Expression\AssignOpNode;
14
+ use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode;
15
+ use Latte\Compiler\Nodes\Php\Expression\TernaryNode;
13
16
  use Latte\Compiler\Nodes\Php\Expression\VariableNode;
14
- use Latte\Compiler\Nodes\Php\ExpressionNode;
15
17
  use Latte\Compiler\Nodes\Php\Scalar\NullNode;
16
18
  use Latte\Compiler\Nodes\StatementNode;
17
19
  use Latte\Compiler\PrintContext;
@@ -67,25 +69,19 @@ class VarNode extends StatementNode
67
69
  public function print(PrintContext $context): string
68
70
  {
69
71
  $res = [];
70
- if ($this->default) {
71
- foreach ($this->assignments as $assign) {
72
+ foreach ($this->assignments as $assign) {
73
+ if ($this->default) {
72
74
  assert($assign->var instanceof VariableNode);
73
- if ($assign->var->name instanceof ExpressionNode) {
74
- $var = $assign->var->name->print($context);
75
- } else {
76
- $var = $context->encodeString($assign->var->name);
77
- }
78
- $res[] = $var . ' => ' . $assign->expr->print($context);
75
+ $assign = new AssignOpNode(
76
+ $assign->var,
77
+ '??',
78
+ new TernaryNode(
79
+ new AuxiliaryNode(fn() => 'array_key_exists(' . $context->encodeString($assign->var->name) . ', get_defined_vars())'),
80
+ new NullNode,
81
+ $assign->expr,
82
+ ),
83
+ );
79
84
  }
80
-
81
- return $context->format(
82
- 'extract([%raw], EXTR_SKIP) %line;',
83
- implode(', ', $res),
84
- $this->position,
85
- );
86
- }
87
-
88
- foreach ($this->assignments as $assign) {
89
85
  $res[] = $assign->print($context);
90
86
  }
91
87
 
@@ -50,7 +50,7 @@ final class TranslatorExtension extends Latte\Extension
50
50
  public function getFilters(): array
51
51
  {
52
52
  return [
53
- 'translate' => fn(Latte\Runtime\FilterInfo $fi, ...$args): string => $this->translator
53
+ 'translate' => fn(Latte\Runtime\FilterInfo $fi, ...$args) => $this->translator
54
54
  ? ($this->translator)(...$args)
55
55
  : $args[0],
56
56
  ];
@@ -59,7 +59,7 @@ class FileLoader implements Latte\Loader
59
59
  */
60
60
  public function getReferredName(string $file, string $referringFile): string
61
61
  {
62
- if ($this->baseDir || !preg_match('#/|\\\\|[a-z][a-z0-9+.-]*:#iA', $file)) {
62
+ if ($this->baseDir || !preg_match('#/|\\\\|[a-z]:|phar:#iA', $file)) {
63
63
  $file = $this->normalizePath($referringFile . '/../' . $file);
64
64
  }
65
65
 
@@ -78,15 +78,16 @@ class FileLoader implements Latte\Loader
78
78
 
79
79
  protected static function normalizePath(string $path): string
80
80
  {
81
+ preg_match('#^([a-z]:|phar://.+?/)?(.*)#i', $path, $m);
81
82
  $res = [];
82
- foreach (explode('/', strtr($path, '\\', '/')) as $part) {
83
- if ($part === '..' && $res && end($res) !== '..') {
83
+ foreach (explode('/', strtr($m[2], '\\', '/')) as $part) {
84
+ if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
84
85
  array_pop($res);
85
86
  } elseif ($part !== '.') {
86
87
  $res[] = $part;
87
88
  }
88
89
  }
89
90
 
90
- return implode(DIRECTORY_SEPARATOR, $res);
91
+ return $m[1] . implode(DIRECTORY_SEPARATOR, $res);
91
92
  }
92
93
  }
@@ -138,7 +138,7 @@ class Template
138
138
 
139
139
  $params = $this->prepare();
140
140
 
141
- if ($this->parentName === null && isset($this->global->coreParentFinder)) {
141
+ if ($this->parentName === null && !$this->referringTemplate && isset($this->global->coreParentFinder)) {
142
142
  $this->parentName = ($this->global->coreParentFinder)($this);
143
143
  }
144
144
 
@@ -26,6 +26,5 @@ class FunctionCallNode extends Expression\FunctionCallNode
26
26
  return '$this->global->sandbox->call('
27
27
  . $context->memberAsString($this->name) . ', '
28
28
  . $context->argumentsAsArray($this->args) . ')';
29
-
30
29
  }
31
30
  }
@@ -15,7 +15,7 @@
15
15
  }
16
16
  ],
17
17
  "require": {
18
- "php": ">=8.0 <8.4"
18
+ "php": "8.0 - 8.4"
19
19
  },
20
20
  "require-dev": {
21
21
  "nette/tester": "^2.5",
@@ -1,5 +1,4 @@
1
- Nette Utility Classes
2
- =====================
1
+ [![Nette Utils](https://github.com/nette/utils/assets/194960/c33fdb74-0652-4cad-ac6e-c1ce0d29e32a)](https://doc.nette.org/en/utils)
3
2
 
4
3
  [![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils)
5
4
  [![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
11
10
  Introduction
12
11
  ------------
13
12
 
14
- In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use:
15
-
16
- - [Arrays](https://doc.nette.org/utils/arrays) - manipulate arrays
17
- - [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
18
- - [Date and Time](https://doc.nette.org/utils/datetime) - modify times and dates
19
- - [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming,
20
- - [Finder](https://doc.nette.org/utils/finder) - finds files and directories
21
- - [Helper Functions](https://doc.nette.org/utils/helpers)
22
- - [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
23
- - [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
24
- - [JSON](https://doc.nette.org/utils/json) - encoding and decoding
25
- - [Generating Random Strings](https://doc.nette.org/utils/random)
26
- - [Paginator](https://doc.nette.org/utils/paginator) - pagination math
27
- - [PHP Reflection](https://doc.nette.org/utils/reflection)
28
- - [Strings](https://doc.nette.org/utils/strings) - useful text functions
29
- - [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
30
- - [Validation](https://doc.nette.org/utils/validators) - validate inputs
31
- - [Type](https://doc.nette.org/utils/type) - PHP data type
32
-
13
+ In package nette/utils you will find a set of useful classes for everyday use:
14
+
15
+ [Arrays](https://doc.nette.org/utils/arrays)<br>
16
+ [Callback](https://doc.nette.org/utils/callback) - PHP callbacks<br>
17
+ [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …<br>
18
+ [Finder](https://doc.nette.org/utils/finder) - finds files and directories<br>
19
+ [Floats](https://doc.nette.org/utils/floats) - floating point numbers<br>
20
+ [Helper Functions](https://doc.nette.org/utils/helpers)<br>
21
+ [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML<br>
22
+ [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images<br>
23
+ [Iterables](https://doc.nette.org/utils/iterables) <br>
24
+ [JSON](https://doc.nette.org/utils/json) - encoding and decoding<br>
25
+ [Generating Random Strings](https://doc.nette.org/utils/random)<br>
26
+ [Paginator](https://doc.nette.org/utils/paginator) - pagination math<br>
27
+ [PHP Reflection](https://doc.nette.org/utils/reflection)<br>
28
+ [Strings](https://doc.nette.org/utils/strings) - useful text functions<br>
29
+ [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements<br>
30
+ [Type](https://doc.nette.org/utils/type) - PHP data type<br>
31
+ ✅ [Validation](https://doc.nette.org/utils/validators) - validate inputs<br>
32
+
33
+  <!---->
33
34
 
34
35
  Installation
35
36
  ------------
@@ -40,11 +41,9 @@ The recommended way to install is via Composer:
40
41
  composer require nette/utils
41
42
  ```
42
43
 
43
- - Nette Utils 4.0 is compatible with PHP 8.0 to 8.3
44
- - Nette Utils 3.2 is compatible with PHP 7.2 to 8.3
45
- - Nette Utils 3.1 is compatible with PHP 7.1 to 8.0
46
- - Nette Utils 3.0 is compatible with PHP 7.1 to 8.0
47
- - Nette Utils 2.5 is compatible with PHP 5.6 to 8.0
44
+ Nette Utils 4.0 is compatible with PHP 8.0 to 8.4.
45
+
46
+  <!---->
48
47
 
49
48
  [Support Me](https://github.com/sponsors/dg)
50
49
  --------------------------------------------
@@ -31,26 +31,12 @@ class CachingIterator extends \CachingIterator implements \Countable
31
31
  private int $counter = 0;
32
32
 
33
33
 
34
- public function __construct($iterator)
34
+ public function __construct(iterable|\stdClass $iterable)
35
35
  {
36
- if (is_array($iterator) || $iterator instanceof \stdClass) {
37
- $iterator = new \ArrayIterator($iterator);
38
-
39
- } elseif ($iterator instanceof \IteratorAggregate) {
40
- do {
41
- $iterator = $iterator->getIterator();
42
- } while ($iterator instanceof \IteratorAggregate);
43
-
44
- assert($iterator instanceof \Iterator);
45
-
46
- } elseif ($iterator instanceof \Iterator) {
47
- } elseif ($iterator instanceof \Traversable) {
48
- $iterator = new \IteratorIterator($iterator);
49
- } else {
50
- throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, get_debug_type($iterator)));
51
- }
52
-
53
- parent::__construct($iterator, 0);
36
+ $iterable = $iterable instanceof \stdClass
37
+ ? new \ArrayIterator($iterable)
38
+ : Nette\Utils\Iterables::toIterator($iterable);
39
+ parent::__construct($iterable, 0);
54
40
  }
55
41
 
56
42
 
@@ -10,9 +10,8 @@ declare(strict_types=1);
10
10
  namespace Nette\Iterators;
11
11
 
12
12
 
13
-
14
13
  /**
15
- * Applies the callback to the elements of the inner iterator.
14
+ * @deprecated use Nette\Utils\Iterables::map()
16
15
  */
17
16
  class Mapper extends \IteratorIterator
18
17
  {
@@ -122,10 +122,11 @@ class Arrays
122
122
 
123
123
  /**
124
124
  * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
125
- * The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
126
- * @template T
127
- * @param array<T> $array
128
- * @return ?T
125
+ * @template K of int|string
126
+ * @template V
127
+ * @param array<K, V> $array
128
+ * @param ?callable(V, K, array<K, V>): bool $predicate
129
+ * @return ?V
129
130
  */
130
131
  public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed
131
132
  {
@@ -138,10 +139,11 @@ class Arrays
138
139
 
139
140
  /**
140
141
  * Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
141
- * The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
142
- * @template T
143
- * @param array<T> $array
144
- * @return ?T
142
+ * @template K of int|string
143
+ * @template V
144
+ * @param array<K, V> $array
145
+ * @param ?callable(V, K, array<K, V>): bool $predicate
146
+ * @return ?V
145
147
  */
146
148
  public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed
147
149
  {
@@ -154,7 +156,11 @@ class Arrays
154
156
 
155
157
  /**
156
158
  * Returns the key of first item (matching the specified predicate if given) or null if there is no such item.
157
- * The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
159
+ * @template K of int|string
160
+ * @template V
161
+ * @param array<K, V> $array
162
+ * @param ?callable(V, K, array<K, V>): bool $predicate
163
+ * @return ?K
158
164
  */
159
165
  public static function firstKey(array $array, ?callable $predicate = null): int|string|null
160
166
  {
@@ -172,7 +178,11 @@ class Arrays
172
178
 
173
179
  /**
174
180
  * Returns the key of last item (matching the specified predicate if given) or null if there is no such item.
175
- * The $predicate has the signature `function (mixed $value, int|string $key, array $array): bool`.
181
+ * @template K of int|string
182
+ * @template V
183
+ * @param array<K, V> $array
184
+ * @param ?callable(V, K, array<K, V>): bool $predicate
185
+ * @return ?K
176
186
  */
177
187
  public static function lastKey(array $array, ?callable $predicate = null): int|string|null
178
188
  {
@@ -368,12 +378,11 @@ class Arrays
368
378
 
369
379
 
370
380
  /**
371
- * Tests whether at least one element in the array passes the test implemented by the provided function,
372
- * which has the signature `function ($value, $key, array $array): bool`.
373
- * @template K
381
+ * Tests whether at least one element in the array passes the test implemented by the provided function.
382
+ * @template K of int|string
374
383
  * @template V
375
- * @param iterable<K, V> $array
376
- * @param callable(V, K, ($array is array ? array<K, V> : iterable<K, V>)): bool $predicate
384
+ * @param array<K, V> $array
385
+ * @param callable(V, K, array<K, V>): bool $predicate
377
386
  */
378
387
  public static function some(iterable $array, callable $predicate): bool
379
388
  {
@@ -388,12 +397,11 @@ class Arrays
388
397
 
389
398
 
390
399
  /**
391
- * Tests whether all elements in the array pass the test implemented by the provided function,
392
- * which has the signature `function ($value, $key, array $array): bool`.
393
- * @template K
400
+ * Tests whether all elements in the array pass the test implemented by the provided function.
401
+ * @template K of int|string
394
402
  * @template V
395
- * @param iterable<K, V> $array
396
- * @param callable(V, K, ($array is array ? array<K, V> : iterable<K, V>)): bool $predicate
403
+ * @param array<K, V> $array
404
+ * @param callable(V, K, array<K, V>): bool $predicate
397
405
  */
398
406
  public static function every(iterable $array, callable $predicate): bool
399
407
  {
@@ -409,11 +417,10 @@ class Arrays
409
417
 
410
418
  /**
411
419
  * Returns a new array containing all key-value pairs matching the given $predicate.
412
- * The callback has the signature `function (mixed $value, int|string $key, array $array): bool`.
413
- * @template K of array-key
420
+ * @template K of int|string
414
421
  * @template V
415
- * @param array<K, V> $array
416
- * @param callable(V, K, array<K, V>): bool $predicate
422
+ * @param array<K, V> $array
423
+ * @param callable(V, K, array<K, V>): bool $predicate
417
424
  * @return array<K, V>
418
425
  */
419
426
  public static function filter(array $array, callable $predicate): array
@@ -430,12 +437,11 @@ class Arrays
430
437
 
431
438
  /**
432
439
  * Returns an array containing the original keys and results of applying the given transform function to each element.
433
- * The function has signature `function ($value, $key, array $array): mixed`.
434
- * @template K of array-key
440
+ * @template K of int|string
435
441
  * @template V
436
442
  * @template R
437
- * @param iterable<K, V> $array
438
- * @param callable(V, K, ($array is array ? array<K, V> : iterable<K, V>)): R $transformer
443
+ * @param array<K, V> $array
444
+ * @param callable(V, K, array<K, V>): R $transformer
439
445
  * @return array<K, R>
440
446
  */
441
447
  public static function map(iterable $array, callable $transformer): array
@@ -449,6 +455,31 @@ class Arrays
449
455
  }
450
456
 
451
457
 
458
+ /**
459
+ * Returns an array containing new keys and values generated by applying the given transform function to each element.
460
+ * If the function returns null, the element is skipped.
461
+ * @template K of int|string
462
+ * @template V
463
+ * @template ResK of int|string
464
+ * @template ResV
465
+ * @param array<K, V> $array
466
+ * @param callable(V, K, array<K, V>): ?array{ResK, ResV} $transformer
467
+ * @return array<ResK, ResV>
468
+ */
469
+ public static function mapWithKeys(array $array, callable $transformer): array
470
+ {
471
+ $res = [];
472
+ foreach ($array as $k => $v) {
473
+ $pair = $transformer($v, $k, $array);
474
+ if ($pair) {
475
+ $res[$pair[0]] = $pair[1];
476
+ }
477
+ }
478
+
479
+ return $res;
480
+ }
481
+
482
+
452
483
  /**
453
484
  * Invokes all callbacks and returns array of results.
454
485
  * @param callable[] $callbacks
@@ -94,7 +94,7 @@ final class Callback
94
94
  }
95
95
 
96
96
  if (is_string($callable) && str_contains($callable, '::')) {
97
- return new ReflectionMethod($callable);
97
+ return new ReflectionMethod(...explode('::', $callable, 2));
98
98
  } elseif (is_array($callable)) {
99
99
  return new ReflectionMethod($callable[0], $callable[1]);
100
100
  } elseif (is_object($callable) && !$callable instanceof \Closure) {