Skip to content

/commands/php/str-contains-check-substring

php check if a string contains a substring (str_contains, strpos)

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
PHP
versions
str_contains: PHP 8.0+, strpos: PHP 4+
os
any

On PHP 8.0 and later, str_contains($haystack, $needle) returns true if $needle occurs anywhere in $haystack; it is case-sensitive. On PHP 7 and older use strpos($haystack, $needle) !== false — with !==, because strpos returns 0 when the match is at the very start, and 0 is falsy. For a case-insensitive test, use stripos(...) !== false.

Examples

The example runs PHP 8.4 compiled to WebAssembly (php-wasm) through a small php wrapper around its CLI:

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund @php-wasm/node@3.1.56 @php-wasm/universal@3.1.56 >/dev/null 2>&1
cat > php.mjs <<'EOF'
import fs from "node:fs";
import { PHP } from "@php-wasm/universal";
import { loadNodeRuntime } from "@php-wasm/node";
const php = new PHP(await loadNodeRuntime("8.4", { emscriptenOptions: { processId: 1 } }));
php.mkdir("/app");
for (const f of fs.readdirSync(".").filter((f) => f.endsWith(".php"))) php.writeFile(`/app/${f}`, fs.readFileSync(f));
php.chdir("/app");
const r = await php.cli(["php", ...process.argv.slice(2)]);
process.stdout.write(await r.stdoutText);
process.stdout.write(await r.stderrText);
process.exit(await r.exitCode);
EOF
cat > contains.php <<'EOF'
<?php
$s = "Error: file not found";
$show = fn ($v) => var_export($v, true);

echo "str_contains 'file':    ", $show(str_contains($s, "file")), "\n";
echo "str_contains 'error':   ", $show(str_contains($s, "error")), "\n";
echo "stripos 'error':        ", $show(stripos($s, "error") !== false), "\n";
echo "strpos 'Error':         ", $show(strpos($s, "Error")), "\n";
echo "if (strpos(...)) bug:   ", (strpos($s, "Error") ? "found" : "not found"), "\n";
echo "strpos !== false:       ", $show(strpos($s, "Error") !== false), "\n";
echo "empty needle:           ", $show(str_contains($s, "")), "\n";
echo "starts / ends with:     ", $show(str_starts_with($s, "Error")), " / ", $show(str_ends_with($s, "found")), "\n";
echo "mb_stripos 'ÄRGER':     ", $show(mb_stripos("Kein Ärger", "ÄRGER") !== false), "\n";
EOF
node php.mjs contains.php

Output

str_contains 'file':    true
str_contains 'error':   false
stripos 'error':        true
strpos 'Error':         0
if (strpos(...)) bug:   not found
strpos !== false:       true
empty needle:           true
starts / ends with:     true / true
mb_stripos 'ÄRGER':     true

The line marked "bug" is the classic mistake: strpos finds Error at position 0, the if treats 0 as false and reports "not found".

Which function to use

CodeCaseReturns
str_contains($s, $n)Sensitivebool (PHP 8.0+)
strpos($s, $n) !== falseSensitivebool, works on every PHP version
stripos($s, $n) !== falseInsensitive (ASCII letters)bool
mb_stripos($s, $n) !== falseInsensitive, multibyte awarebool (needs the mbstring extension)
str_starts_with($s, $n) / str_ends_with($s, $n)Sensitivebool (PHP 8.0+)
preg_match("/err(or)?/i", $s) === 1As the pattern saysUse for patterns, not for plain substrings

Pitfalls

  • == false or a plain if. strpos returns 0 for a match at the start; always compare with !== false.
  • PHP 7. Calling str_contains on PHP 7.4 or older fails with Call to undefined function str_contains(). Upgrade, use strpos, or add the polyfill package symfony/polyfill-php80.
  • Multibyte text. stripos folds only ASCII letters, so Ä and ä do not match; use mb_stripos. str_contains itself is byte-based and works correctly on UTF-8 for case-sensitive checks.
  • Argument order. The haystack comes first, the needle second, for all of these functions.

Confirm it worked

var_dump(str_contains(...)) prints bool(true) or bool(false); test a needle at position 0, as above, to make sure the check does not fall into the strpos trap.

FAQ

Is str_contains case-sensitive?

Yes. str_contains("Hello", "hello") is false. For a case-insensitive check use stripos($haystack, $needle) !== false, or mb_stripos for multibyte text.

Why does str_contains return true for an empty needle?

Every string contains the empty string, so str_contains($s, "") is always true, as the manual documents. Check that the needle is not empty first if it comes from user input.

How do I check the start or end of a string?

PHP 8.0 also added str_starts_with() and str_ends_with(), which return a bool like str_contains.

Sources