PHPのTidy拡張でHTMLを処理していると、「エラーや警告の件数はわかったけれど、具体的にどこが問題なのかをもっと詳しく知りたい」という場面があります。そんなときに使うのが tidy_diagnose() 関数です。
tidy_diagnose() を呼び出すと、Tidy が解析したHTMLに関するより詳細な診断情報(文書の統計情報や追加の警告メッセージ)を $tidy->errorBuffer に追記してくれます。この記事では基本仕様から実践的な活用パターンまで解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | tidy_diagnose() |
| 読み方 | タイディ・ダイアグノーズ |
| 分類 | Tidy関数 |
| 対応バージョン | PHP 5以降(Tidy拡張が有効な場合) |
| 引数 | tidyオブジェクト |
| 戻り値 | bool(成功時 true、失敗時 false) |
| 必要な拡張 | tidy(多くの環境で標準バンドル) |
| OOPスタイル | $tidy->diagnose() |
構文
// 手続き型
tidy_diagnose(tidy $tidy): bool
// オブジェクト指向型
$tidy->diagnose(): bool
tidy_diagnose() は tidy_clean_repair() の後に呼び出すことで、診断レポートを $tidy->errorBuffer プロパティに追記します。この関数を呼ばなくても errorBuffer には基本的なエラーログが入りますが、tidy_diagnose() を呼ぶことで文書統計情報などがさらに追加されます。
errorBuffer への追記の仕組み
tidy_parse_string()
│
▼
tidy_clean_repair()
│ ← errorBuffer にエラー・警告ログが書き込まれる
▼
tidy_diagnose() ← 診断情報(文書統計など)が errorBuffer に追記される
│
▼
$tidy->errorBuffer を参照
└─ cleanRepair のログ + diagnose の追加情報
tidy_diagnose() を呼ぶ前後で errorBuffer の内容を比較すると、診断呼び出し後に「Info:」で始まる統計情報の行が追加されていることが確認できます。
基本的な使い方
<?php
$html = '<html><body><p>テスト<b>太字</b></body></html>';
$tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
tidy_clean_repair($tidy);
echo "=== diagnose 前の errorBuffer ===" . PHP_EOL;
echo $tidy->errorBuffer . PHP_EOL;
tidy_diagnose($tidy);
echo "=== diagnose 後の errorBuffer ===" . PHP_EOL;
echo $tidy->errorBuffer . PHP_EOL;
実行結果:
=== diagnose 前の errorBuffer ===
line 1 column 32 - Warning: missing </body> insertion
=== diagnose 後の errorBuffer ===
line 1 column 32 - Warning: missing </body> insertion
Info: Document content looks like HTML5
Tidy found 0 warnings and 0 errors!
diagnose() 呼び出し後に「Info:」で始まる統計行(文書形式の判定、警告・エラーの総計)が追記されているのが確認できます。
実践的なコード例
例1:診断レポートを整形して出力する基本クラス
<?php
class TidyDiagnosticsReporter
{
public function report(string $html, array $config = []): void
{
$defaultConfig = [
'output-xhtml' => true,
'accessibility-check' => 1,
'char-encoding' => 'utf8',
];
$tidy = tidy_parse_string($html, array_merge($defaultConfig, $config), 'UTF8');
tidy_clean_repair($tidy);
tidy_diagnose($tidy);
echo "=== Tidy 診断レポート ===" . PHP_EOL;
printf("設定エラー : %d 件\n", tidy_config_count($tidy));
printf("HTMLエラー : %d 件\n", tidy_error_count($tidy));
printf("HTML警告 : %d 件\n", tidy_warning_count($tidy));
printf("アクセシビリティ警告 : %d 件\n", tidy_access_count($tidy));
echo PHP_EOL . "=== 詳細ログ (errorBuffer) ===" . PHP_EOL;
echo $tidy->errorBuffer . PHP_EOL;
}
}
$reporter = new TidyDiagnosticsReporter();
$reporter->report(
'<html><body><img src="photo.jpg"><p>テスト<b>未閉じ</body></html>'
);
実行結果:
=== Tidy 診断レポート ===
設定エラー : 0 件
HTMLエラー : 0 件
HTML警告 : 2 件
アクセシビリティ警告 : 1 件
=== 詳細ログ (errorBuffer) ===
line 1 column 35 - Warning: missing </b> before </body>
line 1 column 35 - Warning: missing </body> insertion
line 1 column 14 - Access: [1.1.1]: <img> missing 'alt' text.
Info: Document content looks like HTML5
Tidy found 2 warnings and 0 errors!
例2:diagnose 前後の errorBuffer の差分を取り出す
<?php
class DiagnoseBufferDiffExtractor
{
public function extract(string $html): array
{
$tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
tidy_clean_repair($tidy);
$beforeDiagnose = $tidy->errorBuffer;
tidy_diagnose($tidy);
$afterDiagnose = $tidy->errorBuffer;
// diagnose が追記した部分だけを取り出す
$addedContent = trim(substr($afterDiagnose, strlen($beforeDiagnose)));
return [
'before' => trim($beforeDiagnose),
'added' => $addedContent,
'full' => trim($afterDiagnose),
];
}
}
$extractor = new DiagnoseBufferDiffExtractor();
$diff = $extractor->extract('<html><body><p>サンプル</p></body></html>');
echo "【diagnose 前のログ】" . PHP_EOL;
echo ($diff['before'] ?: '(なし)') . PHP_EOL . PHP_EOL;
echo "【diagnose が追記した内容】" . PHP_EOL;
echo ($diff['added'] ?: '(なし)') . PHP_EOL;
実行結果:
【diagnose 前のログ】
(なし)
【diagnose が追記した内容】
Info: Document content looks like HTML5
Tidy found 0 warnings and 0 errors!
tidy_diagnose() が errorBuffer に追記する動作であることを、差分として可視化した例です。
例3:OOPスタイルでの記述
<?php
class OopTidyDiagnoser
{
private tidy $tidy;
public function process(string $html, array $config = []): self
{
$this->tidy = new tidy();
$this->tidy->parseString($html, array_merge([
'output-xhtml' => true,
'char-encoding' => 'utf8',
], $config), 'UTF8');
$this->tidy->cleanRepair(); // 修正を適用
$this->tidy->diagnose(); // OOPスタイルの diagnose()
return $this;
}
public function getErrorBuffer(): string
{
return $this->tidy->errorBuffer;
}
public function getRepairedHtml(): string
{
return (string) $this->tidy;
}
}
$diagnoser = new OopTidyDiagnoser();
$diagnoser->process('<p>テスト<strong>太字</p>');
echo $diagnoser->getErrorBuffer();
実行結果:
line 1 column 15 - Warning: <strong> inserting missing 'title' attribute
line 1 column 22 - Warning: discarding unexpected </p>
Info: Document content looks like HTML5
Tidy found 2 warnings and 0 errors!
例4:ログをレベル別にパースして構造化する
<?php
class ErrorBufferParser
{
public function parse(string $html): array
{
$tidy = tidy_parse_string($html, [
'output-xhtml' => true,
'accessibility-check' => 1,
], 'UTF8');
tidy_clean_repair($tidy);
tidy_diagnose($tidy);
$lines = explode("\n", $tidy->errorBuffer);
$parsed = ['Warning' => [], 'Error' => [], 'Access' => [], 'Info' => []];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') {
continue;
}
foreach (array_keys($parsed) as $level) {
if (stripos($line, $level . ':') !== false) {
$parsed[$level][] = $line;
break;
}
}
}
return $parsed;
}
}
$parser = new ErrorBufferParser();
$result = $parser->parse(
'<html><body><img src="a.jpg"><p>テスト<b>未閉じ</p></body></html>'
);
foreach ($result as $level => $messages) {
if (!empty($messages)) {
echo "[{$level}]" . PHP_EOL;
foreach ($messages as $msg) {
echo " {$msg}" . PHP_EOL;
}
}
}
実行結果:
[Warning]
line 1 column 28 - Warning: missing </b> before </p>
[Access]
line 1 column 14 - Access: [1.1.1]: <img> missing 'alt' text.
[Info]
Info: Document content looks like HTML5
Tidy found 1 warnings and 0 errors!
errorBuffer の文字列をレベルごとに分類することで、ログを構造化データとして扱いやすくなります。
例5:diagnose 結果をログファイルに保存する
<?php
class TidyDiagnoseLogger
{
public function __construct(
private readonly string $logPath = '/tmp/tidy-diagnose.log'
) {}
public function processAndLog(string $label, string $html): bool
{
$tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
tidy_clean_repair($tidy);
$result = tidy_diagnose($tidy);
$logEntry = sprintf(
"[%s] === %s ===\n%s\n\n",
date('Y-m-d H:i:s'),
$label,
$tidy->errorBuffer
);
file_put_contents($this->logPath, $logEntry, FILE_APPEND | LOCK_EX);
return $result;
}
}
$logger = new TidyDiagnoseLogger();
$logger->processAndLog('トップページ', '<html><body><h1>ホーム</h1></body></html>');
$logger->processAndLog('商品ページ', '<html><body><img src="item.jpg"></body></html>');
echo "ログを {$logger->logPath} に保存しました。" . PHP_EOL;
ログファイルへの出力例:
[2026-06-25 14:30:00] === トップページ ===
Info: Document content looks like HTML5
Tidy found 0 warnings and 0 errors!
[2026-06-25 14:30:00] === 商品ページ ===
line 1 column 14 - Warning: <img> lacks "alt" attribute
Info: Document content looks like HTML5
Tidy found 1 warnings and 0 errors!
例6:複数ページを一括診断してサマリーを出力する
<?php
class BulkDiagnosticRunner
{
public function run(array $pages): void
{
$totalWarnings = 0;
$totalErrors = 0;
printf("%-20s %8s %8s %12s\n", 'ページ名', 'エラー', '警告', 'A11y警告');
echo str_repeat('-', 52) . PHP_EOL;
foreach ($pages as $label => $html) {
$tidy = tidy_parse_string($html, [
'output-xhtml' => true,
'accessibility-check' => 1,
], 'UTF8');
tidy_clean_repair($tidy);
tidy_diagnose($tidy);
$errors = tidy_error_count($tidy);
$warnings = tidy_warning_count($tidy);
$access = tidy_access_count($tidy);
printf("%-20s %8d %8d %12d\n", $label, $errors, $warnings, $access);
$totalErrors += $errors;
$totalWarnings += $warnings;
}
echo str_repeat('-', 52) . PHP_EOL;
printf("%-20s %8d %8d\n", '合計', $totalErrors, $totalWarnings);
}
}
$runner = new BulkDiagnosticRunner();
$runner->run([
'トップ' => '<html><body><h1>ホーム</h1></body></html>',
'商品一覧' => '<html><body><img src="item.jpg"><a href="#"></a></body></html>',
'お問い合わせ' => '<html><body><form><input type="text"><button>送信</button></form></body></html>',
]);
実行結果:
ページ名 エラー 警告 A11y警告
----------------------------------------------------
トップ 0 0 0
商品一覧 0 0 2
お問い合わせ 0 0 1
----------------------------------------------------
合計 0 0
例7:戻り値を使った安全な呼び出し
<?php
class SafeDiagnoser
{
public function diagnose(string $html): ?string
{
$tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
tidy_clean_repair($tidy);
$success = tidy_diagnose($tidy);
if (!$success) {
trigger_error('tidy_diagnose() が失敗しました。', E_USER_WARNING);
return null;
}
return $tidy->errorBuffer;
}
}
$diagnoser = new SafeDiagnoser();
$buffer = $diagnoser->diagnose('<p>シンプルなHTML</p>');
if ($buffer !== null) {
echo $buffer;
}
実行結果:
Info: Document content looks like HTML5
Tidy found 0 warnings and 0 errors!
関連関数との比較
| 関数 | OOPスタイル | 役割 |
|---|---|---|
tidy_diagnose() | diagnose() | 診断情報を errorBuffer に追記する |
tidy_clean_repair() | cleanRepair() | HTMLを修正・整形する(diagnoseの前に呼ぶ) |
tidy_error_count() | getErrorCount() | エラー件数を返す |
tidy_warning_count() | getWarningCount() | 警告件数を返す |
tidy_access_count() | getAccessCount() | アクセシビリティ警告件数を返す |
tidy_config_count() | なし | 設定エラー件数を返す |
$tidy->errorBuffer | errorBuffer プロパティ | ログ文字列を直接参照する |
tidy_diagnose() は件数を返す関数ではなく、errorBuffer の内容を充実させるための関数という位置づけです。詳細なログが必要な場面では cleanRepair() の後に diagnose() を呼び、errorBuffer を参照するというパターンを使いましょう。
よくある落とし穴・注意点
tidy_clean_repair()の前に呼んでも効果が薄いtidy_diagnose()は解析・修正済みのHTMLに対して動作します。cleanRepair()を呼ぶ前にdiagnose()を実行しても、十分な診断情報が得られない場合があります。parse → cleanRepair → diagnoseの順番を守りましょう。errorBufferへの「追記」であり「上書き」ではないtidy_diagnose()はerrorBufferを上書きするのではなく追記します。cleanRepair()時点ですでにerrorBufferに書き込まれていたログの内容はそのまま残り、diagnose()によって末尾に診断情報が付け加えられます。- 戻り値は診断結果の合否ではなく実行成否
tidy_diagnose()の戻り値boolは「問題が見つかった/見つからなかった」を示すものではなく、関数の実行が成功したかどうかを示します。HTML品質の合否判定にはtidy_error_count()などを使ってください。 errorBufferが空文字列になることがある HTMLが完全に正常で診断情報もない場合、errorBufferは空文字列になることがあります。$tidy->errorBufferを参照する前に空チェックを入れておくと、不要な空出力を防げます。- Tidy拡張のバージョンによって診断情報の書式が異なる場合がある
errorBufferの出力内容はTidyライブラリのバージョンに依存します。ログをプログラムで解析する際は、書式が変わる可能性を考慮して過度に厳密なパターンマッチングを避けましょう。
まとめ
| ポイント | 内容 |
|---|---|
| 役割 | 診断情報を errorBuffer に追記する |
| 引数 | tidyオブジェクト |
| 戻り値 | bool(実行成否。診断内容の合否ではない) |
| 呼び出しタイミング | tidy_clean_repair() の後 |
| OOPスタイル | $tidy->diagnose() |
| 追記先 | $tidy->errorBuffer プロパティ |
| よく組み合わせる関数 | tidy_clean_repair(), tidy_error_count(), tidy_warning_count() |
| 主な用途 | 詳細ログの取得、診断レポート生成、CI・ログファイルへの記録 |
| 注意点 | cleanRepair() の後に呼ぶ・追記であり上書きではない・戻り値は品質合否ではない |
tidy_diagnose() は「件数を数える」関数ではなく、「errorBuffer の内容を詳しくする」関数です。エラー件数だけでは不十分で、具体的にどの行で何が問題だったかを知りたい場面では、cleanRepair() とセットで呼び出し、errorBuffer を参照する流れが基本パターンになります。
