PHP
Web Development
Programming Languages
PHP Functions
Website Coding

startsWith() and endsWith() functions in PHP

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

PHP 8.0 introduced built-in str_starts_with() and str_ends_with() functions that check whether a string begins or ends with a given substring. Before PHP 8.0, developers had to implement these functions manually using substr(), strpos(), or strncmp(). Both functions are case-sensitive, perform binary-safe comparisons, and return true or false. This article covers the PHP 8.0+ built-in functions, pre-PHP 8 polyfills, and related string checking patterns.

PHP 8.0+ Built-in Functions

php
1// str_starts_with(string $haystack, string $needle): bool
2echo str_starts_with("Hello World", "Hello");  // true
3echo str_starts_with("Hello World", "World");  // false
4echo str_starts_with("Hello World", "");        // true (empty needle)
5
6// str_ends_with(string $haystack, string $needle): bool
7echo str_ends_with("Hello World", "World");    // true
8echo str_ends_with("Hello World", "Hello");    // false
9echo str_ends_with("Hello World", "");          // true (empty needle)
10
11// Practical examples
12$url = "https://example.com/api/users";
13if (str_starts_with($url, "https://")) {
14    echo "Secure connection";
15}
16
17$filename = "report.pdf";
18if (str_ends_with($filename, ".pdf")) {
19    echo "PDF file detected";
20}

Both functions return true for an empty needle — every string starts and ends with the empty string.

Also Added in PHP 8.0: str_contains()

php
1// str_contains(string $haystack, string $needle): bool
2echo str_contains("Hello World", "lo Wo");  // true
3echo str_contains("Hello World", "xyz");     // false
4
5// Before PHP 8.0, this required strpos
6if (strpos("Hello World", "lo Wo") !== false) {
7    // Found
8}

PHP 8.0 added all three functions together: str_starts_with(), str_ends_with(), and str_contains().

Pre-PHP 8.0 Polyfills

If you must support PHP 7.x, implement these functions manually:

php
1if (!function_exists('str_starts_with')) {
2    function str_starts_with(string $haystack, string $needle): bool {
3        return strncmp($haystack, $needle, strlen($needle)) === 0;
4    }
5}
6
7if (!function_exists('str_ends_with')) {
8    function str_ends_with(string $haystack, string $needle): bool {
9        if ($needle === '') {
10            return true;
11        }
12        return substr($haystack, -strlen($needle)) === $needle;
13    }
14}
15
16if (!function_exists('str_contains')) {
17    function str_contains(string $haystack, string $needle): bool {
18        return $needle === '' || strpos($haystack, $needle) !== false;
19    }
20}

strncmp() is the most efficient for prefix checking because it compares only the first N characters without extracting a substring.

Alternative Implementations (Pre-PHP 8)

php
1// Using substr()
2function startsWith(string $haystack, string $needle): bool {
3    return substr($haystack, 0, strlen($needle)) === $needle;
4}
5
6function endsWith(string $haystack, string $needle): bool {
7    $len = strlen($needle);
8    if ($len === 0) return true;
9    return substr($haystack, -$len) === $needle;
10}
11
12// Using strpos() — less efficient for starts_with
13function startsWithStrpos(string $haystack, string $needle): bool {
14    return strpos($haystack, $needle) === 0;
15}
16
17// Using regex — most flexible but slowest
18function startsWithRegex(string $haystack, string $needle): bool {
19    return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack) === 1;
20}

Case-Insensitive Variants

The built-in functions are case-sensitive. For case-insensitive matching:

php
1// Case-insensitive starts with
2function str_starts_with_ci(string $haystack, string $needle): bool {
3    return str_starts_with(strtolower($haystack), strtolower($needle));
4}
5
6// Case-insensitive ends with
7function str_ends_with_ci(string $haystack, string $needle): bool {
8    return str_ends_with(strtolower($haystack), strtolower($needle));
9}
10
11echo str_starts_with_ci("Hello World", "hello");  // true
12echo str_ends_with_ci("report.PDF", ".pdf");       // true
13
14// Or use strncasecmp for prefix (more efficient)
15function starts_with_case_insensitive(string $haystack, string $needle): bool {
16    return strncasecmp($haystack, $needle, strlen($needle)) === 0;
17}

Practical Examples

php
1// URL routing
2function routeRequest(string $path): string {
3    if (str_starts_with($path, "/api/")) {
4        return "API handler";
5    } elseif (str_starts_with($path, "/admin/")) {
6        return "Admin handler";
7    }
8    return "Default handler";
9}
10
11// File type detection
12function getFileType(string $filename): string {
13    return match(true) {
14        str_ends_with($filename, ".jpg"),
15        str_ends_with($filename, ".png") => "image",
16        str_ends_with($filename, ".pdf") => "document",
17        str_ends_with($filename, ".mp4") => "video",
18        default => "unknown"
19    };
20}
21
22// Checking multiple prefixes
23function hasValidProtocol(string $url): bool {
24    $protocols = ["http://", "https://", "ftp://"];
25    foreach ($protocols as $protocol) {
26        if (str_starts_with($url, $protocol)) {
27            return true;
28        }
29    }
30    return false;
31}
32
33// String cleaning
34function removePrefix(string $str, string $prefix): string {
35    if (str_starts_with($str, $prefix)) {
36        return substr($str, strlen($prefix));
37    }
38    return $str;
39}
40
41echo removePrefix("/api/v1/users", "/api/v1");  // "/users"

Common Pitfalls

  • Using strpos() === 0 instead of str_starts_with() on PHP 8+: While strpos($haystack, $needle) === 0 works for prefix checking, it scans the entire string and is less readable. str_starts_with() stops after comparing the prefix length and communicates intent more clearly.
  • Forgetting strict comparison with strpos(): In pre-PHP 8 code, if (strpos($str, $needle)) is wrong because strpos() returns 0 (falsy) when the needle is at position 0. You must use strpos($str, $needle) !== false with strict comparison.
  • Not handling empty needle: Both str_starts_with() and str_ends_with() return true for empty needles. If your logic should reject empty inputs, check $needle !== '' before calling these functions.
  • Using str_starts_with() on PHP versions below 8.0 without a polyfill: Calling str_starts_with() on PHP 7.x throws Call to undefined function. Either add a polyfill or check your PHP version with PHP_VERSION_ID >= 80000.
  • Assuming case-insensitive behavior: str_starts_with("Hello", "hello") returns false. Use strtolower() on both strings or strncasecmp() if you need case-insensitive matching.

Summary

  • PHP 8.0 added str_starts_with(), str_ends_with(), and str_contains() as built-in functions
  • All three are case-sensitive and return true for empty needles
  • For PHP 7.x, use polyfills based on strncmp() (prefix) and substr() (suffix)
  • Use strncasecmp() or strtolower() for case-insensitive matching
  • Prefer str_starts_with() over strpos() === 0 for readability and performance
  • Always use strict comparison (!== false) with strpos() in pre-PHP 8 code

Course illustration
Course illustration

All Rights Reserved.