Laravel 13 XML 处理完全指南

XML 是重要的数据交换格式,在许多企业系统中广泛使用。本文将深入探讨 Laravel 13 中 XML 处理的各种方法和最佳实践。

XML 解析

简单 XML 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace App\Services;

class XmlParser
{
public function parse(string $xml): array
{
$simpleXml = simplexml_load_string($xml);
return $this->xmlToArray($simpleXml);
}

public function parseFile(string $path): array
{
$simpleXml = simplexml_load_file($path);
return $this->xmlToArray($simpleXml);
}

protected function xmlToArray($xml): array
{
$result = [];

foreach ($xml->children() as $name => $node) {
$value = $this->xmlToArray($node);

if (empty($value)) {
$result[$name] = (string) $node;
} else {
$result[$name] = $value;
}
}

return $result;
}
}

DOMDocument 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php

namespace App\Services;

use DOMDocument;
use DOMXPath;

class DomXmlParser
{
public function parse(string $xml): array
{
$dom = new DOMDocument();
$dom->loadXML($xml);
$dom->preserveWhiteSpace = false;

return $this->domToArray($dom->documentElement);
}

public function query(string $xml, string $xpath): array
{
$dom = new DOMDocument();
$dom->loadXML($xml);

$xpathObj = new DOMXPath($dom);
$nodes = $xpathObj->query($xpath);

$result = [];
foreach ($nodes as $node) {
$result[] = $this->domToArray($node);
}

return $result;
}

protected function domToArray($node): array
{
$result = [];

if ($node->hasAttributes()) {
foreach ($node->attributes as $attr) {
$result['@attributes'][$attr->nodeName] = $attr->nodeValue;
}
}

if ($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
if ($child->nodeType === XML_TEXT_NODE) {
$text = trim($child->nodeValue);
if ($text !== '') {
$result['@text'] = $text;
}
} else {
$childArray = $this->domToArray($child);
$name = $child->nodeName;

if (isset($result[$name])) {
if (!isset($result[$name][0])) {
$result[$name] = [$result[$name]];
}
$result[$name][] = $childArray;
} else {
$result[$name] = $childArray;
}
}
}
}

return $result;
}
}

XML 构建

简单 XML 构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

namespace App\Services;

use SimpleXMLElement;

class XmlBuilder
{
public function build(array $data, string $root = 'root'): string
{
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><' . $root . '/>');
$this->arrayToXml($data, $xml);

return $xml->asXML();
}

protected function arrayToXml(array $data, SimpleXMLElement $xml): void
{
foreach ($data as $key => $value) {
if (is_array($value)) {
if (is_numeric($key)) {
$key = 'item';
}
$subnode = $xml->addChild($key);
$this->arrayToXml($value, $subnode);
} else {
if (is_numeric($key)) {
$key = 'item';
}
$xml->addChild($key, htmlspecialchars($value));
}
}
}
}

DOMDocument 构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

namespace App\Services;

use DOMDocument;
use DOMElement;

class DomXmlBuilder
{
protected DOMDocument $dom;

public function __construct()
{
$this->dom = new DOMDocument('1.0', 'UTF-8');
$this->dom->formatOutput = true;
}

public function build(array $data, string $root = 'root'): string
{
$rootElement = $this->dom->createElement($root);
$this->dom->appendChild($rootElement);

$this->buildElements($data, $rootElement);

return $this->dom->saveXML();
}

protected function buildElements(array $data, DOMElement $parent): void
{
foreach ($data as $key => $value) {
if (str_starts_with($key, '@')) {
$parent->setAttribute(substr($key, 1), $value);
continue;
}

if (is_array($value)) {
if ($this->isNumericArray($value)) {
foreach ($value as $item) {
$element = $this->dom->createElement($key);
$parent->appendChild($element);

if (is_array($item)) {
$this->buildElements($item, $element);
} else {
$element->textContent = $item;
}
}
} else {
$element = $this->dom->createElement($key);
$parent->appendChild($element);
$this->buildElements($value, $element);
}
} else {
$element = $this->dom->createElement($key);
$element->textContent = $value;
$parent->appendChild($element);
}
}
}

protected function isNumericArray(array $array): bool
{
return array_keys($array) === range(0, count($array) - 1);
}
}

XML 验证

XSD 验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

namespace App\Services;

use DOMDocument;

class XmlValidator
{
public function validateAgainstXsd(string $xml, string $xsdPath): array
{
libxml_use_internal_errors(true);

$dom = new DOMDocument();
$dom->loadXML($xml);

$valid = $dom->schemaValidate($xsdPath);

$errors = [];
if (!$valid) {
foreach (libxml_get_errors() as $error) {
$errors[] = [
'line' => $error->line,
'column' => $error->column,
'message' => trim($error->message),
'level' => $error->level === LIBXML_ERR_ERROR ? 'error' : 'warning',
];
}
}

libxml_clear_errors();

return [
'valid' => $valid,
'errors' => $errors,
];
}

public function validateWellFormed(string $xml): array
{
libxml_use_internal_errors(true);

$dom = new DOMDocument();
$valid = $dom->loadXML($xml);

$errors = [];
if (!$valid) {
foreach (libxml_get_errors() as $error) {
$errors[] = [
'line' => $error->line,
'message' => trim($error->message),
];
}
}

libxml_clear_errors();

return [
'valid' => $valid,
'errors' => $errors,
];
}
}

