phpdbgのバックトレースが読みにくいのでコマンドを自作する

`.phpdbginit`という名前のファイルをカレントディレクトリに置いておくと、phpdbg起動時に読み取って勝手に設定してくれる。
ブレークポイントの設定 (break 関数名)などを書くのが定番だけど、関数を作ってregisterしておくこともできて、適当にbtっていうスタックトレースを表示する関数を作ってみた。

こんな風に <: 〜 :>でくくった部分はPHPとして実行され、関数の登録ができる。

```php:.phpdbginit
<:
function bt() {
    $bt = debug_backtrace();
    foreach ($bt as $i => $trace) {
        echo "$i => ";
        if (isset($trace['class'])) {
            echo "\033[36m$trace[class]\033[m$trace[type]";
        }
        echo "\033[32m$trace[function]\033[m(";
        $args = [];
        foreach ($trace['args'] as $arg) {
            switch ($type = gettype($arg)) {
                case 'resource':
                    $args[] = 'resource<' . get_resource_type($arg) . '>';
                    break;
                case 'object':
                    $args[] = get_class($arg);
                    break;
                case 'array':
                    $j = 0;
                    $itype = null;
                    $isArray = true;
                    $size = count($arg);
                    if (!$size) {
                        $args[] = '[]';
                        break;
                    }
                    foreach ($arg as $i => $val) {
                        if ($i !== $j++) {
                            $isArray = false;
                        }
                        if (is_scalar($val)) {
                            $thistype = gettype($val);
                        } elseif (is_array($val)) {
                            $thistype = 'array';
                        } elseif (is_object($val)) {
                            $thistype = get_class($val);
                        } elseif (is_resource($val)) {
                            $thistype = 'resource<' . get_resource_type($val) . '>';
                        } else {
                            $thistype = 'mixed';
                        }
                        if (!$itype) {
                            $itype = $thistype;
                        } elseif ($itype !== $thistype) {
                            $itype = 'mixed';
                            break;
                        }
                    }
                    if ($isArray) {
                        $args[] = $itype . '[' . $size . ']';
                    } else {
                        $args[] = $itype . '{' . $size . '}';
                    }
                    break;
                default:
                    $args[] = json_encode($arg, 448);
                    break;
            }
        }
        echo implode(", ", $args);
        if (isset($trace['file'])) {
            $file = basename($trace['file']);
        } else {
            $file = '';
        }
        echo "):\033[33m$file\033[m:\033[36m$trace[line]\033[m";
        echo PHP_EOL;
    }
}
:>
register bt
```

色付は趣味。

標準に`back`というコマンドがあってバックトレースを表示してくれるんだけど、引数も全部キャプチャしてvar_dumpするので、複雑な引数を渡されると、訳がわからないことになる。

```
// 凄い長さで続く
9,[] => 32,[] => Symfony\Component\Console\Formatter\OutputFormatter Object ([] => 1,[] => Array ([error] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 37,[unset] => 39),[] => Array ([set] => 41,[unset] => 49),[] => Array ()),[info] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 32,[unset] => 39),[] => ,[] => Array ()),[comment] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 33,[unset] => 39),[] => ,[] => Array ()),[question] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 30,[unset] => 39),[] => Array ([set] => 46,[unset] => 49),[] => Array ()),[highlight] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 31,[unset] => 39),[] => ,[] => Array ()),[warning] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 30,[unset] => 39),[] => Array ([set] => 43,[unset] => 49),[] => Array ())),[] => Symfony\Component\Console\Formatter\OutputFormatterStyleStack Object ([] => Array (),[] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => ,[] => ,[] => Array ())))),[] => Resource id #78,[] => 32,[] => Symfony\Component\Console\Formatter\OutputFormatter Object ([] => 1,[] => Array ([error] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 37,[unset] => 39),[] => Array ([set] => 41,[unset] => 49),[] => Array ()),[info] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 32,[unset] => 39),[] => ,[] => Array ()),[comment] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 33,[unset] => 39),[] => ,[] => Array ()),[question] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 30,[unset] => 39),[] => Array ([set] => 46,[unset] => 49),[] => Array ()),[highlight] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 31,[unset] => 39),[] => ,[] => Array ()),[warning] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => Array ([set] => 30,[unset] => 39),[] => Array ([set] => 43,[unset] => 49),[] => Array ())),[] => Symfony\Component\Console\Formatter\OutputFormatterStyleStack Object ([] => Array (),[] => Symfony\Component\Console\Formatter\OutputFormatterStyle Object ([] => ,[] => ,[] => Array ()))))) at /Users/hiraku/src/github.com/hirak/composer/vendor/symfony/console/Application.php:123
frame #13: Composer\Console\Application->run() at /Users/hiraku/src/github.com/hirak/composer/src/Composer/Console/Application.php:99
frame #14: {main} at /Users/hiraku/src/github.com/hirak/composer/bin/composer:44
```

上記の自作btであれば、引数はオブジェクトならクラス名だけ表示する感じで省略するので、大分読みやすくなる。

```
phpdbg> bt
0 => bt():RemoteFilesystem.php:99
1 => Composer\Util\RemoteFilesystem->getContents("packagist.jp", "https://packagist.jp/packages.json", false):ComposerRepository.php:645
2 => Composer\Repository\ComposerRepository->fetchFile("https://packagist.jp/packages.json", "packages.json"):ComposerRepository.php:462
3 => Composer\Repository\ComposerRepository->loadRootServerFile():ComposerRepository.php:256
4 => Composer\Repository\ComposerRepository->hasProviders():Pool.php:99
5 => Composer\DependencyResolver\Pool->addRepository(Composer\Repository\ComposerRepository, array):Installer.php:359
6 => Composer\Installer->doInstall(Composer\Repository\InstalledFilesystemRepository, Composer\Repository\CompositeRepository, Composer\Repository\PlatformRepository, array):Installer.php:215
7 => Composer\Installer->run():UpdateCommand.php:172
8 => Composer\Command\UpdateCommand->execute(Symfony\Component\Console\Input\ArgvInput, Symfony\Component\Console\Output\ConsoleOutput):Command.php:259
9 => Symfony\Component\Console\Command\Command->run(Symfony\Component\Console\Input\ArgvInput, Symfony\Component\Console\Output\ConsoleOutput):Application.php:844
10 => Symfony\Component\Console\Application->doRunCommand(Composer\Command\UpdateCommand, Symfony\Component\Console\Input\ArgvInput, Symfony\Component\Console\Output\ConsoleOutput):Application.php:192
11 => Symfony\Component\Console\Application->doRun(Symfony\Component\Console\Input\ArgvInput, Symfony\Component\Console\Output\ConsoleOutput):Application.php:209
12 => Composer\Console\Application->doRun(Symfony\Component\Console\Input\ArgvInput, Symfony\Component\Console\Output\ConsoleOutput):Application.php:123
13 => Symfony\Component\Console\Application->run(null, Symfony\Component\Console\Output\ConsoleOutput):Application.php:99
14 => Composer\Console\Application->run():composer:44
```

とりあえず満足した。