PHP: How do you check whether a string starts or ends with something?

You often face the requirement of determining whether a string starts or ends with a particular substring. For example whether a URL starts with https://, or whether a file name ends in .php.

From PHP 8 onwards

Since PHP 8.0 there are two functions for this: str_starts_with and str_ends_with. Both are case-sensitive:

<?php
$url = 'https://www.hostinggutscheine.de/blog/';

if (str_starts_with($url, 'https://')) {
  // The following line IS executed.
  echo "The URL starts with 'https://'.\n";
}

if (str_ends_with($url, '/')) {
  // The following line IS executed.
  echo "The URL ends with a slash.\n";
}

if (str_starts_with($url, 'HTTPS://')) {
  // The following line is NOT executed.
  echo "The URL starts with 'HTTPS://'.\n";
}
?>

If case should not matter, convert both strings to lower case beforehand. Use the strtolower function for that:

<?php
if (str_starts_with(strtolower($url), 'https://')) {
  // The following line IS executed.
  echo "The URL starts with 'https://' (regardless of case).\n";
}
?>

An empty needle returns true from both functions. That is the mathematically clean answer, since every string starts and ends with nothing:

str_starts_with('abc', '')   => true
str_ends_with('abc', '')     => true

Before PHP 8

If you are running an older version of PHP you have to write the two functions yourself. For the start of a string that is unproblematic:

<?php
function startsWith($haystack, $needle) {
  return substr($haystack, 0, strlen($needle)) === $needle;
}
?>

For the end, however, there is a trap. This short form is very common:

<?php
function endsWith($haystack, $needle) {
  // Careful, this variant is buggy!
  return substr($haystack, -strlen($needle)) === $needle;
}
?>

If the needle is empty then strlen($needle) is 0 and the call reads substr($haystack, -0). And -0 in PHP is simply 0. So substr() does not return the empty string, it returns the whole string:

substr($url, -0)   => 'https://www.hostinggutscheine.de/blog/'

In that case the function answers false, even though true would be correct. You therefore need an explicit check:

<?php
function endsWith($haystack, $needle) {
  $length = strlen($needle);

  if ($length === 0) {
    return true;
  }

  return substr($haystack, -$length) === $needle;
}
?>

Why not strpos?

You often come across the suggestion of using strpos for the start of a string:

<?php
if (strpos($haystack, $needle) === 0) {
  // ...
}
?>

That works, but it is the wrong tool. strpos() searches for the substring in the entire string, and only afterwards do you compare the position found with 0. Whether the string begins at position 0 is however already settled after comparing the first character.

I measured this. The haystack is 20,000 b characters followed by needle, and we search 20,000 times for needle at the start:

str_starts_with : 0.000 s
strpos === 0    : 0.002 s

In absolute terms that is very little. The point is a different one: the work strpos() does grows with the length of the string, the work str_starts_with() does not. With short strings you will not notice, with large documents you will.

What about accented characters?

The two functions compare byte by byte rather than character by character. You still do not need the mb_* variants:

str_starts_with('Über uns', 'Ü')   => true
strlen('Ü')                        => 2
mb_strlen('Ü')                     => 1

The Ü takes two bytes in UTF-8, yet the result is correct. The reason is that UTF-8 is prefix-free: the first byte of a character can never appear as a continuation byte of another character. A byte-wise comparison at the start or the end of a string therefore cannot land in the middle of a character.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).