[PHP]tidy_access_count関数でHTMLアクセシビリティ警告の件数を取得する方法を解説

PHP

Webアクセシビリティへの関心が高まるなか、HTMLのアクセシビリティ上の問題を自動検出できれば開発効率は大きく向上します。PHPのTidy拡張には、HTMLを解析してアクセシビリティ上の警告件数を取得する tidy_access_count() という関数が用意されています。

知名度は高くありませんが、CIパイプラインでの自動チェックや、CMS系のコンテンツ検証に組み込める実用的な関数です。この記事では基本仕様から実践的な活用例まで詳しく解説します。

関数概要

項目内容
関数名tidy_access_count()
読み方タイディ・アクセス・カウント
分類Tidy関数
対応バージョンPHP 5以降(Tidy拡張が有効な場合)
引数tidyオブジェクト
戻り値int(アクセシビリティ警告の件数)
必要な拡張tidy(多くの環境で標準バンドル)
OOPスタイル$tidy->getAccessCount()

構文

// 手続き型
tidy_access_count(tidy $tidy): int

// オブジェクト指向型(推奨)
$tidy->getAccessCount(): int

tidy_access_count()tidy オブジェクトを受け取り、そのHTMLドキュメントに対して Tidy が検出したアクセシビリティ関連の警告の件数を整数で返します。

重要:この関数が返すのは アクセシビリティ警告 の件数であり、HTMLの構文エラー件数ではありません。構文エラーには tidy_error_count()、警告全体には tidy_warning_count() を使います。

アクセシビリティチェックを有効にするための設定

tidy_access_count() でアクセシビリティ警告を検出するには、Tidyの設定で accessibility-check オプションを明示的に指定する必要があります。

設定値意味
0アクセシビリティチェックなし(デフォルト)
1WCAG 1.0 レベル1(最低限の基準)
2WCAG 1.0 レベル2
3WCAG 1.0 レベル3(最も厳格)
<?php
$config = [
    'accessibility-check' => 3, // 最も厳格なチェック
    'output-xhtml'        => true,
    'char-encoding'       => 'utf8',
];

この設定を tidy_parse_string()tidy_parse_file() に渡すことで、アクセシビリティチェックが有効になります。

処理の流れ

HTML文字列 or ファイル
        │
        │ tidy_parse_string() / tidy_parse_file()
        │ ※ accessibility-check オプションを渡す
        ▼
   tidy オブジェクト
        │
        ├─→ tidy_access_count()   → アクセシビリティ警告の件数
        ├─→ tidy_error_count()    → 構文エラーの件数
        └─→ tidy_warning_count()  → 警告の件数

基本的な使い方

<?php
$html = '<html><body><img src="photo.jpg"></body></html>';

$config = ['accessibility-check' => 1];
$tidy = tidy_parse_string($html, $config, 'UTF8');
tidy_clean_repair($tidy);

$count = tidy_access_count($tidy);
echo "アクセシビリティ警告: {$count} 件" . PHP_EOL;

実行結果:

アクセシビリティ警告: 1 件

<img> タグに alt 属性がないため、アクセシビリティ警告が1件検出されています。

実践的なコード例

例1:HTMLアクセシビリティを検査する基本クラス

<?php
class HtmlAccessibilityChecker
{
    private tidy $tidy;

    public function __construct(
        private readonly int $checkLevel = 1
    ) {}

    public function checkString(string $html): self
    {
        $config = [
            'accessibility-check' => $this->checkLevel,
            'output-xhtml'        => true,
            'char-encoding'       => 'utf8',
        ];

        $this->tidy = tidy_parse_string($html, $config, 'UTF8');
        tidy_clean_repair($this->tidy);

        return $this;
    }

    public function getAccessibilityCount(): int
    {
        return tidy_access_count($this->tidy);
    }

    public function getErrorCount(): int
    {
        return tidy_error_count($this->tidy);
    }

    public function getWarningCount(): int
    {
        return tidy_warning_count($this->tidy);
    }

