From dd9a6c42267a2671ff2534ff4f3f58475b6995b0 Mon Sep 17 00:00:00 2001 From: Chris Reichel Date: Thu, 17 Oct 2024 14:49:18 +0200 Subject: [PATCH 1/6] Optimise ArrayHelper::map + use array_column for faster mapping if no callbacks or grouping are required --- framework/helpers/BaseArrayHelper.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index 56411163e1e..a92aadd9915 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -595,6 +595,9 @@ public static function getColumn($array, $name, $keepKeys = true) */ public static function map($array, $from, $to, $group = null) { + if (is_string($from) && is_string($to) && $group === null) { + return array_column($array, $to, $from); + } $result = []; foreach ($array as $element) { $key = static::getValue($element, $from); From ca4bae91990f40d6ea2c61ceecab285b9392686f Mon Sep 17 00:00:00 2001 From: Chris Reichel Date: Thu, 17 Oct 2024 15:12:09 +0200 Subject: [PATCH 2/6] Optimise ArrayHelper::map + changelog --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 294810f9fc7..1935d097811 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -9,6 +9,7 @@ Yii Framework 2 Change Log - Enh #20247: Support for variadic console controller action methods (brandonkelly) - Bug #20256: Add support for dropping views in MSSQL server when running migrate/fresh (ambrozt) - Enh #20248: Add support for attaching behaviors in configurations with Closure (timkelty) +- Enh #20268: Minor optimisation in `\yii\helpers\BaseArrayHelper::map` (chriscpty) 2.0.51 July 18, 2024 -------------------- From facef5a7db58a2879da875f7c091b644c4b8c51f Mon Sep 17 00:00:00 2001 From: Chris Reichel Date: Thu, 17 Oct 2024 15:23:09 +0200 Subject: [PATCH 3/6] improve ArrayHelper::map tests + also test with callbacks --- tests/framework/helpers/ArrayHelperTest.php | 40 ++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index f450b281f7a..a8afe19f6a6 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -734,6 +734,44 @@ public function testMap() '345' => 'ccc', ], ], $result); + + $result = ArrayHelper::map($array, + static function (array $group) { + return $group['id'] . $group['name']; + }, + static function (array $group) { + return $group['name'] . $group['class']; + } + ); + + $this->assertEquals([ + '123aaa' => 'aaax', + '124bbb' => 'bbbx', + '345ccc' => 'cccy', + ], $result); + + $result = ArrayHelper::map($array, + static function (array $group) { + return $group['id'] . $group['name']; + }, + static function (array $group) { + return $group['name'] . $group['class']; + }, + static function (array $group) { + return $group['class'] . '-' . $group['class']; + } + ); + + $this->assertEquals([ + 'x-x' => [ + '123aaa' => 'aaax', + '124bbb' => 'bbbx', + ], + 'y-y' => [ + '345ccc' => 'cccy', + ], + ], $result); + } public function testKeyExists() @@ -759,7 +797,7 @@ public function testKeyExistsWithFloat() if (version_compare(PHP_VERSION, '8.1.0', '>=')) { $this->markTestSkipped('Using floats as array key is deprecated.'); } - + $array = [ 1 => 3, 2.2 => 4, // Note: Floats are cast to ints, which means that the fractional part will be truncated. From 1fcadcd28e8439a7b0de9735884bfaeec8b904b2 Mon Sep 17 00:00:00 2001 From: Chris Reichel Date: Fri, 18 Oct 2024 09:04:47 +0200 Subject: [PATCH 4/6] fix ArrayHelper::map() for path strings ! don't use array_column if ArrayHelper strings are paths + add according tests + add BaseStringHelper::contains() helper function and according tests --- framework/CHANGELOG.md | 1 + framework/helpers/BaseArrayHelper.php | 2 +- framework/helpers/BaseStringHelper.php | 56 +++++++++++++++----- tests/framework/helpers/ArrayHelperTest.php | 13 +++++ tests/framework/helpers/StringHelperTest.php | 9 ++++ 5 files changed, 66 insertions(+), 15 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 1935d097811..dd4ff449450 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -10,6 +10,7 @@ Yii Framework 2 Change Log - Bug #20256: Add support for dropping views in MSSQL server when running migrate/fresh (ambrozt) - Enh #20248: Add support for attaching behaviors in configurations with Closure (timkelty) - Enh #20268: Minor optimisation in `\yii\helpers\BaseArrayHelper::map` (chriscpty) +- Enh #20268: Add `\yii\helpers\BaseStringHelper::contains()` as a polyfill for `str_contains` (chriscpty) 2.0.51 July 18, 2024 -------------------- diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index a92aadd9915..334f55112e0 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -595,7 +595,7 @@ public static function getColumn($array, $name, $keepKeys = true) */ public static function map($array, $from, $to, $group = null) { - if (is_string($from) && is_string($to) && $group === null) { + if (is_string($from) && is_string($to) && $group === null && !\yii\helpers\StringHelper::contains($from, '.') && !\yii\helpers\StringHelper::contains($to, '.')) { return array_column($array, $to, $from); } $result = []; diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index ec9252aa4c4..48f23c93ab7 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -226,6 +226,29 @@ protected static function truncateHtml($string, $count, $suffix, $encoding = fal return $generator->generateFromTokens($truncated) . ($totalCount >= $count ? $suffix : ''); } + /** + * @param string $string The input string. + * @param string $needle Part to search inside $string + * @param bool $caseSensitive Case sensitive search. Default is true. When case sensitive is enabled, `$with` must + * exactly match the starting of the string in order to get a true value. + * @return bool + */ + public static function contains($string, $needle, $caseSensitive = true) + { + $string = (string)$string; + $needle = (string)$needle; + + if ($caseSensitive) { + if (function_exists('str_contains')) { + return str_contains($string, $needle); + } + $encoding = Yii::$app ? Yii::$app->charset : 'UTF-8'; + return mb_strpos($string, $needle, 0, $encoding) !== false; + } + $encoding = Yii::$app ? Yii::$app->charset : 'UTF-8'; + return mb_stripos($string, $needle, 0, $encoding) !== false; + } + /** * Check if given string starts with specified substring. Binary and multibyte safe. * @@ -313,9 +336,14 @@ public static function explode($string, $delimiter = ',', $trim = true, $skipEmp } if ($skipEmpty) { // Wrapped with array_values to make array keys sequential after empty values removing - $result = array_values(array_filter($result, function ($value) { - return $value !== ''; - })); + $result = array_values( + array_filter( + $result, + function ($value) { + return $value !== ''; + } + ) + ); } return $result; @@ -343,7 +371,7 @@ public static function countWords($string) */ public static function normalizeNumber($value) { - $value = (string) $value; + $value = (string)$value; $localeInfo = localeconv(); $decimalSeparator = isset($localeInfo['decimal_point']) ? $localeInfo['decimal_point'] : null; @@ -396,7 +424,7 @@ public static function floatToString($number) { // . and , are the only decimal separators known in ICU data, // so its safe to call str_replace here - return str_replace(',', '.', (string) $number); + return str_replace(',', '.', (string)$number); } /** @@ -422,14 +450,14 @@ public static function matchWildcard($pattern, $string, $options = []) $replacements = [ '\\\\\\\\' => '\\\\', - '\\\\\\*' => '[*]', - '\\\\\\?' => '[?]', - '\*' => '.*', - '\?' => '.', - '\[\!' => '[^', - '\[' => '[', - '\]' => ']', - '\-' => '-', + '\\\\\\*' => '[*]', + '\\\\\\?' => '[?]', + '\*' => '.*', + '\?' => '.', + '\[\!' => '[^', + '\[' => '[', + '\]' => ']', + '\-' => '-', ]; if (isset($options['escape']) && !$options['escape']) { @@ -483,7 +511,7 @@ public static function mb_ucfirst($string, $encoding = 'UTF-8') */ public static function mb_ucwords($string, $encoding = 'UTF-8') { - $string = (string) $string; + $string = (string)$string; if (empty($string)) { return $string; } diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index a8afe19f6a6..a086503006b 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -772,6 +772,19 @@ static function (array $group) { ], ], $result); + $array = [ + ['id' => '123', 'name' => 'aaa', 'class' => 'x', 'map' => ['a' => '11', 'b' => '22']], + ['id' => '124', 'name' => 'bbb', 'class' => 'x', 'map' => ['a' => '33', 'b' => '44']], + ['id' => '345', 'name' => 'ccc', 'class' => 'y', 'map' => ['a' => '55', 'b' => '66']], + ]; + + $result = ArrayHelper::map($array, 'map.a', 'map.b'); + + $this->assertEquals([ + '11' => '22', + '33' => '44', + '55' => '66' + ], $result); } public function testKeyExists() diff --git a/tests/framework/helpers/StringHelperTest.php b/tests/framework/helpers/StringHelperTest.php index 5f222ec4e7a..49ffee0d876 100644 --- a/tests/framework/helpers/StringHelperTest.php +++ b/tests/framework/helpers/StringHelperTest.php @@ -150,6 +150,15 @@ public function testTruncateWords() $this->assertEquals('This is a test for...', StringHelper::truncateWords('This is a test for a sentance', 5, '...', true)); } + public function testContains() + { + $this->assertEquals(true, StringHelper::contains('Test', 'st')); + $this->assertEquals(false, StringHelper::contains('Test', 'ts')); + $this->assertEquals(false, StringHelper::contains('Test', 'St')); + $this->assertEquals(true, StringHelper::contains('Test', 'St', false)); + $this->assertEquals(false, StringHelper::contains('Test', 'Ste', false)); + } + /** * @dataProvider providerStartsWith * @param bool $result From 75385511351ebaadb408e908ca381eea8543d29f Mon Sep 17 00:00:00 2001 From: Chris Reichel Date: Fri, 18 Oct 2024 09:09:34 +0200 Subject: [PATCH 5/6] fix ArrayHelper::map() for path strings o code style fixes --- framework/helpers/BaseStringHelper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index 48f23c93ab7..bcecca8485b 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -239,11 +239,12 @@ public static function contains($string, $needle, $caseSensitive = true) $needle = (string)$needle; if ($caseSensitive) { + // can be replaced with just the str_contains call when minimum supported PHP version is raised to 8.0 or higher if (function_exists('str_contains')) { return str_contains($string, $needle); } $encoding = Yii::$app ? Yii::$app->charset : 'UTF-8'; - return mb_strpos($string, $needle, 0, $encoding) !== false; + return mb_strpos($string, $needle, 0, $encoding) !== false; } $encoding = Yii::$app ? Yii::$app->charset : 'UTF-8'; return mb_stripos($string, $needle, 0, $encoding) !== false; From 4ed589f7a6b9972a684a1723a0638ff41c661696 Mon Sep 17 00:00:00 2001 From: Chris Reichel Date: Tue, 22 Oct 2024 10:35:15 +0200 Subject: [PATCH 6/6] use strpos rather than polyfill ! use strpos rather than polyfill - remove BaseStringHelper::contains --- framework/CHANGELOG.md | 1 - framework/helpers/BaseArrayHelper.php | 2 +- framework/helpers/BaseStringHelper.php | 24 -------------------- tests/framework/helpers/StringHelperTest.php | 9 -------- 4 files changed, 1 insertion(+), 35 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index dd4ff449450..1935d097811 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -10,7 +10,6 @@ Yii Framework 2 Change Log - Bug #20256: Add support for dropping views in MSSQL server when running migrate/fresh (ambrozt) - Enh #20248: Add support for attaching behaviors in configurations with Closure (timkelty) - Enh #20268: Minor optimisation in `\yii\helpers\BaseArrayHelper::map` (chriscpty) -- Enh #20268: Add `\yii\helpers\BaseStringHelper::contains()` as a polyfill for `str_contains` (chriscpty) 2.0.51 July 18, 2024 -------------------- diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index 334f55112e0..bc770f96cb7 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -595,7 +595,7 @@ public static function getColumn($array, $name, $keepKeys = true) */ public static function map($array, $from, $to, $group = null) { - if (is_string($from) && is_string($to) && $group === null && !\yii\helpers\StringHelper::contains($from, '.') && !\yii\helpers\StringHelper::contains($to, '.')) { + if (is_string($from) && is_string($to) && $group === null && strpos($from, '.') === false && strpos($to, '.') === false) { return array_column($array, $to, $from); } $result = []; diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index bcecca8485b..5854e29766d 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -226,30 +226,6 @@ protected static function truncateHtml($string, $count, $suffix, $encoding = fal return $generator->generateFromTokens($truncated) . ($totalCount >= $count ? $suffix : ''); } - /** - * @param string $string The input string. - * @param string $needle Part to search inside $string - * @param bool $caseSensitive Case sensitive search. Default is true. When case sensitive is enabled, `$with` must - * exactly match the starting of the string in order to get a true value. - * @return bool - */ - public static function contains($string, $needle, $caseSensitive = true) - { - $string = (string)$string; - $needle = (string)$needle; - - if ($caseSensitive) { - // can be replaced with just the str_contains call when minimum supported PHP version is raised to 8.0 or higher - if (function_exists('str_contains')) { - return str_contains($string, $needle); - } - $encoding = Yii::$app ? Yii::$app->charset : 'UTF-8'; - return mb_strpos($string, $needle, 0, $encoding) !== false; - } - $encoding = Yii::$app ? Yii::$app->charset : 'UTF-8'; - return mb_stripos($string, $needle, 0, $encoding) !== false; - } - /** * Check if given string starts with specified substring. Binary and multibyte safe. * diff --git a/tests/framework/helpers/StringHelperTest.php b/tests/framework/helpers/StringHelperTest.php index 49ffee0d876..5f222ec4e7a 100644 --- a/tests/framework/helpers/StringHelperTest.php +++ b/tests/framework/helpers/StringHelperTest.php @@ -150,15 +150,6 @@ public function testTruncateWords() $this->assertEquals('This is a test for...', StringHelper::truncateWords('This is a test for a sentance', 5, '...', true)); } - public function testContains() - { - $this->assertEquals(true, StringHelper::contains('Test', 'st')); - $this->assertEquals(false, StringHelper::contains('Test', 'ts')); - $this->assertEquals(false, StringHelper::contains('Test', 'St')); - $this->assertEquals(true, StringHelper::contains('Test', 'St', false)); - $this->assertEquals(false, StringHelper::contains('Test', 'Ste', false)); - } - /** * @dataProvider providerStartsWith * @param bool $result