インフラエンジニアがLaravelの基本を学んでみた

はじめに

AWS系のインフラエンジニアですが、ローカルにLaravelを入れて、基本的な動きを学習してみました。

経緯は以下です。

  1. RDSで動いているMySQLバージョンアッププロジェクト担当(8.0→8.4)
  2. アプリケーション互換性のチェックが必要になる
  3. 接続元アプリケーションがLaravelで動いてるらしい
  4. Laravelまったくわからない・・・これはまずい

という感じです。
有識者の方からすれば、当たり前すぎて退屈な内容かもしれませんが、自分の備忘録用にブログにできればと存じます。
また、都度気になったこともコラム的に書いておりますので、横道にそれまくりますが、ご容赦くださいませ。

本ブログの構成

  • システム構成
  • 必要パッケージ導入(パッケージとか、モジュールとか、ファイルって)
  • DB作成
  • Laravelの導入と最小アプリケーションの作成
  • サイト定義
  • 登場人物の整理
  • ログの役割整理

システム構成

システム構成は以下でローカルのUbuntu 22.04.5で構築しました。
なお、MySQL8.0は事前に導入済

ブラウザ/curl → [nginx] → [php-fpm 8.1] → [Laravel (メモアプリ)] → [MySQL 8.0]

必要パッケージ導入

以下コマンドで必要パッケージを導入していきます。

sudo apt update
sudo apt install -y nginx php8.1-fpm php8.1-mysql php8.1-xml php8.1-mbstring php8.1-curl php8.1-zip composer

以下、各パッケージの説明です。

nginx           … Webサーバ本体
php8.1-fpm      … PHP の実行エンジンでnginx から PHP 処理を受け取る
php8.1-mysql    … PHP から MySQL に接続する部品
php8.1-xml      … XML を扱う部品。Laravel の内部処理に必要
php8.1-mbstring … 日本語などマルチバイト文字を扱う部品。Laravel の必須要件
php8.1-curl     … PHP から HTTP 通信する部品。composer やライブラリが利用
php8.1-zip      … PHP で zip を扱う部品。composer がライブラリ展開に利用
composer        … PHP のパッケージ管理ツール。Laravel 本体の導入に使う

コラム1:パッケージ・ファイル・プログラム・サービス・モジュールの区別

少し横道に逸れます。
これら用語を区別せずに使ってしまうことがあるので、整理します。

■ パッケージ = ソフトを配る「箱」でファイルの集まり。apt/rpm が台帳で管理する。
例: php8.1-fpm、nginx-core
確認コマンド: dpkg -l(rpm -qa)

■ ファイル = ディスク上に置かれた個々の「中身」
例: /usr/sbin/nginx、/etc/nginx/nginx.conf
確認コマンド: ls、パッケージからファイルの逆引きは dpkg -S(rpm -qf)

