Commit ef127f68 by armab Committed by Alexander Makarov

Fixes #5316: Added `startsWith()` and `endsWith()` to…

Fixes #5316: Added `startsWith()` and `endsWith()` to `yii\helpers\StringHelper`. Methods are binary-safe, multibyte-safe and optionally case-insensitive
parent 3b80b639
......@@ -13,6 +13,7 @@ Yii Framework 2 Change Log
- Enh #4040: Added `$viewFile` and `$params` to the `EVENT_BEFORE_RENDER` and `EVENT_AFTER_RENDER` events for `View` (qiangxue)
- Enh #4275: Added `removeChildren()` to `yii\rbac\ManagerInterface` and implementations (samdark)
- Enh: Added `yii\base\Application::loadedModules` (qiangxue)
- Enh #5316: Added `startsWith()` and `endsWith()` to `yii\helpers\StringHelper`. Methods are binary-safe, multibyte-safe and optionally case-insensitive (armab)
- Chg #2037: Dropped the support for using `yii\base\Module` as concrete module classes (qiangxue)
......
......@@ -7,6 +7,8 @@
namespace yii\helpers;
use Yii;
/**
* BaseStringHelper provides concrete implementation for [[StringHelper]].
*
......@@ -100,8 +102,8 @@ class BaseStringHelper
*/
public static function truncate($string, $length, $suffix = '...', $encoding = null)
{
if (mb_strlen($string, $encoding ?: \Yii::$app->charset) > $length) {
return trim(mb_substr($string, 0, $length, $encoding ?: \Yii::$app->charset)) . $suffix;
if (mb_strlen($string, $encoding ?: Yii::$app->charset) > $length) {
return trim(mb_substr($string, 0, $length, $encoding ?: Yii::$app->charset)) . $suffix;
} else {
return $string;
}
......@@ -124,4 +126,50 @@ class BaseStringHelper
return $string;
}
}
/**
* Check if given string starts with specified substring.
* Binary and multibyte safe.
*
* @param string $string Input string
* @param string $with Part to search
* @param boolean $caseSensitive Case sensitive search. Default is true.
* @return boolean Returns true if first input starts with second input, false otherwise
*/
public static function startsWith($string, $with, $caseSensitive = true)
{
if (!$bytes = static::byteLength($with)) {
return true;
}
if ($caseSensitive) {
return strncmp($string, $with, $bytes) === 0;
} else {
return mb_strtolower(mb_substr($string, 0, $bytes, '8bit'), Yii::$app->charset) === mb_strtolower($with, Yii::$app->charset);
}
}
/**
* Check if given string ends with specified substring.
* Binary and multibyte safe.
*
* @param string $string
* @param string $with
* @param boolean $caseSensitive Case sensitive search. Default is true.
* @return boolean Returns true if first input ends with second input, false otherwise
*/
public static function endsWith($string, $with, $caseSensitive = true)
{
if (!$bytes = static::byteLength($with)) {
return true;
}
if ($caseSensitive) {
// Warning check, see http://php.net/manual/en/function.substr-compare.php#refsect1-function.substr-compare-returnvalues
if (static::byteLength($string) < $bytes) {
return false;
}
return substr_compare($string, $with, -$bytes, $bytes) === 0;
} else {
return mb_strtolower(mb_substr($string, -$bytes, null, '8bit'), Yii::$app->charset) === mb_strtolower($with, Yii::$app->charset);
}
}
}
......@@ -114,4 +114,109 @@ class StringHelperTest extends TestCase
$this->assertEquals('это тестовая multibyte!!!', StringHelper::truncateWords('это тестовая multibyte строка', 3, '!!!'));
$this->assertEquals('это строка с неожиданными...', StringHelper::truncateWords('это строка с неожиданными пробелами', 4));
}
/**
* @dataProvider providerStartsWith
*/
public function testStartsWith($result, $string, $with)
{
// case sensitive version check
$this->assertSame($result, StringHelper::startsWith($string, $with));
// case insensitive version check
$this->assertSame($result, StringHelper::startsWith($string, $with, false));
}
/**
* Rules that should work the same for case-sensitive and case-insensitive `startsWith()`
*/
public function providerStartsWith()
{
return [
// positive check
[true, '', ''],
[true, '', null],
[true, 'string', ''],
[true, ' string', ' '],
[true, 'abc', 'abc'],
[true, 'Bürger', 'Bürger'],
[true, '我Я multibyte', '我Я'],
[true, 'Qנטשופ צרכנות', 'Qנ'],
[true, 'ไทย.idn.icann.org', 'ไ'],
[true, '!?+', "\x21\x3F"],
[true, "\x21?+", '!?'],
// false-positive check
[false, '', ' '],
[false, ' ', ' '],
[false, 'Abc', 'Abcde'],
[false, 'abc', 'abe'],
[false, 'abc', 'b'],
[false, 'abc', 'c'],
];
}
public function testStartsWithCaseSensitive()
{
$this->assertFalse(StringHelper::startsWith('Abc', 'a'));
$this->assertFalse(StringHelper::startsWith('üЯ multibyte', 'Üя multibyte'));
}
public function testStartsWithCaseInsensitive()
{
$this->assertTrue(StringHelper::startsWith('sTrInG', 'StRiNg', false));
$this->assertTrue(StringHelper::startsWith('CaSe', 'cAs', false));
$this->assertTrue(StringHelper::startsWith('HTTP://BÜrger.DE/', 'http://bürger.de', false));
$this->assertTrue(StringHelper::startsWith('üЯйΨB', 'ÜяЙΨ', false));
}
/**
* @dataProvider providerEndsWith
*/
public function testEndsWith($result, $string, $with)
{
// case sensitive version check
$this->assertSame($result, StringHelper::endsWith($string, $with));
// case insensitive version check
$this->assertSame($result, StringHelper::endsWith($string, $with, false));
}
/**
* Rules that should work the same for case-sensitive and case-insensitive `endsWith()`
*/
public function providerEndsWith()
{
return [
// positive check
[true, '', ''],
[true, '', null],
[true, 'string', ''],
[true, 'string ', ' '],
[true, 'string', 'g'],
[true, 'abc', 'abc'],
[true, 'Bürger', 'Bürger'],
[true, 'Я multibyte строка我!', ' строка我!'],
[true, '+!?', "\x21\x3F"],
[true, "+\x21?", "!\x3F"],
[true, 'נטשופ צרכנות', 'ת'],
// false-positive check
[false, '', ' '],
[false, ' ', ' '],
[false, 'aaa', 'aaaa'],
[false, 'abc', 'abe'],
[false, 'abc', 'a'],
[false, 'abc', 'b'],
];
}
public function testEndsWithCaseSensitive()
{
$this->assertFalse(StringHelper::endsWith('string', 'G'));
$this->assertFalse(StringHelper::endsWith('multibyte строка', 'А'));
}
public function testEndsWithCaseInsensitive()
{
$this->assertTrue(StringHelper::endsWith('sTrInG', 'StRiNg', false));
$this->assertTrue(StringHelper::endsWith('string', 'nG', false));
$this->assertTrue(StringHelper::endsWith('BüЯйΨ', 'ÜяЙΨ', false));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment