__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(),不影响其他调试手段 |
触发流程
var_dump($object)被调用- PHP 检查对象是否定义了
__debugInfo() - 如果存在,调用该方法获取自定义属性数组
- 如果不存在,使用对象的全部可见和不可见属性
- 按照数组内容格式化输出
返回值要求
- 必须返回数组:如果返回非数组类型,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);
}
}