Lepszy var_dump czyli przyjemniejsze debugowanie PHP

bigWeb/Debug/Dumper to narzędzie funkcjonalnie odpowiadające funkcji var_dump
Jego przewagą jest sposób prezentacji danych, a także dodatkowe informacje
ułatwiające debugowanie aplikacji.

Najpoważniejszą wadą Dumpera jest to, że jest on dość obciążający dla aplikacji
gdyż uzyskanie informacji o pliku i linii, w której dump został wywołany
wymaga każdorazowo rzucenia wyjątku. Dlatego też w wersji produkcyjnej
Dumper powinien być wyłączony

UWAGA! Biblioteka zaprezentowana w przykładach wymaga min PHP 5.3
z uwagi na użycie przestrzeni nazw.
Ponieważ jednak nie na wszystkich serwerach jest już PHP w wersji obsługującej
przestrzenie nazw przygotowałem także wersję Dumpera nie wymagającą ich użycia.
w takim przypadku wywołanie bigWeb\Debug\Dumper::factory(); należy zastąpić
poprzez wywołanie bigWeb_Debug_Dumper::factory(); i analogicznie w przypadku
innych klas. Wersja Dumpera dla PHP < 5.3 zawarta jest w archiwum zip bigWeb\Debug\Dumper do ściągnięcia.

Instalacja

include_once('./Debug.php');

Można też użyć autoloadera. Wszystkie klasy potrzebne do działania Dumpera
zdefiniowane są w pliku „Debug.php”. Wyjątkiem jest FirePHP, który należy
dodać osobno jeśli chcemy wyświetlać dane w konsoli javascript

include_once('./FirePHP.php');

Podstawowe użycie

dump('some data');

w wyniku otrzymamy:

TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 37 ET: 0 MU: 1.44 mb MPU: 1.67 mb

‚some data’
string(9) "some data"

Dumpa można wywołać z wieloma parametrami o różnych wartościach równocześnie.

$str = 'text';
$int = 7;
$arr = array('foo', 'bar');
$ob = new ArrayObject();
$bool = FALSE;
 
dump($str, $int, $arr, $ob, $bool);
TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 47 ET: 0.000904 MU: 1.47 mb MPU: 1.67 mb

$str
string(4) "text"
$int
int(7)
$arr
array(2) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
}
$ob
object(ArrayObject)#3 (1) {
  ["storage":"ArrayObject":private]=>
  array(0) {
  }
}
$bool
bool(false)

Ukrywanie komunikatów

Aby zapobiec wyświetlaniu jakichkolwiek komunikatów przez dumpera należy go wyłączyć.

bigWeb\Debug\Dumper::setEnabled(FALSE);
dump('it should not show');

Aby ponownie włączyć:

bigWeb\Debug\Dumper::setEnabled(TRUE);

Definiowanie alternatywnych logerów

Domyślnie dumper do zrzutu danych używa wbudowanej funkcji var_dump jednak
klasa Dumpera jest zbudowana w oparciu o wzorzec projektowy Obserwator, gdzie
obserwatorami są wyspecjalizowane klasy do logowania i prezentacji zrzucanych danych.
Dzięki temu możemy wybierać sposób logowania informacji.

Możemy zapisywać dane w pliku – przydatne np. przy testowaniu przekierowań

$d = bigWeb\Debug\Dumper::factory();
$o = new bigWeb\Debug\Dumper\FileDump();

Koniecznym jest wskazanie katalogu w którym będą zapisywane logi.
Katalog ten musi mieć oczywiście ustawione prawa do zapisu

$o->setDir(dirname(__FILE__));
$d->attach($o);
$d->notify('foo');

wynik zostanie zapisany w pliku /home/www/bigWeb/Debug/2012-01-12.log.php

TIME: 20:46:18 FILE: /home/zh/www/Debug/example.php LINE: 71 ET: 0.001143 MU: 1.55 mb MPU: 1.67 mb
----------| 'foo' |----------
foo
.----------------------------

