PHP 7.4: Call to undefined function str_starts_with()
The symptom is a blank page. Sometimes a 500. Nothing in the browser, nothing obviously wrong in the code. Then you find the log:
PHP Fatal error: Uncaught Error: Call to undefined function str_starts_with()
The function exists. You’ve used it a hundred times. It just doesn’t exist on this server, because str_starts_with() was added in PHP 8.0, and a lot of NAS boxes, shared hosts and long-lived corporate servers are still on 7.4.
Why this keeps happening now
This bug has gotten much more common for a specific reason: if you write code with an AI assistant, it will default to modern PHP. It has no way to know your server is four years behind unless you tell it. Every model I’ve used writes PHP 8 by default, confidently, and the result runs perfectly on your laptop.
I hit this repeatedly building internal tools for a hospital, where the server is a NAS pinned to PHP 7.4 and not moving.
The four that break PHP 7.4
These are the ones I actually tripped over, in order of how often:
// 1. String helpers — PHP 8.0
str_starts_with($s, 'abc');
str_ends_with($s, 'xyz');
str_contains($s, 'mid');
// 2. match expression — PHP 8.0
$label = match($code) { 1 => 'open', 2 => 'closed', default => '?' };
// 3. Nullsafe operator — PHP 8.0
$name = $user?->profile?->name;
// 4. Named arguments and constructor promotion — PHP 8.0
class T { public function __construct(private string $id) {} }
The nasty one is match, because it’s a parse error, not a runtime error. A parse error means the whole file fails to compile, so the failure isn’t at the line you wrote — it’s the entire file going dark, including the part that would have logged something useful.
The 7.4-safe versions
// str_starts_with / str_ends_with / str_contains
substr($s, 0, strlen($p)) === $p; // starts with
substr($s, -strlen($p)) === $p; // ends with
strpos($s, $needle) !== false; // contains
// match -> switch
switch ($code) {
case 1: $label = 'open'; break;
case 2: $label = 'closed'; break;
default: $label = '?';
}
// nullsafe -> explicit checks
$name = ($user && $user->profile) ? $user->profile->name : null;
If you want the modern names without the modern PHP, define them once and include that file everywhere:
if (!function_exists('str_starts_with')) {
function str_starts_with($h, $n) { return substr($h, 0, strlen($n)) === $n; }
}
That’s the version I use, because it means AI-written code keeps working without me auditing every function call.
Catch it before the server does
The check that actually pays for itself is a lint pass against your real target version, not your laptop’s:
# run this against the version the server runs, not the one you have
php7.4 -l path/to/file.php
-l only parses — it won’t catch str_starts_with, because that’s a runtime error. For the runtime side, grep is enough and takes a second:
grep -rnE 'str_starts_with|str_ends_with|str_contains|\?->|match\s*\(' --include='*.php' .
I run that before every deploy now. It has never taken more than two seconds and it has caught real breakage more than once.
The part worth generalizing
The reason this class of bug hurts isn’t difficulty — every fix above is trivial. It’s that the failure appears at deploy time, in an environment you’re not looking at, with the log in a place you have to go find. Locally, everything is green.
Anything that only fails in an environment you don’t watch is effectively an unmonitored assumption. Pin the version somewhere your tooling can see it, and check against that, not against whatever your machine happens to have installed.
Comments
Loading comments…