PHP DISCUSSION

PHP strpos() check fails when the match is at the start of the string: == versus === explained

Started by siva PHP strposstrict comparisonloose comparisonstr_containstype juggling
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

PHP strpos() check fails when the match is at the start of the string: == versus === explained

siva PHP Forum
#1

My script looks for a status keyword in a line received from a serial gateway: if (strpos($line, 'ERR')) { ... }. It detects ERR in the middle of a line but not when the line begins with it, which is the case I care about. A related check, if ($code == 'abc'), used to be true when $code was the integer 0.

What is going on with these comparisons, and what is the correct way to test whether a string contains or starts with another string?

Community replies 5

Re: PHP strpos() check fails when the match is at the start of the string: == versus === explained

#2

strpos returns the position of the first match as an integer, or false when there is none. A match at the start is position 0, and in a boolean context 0 is falsy, so your if treats "found at the beginning" the same as "not found". Compare with the strict operator: if (strpos($line, 'ERR') !== false). !== checks type as well as value, so integer 0 and boolean false are different.

To test specifically for a prefix: strpos($line, 'ERR') === 0.

Re: PHP strpos() check fails when the match is at the start of the string: == versus === explained

#3

On PHP 8 there are dedicated functions that return a real boolean and remove the trap: str_contains($line, 'ERR'), str_starts_with($line, 'ERR') and str_ends_with($line, "\r\n"). They are case-sensitive. For a case-insensitive test use stripos($line, 'err') !== false.

If you need a whole word and not a fragment, so that ERR does not match inside OVERRUN, use a regular expression with word boundaries: preg_match('/\bERR\b/', $line) === 1.

Re: PHP strpos() check fails when the match is at the start of the string: == versus === explained

#4

The second symptom is loose comparison. == converts the operands to a common type before comparing; === does not convert. Before PHP 8, comparing a number with a non-numeric string converted the string to a number, so 0 == 'abc' was true because 'abc' became 0.

PHP 8 changed this: the number is converted to a string instead, and 0 == 'abc' is now false. Code that must behave the same on both should not rely on either rule; use ===.

Re: PHP strpos() check fails when the match is at the start of the string: == versus === explained

#5

Even on PHP 8, loose comparison of two numeric strings is numeric: '1' == '01' is true and '10' == '1e1' is true, while 'abc' == 'ABC' is false.

The dangerous case is comparing hashes or tokens: two different strings that both look like 0e followed by digits are each read as zero in scientific notation and compare equal with ==. Compare secrets with hash_equals($expected, $given), which is also constant-time, and everything else with ===.

Re: PHP strpos() check fails when the match is at the start of the string: == versus === explained

#6

The same loose rules hide inside other constructs. in_array($value, $list) and array_search compare loosely unless you pass true as the third argument. switch compares loosely; the match expression in PHP 8 compares strictly. empty('0') is true, so a form field containing the digit zero counts as empty.

A simple policy covers all of it: === and !== by default, strict flags on the array functions, and an explicit cast such as (int) $input at the point where external text becomes a number.

TEP COMMUNITY