    public function getSummary(): array
    {
        return [
            'accessibility_warnings' => $this->getAccessibilityCount(),
            'errors'                 => $this->getErrorCount(),
            'warnings'               => $this->getWarningCount(),
            'is_accessible'          => $this->getAccessibilityCount() === 0,
        ];
    }
}

$checker = new HtmlAccessibilityChecker(checkLevel: 1);

$html = <<<HTML
<html>
<body>
  <img src="logo.png">
  <a href="#"></a>
  <input type="text">
</body>
</html>
HTML;

$checker->checkString($html);
print_r($checker->getSummary());

実行結果:

Array
(
    [accessibility_warnings] => 3
    [errors] => 0
    [warnings] => 1
    [is_accessible] => 
)

<img>alt 属性欠落、<a> のテキスト欠落、<input><label> 欠落がそれぞれ検出されています。

例2:チェックレベルごとの件数を比較する

<?php
class AccessibilityLevelComparator
{
    public function compare(string $html): array
    {
        $results = [];

        foreach ([0, 1, 2, 3] as $level) {
            $config = ['accessibility-check' => $level];
            $tidy   = tidy_parse_string($html, $config, 'UTF8');
            tidy_clean_repair($tidy);

            $results["level_{$level}"] = tidy_access_count($tidy);
        }

        return $results;
    }
}

$comparator = new AccessibilityLevelComparator();

$html = <<<HTML
<html>
<body>
  <img src="banner.jpg">
  <table><tr><td>データ</td></tr></table>
  <blink>古いタグ</blink>
</body>
</html>
HTML;

$results = $comparator->compare($html);

foreach ($results as $level => $count) {
    printf("%-10s: %d 件\n", $level, $count);
}

実行結果:

level_0   : 0 件
level_1   : 1 件
level_2   : 2 件
level_3   : 3 件

レベルが上がるほど検出されるアクセシビリティ問題の件数が増えることがわかります。level_0 の場合はアクセシビリティチェックそのものが無効なため常に0件です。

例3:CIパイプライン向けの合否判定スクリプト

<?php
class AccessibilityGate
{
    public function __construct(
        private readonly int $maxAllowed = 0,
        private readonly int $checkLevel = 1
    ) {}

    public function evaluate(string $html, string $label = 'HTML'): bool
    {
        $config = ['accessibility-check' => $this->checkLevel];
        $tidy   = tidy_parse_string($html, $config, 'UTF8');
        tidy_clean_repair($tidy);

        $count = tidy_access_count($tidy);
        $pass  = $count <= $this->maxAllowed;

        $icon   = $pass ? '✓' : '✗';
        $status = $pass ? 'PASS' : 'FAIL';

        printf(
            "[%s] %s - %s: アクセシビリティ警告 %d 件(許容上限: %d 件)\n",
            $icon, $status, $label, $count, $this->maxAllowed
        );

        return $pass;
    }
}

$gate = new AccessibilityGate(maxAllowed: 0, checkLevel: 1);

$pages = [
    'トップページ' => '<html><body><h1>ようこそ</h1><p>本文</p></body></html>',
    '商品一覧'     => '<html><body><img src="item.jpg"><a href="#"></a></body></html>',
];

$allPassed = true;
foreach ($pages as $label => $html) {
    if (!$gate->evaluate($html, $label)) {
        $allPassed = false;
    }
}

echo PHP_EOL . ($allPassed ? "すべてのページが合格しました。" : "アクセシビリティ上の問題が検出されました。") . PHP_EOL;
exit($allPassed ? 0 : 1);

実行結果:

[✓] PASS - トップページ: アクセシビリティ警告 0 件(許容上限: 0 件)
[✗] FAIL - 商品一覧: アクセシビリティ警告 2 件(許容上限: 0 件)

アクセシビリティ上の問題が検出されました。

例4:ファイルを読み込んで検査する

<?php
class HtmlFileAccessibilityAuditor
{
    public function __construct(
        private readonly int $checkLevel = 1
    ) {}

