JMS Serializer序列化规则配置问题咨询(基于xsd2php)
先还原下我的使用场景:我用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