XML 转换服务

完整 XML 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php

namespace App\Services;

use SimpleXMLElement;
use DOMDocument;

class XmlService
{
public function toArray(string $xml): array
{
$simpleXml = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
return json_decode(json_encode($simpleXml), true);
}

public function toJson(string $xml, int $options = 0): string
{
$array = $this->toArray($xml);
return json_encode($array, $options);
}

public function fromArray(array $data, string $root = 'root', bool $formatOutput = true): string
{
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = $formatOutput;

$rootElement = $dom->createElement($root);
$dom->appendChild($rootElement);

$this->buildXml($dom, $rootElement, $data);

return $dom->saveXML();
}

protected function buildXml(DOMDocument $dom, DOMElement $parent, array $data): void
{
foreach ($data as $key => $value) {
if (str_starts_with($key, '@')) {
$parent->setAttribute(substr($key, 1), (string) $value);
continue;
}

if (str_starts_with($key, '#')) {
$parent->textContent = (string) $value;
continue;
}

if (is_array($value)) {
if ($this->isSequential($value)) {
foreach ($value as $item) {
$child = $dom->createElement($key);
$parent->appendChild($child);
if (is_array($item)) {
$this->buildXml($dom, $child, $item);
} else {
$child->textContent = (string) $item;
}
}
} else {
$child = $dom->createElement($key);
$parent->appendChild($child);
$this->buildXml($dom, $child, $value);
}
} else {
$child = $dom->createElement($key);
$child->textContent = (string) $value;
$parent->appendChild($child);
}
}
}

protected function isSequential(array $array): bool
{
return array_keys($array) === range(0, count($array) - 1);
}

public function transform(string $xml, string $xsltPath): string
{
$xmlDoc = new DOMDocument();
$xmlDoc->loadXML($xml);

$xsltDoc = new DOMDocument();
$xsltDoc->load($xsltPath);

$processor = new \XSLTProcessor();
$processor->importStylesheet($xsltDoc);

return $processor->transformToXML($xmlDoc);
}
}

XML 导入导出

模型 XML 导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php

namespace App\Services;

use App\Models\User;
use App\Services\XmlService;

class UserXmlExport
{
protected XmlService $xmlService;

public function __construct(XmlService $xmlService)
{
$this->xmlService = $xmlService;
}

public function export(array $userIds = null): string
{
$query = User::query();

if ($userIds) {
$query->whereIn('id', $userIds);
}

$users = $query->get();

$data = [
'@version' => '1.0',
'@generated' => now()->toIso8601String(),
'users' => [
'user' => $users->map(fn($user) => $this->formatUser($user))->toArray(),
],
];

return $this->xmlService->fromArray($data, 'users');
}

protected function formatUser(User $user): array
{
return [
'@id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'created_at' => $user->created_at->toIso8601String(),
];
}

public function download(array $userIds = null)
{
$xml = $this->export($userIds);

return response($xml, 200, [
'Content-Type' => 'application/xml',
'Content-Disposition' => 'attachment; filename="users.xml"',
]);
}
}

XML 导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

namespace App\Services;

use App\Models\User;
use App\Services\XmlService;

class UserXmlImport
{
protected XmlService $xmlService;

public function __construct(XmlService $xmlService)
{
$this->xmlService = $xmlService;
}

public function import(string $xml): array
{
$data = $this->xmlService->toArray($xml);

$results = [
'total' => 0,
'success' => 0,
'failed' => 0,
'errors' => [],
];

$users = $data['users']['user'] ?? [];

if (isset($users['@id'])) {
$users = [$users];
}

foreach ($users as $index => $userData) {
$results['total']++;

try {
$this->importUser($userData);
$results['success']++;
} catch (\Exception $e) {
$results['failed']++;
$results['errors'][] = [
'index' => $index,
'error' => $e->getMessage(),
];
}
}

return $results;
}

protected function importUser(array $data): User
{
$id = $data['@id'] ?? null;

$userData = [
'name' => $data['name'] ?? '',
'email' => $data['email'] ?? '',
];

if ($id) {
$user = User::updateOrCreate(['id' => $id], $userData);
} else {
$user = User::create($userData);
}

return $user;
}
}

总结

Laravel 13 的 XML 处理提供了:

  • SimpleXML 和 DOMDocument 解析
  • 灵活的 XML 构建器
  • XSD 验证支持
  • XML 与数组互转
  • XSLT 转换功能
  • 模型导入导出

掌握 XML 处理技巧可以满足各种数据交换需求。