guzzle/guzzle · error · GuzzleHttp\Exception\InvalidArgumentException

%s entries must be strings or stringable objects.

Error message

%s entries must be strings or stringable objects.

What it means

Thrown by normalizeCurlHeaderOptions() when an entry in CURLOPT_HTTPHEADER or CURLOPT_PROXYHEADER is neither a string nor an object implementing __toString(). cURL header arrays must be strings, so any other type (int, array, null, plain object) is rejected to avoid silent type coercion bugs.

Source

Thrown at src/Handler/CurlFactory.php:1569

        #[\SensitiveParameter]
        array &$conf
    ): void {
        $options = [\CURLOPT_HTTPHEADER => 'CURLOPT_HTTPHEADER'];
        if (\defined('CURLOPT_PROXYHEADER')) {
            $options[(int) \constant('CURLOPT_PROXYHEADER')] = 'CURLOPT_PROXYHEADER';
        }

        foreach ($options as $option => $label) {
            if (!\array_key_exists($option, $conf) || !\is_array($conf[$option])) {
                continue;
            }

            $normalized = [];
            foreach ($conf[$option] as $key => $entry) {
                if (\is_object($entry) && \method_exists($entry, '__toString')) {
                    $entry = (string) $entry;
                } elseif (!\is_string($entry)) {
                    throw new InvalidArgumentException(\sprintf('%s entries must be strings or stringable objects.', $label));
                }

                if (\strpbrk($entry, "\r\n") !== false) {
                    throw new InvalidArgumentException(\sprintf('%s entries must not contain a carriage return or line feed.', $label));
                }

                $normalized[$key] = $entry;
            }

            $conf[$option] = $normalized;
        }
    }

    /**
     * @param array<int|string, mixed> $conf
     */
    private static function requiresFreshConnectionForAuthenticatedProxy(RequestInterface $request, string $proxy, array $conf): bool
    {

View on GitHub (pinned to 9b200fc580)

Solutions

  1. Cast each header entry to a string before adding it to CURLOPT_HTTPHEADER/CURLOPT_PROXYHEADER.
  2. Implement __toString() on value objects used as header entries.
  3. Filter the array to remove non-string entries before passing.

Example fix

// before
['curl' => [CURLOPT_HTTPHEADER => ['Accept: application/json', $config['headerValue']]]]
// where $config['headerValue'] is null/int.
// after
['curl' => [CURLOPT_HTTPHEADER => ['Accept: application/json', (string) $config['headerValue']]]]
Defensive patterns

Strategy: type-guard

Validate before calling

foreach ($options['curl'][CURLOPT_HTTPHEADER] ?? [] as $entry) {
    if (!is_string($entry) && !(is_object($entry) && method_exists($entry, '__toString'))) {
        throw new \InvalidArgumentException('CURLOPT_HTTPHEADER entries must be strings or stringable.');
    }
}

Type guard

/** @param mixed $entry */
function isStringableHeader($entry): bool {
    return is_string($entry) || (is_object($entry) && method_exists($entry, '__toString'));
}

Try / catch

try {
    $client->get($url, $options);
} catch (\GuzzleHttp\Exception\InvalidArgumentException $e) {
    // Cast or filter header entries to strings.
}

Prevention

When it happens

Trigger: Passing ['curl' => [CURLOPT_HTTPHEADER => ['X-Foo: bar', ['nested']]]] or ['curl' => [CURLOPT_HTTPHEADER => ['X-Foo: bar', 123]]], where an entry is an array, integer, boolean, or non-stringable object.

Common situations: Building header arrays dynamically and forgetting to cast values; appending an array by mistake; passing a value object without __toString.

Related errors


AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04). Data as JSON: /data/errors/5d3c548ae766ebf0.json. Report an issue: GitHub.