{"record":{"id":"30321fdd1b6b73f8","repo":"w7corp/easywechat","slug":"msgtype-cannot-be-empty-30321f","errorCode":null,"errorMessage":"MsgType cannot be empty.","messagePattern":"MsgType cannot be empty\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Traits/RespondXmlMessage.php","lineNumber":65,"sourceCode":"     * @return array<string, mixed>\n     *\n     * @throws InvalidArgumentException\n     */\n    protected function normalizeResponse(mixed $response): array\n    {\n        if (! is_string($response) && is_callable($response)) {\n            $response = $response();\n        }\n\n        if (is_array($response)) {\n            $normalized = [];\n\n            foreach ($response as $key => $value) {\n                $normalized[(string) $key] = $value;\n            }\n\n            if (! isset($normalized['MsgType'])) {\n                throw new InvalidArgumentException('MsgType cannot be empty.');\n            }\n\n            return $normalized;\n        }\n\n        if (is_string($response) || is_numeric($response)) {\n            return [\n                'MsgType' => 'text',\n                'Content' => $response,\n            ];\n        }\n\n        throw new InvalidArgumentException(\n            sprintf('Invalid Response type \"%s\".', gettype($response))\n        );\n    }\n\n    /**","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Traits/RespondXmlMessage.php#L47-L83","documentation":"For XML passive replies (OfficialAccount/OpenPlatform/OpenWork Servers and Work in xml messageType), normalizeResponse() requires the exact-case 'MsgType' key in the array a handler returns — isset($normalized['MsgType']) is case-sensitive. A missing or differently-cased key throws InvalidArgumentException before any XML is built. Plain strings and numbers never hit this error because they are auto-wrapped as text replies.","triggerScenarios":"A handler returns ['Content' => 'hi'] without a type key; returns ['msgtype' => 'text'] (lowercase, JSON-mode convention); keys were normalized somewhere upstream (e.g. array_change_key_case) before returning.","commonSituations":"Shared reply builders used in both JSON mode (msgtype) and XML mode (MsgType); refactors lowercasing array keys; reply templates from config missing the MsgType entry.","solutions":["Return arrays with 'MsgType' (capital M, capital T) plus the type-specific fields (e.g. Content).","Or simply return a string — the trait wraps it as a text reply automatically.","Guard shared builders in XML mode: $reply += ['MsgType' => 'text'].","Return a PSR-7 ResponseInterface to skip normalization."],"exampleFix":"// before: lowercase key (JSON-mode convention) in XML mode\nreturn ['msgtype' => 'text', 'Content' => 'hi'];\n// after: exact-case MsgType — or just return the string 'hi'\nreturn ['MsgType' => 'text', 'Content' => 'hi'];","handlingStrategy":"validation","validationCode":"if (!isset($reply['MsgType']) && isset($reply['msgtype'])) { $reply['MsgType'] = $reply['msgtype']; unset($reply['msgtype']); }\nif (!isset($reply['MsgType'])) { $reply = ['MsgType' => 'text', 'Content' => (string) $reply]; }","typeGuard":"function isXmlReplyArray(mixed $r): bool { return is_array($r) && isset($r['MsgType']); }","tryCatchPattern":"try { return $server->serve(); } catch (\\EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException $e) { if (str_contains($e->getMessage(), 'MsgType')) { \\Log::warning('XML reply missing MsgType'); return new \\Nyholm\\Psr7\\Response(200, [], 'success'); } throw $e; }","preventionTips":["Use per-mode reply builders with exact key casing","Assert MsgType in reply tests","Prefer returning plain strings for simple text replies"],"tags":["php","xml-reply","official-account","message-push"],"backgroundTag":"invalid-response-format","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}