Można też skorzystać z dobrodziejstw FireBuga – dodatku do Firefoxa i
wyświetlać dane w konsoli javascript. W tym przypadku musimy najpierw
załadować bibliotekę FirePHP będącej „pomostem” pomiędzy PHP i FireBugiem.

include_once('./FirePHP.php');
 
$d = bigWeb\Debug\Dumper::factory();
$d->attach(new bigWeb\Debug\Dumper\FireDump());
$d->notify('bar');

wynik pokarze nam się w konsoli FireBug-a

Wynik Dumpera w konsoli FireBug

UWAGA! Ponieważ informacje do FireBuga są przesyłane za pomocą nagłówków HTTP
biblioteka FirePHP wymaga buforowania wyjścia (output bufering). Koniecznym
jest zatem użycie funkcji ob_start() w przeciwnym razie można się spotkać
z błędem „headers already sent error”

Łańcuch wywołań

Niekiedy chcemy wiedzieć jakie funkcje i metody zostały wywołane nim został
wykonany kod w danym miejscu. Aby zobaczyć cały łańcuch wywołań należy
ustawić flagę show_trace na TRUE.

function foo() {
    $d = bigWeb\Debug\Dumper::factory();
    $d->attach(new bigWeb\Debug\Dumper\VarDump());
    $d->setShowTrace(TRUE);
    $d->notify('Show chain requests');
}
 
function bar() {
    foo();
}
 
bar();
TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 97 ET: 0.005149 MU: 1.57 mb MPU: 1.67 mb

$d->notify(‚Show chain requests’);
string(19) "Show chain requests"
Array
(
    [0] => Array
        (
            [file] => /home/www/bigWeb/Debug/example.php
            [line] => 97
            [function] => notify
            [class] => bigWeb\Debug\Dumper
            [type] => ->
            [args] => Array
                (
                    [0] => Show chain requests
                )

        )

    [1] => Array
        (
            [file] => /home/www/bigWeb/Debug/example.php
            [line] => 101
            [function] => foo
            [args] => Array
                (
                )

        )

    [2] => Array
        (
            [file] => /home/www/bigWeb/Debug/example.php
            [line] => 104
            [function] => bar
            [args] => Array
                (
                )

        )

)

Praca na serwerze produkcyjnym

W zasadzie Dumper powinien być wyłączony na serwerze produkcyjnym bo obciąża
aplikację, a ponadto może wyświetlać dane wrażliwe. Nie mniej w pewnych
wyjątkowych okolicznościach możemy chcieć go użyć. Musimy wcześniej przewidzieć
taką sytuację i dodatkowo skonfigurować przynajmniej jedną z instancji Dumpera
ustawiając tak zwany secret_key

$d = bigWeb\Debug\Dumper::factory();
$d->attach(new bigWeb\Debug\Dumper\FireDump());
$d->setSecretKey('verysicretkey');

Aby te dane były widoczne należy wywołać url metodą GET z parametrem
secret_key=1. W tym przypadku będzie to

example.php?verysicretkey=1
bigWeb\Debug\Dumper::setEnabled(FALSE);
$d->notify('Visible when set secret_key');

aby ponownie włączyć:

bigWeb\Debug\Dumper::setEnabled(TRUE);

Jeśli wywołamy url metodą GET z sekretnym kluczem, Dumper ustawia ciasteczko
debugcookie o wartości wywiedzionej z secret_key. Od tej pory nie trzeba już dodawać do
adresu żadnego specjalnego parametru. Jeśli jednak chcielibyśmy wyłączyć
debugowanie należy wywołać url z secret_key=0

example.php?verysicretkey=0

Tworzenie funkcji pomocniczych

Tworzenie obiektu i dodawanie obserwatorów nie jest zbyt wygodnym rozwiązaniem.
Narzędzie do debugowania powinno być extremalnie proste i szybkie w użyciu.
Dlatego warto sobie zdefiniować funkcję pomocniczą – podobną do „debug();”

