diff --git a/builtin-functions/_functions.txt b/builtin-functions/_functions.txt index af35a1d43f..2c71ffee3e 100644 --- a/builtin-functions/_functions.txt +++ b/builtin-functions/_functions.txt @@ -663,6 +663,7 @@ function substr_compare ($main_str ::: string, $str ::: string, $offset ::: int, function str_starts_with ($haystack ::: string, $needle ::: string) ::: bool; function str_ends_with ($haystack ::: string, $needle ::: string) ::: bool; +function str_contains ($haystack ::: string, $needle ::: string) ::: bool; function trim ($s ::: string, $what ::: string = " \n\r\t\v\0") ::: string; function ltrim ($s ::: string, $what ::: string = " \n\r\t\v\0") ::: string; diff --git a/runtime/string.inl b/runtime/string.inl index 03ac0cf420..f3812a20e8 100644 --- a/runtime/string.inl +++ b/runtime/string.inl @@ -344,6 +344,20 @@ bool string::ends_with(const string &other) const noexcept { return other.size() > size() ? false : !memcmp(c_str() + (size() - other.size()), other.c_str(), other.size()); } +bool string::contains(const string &other) const noexcept { + if (other.size() > size()) { + return false; + } + + for (size_type i = 0; i < (size() - other.size() + 1); i++) { + if (memcmp(c_str() + i, other.c_str(), other.size()) == 0) { + return true; + } + } + + return false; +} + const char &string::operator[](size_type pos) const { return p[pos]; } diff --git a/runtime/string_decl.inl b/runtime/string_decl.inl index 6a51c582be..334b88069c 100644 --- a/runtime/string_decl.inl +++ b/runtime/string_decl.inl @@ -96,6 +96,7 @@ public: inline bool empty() const; inline bool starts_with(const string &other) const noexcept; inline bool ends_with(const string &other) const noexcept; + inline bool contains(const string &other) const noexcept; inline const char &operator[](size_type pos) const; inline char &operator[](size_type pos); diff --git a/runtime/string_functions.cpp b/runtime/string_functions.cpp index 7c483b077d..7ed984dcad 100644 --- a/runtime/string_functions.cpp +++ b/runtime/string_functions.cpp @@ -2327,6 +2327,10 @@ bool f$str_ends_with(const string &haystack, const string &needle) { return haystack.ends_with(needle); } +bool f$str_contains(const string &haystack, const string &needle) { + return haystack.contains(needle); +} + string f$trim(const string &s, const string &what) { const char *mask = get_mask(what); diff --git a/runtime/string_functions.h b/runtime/string_functions.h index 28f339c4d1..4f1e3b9f2f 100644 --- a/runtime/string_functions.h +++ b/runtime/string_functions.h @@ -236,6 +236,8 @@ bool f$str_starts_with(const string &haystack, const string &needle); bool f$str_ends_with(const string &haystack, const string &needle); +bool f$str_contains(const string &haystack, const string &needle); + string f$trim(const string &s, const string &what = WHAT); string f$ucfirst(const string &str); diff --git a/tests/cpp/runtime/string-test.cpp b/tests/cpp/runtime/string-test.cpp index 7423fc95ab..53faec110c 100644 --- a/tests/cpp/runtime/string-test.cpp +++ b/tests/cpp/runtime/string-test.cpp @@ -56,6 +56,25 @@ TEST(string_test, test_ends_with) { ASSERT_FALSE(str.starts_with(string{"hello world!"})); } +TEST(string_test, test_contains) { + string empty_str{""}; + ASSERT_TRUE(empty_str.contains(string{""})); + ASSERT_FALSE(empty_str.contains(string{"a"})); + + string str{"hello world"}; + ASSERT_TRUE(str.contains(string{"hello"})); + ASSERT_TRUE(str.contains(string{"world"})); + ASSERT_TRUE(str.contains(string{"orld"})); + ASSERT_TRUE(str.contains(string{"o w"})); + ASSERT_TRUE(str.contains(string{"d"})); + ASSERT_TRUE(str.contains(string{""})); + + ASSERT_FALSE(str.contains(string{"hEllo"})); + ASSERT_FALSE(str.contains(string{"o W"})); + + ASSERT_FALSE(str.contains(string{"hello world!"})); +} + TEST(string_test, test_make_const_string_on_memory) { char mem[1024]; diff --git a/tests/python/tests/composer/php/test_autoload_files/lib/polyfills/file2.php b/tests/python/tests/composer/php/test_autoload_files/lib/polyfills/file2.php index 2c8d5e2cb8..c82eed9ac7 100644 --- a/tests/python/tests/composer/php/test_autoload_files/lib/polyfills/file2.php +++ b/tests/python/tests/composer/php/test_autoload_files/lib/polyfills/file2.php @@ -3,8 +3,10 @@ global $global_map; $global_map[__FILE__] = true; +#ifndef KPHP if (!function_exists('str_contains')) { function str_contains(string $haystack, string $needle): bool { return '' === $needle || false !== strpos($haystack, $needle); } } +#endif