How can I write two functions that would take a string and return if it starts with the specified character/string or ends with it?
For example:
$str = '|apples}';
echo startsWith($str, '|'); //Returns true
echo endsWith($str, '}'); //Returns true
s($str)->startsWith('|')
and s($str)->endsWith('}')
helpful, as found in this standalone library.
str_starts_with
and str_end_with
: stackoverflow.com/a/64160081/7082164
PHP 8.0 and higher
Since PHP 8.0 you can use the
str_starts_with
Manual and
str_ends_with
Manual
Example
echo str_starts_with($str, '|');
PHP before 8.0
function startsWith( $haystack, $needle ) {
$length = strlen( $needle );
return substr( $haystack, 0, $length ) === $needle;
}
function endsWith( $haystack, $needle ) {
$length = strlen( $needle );
if( !$length ) {
return true;
}
return substr( $haystack, -$length ) === $needle;
}
You can use substr_compare
function to check start-with and ends-with:
function startsWith($haystack, $needle) {
return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
function endsWith($haystack, $needle) {
return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
This should be one of the fastest solutions on PHP 7 (benchmark script). Tested against 8KB haystacks, various length needles and full, partial and no match cases. strncmp
is a touch faster for starts-with but it cannot check ends-with.
strrpos
which (should) fail immediately if needle does not match the beginning of haystack.
-strlength($haystack)
) and searching backward from there? Doesn't that mean you're not searching anything? I also don't understand the !== false
parts of this. I'm guessing this is relying on a quirk of PHP where some values are "truthy" and others "falsy" but how does that work in this case?
xxxyyy
needle = yyy
and using strrpos
the search starts from the first x
. Now we do not have a successful match here (found x instead of y) and we cannot go backward anymore (we're at start of string) the search fails immediately. About using !== false
-- strrpos
in the above example will return 0 or false and not other value. Likewise, strpos
in the above example can return $temp
(the expected position) or false. I went with !== false
for consistency but you could use === 0
and === $temp
in the functions respectively.
Updated 23-Aug-2016
Functions
function substr_startswith($haystack, $needle) {
return substr($haystack, 0, strlen($needle)) === $needle;
}
function preg_match_startswith($haystack, $needle) {
return preg_match('~' . preg_quote($needle, '~') . '~A', $haystack) > 0;
}
function substr_compare_startswith($haystack, $needle) {
return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
function strpos_startswith($haystack, $needle) {
return strpos($haystack, $needle) === 0;
}
function strncmp_startswith($haystack, $needle) {
return strncmp($haystack, $needle, strlen($needle)) === 0;
}
function strncmp_startswith2($haystack, $needle) {
return $haystack[0] === $needle[0]
? strncmp($haystack, $needle, strlen($needle)) === 0
: false;
}
Tests
echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
if($i % 2500 === 0) echo '.';
$test_cases[] = [
random_bytes(random_int(1, 7000)),
random_bytes(random_int(1, 3000)),
];
}
echo "done!\n";
$functions = ['substr_startswith', 'preg_match_startswith', 'substr_compare_startswith', 'strpos_startswith', 'strncmp_startswith', 'strncmp_startswith2'];
$results = [];
foreach($functions as $func) {
$start = microtime(true);
foreach($test_cases as $tc) {
$func(...$tc);
}
$results[$func] = (microtime(true) - $start) * 1000;
}
asort($results);
foreach($results as $func => $time) {
echo "$func: " . number_format($time, 1) . " ms\n";
}
Results (PHP 7.0.9)
(Sorted fastest to slowest)
strncmp_startswith2: 40.2 ms
strncmp_startswith: 42.9 ms
substr_compare_startswith: 44.5 ms
substr_startswith: 48.4 ms
strpos_startswith: 138.7 ms
preg_match_startswith: 13,152.4 ms
Results (PHP 5.3.29)
(Sorted fastest to slowest)
strncmp_startswith2: 477.9 ms
strpos_startswith: 522.1 ms
strncmp_startswith: 617.1 ms
substr_compare_startswith: 706.7 ms
substr_startswith: 756.8 ms
preg_match_startswith: 10,200.0 ms
function startswith5b($haystack, $needle) {return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;}
I added a reply below.
$haystack[0]
will throw a notice error if you don't test it with isset. The same for needles. But if you add tests, it will slow down its performance
All answers so far seem to do loads of unnecessary work, strlen calculations
, string allocations (substr)
, etc. The 'strpos'
and 'stripos'
functions return the index of the first occurrence of $needle
in $haystack
:
function startsWith($haystack,$needle,$case=true)
{
if ($case)
return strpos($haystack, $needle, 0) === 0;
return stripos($haystack, $needle, 0) === 0;
}
function endsWith($haystack,$needle,$case=true)
{
$expectedPosition = strlen($haystack) - strlen($needle);
if ($case)
return strrpos($haystack, $needle, 0) === $expectedPosition;
return strripos($haystack, $needle, 0) === $expectedPosition;
}
endsWith()
function has an error. Its first line should be (without the -1): $expectedPosition = strlen($haystack) - strlen($needle);
strpos($haystack, "$needle", 0)
if there's any chance it's not already a string (e.g., if it's coming from json_decode()
). Otherwise, the [odd] default behavior of strpos()
may cause unexpected results: "If needle is not a string, it is converted to an integer and applied as the ordinal value of a character."
function startsWith($haystack, $needle, $case = true) {
if ($case) {
return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
}
return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
}
function endsWith($haystack, $needle, $case = true) {
if ($case) {
return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
}
return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
}
Credit To:
Check if a string ends with another string
Check if a string begins with another string
PHP 8 update
PHP 8 includes new str_starts_with
and str_ends_with
functions that finally provide a performant and convenient solution to this problem:
$str = "beginningMiddleEnd";
if (str_starts_with($str, "beg")) echo "printed\n";
if (str_starts_with($str, "Beg")) echo "not printed\n";
if (str_ends_with($str, "End")) echo "printed\n";
if (str_ends_with($str, "end")) echo "not printed\n";
The RFC for this feature provides more information, and also a discussion of the merits and problems of obvious (and not-so-obvious) userland implementations.
This question already has many answers, but in some cases you can settle for something simpler than all of them. If the string you're looking for is known (hardcoded), you can use regular expressions without any quoting etc.
Check if a string starts with 'ABC':
preg_match('/^ABC/', $myString); // "^" here means beginning of string
ends with 'ABC':
preg_match('/ABC$/', $myString); // "$" here means end of string
In my simple case, I wanted to check if a string ends with slash:
preg_match('#/$#', $myPath); // Use "#" as delimiter instead of escaping slash
The advantage: since it's very short and simple, you don't have to define a function (such as endsWith()
) as shown above.
But again -- this is not a solution for every case, just this very specific one.
The regex functions above, but with the other tweaks also suggested above:
function startsWith($needle, $haystack) {
return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack);
}
function endsWith($needle, $haystack) {
return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
}
Fastest endsWith() solution:
# Checks if a string ends in a string
function endsWith($haystack, $needle) {
return substr($haystack,-strlen($needle))===$needle;
}
Benchmark:
# This answer
function endsWith($haystack, $needle) {
return substr($haystack,-strlen($needle))===$needle;
}
# Accepted answer
function endsWith2($haystack, $needle) {
$length = strlen($needle);
return $length === 0 ||
(substr($haystack, -$length) === $needle);
}
# Second most-voted answer
function endsWith3($haystack, $needle) {
// search forward starting from end minus needle length characters
if ($needle === '') {
return true;
}
$diff = \strlen($haystack) - \strlen($needle);
return $diff >= 0 && strpos($haystack, $needle, $diff) !== false;
}
# Regex answer
function endsWith4($haystack, $needle) {
return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
}
function timedebug() {
$test = 10000000;
$time1 = microtime(true);
for ($i=0; $i < $test; $i++) {
$tmp = endsWith('TestShortcode', 'Shortcode');
}
$time2 = microtime(true);
$result1 = $time2 - $time1;
for ($i=0; $i < $test; $i++) {
$tmp = endsWith2('TestShortcode', 'Shortcode');
}
$time3 = microtime(true);
$result2 = $time3 - $time2;
for ($i=0; $i < $test; $i++) {
$tmp = endsWith3('TestShortcode', 'Shortcode');
}
$time4 = microtime(true);
$result3 = $time4 - $time3;
for ($i=0; $i < $test; $i++) {
$tmp = endsWith4('TestShortcode', 'Shortcode');
}
$time5 = microtime(true);
$result4 = $time5 - $time4;
echo $test.'x endsWith: '.$result1.' seconds # This answer<br>';
echo $test.'x endsWith2: '.$result4.' seconds # Accepted answer<br>';
echo $test.'x endsWith3: '.$result2.' seconds # Second most voted answer<br>';
echo $test.'x endsWith4: '.$result3.' seconds # Regex answer<br>';
exit;
}
timedebug();
Benchmark Results:
10000000x endsWith: 1.5760900974274 seconds # This answer
10000000x endsWith2: 3.7102129459381 seconds # Accepted answer
10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer
10000000x endsWith4: 2.1521229743958 seconds # Regex answer
If speed is important for you, try this.(I believe it is the fastest method)
Works only for strings and if $haystack is only 1 character
function startsWithChar($needle, $haystack)
{
return ($needle === $haystack[0]);
}
function endsWithChar($needle, $haystack)
{
return ($needle === $haystack[strlen($haystack) - 1]);
}
$str='|apples}';
echo startsWithChar('|',$str); //Returns true
echo endsWithChar('}',$str); //Returns true
echo startsWithChar('=',$str); //Returns false
echo endsWithChar('#',$str); //Returns false
endsWithChar('','x')
, but the result is correct
Creative. Needles which contain haystacks.
... I didn't pay enough attention. Thanks! I fixed it. :)
Here are two functions that don't introduce a temporary string, which could be useful when needles are substantially big:
function startsWith($haystack, $needle)
{
return strncmp($haystack, $needle, strlen($needle)) === 0;
}
function endsWith($haystack, $needle)
{
return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
endsWidth
should do return $needle==='' || substr_compare(
... so it works as expected for -strlen($needle)===0
which, without the fix, makes endsWith('a','')
return false
substr_compare()
actually, so I've added a PR to fix that :)
endsWith('', 'foo')
triggers a Warning: “substr_compare(): The start position cannot exceed initial string length”. Maybe that's another bug in substr_compare()
, but to avoid it, you need a pre-check like ...|| (strlen($needle) <= strlen($haystack) && substr_compare(
...) === 0);
return $needle === '' || @substr_compare(
.. to suppress this warning.
I realize this has been finished, but you may want to look at strncmp as it allows you to put the length of the string to compare against, so:
function startsWith($haystack, $needle, $case=true) {
if ($case)
return strncasecmp($haystack, $needle, strlen($needle)) == 0;
else
return strncmp($haystack, $needle, strlen($needle)) == 0;
}
Here's a multi-byte safe version of the accepted answer, it works fine for UTF-8 strings:
function startsWith($haystack, $needle)
{
$length = mb_strlen($needle, 'UTF-8');
return (mb_substr($haystack, 0, $length, 'UTF-8') === $needle);
}
function endsWith($haystack, $needle)
{
$length = mb_strlen($needle, 'UTF-8');
return $length === 0 ||
(mb_substr($haystack, -$length, $length, 'UTF-8') === $needle);
}
startsWith
it should be $length = mb_strlen($needle, 'UTF-8');
You can use strpos
and strrpos
$bStartsWith = strpos($sHaystack, $sNeedle) == 0;
$bEndsWith = strrpos($sHaystack, $sNeedle) == strlen($sHaystack)-strlen($sNeedle);
strpos($sHaystack, $sNeedle) == 0
like this strpos($sHaystack, $sNeedle) === 0
? I see a bug, when false == 0
evaluates to true
.
Short and easy-to-understand one-liners without regular expressions.
startsWith() is straight forward.
function startsWith($haystack, $needle) {
return (strpos($haystack, $needle) === 0);
}
endsWith() uses the slightly fancy and slow strrev():
function endsWith($haystack, $needle) {
return (strpos(strrev($haystack), strrev($needle)) === 0);
}
Focusing on startswith, if you are sure strings are not empty, adding a test on the first char, before the comparison, the strlen, etc., speeds things up a bit:
function startswith5b($haystack, $needle) {
return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;
}
It is somehow (20%-30%) faster. Adding another char test, like $haystack{1}===$needle{1} does not seem to speedup things much, may even slow down.
===
seems faster than ==
Conditional operator (a)?b:c
seems faster than if(a) b; else c;
For those asking "why not use strpos?" calling other solutions "unnecessary work"
strpos is fast, but it is not the right tool for this job.
To understand, here is a little simulation as an example:
Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c
What the computer does "inside"?
With strccmp, etc...
is a===b? NO
return false
With strpos
is a===b? NO -- iterating in haysack
is a===c? NO
is a===d? NO
....
is a===g? NO
is a===g? NO
is a===a? YES
is 1===1? YES -- iterating in needle
is 2===3? YES
is 4===4? YES
....
is 8===8? YES
is c===x? NO: oh God,
is a===1? NO -- iterating in haysack again
is a===2? NO
is a===3? NO
is a===4? NO
....
is a===x? NO
is a===b? NO
is a===b? NO
is a===b? NO
is a===b? NO
is a===b? NO
is a===b? NO
is a===b? NO
...
... may many times...
...
is a===b? NO
is a===a? YES -- iterating in needle again
is 1===1? YES
is 2===3? YES
is 4===4? YES
is 8===8? YES
is c===c? YES YES YES I have found the same string! yay!
was it at position 0? NOPE
What you mean NO? So the string I found is useless? YEs.
Damn.
return false
Assuming strlen does not iterate the whole string (but even in that case) this is not convenient at all.
I hope that the below answer may be efficient and also simple:
$content = "The main string to search";
$search = "T";
//For compare the begining string with case insensitive.
if(stripos($content, $search) === 0) echo 'Yes';
else echo 'No';
//For compare the begining string with case sensitive.
if(strpos($content, $search) === 0) echo 'Yes';
else echo 'No';
//For compare the ending string with case insensitive.
if(stripos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';
//For compare the ending string with case sensitive.
if(strpos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';
I usually end up going with a library like underscore-php these days.
require_once("vendor/autoload.php"); //use if needed
use Underscore\Types\String;
$str = "there is a string";
echo( String::startsWith($str, 'the') ); // 1
echo( String::endsWith($str, 'ring')); // 1
The library is full of other handy functions.
The answer by mpen is incredibly thorough, but, unfortunately, the provided benchmark has a very important and detrimental oversight.
Because every byte in needles and haystacks is completely random, the probability that a needle-haystack pair will differ on the very first byte is 99.609375%, which means that, on average, about 99609 of the 100000 pairs will differ on the very first byte. In other words, the benchmark is heavily biased towards startswith
implementations which check the first byte explicitly, as strncmp_startswith2
does.
If the test-generating loop is instead implemented as follows:
echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
if($i % 2500 === 0) echo '.';
$haystack_length = random_int(1, 7000);
$haystack = random_bytes($haystack_length);
$needle_length = random_int(1, 3000);
$overlap_length = min(random_int(0, $needle_length), $haystack_length);
$needle = ($needle_length > $overlap_length) ?
substr($haystack, 0, $overlap_length) . random_bytes($needle_length - $overlap_length) :
substr($haystack, 0, $needle_length);
$test_cases[] = [$haystack, $needle];
}
echo " done!<br />";
the benchmark results tell a slightly different story:
strncmp_startswith: 223.0 ms
substr_startswith: 228.0 ms
substr_compare_startswith: 238.0 ms
strncmp_startswith2: 253.0 ms
strpos_startswith: 349.0 ms
preg_match_startswith: 20,828.7 ms
Of course, this benchmark may still not be perfectly unbiased, but it tests the efficiency of the algorithms when given partially matching needles as well.
in short:
function startsWith($str, $needle){
return substr($str, 0, strlen($needle)) === $needle;
}
function endsWith($str, $needle){
$length = strlen($needle);
return !$length || substr($str, - $length) === $needle;
}
Do it faster:
function startsWith($haystack,$needle) {
if($needle==="") return true;
if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!
return (0===substr_compare($haystack,$needle,0,strlen($needle)));
}
That extra line, comparing the first character of the strings, can make the false case return immediately, therefore making many of your comparisons a lot faster (7x faster when I measured). In the true case you pay virtually no price in performance for that single line so I think it's worth including. (Also, in practice, when you test many strings for a specific starting chunk, most comparisons will fail since in a typical case you're looking for something.)
NOTE: the bug in @Tino's comment below has aleady been fixed
As for strings vs integers
If you want to force string comparison (that is, you expect startsWith("1234",12) to be true), you'll need some typecasting:
function startsWith($haystack,$needle) {
if($needle==="") return true;
$haystack = (string)$haystack;
$needle = (string)$needle;
if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!
return (0===substr_compare($haystack,$needle,0,strlen($needle)));
}
I don't think it's necessary but it's an interesting edge case, leading to questions like "does boolean true begin with a t?" - so you decide, but make sure you decide for good.
startsWith("123", "0")
gives true
This may work
function startsWith($haystack, $needle) {
return substr($haystack, 0, strlen($needle)) == $needle;
}
Source: https://stackoverflow.com/a/4419658
The substr
function can return false
in many special cases, so here is my version, which deals with these issues:
function startsWith( $haystack, $needle ){
return $needle === ''.substr( $haystack, 0, strlen( $needle )); // substr's false => empty string
}
function endsWith( $haystack, $needle ){
$len = strlen( $needle );
return $needle === ''.substr( $haystack, -$len, $len ); // ! len=0
}
Tests (true
means good):
var_dump( startsWith('',''));
var_dump( startsWith('1',''));
var_dump(!startsWith('','1'));
var_dump( startsWith('1','1'));
var_dump( startsWith('1234','12'));
var_dump(!startsWith('1234','34'));
var_dump(!startsWith('12','1234'));
var_dump(!startsWith('34','1234'));
var_dump('---');
var_dump( endsWith('',''));
var_dump( endsWith('1',''));
var_dump(!endsWith('','1'));
var_dump( endsWith('1','1'));
var_dump(!endsWith('1234','12'));
var_dump( endsWith('1234','34'));
var_dump(!endsWith('12','1234'));
var_dump(!endsWith('34','1234'));
Also, the substr_compare
function also worth looking. http://www.php.net/manual/en/function.substr-compare.php
Why not the following?
//How to check if a string begins with another string
$haystack = "valuehaystack";
$needle = "value";
if (strpos($haystack, $needle) === 0){
echo "Found " . $needle . " at the beginning of " . $haystack . "!";
}
Output:
Found value at the beginning of valuehaystack!
Keep in mind, strpos
will return false if the needle was not found in the haystack, and will return 0 if, and only if, needle was found at index 0 (AKA the beginning).
And here's endsWith:
$haystack = "valuehaystack";
$needle = "haystack";
//If index of the needle plus the length of the needle is the same length as the entire haystack.
if (strpos($haystack, $needle) + strlen($needle) === strlen($haystack)){
echo "Found " . $needle . " at the end of " . $haystack . "!";
}
In this scenario there is no need for a function startsWith() as
(strpos($stringToSearch, $doesItStartWithThis) === 0)
will return true or false accurately.
It seems odd it's this simple with all the wild functions running rampant here.
strpos()
will be slow except when it does match. strncmp()
would be much better in this case.
I would do it like this
function startWith($haystack,$needle){
if(substr($haystack,0, strlen($needle))===$needle)
return true;
}
function endWith($haystack,$needle){
if(substr($haystack, -strlen($needle))===$needle)
return true;
}
Based on James Black's answer, here is its endsWith version:
function startsWith($haystack, $needle, $case=true) {
if ($case)
return strncmp($haystack, $needle, strlen($needle)) == 0;
else
return strncasecmp($haystack, $needle, strlen($needle)) == 0;
}
function endsWith($haystack, $needle, $case=true) {
return startsWith(strrev($haystack),strrev($needle),$case);
}
Note: I have swapped the if-else part for James Black's startsWith function, because strncasecmp is actually the case-insensitive version of strncmp.
strrev()
is creative but very costly, especially if you have strings of say... 100Kb.
===
instead of ==
to be sure. 0
is equal to a lot of things in PHP.
Many of the previous answers will work just as well. However, this is possibly as short as you can make it and have it do what you desire. You just state that you'd like it to 'return true'. So I've included solutions that returns boolean true/false and the textual true/false.
// boolean true/false
function startsWith($haystack, $needle)
{
return strpos($haystack, $needle) === 0 ? 1 : 0;
}
function endsWith($haystack, $needle)
{
return stripos($haystack, $needle) === 0 ? 1 : 0;
}
// textual true/false
function startsWith($haystack, $needle)
{
return strpos($haystack, $needle) === 0 ? 'true' : 'false';
}
function endsWith($haystack, $needle)
{
return stripos($haystack, $needle) === 0 ? 'true' : 'false';
}
'true'
and 'false'
as strings, which are both true
in a boolean sense. It's a good pattern for something like underhanded.xcott.com though ;)
No-copy and no-intern-loop:
function startsWith(string $string, string $start): bool
{
return strrpos($string, $start, - strlen($string)) !== false;
}
function endsWith(string $string, string $end): bool
{
return ($offset = strlen($string) - strlen($end)) >= 0
&& strpos($string, $end, $offset) !== false;
}
Here’s an efficient solution for PHP 4. You could get faster results if on PHP 5 by using substr_compare
instead of strcasecmp(substr(...))
.
function stringBeginsWith($haystack, $beginning, $caseInsensitivity = false)
{
if ($caseInsensitivity)
return strncasecmp($haystack, $beginning, strlen($beginning)) === 0;
else
return strncmp($haystack, $beginning, strlen($beginning)) === 0;
}
function stringEndsWith($haystack, $ending, $caseInsensitivity = false)
{
if ($caseInsensitivity)
return strcasecmp(substr($haystack, strlen($haystack) - strlen($ending)), $haystack) === 0;
else
return strpos($haystack, $ending, strlen($haystack) - strlen($ending)) !== false;
}
You also can use regular expressions:
function endsWith($haystack, $needle, $case=true) {
return preg_match("/.*{$needle}$/" . (($case) ? "" : "i"), $haystack);
}
preg_quote($needle, '/')
.
Success story sharing
return substr($haystack, -strlen($needle))===$needle;
$needle
is not empty.if
altogether by passing$length
as the third parameter tosubstr
:return (substr($haystack, -$length, $length);
. This handles the case of$length == 0
by returning an empty string and not the whole$haystack
.