str_contains takes one needle, deliberately, and the array version everybody wants was rejected during the RFC.
str_contains($haystack, 'admin'); // bool
str_starts_with($path, '/api/');
str_ends_with($file, '.php');
// what does not exist, and will not:
// str_contains($haystack, ['admin', 'editor']);
// the honest version
$found = array_filter(
$needles,
static fn (string $n): bool => str_contains($haystack, $n)
);
The argument against an array form was ambiguity — any or all — and the resulting function would have needed a flag, at which point it is not simpler than the loop. The three functions do fix the real problem, which was strpos($h, $n) !== false being wrong in a specific way every few years: a needle at position zero is falsy, and the !== false that guards against it is omitted often enough to be a recurring bug class.