
PHP 8.1 起,DOMDocument 及其子类被明确标记为不可序列化(__serialize/__sleep 均失效),这是由核心源码强制限制所致;本文详解原因,并提供兼容 PHP 8.1+ 的安全、无破坏性替代方案。
php 8.1 起,domdocument 及其子类被明确标记为不可序列化(`__serialize`/`__sleep` 均失效),这是由核心源码强制限制所致;本文详解原因,并提供兼容 php 8.1+ 的安全、无破坏性替代方案。
自 PHP 8.1 RC1(2021年8月10日)起,PHP 核心团队在源码中显式禁用了所有 DOM 相关类(包括 DOMDocument、DOMNode 等)的序列化能力。相关提交 ca94d55 将 DOMDocument 类标记为 IS_NOT_SERIALIZABLE,这意味着无论是否定义 __sleep()、__wakeup() 或 PHP 7.4+ 引入的 __serialize() / __unserialize(),调用 serialize() 时均会直接抛出 Exception: Serialization of '...' is not allowed —— 该限制发生在序列化引擎最底层,完全绕过用户定义的魔术方法。
因此,以下两种写法在 PHP 8.1+ 中均无效:
// ❌ 无效:__sleep/__wakeup 被忽略
class SerializableDomDocument extends DOMDocument
{
private $xmlData;
public function __sleep(): array { $this->xmlData = $this->saveXML(); return ['xmlData']; }
public function __wakeup(): void { $this->loadXML($this->xmlData); }
}
// ❌ 无效:__serialize/__unserialize 同样被拦截
class SerializableDomDocument extends DOMDocument
{
public function __serialize(): array { return ['xml' => $this->saveXML()]; }
public function __unserialize(array $data): void { $this->loadXML($data['xml']); }
}✅ 正确的应对策略是:放弃继承 DOMDocument 实现自动序列化,转而采用组合模式 + 显式序列化代理。这既保持 API 兼容性,又完全规避内核限制:
class SerializableDomDocument
{
private DOMDocument $dom;
public function __construct(string $xml = '', string $charset = 'UTF-8')
{
$this->dom = new DOMDocument();
if ($xml !== '') {
$this->dom->loadXML($xml) === true || throw new InvalidArgumentException('Invalid XML');
}
$this->dom->encoding = $charset;
}
// 代理所有 DOMDocument 方法(可按需实现或使用 __call)
public function saveXML(?DOMNode $node = null, int $options = 0): string
{
return $this->dom->saveXML($node, $options);
}
public function loadXML(string $source, int $options = 0): bool
{
return $this->dom->loadXML($source, $options);
}
// ✅ 安全序列化:仅序列化字符串,不触碰 DOMDocument 实例
public function __serialize(): array
{
return ['xml' => $this->dom->saveXML(), 'encoding' => $this->dom->encoding];
}
public function __unserialize(array $data): void
{
$this->dom = new DOMDocument();
$this->dom->encoding = $data['encoding'] ?? 'UTF-8';
$this->dom->loadXML($data['xml']) === true || throw new RuntimeException('Failed to restore XML');
}
// 提供 DOMDocument 实例访问(只读推荐,避免意外序列化)
public function getDOMDocument(): DOMDocument
{
return $this->dom;
}
}? 关键注意事项:
立即学习“PHP免费学习笔记(深入)”;
- 不要将
DOMDocument实例作为属性直接序列化(即使未继承它),因为serialize()会递归检查所有属性类型; - 若需跨请求/存储传递,始终以
string(XML)形式保存,运行时再重建DOMDocument; - 使用
__serialize()时,确保返回数组仅含标量、数组、对象(且其类本身可序列化)——本例中DOMDocument不出现于序列化数据中; - 对性能敏感场景,可缓存
saveXML()结果,避免重复解析。
此方案无需修改现有调用方代码(只要接口契约不变),零破坏升级 PHP 8.1+,是当前最稳健、符合 PHP 内核演进方向的实践。



















