Laravelの学習環境を構築する

# **はじめに**
今日はLaravelの学習環境を構築したいと思います。私事で恐縮ですが、仕事でLaravelの知識が必要になる為。以前投稿した記事をご覧になった方はご存知かと思いますが、四苦八苦しながら構築したDocker環境上にLaravel学習環境を構築したいと思います。以前の話に関しては、以下の投稿を見て頂ければ分かると思います。良かったらどうぞ。

https://qiita.com/829yasubee/items/93ab15182f93b2689afa

https://qiita.com/829yasubee/items/8c8390767f1404074daa

:::note alert
2022/05/01 追記
本記事で紹介したLaravel学習環境の構築に関して、Laravelトップページの表示確認まではOKでしたが、その後、自分で作成した画面を表示しようとしても404 Page Not Foundになってしまう現象を確認しました。その後の調査で原因が分かり、一部定義を追加・変更しましたので追記します。追記箇所には日付入りで赤の補足説明を入れておきます。
:::

# **環境周りの情報**
以前までに構築した環境に今回構築する環境を加えた完成予想図は下記の通りとなります。
前述の投稿での最終形態を図に反映できてませんでしたので、現状に合わせて最新化しました。
iMacにもVSCodeをインストールして"Settings Sync"機能でWindows仮想マシン側のVSCodeと同期する構成にする事で、Windows仮想マシンを起動しない分、Macのメモリ消費量を抑えつつ、開発環境の二元管理を避けるようにしました。ですので、普段はMac側のVSCodeを利用しています。
そして、今回の投稿では、図の赤枠部分を構築します。図ではコンテナ３はコンテナが１つ構成に見えますが、実際にはコンテナは２つ構成（APPサーバーとDBサーバー）になる予定です。

![スクリーンショット1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/3b68da2c-1b2e-5bc3-708f-153876303b04.png)

# **環境構築手順**
今回構築する環境はLaravelの学習が目的の環境で、かつ近々必要になる知識といった差し迫った状況なので、なるべく悩まずに早く環境構築したいと考えております。その為、今まで得た知識を駆使しつつ、メンテナンスや環境構築の労力を抑える為に、VSCode + Dockerを用いて環境構築する点に関しては方針変更しないで進めようと思います。
事前に色々と調査・検討した結果、次のような構成で環境構築したいと思います。

- APPサーバーは、Apache + PHPのDockerイメージを基に、Composerを追加インストールしてコンテナ作成
- DBサーバーは、MySQLのDockerイメージを基にコンテナ作成
- APPサーバーのコンテナにアクセスし、Composerコマンドを使用してLaravelプロジェクトを作成
- APPサーバーのコンテナにアクセスし、LaravelからMySQLへのDB接続設定を行う

上記まで作業を終え、Laravelのトップページが表示される事、LaravelからMySQLへDB接続できる事が確認できれば、今回の目的を達成した事にしたいと思います。

### **各種定義ファイルの作成**
今回作成する各種定義ファイルに関しては、ポイントだけ解説したいと思います。以前までの投稿で触れてきた知識に関しては、特に説明しませんが、定義ファイルの内容は掲載しますので、以前の投稿を参考に読み解いて頂ければと思います。読み解く際はこちらの過去投稿が参考になります。

https://qiita.com/829yasubee/items/93ab15182f93b2689afa

https://qiita.com/829yasubee/items/8c8390767f1404074daa

定義ファイルの中身を見る前に、定義ファイルを配置したツリー構造を紹介しておきます。
以前作成したコンテナとあまり変わり映えしませんが、一応画像を貼っておきます。
![スクリーンショット2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/13a798b6-33f0-ac3a-7645-e50f3f4a3909.png)

:::note alert
2022/05/01追記
上記画像の定義ファイルを配置したツリー構造に対して、以下を追加・変更しました。
- 追加 → build/app/000-default.conf
- 変更 → build/app/Dockerfile
:::

次に、各種定義ファイルの中身を見ていきます。

**devcontainer.json**
こちらも以前作成したコンテナと変わり映えしませんので、前述した過去の投稿を参考にして下さい。

