{"record":{"id":"1b4c2e1bc4b84e43","repo":"yiisoft/yii2","slug":"if-exclude-include-pattern-is-an-array-it-must-con","errorCode":null,"errorMessage":"If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.","messagePattern":"If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"framework/helpers/BaseFileHelper.php","lineNumber":840,"sourceCode":"     * any, determines the fate.  Returns the element which\n     * matched, or null for undecided.\n     *\n     * Based on last_exclude_matching_from_list() from dir.c of git 1.8.5.3 sources.\n     *\n     * @param string $basePath\n     * @param string $path\n     * @param array $excludes list of patterns to match $path against\n     * @return array|null null or one of $excludes item as an array with keys: 'pattern', 'flags'\n     * @throws InvalidArgumentException if any of the exclude patterns is not a string or an array with keys: pattern, flags, firstWildcard.\n     */\n    private static function lastExcludeMatchingFromList($basePath, $path, $excludes)\n    {\n        foreach (array_reverse($excludes) as $exclude) {\n            if (is_string($exclude)) {\n                $exclude = self::parseExcludePattern($exclude, false);\n            }\n            if (!isset($exclude['pattern']) || !isset($exclude['flags']) || !isset($exclude['firstWildcard'])) {\n                throw new InvalidArgumentException('If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.');\n            }\n            if ($exclude['flags'] & self::PATTERN_MUSTBEDIR && !is_dir($path)) {\n                continue;\n            }\n\n            if ($exclude['flags'] & self::PATTERN_NODIR) {\n                if (self::matchBasename(basename($path), $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {\n                    return $exclude;\n                }\n                continue;\n            }\n\n            if (self::matchPathname($path, $basePath, $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {\n                return $exclude;\n            }\n        }\n\n        return null;","sourceCodeStart":822,"sourceCodeEnd":858,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/helpers/BaseFileHelper.php#L822-L858","documentation":"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.","triggerScenarios":"'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.","commonSituations":"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.","solutions":["Pass plain glob strings ('except' => ['*.log', '/vendor']) and let FileHelper do the parsing — the array form is an internal representation.","If arrays are unavoidable, include all three keys exactly as parseExcludePattern() returns them: ['pattern' => '*.log', 'flags' => 0, 'firstWildcard' => 1].","Validate pattern lists before use: every item is a string or an array with isset($p['pattern'], $p['flags'], $p['firstWildcard']).","Reject or normalize pattern data coming from JSON/YAML config instead of forwarding it unchecked."],"exampleFix":"// before\n$options = ['except' => [\n    ['pattern' => '*.log', 'flags' => 0], // missing firstWildcard\n]];\n$files = \\yii\\helpers\\FileHelper::findFiles($dir, $options);\n\n// after\n$options = ['except' => ['*.log']];\n$files = \\yii\\helpers\\FileHelper::findFiles($dir, $options);","handlingStrategy":"validation","validationCode":"/** Every pattern must be a string, or a fully pre-parsed pattern array. */\nfunction validPatterns(array $patterns): bool\n{\n    foreach ($patterns as $p) {\n        if (is_string($p)) { continue; }\n        if (is_array($p) && isset($p['pattern'], $p['flags'], $p['firstWildcard'])) { continue; }\n        return false;\n    }\n    return true;\n}\n\nif (!validPatterns($options['except'] ?? []) || !validPatterns($options['only'] ?? [])) {\n    throw new \\InvalidArgumentException('Pattern lists may contain strings or fully pre-parsed arrays only.');\n}\n$files = \\yii\\helpers\\FileHelper::findFiles($dir, $options);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat 'only'/'except' as plain string glob lists in configuration; the array form is internal.","Keep config schemas strict: patterns must be strings.","Unit-test every config-driven pattern list so shape drift fails in CI, not in production."],"tags":["filesystem","glob","filters","options-validation","yii2"],"backgroundTag":"config-array-missing-keys","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}