Xdebug

デバッグPHPプロファイリングステップデバッグIDEスタックトレースコードカバレッジ

デバッグツール

Xdebug

概要

Xdebugは、PHPのデバッグ・プロファイリング拡張です。ステップデバッグ、スタックトレース、コードカバレッジ分析を提供し、PHP開発の必須ツールとして広く使用されています。

詳細

Xdebugは、PHP開発において最も重要なデバッグ・プロファイリング拡張として、2002年にDerick Reethansによって開発が開始されました。現在、PHP開発者にとってデファクトスタンダードのデバッグツールとなっており、全世界で数百万の開発者に利用されています。主要な機能として、ステップデバッグ、関数トレース、プロファイリング、コードカバレッジ分析、エラーレポートの改善を提供します。

Xdebug 3(2020年リリース)では、モードベースの設定システムが導入され、パフォーマンスとセキュリティが大幅に改善されました。従来のバージョンでは各機能に個別の有効化設定がありましたが、Xdebug 3ではxdebug.mode設定により、debug(ステップデバッグ)、develop(開発支援)、coverage(カバレッジ分析)、profile(プロファイリング)、trace(関数トレース)など、必要な機能のみを選択的に有効化できます。

IDE統合においても大きな進歩を遂げており、PhpStorm、Visual Studio Code、Eclipse、Sublime Textなど主要なIDEと連携し、ブレークポイント設定、変数監視、ステップ実行、コールスタック分析などを直感的なGUIで実行できます。DBGpプロトコルによる標準化されたデバッグ通信により、様々な開発環境で一貫したデバッグ体験を提供します。

Docker環境での使用も簡素化され、Xdebug 3以前では困難だったコンテナ内デバッグも設定が容易になりました。リモートデバッグ機能により、本番環境に近い環境でのデバッグも可能です。2024年現在も活発に開発が継続されており、PHP 8.x系の最新機能にも対応しています。

メリット・デメリット

メリット

  • デファクトスタンダード: PHP開発における標準デバッグツール
  • 豊富な機能: ステップデバッグ、プロファイリング、カバレッジ分析
  • IDE統合: 主要IDEとのシームレスな連携
  • Docker対応: コンテナ環境での簡単な設定
  • リモートデバッグ: ネットワーク経由でのデバッグ可能
  • 詳細な情報: 改善されたエラーメッセージとスタックトレース
  • オープンソース: 無料で利用可能
  • 活発な開発: 継続的な機能改善とPHP新版対応

デメリット

  • パフォーマンス影響: デバッグ有効時の実行速度低下
  • 設定の複雑さ: 初期設定やIDE連携設定が煩雑
  • メモリ消費: 大規模アプリケーションでのメモリ使用量増加
  • 本番環境リスク: 誤って本番で有効化した場合のセキュリティリスク
  • 依存関係: PHPエクステンションとしてのインストール要件
  • バージョン互換性: PHP/IDE/Xdebugバージョン間の組み合わせ問題
  • 学習コスト: 効果的な使用方法の習得に時間が必要

主要リンク

書き方の例

Xdebugのインストール

# PECLを使用したインストール
pecl install xdebug

# Linuxパッケージマネージャーを使用
sudo apt-get install php-xdebug  # Ubuntu/Debian
sudo yum install php-xdebug      # CentOS/RHEL

# Composerでの開発依存関係として
composer require --dev xdebug/xdebug

# インストール確認
php -m | grep xdebug
php --version

php.iniでの基本設定

; Xdebug 3 基本設定
zend_extension=xdebug

; モード設定(複数指定可能)
xdebug.mode=debug,develop,coverage,profile

; ステップデバッグ設定
xdebug.start_with_request=yes
xdebug.client_host=localhost
xdebug.client_port=9003

; IDE Key (オプション)
xdebug.idekey=PHPSTORM

; ログ設定
xdebug.log=/tmp/xdebug.log
xdebug.log_level=7

; 出力ディレクトリ
xdebug.output_dir=/tmp/xdebug

VS Codeでのデバッグ設定

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "/var/www/html": "${workspaceFolder}"
            }
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 0,
            "runtimeArgs": [
                "-dxdebug.start_with_request=yes"
            ],
            "env": {
                "XDEBUG_MODE": "debug,develop",
                "XDEBUG_CONFIG": "client_port=${port}"
            }
        }
    ]
}

PhpStormでのデバッグ設定

<?php
// PhpStormでの設定
// 1. Settings → Languages & Frameworks → PHP → Debug
// 2. Xdebugポート: 9003
// 3. Settings → Languages & Frameworks → PHP → Servers
// 4. サーバー設定: Host=localhost, Port=80, Debugger=Xdebug

// ブラウザでデバッグセッション開始
// URL後に ?XDEBUG_SESSION_START=PHPSTORM を追加
// または Xdebug helper 拡張機能を使用
?>

ステップデバッグの実践例

<?php
// デバッグ対象のPHPコード
class UserService 
{
    private $database;
    
    public function __construct($database) 
    {
        $this->database = $database;
    }
    
    public function getUserById($id) 
    {
        // ブレークポイントを設定する行
        $sql = "SELECT * FROM users WHERE id = ?";
        $stmt = $this->database->prepare($sql);
        
        // 変数の値を確認
        $stmt->bind_param("i", $id);
        $stmt->execute();
        
        $result = $stmt->get_result();
        $user = $result->fetch_assoc();
        
        // 条件付きデバッグ
        if ($user === null) {
            // ここでブレークポイント設定
            throw new Exception("User not found: " . $id);
        }
        
        return $user;
    }
    
