w7corp/easywechat · error · InvalidArgumentException
The type of `xml` must be string or array.
Error message
The type of `xml` must be string or array.
What it means
RequestUtil::formatBody() normalizes request options: an 'xml' option that is an array is converted via Xml::build, and if the result is not a string it throws InvalidArgumentException('The type of `xml` must be string or array.'). Only two shapes are accepted: a plain PHP array or an already-built XML string.
Source
Thrown at src/Kernel/HttpClient/RequestUtil.php:122
}
/**
* @param array{headers?:array<string, string>, xml?:mixed, body?:array|string, json?:mixed} $options
* @return array{headers?:array<string, string|array<string, string>|array<string>>, xml?:array|string, body?:array|string}
*
* @throws InvalidArgumentException
*/
public static function formatBody(array $options): array
{
$contentType = $options['headers']['Content-Type'] ?? $options['headers']['content-type'] ?? null;
if (array_key_exists('xml', $options)) {
if (is_array($options['xml'])) {
$options['xml'] = Xml::build($options['xml']);
}
if (! is_string($options['xml'])) {
throw new InvalidArgumentException('The type of `xml` must be string or array.');
}
if (! $contentType) {
$options['headers']['Content-Type'] = 'text/xml';
}
$options['body'] = $options['xml'];
unset($options['xml']);
}
if (array_key_exists('json', $options)) {
if (is_array($options['json'])) {
/** XXX: 微信的 JSON 是比较奇葩的,比如菜单不能把中文 encode 为 unicode */
$options['json'] = json_encode(
$options['json'],
empty($options['json']) ? JSON_FORCE_OBJECT : JSON_UNESCAPED_UNICODE
);
}View on GitHub (pinned to f0cf0a8b83)
Solutions
- Pass the XML payload as an array (recommended: the client builds it) or as a finished XML string
- Guard builders that may return null: default to [] before assigning to the 'xml' option
- Log the option type right before the request when this fires intermittently
Example fix
// before $body = ['xml' => $this->buildOrder($order)]; // buildOrder() returned null // after $body = ['xml' => $this->buildOrder($order) ?? []];
Defensive patterns
Strategy: type-guard
Type guard
function isValidXmlOption(mixed $xml): bool
{
return is_array($xml) || is_string($xml);
}
// before sending
if (! isValidXmlOption($options['xml'] ?? null)) {
throw new InvalidArgumentException("xml option must be array|string, got ".gettype($options['xml']));
} Try / catch
try {
$response = $api->postJson('/pay/unifiedorder', $options);
} catch (\EasyWeChat\Kernel\Exceptions\InvalidArgumentException $e) {
if (str_contains($e->getMessage(), '`xml`')) {
$options['xml'] = $options['xml'] ?? [];
$response = $api->postJson('/pay/unifiedorder', $options);
}
} Prevention
- Default conditional XML payloads to [] instead of null
- Type request builders to always return array<string, mixed>
When it happens
Trigger: Calling a v2 Pay API client (XML-transport endpoints) with ['xml' => null] or ['xml' => 0] because an upstream builder returned null, or passing a SimpleXMLElement/object instead of an array or string.
Common situations: Optional payloads built conditionally and ending up null, refactoring that changes the builder's return type, feeding an object from another XML library into the option.
Related errors
- The type of `json` must be string or array.
- 400
- The method "%s" is not supported.
- Response body is empty.
- %s does't implements %s
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/4df2c4d37929985b.
Report an issue: GitHub.