はじめに

Laravelでの開発をする際のデバッグ方法について、参考書やググった結果で多いのは
Logコマンドを使用して、変数の内容を出力したり、どの処理が行われたかを確認する。
ddコマンドや、dumpコマンドを使用して、処理の途中の変数の内容を確認していく。
でした。
Java開発やC#での開発ではステップ実行が基本なのに、Laravelでは無いのかと思い探した結果、Xdebugという拡張機能を見つけました。
なので、今回はステップ実行を行う為のXdebugの導入方法の説明をしていきます。

また、開発はWindows、本番がLinuxといった状況や、開発環境と本番環境が共にLinuxだった場合でもインストールされているものなどの差で、本番リリース時にうまく動かないといったことが発生する懸念がありました。
そういったことが発生しない為にはどうしたらと考えた結果、本番環境と同じDocker環境を立ち上げて、その中で開発を行えばいいのではと思いました。
その為、今回の説明で作成する環境としては、
本番環境でもそのまま使用できるDocker環境の作成
Docker内のコードを直接変更し、ステップ実行でのデバッグを行う
を実現します。

環境説明

Linux内にDockerを立ち上げ、そのDocker内のコードを直接変更してLaravel開発を行う環境を構築していきます。
今回の説明について、Linux環境については、人それぞれでUbuntu、AlmaLinuxの人もいれば、Windows環境下でWSL2の人もいると思うでその辺は省略します。

今回の環境作成の前提として、各々のLinux環境でDockerが使用出来ることとします。
また、Laravelの開発エディタはVSCodeで行っていきます。Dockerを参照する際もVSCodeのライブラリを使用します。

環境構成

フォルダ構成

プロジェクトフォルダ
⇒docker
 ・docker-compose.yml
 ⇒app
  ・Dockerfile
  ・docker-php-ext-xdebug.ini
 ⇒nginx
  ・Dockerfile
  ・default.conf

dockerフォルダにappとnginxという2つのフォルダがあり、それぞれにDockerfileを用意しています。
今回の環境では、Laravelが動くapp環境とサーバー用のnginx環境を別々に用意します。

app環境のDocker

Dockerfile

# DockerイメージはDockerhubのPHP公式の8.1-fpmを使用。バージョンについてはお好みで問題ありません。
FROM php:8.1-fpm
WORKDIR /tmp

# 環境の最新化
RUN apt-get update && apt-get install -y

# 各種必要なライブラリのインストール
RUN apt -y install nginx wget git iputils-ping net-tools vim nodejs npm

# composerのインストール
RUN wget https://getcomposer.org/installer -O composer-installer.php
RUN php composer-installer.php --filename=composer --install-dir=/usr/local/bin
RUN composer self-update

# xdebugのインストール
RUN pecl install xdebug

# タイムゾーンの設定
RUN apt-get install -y tzdata && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

WORKDIR /opt/html

docker-php-ext-xdebug.ini

zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=localhost
xdebug.client_port=9003

※xdebugを適用するためのphp.iniとなります
※今回の環境では、php.iniと同フォルダに配置し、自動で読み込んでもらう動きとなります。そのため、ファイル名はお好みで問題ありません
※ポート番号の9003はxdebugの標準となる為、変更しない方が無難です

nginx環境のDocker

Dockerfile

# DockerイメージはDockerhubのnginx公式の1.20-alpineを使用。
FROM nginx:1.20-alpine

# 環境設定用に用意したdefault.confを適用します。
COPY ./default.conf /etc/nginx/conf.d/default.conf

default.conf

