CakePHP 4.1 以降の debug() 実行時に配列やオブジェクトを開いた状態で表示する方法

はじめに

CakePHP 4.1 から debug() が強化され、色分けされたり、配列やオブジェクトのダンプ情報の動的な表示切替が可能になりました。

2層目以降の配列やオブジェクトの中身はデフォルトで隠れるようになっています。
これは大きめの変数をダンプするときには便利なのですが、確認する度にクリックする手間が発生し、それが煩わしく感じる場合があります。

そこで今日は、CakePHP 4 の debug() で配列やオブジェクトを開いた状態で表示する方法を2つ考えましたので、ご紹介します。

今回使用したのは CakePHP 4.1.1 です。

目次
  1. dumpHeader.html を変更して対応
  2. debug() を自作して対応
  3. app_local.php で切替可能に
  4. おわりに

1. dumpHeader.html を変更して対応

debug() で表示される <style> や <script> は、
/vendor/cakephp/cakephp/src/Error/Debug/dumpHeader.html に書かれており、
オブジェクトや配列の表示切替はその JS で制御されています。

ここに各要素を開く JS を追記すれば、初期表示の状態を変更することが可能です。

しかし、このファイルは vendor の中にあり、composer update 等で上書きされる可能性があります。

そこで composer のイベントフックを使い、composer install、composer update の後に、自動的に dumpHeader.html に JS を追記する下記プログラムを作りました。

/src/Console/DebugHeader.php
<?php
declare(strict_types=1);

namespace App\Console;

use Composer\Script\Event;

class DebugHeader
{
    public static function addOpenAllItems(Event $event)
    {
        $io = $event->getIO();

        // dumpHeader.html のファイルパス
        $filePath = dirname(dirname(__DIR__))
            . str_replace('/', DIRECTORY_SEPARATOR, '/vendor/cakephp/cakephp/src/Error/Debug/dumpHeader.html');
        if (!file_exists($filePath)) {
            $io->write('dumpHeader.html が見つかりません');
            $io->write($filePath);
            return;
        }

        // 初期表示で全項目を開く Javascriptを追加
        // (追加済か否かは $comment の有無で判断しています)
        $body = file_get_contents($filePath);
        $comment = '<!-- Open all items -->';
        if (strpos($body, $comment) !== false) {
            $io->write('dumpHeader.html は変更済みです');
            return;
        }
        // 対象要素のクリックイベントを発火させることで開く状態にする
        $body .= $comment . "\n"
            . '<script>'
            .   'window.onload = function () {'
            .     'document.querySelectorAll(\'.cake-dbg-array-items, .cake-dbg-object-props\').forEach(function (parentNode) {'
            .       'parentNode.querySelectorAll(\'.cake-dbg-collapse[data-open="false"]\').forEach(function (node) {'
            .         'node.click();'
            .       '});'
            .     '});'
            .   '};'
            . '</script>';

        if (file_put_contents($filePath, $body) === false) {
            $io->write('dumpHeader.html に書き込めません');
            return;
        }
        $io->write('dumpHeader.html を更新しました');
    }
}

composer のイベントフックを使うために、下記を参考に composer.json の post-install-cmd と post-update-cmd を変更してください。
(post-update-cmd が無ければ追加してください)

/composer.json
{
    ...
    "scripts": {
      "post-install-cmd": [
          "App\\Console\\Installer::postInstall",
          "App\\Console\\DebugHeader::addOpenAllItems"
      ],
      "post-update-cmd": "App\\Console\\DebugHeader::addOpenAllItems",
      ...
    }
    ...
}

下記のように composer install や update を実行すれば
DebugHeader::addOpenAllItems() が走ります。

$ cd /path/to/cakephp4
$ composer install

addOpenAllItems() が完了して dumpHeader.html に追記されたら debug() を使ってみてください。配列やオブジェクトの内容が自動で開かれたら成功です。

余談ですが Composer のイベントフックにつきましては、下記公式ページに説明があります。

2. debug() を自作して対応

CakePHP 4 の debug() は /vendor/cakephp/cakephp/src/basics.php の中で定義されています。その定義前に if (!function_exists('debug')) で確認していることから、basics.php が読み込まれる前にオリジナルの debug() を定義すれば、そちらを使用することが可能です。

そこで、下記のような debug() 関数を作り、通常の debug() で出力される内容に加えて、配列とオブジェクトの要素を開く JS も併せて出力するようにしました。