```yaml:.devcontainer/devcontainer.json
{
    "name": "laravel",
    "dockerComposeFile": "../docker-compose.yml",
    "service": "app",
    "workspaceFolder": "/var/www/app",
    "extensions": [
        "bmewburn.vscode-intelephense-client",
        "mtxr.sqltools",
        "mtxr.sqltools-driver-mysql"
    ],
    "remoteUser": "${localEnv:USER}"
}
```

:::note alert
2022/05/01追記
000-default.confを追加定義しましたので、その内容を追記します。
:::

**000-default.conf**
apacheの元々の定義を基にして追記しましたので、余計なコメントが含まれていますが、コメント部分は無くても大丈夫です。
DocumentRootの箇所は元々Dockerfile(Appサーバー用)にて、sedコマンドで記述を書き換えていましたが、今回の問題で他にも追記しないといけなくなったので、confファイルを置き換える方式に変更しました。
肝心の問題箇所は、Directoryディレクティブ部分です。この定義が無いとドキュメントルート配下にあるLaravel用の.htaccessファイルが読み込まれない状態となっており、これが本事象の原因の１つとなっていました。

```conf:build/app/000-default.conf
<VirtualHost *:80>
        # The ServerName directive sets the request scheme, hostname and port that
        # the server uses to identify itself. This is used when creating
        # redirection URLs. In the context of virtual hosts, the ServerName
        # specifies what hostname must appear in the request's Host: header to
        # match this virtual host. For the default virtual host (this file) this
        # value is not decisive as it is used as a last resort host regardless.
        # However, you must set it for any further virtual host explicitly.
        #ServerName www.example.com
 
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/app/public
        <Directory /var/www/app/public/>
                AllowOverride All
        </Directory>
 
        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
        # error, crit, alert, emerg.
        # It is also possible to configure the loglevel for particular
        # modules, e.g.
        #LogLevel info ssl:warn
 
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
 
        # For most configuration files from conf-available/, which are
        # enabled or disabled at a global level, it is possible to
        # include a line for only one particular virtual host. For example the
        # following line enables the CGI configuration for this host only
        # after it has been globally disabled with "a2disconf".
        #Include conf-available/serve-cgi-bin.conf
</VirtualHost>
```

**Dockerfile(APPサーバー用)**
注目すべき点は15〜21行目の定義です。
今回使用するイメージは、Apacheが最初からインストールされているPHPバージョン8.1の環境です。
15行目では、この環境にComposerをインストールする為、COPYコマンドでComposerの公式イメージの最新版からComposerのコマンド本体をコピーしています。Composerはこの後のLaravelプロジェクトを生成する手順で必要となります。
17行目〜19行目では、GitとPHPのMySQL拡張モジュールをインストールしています。Gitはこの後Composer経由でLaravelプロジェクトを生成する際に必要となります。また、MySQL拡張モジュールはAPPサーバーのLaravelからDBサーバーのMySQLへ接続する為に必要となります。

:::note alert
2022/05/01 追記
下記の斜線説明部分の下に変更内容を記載します。
:::

~~21行目では、Apacheのドキュメントルートを既定の設定からLaravel用の設定に書き換えています。Laravelのドキュメントルートは、Laravelの起点ディレクトリ配下のpublicディレクトリと決められています。~~
sedコマンドの実行箇所は、000-default.conf自体を直接置き換えるようにしました。000-default.confの記載内容は前述の通りです。
後、a2enmodというモジュールが有効になっていない事も原因の１つでしたので、その記述を追加しました。

```Dockerfile:build/app/Dockerfile
FROM php:8.1-apache

ARG USER_UID
ARG USER_NAME
ARG GROUP_GID
ARG GROUP_NAME

RUN groupadd -g ${GROUP_GID} ${GROUP_NAME} \
  && useradd -m -s /bin/bash -u ${USER_UID} -g ${GROUP_GID} ${USER_NAME} \
  && apt-get update \
  && apt-get install -y sudo \
  && echo "${USER_NAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USER_NAME} \
  && chmod 0440 /etc/sudoers.d/${USER_NAME}

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

RUN apt-get update \
  && apt-get install -y git \
  && docker-php-ext-install pdo_mysql

#2022/05/01 mod
#RUN sed -i 's!/var/www/html!/var/www/app/public!g' /etc/apache2/sites-available/000-default.conf
ADD 000-default.conf /etc/apache2/sites-available/
RUN a2enmod rewrite

USER ${USER_NAME}
```