    public function createUser($userData) 
    {
        // ステップイン/ステップオーバーでの追跡
        $this->validateUserData($userData);
        
        $sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
        $stmt = $this->database->prepare($sql);
        
        // 配列データの確認
        $stmt->bind_param("ssi", 
            $userData['name'], 
            $userData['email'], 
            $userData['age']
        );
        
        return $stmt->execute();
    }
    
    private function validateUserData($userData) 
    {
        // 関数内でのローカル変数確認
        $required = ['name', 'email', 'age'];
        
        foreach ($required as $field) {
            if (!isset($userData[$field])) {
                // エラー時のデバッグ情報
                throw new InvalidArgumentException("Missing field: " . $field);
            }
        }
    }
}

// デバッグセッションの開始
$userService = new UserService($database);

// ここからステップ実行開始
try {
    $user = $userService->getUserById(123);
    
    // 変数の監視
    $newUser = [
        'name' => 'John Doe',
        'email' => '[email protected]',
        'age' => 30
    ];
    
    $userService->createUser($newUser);
} catch (Exception $e) {
    // 例外発生時のデバッグ
    error_log($e->getMessage());
}
?>

プロファイリング設定と分析

; プロファイリング専用設定
xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.trigger_value=profile
xdebug.output_dir=/tmp/xdebug/profiles

; フィルタ設定(特定のリクエストのみ)
xdebug.profiler_enable_trigger=1
<?php
// プロファイリング対象のコード
function heavyFunction() 
{
    // 重い処理のシミュレーション
    for ($i = 0; $i < 1000000; $i++) {
        $result = sqrt($i) * 2.5;
    }
    return $result;
}

function databaseQuery() 
{
    // データベース処理のシミュレーション
    sleep(0.1); // 100ms のシミュレーション
    return "query result";
}

// URL: http://localhost/script.php?XDEBUG_PROFILE=profile
// でプロファイリング実行

$start = microtime(true);

$data = heavyFunction();
$dbResult = databaseQuery();

$end = microtime(true);
echo "Execution time: " . ($end - $start) . " seconds";

// KCacheGrind で /tmp/xdebug/profiles/*.out ファイルを解析
?>

関数トレース設定

; 関数トレース設定
xdebug.mode=trace
xdebug.start_with_request=trigger
xdebug.trace_format=0
xdebug.trace_options=0
xdebug.collect_params=4
xdebug.collect_return=1
xdebug.show_mem_delta=1
<?php
// トレース対象の関数
class Calculator 
{
    public function add($a, $b) 
    {
        return $this->performOperation($a, $b, '+');
    }
    
    public function multiply($a, $b) 
    {
        return $this->performOperation($a, $b, '*');
    }
    
    private function performOperation($a, $b, $operation) 
    {
        switch ($operation) {
            case '+':
                return $a + $b;
            case '*':
                return $a * $b;
            default:
                throw new InvalidArgumentException("Unsupported operation");
        }
    }
}

// URL: http://localhost/calc.php?XDEBUG_TRACE=trace
$calc = new Calculator();
$result1 = $calc->add(10, 20);
$result2 = $calc->multiply(5, 6);

// トレースファイルで詳細な実行履歴を確認
?>

Docker環境でのXdebug設定

# Dockerfile でのXdebug設定
FROM php:8.2-apache

RUN pecl install xdebug \
    && docker-php-ext-enable xdebug

COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

EXPOSE 80
# docker用 xdebug.ini
zend_extension=xdebug

xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.idekey=PHPSTORM

; ログ設定
xdebug.log=/tmp/xdebug.log
# docker-compose.yml
version: '3.8'
services:
  php-app:
    build: .
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html
    environment:
      - XDEBUG_MODE=debug
      - XDEBUG_CONFIG=client_host=host.docker.internal

コードカバレッジ測定

<?php
// コードカバレッジ測定の開始
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);

// テスト対象のコード
function processUser($user) {
    if (!isset($user['name'])) {
        return false; // この行がカバーされるか?
    }
    
    if ($user['age'] < 18) {
        return 'minor'; // この行がカバーされるか?
    }
    
    return 'adult';
}

// テストケース実行
$testCases = [
    ['name' => 'John', 'age' => 25],
    ['name' => 'Jane', 'age' => 16],
    // ['age' => 30] // nameが欠如したケースはテストしない
];

foreach ($testCases as $user) {
    $result = processUser($user);
    echo "Result: $result\n";
}

// カバレッジデータ取得
$coverage = xdebug_get_code_coverage();
xdebug_stop_code_coverage();

// カバレッジレポート生成
foreach ($coverage as $file => $lines) {
    echo "File: $file\n";
    foreach ($lines as $line => $count) {
        if ($count === -1) {
            echo "  Line $line: Not executable\n";
        } elseif ($count === -2) {
            echo "  Line $line: Dead code\n";
        } elseif ($count === 0) {
            echo "  Line $line: Not covered\n";
        } else {
            echo "  Line $line: Covered ($count times)\n";
        }
    }
}
?>

CLI環境でのデバッグ

# コマンドラインでのXdebugデバッグ
export XDEBUG_MODE=debug
export XDEBUG_SESSION=1

# PHPスクリプト実行(IDEでリスニング開始後)
php -dxdebug.start_with_request=yes script.php

# 特定の設定でプロファイリング実行
php -dxdebug.mode=profile -dxdebug.start_with_request=yes script.php