■ プログラム = ファイルのうちOSが直接実行できるもの(実行権限 x があり、中身が機械語のバイナリ、または1行目が #!のスクリプト)
例:/usr/sbin/php-fpm8.1
確認コマンド:ls
※詳細はコラム6,7で説明しています。

■ サービス = プログラムが動いている状態
例: nginx、php8.1-fpm、mysql
確認コマンド: systemctl status、ps aux

■ モジュール = プログラムに読み込まれる「部品」
単独では動けず、親プログラムに機能を追加する。
例: mysqli、pdo_mysql、mbstring
確認コマンド: php -m

例:php8.1-fpm というパッケージを入れると /usr/sbin/php-fpm8.1 等のファイルが置かれる。これは実行できるファイル=プログラムで、systemctl start でサービスとして常駐し、その中に mysqli 等のモジュールが読み込まれる

コラム2:systemctl status から実行ファイルの確認

systemctl status mysql を実行すると以下のような結果が返って来ます。

● mysql.service - MySQL Community Server
     Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2026-07-18 12:46:07 JST; 1h 26min ago
   Main PID: 273 (mysqld)

ここから、プログラムの実体を探してみます。

sudo readlink /proc/273/exe
/usr/sbin/mysqld

PID 273 として動いているのは、/usr/sbin/mysqld というプログラムが起動されたもの。ということがわかります。
コマンドの補足:proc/プロセスID/exeは該当プロセスが実行しているプログラムファイルを指すリンクです。
そのためsudo ls -l /proc/273/exeでも同様の結果を取得することができます。

sudo ls -l /proc/273/exe
lrwxrwxrwx 1 mysql mysql 0 Aug  1 11:02 /273/304/exe -> /usr/sbin/mysqld

プログラムを探す際に、whichも利用可能ですが、これはあくまで環境変数に登録されているものを出すだけなので、
実体とずれる可能性があります。

DB作成

以下のコマンドでDBとユーザーの作成をします。

sudo systemctl start mysql
sudo mysql
CREATE DATABASE studyapp;
CREATE USER 'study'@'localhost' IDENTIFIED BY 'StudyPass123!';
GRANT ALL ON studyapp.* TO 'study'@'localhost';

MySQL8.4からデフォルトの認証プラグインが「caching_sha2_password」になるので、そこもチェックします。

「mysql_native_password」で接続する既存ユーザーについて、バージョンアップ時に変更されることはありませんが、
新規作成ユーザーの認証プラグインは「caching_sha2_password」になりますので、注意が必要です。
※実機検証済

mysql> SELECT user, host, plugin FROM mysql.user WHERE user IN ('study','root');
+-------+-----------+-----------------------+
| user  | host      | plugin                |
+-------+-----------+-----------------------+
| root  | localhost | auth_socket           |
| study | localhost | caching_sha2_password |
+-------+-----------+-----------------------+
2 rows in set (0.01 sec)

Laravelの導入と最小アプリケーションの作成

早速Laravelを導入し、Laravel経由でDBに触れるようにしていきます。

sudo mkdir -p /var/www && sudo chown $USER /var/www
cd /var/www && composer create-project laravel/laravel studyapp
cd studyapp

compoerはPHPのパッケージ管理ツールで、Laravelのひな形プロジェクトをstudyapp/に一気に展開します。

.envの設定を書き換えて、テーブルとルートを作成します。

DB_DATABASE=studyapp
DB_USERNAME=study
DB_PASSWORD=StudyPass123!
php artisan make:migration create_memos_table --create=memos

DBのテーブル定義をPHPファイルとして残すためのひな形ファイルを作成します。
前提としてテーブル定義は「マイグレーションファイル」というファイルで管理されます。
以下のようなファイルです。

  public function up(): void
  {
      Schema::create('memos', function (Blueprint $table) {
          $table->id();
          $table->timestamps();
      });
  }

  public function down(): void
  {
      Schema::dropIfExists('memos');
  }

このコマンドはmemosというテーブル用のマイグレーションファイルを作成して。という意味になります。
このファイルはdatabase/migrations/配下に作成されます。

作成されたマイグレーションファイルに以下を追加します。

$table->string('text');

次に、routes/web.php の末尾に以下を追加します。
ちなみにこのファイルにはURLと関数を紐づける対応表なのであり、以下 [Laravel (メモアプリ)] の中で処理されます。
ブラウザからの問い合わせは以下の順番で処理されます。

ブラウザ/curl → [nginx] → [php-fpm 8.1] → [Laravel (メモアプリ)] → [MySQL 8.0]
use Illuminate\Support\Facades\DB;
Route::get('/memos', fn () => DB::table('memos')->get());
Route::get('/memos/add/{text}', function (string $text) {
    DB::table('memos')->insert(['text' => $text, 'created_at' => now()]);
    return ['saved' => $text];
});

詳細な説明は省きますが要約すると以下のようになります。

Route::get('/memos', fn () => DB::table('memos')->get());
//         ↑URL      ↑その URL に GET が来たとき実行する処理

では実際にマイグレーションファイルの設定を適用していきます。

php artisan migrate
sudo chown -R www-data:www-data storage bootstrap/cache

同時に、ログの出力先やキャッシュ用フォルダの権限を変更します。
→php-fpmのworkerプロセスの実行ユーザはwww-dataであるため

なお、pool定義は/etc/php/8.1/fpm/pool.d/www.conf ファイルに記載されております。

grep -E '^(user|group)' /etc/php/8.1/fpm/pool.d/www.conf
user = www-data
group = www-data

次にサイト定義をしていきます。
これはブラウザからのリクエストを最初に処理するnginxのルーティング定義を記載します。

sudo tee /etc/nginx/sites-available/studyapp << 'EOF'
server {
    listen 80;
    root /var/www/studyapp/public;  # ドキュメントルート
    index index.php;  # 「/」で来たら index.php を探す

    access_log /var/log/nginx/studyapp_access.log;   # ← サイト専用ログ
    error_log  /var/log/nginx/studyapp_error.log;

    location / {
        try_files $uri /index.php?$query_string; #リクエストされたパスに実ファイルがあればそれを返し、無ければ全部 index.php に回す
    }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;   # ← php-fpm への受け渡し口
    }
}
EOF
sudo rm /etc/nginx/sites-enabled/default          # 既定サイトを外して差し替え
sudo ln -s /etc/nginx/sites-available/studyapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginx php8.1-fpm

