在PHP中模拟LIKE

时间:2012-07-11 13:58:01

标签: php sql string pcre sql-like

有没有办法在PHP中使用相同的语法模拟SQL的LIKE运算符? (%_通配符以及通用$escape转义符号)? 所以有:

$value LIKE $string ESCAPE $escape

你可以有一个函数,在不使用数据库的情况下返回PHP的评估吗? (请考虑已设置$value$string$escape值。

4 个答案:

答案 0 :(得分:4)

这基本上是你如何实现这样的:

$input = '%ST!_ING_!%';
$value = 'ANYCHARS HERE TEST_INGS%';

// Mapping of wildcards to their PCRE equivalents
$wildcards = array( '%' => '.*?', '_' => '.');

// Escape character for preventing wildcard functionality on a wildcard
$escape = '!';

// Shouldn't have to modify much below this

$delimiter = '/'; // regex delimiter

// Quote the escape characters and the wildcard characters
$quoted_escape = preg_quote( $escape);
$quoted_wildcards = array_map( function( $el) { return preg_quote( $el); }, array_keys( $wildcards));

// Form the dynamic regex for the wildcards by replacing the "fake" wildcards with PRCE ones
$temp_regex = '((?:' . $quoted_escape . ')?)(' . implode( '|', $quoted_wildcards) . ')';

// Escape the regex delimiter if it's present within the regex
$wildcard_replacement_regex = $delimiter . str_replace( $delimiter, '\\' . $delimiter, $temp_regex) . $delimiter;

// Do the actual replacement
$regex = preg_replace_callback( $wildcard_replacement_regex, function( $matches) use( $wildcards) { return !empty( $matches[1]) ? preg_quote( $matches[2]) : $wildcards[$matches[2]]; }, preg_quote( $input)); 

// Finally, test the regex against the input $value, escaping the delimiter if it's present
preg_match( $delimiter . str_replace( $delimiter, '\\' . $delimiter, $regex) . $delimiter .'i', $value, $matches);

// Output is in $matches[0] if there was a match
var_dump( $matches[0]);

这会形成一个基于$wildcards$escape的动态正则表达式,以便将所有“假”通配符替换为其PCRE等价物,除非“假”通配符以转义字符为前缀,在哪种情况下,不做替换。为了进行此替换,创建了$wildcard_replacement_regex

一旦所有事情都说完了,$wildcard_replacement_regex看起来就像这样:

/((?:\!)?)(%|_)/

因此它使用两个捕获组来(可选)获取转义字符和其中一个通配符。这使我们能够测试它是否抓住了回调中的转义字符。如果它能够在通配符之前获取转义字符,$matches[1]将包含转义字符。如果没有,$matches[1]将为空。这就是我如何确定是否用它的PCRE等效替换通配符,或者仅仅通过preg_quote()使它保持不变。

你可以玩它at codepad

答案 1 :(得分:3)

好的,经过这么多的有趣和游戏之后我就想到了:

function preg_sql_like ($input, $pattern, $escape = '\\') {

    // Split the pattern into special sequences and the rest
    $expr = '/((?:'.preg_quote($escape, '/').')?(?:'.preg_quote($escape, '/').'|%|_))/';
    $parts = preg_split($expr, $pattern, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

    // Loop the split parts and convert/escape as necessary to build regex
    $expr = '/^';
    $lastWasPercent = FALSE;
    foreach ($parts as $part) {
        switch ($part) {
            case $escape.$escape:
                $expr .= preg_quote($escape, '/');
                break;
            case $escape.'%':
                $expr .= '%';
                break;
            case $escape.'_':
                $expr .= '_';
                break;
            case '%':
                if (!$lastWasPercent) {
                    $expr .= '.*?';
                }
                break;
            case '_':
                $expr .= '.';
                break;
            default:
                $expr .= preg_quote($part, '/');
                break;
        }
        $lastWasPercent = $part == '%';
    }
    $expr .= '$/i';

    // Look for a match and return bool
    return (bool) preg_match($expr, $input);

}

我不能打破它,也许你可以找到一些会。我的主要方式与@ nickb不同的是,我将输入表达式“解析”(ish)为令牌以生成正则表达式,而不是将其转换为正则表达式。

该函数的前3个参数应该是相当自我解释的。 第四个允许您传递PCRE modifiers以影响用于匹配的最终正则表达式。我提出这个问题的主要原因是允许你传递i所以它不区分大小写 - 我想不出任何其他可以安全使用的修饰符,但可能不是这样。 删除了以下评论

函数只返回一个布尔值,表示$input文本是否与$pattern匹配。

Here's a codepad of it

编辑哎呀,已经破了,现在已经修好了。 New codepad

编辑删除了第四个参数,并根据以下评论使所有匹配不区分大小写

编辑一些小修补/改进:

  • 将字符串断言的开始/结束添加到生成的正则表达式
  • 添加了对最后一个令牌的跟踪,以避免生成的正则表达式中的多个.*?序列

答案 2 :(得分:1)

您可以使用regexp,例如:preg_match

答案 3 :(得分:0)

其他的例子对我来说有点过于复杂(而且我的清洁代码眼睛很痛苦),所以我用这个简单的方法重新实现了功能:

public function like($needle, $haystack, $delimiter = '~')
{
    // Escape meta-characters from the string so that they don't gain special significance in the regex
    $needle = preg_quote($needle, $delimiter);

    // Replace SQL wildcards with regex wildcards
    $needle = str_replace('%', '.*?', $needle);
    $needle = str_replace('_', '.', $needle);

    // Add delimiters, beginning + end of line and modifiers
    $needle = $delimiter . '^' . $needle . '$' . $delimiter . 'isu';

    // Matches are not useful in this case; we just need to know whether or not the needle was found.
    return (bool) preg_match($needle, $haystack);
}

Modifiers

  • i:忽略套管。
  • s:使点元字符匹配任何内容,包括换行符。
  • u:UTF-8兼容性。