Skip to content

Commit f846c6e

Browse files
authored
Fix #168: Add benchmark, improve ArrayHelper::htmlEncode() performance
1 parent e9c3798 commit f846c6e

File tree

8 files changed

+697
-127
lines changed

8 files changed

+697
-127
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 3.2.1 under development
44

5+
- Enh #168: Add benchmark, improve `ArrayHelper::htmlEncode()` performance (@samdark)
56
- Enh #169: Bump `yiisoft/strings` dependency to `^2.6` (@vjik)
67

78
## 3.2.0 February 01, 2025

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"rector/rector": "^2.0.9",
3838
"roave/infection-static-analysis-plugin": "^1.35",
3939
"spatie/phpunit-watcher": "^1.24",
40-
"vimeo/psalm": "^5.26.1 || ^6.5.1"
40+
"vimeo/psalm": "^5.26.1 || ^6.5.1",
41+
"phpbench/phpbench": "^1.4"
4142
},
4243
"autoload": {
4344
"psr-4": {

phpbench.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
3+
"runner.bootstrap": "vendor/autoload.php",
4+
"runner.path": "tests/Benchmark",
5+
"runner.php_config": {
6+
"memory_limit": "1G"
7+
},
8+
"report.generators": {
9+
"default": {
10+
"generator": "composite",
11+
"reports": ["aggregate", "env"]
12+
}
13+
}
14+
}

src/ArrayHelper.php

Lines changed: 98 additions & 59 deletions
Large diffs are not rendered by default.

src/ArraySorter.php

Lines changed: 30 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@
77
use Closure;
88
use InvalidArgumentException;
99

10-
use function array_fill;
1110
use function array_multisort;
1211
use function count;
1312
use function is_array;
14-
use function is_scalar;
15-
use function range;
1613

1714
final class ArraySorter
1815
{
@@ -30,7 +27,7 @@ final class ArraySorter
3027
* ArraySorter::multisort($data, ['age', 'name'], [SORT_ASC, SORT_DESC]);
3128
* ```
3229
*
33-
* After sorting we'll get the following in `$data`:
30+
* After sorting, we'll get the following in `$data`:
3431
*
3532
* ```php
3633
* [
@@ -43,7 +40,7 @@ final class ArraySorter
4340
* @param array<array-key, array|object> $array The array to be sorted. The array will be modified after calling
4441
* this method.
4542
* @param array<array-key, Closure|string>|Closure|string $key The key(s) to be sorted by. This refers to a key
46-
* name of the sub-array elements, a property name of the objects, or an anonymous function returning the values
43+
* name of the subarray elements, a property name of the objects, or an anonymous function returning the values
4744
* for comparison purpose. The anonymous function signature should be: `function($item)`.
4845
* To sort by multiple keys, provide an array of keys here.
4946
* @param array<array-key, int>|int $direction The sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
@@ -62,80 +59,47 @@ public static function multisort(
6259
array|int $direction = SORT_ASC,
6360
array|int $sortFlag = SORT_REGULAR
6461
): void {
65-
$keys = self::getKeys($array, $key);
66-
if (empty($keys)) {
62+
$count = count($array);
63+
if ($count === 0) {
6764
return;
6865
}
6966

70-
$n = count($keys);
71-
if (is_scalar($direction)) {
72-
$direction = array_fill(0, $n, $direction);
73-
} elseif (count($direction) !== $n) {
74-
throw new InvalidArgumentException('The length of $direction parameter must be the same as that of $keys.');
67+
$keys = is_array($key) ? $key : [$key];
68+
$keysCount = count($keys);
69+
if ($keysCount === 0) {
70+
return;
7571
}
7672

77-
if (is_scalar($sortFlag)) {
78-
$sortFlag = array_fill(0, $n, $sortFlag);
79-
} elseif (count($sortFlag) !== $n) {
73+
if (is_array($direction) && count($direction) !== $keysCount) {
74+
throw new InvalidArgumentException('The length of $direction parameter must be the same as that of $keys.');
75+
}
76+
if (is_array($sortFlag) && count($sortFlag) !== $keysCount) {
8077
throw new InvalidArgumentException('The length of $sortFlag parameter must be the same as that of $keys.');
8178
}
8279

83-
$_args = self::getArguments($array, $keys, $direction, $sortFlag);
84-
85-
/** @psalm-suppress UnsupportedReferenceUsage */
86-
$_args[] = &$array;
87-
88-
/** @psalm-suppress MixedArgument */
89-
array_multisort(...$_args);
90-
}
80+
$args = [];
9181

92-
/**
93-
* Get keys for get arguments.
94-
*
95-
* @param array<array-key, array|object> $array The array to be sorted.
96-
* @param array<array-key, Closure|string>|Closure|string $key The keys to be sorted by. This refers to a key name
97-
* of the sub-array elements, a property name of the objects, or an anonymous function returning the values for
98-
* comparison purpose. The anonymous function signature should be: `function($item)`.
99-
* To sort by multiple keys, provide an array of keys here.
100-
*
101-
* @return array<array-key, Closure|string> The keys.
102-
*/
103-
private static function getKeys(array $array, array|Closure|string $key): array
104-
{
105-
$keys = is_array($key) ? $key : [$key];
106-
if (empty($keys) || empty($array)) {
107-
return [];
82+
for ($i = 0; $i < $keysCount; $i++) {
83+
$args[] = ArrayHelper::getColumn($array, $keys[$i]);
84+
$args[] = is_array($direction) ? $direction[$i] : $direction;
85+
$args[] = is_array($sortFlag) ? $sortFlag[$i] : $sortFlag;
10886
}
10987

110-
return $keys;
111-
}
112-
113-
/**
114-
* Get arguments for multisort.
115-
*
116-
* @param array<array-key, array|object> $array The array to be sorted.
117-
* @param array<array-key, Closure|string> $keys Array of keys.
118-
* @param array<array-key, int> $direction Array of sorting directions.
119-
* @param array<array-key, int> $sortFlags Array of sort flags.
120-
*
121-
* @return array The arguments.
122-
*/
123-
private static function getArguments(array $array, array $keys, array $direction, array $sortFlags): array
124-
{
125-
$args = [];
126-
foreach ($keys as $i => $iKey) {
127-
$flag = $sortFlags[$i];
128-
$args[] = ArrayHelper::getColumn($array, $iKey);
129-
$args[] = $direction[$i];
130-
$args[] = $flag;
88+
// Add tie-breaker only for non-empty arrays
89+
if ($count > 0) {
90+
$tieBreaker = [];
91+
for ($i = 0; $i < $count; $i++) {
92+
$tieBreaker[$i] = $i + 1;
93+
}
94+
$args[] = $tieBreaker;
95+
$args[] = SORT_ASC;
96+
$args[] = SORT_NUMERIC;
13197
}
13298

133-
// This fix is used for cases when main sorting specified by columns has equal values.
134-
// Without it will lead to Fatal Error: Nesting level too deep - recursive dependency?
135-
$args[] = range(1, count($array));
136-
$args[] = SORT_ASC;
137-
$args[] = SORT_NUMERIC;
99+
/** @psalm-suppress UnsupportedReferenceUsage */
100+
$args[] = &$array;
138101

139-
return $args;
102+
/** @psalm-suppress ArgumentTypeCoercion */
103+
array_multisort(...$args);
140104
}
141105
}

0 commit comments

Comments
 (0)