Posted at

__debugInfo の隠れ仕様的なもの


__debugInfo の隠れ仕様的なもの

php 5.6 より __debugInfovar_dump の出力を弄れる様になってますが、その挙動についてドキュメントで触れられていないことがあるので少々補完します。

※ アンドキュメントってことは正しい仕様ではなく「現在たまたまそうなっているだけ」の可能性があります

※ 確認は全て手元の php 7.1.14 で行いました


おさらい

class Debug

{
private $privateField = 1;
protected $protectedField = 2;
public $publicField = 3;
}

$debug = new Debug();
$debug->dynamicField = 4;

var_dump($debug);

このようなコードを実行すると下記のような出力が得られます。

class Debug#2 (4) {

private $privateField =>
int(1)
protected $protectedField =>
int(2)
public $publicField =>
int(3)
public $dynamicField =>
int(4)
}

このクラスにマニュアル通り__debugInfo を実装します。

class Debug

{
private $privateField = 1;
protected $protectedField = 2;
public $publicField = 3;

public function __debugInfo()
{
return [
'propSquared' => $this->privateField ** 2,
];
}
}

$debug = new Debug();
$debug->dynamicField = 4;

var_dump($debug);

出力は下記のようになります。

class Debug#2 (1) {

public $propSquared =>
int(1)
}

まぁ仕様どおりですね。


隠れた仕様1(アクセスレベル)

実際、上記のような完全カスタムの用途が多いと思いますが、私が今回やりたかったのは「でかいプロパティを伏せる」でした。

巨大フィールドを抱えていると var_dump の出力がでかすぎて視認性が悪いからです。

これはその過程で気づいた仕様です(private とかを維持したままカスタムする方法がわからなかった)。

ドキュメントに一切記載がないのですが、どうも __debugInfo の返り値はオブジェクトを配列キャストしたものであるべきのようです。

public フィールドを配列キャストしても普通のキーになるため、素の配列を返すと public と判定されます。上記で public $propSquared となっているのはそのためです。

つまり、それらしい配列を返してやれば __debugInfo で private/protected(っぽい)出力をすることができます。

    public function __debugInfo()

{
return [
"\0Debug\0dummy1" => 'this is private',
"\0*\0dummy2" => 'this is protected',
];
}

この出力は下記のようになり、あたかも dummy1 という private フィールドと dummy2という protected フィールドがあるかのような出力になります。

class Debug#2 (2) {

private $dummy1 =>
string(15) "this is private"
protected $dummy2 =>
string(17) "this is protected"
}

ここで冒頭に戻り、「でかい(特定)プロパティを伏せる」ためには配列キャストしてからそれらを伏せれば良いことが分かります。

    public function __debugInfo()

{
$properties = (array) $this;
unset($properties["\0Debug\0privateField"]);
return $properties;
}

class Debug#2 (3) {

protected $protectedField =>
int(2)
public $publicField =>
int(3)
public $dynamicField =>
int(4)
}

それらしいキーを伏せているため、 $privateField が消えています。


隠れた仕様2(再帰参照)

再帰参照を含むオブジェクトで __debugInfo を実装すると出力がやべぇことになります。

class Debug

{
public function __debugInfo()
{
return (array) $this;
}
}

$debug = new Debug();
$debug->dynamicField = $debug;

var_dump($debug);

上記の出力は下記のようになります(長すぎるので抜粋)。

class Debug#2 (1) {

public $dynamicField =>
class Debug#2 (1) {
public $dynamicField =>
class Debug#2 (1) {
public $dynamicField =>
class Debug#2 (1) {
public $dynamicField =>
class Debug#2 (1) {
...
}
}
}
}
}

上記のインデントのズレは私のミスでもなんでもなく、本当に大量にネストされて遥か右方へ追いやられた結果です(前後は省略)。

なお、 __debugInfo を実装しなければこのようなことは起きません。

多分組み込みなら再帰の検出ができるけど、 __debugInfo を自前実装した結果、(配列なので)再帰の検出ができなくなってるのかな?(だとしても最終的に ... が出るのが謎い)。

__debugInfo を実装するときは再帰に注意するか、明示的な(シンプルな)配列を返したほうが良さそうです。


隠れた仕様3(print_r)

これもドキュメントにないのですが、 __debugInfo を実装すると print_r にも影響が出ます(https://github.com/php/php-src/blob/php-7.1.14/Zend/zend.c#L186)。

class Debug

{
public function __debugInfo()
{
return ['field' => '__debugInfo の返り値です'];
}
}

print_r($debug = new Debug());

実行すると下記のような出力になり、 print_r にも影響が出ていることが分かります。

Debug Object

(
[field] => __debugInfo の返り値です
)