ほたてメモ

日々学んだことをメモメモ

PHP8.2 で DOM操作をする際のエンティティの変換について

PHP7.4 で HTML を DOMDocument を使って変換する処理をしていたが PHP8.2 に上げたところ、 mb_convert_encoding の Deprecated エラーが出るようになった。

PHP Deprecated:  mb_convert_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity 

エラーを解消するのに手間取ったので記録として残しておく。

従来の方法

これまでは以下のような変換をしていた。

<?php

// 1. DOMDocument を使いたいが、単独の & を渡すと warning が出るので事前に &amp; に変換する
$escaped = str_replace('&', '&amp;', $html);

// 2. DOMDocument に utf-8 の文字列を渡すとそのままでは文字化けするので、mb_convert_encoding でエンティティに変換する
$encoded = mb_convert_encoding($escaped, 'HTML-ENTITIES', 'utf-8');

// 3. DOM操作をして変換後の HTML を取得する
$doc = new DOMDocument();
$doc->loadHTML($encoded);
$savedHtml = $doc->saveHTML();

// 4. 変換後のHTMLはエンティティのままなので、再度 mb_convert_encoding で元に戻す
$result = mb_convert_encoding($savedHtml, 'utf-8', 'HTML-ENTITIES');

新しい方法

最終的に以下の形にした。

<?php

// 1.事前に & → &amp; に変換する処理は同じ
$escaped = str_replace('&', '&amp;', $html);

// 2. mb_convert_encoding の代わりに mb_encode_numericentity を使う
$map = [0x80, 0x10ffff, 0, 0x1fffff];  // ascii を除くユニコード文字の範囲
$encoded = mb_encode_numericentity($escaped, $map, 'utf-8');

// 3. DOM操作をして変換後の HTML を取得する
$doc = new DOMDocument();
$doc->loadHTML($encoded);
$savedHtml = $doc->saveHTML();

// 4. html_entity_decode でエンティティを元の文字に戻す
$result = html_entity_decode($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'utf-8');

変換処理について

mb_convert_encoding の代わりに、 mb_encode_numericentityhtml_entity_decode を使用したが、それぞれどのように変換されるか実験した。

例として、元の HTML が以下の形だったとする。

<p>&</p>

<p> タグの中は以下のように変化した。

初期状態 あ & ♥ あ & ♥
1. & に変換 あ &amp; ♥ あ &amp; ♥
2. エンティティに変換 &#12354; &amp; &hearts; &#12354; &amp; &#9829;
3. DOM操作 &#12354; &amp; &hearts; &#12354; &amp; &hearts;
4. デコード あ & ♥ あ & ♥
  1. &&amp; の置き換えは DOMDocument に渡すために必要なので、どちらの方式も同じ
  2. mb_convert_encoding では「♥」が名前付きエンティティ (&hearts;) に変換されていたが、 mb_encode_numericentityでは数値エンティティに置き換わっている点が異なる
  3. DOMDocument にエンティティに変換した文字列を渡すと、名前付きエンティティを持っている数値エンティティは名前付きに変換されるため、新方式ではこのタイミングで &hearts; に置き換わっている
  4. 旧方式 (mb_convert_encoding) では名前付き・数値エンティティのどちらも変換されるので、元の文字列に戻る。
    新方式は 2. で使った mb_encode_numericentity の対になる関数は mb_decode_numericentity だが、数値エンティティのみデコードするため、&amp;&hearts; は残ってしまう。 html_entity_decode であればどちらも変換するので、今回はデコードに html_entity_decode を使う形にした。