From 6011cf4410789d864bbad08d0ddfc4c318e247ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Wed, 27 May 2026 11:54:05 +0200 Subject: [PATCH 1/8] Adjust docstrings --- src/Str.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Str.php b/src/Str.php index 0dac0bb..9232742 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 */ @@ -76,7 +76,7 @@ public static function symmetricSplit( * * @param ?string $subject * @param string $delimiter - * @param ?int $limit + * @param ?int $limit * * @return array */ From ed5fdc3368dde596ec561b176fef5cf06b73be04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Wed, 27 May 2026 11:42:02 +0200 Subject: [PATCH 2/8] Add isEmpty method --- src/Str.php | 15 +++++++++++++++ tests/StrTest.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/Str.php b/src/Str.php index 9232742..e48f01d 100644 --- a/src/Str.php +++ b/src/Str.php @@ -90,4 +90,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..3e7af67 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -47,6 +47,36 @@ public function testStartsWithReturnsFalseIfStringDoesNotStartWithTheSpecifiedSu $this->assertFalse(Str::startsWith('FOOBAR', 'foo', 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 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)); From 5431a1932bee4f343120ef85765fe4947b1a3ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Wed, 27 May 2026 12:49:24 +0200 Subject: [PATCH 3/8] Add endsWith --- src/Str.php | 19 +++++++++++++++++++ tests/StrTest.php | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/Str.php b/src/Str.php index e48f01d..acb9181 100644 --- a/src/Str.php +++ b/src/Str.php @@ -46,6 +46,25 @@ 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); + } + /** * Split string into an array padded to the size specified by limit * diff --git a/tests/StrTest.php b/tests/StrTest.php index 3e7af67..9464319 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -47,6 +47,26 @@ 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 testIsEmptyReturnsTrueForNull() { $this->assertTrue(Str::isEmpty(null)); From 011beedfe438ec29c70ba2b8a4d66e4f24721c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Fri, 29 May 2026 08:53:26 +0200 Subject: [PATCH 4/8] Add contains --- src/Str.php | 19 +++++++++++++++++++ tests/StrTest.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/Str.php b/src/Str.php index acb9181..7ae71e9 100644 --- a/src/Str.php +++ b/src/Str.php @@ -65,6 +65,25 @@ public static function endsWith(?string $subject, string $end, bool $caseSensiti 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); + } + /** * Split string into an array padded to the size specified by limit * diff --git a/tests/StrTest.php b/tests/StrTest.php index 9464319..0cd8900 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -67,6 +67,34 @@ public function testEndsWithReturnsFalseIfStringDoesNotEndWithTheSpecifiedSubstr $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 testIsEmptyReturnsTrueForNull() { $this->assertTrue(Str::isEmpty(null)); From 69a3d8c5832b4972be88b5c5d92bf8a8c4bbcbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Fri, 29 May 2026 08:54:17 +0200 Subject: [PATCH 5/8] Add containsAny --- src/Str.php | 20 ++++++++++++++++++++ tests/StrTest.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/Str.php b/src/Str.php index 7ae71e9..e33b48c 100644 --- a/src/Str.php +++ b/src/Str.php @@ -84,6 +84,26 @@ public static function contains(?string $subject, string $needle, bool $caseSens 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; + } + /** * Split string into an array padded to the size specified by limit * diff --git a/tests/StrTest.php b/tests/StrTest.php index 0cd8900..7ec68df 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -95,6 +95,40 @@ public function testContainsReturnsFalseIfStringDoesNotContainTheSpecifiedSubstr )); } + 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 testIsEmptyReturnsTrueForNull() { $this->assertTrue(Str::isEmpty(null)); From 66e5826d7a8228ec389e8c4842d26aa61e19c262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Fri, 29 May 2026 08:54:26 +0200 Subject: [PATCH 6/8] Add containsAll --- src/Str.php | 20 ++++++++++++++++++++ tests/StrTest.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/Str.php b/src/Str.php index e33b48c..d969a6d 100644 --- a/src/Str.php +++ b/src/Str.php @@ -104,6 +104,26 @@ public static function containsAny(?string $haystack, array $needles, bool $case 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 * diff --git a/tests/StrTest.php b/tests/StrTest.php index 7ec68df..26cd8ac 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -129,6 +129,40 @@ public function testContainsAnyReturnsFalseIfStringContainsNoneOfTheSpecifiedSub ], 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)); From 6fc6881ae1e7ffbdcaf86d8f6b7e1d16b407fc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Fri, 29 May 2026 13:26:07 +0200 Subject: [PATCH 7/8] fixup! add test for `isEmpty('0')` --- tests/StrTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/StrTest.php b/tests/StrTest.php index 26cd8ac..a234c65 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -178,6 +178,11 @@ public function testIsEmptyReturnsTrueForStringWithLeadingAndTrailingWhitespace( $this->assertTrue(Str::isEmpty(' ')); } + public function testIsEmptyReturnsFalseForZero() + { + $this->assertFalse(Str::isEmpty('0')); + } + public function testIsEmptyReturnsFalseForNonEmptyString() { $this->assertFalse(Str::isEmpty('Warning')); From 02d2c9084c557b7465bd6b04973e66a145f3b7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Rie=C3=9F?= Date: Fri, 29 May 2026 13:29:28 +0200 Subject: [PATCH 8/8] fixup! added test for only whitespace --- tests/StrTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/StrTest.php b/tests/StrTest.php index a234c65..48c4598 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -178,6 +178,11 @@ public function testIsEmptyReturnsTrueForStringWithLeadingAndTrailingWhitespace( $this->assertTrue(Str::isEmpty(' ')); } + public function testIsEmptyReturnsTrueForStringWithOnlyWhitespace() + { + $this->assertTrue(Str::isEmpty("\t\n")); + } + public function testIsEmptyReturnsFalseForZero() { $this->assertFalse(Str::isEmpty('0'));