Skip to content

__debugInfo — 自定义调试输出

概述

__debugInfo() 是 PHP 5.6 引入的魔术方法,当使用 var_dump() 输出对象时自动调用。它允许开发者控制对象在调试时的展示内容,隐藏敏感信息、简化复杂输出、只暴露关键的调试属性。

通过实现 __debugInfo(),你可以完全自定义 var_dump() 显示的属性列表和格式,而无需修改类本身的内部结构。

基础概念

调用时机

  • var_dump() 输出对象时自动调用
  • 不会影响 print_r() 或其他输出方式
  • 返回值必须是一个数组,键名为属性名,键值为显示值

方法签名

php
<?php

declare(strict_types=1);

public function __debugInfo(): array

语法与代码

基本用法

php
<?php

declare(strict_types=1);

class User
{
    private string $name;
    private string $email;
    private string $passwordHash;

    public function __construct(
        string $name,
        string $email,
        string $passwordHash
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->passwordHash = $passwordHash;
    }

    public function __debugInfo(): array
    {
        return [
            'name'  => $this->name,
            'email' => $this->email,
            // 故意不暴露 passwordHash
        ];
    }
}

$user = new User('Alice', 'alice@example.com', '$2y$10$hashed...');
var_dump($user);

输出结果:

object(User)#1 (2) {
  ["name"]=>
  string(5) "Alice"
  ["email"]=>
  string(17) "alice@example.com"
}

与不实现 __debugInfo 的对比

php
<?php

declare(strict_types=1);

class Config
{
    private string $apiKey = 'sk_live_abc123';
    private string $dbHost = 'localhost';
    private int $dbPort = 3306;

    // 不实现 __debugInfo()
}

$config = new Config();
var_dump($config);

输出结果(显示所有属性,包括敏感信息):

object(Config)#1 (3) {
  ["apiKey":"Config":private]=>
  string(14) "sk_live_abc123"
  ["dbHost":"Config":private]=>
  string(9) "localhost"
  ["dbPort":"Config":private]=>
  int(3306)
}

返回空数组隐藏所有属性

php
<?php

declare(strict_types=1);

class SecretService
{
    private string $token;
    private string $internalState;

    public function __construct(string $token)
    {
        $this->token = $token;
        $this->internalState = 'active';
    }

    public function __debugInfo(): array
    {
        return [];
    }
}

$service = new SecretService('super-secret');
var_dump($service);

输出结果:

object(SecretService)#1 (0) {
}

计算属性与格式化输出

php
<?php

declare(strict_types=1);

class Product
{
    private string $name;
    private int $priceCents;
    private float $taxRate;

    public function __construct(string $name, int $priceCents, float $taxRate)
    {
        $this->name = $name;
        $this->priceCents = $priceCents;
        $this->taxRate = $taxRate;
    }

    public function __debugInfo(): array
    {
        return [
            'name'     => $this->name,
            'price'    => sprintf('$%.2f', $this->priceCents / 100),
            'taxRate'  => sprintf('%.1f%%', $this->taxRate * 100),
            'priceWithTax' => sprintf(
                '$%.2f',
                $this->priceCents / 100 * (1 + $this->taxRate)
            ),
        ];
    }
}

$product = new Product('Laptop', 99999, 0.08);
var_dump($product);

输出结果:

object(Product)#1 (4) {
  ["name"]=>
  string(6) "Laptop"
  ["price"]=>
  string(6) "$999.99"
  ["taxRate"]=>
  string(4) "8.0%"
  ["priceWithTax"]=>
  string(7) "$1079.99"
}

与 print_r 的配合

php
<?php

declare(strict_types=1);

class Order
{
    private int $id;
    private string $status;
    private array $items;

    public function __construct(int $id, string $status, array $items)
    {
        $this->id = $id;
        $this->status = $status;
        $this->items = $items;
    }

    public function __debugInfo(): array
    {
        return [
            'id'     => $this->id,
            'status' => $this->status,
            'items'  => count($this->items) . ' items',
        ];
    }
}

$order = new Order(1, 'pending', ['Apple', 'Banana', 'Cherry']);

echo "var_dump output:\n";
var_dump($order);

echo "\nprint_r output:\n";
print_r($order);

输出结果(注意 print_r 不受 __debugInfo 影响):

var_dump output:
object(Order)#1 (3) {
  ["id"]=>
  int(1)
  ["status"]=>
  string(7) "pending"
  ["items"]=>
  string(9) "3 items"
}

print_r output:
Order Object
(
    [id:Order:private] => 1
    [status:Order:private] => pending
    [items:Order:private] => Array
        (
            [0] => Apple
            [1] => Banana
            [2] => Cherry
        )
)

详细说明

__debugInfo 与其他魔术方法的关系

