w7corp/easywechat · error · InvalidArgumentException
MsgType cannot be empty.
Error message
MsgType cannot be empty.
What it means
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.
Source
Thrown at src/Kernel/Traits/RespondXmlMessage.php:65
* @return array<string, mixed>
*
* @throws InvalidArgumentException
*/
protected function normalizeResponse(mixed $response): array
{
if (! is_string($response) && is_callable($response)) {
$response = $response();
}
if (is_array($response)) {
$normalized = [];
foreach ($response as $key => $value) {
$normalized[(string) $key] = $value;
}
if (! isset($normalized['MsgType'])) {
throw new InvalidArgumentException('MsgType cannot be empty.');
}
return $normalized;
}
if (is_string($response) || is_numeric($response)) {
return [
'MsgType' => 'text',
'Content' => $response,
];
}
throw new InvalidArgumentException(
sprintf('Invalid Response type "%s".', gettype($response))
);
}
/**View on GitHub (pinned to f0cf0a8b83)
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.
Example fix
// before: lowercase key (JSON-mode convention) in XML mode return ['msgtype' => 'text', 'Content' => 'hi']; // after: exact-case MsgType — or just return the string 'hi' return ['MsgType' => 'text', 'Content' => 'hi'];
Defensive patterns
Strategy: validation
Validate before calling
if (!isset($reply['MsgType']) && isset($reply['msgtype'])) { $reply['MsgType'] = $reply['msgtype']; unset($reply['msgtype']); }
if (!isset($reply['MsgType'])) { $reply = ['MsgType' => 'text', 'Content' => (string) $reply]; } Type guard
function isXmlReplyArray(mixed $r): bool { return is_array($r) && isset($r['MsgType']); } Try / catch
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; } Prevention
- Use per-mode reply builders with exact key casing
- Assert MsgType in reply tests
- Prefer returning plain strings for simple text replies
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid Response type "%s".
- Request ciphertext must not be empty.
- msgtype cannot be empty.
- token or aes_key cannot be empty.
- No secret configured.
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/30321fdd1b6b73f8.
Report an issue: GitHub.