下記サンプルのファイル名は basics に合わせていますが、他でも OK です。

/config/basics.php
<?php
declare(strict_types=1);

use Cake\Core\Configure;
use Cake\Error\Debugger;

function debug($var, $showHtml = null, $showFrom = true)
{
    if (!Configure::read('debug')) {
        return $var;
    }
    // 元々の debug() と同様に表示
    $location = [];
    if ($showFrom) {
        $trace = Debugger::trace([
          'depth' => 2,
          'format' => 'array',
          'start' => 1
        ])[0];
        $location = [
          'file' => $trace['file'],
          'line' => $trace['line'],
        ];
    }
    Debugger::printVar($var, $location, $showHtml);

    // HTML 表示でない場合は終了
    if ($showHtml === false) {
        return $var;
    }

    // 各要素を開く処理の JS を表示
    echo '<script>'
        .   'window.onload = function () {'
        .      'document.querySelectorAll(\'.cake-dbg-array-items, .cake-dbg-object-props\').forEach(function (parentNode) {'
        .        'parentNode.querySelectorAll(\'.cake-dbg-collapse[data-open="false"]\').forEach(function (node) {'
        .          'node.click();'
        .        '});'
        .      '});'
        .   '};'
        . '</script>';

    return $var;
}
/config/bootstrap.php
<?php
declare(strict_types=1);

// ▼これを追加
require __DIR__ . '/basics.php';
...

3. app_local.php で切替可能に

debug() 実行時に配列やオブジェクトの内容を表示したいかどうかは、状況によって変わると思います。また複数人で開発する場合には、人によっても違いますよね。

そこで「2. debug() を自作して対応」のコードを改良して、 app_local.php で初期表示状態を変更できるようにしました。

/config/app_local.php
<?php
return [
    ...
    'debug' => filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),

    // ▼これを追加
    'DebugOptions' => [
        'openArray' => false, // true なら配列を開いた状態で表示
        'openObject' => false, // true ならオブジェクトを開いた状態で表示
    ],
    ...
];
/config/basics.php
<?php
declare(strict_types=1);

use Cake\Core\Configure;
use Cake\Error\Debugger;

function debug($var, $showHtml = null, $showFrom = true)
{
    if (!Configure::read('debug')) {
        return $var;
    }
    // 元々の debug() と同様に表示
    $location = [];
    if ($showFrom) {
        $trace = Debugger::trace([
          'depth' => 2,
          'format' => 'array',
          'start' => 1
        ])[0];
        $location = [
          'file' => $trace['file'],
          'line' => $trace['line'],
        ];
    }
    Debugger::printVar($var, $location, $showHtml);

    // HTML 表示でない場合は終了
    if ($showHtml === false) {
        return $var;
    }

    // 初期表示で開く状態にする要素
    $options = Configure::read('DebugOptions');
    $selectors = [];
    if ($options['openArray']) {
        $selectors[] = '.cake-dbg-array-items';
    }
    if ($options['openObject']) {
        $selectors[] = '.cake-dbg-object-props';
    }
    if (count($selectors) === 0) {
        return $var;
    }
    $selector = implode(',', $selectors);

    // 各要素を開く処理の JS を表示
    echo '<script>'
        .   'window.onload = function () {'
        .      "document.querySelectorAll('{$selector}').forEach(function (parentNode) {"
        .        'parentNode.querySelectorAll(\'.cake-dbg-collapse[data-open="false"]\').forEach(function (node) {'
        .          'node.click();'
        .        '});'
        .      '});'
        .   '};'
        . '</script>';

    return $var;
}
/config/bootstrap.php
<?php
declare(strict_types=1);

// ▼これを追加(追加済の場合は不要です)
require __DIR__ . '/basics.php';
...

これで app_local.php の openArray と openProperty の true / false を切り替えることで debug() の初期表示状態を変更できます。

Git などを使っている場合など、必要に応じて「config/app_local.example.php」にも追記してください。

4. おわりに

個人的には debug() の結果が色分けされるようになって見やすくなったと思いますが、好みが分かれるかもしれませんね。

シンプルな見た目が良い方はカスタム debug() を実装しても良いかもしれません。

オリジナルの debug() で実装する場合には、下記2ファイルが参考になると思います。

/vendor/cakephp/cakephp/src/basics.php (debug() 関数)
/vendor/cakephp/cakephp/src/Error/Debugger.php(printVar() 関数など)