[PHP]tidy_diagnose関数でHTMLの診断情報をerrorBufferに追記する方法を解説

PHP

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->errorBuffererrorBuffer プロパティログ文字列を直接参照する

tidy_diagnose() は件数を返す関数ではなく、errorBuffer内容を充実させるための関数という位置づけです。詳細なログが必要な場面では cleanRepair() の後に diagnose() を呼び、errorBuffer を参照するというパターンを使いましょう。

よくある落とし穴・注意点

  1. tidy_clean_repair() の前に呼んでも効果が薄い tidy_diagnose() は解析・修正済みのHTMLに対して動作します。cleanRepair() を呼ぶ前に diagnose() を実行しても、十分な診断情報が得られない場合があります。parse → cleanRepair → diagnose の順番を守りましょう。
  2. errorBuffer への「追記」であり「上書き」ではない tidy_diagnose()errorBuffer を上書きするのではなく追記します。cleanRepair() 時点ですでに errorBuffer に書き込まれていたログの内容はそのまま残り、diagnose() によって末尾に診断情報が付け加えられます。
  3. 戻り値は診断結果の合否ではなく実行成否 tidy_diagnose() の戻り値 bool は「問題が見つかった/見つからなかった」を示すものではなく、関数の実行が成功したかどうかを示します。HTML品質の合否判定には tidy_error_count() などを使ってください。
  4. errorBuffer が空文字列になることがある HTMLが完全に正常で診断情報もない場合、errorBuffer は空文字列になることがあります。$tidy->errorBuffer を参照する前に空チェックを入れておくと、不要な空出力を防げます。
  5. 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 を参照する流れが基本パターンになります。

タイトルとURLをコピーしました