{"record":{"id":"d91762ca997ef540","repo":"getgrav/grav","slug":"uri-scheme-must-be-a-string","errorCode":null,"errorMessage":"Uri scheme must be a string","messagePattern":"Uri scheme must be a string","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Uri/UriPartsFilter.php","lineNumber":32,"sourceCode":"use function is_string;\n\n/**\n * Class Uri\n * @package Grav\\Framework\\Uri\n */\nclass UriPartsFilter\n{\n    const HOSTNAME_REGEX = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$/u';\n\n    /**\n     * @param string $scheme\n     * @return string\n     * @throws InvalidArgumentException If the scheme is invalid.\n     */\n    public static function filterScheme($scheme)\n    {\n        if (!is_string($scheme)) {\n            throw new InvalidArgumentException('Uri scheme must be a string');\n        }\n\n        return strtolower($scheme);\n    }\n\n    /**\n     * Filters the user info string.\n     *\n     * @param string $info The raw user or password.\n     * @return string The percent-encoded user or password string.\n     * @throws InvalidArgumentException\n     */\n    public static function filterUserInfo($info)\n    {\n        if (!is_string($info)) {\n            throw new InvalidArgumentException('Uri user info must be a string');\n        }\n","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Uri/UriPartsFilter.php#L14-L50","documentation":"UriPartsFilter::filterScheme() asserts its argument is a string before lowercasing it, throwing InvalidArgumentException otherwise. It is called from AbstractUri::withScheme() and from the Uri parts constructor. Note that withScheme() declares a native `string $scheme` parameter, so through that path PHP itself usually raises a TypeError first; this exception surfaces mainly on direct filterScheme() calls or untyped call sites.","triggerScenarios":"Calling UriPartsFilter::filterScheme(null) directly; passing a config value that is null/int because the key was missing (`$config->get('scheme')` returning null); building a Uri from a hand-assembled parts array where 'scheme' holds a non-string.","commonSituations":"Code reading optional scheme settings from YAML/config without a default; arrays built from heterogeneous data passed to Uri::createFromString/UriFactory::createFromParts paths.","solutions":["Default the value before passing: `$scheme = $config['scheme'] ?? 'https'`","Cast scalars to string; reject arrays/objects explicitly","Prefer the typed API `$uri->withScheme('https')` over calling the filter directly"],"exampleFix":"// before\n$scheme = $settings['scheme']; // missing key -> null\n$new = $uri->withScheme(UriPartsFilter::filterScheme($scheme));\n\n// after\n$scheme = $settings['scheme'] ?? 'https';\n$new = $uri->withScheme((string) $scheme);","handlingStrategy":"type-guard","validationCode":"$scheme = $config['scheme'] ?? 'https';\nif (!is_string($scheme)) {\n    throw new \\InvalidArgumentException('scheme must be a string');\n}","typeGuard":"function isSchemeString(mixed $value): bool\n{\n    return is_string($value) && preg_match('/^[a-z][a-z0-9+.-]*$/i', $value) === 1;\n}","tryCatchPattern":null,"preventionTips":["Default config-derived scheme values before passing them on ($config['scheme'] ?? 'https')","Prefer the typed $uri->withScheme() API over calling UriPartsFilter directly","Reject array input at the request boundary for fields destined for URI building"],"tags":["uri","type-check","validation"],"backgroundTag":"type-validation-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}