sudo tee … << 'EOF' について

  • < ファイル ではなく sudo tee なのかというと、sudo echo … > /etc/… と書くと、リダイレクト > は sudo の対象外(シェル自身が一般ユーザ権限でファイルを開く)ため Permission denied になります。
  • sudo tee なら「ファイルを開いて書く」処理自体が root で動きます。
  • ‘EOF’ とクォートしている理由: クォートするとヒアドキュメント内の $uri や $query_string をシェルが展開せず、そのまま書き込まれます。クォートなしだと $uri が空文字に化けて壊れた設定ファイルができます。

fastcgi_passについて
nginxはPHPを自分で実行できないので、php-fpm に処理を委託します。
fastcgi_passで指定される場所がその受け渡し先になります。なお、この内容は/etc/php/8.1/fpm/pool.d/www.conf のlinsten行と一致している必要があります。

ファイル 意味
php-fpm(受ける側) /etc/php/8.1/fpm/pool.d/www.conf listen = /run/php/php8.1-fpm.sock 起動時にこのパスにソケットを作って待ち受ける
nginx(渡す側) /etc/nginx/sites-available/studyapp fastcgi_pass unix:/run/php/php8.1-fpm.sock; PHPリクエストをこのパスへ送る

構築自体は以上で終わりです。実際に動作確認してみます。

curl http://localhost/memos/add/hello    # → {"saved":"hello"}
curl http://localhost/memos              # → [{"id":1,"text":"hello",...}]

登場人物の整理

以降は情報を整理していきます。
リクエストは以下のように処理されていきます。

curl → ①nginx → ②php-fpm → ③Laravel(index.php) → ④Laravelルータ → ⑤無名関数 → ⑥MySQL → 逆順に返る

①nginxが受ける
try_files $uri /index.php?$query_string; により、実ファイル /memos/add/hello が無いので /index.php へ内部転送される。nginxはURLの意味を理解していない。

②nginx → php-fpmに委託
location ~ \.php$ にマッチし、fastcgi_pass のソケット経由でphp-fpmのworkerに渡す。元のURLも一緒に渡る。

③php-fpmがLaravelの入口 public/index.php を実行
URLが何であれ入口は常にこの1ファイル。

④Laravelのルータが routes/web.php と照合
/memos/add/hello'/memos/add/{text}' に一致し、$text = "hello" として関数に渡す。

⑤無名関数が実行される

DB::table('memos')->insert(['text' => $text, 'created_at' => now()]);
return ['saved' => $text];

⑥MySQLが実行される

各ログの役割整理

そもそもの目的がDBアップグレードに際してのアプリケーション互換性確認だったので、この最小限構成での4つのログについて整理します。

/var/log/nginx/studyapp_error.log
/var/log/nginx/studyapp_access.log
/var/log/php8.1-fpm.log
/var/www/studyapp/storage/logs/laravel.log

① /var/log/nginx/studyapp_error.log — nginx のエラーログ
nginxのエラーログです。php-fpmに接続できない時などにエラーがでます。

sudo systemctl stop php8.1-fpm
curl http://localhost/memos     # → 502 Bad Gateway

②/var/log/nginx/studyapp_access.log
nginxのアクセスログです

③/var/log/php8.1-fpm.log
php-fpmのログです。サービス自体の起動・停止・警告(例: worker が足りない pm.max_children 警告)が出ます。

④/var/www/studyapp/storage/logs/laravel.log
Laravel(アプリ)のログです。パスワード間違いや、mysqlが上がってないなどのDB関連のエラーはここに出ます。
なお、他のログと違い、アプリフォルダ内にあります。そのため構築時にchown www-data storage が必要でした。

本ブログの大枠はここまでで、あとはコラムが続きます。

コラム3:ステータスコードの覚え方

ステータスコードなんですが、2xx系と3xx系はまだ覚えやすいのですが、4xxと5xxでよく混乱します。
受けた側(サーバ)のミス 「5=ご=ごめん、こっちのミス」と最近覚えることにしました。

コラム4:php-fpmのログ出力先

/var/log/php8.1-fpm.logに出力されるログですが、以下から裏どりできます。
①php-fpmのプロセスを確認
②設定ファイル(/etc/php/8.1/fpm/php-fpm.conf)から「error_log」で検索

error_log = /var/log/php8.1-fpm.log

コラム5:Laravelのログ出力先

/var/www/studyapp/config/logging.phpで定義されています。

