PHP Mentors (Posts tagged debugging)

1.5M ratings
277k ratings

See, that’s what the app is perfect for.

Sounds perfect Wahhhh, I don’t wanna

Practical Symfony #19: SymfonyのProfilerを特定のアクションで無効にするには

Symfonyには強力なProfilerがあり、開発時にはとても役に立ちますが、Profilerが開発の邪魔をするケースもあります。例えば、Doctrine経由で件数の多いレコードを取得しようとすると、Profiler用のDataCollectorへの記録に大量にメモリを消費し、応答に時間がかかったりPHPのメモリ制限値を超えて実行できなくなったりします。

回避策として、そのような特殊な処理を実行するアクションで、プロファイラを無効にしてしまいます。Profilerはdevの時に組み込まれるサービスの1つとして機能しているので、サービスコンテナから該当するオブジェクトを取得することで、Profilerを操作できます。

<?php
// プロファイラを無効にしたい
// コントローラのアクションメソッド内
if ($this->has('profiler')) {
    /* @var $profiler \Symfony\Component\HttpKernel\Profiler\Profiler */
    $profiler = $this->get('profiler');
    $profiler->disable();
}

関連

practical.symfony debugging

print_oで複雑な構成のオブジェクトグラフをビジュアライズする

image

PHPにはvar_dump()という関数があり、階層構造を持つオブジェクト(オブジェクトグラフ)をテキスト表現にできます。この情報を、グラフィカルで直感的に分かりやすい形式で出力するためのユーティリティがprint_oです。@koriymさんが開発されています。

print_oのインストール方法

print_oはPHPのライブラリとしては単独で利用可能です。Webブラウザへの描画用に外部のJavaScript/CSSを利用していますが、これらはGoogleやGitHubにホストされたファイルを読み込むようになっています。したがってインターネットに接続された環境であれば、print_o本体のインストール以外に特別な手順は不要です。

たとえばcomposerを利用する場合は、composer.jsonに以下のようにprint_o用の依存関係エントリを記述します(2012/8/10時点、Packagistにパッケージ登録されていないので、GitHubのリポジトリから直接取得できるようrepositories設定も記述しています。BEAR.Sundayのcomposer.jsonを参考にしています)。

{
    "require": {
        "print_o/print_o": "*"
    },
    "repositories": [
        {
            "type":"package",
            "package": {
                "name": "print_o/print_o",
                "version": "0.1.0",
                "target-dir": "print_o",
                "dist": {
                    "url": "https://github.com/koriym/print_o/zipball/master",
                    "type": "zip"
                },
                "source": {
                    "url": "https://github.com/koriym/print_o.git",
                    "type": "git",
                    "reference": "master"
                }
            }
        }
    ]
}

composer.jsonを作成したら、composer installを実行してパッケージを取得してください。

$ php composer.phar install

print_oの使い方

デバッグを行う側では、print_oのsrc.phpをrequire_onceで読み込んだ後、デバッグしたいオブジェクトをprint_oに渡すだけです。

<?php
require_once 'vendor/print_o/print_o/print_o/src.php';

class A
{
    public function __construct() {}
}

class B
{
    protected $a;
    protected $createdAt;
    public function __construct(A $a)
    {
        $this->a = $a;
        $this->createdAt = new \DateTime();
    }
}

$a = new \A;
$b = new \B($a);

print_o($b);

var_dumpだと以下のような表示でした。

object(B)#2 (2) {
  ["a":protected]=>
  object(A)#1 (0) {
  }
  ["createdAt":protected]=>
  object(DateTime)#3 (3) {
    ["date"]=>
    string(19) "2012-08-09 21:35:23"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(10) "Asia/Tokyo"
  }
}

print_oでグラフィカル表示すると次のようになります。各要素をクリックして子へ展開していったり、要素内の変数情報を閲覧したりできます。要素をドラッグして配置を変えることもできます。

image

Zend\DiのInstanceManagerの周辺オブジェクトでvar_dumpとprint_oの比較

