getgrav/grav · error · InvalidArgumentException

URL must be a string

Error message

URL must be a string

What it means

UriFactory::parseUrl() is Grav's UTF-8 aware replacement for parse_url(); it first asserts the input is a PHP string and throws InvalidArgumentException otherwise. It is reached through UriFactory::createFromString() and direct parseUrl() calls. Non-string values (arrays, null, objects) are rejected before any parsing happens.

Source

Thrown at system/src/Grav/Framework/Uri/UriFactory.php:123

            'pass' => $pass,
            'host' => $host,
            'port' => $port,
            'path' => $path,
            'query' => $query
        ];
    }

    /**
     * UTF-8 aware parse_url() implementation.
     *
     * @param string $url
     * @return array
     * @throws InvalidArgumentException
     */
    public static function parseUrl($url)
    {
        if (!is_string($url)) {
            throw new InvalidArgumentException('URL must be a string');
        }

        $encodedUrl = preg_replace_callback(
            '%[^:/@?&=#]+%u',
            static fn($matches) => rawurlencode((string) $matches[0]),
            $url
        );

        $parts = is_string($encodedUrl) ? parse_url($encodedUrl) : false;
        if ($parts === false) {
            throw new InvalidArgumentException("Malformed URL: {$url}");
        }

        return $parts;
    }

    /**
     * Parse query string and return it as an array.

View on GitHub (pinned to 6040efed04)

Solutions

  1. Validate with is_string() before calling parseUrl()/createFromString()
  2. Reject array input explicitly when reading query parameters you intend to use as URLs
  3. Cast known scalar inputs: `(string) $value` — but reject arrays/objects first

Example fix

// before
$parts = UriFactory::parseUrl($_GET['redirect'] ?? null); // ?redirect[]=x -> array -> throws

// after
$raw = $_GET['redirect'] ?? '';
$parts = is_string($raw) && $raw !== '' ? UriFactory::parseUrl($raw) : null;
Defensive patterns

Strategy: type-guard

Validate before calling

$url = $_GET['url'] ?? '';
if (!is_string($url) || $url === '') {
    throw new \InvalidArgumentException('url parameter must be a non-empty string');
}

Type guard

/** @param mixed $value */
function isParsableUrlString(mixed $value): bool
{
    return is_string($value) && $value !== '';
}

Try / catch

try {
    $uri = Grav\Framework\Uri\UriFactory::createFromString($url);
} catch (\InvalidArgumentException $e) {
    // Covers both 'URL must be a string' and 'Malformed URL'
    throw new HttpBadRequest($request, 'Invalid URL parameter');
}

Prevention

When it happens

Trigger: Passing `$_GET['url']` straight in when the client sends `?url[]=x` (PHP turns that into an array); passing null from an optional parameter default; passing an object (even Stringable — no __toString invocation happens here).

Common situations: Endpoints that parse user-supplied redirect targets or link URLs; array-syntax query-parameter injection (`param[]=`) hitting unvalidated code; optional config values that default to null being fed to createFromString().

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/bf6fa9d4a31b1e5a. Report an issue: GitHub.