PCRE Fnmatch() PHP Function for Wildcard String Match

Posted on August 30, 2020 at 12:14 pm

PHP function fnmatch() has 4096 characters limit:

Warning: fnmatch(): Filename exceeds the maximum allowed length of 4096 characters

To avoid these limits we can use a PCRE-like fnmatch() function:

https://www.php.net/manual/en/function.fnmatch.php#100207

Here is a modified PHP code found in the php.net website:

if (!function_exists('fnmatch')) {
    define('FNM_PATHNAME', 1);
    define('FNM_NOESCAPE', 2);
    define('FNM_PERIOD', 4);
    define('FNM_CASEFOLD', 16);
 
    function fnmatch($pattern, $string, $flags = 0) {
        return pcre_fnmatch($pattern, $string, $flags);
    }
}
 
function pcre_fnmatch($pattern, $string, $flags = 0) {
    $modifiers = null;
    $transforms = array(
        '\*'    => '.*',
        '\?'    => '.',
        '\[\!'    => '[^',
        '\['    => '[',
        '\]'    => ']',
        '\.'    => '\.',
        '\\'    => '\\\\'
    );
 
    // Forward slash in string must be in pattern:
    if ($flags & FNM_PATHNAME) {
        $transforms['\*'] = '[^/]*';
    }
 
    // Back slash should not be escaped:
    if ($flags & FNM_NOESCAPE) {
        unset($transforms['\\']);
    }
 
    // Perform case insensitive match:
    if ($flags & FNM_CASEFOLD) {
        $modifiers .= 'i';
    }
 
    // Period at start must be the same as pattern:
    if ($flags & FNM_PERIOD) {
        if (strpos($string, '.') === 0 && strpos($pattern, '.') !== 0) return false;
    }
 
    $pattern = '#^'
        . strtr(preg_quote($pattern, '#'), $transforms)
        . '$#'
        . $modifiers."s";
 
    return preg_match($pattern, $string) ? true : false;
}

What I added is just .”s” here: . $modifiers.”s”;

Another simple PHP function that can be used for wildcard string match is:

function match_wildcard($pattern, $string, $case_sensitive = false) {
    $modifiers = ($case_sensitive) ? "" : "i";
 
    return preg_match("#^".strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.'))."$#".$modifiers."s", $string) ? true : false;
}

By default it uses “i” modifier that means case insensitive.

Here is a test I made with the above two functions:

$test = "fo.ob/a[r b//a[r b/\/a[r a\yd blob\netc<>|)a@!\"£$%&/(z)=?.";
 
var_dump(
    match_wildcard('fo.o*', $test),     //bool(true)
    match_wildcard('ba#r*', $test),     //bool(false)
    match_wildcard('*b/a[r*', $test),   //bool(true)
    match_wildcard('**blob**', $test),  //bool(true)
    match_wildcard('*a?d*', $test),     //bool(false)
    match_wildcard('*etc**', $test),    //bool(true)
    match_wildcard('*a\y*', $test),     //bool(true)
    match_wildcard('*(z)=*', $test),    //bool(true)
    match_wildcard('*b//a[r*', $test),  //bool(true)
    match_wildcard('*b/\/a[r*', $test), //bool(true)
);
 
var_dump(
    pcre_fnmatch("fo.o*", $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(true)
    pcre_fnmatch('ba#r*', $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(false)
    pcre_fnmatch('*b/a[r*', $test, FNM_NOESCAPE | FNM_CASEFOLD),   //bool(true)
    pcre_fnmatch('**blob**', $test, FNM_NOESCAPE | FNM_CASEFOLD),  //bool(true)
    pcre_fnmatch('*a?d*', $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(false)
    pcre_fnmatch('*etc**', $test, FNM_NOESCAPE | FNM_CASEFOLD),    //bool(true)
    pcre_fnmatch('*a\y*', $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(true)
    pcre_fnmatch('*(z)=*', $test, FNM_NOESCAPE | FNM_CASEFOLD),    //bool(true)
    pcre_fnmatch('*b//a[r*', $test, FNM_NOESCAPE | FNM_CASEFOLD),  //bool(true)
    pcre_fnmatch('*b/\/a[r*', $test, FNM_NOESCAPE | FNM_CASEFOLD), //bool(true)
);
 
var_dump(
    fnmatch("fo.o*", $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(true)
    fnmatch('ba#r*', $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(false)
    fnmatch('*b/a[r*', $test, FNM_NOESCAPE | FNM_CASEFOLD),   //bool(true)
    fnmatch('**blob**', $test, FNM_NOESCAPE | FNM_CASEFOLD),  //bool(true)
    fnmatch('*a?d*', $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(false)
    fnmatch('*etc**', $test, FNM_NOESCAPE | FNM_CASEFOLD),    //bool(true)
    fnmatch('*a\y*', $test, FNM_NOESCAPE | FNM_CASEFOLD),     //bool(true)
    fnmatch('*(z)=*', $test, FNM_NOESCAPE | FNM_CASEFOLD),    //bool(true)
    fnmatch('*b//a[r*', $test, FNM_NOESCAPE | FNM_CASEFOLD),  //bool(true)
    fnmatch('*b/\/a[r*', $test, FNM_NOESCAPE | FNM_CASEFOLD), //bool(true)
);

Receive updates via email

Other Posts

Updated Posts