object(Zend\Di\InstanceManager)#13 (6) {
  ["sharedInstances":protected]=>
  array(2) {
    ["SendmailMailer"]=>
    object(SendmailMailer)#14 (0) {
    }
    ["NewsletterTransfer"]=>
    object(NewsletterTransfer)#19 (1) {
      ["mailer":protected]=>
      object(SendmailMailer)#14 (0) {
      }
    }
  }
  ["sharedInstancesWithParams":protected]=>
  array(2) {
    ["hashShort"]=>
    array(0) {
    }
    ["hashLong"]=>
    array(0) {
    }
  }
  ["aliases":protected]=>
  array(0) {
  }
  ["configurationTemplate":protected]=>
  array(3) {
    ["parameters"]=>
    array(0) {
    }
    ["injections"]=>
    array(0) {
    }
    ["shared"]=>
    bool(true)
  }
  ["configurations":protected]=>
  array(0) {
  }
  ["typePreferences":protected]=>
  array(1) {
    ["MailerInterface"]=>
    array(1) {
      [0]=>
      string(14) "SendmailMailer"
    }
  }
}
image

まとめ

print_oを使うと、var_dumpでは把握しづらいようなオブジェクトグラフを視覚的にとらえることができるようになります。たとえば上で紹介したZend\DiのほかにSymfonyやCake等、コンポーネントやフレームワーク組み込みのオブジェクトは複雑なオブジェクトグラフを持つことが多く、それらの構造や状態をすばやくとらえるには、print_oのようなビジュアライズツールが必須となるでしょう。

また、オブジェクトグラフがビジュアライズされることで、すばやく視覚的に構造をとらえられるだけでなく、その豊かな表現からオブジェクトそのものに対する理解のブレークスルーにもつながります。

参考

php debugging print_o

Practical Symfony #9: Symfonyでerror_reporting設定を変更する

PHPメンターズの後藤です。先日、日本Symfonyユーザー会のメーリングリストに次のような質問があり、回答した内容について記事にしておきます。補足の説明とともに、このような拡張を行えるポイントも紹介します。

質問内容は「Symfony2でerror_reportingの設定を変更したいが、そのような設定項目は?」というものです。

Symfonyにはさまざまな設定を行うためのコンフィギュレーションファイルがありますが、これは文字通り「バンドルのコンフィギュレーション」、つまり「バンドルが公開している拡張ポイントに対する設定」を目的としています。ですので、PHPに関するあらゆる設定がSymfonyの設定ファイルから行えるというのではなく、むしろ逆で、Symfonyで扱っているバンドルの関心事以外のことは、コンフィギュレーションファイルでは設定できません。

今回の質問にあるerror_reportingの設定であれば、Symfonyはフレームワークとして使いやすいデフォルト値を内部で設定していますが、そのデフォルト値をコンフィギュレーションファイルから変更するといったことはできません。

Symfonyでは、追加で必要な設定は開発者が自らコードを記述して行います。たとえばapp/AppKernel.phpは開発者の手にあるスクリプトなので、ここに設定用のメソッドやコードを追加することは自由に行えます。適切なタイミングで呼び出されるメソッドをオーバーライドし、希望の処理を追加すればよいでしょう。

今回の問題であれば、error_reportingはKernelのinit()メソッド内で設定されています。この部分を何らかの形で上書きできればよさそうです。

Symfony/Component/HttpKernel/Kernel.php

<php

// snip
abstract class Kernel implements KernelInterface
{
    // snip

    public function init()
    {
        if ($this->debug) {
            ini_set('display_errors', 1);
            error_reporting(-1);

            DebugUniversalClassLoader::enable();
            ErrorHandler::register();
            if ('cli' !== php_sapi_name()) {
                ExceptionHandler::register();
            }
        } else {
            ini_set('display_errors', 0);
        }
    }

開発者が設定を差し込める部分

実際にどういったメソッドが、開発者がオーバーライドしてもよいものなのでしょうか。いくつか紹介します。

web/app.php web/app_dev.php

フロントコントローラスクリプトです。Symfony Standard Editionに付属するコードはフレームワークで処理を実行する最小限のものしか書かれていませんが、ここに独自のコードを追加することはできます。

app/AppKernel.php

アプリケーションカーネルクラスです。初期化が行われるメソッドをオーバーライドして設定を挿し込めばよいでしょう。

  • コンストラクタ
  • boot()
boot()メソッドはHttpKernel\KernelInterfaceに定義されており、ステーブルAPIです。

src/Acme/Bundle/DemoBundle/AcmeDemoBundle.php

バンドルクラスです。バンドルはすべて開発者の領域ですから当然バンドルクラスに開発者のコードを入れられます。