server {
    listen 80;
    server_name example.com;
    root /opt/html/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass laravel_app:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

※標準の設定から特に変更はありません。気にする個所としては、この後の内容で出てくるdocker-compose内のサービス名と下記の部分のサービス名を間違えずに記載する必要があります
fastcgi_pass laravel_app:9000;

docker-composeの内容

docker-compose.yml

version: '3'

services:
  laravel_app:
    build: ./docker/app
    ports:
      - 1001:1000
    volumes:
      - ./laravel:/opt/html
      - ./docker/app/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
    environment:
      TZ: Asia/Tokyo
    restart: always
    tty: true

  laravel_nginx:
    build: ./docker/nginx
    ports:
      - 8081:80
    volumes:
      - ./laravel:/opt/html
    depends_on:
      - laravel_app
    environment:
      TZ: Asia/Tokyo
    restart: always
    tty: true

docker-composeの記載例は上記の内容になります。
実行時のポート番号が8081で書いてあります。既に使用されている場合は違うポート番号に変えてください。
下記の内容は最低限となるため、必要な設定は適時追加してください。

以上が、Docker環境の説明となります。

Laravel環境構築

上記で作成したDocker環境では、composerのインストールしかしていない為、Laravel環境の構築を行っていきます。
Docker環境を立ち上げ、その中に入ります。

root@c568e3635ce4:/opt/html# cd ..

/opt/htmlフォルダにLaravel環境を作成するため、1個上の階層に移動します。

root@c568e3635ce4:/opt# composer create-project laravel/laravel html "10.*"

composerのコマンドを使用してLaravel環境を作成します。
今回はLaravel 10で作成しました。お好みのバージョンで問題ありません。

chmod -R 777 /opt/html/storage

Laravel環境のstorageフォルダのパーミッション変更も忘れずに行います。

http://localhost:8081/

Laravelの環境が完成しました。

Dockerに入って開発開始

VSCodeを立ち上げ、適当なフォルダを開きます。
Docker環境に入りステップ実行するために、下記をインストールします。

PHP Debug

Docker

Remote Development

そうすると、VSCodeで下記のようにDocker環境へのアタッチが可能となるので、赤線部分を選択してアタッチします。

Docker内のファイルを参照できています。

ステップ実行の準備

作成したままの環境では実行するものがないので、とりあえずいつものHello Worldで確認を行う為のcontrollerを作成します。

VSCodeのコンソールからサーバーへのコマンドが実行できます。

php artisan make:controller HelloController

HelloController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HelloController extends Controller
{
    public function hello () {

        $hello = 'Hello World!';

        return view('hello', compact('hello'));
    }
}

hello.blade.php

{{ $hello }}

web.php

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/hello', 'App\Http\Controllers\HelloController@hello');

http://localhost:8081/hello

ステップ実行

上記までの内容で簡単ですがcontrollerで変数に値を設定し、viewに表示するまでのコードが完成しました。
では、ステップ実行の方法です。

VSCodeの実行とデバッグをクリックし、「launch.jsonファイルを作成します。」を選択します。

続けて、「PHP」を選択します。

.vscodeフォルダ内にlaunch.jsonが作成されました。

作成されたlaunch.json内で今回必要なのは「Listen for Xdebug」の部分だけとなります。
このままでも問題ありませんが、デバッグの選択時に気になる方はほかの部分は削除しても問題ありません。

「Listen for Xdebug」を実行します。
そうすると、一時停止等のボタンも登場します。
これで準備ができました。

後は、止めたい場所にブレークポイントを設置して、実行します。

http://localhost:8081/hello
※画面が読み込み中のままで停止します

VSCode側では、ブレークポイントを設置した箇所で止まりました。

この状態から、ステップインやステップアウト等の選択が可能となります。
変数等をウォッチに追加することで、各処理時での内容の参照も可能となりますので、ログに出力したり、dd関数を使用するよりも効率的にデバッグを行うことができます。

最後に

上記の内容で、ステップ実行でのデバッグを行うことが出来ました。
本番環境として作成した環境でも、VSCodeでのアタッチが可能な場所に設置されたサーバーであれば、Docker内に入りデバッグが可能となります。
操作をしているPCから直接参照できない場合でも、アタッチを何段階か繰り返すことにより、参照が可能となります。
これにより、開発環境だけでなく、最終手段として本番環境でのデバッグも可能となります。
しかし、ステップ実行中は当然ですが、サーバーからの応答がない状態となり、ブレークポイントを置いた箇所の処理は全て止まってしまう為、
実際の本番環境でそのまま止めた場合は、タイムアウトが多発すると思います。
Docker自体は同じものを使用している為、本番環境でのデバッグが必要な場面はまず発生しないと思いますが、万が一の為に覚えておいて損はないと思います。