**Dockerfile(DBサーバー用)**
こちらは以前作成したコンテナと変わり映えしませんので、前述した過去の投稿を参考にして下さい。
ただ、今回使用するイメージはMySQLバージョン8.0となっています。今後、学習を進める中で色々問題が出るかも知れませんが、今回は新しいバージョンを使用したいと思います。

```Dockerfile:build/db/Dockerfile
FROM mysql:8.0

ARG USER_UID
ARG USER_NAME
ARG GROUP_GID
ARG GROUP_NAME

RUN groupadd -g ${GROUP_GID} ${GROUP_NAME} \
  && useradd -m -s /bin/bash -u ${USER_UID} -g ${GROUP_GID} ${USER_NAME} \
  && apt-get update \
  && apt-get install -y sudo \
  && echo "${USER_NAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USER_NAME} \
  && chmod 0440 /etc/sudoers.d/${USER_NAME}
```

**.env**
こちらも以前作成したコンテナと変わり映えしませんので、前述した過去の投稿を参考にして下さい。尚、定義内容の設定値に関しては、ご自身の環境に合わせて読み替えて下さい。

```text:.env
APP_FORWARD_PORT={Webブラウザからのアクセス用フォワードポート}

DB_ROOT_PASSWORD={MySQLのrootユーザーパスワード}
DB_DATABASE={MySQLのLaravel学習用のデータベース名}
DB_USER={MySQLの作業用ユーザーのユーザー名}
DB_PASSWORD={MySQLの作業用ユーザーのパスワード}

USER_UID={DockerホストOSの一般ユーザーのUID}
USER_NAME={DockerホストOSの一般ユーザーのユーザー名}
GROUP_GID={DockerホストOSの一般ユーザーのGID}
GROUP_NAME={DockerホストOSの一般ユーザーのグループ名}
```

**docker-compose.yml**
こちらも以前作成したコンテナと変わり映えしませんので、前述した過去の投稿を参考にして下さい。

:::note warn
MySQLバージョン8では認証方式のデフォルトが変更になったようで、下記定義を追加しないと、DBクライアントツールからの接続で認証エラーになります。私の場合、VSCodeの"SQLTools"拡張機能を使っているのですが、認証エラーになりました。

command: --default-authentication-plugin=mysql_native_password
:::

```yaml:docker-compose.yml
version: '3'
services:
  db:
    build:
      context: ./build/db/
      dockerfile: Dockerfile
      args:
        USER_UID: ${USER_UID}
        USER_NAME: ${USER_NAME}
        GROUP_GID: ${GROUP_GID}
        GROUP_NAME: ${GROUP_NAME}
    container_name: laravel_db
    hostname: laravel_db
    networks:
      - net
    volumes:
      - ./data/mysql:/var/lib/mysql
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    environment:
      TZ: Asia/Tokyo
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
  app:
    depends_on:
      - db
    build:
      context: ./build/app/
      dockerfile: Dockerfile
      args:
        USER_UID: ${USER_UID}
        USER_NAME: ${USER_NAME}
        GROUP_GID: ${GROUP_GID}
        GROUP_NAME: ${GROUP_NAME}
    container_name: laravel_app
    hostname: laravel_app
    networks:
      - net
    volumes:
      - ./data/app:/var/www/app
    ports:
      - ${APP_FORWARD_PORT}:80
    restart: always
    environment:
      TZ: Asia/Tokyo
networks:
  net:
    name: laravel_net
```

### **コンテナの生成**
各種定義ファイルの作成が完了したので、コンテナの生成をします。
今回はコマンドベースではなく、VSCodeの[Compose Up]メニューを使用します。
VSCodeの左側タブで"エクスプローラー"タブに切り替え、docker-compose.ymlファイルを右クリックし、[Compose Up]を選択。すると、コンテナの生成が始まり、進捗状況がターミナルウィンドウに表示されます。終わるまで待ちます。
![スクリーンショット3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/fff7da9d-7c34-b6b2-c6c2-3a1fb89bcdcf.png)
ターミナルウィンドウにエラー表示なくコンテナの生成が終わったら、無事コンテナが生成されているか確認してみましょう。
VSCodeの左側タブで"Docker"タブに切り替えます。カスタムイメージとコンテナとネットワークが生成され、コンテナが起動しています。
![スクリーンショット4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/360b3037-4c6c-e6b4-ee41-45416d13d747.png)