return [
    'default'  => env('LOG_CHANNEL', 'stack'),   // 普段使う書き先の名前
    'channels' => [                              // 書き先の一覧
        'stack'  => ['driver' => 'stack',  'channels' => ['single']],
        'single' => ['driver' => 'single', 'path' => storage_path('logs/laravel.log')],
        'daily'  => ['driver' => 'daily',  'path' => storage_path('logs/laravel.log'), 'days' => 14],
        'slack'  => ['driver' => 'slack',  'url' => env('LOG_SLACK_WEBHOOK_URL')],
        ...
    ],
];

channels
ログの書き先の一覧。’名前’ => [設定] で複数登録しておく場所

driver
各書き先の種類。single=1ファイル追記/daily=日付ごとに分割/slack=Slack 送信/stack=他の書き先をまとめて呼ぶ。この値で必要な設定項目(path・url…)が決まる

stack
書き先のひとつの名前。ただし自分では書かず、channels に並べた書き先すべてへ同じログを流す束ね役

たどり方は
①default→stack
②stack→single
③sinlge→path
です。

わざわざstackを挟む理由は、
stack が「複数の書き先へ同時に書く」機能そのものだから。将来 Slack 通知を足すとき channels: [‘single’, ‘slack’] と1語足すだけで済むよう、最初から中継点を置いてある。stack に path を直接書けないのは、driver=stack が「他を呼ぶ」機能しか持たないため。

コラム6:インタープリタとは

よくソースコード(index.php)をPHPが実行する。みたいな言い方をしますが、どっちもPHPじゃないの?
と疑問に思ってしまったので、このあたりも少し深掘りします。

前提として人間が書いたコード(ただのテキスト)は、そのままでは CPU は実行できず、CPU が実行できる形にしなければいけません。
種類としては以下に分類されます。

コンパイル型 インタープリタ型
やり方 事前にコンパイラで機械語に全訳し、実行ファイル(バイナリ)を作る。実行時はバイナリだけが動く 実行のたびにインタープリタがソースを読み、逐次解釈しながら実行する
例えると 翻訳書を先に出版し、読者は訳書だけ読む 同時通訳者が原文をその場で訳しながら伝える
実行に必要なもの バイナリのみ(ソース不要) ソース+インタープリタ本体の両方
代表例 C、C++、Go、Rust PHP、Python、Ruby、シェルスクリプト

PHPはインタープリタ型なので、phpファイルは単体ではただのテキストです。
そのインタープリタはバイナリファイルとして、以下に保存されています。

$ ls -l /usr/bin/php8.1 /usr/sbin/php-fpm8.1
-rwxr-xr-x 1 root root ... /usr/bin/php8.1        ← CLI(都度実行)
-rwxr-xr-x 1 root root ... /usr/sbin/php-fpm8.1   ← FPM(常駐)

つまり、インタープリタはプログラム(OS が直接起動できるバイナリ)であり、.php ソースは単独では実行できないファイル(インタープリタが開いて読む入力データ)である。ということになります。

「index.php を実行する」の実体は、インタープリタが実行されて、そのインプットとしてindex.phpが読まれることになります。

コラム7:shebang(シバン)とは — composerはテキストなのに直接実行できる

インタープリタの話の続きです。「.phpは単独では実行できない」と書きましたが、同じPHPのテキストなのに直接叩けるファイルがあります。composerです。

$ head -2 /usr/bin/composer
#!/usr/bin/php        ← これがshebang
<?php                 ← 2行目以降は普通のPHPソース

$ file /usr/bin/composer
/usr/bin/composer: a /usr/bin/php script, ASCII text executable

shebangとは、テキストファイルの1行目に書く #! で始まる行のことで、「このファイルは #! の後ろのプログラムに読ませてください」というOS向けのメモ書きです(#=sharp/hash + !=bang → sha-bang)。

composer --version と打ったときに起きていることは以下です。
①シェルが /usr/bin/composer をOSに「起動して」と渡す
②OSが先頭を見る → 機械語ではなく #! で始まっている
③OSは #! の後ろの /usr/bin/php を代わりに起動し、引数として /usr/bin/composer 自身を渡す
④つまり実際に動いているのは php /usr/bin/composer --version と同じ

$ composer --version
Composer 2.2.6 2022-02-04 17:00:38
$ php /usr/bin/composer --version
Composer 2.2.6 2022-02-04 17:00:38     ← 完全に同じ

index.phpとの違いは「OSから直接起動できる形(shebang+実行権限x)にしてあるか」だけで、中身を動かしているのがPHPインタープリタである点は同じです。

終わりに

以上です。

Last modified: 2026-08-15

Author