PHP8.3 ＋ Apache ＋ MariaDB のDocker環境を構築

# 概要
DockerでPHP8.3 ＋ Apache ＋ MariaDB の構築してみました。
windowsのWSL2・Ubuntu環境になります。
自分用のメモ的な記事になるので説明不足の点も多いですがご了承を。

# ディレクトリ構成
```
.
├── mariadb/
│   ├── init/
│   │   └── init.sql
│   └── my.cnf
├── php/
│   ├── Dockerfile
│   └── php.ini
├── src/
│   └── index.php
└── compose.yaml
```

# compose.yamlの設定を確認
コンテナをそれぞれ定義します
```compose.yaml
services:
  php: # PHP+Apacheのサービス
    build: ./php # コンテナのイメージを./phpディレクトリからビルド
    container_name: apache_php # コンテナ名
    user: "${UID_GID:-1000:1000}" # ホストマシンと同じユーザー権限でコンテナ内のプロセスを実行
    volumes:
      - ./src:/var/www/html
    ports: 
      - "80:80" # ホストのポート：コンテナのポート
  db:
    image: mariadb:11.2.5 # MariaDBイメージのバージョンを指定
    container_name: mariadb
    environment: # Compose内の環境変数を指定
      - MYSQL_ROOT_PASSWORD=rootpass
      - MYSQL_DATABASE=database
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
      - TZ=Asia/Tokyo
    ports:
      - "3306:3306"
    volumes: 
    - type: volume
      source: db-store # 定義済みのボリュームdb-storeを使用
      target: /var/lib/mysql # MariaDBのデータディレクトリにマウント (データ永続化)
    - type: bind
      source: ./mariadb/my.cnf # ホスト側で設定を記述
      target: /etc/mysql/conf.d/my.cnf # MariaDB の設定ファイルとしてマウント
    - type: bind
      source: ./mariadb/init # 初期化時にテーブルの作成やデータの挿入を行う
      target: /docker-entrypoint-initdb.d 
    depends_on:
      - php # PHPコンテナが起動した後にMariaDBコンテナを起動
  pma:
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    environment:
      - PMA_ARBITRARY=1 # 任意のホストへの接続を許可
      - PMA_HOST=db # データベースのサービス名を指定
      - PMA_USER=user # データベースユーザー名
      - PMA_PASSWORD=password # データベースユーザーのパスワード
      - TZ=Asia/Tokyo
    ports:
      - "8080:80"
    depends_on:
      - db
  mailhog:
    image: mailhog/mailhog
    container_name: mail_server
    ports:
      - "8025:8025"
      - "1025:1025"
volumes:
  db-store: # MariaDBのデータを保存するためのボリューム
```

mariadbコンテナの初期化時にテスト用のテーブル作成とレコード挿入を行っています。
MariaDBの場合、/docker-entrypoint-initdb.d/ディレクトリに配置された.sqlファイルは、コンテナ起動時に自動的に実行されます。

```init.sql
CREATE TABLE IF NOT EXISTS test (
  id INT NOT NULL AUTO_INCREMENT, 
  name VARCHAR(255) NOT NULL, 
  PRIMARY KEY (id)
);

INSERT INTO test (name) VALUES
('TOM'),
('BOB');
```

# コンテナイメージのビルド
Dockerイメージを作成するためのDockerfileをそれぞれ作成します。
今回でいうと**PHP**サービスのイメージを詳細に設定します。

```Dockerfile
FROM php:8.3-apache

# パッケージリストを更新し必要なライブラリ・ツールをインストール
# 最後にPHP拡張機能をインストール
# docker-php-ext-installは、PHP公式Dockerイメージに含まれるスクリプト
RUN apt-get update && apt-get install -y \
    libzip-dev \
    zip \
    unzip \
    git \
  && apt-get clean \
  && docker-php-ext-install zip pdo_mysql mysqli 

# PHPの設定ファイルをホストからイメージ内にコピー
COPY php.ini /usr/local/etc/php/

# Composerのインストール
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \
  && php -r "unlink('composer-setup.php');"

# ユーザーを作成する
RUN useradd -m -u 1000 -g www-data ww-data

# www-data ユーザーに変更
USER www-data

# ドキュメントルートを設定
WORKDIR /var/www/html
```
#### ライブラリ・ツールの補足
- RUN apt-get update：パッケージリストを最新の状態に更新
- apt-get install -y：必要なパッケージをインストール
- libzip-dev：zip拡張機能をコンパイルするために必要な開発ライブラリ
- zip、unzip：圧縮ファイルを作成・解凍するためのコマンドラインツール
- git：ソースコードの管理や外部リポジトリからの取得に使用
- apt-get clean：パッケージキャッシュを削除して、イメージのサイズを減らす

