{"record":{"id":"eb0372da70f8cb21","repo":"getgrav/grav","slug":"uri-host-must-be-a-string","errorCode":null,"errorMessage":"Uri host must be a string","messagePattern":"Uri host must be a string","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Uri/UriPartsFilter.php","lineNumber":66,"sourceCode":"            throw new InvalidArgumentException('Uri user info must be a string');\n        }\n\n        return preg_replace_callback(\n            '/(?:[^a-zA-Z0-9_\\-\\.~!\\$&\\'\\(\\)\\*\\+,;=]+|%(?![A-Fa-f0-9]{2}))/u',\n            fn($match) => rawurlencode((string) $match[0]),\n            $info\n        ) ?? '';\n    }\n\n    /**\n     * @param string $host\n     * @return string\n     * @throws InvalidArgumentException If the host is invalid.\n     */\n    public static function filterHost($host)\n    {\n        if (!is_string($host)) {\n            throw new InvalidArgumentException('Uri host must be a string');\n        }\n\n        if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {\n            $host = '[' . $host . ']';\n        } elseif ($host && preg_match(static::HOSTNAME_REGEX, $host) !== 1) {\n            throw new InvalidArgumentException('Uri host name validation failed');\n        }\n\n        return strtolower($host);\n    }\n\n    /**\n     * Filter Uri port.\n     *\n     * This method\n     *\n     * @param int|null $port\n     * @return int|null","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Uri/UriPartsFilter.php#L48-L84","documentation":"UriPartsFilter::filterHost() asserts the host is a string before normalizing (IPv6 bracketing, hostname regex, lowercasing), throwing InvalidArgumentException otherwise. It backs AbstractUri::withHost() and the Uri parts constructor; since withHost() declares a native `string` parameter, the is_string throw is mostly reachable via direct filterHost() calls or untyped callers passing null/array.","triggerScenarios":"Calling filterHost(null) when a Host header is absent; passing an array from a malformed parse result; feeding `$env['HTTP_HOST']` derived value that was never cast when calling the filter directly.","commonSituations":"Middleware reading Host/X-Forwarded-Host headers that may be missing on CLI or synthetic requests; building test fixtures where the host field was left null.","solutions":["Default the host: `$host = $headerHost ?? 'localhost'`","Cast scalar values to string before filtering","Use `$uri->withHost((string) $host)` so failures surface as a clear TypeError at the typed boundary"],"exampleFix":"// before\n$host = UriPartsFilter::filterHost($env['HTTP_HOST'] ?? null); // CLI: null -> throws\n\n// after\n$host = UriPartsFilter::filterHost((string) ($env['HTTP_HOST'] ?? 'localhost'));","handlingStrategy":"type-guard","validationCode":"$host = $env['HTTP_HOST'] ?? $env['SERVER_NAME'] ?? 'localhost';\nif (!is_string($host)) {\n    throw new \\InvalidArgumentException('host must be a string');\n}","typeGuard":"function isHostString(mixed $value): bool\n{\n    return is_string($value) && $value !== '';\n}","tryCatchPattern":null,"preventionTips":["Always default host-derived values ('localhost') for CLI/synthetic requests","Cast header values to string at the trust boundary","Use withHost() so type errors surface at the typed API edge"],"tags":["uri","type-check","validation","host"],"backgroundTag":"type-validation-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}