{"record":{"id":"556ba8f11c3baf02","repo":"yiisoft/yii2","slug":"exclude-include-pattern-must-be-a-string","errorCode":null,"errorMessage":"Exclude/include pattern must be a string.","messagePattern":"Exclude/include pattern must be a string\\.","errorType":"exception","errorClass":"yii\\base\\InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"framework/helpers/BaseFileHelper.php","lineNumber":871,"sourceCode":"            if (self::matchPathname($path, $basePath, $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) {\n                return $exclude;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead.\n     * @param string $pattern\n     * @param bool $caseSensitive\n     * @return array with keys: (string) pattern, (int) flags, (int|bool) firstWildcard\n     * @throws InvalidArgumentException\n     */\n    private static function parseExcludePattern($pattern, $caseSensitive)\n    {\n        if (!is_string($pattern)) {\n            throw new InvalidArgumentException('Exclude/include pattern must be a string.');\n        }\n\n        $result = [\n            'pattern' => $pattern,\n            'flags' => 0,\n            'firstWildcard' => false,\n        ];\n\n        if (!$caseSensitive) {\n            $result['flags'] |= self::PATTERN_CASE_INSENSITIVE;\n        }\n\n        if (empty($pattern)) {\n            return $result;\n        }\n\n        if (strncmp($pattern, '!', 1) === 0) {\n            $result['flags'] |= self::PATTERN_NEGATIVE;","sourceCodeStart":853,"sourceCodeEnd":889,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/helpers/BaseFileHelper.php#L853-L889","documentation":"parseExcludePattern() is the single funnel that turns a glob string into the internal ['pattern', 'flags', 'firstWildcard'] structure, and it starts with a defensive type assertion: anything that is not a PHP string throws InvalidArgumentException. Note that both public call paths (normalizeOptions() and lastExcludeMatchingFromList()) guard the call with is_string(), so through the normal API a non-string pattern usually surfaces as the sibling error 262 instead; hitting 263 means a custom subclass or patched helper invoked the parser directly with a scalar/object.","triggerScenarios":"A non-string element (int, float, bool, object) in options['except']/options['only'] reaching the parser — typically via a subclass overriding normalizeOptions()/filterPath() without the is_string() guard; test code calling the private method via reflection; code that injects pre-parsed values into the pattern list mixed with raw non-string scalars.","commonSituations":"Custom FileHelper subclasses that pre-process pattern lists; config-driven pattern lists where a stray integer or null slips in; refactors that bypass the built-in string checks.","solutions":["Make every entry of 'only'/'except' a plain string glob in config and call sites.","If subclassing, keep the is_string() guard before any pattern parsing.","Sanitize pattern arrays before passing them: array_filter($patterns, 'is_string') or map non-strings to their string form.","Add a unit assertion over config-driven pattern lists so type drift is caught in CI."],"exampleFix":"// before\n$options = ['except' => [0, '*.log', new \\stdClass()]];\n$files = \\yii\\helpers\\FileHelper::findFiles($dir, $options);\n\n// after\n$options = ['except' => ['*.log']];\n$files = \\yii\\helpers\\FileHelper::findFiles($dir, $options);","handlingStrategy":"type-guard","validationCode":"// Keep pattern lists string-only before any FileHelper call\n$options['except'] = array_values(array_filter($options['except'] ?? [], 'is_string'));\n$options['only'] = array_values(array_filter($options['only'] ?? [], 'is_string'));","typeGuard":"/** @param array<int|string, mixed> $patterns @return list<string> */\nfunction stringPatterns(array $patterns): array\n{\n    $out = [];\n    foreach ($patterns as $p) {\n        if (is_string($p)) { $out[] = $p; }\n        elseif (is_scalar($p)) { $out[] = (string) $p; }\n        // arrays/objects are dropped — they are not valid patterns\n    }\n    return $out;\n}","tryCatchPattern":null,"preventionTips":["Declare pattern lists as string[] in docblocks and config schemas.","If you subclass FileHelper, keep the built-in is_string() guard before pattern parsing.","Filter external pattern input through a string-only normalizer at the trust boundary."],"tags":["filesystem","glob","type-validation","yii2"],"backgroundTag":"wrong-argument-type","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}