#### インストールするPHP拡張機能の補足
- zip：ZIP圧縮ファイルを扱うためのPHP拡張機能
- pdo_mysql：MySQLデータベースと連携するためのPDO（PHP Data Objects）ドライバ
- mysqli：MySQLデータベースと連携するための拡張機能です。mysql_*関数に対応

#### Composerのインストール
ComposerはDockerイメージを利用するなど色々方法はあると思いますが、composer公式サイトのインストール方法を参考にしました。(https://getcomposer.org/download/)
curlでComposerのインストーラーを取得し、PHPで実行します。
`--install-dir=/usr/local/bin`でComposerを/usr/local/binにインストールします。
`--filename=composer`で、実行ファイル名をcomposerに設定します。これがないとコンテナ内でcomposerコマンドが使えません。

#### MariaDBの設定
MariaDBの設定はmy.cnfに記述しています。
```my.cnf
# クライアントアプリケーション（mysqlコマンドラインなど）が使用するデフォルトの文字セットを指定
[client]
default-character-set=utf8mb4

# mysqlコマンドラインクライアントのデフォルトの文字セットを指定
[mysql]
default-character-set=utf8mb4

# サーバーのデフォルトの文字セットと照合順序を設定
# character-set-server: サーバーのデフォルトの文字セット
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
```

# コンテナを作成
`docker compose up -d`でコンテナを作成しsrc/index.phpを下記のように記述しPHPとMariaDBが正常に動作しているか確認してください。確認URL：http\://localhost/

```php:index.php
<?php
// データベース接続情報
$host = 'db';         // ホスト名
$dbname = 'database';        // データベース名
$username = 'user';          // ユーザー名
$password = 'password';              // パスワード

// データベース接続
try {
    $dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
    $pdo = new PDO($dsn, $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("データベース接続エラー: " . $e->getMessage());
}

// データ取得クエリ
try {
    $sql = "SELECT * FROM test";
    $stmt = $pdo->query($sql);

    // テーブルのデータがあるか確認
    if ($stmt->rowCount() > 0) {
        echo "<table border='1' cellpadding='5' cellspacing='0'>";
        echo "<tr><th>ID</th><th>Name</th></tr>";

        // データを一覧表示
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            echo "<tr>";
            echo "<td>" . htmlspecialchars($row['id']) . "</td>";
            echo "<td>" . htmlspecialchars($row['name']) . "</td>";
            echo "</tr>";
        }

        echo "</table>";
    } else {
        echo "テーブルにデータがありません。";
    }
} catch (PDOException $e) {
    die("データ取得エラー: " . $e->getMessage());
}

phpinfo(); // 現在のPHPの設定状態を確認する。
?>
```

# メール送信
PHPでメール送信の機能を実装する機会も多いと思います。メールテストツールで**mailhog**のコンテナを作成してるので試しに簡単なメール送信機能を作ってみました。

PHPのライブラリである**PHPMailer**を使います。とりあえずcomposerでインストールします。
```
# phpサービスのコンテナにまず入る
$ docker compose exec php /bin/bash

# PHPMailerをインストール
$ composer require phpmailer/phpmailer
```

下記のphpファイルを用意。/srcフォルダに設置してブラウザにアクセスして「メール送信成功」と表示されれば成功。http\://localhost:8025/ にアクセスしてメールの内容も確認してみてください。

```php:send.php
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);
$mail->CharSet = 'UTF-8';

try {
  // SMTP 設定
  $mail->SMTPDebug = 0;
  $mail->isSMTP();
  $mail->Host = 'mailhog'; // Docker Compose のサービス名
  $mail->SMTPAuth = false;
  $mail->Port = 1025;

  // 送信者情報
  $mail->setFrom('from@example.com', '送信元');
  $mail->addAddress('to@example.com', '送信先');

  // メール内容
  $mail->isHTML(true);
  $mail->Subject = 'テスト件名';
  $mail->Body    = '<h1>これはテストメールです</h1>';
  $mail->AltBody = 'テストメール';

  $mail->send();
  echo 'メール送信成功';
} catch(Exception $e) {
  echo "メッセージを送信できませんでした。 Mailer Error: {$mail->ErrorInfo}";
}
```

# 参考文献
> 参考サイト
[DockerでApache+PHP+MySQLの環境を構築してみる](https://qiita.com/me-654393/items/1ed212cb33eafe734835)

> 参考書籍
[Docker&仮想サーバー完全入門　Webクリエイター＆エンジニアの作業がはかどる開発環境構築ガイド](https://book.impress.co.jp/books/1121101138)

> PHPMailer
https://github.com/PHPMailer/PHPMailer