diff --git a/src/Str.php b/src/Str.php index 0dac0bb..d969a6d 100644 --- a/src/Str.php +++ b/src/Str.php @@ -32,7 +32,7 @@ public static function camel(?string $subject): string * * @param ?string $subject * @param string $start - * @param bool $caseSensitive + * @param bool $caseSensitive * * @return bool */ @@ -46,6 +46,84 @@ public static function startsWith(?string $subject, string $start, bool $caseSen return str_starts_with($subject, $start); } + /** + * Check if the given string ends with the specified substring + * + * @param ?string $subject + * @param string $end + * @param bool $caseSensitive + * + * @return bool + */ + public static function endsWith(?string $subject, string $end, bool $caseSensitive = true): bool + { + $subject ??= ''; + if (! $caseSensitive) { + return strncasecmp($subject, $end, strlen($end)) === 0; + } + + return str_ends_with($subject, $end); + } + + /** + * Check if the given string contains the specified substring + * + * @param ?string $subject + * @param string $needle + * @param bool $caseSensitive + * + * @return bool + */ + public static function contains(?string $subject, string $needle, bool $caseSensitive = true): bool + { + $subject ??= ''; + if (! $caseSensitive) { + return str_contains(strtolower($subject), strtolower($needle)); + } + + return str_contains($subject, $needle); + } + + /** + * Check if the given string contains any of the specified substrings + * + * @param ?string $haystack + * @param string[] $needles + * @param bool $caseSensitive + * + * @return bool + */ + public static function containsAny(?string $haystack, array $needles, bool $caseSensitive = true): bool + { + foreach ($needles as $needle) { + if (self::contains($haystack, $needle, $caseSensitive)) { + return true; + } + } + + return false; + } + + /** + * Check if the given string contains all the specified substrings + * + * @param ?string $haystack + * @param string[] $needles + * @param bool $caseSensitive + * + * @return bool + */ + public static function containsAll(?string $haystack, array $needles, bool $caseSensitive = true): bool + { + foreach ($needles as $needle) { + if (! self::contains($haystack, $needle, $caseSensitive)) { + return false; + } + } + + return true; + } + /** * Split string into an array padded to the size specified by limit * @@ -76,7 +154,7 @@ public static function symmetricSplit( * * @param ?string $subject * @param string $delimiter - * @param ?int $limit + * @param ?int $limit * * @return array */ @@ -90,4 +168,19 @@ public static function trimSplit(?string $subject, string $delimiter = ',', ?int return array_map('trim', $exploded); } + + /** + * Check if the given string is empty + * + * Null is considered empty and strings are trimmed before checking. + * + * @param ?string $subject + * @param string $characters + * + * @return bool + */ + public static function isEmpty(?string $subject, string $characters = " \n\r\t\v\0"): bool + { + return $subject === null || trim($subject, $characters) === ''; + } } diff --git a/tests/StrTest.php b/tests/StrTest.php index d4c7075..48c4598 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -47,6 +47,162 @@ public function testStartsWithReturnsFalseIfStringDoesNotStartWithTheSpecifiedSu $this->assertFalse(Str::startsWith('FOOBAR', 'foo', true)); } + public function testEndsWithReturnsTrueIfStringEndsWithTheSpecifiedSubstring() + { + $this->assertTrue(Str::endsWith('config.ini', '.ini')); + } + + public function testEndsWithReturnsFalseIfStringDoesNotEndWithTheSpecifiedSubstring() + { + $this->assertFalse(Str::endsWith('config.ini', '.php')); + } + + public function testEndsWithReturnsTrueIfStringEndsWithTheSpecifiedSubstringAndCaseIsStrict() + { + $this->assertTrue(Str::endsWith('config.INI', '.INI', true)); + } + + public function testEndsWithReturnsFalseIfStringDoesNotEndWithTheSpecifiedSubstringAndCaseIsStrict() + { + $this->assertFalse(Str::endsWith('config.INI', '.ini', true)); + } + + public function testContainsReturnsTrueIfStringContainsTheSpecifiedSubstring() + { + $this->assertTrue(Str::contains('MySQL server has gone away', 'server has gone away')); + } + + public function testContainsReturnsFalseIfStringDoesNotContainTheSpecifiedSubstring() + { + $this->assertFalse(Str::contains('Query executed successfully', 'server has gone away')); + } + + public function testContainsReturnsTrueIfStringContainsTheSpecifiedSubstringAndCaseIsStrict() + { + $this->assertTrue(Str::contains( + 'Lost connection to MySQL server during query', + 'Lost connection', + true, + )); + } + + public function testContainsReturnsFalseIfStringDoesNotContainTheSpecifiedSubstringAndCaseIsStrict() + { + $this->assertFalse(Str::contains( + 'lost connection to MySQL server during query', + 'Lost connection', + true, + )); + } + + public function testContainsAnyReturnsTrueIfStringContainsOneOfTheSpecifiedSubstrings() + { + $this->assertTrue(Str::containsAny('MySQL server has gone away', [ + 'server has gone away', + 'Lost connection', + 'Connection refused', + ])); + } + + public function testContainsAnyReturnsFalseIfStringContainsNoneOfTheSpecifiedSubstrings() + { + $this->assertFalse(Str::containsAny('Query executed successfully', [ + 'server has gone away', + 'Lost connection', + 'Connection refused', + ])); + } + + public function testContainsAnyReturnsTrueIfStringContainsOneOfTheSpecifiedSubstringsCaseInsensitively() + { + $this->assertTrue(Str::containsAny('LOST CONNECTION to MySQL server', [ + 'server has gone away', + 'Lost connection', + ], false)); + } + + public function testContainsAnyReturnsFalseIfStringContainsNoneOfTheSpecifiedSubstringsAndCaseIsStrict() + { + $this->assertFalse(Str::containsAny('lost connection to MySQL server', [ + 'server has gone away', + 'Lost connection', + ], true)); + } + + public function testContainsAllReturnsTrueIfStringContainsAllSpecifiedSubstrings() + { + $this->assertTrue(Str::containsAll( + 'host=db1.example.com;dbname=icingaweb2;charset=utf8', + ['host=', 'dbname=', 'charset='], + )); + } + + public function testContainsAllReturnsFalseIfStringIsMissingOneOfTheSpecifiedSubstrings() + { + $this->assertFalse(Str::containsAll( + 'host=db1.example.com;charset=utf8', + ['host=', 'dbname=', 'charset='], + )); + } + + public function testContainsAllReturnsTrueIfStringContainsAllSpecifiedSubstringsCaseInsensitively() + { + $this->assertTrue(Str::containsAll( + 'HOST=db1.example.com;DBNAME=icingaweb2', + ['host=', 'dbname='], + false, + )); + } + + public function testContainsAllReturnsFalseIfStringIsMissingOneOfTheSpecifiedSubstringsAndCaseIsStrict() + { + $this->assertFalse(Str::containsAll( + 'HOST=db1.example.com;DBNAME=icingaweb2', + ['host=', 'dbname='], + true, + )); + } + + public function testIsEmptyReturnsTrueForNull() + { + $this->assertTrue(Str::isEmpty(null)); + } + + public function testIsEmptyReturnsTrueForEmptyString() + { + $this->assertTrue(Str::isEmpty('')); + } + + public function testIsEmptyReturnsTrueForStringWithLeadingAndTrailingWhitespace() + { + $this->assertTrue(Str::isEmpty(' ')); + } + + public function testIsEmptyReturnsTrueForStringWithOnlyWhitespace() + { + $this->assertTrue(Str::isEmpty("\t\n")); + } + + public function testIsEmptyReturnsFalseForZero() + { + $this->assertFalse(Str::isEmpty('0')); + } + + public function testIsEmptyReturnsFalseForNonEmptyString() + { + $this->assertFalse(Str::isEmpty('Warning')); + } + + public function testIsEmptyReturnsFalseForStringWithContentAndSurroundingWhitespace() + { + $this->assertFalse(Str::isEmpty(' Warning ')); + } + + public function testIsEmptyReturnsTrueForStringWithCustomCharacters() + { + $this->assertTrue(Str::isEmpty('---', '-')); + } + public function testSymmetricSplitReturnsArrayPaddedToTheSizeSpecifiedByLimitUsingNullAsValueByDefault() { $this->assertSame(['foo', 'bar', null, null], Str::symmetricSplit('foo,bar', ',', 4));