Skip to content

str_(starts|ends)_with variadic needle #18825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -2443,10 +2443,10 @@ function str_contains(string $haystack, string $needle): bool {}
* @compile-time-eval
* @frameless-function {"arity": 2}
*/
function str_starts_with(string $haystack, string $needle): bool {}
function str_starts_with(string $haystack, string ...$needle): bool {}

/** @compile-time-eval */
function str_ends_with(string $haystack, string $needle): bool {}
function str_ends_with(string $haystack, string ...$needle): bool {}

/**
* @compile-time-eval
Expand Down Expand Up @@ -3834,3 +3834,4 @@ function sapi_windows_set_ctrl_handler(?callable $handler, bool $add = true): bo

function sapi_windows_generate_ctrl_event(int $event, int $pid = 0): bool {}
#endif
//
9 changes: 6 additions & 3 deletions ext/standard/basic_functions_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 62 additions & 22 deletions ext/standard/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -1839,14 +1839,32 @@ ZEND_FRAMELESS_FUNCTION(str_contains, 2)
/* {{{ Checks if haystack starts with needle */
PHP_FUNCTION(str_starts_with)
{
zend_string *haystack, *needle;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(haystack)
Z_PARAM_STR(needle)
ZEND_PARSE_PARAMETERS_END();

RETURN_BOOL(zend_string_starts_with(haystack, needle));
zend_string *haystack;
zval *needles;
size_t num_needles;

ZEND_PARSE_PARAMETERS_START(2, -1)
Z_PARAM_STR(haystack)
Z_PARAM_VARIADIC('+', needles, num_needles)
ZEND_PARSE_PARAMETERS_END();

/* Check each needle */
for (size_t i = 0; i < num_needles; ++i) {
if (UNEXPECTED(Z_TYPE(needles[i]) != IS_STRING)) {
if (ZEND_ARG_USES_STRICT_TYPES()) {
zend_type_error("Argument %zu passed to str_starts_with() must be of type string, %s given",
i + 2, zend_zval_type_name(&needles[i]));
return;
} else {
convert_to_string(&needles[i]);
}
}
if (zend_string_starts_with(haystack, Z_STR(needles[i]))) {
RETURN_TRUE;
}
}

RETURN_FALSE;
}
/* }}} */

Expand All @@ -1868,20 +1886,42 @@ ZEND_FRAMELESS_FUNCTION(str_starts_with, 2)
/* {{{ Checks if haystack ends with needle */
PHP_FUNCTION(str_ends_with)
{
zend_string *haystack, *needle;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(haystack)
Z_PARAM_STR(needle)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) {
RETURN_FALSE;
}

RETURN_BOOL(memcmp(
ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - ZSTR_LEN(needle),
ZSTR_VAL(needle), ZSTR_LEN(needle)) == 0);
zend_string *haystack;
zval *needles;
size_t num_needles;

ZEND_PARSE_PARAMETERS_START(2, -1) /* 2 to unlimited args */
Z_PARAM_STR(haystack)
Z_PARAM_VARIADIC('+', needles, num_needles)
ZEND_PARSE_PARAMETERS_END();

size_t haystack_len = ZSTR_LEN(haystack);

for (size_t i = 0; i < num_needles; ++i) {
if (UNEXPECTED(Z_TYPE(needles[i]) != IS_STRING)) {
if (ZEND_ARG_USES_STRICT_TYPES()) {
zend_type_error("Argument %zu passed to str_ends_with() must be of type string, %s given",
i + 2, zend_zval_type_name(&needles[i]));
return;
} else {
convert_to_string(&needles[i]);
}
}
zend_string *needle = Z_STR(needles[i]);
size_t needle_len = ZSTR_LEN(needle);
if (needle_len > haystack_len) {
continue;
}
/* Check if haystack ends with this needle */
if (memcmp(
ZSTR_VAL(haystack) + haystack_len - needle_len,
ZSTR_VAL(needle),
needle_len) == 0) {
RETURN_TRUE;
}
}

RETURN_FALSE;
}
/* }}} */

Expand Down
19 changes: 19 additions & 0 deletions ext/standard/tests/strings/str_ends_with.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
str_ends_with() function - unit tests for str_ends_with()
--FILE--
<?php
declare(strict_types=1);
$testStr = "beginningMiddleEnd";
var_dump(str_ends_with($testStr, "End"));
var_dump(str_ends_with($testStr, "end"));
Expand All @@ -20,6 +21,24 @@ var_dump(str_ends_with("a\x00b", "d\x00b"));
var_dump(str_ends_with("a\x00b", "a\x00z"));
var_dump(str_ends_with("a", "\x00a"));
var_dump(str_ends_with("a", "a\x00"));
// now test varargs
if (!str_ends_with("foo", "a", "o")) {
throw new \Exception("str does not end with o");
}
try {
if (!str_ends_with("foo1", "a", "o", 1, "x")) {
throw new \Exception("str does not end with 1");
}
throw new \Exception("int(1) did not trigger TypeError");
} catch (\TypeError $e) {
// Expected TypeError due to int(1) + strict_types
}
if (!str_ends_with("foo1", "a", "o", "1", "x")) {
throw new \Exception("str does not end with 1");
}
if (str_ends_with("", "a", "o")) {
throw new \Exception("Empty string ends with something");
}
?>
--EXPECT--
bool(true)
Expand Down
19 changes: 19 additions & 0 deletions ext/standard/tests/strings/str_starts_with.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
str_starts_with() function - unit tests for str_starts_with()
--FILE--
<?php
declare(strict_types=1);
$testStr = "beginningMiddleEnd";
var_dump(str_starts_with($testStr, "beginning"));
var_dump(str_starts_with($testStr, "Beginning"));
Expand All @@ -20,6 +21,24 @@ var_dump(str_starts_with("a\x00b", "a\x00d"));
var_dump(str_starts_with("a\x00b", "z\x00b"));
var_dump(str_starts_with("a", "a\x00"));
var_dump(str_starts_with("a", "\x00a"));
// now test varargs
if (!str_starts_with("foo", "a", "f")) {
throw new \Exception("str does not start with f");
}
try {
if (!str_starts_with("1foo", "a", "f", 1, "x")) {
throw new \Exception("str does not start with 1");
}
throw new \Exception("int(1) did not trigger TypeError");
} catch (\TypeError $e) {
// Expected TypeError due to int(1) + strict_types
}
if (!str_starts_with("1foo", "a", "f", "1", "x")) {
throw new \Exception("str does not start with 1");
}
if (str_starts_with("", "a", "f")) {
throw new \Exception("Empty string starts with something");
}
?>
--EXPECT--
bool(true)
Expand Down