### **Laravelプロジェクトの生成**
それでは、Laravelプロジェクトの生成をしたいと思います。
まず、VSCodeでリモートコンテナにアクセスします。
VSCodeの画面左下[><]→[Reopen in Container]を選択。コンテナ内に入れます。
![スクリーンショット5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/99026b7e-ab04-cb93-70e3-8d539c4c826f.png)
VSCodeの画面左下[><]より、Laravelコンテナに入れた事が確認できます。ただし、まだLaravelプロジェクトは生成していませんので、エクスプローラーは空の状態です。
![スクリーンショット6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/5f206e0f-5689-a7ea-5d65-322874ff7749.png)
VSCodeでターミナルウィンドウを開きます。カレントディレクトリが"/var/www/app"になっている事を確認して下さい。

:::note info
リモートコンテナへアクセス時は、カレントディレクトリが"/var/www/app"となるようにdevcontainer.jsonで定義しています。もし、カレントディレクトリが異なる場合には、"/var/www/app"ディレクトリに移動してから次の手順を実行して下さい。
:::

VSCodeのターミナルウィンドウで、次のコマンドを実行します。Composer経由でLaravelプロジェクトが生成されます。結構時間がかかりますので、のんびりと待ちましょう。

```bash:実行コマンド
$ composer create-project laravel/laravel . --prefer-dist
```

ターミナルウィンドウにエラー表示なくLaravelプロジェクトの生成が終わったら、Laravelがちゃんと動作しているか確認してみましょう。ブラウザを起動し、下記形式のURLへアクセスして下さい。

```text:アクセスURL
http://{APPサーバーのIPアドレスまたはホスト名}:{.envファイルのAPP_FORWARD_PORTの設定値}/
```

下記画像のようなLaravelのデフォルト画面が表示されればOKです。
![スクリーンショット7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206277/e4d24aa2-dc76-4c4b-0855-3360b010f3b5.png)

### **LaravelからMySQLへのDB接続設定**
Laravelの環境情報設定ファイル(.env)にMySQLへのDB接続情報を設定します。
設定値は、前述したコンテナ生成用の.envファイルを基にして下さい。

```text:/var/www/app/.env
:
(中略)
:
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE={コンテナ生成用.envファイルのDB_DATABASEの設定値}
DB_USERNAME={コンテナ生成用.envファイルのDB_USERの設定値}
DB_PASSWORD={コンテナ生成用.envファイルのDB_PASSWORDの設定値}
:
(中略)
:
```

設定できたら、下記コマンドを実行して、データベースをマイグレーションします。
下記のような実行結果が表示されれば、LaravelからMySQLへのDB接続は成功しています。

```bash:実行コマンド
$ php artisan migrate
```

```text:実行結果
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (15.07ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (12.77ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (12.90ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (17.83ms)
```

:::note warn
私はここでどハマりしましたので、他の方が同じ目に遭わないように、記録を残しておこうとと思います。マイグレーションを実行したところ、下記エラーに遭遇しました。

SQLSTATE[HY000] [1045] Access denied for user

色々な方がここでハマっているようでしたが、どの方の原因とも違い、解決にかなり時間がかかりました。結局、私の場合の原因は、Laravelの.envファイルのDB_PASSWORDの設定値に記号を含んでいたからでした。もし、パスワードに記号を含んでいる場合には、設定値をダブルクォーテーションで括る必要があります。皆様、お気を付け下さい。
:::

# **最後に**
マイグレーションでどハマりしましたが、なんとか同日に解決できました。今までの知識の積み重ねの甲斐あって、希望通りのLaravel学習環境が整いました。これからこの環境で学習しつつ仕事でも実践していく事で、Laravelをマスターしていきたいと思います。

お疲れ様でした！