inString() Helper Function - Check if substring is in string
I like YourCommonSense's unconditional casting of $needle as an array.

bumperbox's advice on preg_quote() is good and necessary.

bumperbox's patterns will have the desired accuracy because word boundaries (spelled with an 'a') when used with alternatives (|) must wrap each alternative or the alternatives must be grouped.  Otherwise, if you have a pattern like: /\bdog|cat|fish\b/ then doghouse, category, scat, and blowfish will be unintentionally matched.  (Bad Pattern Demo)  In this case, /\b(?:dog|cat|fish)\b/ takes fewer steps than /\bdog\b|\bcat\b|\bfish\b/.

Capture groups () will not be necessary for this task, so use a non-capturing group (?:).  The fullstring match will contain the substring.  Unnecessary capture groups bloat the output array (although only by one element in this case).

Different from the other answers, I have built in a shortcut to a non-regex method.  The rationale behind this is the general knowledge that regex functions are notoriously/usually slower than non-regex functions.  The cost of building the path to the non-regex processes is minimal -- just 3 conditions, only the 2nd condition uses a function call.  I suspect any gains in performance to only manifest when dealing with rather large haystacks.  Regardless, I think it is worth considering.

My function performs more checks than the others to prioritize building a lean, accurate pattern.

My function will return false or the matched needle string.

*For those who might not know the behavior of alternatives in regex, the match will be the alternative found with the lowest "offset".  Regex will not prioritize matching by the order of the alternatives.

Code: (PHP Demo)

function inString($needle, $haystack, $checkBoundaries = false, $caseSensitive = false){
    $needle=(array)$needle;  // unconditionally cast $needle to be an array

    // conditional shortcut to non-regex method:
    if(!$checkBoundaries &amp;&amp; sizeof($needle)==1){
        $needle=$needle[0];  // or current($needle) if not indexed
        if(!$caseSensitive){
            return stripos($haystack,$needle)!==false?$needle:false;
        }
        return strpos($haystack,$needle)!==false?$needle:false;
    }

    // build regex pattern
    foreach($needle as &amp;$word){
        $word=preg_quote($word,'/');  // escape regex-sensitive characters
    }
    $pattern=implode('|',$needle);  // pipe together the alternatives
    if($checkBoundaries){
        if(sizeof($needle)&gt;1){
            $pattern="(?:{$pattern})";  // non-capture group required
        }
        $pattern="\b{$pattern}\b";  // word boundaries required
    }
    $pattern="/{$pattern}/";  // add pattern delimiters
    if(!$caseSensitive){
        $pattern.='i';  // case-insensitive modifier required
    }
    // execute pattern and return string on true
    return preg_match($pattern, $haystack, $matches)?$matches[0]:false;
}




Alternatively, if you are seeking pattern accuracy with fewest function calls here is something new to consider:

Code #2: (Demo)

function inString($needle, $haystack, $checkBoundaries = false, $caseSensitive = false){
    $pattern='\Q'.implode('\E|\Q',(array)$needle).'\E';  // build "literal" alternatives
    if($checkBoundaries){
        $pattern="\b(?:{$pattern})\b";  // word boundaries required
    }
    $pattern="/{$pattern}/";  // wrap with pattern delimiters
    if(!$caseSensitive){
        $pattern.='i';  // case-insensitive modifier required
    }
    // execute pattern and return string on true
    return preg_match($pattern, $haystack, $matches)?$matches[0]:false;
}


Running conditional checks on $checkBoundaries and $caseSensitive is completely unavoidable for this task.  This second method will make just one function call: implode() to complete the pattern!


By utilizing an "escape sequence" (\Q and \E), I was able to remove the foreach() loop and iterated call of preg_quote()!  The addition of the escape sequence seems to have no impact on regex step count; it is just a different syntax.  The effect on the pattern is in some cases a visual improvement, and in other cases a detriment to pattern readability (though I don't think pattern readability is a focus for this task).  The above method wraps each alternative in \Q...\E whether it needs it or not.  To contrast, preg_quote() will (only as necessary) add \ before each and every character in its list . \ + * ? [ ^ ] $ ( ) { } = ! &lt; &gt; | : - /.  As I mentioned before either approach risks obfuscating the pattern.
In all fairness, this method builds a less lean pattern than my first method, but you can logically expect that it will construct pattern with the greatest efficiency and with minimal drag from preg_match().