{"record":{"id":"4df2c4d37929985b","repo":"w7corp/easywechat","slug":"the-type-of-xml-must-be-string-or-array","errorCode":null,"errorMessage":"The type of `xml` must be string or array.","messagePattern":"The type of `xml` must be string or array\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Kernel/HttpClient/RequestUtil.php","lineNumber":122,"sourceCode":"    }\n\n    /**\n     * @param  array{headers?:array<string, string>, xml?:mixed, body?:array|string, json?:mixed}  $options\n     * @return array{headers?:array<string, string|array<string, string>|array<string>>, xml?:array|string, body?:array|string}\n     *\n     * @throws InvalidArgumentException\n     */\n    public static function formatBody(array $options): array\n    {\n        $contentType = $options['headers']['Content-Type'] ?? $options['headers']['content-type'] ?? null;\n\n        if (array_key_exists('xml', $options)) {\n            if (is_array($options['xml'])) {\n                $options['xml'] = Xml::build($options['xml']);\n            }\n\n            if (! is_string($options['xml'])) {\n                throw new InvalidArgumentException('The type of `xml` must be string or array.');\n            }\n\n            if (! $contentType) {\n                $options['headers']['Content-Type'] = 'text/xml';\n            }\n\n            $options['body'] = $options['xml'];\n            unset($options['xml']);\n        }\n\n        if (array_key_exists('json', $options)) {\n            if (is_array($options['json'])) {\n                /** XXX: 微信的 JSON 是比较奇葩的，比如菜单不能把中文 encode 为 unicode */\n                $options['json'] = json_encode(\n                    $options['json'],\n                    empty($options['json']) ? JSON_FORCE_OBJECT : JSON_UNESCAPED_UNICODE\n                );\n            }","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/HttpClient/RequestUtil.php#L104-L140","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n$body = ['xml' => $this->buildOrder($order)]; // buildOrder() returned null\n\n// after\n$body = ['xml' => $this->buildOrder($order) ?? []];","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isValidXmlOption(mixed $xml): bool\n{\n    return is_array($xml) || is_string($xml);\n}\n\n// before sending\nif (! isValidXmlOption($options['xml'] ?? null)) {\n    throw new InvalidArgumentException(\"xml option must be array|string, got \".gettype($options['xml']));\n}","tryCatchPattern":"try {\n    $response = $api->postJson('/pay/unifiedorder', $options);\n} catch (\\EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException $e) {\n    if (str_contains($e->getMessage(), '`xml`')) {\n        $options['xml'] = $options['xml'] ?? [];\n        $response = $api->postJson('/pay/unifiedorder', $options);\n    }\n}","preventionTips":["Default conditional XML payloads to [] instead of null","Type request builders to always return array<string, mixed>"],"tags":["php","easywechat","http-client","xml","request-body"],"backgroundTag":"invalid-request-body","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}