yiisoft/yii2 · error · InvalidArgumentException

If exclude/include pattern is an array it must contain the p

Error message

If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.

What it means

When FileHelper filters paths (findFiles/findDirectories/copyDirectory/removeDirectory with 'only'/'except' options), each pattern may be a string or an already-parsed array with exactly the keys 'pattern', 'flags' and 'firstWildcard' — the shape produced internally by parseExcludePattern(). lastExcludeMatchingFromList() re-validates that shape on every entry and throws InvalidArgumentException if any of the three keys is missing. Hand-written or truncated pattern arrays are the typical cause.

Source

Thrown at framework/helpers/BaseFileHelper.php:840

     * any, determines the fate.  Returns the element which
     * matched, or null for undecided.
     *
     * Based on last_exclude_matching_from_list() from dir.c of git 1.8.5.3 sources.
     *
     * @param string $basePath
     * @param string $path
     * @param array $excludes list of patterns to match $path against
     * @return array|null null or one of $excludes item as an array with keys: 'pattern', 'flags'
     * @throws InvalidArgumentException if any of the exclude patterns is not a string or an array with keys: pattern, flags, firstWildcard.
     */
    private static function lastExcludeMatchingFromList($basePath, $path, $excludes)
    {
        foreach (array_reverse($excludes) as $exclude) {
            if (is_string($exclude)) {
                $exclude = self::parseExcludePattern($exclude, false);
            }
            if (!isset($exclude['pattern']) || !isset($exclude['flags']) || !isset($exclude['firstWildcard'])) {
                throw new InvalidArgumentException('If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.');
            }
            if ($exclude['flags'] & self::PATTERN_MUSTBEDIR && !is_dir($path)) {
                continue;
            }

            if ($exclude['flags'] & self::PATTERN_NODIR) {
                if (self::matchBasename(basename($path), $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {
                    return $exclude;
                }
                continue;
            }

            if (self::matchPathname($path, $basePath, $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {
                return $exclude;
            }
        }

        return null;

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Pass plain glob strings ('except' => ['*.log', '/vendor']) and let FileHelper do the parsing — the array form is an internal representation.
  2. If arrays are unavoidable, include all three keys exactly as parseExcludePattern() returns them: ['pattern' => '*.log', 'flags' => 0, 'firstWildcard' => 1].
  3. Validate pattern lists before use: every item is a string or an array with isset($p['pattern'], $p['flags'], $p['firstWildcard']).
  4. Reject or normalize pattern data coming from JSON/YAML config instead of forwarding it unchecked.

Example fix

// before
$options = ['except' => [
    ['pattern' => '*.log', 'flags' => 0], // missing firstWildcard
]];
$files = \yii\helpers\FileHelper::findFiles($dir, $options);

// after
$options = ['except' => ['*.log']];
$files = \yii\helpers\FileHelper::findFiles($dir, $options);
Defensive patterns

Strategy: validation

Validate before calling

/** Every pattern must be a string, or a fully pre-parsed pattern array. */
function validPatterns(array $patterns): bool
{
    foreach ($patterns as $p) {
        if (is_string($p)) { continue; }
        if (is_array($p) && isset($p['pattern'], $p['flags'], $p['firstWildcard'])) { continue; }
        return false;
    }
    return true;
}

if (!validPatterns($options['except'] ?? []) || !validPatterns($options['only'] ?? [])) {
    throw new \InvalidArgumentException('Pattern lists may contain strings or fully pre-parsed arrays only.');
}
$files = \yii\helpers\FileHelper::findFiles($dir, $options);

Prevention

When it happens

Trigger: 'except' => [['pattern' => '*.log', 'flags' => 0]] with firstWildcard missing; a nested array like ['only' => [['*.txt']]]; non-string, non-array scalars such as 123 or true in the pattern list (isset() on a scalar fails the same check); pre-parsed pattern arrays round-tripped through cache/serialization losing keys.

Common situations: Developers 'optimizing' by pre-parsing glob patterns into arrays but guessing the key set; YAML/JSON config turning pattern lists into nested structures that are forwarded verbatim; patterns sourced from user input or database settings arriving as arrays instead of strings.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/1b4c2e1bc4b84e43. Report an issue: GitHub.