方法触发场景返回值
__debugInfo()var_dump() 调用array
__toString()echo / 字符串上下文string
__set_state()var_export() 输出对象实例
__debugInfo 仅作用于 var_dump(),不影响其他调试手段

触发流程

  1. var_dump($object) 被调用
  2. PHP 检查对象是否定义了 __debugInfo()
  3. 如果存在,调用该方法获取自定义属性数组
  4. 如果不存在,使用对象的全部可见和不可见属性
  5. 按照数组内容格式化输出

返回值要求

  • 必须返回数组:如果返回非数组类型,PHP 会抛出 TypeError
  • 键名:字符串或整数,建议使用字符串键名表示属性名
  • 键值:任意类型,var_dump 会按各自的类型规则输出

实战示例

场景一:数据库连接对象调试

php
<?php

declare(strict_types=1);

class DatabaseConnection
{
    private string $host;
    private int $port;
    private string $username;
    private string $password;
    private string $database;
    private bool $connected = false;
    private int $queryCount = 0;

    public function __construct(
        string $host,
        int $port,
        string $username,
        string $password,
        string $database
    ) {
        $this->host = $host;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
        $this->database = $database;
    }

    public function connect(): void
    {
        $this->connected = true;
    }

    public function query(string $sql): void
    {
        $this->queryCount++;
    }

    public function __debugInfo(): array
    {
        return [
            'dsn'         => "{$this->host}:{$this->port}/{$this->database}",
            'user'        => $this->username,
            'connected'   => $this->connected,
            'queryCount'  => $this->queryCount,
            // password 不暴露
        ];
    }
}

$db = new DatabaseConnection('db.example.com', 3306, 'admin', 'secret', 'myapp');
$db->connect();
$db->query('SELECT * FROM users');
var_dump($db);

场景二:集合类的精简调试

php
<?php

declare(strict_types=1);

class UserCollection
{
    /** @var User[] */
    private array $users = [];

    public function add(User $user): void
    {
        $this->users[] = $user;
    }

    public function __debugInfo(): array
    {
        return [
            'count'    => count($this->users),
            'users'    => array_map(
                fn(User $u) => $u->name,
                $this->users
            ),
        ];
    }
}

class User
{
    public function __construct(
        public readonly string $name,
        public readonly int $age
    ) {}
}

$collection = new UserCollection();
$collection->add(new User('Alice', 30));
$collection->add(new User('Bob', 25));
var_dump($collection);

场景三:结合日志记录的调试输出

php
<?php

declare(strict_types=1);

class Request
{
    private string $method;
    private string $uri;
    private array $headers;
    /** @var array<string, mixed> */
    private array $body;
    private array $serverParams;

    public function __construct(
        string $method,
        string $uri,
        array $headers,
        array $body,
        array $serverParams
    ) {
        $this->method = $method;
        $this->uri = $uri;
        $this->headers = $headers;
        $this->body = $body;
        $this->serverParams = $serverParams;
    }

    public function __debugInfo(): array
    {
        return [
            'method'  => $this->method,
            'uri'     => $this->uri,
            'headers' => $this->headers,
            'body'    => [
                'keys'  => array_keys($this->body),
                'count' => count($this->body),
            ],
            // serverParams 可能包含大量信息,隐藏
        ];
    }
}

注意事项

注意事项

  • __debugInfo() var_dump() 时触发,不会影响 print_r()var_export() 等其他调试函数
  • 返回值必须是数组类型,否则会触发 TypeError
  • 该方法不应有副作用(side effects),因为它是调试用途
  • PHP 内部类和未定义 __debugInfo() 的类仍然使用默认行为

小贴士

  • __debugInfo() 中进行复杂计算可能影响调试性能,保持简单
  • 对于敏感数据(密码、密钥、Token),务必在 __debugInfo() 中排除
  • __debugInfo() 在日志截获 var_dump 输出时同样生效

最佳实践

1. 始终排除敏感属性

php
<?php

declare(strict_types=1);

class ApiClient
{
    private string $apiKey;
    private string $endpoint;

    public function __debugInfo(): array
    {
        return [
            'endpoint' => $this->endpoint,
            'apiKey'   => '***REDACTED***',
        ];
    }
}

2. 简化大型对象

对于包含大量数据的集合类,只展示摘要信息而非全部数据。

3. 添加类型转换后的友好值

将原始数据(如分、毫秒)转换为人类可读格式。

4. 与 __toString 分离职责

php
<?php

declare(strict_types=1);

class Money
{
    private int $amount;
    private string $currency;

    public function __debugInfo(): array
    {
        return [
            'amount'   => $this->amount,
            'currency' => $this->currency,
            'display'  => $this->format(),
        ];
    }

    public function __toString(): string
    {
        return $this->format();
    }

    private function format(): string
    {
        return sprintf('%s %.2f', $this->currency, $this->amount / 100);
    }
}

参考链接