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

  1. Return arrays with 'MsgType' (capital M, capital T) plus the type-specific fields (e.g. Content).
  2. Or simply return a string — the trait wraps it as a text reply automatically.
  3. Guard shared builders in XML mode: $reply += ['MsgType' => 'text'].
  4. 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

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


AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21). Data as JSON: /api/errors/30321fdd1b6b73f8. Report an issue: GitHub.