【初心者向け】Laravel Sail でサクッとローカル開発環境を構築する

:::note info
2024/11/7 オフライン勉強会開催のお知らせ
弊社[ミライトデザイン](https://miraito-inc.co.jp)主催で本記事のハンズオン勉強会を開催します。

オンライン配信もありますので、お気軽にご参加ください。

[【サクッと!!】Laravel ローカル開発環境構築ハンズオン【初心者歓迎】](https://phper-oop.connpass.com/event/333013)
:::

## Laravel Sail とは

Laravelに標準で搭載されているDocker環境を手軽に構築するためのCLIです。
macOS, Linux, Windows(WSL2)で動作します。

本記事ではmacOSで行います。
Windowsを利用する方は一度[こちら](https://qiita.com/ucan-lab/items/0ef01c94e7e07a061311#windows-wsl2%E3%81%A7%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88)を参照ください。

## 公式ドキュメント

https://readouble.com/laravel/11.x/ja/sail.html
https://readouble.com/laravel/11.x/ja/installation.html#docker-installation-using-sail

## 環境

- macOS: Sonoma 14.6.1
- [OrbStack](https://orbstack.dev): 1.7.2
	- ※ [Docker Desktop](https://www.docker.com/ja-jp/products/docker-desktop) でも良い

```shell-session
$ docker -v
Docker version 27.1.2, build d01f264
```

Dockerが動作する環境であること。

## 環境構築の手順

### Laravelのインストール

下記のコマンドを実行してLaravelプロジェクトをインストールします。
インストールしたいディレクトリで実行します。
`example-app` には好きなプロジェクト名にしてください。

Laravelのバージョンを指定してインストールしたい方は[補足](https://qiita.com/ucan-lab/items/0ef01c94e7e07a061311#%E8%A3%9C%E8%B6%B3-%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%A6%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)参照ください。

```shell-session
$ curl -s "https://laravel.build/example-app" | bash

...

Please provide your password so we can make some final adjustments to your application's permissions.
Password: [パソコンのログインパスワード]
```

Laravelプロジェクトのインストール最後に権限設定でログインパスワードを求められるので入力します。

Windows(WSL2)の場合はUbuntuのログインパスワードを使用してください。
これに失敗した状態で進めるとパーミッションエラーが発生します。

ログインパスワードの入力に失敗した場合は[こちら](https://qiita.com/ucan-lab/items/0ef01c94e7e07a061311#%E8%A3%9C%E8%B6%B3-%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E3%81%AE%E5%85%A5%E5%8A%9B%E3%81%AB%E5%A4%B1%E6%95%97%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88)を参照ください。

ビルド時間は大体6分弱かかったので、コーヒーでも飲んで気長に待ちましょう☕️

### Docker イメージビルド&コンテナ作成

```shell-session
$ cd example-app

$ ./vendor/bin/sail up -d
```

`-d` を付けるとデタッチドモードで実行してくれます。
付けない場合はフォアグラウンドになり、ターミナルが占有されるので、別ターミナルでコマンドを打つ必要があります。

### マイグレーションの実行

```shell-session
$ ./vendor/bin/sail artisan migrate
```

マイグレーションが適用されたことを確認します。

```shell-session
$ ./vendor/bin/sail artisan migrate:status

  Migration name ............................................................... Batch / Status
  0001_01_01_000000_create_users_table ................................................ [1] Ran
  0001_01_01_000001_create_cache_table ................................................ [1] Ran
  0001_01_01_000002_create_jobs_table ................................................. [1] Ran
```

### コンテナの起動確認

```shell-session
$ ./vendor/bin/sail ps
NAME                      IMAGE                          COMMAND                  SERVICE        CREATED         STATUS                   PORTS
meet-hub-laravel.test-1   sail-8.3/app                   "start-container"        laravel.test   3 minutes ago   Up 3 minutes             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:5173->5173/tcp, :::5173->5173/tcp
meet-hub-mailpit-1        axllent/mailpit:latest         "/mailpit"               mailpit        3 minutes ago   Up 3 minutes (healthy)   0.0.0.0:1025->1025/tcp, :::1025->1025/tcp, 0.0.0.0:8025->8025/tcp, :::8025->8025/tcp, 1110/tcp
meet-hub-meilisearch-1    getmeili/meilisearch:latest    "tini -- /bin/sh -c …"   meilisearch    3 minutes ago   Up 3 minutes (healthy)   0.0.0.0:7700->7700/tcp, :::7700->7700/tcp
meet-hub-mysql-1          mysql/mysql-server:8.0         "/entrypoint.sh mysq…"   mysql          3 minutes ago   Up 3 minutes (healthy)   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060-33061/tcp
meet-hub-redis-1          redis:alpine                   "docker-entrypoint.s…"   redis          3 minutes ago   Up 3 minutes (healthy)   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp
meet-hub-selenium-1       selenium/standalone-chromium   "/opt/bin/entry_poin…"   selenium       3 minutes ago   Up 3 minutes             4444/tcp, 5900/tcp
```

上記のように STATUS の項目がすべて Up となっていればokです。

### 動作確認

http://localhost

Laravelのウェルカム画面が表示されればokですね。
OSの設定でライトモードだとこちらの画面。

![localhost_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/bb3d442f-4721-3d52-9694-c52925e5bdb9.png)

OSの設定でダークモードだとこちらの画面が表示されます。

![localhost_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/ca708437-b9db-9721-1b94-3d2162314270.png)

## 任意: シェルエイリアスの設定

現在使用しているシェルを確認します。

```shell-session
$ echo $SHELL
/bin/zsh
```

zshの場合は `~/.zshrc`, bashの場合は `~/.bashrc` へ次のコードを書き込みます。

```~/.zshrc
alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'
```

シェルを再起動します。

```shell-session
$ exec $SHELL -l
```

ターミナルを閉じて開き直しても良いです。

```shell-session
$ sail up
```

エイリアスが有効になっていることを確認します。
フォアグラウンドで起動しているので `control` + `c` で処理を止めます。

以降の記事は `sail` で表記します。
エイリアスを設定しない人は `./vendor/bin/sail` と読み替えてください。

## 補足: Sail コマンド一覧

sailコマンドはどんなコマンドが用意されているのか、調べたい人は `-h` オプションを実行します。

```shell-session
$ sail -h
Laravel Sail

Usage:
  sail COMMAND [options] [arguments]

Unknown commands are passed to the docker-compose binary.

docker-compose Commands:
  sail up        Start the application
  sail up -d     Start the application in the background
  sail stop      Stop the application
  sail restart   Restart the application
  sail ps        Display the status of all containers

Artisan Commands:
  sail artisan ...          Run an Artisan command
  sail artisan queue:work

PHP Commands:
  sail php ...   Run a snippet of PHP code
  sail php -v

Composer Commands:
  sail composer ...                       Run a Composer command
  sail composer require laravel/sanctum

Node Commands:
  sail node ...         Run a Node command
  sail node --version

NPM Commands:
  sail npm ...        Run a npm command
  sail npx            Run a npx command
  sail npm run prod

PNPM Commands:
  sail pnpm ...        Run a pnpm command
  sail pnpx            Run a pnpx command
  sail pnpm run prod

Yarn Commands:
  sail yarn ...        Run a Yarn command
  sail yarn run prod

Bun Commands:
  sail bun ...        Run a bun command
  sail bunx           Run a bunx command
  sail bun run prod

Database Commands:
  sail mysql     Start a MySQL CLI session within the 'mysql' container
  sail mariadb   Start a MySQL CLI session within the 'mariadb' container
  sail psql      Start a PostgreSQL CLI session within the 'pgsql' container
  sail redis     Start a Redis CLI session within the 'redis' container

Debugging:
  sail debug ...          Run an Artisan command in debug mode
  sail debug queue:work

Running Tests:
  sail test          Run the PHPUnit tests via the Artisan test command
  sail phpunit ...   Run PHPUnit
  sail pest ...      Run Pest
  sail pint ...      Run Pint
  sail dusk          Run the Dusk tests (Requires the laravel/dusk package)
  sail dusk:fails    Re-run previously failed Dusk tests (Requires the laravel/dusk package)

Container CLI:
  sail shell        Start a shell session within the application container
  sail bash         Alias for 'sail shell'
  sail root-shell   Start a root shell session within the application container
  sail root-bash    Alias for 'sail root-shell'
  sail tinker       Start a new Laravel Tinker session

Sharing:
  sail share   Share the application publicly via a temporary URL
  sail open    Open the site in your browser

Binaries:
  sail bin ...   Run Composer binary scripts from the vendor/bin directory

Customization:
  sail artisan sail:publish   Publish the Sail configuration files
  sail build --no-cache       Rebuild all of the Sail containers
```

## 補足: バージョン確認

```shell-session
$ sail php -v
PHP 8.3.11 (cli) (built: Aug 30 2024 09:27:49) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.11, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.11, Copyright (c), by Zend Technologies
    with Xdebug v3.3.2, Copyright (c) 2002-2024, by Derick Rethans

$ sail artisan -v
Laravel Framework 11.23.5

$ sail composer -V
Composer version 2.7.9 2024-09-04 14:43:28
PHP version 8.3.11 (/usr/bin/php8.3)

$ sail node -v
v20.17.0

$ sail npm -v
10.8.3

$ sail yarn -v
1.22.22

$ sail pnpm -v
9.10.0

$ docker compose exec mysql mysql -V
mysql  Ver 8.0.32 for Linux on aarch64 (MySQL Community Server - GPL)
```

## 任意: Composer (診断)

sailでインストールされているComposerのバージョンを確認します。
ComposerはPHPのパッケージ管理ツールです。

diagnose コマンドを実行すると、Composer の設定、依存関係、PHP のバージョン互換性などをチェックし、問題がないかを確認します。
例えば、以下のコマンドで診断を実行できます。

```shell-session
$ sail composer diagnose
Checking composer.json: OK
Checking platform settings: OK
Checking git settings: OK git version 2.34.1
Checking http connectivity to packagist: OK
Checking https connectivity to packagist: OK
Checking github.com rate limit: OK
Checking disk free space: OK
Checking pubkeys: FAIL
Missing pubkey for tags verification
Missing pubkey for dev verification
Run composer self-update --update-keys to set them up
Checking Composer version: OK
Checking Composer and its dependencies for vulnerabilities: OK
Composer version: 2.7.9
PHP version: 8.3.11
PHP binary path: /usr/bin/php8.3
OpenSSL version: OpenSSL 3.0.2 15 Mar 2022
curl version: 7.81.0 libz 1.2.11 ssl OpenSSL/3.0.2
zip: extension present, unzip present, 7-Zip not available
```

pubkeys のチェックで失敗しました。
この場合は次のページからComposerの公開鍵を取得して次のコマンドでコピペして設定します。

https://composer.github.io/pubkeys.html

```shell-session
$ sail composer self-update --update-keys
```

diagnoseコマンドを実行して再診断します。

```shell-session
$ sail composer diagnose
Checking composer.json: OK
Checking platform settings: OK
Checking git settings: OK git version 2.34.1
Checking http connectivity to packagist: OK
Checking https connectivity to packagist: OK
Checking github.com rate limit: OK
Checking disk free space: OK
Checking pubkeys:
Tags Public Key Fingerprint: 57815BA2 7E54DC31 7ECC7CC5 573090D0  87719BA6 8F3BB723 4E5D42D0 84A14642
Dev Public Key Fingerprint: 4AC45767 E5EC2265 2F0C1167 CBBB8A2B  0C708369 153E328C AD90147D AFE50952
OK
Checking Composer version: OK
Checking Composer and its dependencies for vulnerabilities: OK
Composer version: 2.7.9
PHP version: 8.3.11
PHP binary path: /usr/bin/php8.3
OpenSSL version: OpenSSL 3.0.2 15 Mar 2022
curl version: 7.81.0 libz 1.2.11 ssl OpenSSL/3.0.2
zip: extension present, unzip present, 7-Zip not available
```

これで問題は解決できました。
次のコマンドを実行してPHPライブラリをインストールします。

```shell-session
$ sail composer install
```

※初期インストール状態だとLaravelプロジェクトインストールコマンドで実行済みなので変更がなければ差分は発生しないはずです。

## 任意: Node (npm)

sailコマンドで node を実行できます。
ちなみに node も php と同じコンテナで実行されます。

```shell-session
$ sail npm install
```

初期インストール状態だと `package-lock.json` はコミットされていないため新しくファイルが作られます。
次のコマンドでコミットしておくと良いでしょう。

```shell-session
$ git add package-lock.json
$ git commit -m "sail npm install"
```

```shell-session
$ sail npm run dev
```

※ Sailにはデフォルトでpnpmとyarnがインストールされています。
お好みのNodeパッケージ管理ツールを使用してください。

## 補足: Composer スクリプト

`composer.json` の `scripts.dev` を次のように書き換えます。

### before

```json:composer.json
        "dev": [
            "Composer\\Config::disableProcessTimeout",
            "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
        ],
```

`php artisan serve` はWebサーバーを起動していますが、DockerコンテナがWebサーバーを兼ねているのでこの箇所を削除します。

### after

```json:composer.json
{
    "scripts": {
        // ...
        "dev": [
            "Composer\\Config::disableProcessTimeout",
            "npx concurrently -c \"#c4b5fd,#fb7185,#fdba74\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=queue,logs,vite"
        ]
    }
}
```

何をやってるかというと、キューワーカー、ログ監視、フロントエンドビルドを並列で実行しています。それぞれのコマンドを叩く必要がないのでちょっと便利に使えます。

各コマンドの補足です。

- Composer の process-timeout を無効化
- npx concurrently コマンドの並列化
- `-c "#c4b5fd,#fb7185,#fdba74"`
  - 各プロセスに適用される色
- `"php artisan queue:listen --tries=1"`
  - キューワーカーの起動
- `"php artisan pail --timeout=0"`
  - Laravelログ監視
- `"npm run dev"`
  - フロントエンドのビルドツールを開発モードで起動
- `--names=queue,logs,vite`
  - 各プロセスに名前付け

## 補足: Tinker

Tinkerは、[PsySH](https://github.com/bobthecow/psysh)パッケージのLaravel用のラッパーライブラリです。

対話形式でLaravelのコマンドを実行できます。
ちょっとした動作確認に使用できます。

https://readouble.com/laravel/11.x/ja/artisan.html

```shell-session
$ sail tinker

> App\Models\User::factory()->create();
= App\Models\User {#6190
    name: "Sigurd Quigley",
    email: "krajcik.margie@example.com",
    email_verified_at: "2024-09-19 16:43:20",
    #password: "$2y$12$kfh0FpII9Fr.u3JVvPscZ.AhEkKUBlXXsqQy/6FyRLSoRT0xeEfF.",
    #remember_token: "63UNpigQB6",
    updated_at: "2024-09-19 16:43:21",
    created_at: "2024-09-19 16:43:21",
    id: 1,
  }
```

## 補足: MySQL でSQL実行

```shell-session
$ sail mysql

> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| performance_schema |
| testing            |
+--------------------+
4 rows in set (0.00 sec)

> show tables;
+-----------------------+
| Tables_in_laravel     |
+-----------------------+
| cache                 |
| cache_locks           |
| failed_jobs           |
| job_batches           |
| jobs                  |
| migrations            |
| password_reset_tokens |
| sessions              |
| users                 |
+-----------------------+
9 rows in set (0.01 sec)

> select * from users \G;
*************************** 1. row ***************************
               id: 1
             name: Sigurd Quigley
            email: krajcik.margie@example.com
email_verified_at: 2024-09-19 16:43:20
         password: $2y$12$kfh0FpII9Fr.u3JVvPscZ.AhEkKUBlXXsqQy/6FyRLSoRT0xeEfF.
   remember_token: 63UNpigQB6
       created_at: 2024-09-19 16:43:21
       updated_at: 2024-09-19 16:43:21
1 row in set (0.01 sec)

ERROR:
No query specified
```

## 必須: Pint

Pintはコードフォーマッターです。
php-cs-fixerのラッパーライブラリです。

次のコマンドでPHPコードのフォーマットチェックを行います。

```shell-session
$ sail pint --test
```

次のコマンドでPHPコードのフォーマットチェックとルール違反コードの修正を行います。

```shell-session
$ sail pint
```

## 必須: Test

PHPUnitを実行して動作確認します。

```shell-session
$ sail test
# or
$ sail phpunit
```

## 補足: コンテナシェル

sailユーザーでログインする

```shell-session
$ sail shell
```

rootユーザーでログインする

```shell-session
$ sail root-shell
```

## tips

### 補足: ポート番号の変更

複数のプロジェクト開発を変更していると使用ポートがバッティングしてしまい、毎回コンテナを停止して、起動して...をする必要が出てきます。

そういう時はコンテナの公開ポートを変更すると良いです。

```.env
APP_PORT=8080
FORWARD_DB_PORT=3307
```

`docker-compose.yaml` で参照している値を変更した場合はコンテナの破棄と作成を行なって反映します。

```shell-session
$ sail down
$ sail up -d
$ sail ps
```

公開ポートが変更されていることを確認できればokです。

### ほぼ必須: MySQL 日本語文字化け対策

初期インストール状態だと日本語を入力すると文字化けします。

```shell-session
$ sail tinker

> App\Models\User::factory()->create(['name' => 'テスト']);
= App\Models\User {#6214
    name: "<E3><83><86><E3><82><B9><E3><83><88>",
    email: "bogisich.adrienne@example.org",
    email_verified_at: "2024-09-19 17:25:30",
    #password: "$2y$12$5GR6FfqxG16u4HlAtt3nwuhUxQthNJzoEh3AmyNfIFbArk9.xJhsy",
    #remember_token: "vF61cyYCGb",
    updated_at: "2024-09-19 17:25:30",
    created_at: "2024-09-19 17:25:30",
    id: 4,
  }
```

MySQLのデータを確認しますが、 `???` と表示されます。

```shell-session
$ sail mysql
$ select * from users order by id desc limit 1\G;
*************************** 1. row ***************************
               id: 4
             name: ???
            email: bogisich.adrienne@example.org
email_verified_at: 2024-09-19 17:25:30
         password: $2y$12$5GR6FfqxG16u4HlAtt3nwuhUxQthNJzoEh3AmyNfIFbArk9.xJhsy
   remember_token: vF61cyYCGb
       created_at: 2024-09-19 17:25:30
       updated_at: 2024-09-19 17:25:30
1 row in set (0.00 sec)

ERROR:
No query specified
```

理由は `latin1` の文字コードが使用されているためです。

```shell-session
> show variables like '%char%';
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | latin1                         |
| character_set_connection | latin1                         |
| character_set_database   | utf8mb4                        |
| character_set_filesystem | binary                         |
| character_set_results    | latin1                         |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8mb3                        |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+
8 rows in set (0.09 sec)
```

次のコマンドを実行すると sail の設定ファイルをコピーしてくれます。

```shell-session
$ sail artisan sail:publish
```

次のファイルが生成されます。
Gitでファイル差分を確認します。

```shell-session
$ git status -s
 M docker-compose.yml
?? docker/8.0/Dockerfile
?? docker/8.0/php.ini
?? docker/8.0/start-container
?? docker/8.0/supervisord.conf
?? docker/8.1/Dockerfile
?? docker/8.1/php.ini
?? docker/8.1/start-container
?? docker/8.1/supervisord.conf
?? docker/8.2/Dockerfile
?? docker/8.2/php.ini
?? docker/8.2/start-container
?? docker/8.2/supervisord.conf
?? docker/8.3/Dockerfile
?? docker/8.3/php.ini
?? docker/8.3/start-container
?? docker/8.3/supervisord.conf
?? docker/mariadb/create-testing-database.sh
?? docker/mysql/create-testing-database.sh
?? docker/pgsql/create-testing-database.sql
```

使用しないファイルは削除して良いと思います。
残したファイルは下記のファイルです。

```shell-session
$ git status -s
 M docker-compose.yml
?? docker/8.3/Dockerfile
?? docker/8.3/php.ini
?? docker/8.3/start-container
?? docker/8.3/supervisord.conf
?? docker/mysql/create-testing-database.sh
```

docker-compose.yaml も参照先が次のように変更されています。

```diff:docker-compose.yaml
services:  
    laravel.test:  
        build:
-            context: './vendor/laravel/sail/runtimes/8.3'
+            context: './docker/8.3'
    mysql:  
        volumes:
-            - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker
+            - './docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
```

次のコマンドを実行してmysqlコンテナ内のファイルをローカルへコピーします。

```shell-session
$ docker compose cp mysql:/etc/my.cnf ./docker/mysql/my.cnf
```

コピーしたファイルに次の内容に置き換えます。

```cnf:docker/mysql/my.cnf
# For advice on how to change settings please see  
# http://dev.mysql.com/doc/refman/8.0/en/server-configuration-defaults.html  
  
[mysqld]  
#  
# Remove leading # and set to the amount of RAM for the most important data  
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.  
# innodb_buffer_pool_size = 128M  
#  
# Remove leading # to turn on a very important data integrity option: logging  
# changes to the binary log between backups.  
# log_bin  
#  
# Remove leading # to set options mainly useful for reporting servers.  
# The server defaults are faster for transactions and fast SELECTs.  
# Adjust sizes as needed, experiment to find the optimal values.  
# join_buffer_size = 128M  
# sort_buffer_size = 2M  
# read_rnd_buffer_size = 2M  
  
# Remove leading # to revert to previous value for default_authentication_plugin,  
# this will increase compatibility with older clients. For background, see:  
# https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_default_authentication_plugin  
# default-authentication-plugin=mysql_native_password  
skip-host-cache  
skip-name-resolve  
datadir=/var/lib/mysql  
socket=/var/lib/mysql/mysql.sock  
secure-file-priv=/var/lib/mysql-files  
user=mysql  
  
pid-file=/var/run/mysqld/mysqld.pid

# 以下の設定を追加
character-set-server = utf8mb4  
collation-server = utf8mb4_bin  
  
[client]  
default-character-set = utf8mb4
```

```diff:docker-compose.yaml
services:
    mysql:
        volumes:
            - './docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
+			- './docker/mysql/my.cnf:/etc/my.cnf.d/my.cnf'
```

コンテナの破棄とコンテナの作成を行ないます。

```shell-session
$ sail down
$ sail up -d
$ sail ps # mysql コンテナの STATUS が Up になっていること
$ sail mysql
> show variables like '%char%';
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | utf8mb4                        |
| character_set_connection | utf8mb4                        |
| character_set_database   | utf8mb4                        |
| character_set_filesystem | binary                         |
| character_set_results    | utf8mb4                        |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8mb3                        |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+
8 rows in set (0.01 sec)
```

文字コードの設定が変更されているので、再度確認します。

```shell-session
$ sail tinker
> App\Models\User::factory()->create(['name' => 'テスト']);
= App\Models\User {#5234
    name: "<E3><83><86><E3><82><B9><E3><83><88>",
    email: "yspinka@example.net",
    email_verified_at: "2024-09-20 02:01:49",
    #password: "$2y$12$4Hv8bZ78NKJEMiPiElSh3O2I83/3XHOEtWyj2z7OGKx5Go5CJ3wIC",
    #remember_token: "73a6P5Twa8",
    updated_at: "2024-09-20 02:01:49",
    created_at: "2024-09-20 02:01:49",
    id: 11,
  }
```

tinker上は日本語を表示できていないですが、別ターミナルを開いてMySQLを確認します。

```shell-session
$ sail mysql
> select * from users order by id desc limit 1\G;
*************************** 1. row ***************************
               id: 11
             name: テスト
            email: yspinka@example.net
email_verified_at: 2024-09-20 02:01:49
         password: $2y$12$4Hv8bZ78NKJEMiPiElSh3O2I83/3XHOEtWyj2z7OGKx5Go5CJ3wIC
   remember_token: 73a6P5Twa8
       created_at: 2024-09-20 02:01:49
       updated_at: 2024-09-20 02:01:49
1 row in set (0.01 sec)

ERROR:
No query specified
```

MySQLの方は日本語が正しく表示されています。

### 任意: tinker 日本語文字化け対策

MySQLの日本語文字化け対策はやった方がいいですが、こっちの設定は優先度下げてもいいとは思います。

```shell-session
$ sail tinker

> App\Models\User::factory()->create(['name' => 'テスト']);
= App\Models\User {#6214
    name: "<E3><83><86><E3><82><B9><E3><83><88>",
    email: "bogisich.adrienne@example.org",
    email_verified_at: "2024-09-19 17:25:30",
    #password: "$2y$12$5GR6FfqxG16u4HlAtt3nwuhUxQthNJzoEh3AmyNfIFbArk9.xJhsy",
    #remember_token: "vF61cyYCGb",
    updated_at: "2024-09-19 17:25:30",
    created_at: "2024-09-19 17:25:30",
    id: 4,
  }
```

```dockerfile:docker/8.3/Dockerfile
# ...
ENV SUPERVISOR_PHP_USER="sail"
# この行を追加
ENV LANG=ja_JP.UTF-8

RUN apt-get update \
# ...
    # この行を追加
    && apt-get install -y language-pack-ja \
    && apt-get -y autoremove \
    && apt-get clean \
# ...
```

ENV 命令で `LANG=ja_JP.UTF-8` を追加します。
RUN 命令に `&& apt-get install -y language-pack-ja \` を追加します。

Dockerfileを変更した場合、Dockerイメージのビルドとコンテナを破棄して、コンテナを作成し直す必要があります。

```shell-session
$ sail build --no-cache
$ sail down
$ sail up -d
```

イメージビルドのキャッシュを参照して変更が反映されない場合もあるので、 `--no-cache` オプションを付けると良いです。

```shell-session
$ sail tinker

> App\Models\User::factory()->create(['name' => 'テスト']);
= App\Models\User {#5234
    name: "テスト",
    email: "sshanahan@example.com",
    email_verified_at: "2024-09-20 03:01:04",
    #password: "$2y$12$thEG7/a91L6ftCmcJHv.We8ySjhvMH9f9UhaMkcXF5lscJrAN5u36",
    #remember_token: "WarrXVkrV6",
    updated_at: "2024-09-20 03:01:04",
    created_at: "2024-09-20 03:01:04",
    id: 12,
  }
```

tinker でも日本語が正しく表示されるようになりました。

### 任意: メール送信

SMTPを使ったメール送信テストを行えるMailpitが入っています。
次のコードを実行するとテストメールを送れます。

```shell-session
$ sail tinker
Mail::raw('test mail',function($message){$message->to('test@example.com')->subject('test');});
```

http://localhost:8025

### 任意: Redis

```shell-session
$ sail tinker
use Illuminate\Support\Facades\Redis;
Redis::set('name', 'foo');
Redis::get('name');
```

```shell-session
$ sail redis

> keys *
1) "laravel_database_name"

> get laravel_database_name
"foo"

> set name "bar"
OK
> get name
"bar"

> keys *
1) "name"
2) "laravel_database_name"

> flushall
OK

> keys *
(empty array)
```

### 補足: MySQL クライアントツール

[Sequel Ace](https://github.com/Sequel-Ace/Sequel-Ace) を使用した場合は次の設定で接続できます。

- Name: `sail` (任意)
- Host: `localhost` (もしくは `127.0.0.1`)
  - .envのDB_HOSTはDocker Composeのサービス名が入り、コンテナ間通信で利用される
- Username: `sail` (.envのDB_USERNAME)
- Password: `password` (.envのDB_PASSWORD)
- Database: `laravel` (.envのDB_DATABASE)
- Database: `3306` (.envのDB_PORT)

![ScreenShot 2024-09-20 4.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/1bb277ec-191f-0628-abb3-72227b2df7fe.png)

![ScreenShot 2024-09-20 4.03.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/dccde6f2-7f3f-b4a1-b95d-28dcb2ded6fc.png)

### 補足: バージョンを指定してインストール

新規プロジェクトであれば基本的に最新で良いのですが、参考書を見ながら進めたい場合などはバージョンを合わせたい時もあるかと思います。

```shell-session
# Laravel 8 (PHP7.4)
$ curl -s "https://laravel.build/example-app?php=74&devcontainer" | bash
# Laravel 9 (PHP8.0)
$ curl -s "https://laravel.build/example-app?php=80&devcontainer" | bash
# Laravel 10 (PHP8.1)
$ curl -s "https://laravel.build/example-app?php=81&devcontainer" | bash
# Laravel 11 (PHP8.2)
$ curl -s "https://laravel.build/example-app?php=82&devcontainer" | bash
```

Laravelのバージョンを直接指定はできなかったのですが、LaravelはバージョンによってPHPの最低バージョンが異なることからPHPのバージョン指定することによって実質的にLaravelのバージョンを選択できます。

### 補足: localhost で接続が拒否されました。

`http://localhost` へアクセスして次のエラーが出た場合は、他プロセスが 80 ポートをすでに利用している場合があります。

![ScreenShot 2024-10-12 20.31.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/4503c6fc-9ee1-7e8b-ac08-d4b058ecb516.png)

心当たりがあればそのサービスを停止してください。
不明な場合は次のコマンドからそのポートを利用しているプロセスを調べられます。

```shell-session
$ sudo lsof -P -i:80
COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
OrbStack  26865 ucan   86u  IPv4 0xe7f199b22192fe94      0t0  TCP *:80 (LISTEN)
```

表示されたPIDを殺します。
例えば `26865` PIDを削除したい場合は次のコマンドを実行します。

```shell-session
sudo kill -9 26865
```

再度、sailコマンドからコンテナを再作成してください。

```
$ sail down
$ sail up -d
```

## Windows WSL2で環境構築する場合

### WSL2 インストール

WindowsにWSL2をインストールします。
こちらの記事を参考にしてください。

- [How to install Linux on Windows with WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
- [WSL2のインストールを分かりやすく解説【Windows10/11】](https://chigusa-web.com/blog/wsl2-win11)

### Docker Desktop インストール

次に[Docker Desktop](https://www.docker.com/ja-jp/products/docker-desktop)のWindows版をインストールします。
こちらの記事を参考にしてください。

[【Docker Desktop】Windowsにインストール（WSL2）](https://chigusa-web.com/blog/windows%e3%81%abdocker%e3%82%92%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab%e3%81%97%e3%81%a6python%e7%92%b0%e5%a2%83%e3%82%92%e6%a7%8b%e7%af%89/)

### WSL2 遅い問題の対策

次にディスクフォーマットの問題で遅くなる問題があるため、Laravelプロジェクトをインストールする場所は `¥¥wsl$Ubuntu¥mnt¥` ではなく `¥¥wsl$Ubuntu¥home¥` に移動してください。
こちらの記事を参考にしてください。

[Windows + WSL2 + docker + laravel を 10 倍速くする方法](https://www.aska-ltd.jp/jp/blog/197)

## 補足: laravel.build のスクリプトは何をしているのか？

```shell
$ curl -s "https://laravel.build/example-app" | bash
```

どんなスクリプトが実行されているか調べたい場合は `bash` を `cat` に変えればスクリプトの中身を確認できます。

```shell
$ curl -s "https://laravel.build/example-app" | cat

docker info > /dev/null 2>&1

# Ensure that Docker is running...
if [ $? -ne 0 ]; then
    echo "Docker is not running."

    exit 1
fi

docker run --rm \
    --pull=always \
    -v "$(pwd)":/opt \
    -w /opt \
    laravelsail/php83-composer:latest \
    bash -c "laravel new example-app --no-interaction && cd example-app && php ./artisan sail:install --with=mysql,redis,meilisearch,mailpit,selenium "

cd example-app

# Allow build with no additional services..
if [ "mysql redis meilisearch mailpit selenium" == "none" ]; then
    ./vendor/bin/sail build
else
    ./vendor/bin/sail pull mysql redis meilisearch mailpit selenium
    ./vendor/bin/sail build
fi

CYAN='\033[0;36m'
LIGHT_CYAN='\033[1;36m'
BOLD='\033[1m'
NC='\033[0m'

echo ""

if command -v doas &>/dev/null; then
    SUDO="doas"
elif command -v sudo &>/dev/null; then
    SUDO="sudo"
else
    echo "Neither sudo nor doas is available. Exiting."
    exit 1
fi

if $SUDO -n true 2>/dev/null; then
    $SUDO chown -R $USER: .
    echo -e "${BOLD}Get started with:${NC} cd example-app && ./vendor/bin/sail up"
else
    echo -e "${BOLD}Please provide your password so we can make some final adjustments to your application's permissions.${NC}"
    echo ""
    $SUDO chown -R $USER: .
    echo ""
    echo -e "${BOLD}Thank you! We hope you build something incredible. Dive in with:${NC} cd example-app && ./vendor/bin/sail up"
fi
```

## 補足: ログインパスワードの入力に失敗した場合

```shell
$ curl -s "https://laravel.build/example-app" | bash

...

Please provide your password so we can make some final adjustments to your application's permissions.
Password: [パソコンのログインパスワード]
```

一つ上でスクリプトを見たところ、最後は次の権限設定コマンドを実行しているだけのようです。

```
$ cd example-app
$ sudo chown -R $USER: .
```