    public function audit(string $filePath): array
    {
        if (!file_exists($filePath)) {
            throw new RuntimeException("ファイルが見つかりません: {$filePath}");
        }

        $config = ['accessibility-check' => $this->checkLevel];
        $tidy   = tidy_parse_file($filePath, $config, 'UTF8');
        tidy_clean_repair($tidy);

        return [
            'file'                   => basename($filePath),
            'accessibility_warnings' => tidy_access_count($tidy),
            'errors'                 => tidy_error_count($tidy),
            'warnings'               => tidy_warning_count($tidy),
        ];
    }
}

$auditor = new HtmlFileAccessibilityAuditor(checkLevel: 2);

// HTMLファイルが /tmp/page.html に存在すると仮定
// $result = $auditor->audit('/tmp/page.html');
// print_r($result);

例5:複数HTMLのレポートを一括生成する

<?php
class BulkAccessibilityReporter
{
    private array $results = [];

    public function __construct(
        private readonly int $checkLevel = 1
    ) {}

    public function addHtml(string $label, string $html): self
    {
        $config = ['accessibility-check' => $this->checkLevel];
        $tidy   = tidy_parse_string($html, $config, 'UTF8');
        tidy_clean_repair($tidy);

        $this->results[] = [
            'label'   => $label,
            'count'   => tidy_access_count($tidy),
            'errors'  => tidy_error_count($tidy),
        ];

        return $this;
    }

    public function printReport(): void
    {
        $total = array_sum(array_column($this->results, 'count'));

        printf("%-20s %10s %10s\n", 'ページ名', 'A11y警告', 'エラー');
        echo str_repeat('-', 42) . PHP_EOL;

        foreach ($this->results as $r) {
            printf("%-20s %10d %10d\n", $r['label'], $r['count'], $r['errors']);
        }

        echo str_repeat('-', 42) . PHP_EOL;
        printf("%-20s %10d\n", '合計', $total);
    }
}

$reporter = new BulkAccessibilityReporter(checkLevel: 1);

$reporter
    ->addHtml('トップ', '<html><body><h1>ホーム</h1></body></html>')
    ->addHtml('お問い合わせ', '<html><body><img src="x.jpg"><form><input type="text"></form></body></html>')
    ->addHtml('会社概要', '<html><body><img src="logo.png" alt="ロゴ"><p>会社情報</p></body></html>');

$reporter->printReport();

実行結果:

ページ名                  A11y警告     エラー
------------------------------------------
トップ                           0          0
お問い合わせ                     2          0
会社概要                         0          0
------------------------------------------
合計                             2

例6:OOPスタイル(getAccessCount)での記述

<?php
class OopStyleChecker
{
    private tidy $tidy;

    public function parse(string $html, int $level = 1): self
    {
        $this->tidy = new tidy();
        $this->tidy->parseString($html, ['accessibility-check' => $level], 'UTF8');
        $this->tidy->cleanRepair();
        return $this;
    }

    public function report(): void
    {
        // OOPスタイルの getAccessCount() は tidy_access_count() と同等
        $access   = $this->tidy->getAccessCount();
        $errors   = $this->tidy->getErrorCount();
        $warnings = $this->tidy->getWarningCount();

        printf(
            "アクセシビリティ警告: %d 件 / エラー: %d 件 / 警告: %d 件\n",
            $access, $errors, $warnings
        );
    }
}

$checker = new OopStyleChecker();
$checker
    ->parse('<html><body><img src="a.jpg"><p>テスト</p></body></html>', 1)
    ->report();

実行結果:

アクセシビリティ警告: 1 件 / エラー: 0 件 / 警告: 0 件

例7:警告件数をもとにHTMLを修正提案と組み合わせる

<?php
class AccessibilityAdvisor
{
    public function analyze(string $html): void
    {
        $config = ['accessibility-check' => 1];
        $tidy   = tidy_parse_string($html, $config, 'UTF8');
        tidy_clean_repair($tidy);

        $count = tidy_access_count($tidy);

        echo "検出されたアクセシビリティ警告: {$count} 件" . PHP_EOL;

        if ($count === 0) {
            echo "問題は検出されませんでした。" . PHP_EOL;
            return;
        }

        echo PHP_EOL . "よくある原因と対処法:" . PHP_EOL;
        echo "  - <img> に alt 属性が指定されていない → alt=\"画像の説明\" を追加" . PHP_EOL;
        echo "  - <a> の中にテキストやaria-labelがない → リンクの意味がわかるテキストを追加" . PHP_EOL;
        echo "  - <input> に関連する <label> がない → id属性とfor属性で紐付け" . PHP_EOL;
        echo "  - <table> にsummaryや<caption>がない → テーブルの用途を明示" . PHP_EOL;

        echo PHP_EOL . "Tidyの詳細ログ:" . PHP_EOL;
        echo $tidy->errorBuffer . PHP_EOL;
    }
}

