You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JMS Serializer序列化规则配置问题咨询(基于xsd2php)

XSD2PHP + JMS Serializer XML序列化问题解析

先还原下我的使用场景:我用xsd2php解析API请求体的XSD生成对应PHP类,然后基于JMS Serializer序列化对象。

对象构建代码

$payload = new TrackRequest;
$searchCriteria = new SearchCriteriaAType;
$searchCriteria->addToConsignmentNumber(11111);
$payload->setSearchCriteria($searchCriteria);
$levelOfDetail = new LevelOfDetailAType;
$levelOfDetail->setSummary(true);
$payload->setLevelOfDetail($levelOfDetail);

序列化配置

$serializerBuilder = SerializerBuilder::create();
$serializerBuilder->addMetadataDir(__DIR__ . '/../../metadata/Tracking', 'TNTExpressConnect\Tracking\XSD');
$serializerBuilder->setPropertyNamingStrategy(new IdenticalPropertyNamingStrategy);
$serializerBuilder->configureHandlers(function (HandlerRegistryInterface $handler) use ($serializerBuilder) {
    $serializerBuilder->addDefaultHandlers();
    $handler->registerSubscribingHandler(new BaseTypesHandler()); // XMLSchema List handling
    $handler->registerSubscribingHandler(new XmlSchemaDateHandler()); // XMLSchema date handling
});

当前序列化结果

<?xml version="1.0" encoding="UTF-8"?>
<result>
  <searchCriteria>
    <account/>
    <alternativeConsignmentNumber/>
    <consignmentNumber>
      <entry><![CDATA[11111]]></entry>
    </consignmentNumber>
    <customerReference/>
    <pieceReference/>
  </searchCriteria>
  <levelOfDetail>
    <summary>true</summary>
  </levelOfDetail>
</result>

针对上面的结果,我有四个技术疑问,下面逐个解答:


1. 为何根元素是<result>而非<TrackRequest>?

这是因为JMS Serializer默认会用序列化操作的"结果"作为根元素名,除非你在类的元数据中显式指定了根元素。xsd2php生成元数据时可能没自动继承XSD中的根元素定义,需要手动补充配置。

解决方案(YAML元文件方式):
找到TrackRequest类对应的元数据文件(比如metadata/Tracking/TrackRequest.yml),添加xml_root_name配置:

TNTExpressConnect\Tracking\XSD\TrackRequest:
    xml_root_name: TrackRequest
    # 保留原有其他配置,比如properties等

修改后重新序列化,根元素就会变成<TrackRequest>。


2. 如何移除CDATA标记?

CDATA是JMS Serializer为了规避特殊字符自动添加的,但对于纯数字或安全字符串来说完全没必要。你可以在列表项的元数据中关闭CDATA生成。

解决方案(YAML元文件方式):
编辑SearchCriteriaAType的元数据文件,找到consignmentNumber字段的xml_list配置,添加cdata: false:

TNTExpressConnect\Tracking\XSD\SearchCriteriaAType:
    properties:
        consignmentNumber:
            # 原有类型、访问器等配置
            xml_list:
                inline: false
                cdata: false # 关闭CDATA

3. 如何替换<entry>标签,为每个条目生成独立的<consignmentNumber>标签?

这也是XML列表的元配置问题,JMS Serializer默认用<entry>作为列表项的标签名,我们需要显式指定entry_name为目标标签名。

解决方案(YAML元文件方式):
继续在SearchCriteriaAType的元数据中修改consignmentNumber的配置,补充entry_name:

TNTExpressConnect\Tracking\XSD\SearchCriteriaAType:
    properties:
        consignmentNumber:
            # 原有类型、访问器等配置
            xml_list:
                inline: false
                entry_name: consignmentNumber # 替换entry为consignmentNumber
                cdata: false # 同时解决CDATA问题

修改后,序列化的这部分会变成:

<consignmentNumber>
  <consignmentNumber>11111</consignmentNumber>
</consignmentNumber>

如果希望外层的<consignmentNumber>直接包含多个子标签(不需要外层包裹),可以把inline: true,这样会变成:

<consignmentNumber>11111</consignmentNumber>

具体根据XSD的要求调整inline参数。


4. 如何将<summary>true</summary>替换为自闭合标签<summary/>?

JMS Serializer没有内置配置直接实现这个逻辑,因为布尔值true默认会被序列化为文本节点。我们需要自定义一个订阅处理器来拦截布尔值的XML序列化过程,当值为true时生成自闭合标签。

自定义处理器实现:

use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\XmlSerializationVisitor;

class BooleanSelfClosingHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods(): array
    {
        return [
            [
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
                'format' => 'xml',
                'type' => 'boolean',
                'method' => 'serializeBoolean',
            ],
        ];
    }

    public function serializeBoolean(XmlSerializationVisitor $visitor, $value, array $type, Context $context)
    {
        // 仅处理值为true的情况,生成自闭合标签
        if ($value === true) {
            $currentNodeName = $visitor->getCurrentNode()->nodeName;
            $selfClosingNode = $visitor->document->createElement($currentNodeName);
            $visitor->getCurrentParent()->appendChild($selfClosingNode);
            return null; // 返回null阻止默认的文本节点生成
        }

        // 值为false时按默认逻辑处理(也可以自定义为不输出标签)
        return $visitor->visitBoolean($value, $type, $context);
    }
}

注册处理器:
在序列化器构建的configureHandlers回调中添加这个处理器:

$serializerBuilder->configureHandlers(function (HandlerRegistryInterface $handler) use ($serializerBuilder) {
    $serializerBuilder->addDefaultHandlers();
    $handler->registerSubscribingHandler(new BaseTypesHandler());
    $handler->registerSubscribingHandler(new XmlSchemaDateHandler());
    $handler->registerSubscribingHandler(new BooleanSelfClosingHandler()); // 新增这行
});

这样当summary的值为true时,就会生成<summary/>自闭合标签。


内容的提问来源于stack exchange,提问作者sevavietl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:24:27