function dump_all() {
    $_args = func_get_args();
    static $d = null;
    if ( $d === null )
    {
        // Proszę zwrócić uwagę na wywołanie metody factory z parametrem $level = 3
        $d = bigWeb\Debug\Dumper::factory(3);
        $o = new bigWeb\Debug\Dumper\FileDump();
        $o->setDir(dirname(__FILE__));
        $d->attach($o);
        $d->attach(new bigWeb\Debug\Dumper\FireDump());
        $d->attach(new bigWeb\Debug\Dumper\VarDump());
 
    }
    call_user_func_array(array($d, 'notify'), $_args);
}
 
dump_all('foo bar');

wynik zostanie zapisany w pliku

TIME: 20:46:44 FILE: /home/zh/Praca/bigWeb/Debug/example.php LINE: 155 ET: 0.006137 MU: 1.59 mb MPU: 1.67 mb
----------| 'foo bar' |----------
foo bar
.--------------------------------

w konsoli FireBug-a

Wynik Dumpera w konsoli FireBug

oraz wyświetlony w przeglądarce:

TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 155 ET: 0.006137 MU: 1.59 mb MPU: 1.67 mb

‚foo bar’
string(7) "foo bar"

UWAGA! Od wartości level zależy prawidłowe wskazanie linii oraz pliku, w którym
wywołano dumpa. Domyślnie level = 1. Jeżeli metoda „notify” jest zagnieżdżona
w funkcji pomocniczej to wartość level powinna być inkrementowana (level = 2).
Jeśli dodatkowo metoda „notify” jest wywoływana za pośrednictwem funkcji
„call_user_func_array” to należy ustawić level = 3

$d = bigWeb\Debug\Dumper::factory(1);
 
function d1($param) {
    $d = bigWeb\Debug\Dumper::factory(2);
    // ...
    $d->notify($param);
}
 
function d2($param) {
    $_args = func_get_args();
    $d = bigWeb\Debug\Dumper::factory(3);
    // ...
    call_user_func_array(array($d, 'notify'), $_args);
}

UWAGA! Jeśli masz już zdefiniowaną funkcję „dump” w swojej aplikacji
to po dołączeniu kodu Dumpera otrzymasz wszystkomówiący wyjątek. Poinformuje
Cię on, że musisz zdefiniować sobie funkcję pomocniczą o innej nazwie.
Należy zakomentować kod wywołujący wyjątek lub też dołączyć kod Dumpera
w sposób umożliwiający przechwycenie wyjątka i utworzyć funkcję pomocniczą
np. o nazwie „d”.

try { include_once("Debug.php"); } catch (bigWeb\Debug\Exception $e) {
    function d() {
        $_args = func_get_args();
        static $d = null;
        if ( $d === null )
        {
            $d = bigWeb\Debug\Dumper::factory(3);
            $d->attach(new bigWeb\Debug\Dumper\VarDump());
 
        }
        call_user_func_array(array($d, "notify"), $_args);
    }
}

Napisana przeze mnie klasa nie zastąpi zaawansowanych i rozbudowanych narzędzi debugowania i profilowania aplikacji jednak jest prosta w użyciu nie związana stricte z żadnym frameworkiem, przez co łatwo ją zaadaptować zarówno do pracy z Zend Frameworkiem, Symfony, Kohaną czy jakimkolwiek innym – napisanym w PHP – skryptem. Biblioteka zwraca wyniki w postaci pokolorowanego kodu przy okazji pokazując czas wykonania oraz wielkość użytych zasobów. Zapomniany var_dump potrafi zmusić programistę do przeszukiwania plików projektu. W przypadku Dumpera nie ma takiego zagrożenia gdyż każdorazowo wskazuje on ścieżkę do pliku oraz nr linii, w której funkcja robiąca zrzut danych została wywołana.

Serdecznie zapraszam wszystkich do wypróbowania Dumpera oraz wszelkich uwag na temat wdrożeń i ewentualnych błędów.