$advisor = new AccessibilityAdvisor();
$advisor->analyze('<html><body><img src="top.jpg"><a href="#next"></a></body></html>');

実行結果:

検出されたアクセシビリティ警告: 2 件

よくある原因と対処法:
  - <img> に alt 属性が指定されていない → alt="画像の説明" を追加
  - <a> の中にテキストやaria-labelがない → リンクの意味がわかるテキストを追加
  - <input> に関連する <label> がない → id属性とfor属性で紐付け
  - <table> にsummaryや<caption>がない → テーブルの用途を明示

Tidyの詳細ログ:
line 1 column 1 - Access: [1.1.1]: <img> missing 'alt' text.
line 1 column 1 - Access: [13.1.1]: link text not meaningful.

関連関数との比較

関数OOPスタイル取得できる情報
tidy_access_count()getAccessCount()アクセシビリティ警告の件数
tidy_error_count()getErrorCount()HTML構文エラーの件数
tidy_warning_count()getWarningCount()警告の件数
tidy_parse_string()parseString()HTML文字列を解析してtidyオブジェクトを生成
tidy_parse_file()parseFile()HTMLファイルを解析してtidyオブジェクトを生成
tidy_clean_repair()cleanRepair()HTMLの自動修正を実行(カウント前に必要)

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

  1. accessibility-check を設定しないと常に0件になる 最も多い誤りがこれです。デフォルトの設定(accessibility-check: 0)ではアクセシビリティチェックが無効なため、tidy_access_count() は常に 0 を返します。必ず 13 のいずれかを明示的に指定してください。
  2. tidy_clean_repair() を呼ばないとカウントが正確でない場合がある Tidyは cleanRepair() を実行することでHTMLの解析と修正を完了させます。この処理を省略すると、アクセシビリティチェックの結果が不正確になることがあります。parsecleanRepaircount の順番を守りましょう。
  3. WCAGの最新基準(2.1/2.2)には対応していない Tidyが参照するアクセシビリティ基準は WCAG 1.0 ベースであり、現在主流の WCAG 2.1/2.2 の基準をすべてカバーするものではありません。本格的なアクセシビリティ検証には、axe-core や Lighthouse など専用ツールの併用が推奨されます。
  4. Tidy拡張が無効な環境では使用できない tidy_access_count() を使う前に extension_loaded('tidy') で拡張の有効化を確認しましょう。共有ホスティング環境ではTidy拡張が無効になっている場合があります。
  5. 日本語(マルチバイト)コンテンツの扱い 文字エンコーディングを 'UTF8' と明示的に指定することが重要です。指定を誤るとHTMLのパースが不正確になり、アクセシビリティチェックの結果にも影響が出ることがあります。

まとめ

ポイント内容
役割Tidyが解析したHTMLのアクセシビリティ警告件数を取得する
引数tidyオブジェクト
戻り値int(アクセシビリティ警告の件数)
必須の事前設定accessibility-check を1〜3で指定してからparse
OOPスタイル$tidy->getAccessCount()
よく組み合わせる関数tidy_parse_string(), tidy_clean_repair(), tidy_error_count()
主な用途CIでのHTML品質チェック、CMS投稿コンテンツの検証、一括アクセシビリティレポート
注意点デフォルトで0件になる(設定必須)、WCAG 1.0ベースのみ対応

tidy_access_count()accessibility-check オプションとセットで使うことではじめて真価を発揮します。WCAG 1.0 ベースの検出という制限はあるものの、PHPだけで完結するアクセシビリティ検査の入り口として、CIパイプラインやCMSのコンテンツ検証に手軽に組み込める便利な関数です。

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