  • コンストラクタ
  • boot()
boot()メソッドはHttpKernel\Bundle\BundleInterfaceに定義されており、ステーブルAPIです。

Kernelのinit()メソッドはKernelのコンストラクタから呼び出されています。init()メソッド自体をAppKernel側でオーバーライドしてもよさそうですが、init()メソッドはステーブルAPIとして宣言されていません。他のメソッドでも代用可能な場合は、ステーブルAPIとして宣言されているものを使う方が望ましいでしょう。今回はKernelクラスのコンストラクタをAppKernel側でオーバーライドします。

app/AppKernel.php

<php
// snip
 
public function __construct($environment, $debug)
{
    parent::__construct($environment, $debug);

    if ($this->debug) {
        error_reporting(E_ALL | E_STRICT);
    } else {
        error_reporting(E_PARSE | E_COMPILE_ERROR | E_ERROR | E_CORE_ERROR | E_USER_ERROR);
    }
}

ごく稀にフレームワークのアップデート時にAppKernel.phpファイルやフロントコントローラファイルに修正が必要になる場合もありますが、万が一そのような場合はフレームワークのUPGRADEファイルに手順が記載されることになっています。AppKernel.phpやapp.phpは開発者の手にある領域なので、フレームワークのバージョンアップ時の修正などは、開発者の責任ということです。

まとめ

Symfonyでerror_reporting設定を変更する方法について解説しました。フレームワークは万能のツールキットではありません。特にSymfonyは、このような意図を明確に体現しています。フレームワークの責務と開発者自身が制御する領域を明確に認識することで、フレームワークの用意していない拡張が必要な場合の実装の糸口を見つけやすくなるでしょう。

参考

symfony debugging practical.symfony

Practical Symfony #5: Symfony2でリダイレクトされるアクションでもきちんとデバッグする設定 : Symfony Advent Calender 2011 JP - 17日目

PHPメンターズの後藤です。この記事はSymfony Advent Calendar JP 2011の17日目の記事です。

Webアプリケーションでは、ユーザーがフォームに入力した情報をPOSTメソッドで受け取ってアクションで処理した後、完了画面などの処理結果を表示する画面のURLへリダイレクトによって遷移させることが常套手段となっています。Symfonyは、WebデバッグツールバーやWebプロファイラを使った簡単なデバッグができるのが特徴ですが、あくまでWebブラウザの画面に描画されるものであるため、リダイレクトされる直前の、実際に処理を行なっている時の状況をWebデバッグツールバーやWebプロファイラで見ることができないように思えます。

リダイレクト・インターセプション

そこでSymfony2では、「リダイレクト・インターセプション」という機能が導入されました。この機能を有効にすると、アクションからリダイレクト指示をレスポンスとして返した場合、ユーザーのWebブラウザへ返す前にSymfonyがそれを捕捉(インターセプト)し、「●●のURLへリダイレクトされます」というような文面の通常のHTMLレスポンスに自動的に書き換えてWebブラウザへ返すようになります。もちろんこの場合には、WebデバッグツールバーとWebプロファイラも見ることができます。

リダイレクト・インターセプションを有効化するには

リダイレクト・インターセプションは、Symfony2 Standard Editionのデフォルトの設定では無効になっていますが、単に設定ファイルで「intercept_redirects」をtrueに書き換えるだけで有効化できます。

app/config/config_dev.yml

# snip
web_profiler:
    toolbar: true
    intercept_redirects: true # trueに変更
# snip

このように変更し、アクションで次のようにリダイレクトレスポンスを返したとします。

public function helloAction($name)
{
    // 何らかの処理
    // :

    // トップページヘリダイレクト
    return $this->redirect($this->generateUrl('toppage'));
}

すると、直接遷移先のページへリダイレクトするのではなく、次のようなページが表示されるようになります。

image

中央に表示されているリンクをクリックすれば、本来の遷移先へ画面が切り替わりますが、その前にプロファイル情報などを確認できます。

まとめ

Symfony2のリダイレクト・インターセプションについて概要と設定方法を解説しました。Symfoy2 Standard Editionでは、デフォルトではリダイレクト・インターセプションが無効になっていますが、開発をスタートする段階で有効化しておくと便利です。

Symfony Advent Calendar JP 2011 18日目となる明日の担当は、OpenPNESocietoなどの開発をされている@co3kさんです。

参考

symfony debugging practical.symfony