Laravel

Laravel入門

小さな投稿アプリを題材に、Laravelの仕組みをコードと実行結果で一歩ずつ学びましょう。

この講座はLaravel 13、PHP 8.3以降を対象にしています。

01

Laravelとは

LaravelはPHPでWebアプリケーションを作るためのフレームワークです。URLを受け取り、入力を確認し、データベースを操作し、HTMLやJSONを返すまでに必要な部品を、決まった構造で組み合わせられます。

Laravelでできること
  • ブログ、掲示板、日報などの画面とデータ管理
  • ログイン、パスワードリセット、メール認証
  • JavaScriptアプリやスマートフォン向けのJSON API
  • キュー、定期実行、メール、ファイル保存
  • HTTPリクエスト、データベース、画面の自動テスト

フレームワークは、すべてを自動で正解にしてくれる魔法ではありません。Laravelが用意した約束に沿って責務を分けることで、コードが増えても調査と変更をしやすくする道具です。

ブラウザールートコントローラーモデル・Bladeレスポンス
02

準備と環境構築

Laravel 13ではPHP 8.3以上が必要です。Composer、Laravelインストーラー、Node.jsとnpmまたはBunを用意します。最初はSQLiteを選ぶと、別のDBサーバーを起動せずに学習を始められます。

バージョン確認COMMAND
php --version
composer --version
node --version
npm --version
Laravelインストーラーと作成COMMAND
composer global require laravel/installer
laravel new note-board
cd note-board

インストーラーの質問では、テストフレームワーク、データベース、スターターキットを選べます。ここでは認証を後から理解するため、まずは最小構成で進めます。

起動COMMAND
npm install && npm run build
composer run dev
ブラウザーURL
http://localhost:8000
起動できないとき PHPの版、ComposerのPATH、使用中のポート、Node.jsのインストールを順に確認します。コマンドの先頭に./vendor/bin/が必要な場合は、プロジェクト内の依存関係を使っているかも確認しましょう。
03

プロジェクト構成を読む

最初から全ファイルを暗記する必要はありません。自分が変更する代表的な場所と役割を知り、処理を置く場所に迷わないことが目標です。

主なディレクトリーTEXT
app/Models/          データを表すモデル
app/Http/Controllers/ 画面やAPIの処理
app/Http/Requests/    入力検証をまとめるクラス
resources/views/      Bladeテンプレート
routes/web.php        ブラウザー向けのルート
routes/api.php        API向けのルート(必要に応じて有効化)
database/migrations/  テーブル構造の変更履歴
database/seeders/     学習用データの投入
tests/Feature/        アプリ全体に近いテスト
config/               設定ファイル
storage/              ログや生成ファイル

.envには環境ごとに異なる設定を書きます。パスワードやAPIキーが入る可能性があるので、Gitへコミットしません。APP_KEYは暗号化などに使われる重要な値です。

責務を迷ったとき URLとの対応はルート、HTTP処理はコントローラー、データのルールと保存はモデル、見た目はBladeへ置くところから始めます。複雑になったらForm Request、Policy、Serviceなどへ分けます。
04

リクエストの流れを追う

画面が表示されるまでの経路を追えると、Laravelの記法が急に分かりやすくなります。たとえばGET /greetingを受け取り、名前をビューへ渡して表示します。

routes/web.phpPHP
use Illuminate\Support\Facades\Route;

Route::get('/greeting', function () {
    return view('greeting', ['name' => 'Mity']);
});
resources/views/greeting.blade.phpBLADE
<h1>こんにちは、{{ $name }}さん</h1>

ブラウザーで/greetingを開くと、次のHTMLが生成されます。

表示結果OUTPUT
こんにちは、Mityさん
処理の順番
  1. ブラウザーがGETリクエストを送る
  2. LaravelがRoute::getに一致させる
  3. クロージャーがビューへ$nameを渡す
  4. BladeがHTMLを生成してレスポンスを返す
05

ルーティング

ルートはHTTPメソッド、URL、実行する処理の組み合わせです。同じURLでもGETは表示、POSTは登録というように役割を分けます。

routes/web.phpPHP
Route::get('/posts', [PostController::class, 'index'])
    ->name('posts.index');
Route::get('/posts/create', [PostController::class, 'create'])
    ->name('posts.create');
Route::post('/posts', [PostController::class, 'store'])
    ->name('posts.store');
Route::get('/posts/{post}', [PostController::class, 'show'])
    ->name('posts.show');

URLへ直接文字列を埋め込むより、名前付きルートとroute()を使うと、URLを変更してもリンクの修正箇所を減らせます。

リンクBLADE
<a href="{{ route('posts.index') }}">投稿一覧</a>
<a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
確認コマンドCOMMAND
php artisan route:list
ルートパラメーター {post}のような値は、次のコントローラーで受け取ります。Laravelの暗黙のモデル結合を使うと、IDに対応するPostを自動取得できます。

ルーティングとBladeをもっと詳しく学ぶ場合はルーティングとBladeへ進んでください。

06

コントローラーで処理を分ける

ルートへ長い処理を書き続けると、入力、DB、画面の責務が混ざります。コントローラーを作り、リクエストを受けて必要な処理へ橋渡しします。

作成コマンドCOMMAND
php artisan make:controller PostController
app/Http/Controllers/PostController.phpPHP
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\View\View;

class PostController extends Controller
{
    public function index(): View
    {
        $posts = Post::query()->latest()->paginate(10);

        return view('posts.index', compact('posts'));
    }
}

latest()paginate()はモデルへ問い合わせる処理、view()は表示へ渡す処理です。コントローラーにHTML文字列や複雑なSQLを詰め込みすぎないようにします。

07

BladeでHTMLを作る

BladeはLaravelのテンプレートエンジンです。{{ }}で値を表示し、通常はHTMLとして安全にエスケープされます。条件分岐や繰り返しも、HTMLの流れを保ったまま書けます。

resources/views/posts/index.blade.phpBLADE
<h1>投稿一覧</h1>

@forelse ($posts as $post)
    <article>
        <h2>{{ $post->title }}</h2>
        <p>{{ $post->body }}</p>
        <a href="{{ route('posts.show', $post) }}">続きを読む</a>
    </article>
@empty
    <p>まだ投稿がありません。</p>
@endforelse

{{ $posts->links() }}

共通レイアウトは@extends@sectionで分け、部品は@includeやBladeコンポーネントへまとめます。

レイアウトBLADE
<!-- resources/views/layouts/app.blade.php -->
<!doctype html>
<html lang="ja">
<body>
    <header>Note Board</header>
    @yield('content')
</body>
</html>

<!-- posts/index.blade.php -->
@extends('layouts.app')
@section('content')
    <h1>投稿一覧</h1>
@endsection
表示時のエスケープ 利用者の入力を表示する場所では{{ $value }}を使います。HTMLを解釈させる{!! $value !!}は、信頼できる生成済みHTMLに限り、入力値へ使わないでください。
08

フォームとCSRF

表示用のGETと、データを変更するPOSTを分けます。LaravelのWebルートでPOSTフォームを作るときは、@csrfでCSRFトークンを埋め込みます。これにより、別サイトから勝手に送信されるリクエストを検査できます。

投稿フォームBLADE
<form method="POST" action="{{ route('posts.store') }}">
    @csrf
    <label for="title">タイトル</label>
    <input id="title" name="title" value="{{ old('title') }}" required>

    <label for="body">本文</label>
    <textarea id="body" name="body" required>{{ old('body') }}</textarea>
    <button type="submit">保存</button>
</form>
編集・削除BLADE
<form method="POST" action="{{ route('posts.update', $post) }}">
    @csrf
    @method('PUT')
    <input name="title" value="{{ old('title', $post->title) }}">
    <button type="submit">更新</button>
</form>

<form method="POST" action="{{ route('posts.destroy', $post) }}">
    @csrf
    @method('DELETE')
    <button type="submit">削除</button>
</form>
GETで削除しない リンクをクリックしただけで状態が変わる設計は避けます。更新・削除はPOST、PUT、PATCH、DELETEなどの変更用メソッドと認可を組み合わせます。
09

バリデーションで入力を確認する

フォームの値は、利用者が正しく入力するとは限りません。保存前に必須、文字数、形式などのルールを確認し、失敗したら入力画面へ戻します。

storeメソッドPHP
use Illuminate\Http\Request;

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => ['required', 'string', 'max:100'],
        'body' => ['required', 'string', 'max:5000'],
    ]);

    Post::create($validated);

    return redirect()
        ->route('posts.index')
        ->with('status', '投稿を保存しました。');
}
エラー表示BLADE
@error('title')
    <p class="error">{{ $message }}</p>
@enderror

@if (session('status'))
    <p>{{ session('status') }}</p>
@endif

入力を検証した後は、検証済みの$validatedだけを保存処理へ渡します。大量代入を使うモデルでは$fillableなどの設定も確認し、利用者が変更してはいけない列を受け取らないようにします。

フォーム検証の詳しい例と練習問題はフォームとバリデーションにまとめています。

10

データベースとマイグレーション

マイグレーションは、テーブル構造の変更をコードで記録する仕組みです。手作業でDBを編集するのではなく、チーム全員が同じ履歴を適用できるようにします。

モデルとマイグレーションの作成COMMAND
php artisan make:model Post -mf
php artisan migrate
create_posts_table.phpPHP
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('title', 100);
    $table->text('body');
    $table->string('image_path')->nullable();
    $table->timestamps();
});

開発中に最後の変更を取り消して作り直すならphp artisan migrate:fresh --seedがあります。ただし、このコマンドはDBのテーブルとデータをすべて削除するため、本番環境では実行しません。

設計時に考えること
  • 空欄を許す列か、必須の列か
  • 文字列の最大長や数値の範囲は何か
  • 削除された利用者と投稿をどう扱うか
  • 検索や並び替えに必要なインデックスは何か
11

Eloquentでデータを扱う

Eloquentは、データベースの行をモデルオブジェクトとして扱うORMです。便利なメソッドを使いながらも、実際にはSQLが発行されることを意識し、必要なデータだけを取得します。

PostモデルPHP
class Post extends Model
{
    protected $fillable = ['title', 'body', 'image_path'];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

// 検索・作成・更新・削除
$posts = Post::where('title', 'like', '%Laravel%')
    ->latest()
    ->get();
$post = Post::create($validated);
$post->update(['title' => '更新後のタイトル']);
$post->delete();
実行結果を確認する例TINKER
php artisan tinker
>>> Post::count();
= 3
>>> Post::latest()->first()->title;
= "Laravelを学び始めました"
N+1問題に注意 投稿ごとに利用者を取得するとクエリが増えることがあります。一覧で利用者も使うならPost::with('user')->latest()->get()のようにEager Loadingを検討し、クエリログや実際の件数で確認します。

マイグレーション、モデル、CRUDの詳しい手順はデータベースとEloquentで練習できます。

12

CRUDを一周する

投稿アプリの基本は、Create(作成)、Read(読込)、Update(更新)、Delete(削除)です。表示・フォーム・保存・編集・削除をルートとコントローラーへ対応させます。

リソースルートPHP
Route::resource('posts', PostController::class);
メソッドURL目的
GET/posts一覧(index)
GET/posts/create作成フォーム(create)
POST/posts保存(store)
GET/posts/{post}詳細(show)
GET/posts/{post}/edit編集フォーム(edit)
PUT/PATCH/posts/{post}更新(update)
DELETE/posts/{post}削除(destroy)

まず一覧を表示し、次に新規作成、編集、削除を一つずつ追加します。各段階で正常な入力だけでなく、存在しないID、空欄、他人の投稿も確認すると、後の不具合を早く見つけられます。

13

モデル同士の関係を表す

投稿は利用者に属し、利用者は複数の投稿を持ちます。リレーションをモデルへ定義すると、関連データを意味のあるコードで取得できます。

UserモデルPHP
use Illuminate\Database\Eloquent\Relations\HasMany;

public function posts(): HasMany
{
    return $this->hasMany(Post::class);
}

// 利用者の投稿を取得
$user->posts()->latest()->get();
PostモデルPHP
use Illuminate\Database\Eloquent\Relations\BelongsTo;

public function user(): BelongsTo
{
    return $this->belongsTo(User::class);
}

echo $post->user->name;

多対多ならbelongsToManyと中間テーブルを使います。関係をコードにしたら、外部キー、削除時の動作、一覧時のクエリ数も合わせて設計します。

14

認証と認可

認証は「誰か」を確認すること、認可は「何をしてよいか」を確認することです。ログインできても、他人の投稿まで編集できてよいとは限りません。

認証ミドルウェアPHP
Route::middleware('auth')->group(function () {
    Route::resource('posts', PostController::class)
        ->except(['index', 'show']);
});
Policyの考え方PHP
public function update(User $user, Post $post): bool
{
    return $user->id === $post->user_id;
}

// コントローラー
$this->authorize('update', $post);

ログイン画面は公式スターターキットを利用すると、登録、ログイン、パスワードリセットなどの土台を用意できます。パスワードを自作の暗号化で保存したり、認証を自己流で置き換えたりせず、Laravelの標準機能と公式資料を確認しましょう。

認証とAPIトークンを詳しく学ぶ場合は認証とAPIへ進んでください。

15

JSON APIを作る

LaravelはHTML画面だけでなく、JSONを返すAPIのバックエンドにも使えます。APIでは、成功時と失敗時のステータスコード、返す項目、認証方法を先に決めます。

JSONを返すルートPHP
use App\Models\Post;
use Illuminate\Http\JsonResponse;

Route::get('/posts', function (): JsonResponse {
    return response()->json(
        Post::latest()->get(['id', 'title', 'body'])
    );
});
レスポンス例JSON
[
  {
    "id": 3,
    "title": "Laravelを学び始めました",
    "body": "まずはルートを動かします。"
  }
]

実際のアプリではAPI Resourceで公開する項目を定義し、認証が必要なルートにはSanctumなどを使います。モデルをそのまま返すと、意図しない列を公開する恐れがあるため注意が必要です。

16

サービス、メール、キュー

処理が増えたら、コントローラーから業務処理をサービスクラスへ分けます。たとえば投稿保存と画像処理、通知を別の責務にするとテストしやすくなります。

サービスクラスの例PHP
final class PublishPost
{
    public function handle(User $user, array $data): Post
    {
        return $user->posts()->create([
            'title' => $data['title'],
            'body' => $data['body'],
        ]);
    }
}

すぐ返す必要のないメール送信や重い処理は、ジョブをキューへ渡します。キューワーカーは別プロセスなので、ローカルで実行する場合もphp artisan queue:workを起動し、失敗したジョブや再試行を確認します。

定期実行の確認COMMAND
php artisan schedule:list
php artisan queue:work
後回しにしてよい順序 最初はルート、Blade、DB、バリデーション、認可を優先します。メールやキューは、同期処理で動く完成形を作ってから非同期化すると、原因を切り分けやすくなります。
17

テストで動作を守る

テストは、書いた直後だけでなく、将来の変更で既存機能が壊れていないかを確認するためのコードです。LaravelではUnitテストとFeatureテストを使い分けますが、画面・DB・認証まで確認するFeatureテストが特に実践的です。

テスト作成COMMAND
php artisan make:test PostTest
php artisan test
tests/Feature/PostTest.phpPHP
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

test('投稿一覧を表示できる', function () {
    $post = Post::factory()->create([
        'title' => 'テスト投稿',
    ]);

    $this->get('/posts')
        ->assertOk()
        ->assertSee('テスト投稿');
});
登録失敗もテストするPHP
test('タイトルが空なら保存しない', function () {
    $response = $this->post('/posts', [
        'title' => '',
        'body' => '本文',
    ]);

    $response->assertSessionHasErrors('title');
    $this->assertDatabaseCount('posts', 0);
});

テストでは、DBや外部メールへ本物の処理を送らない設定を使います。正常系だけでなく、未認証、入力エラー、権限不足、存在しないデータもテストすると、利用者の操作に近い品質を確認できます。

18

公開前の確認

ローカルで動くことと、インターネットへ安全に公開できることは別です。公開前には設定、権限、ログ、バックアップ、HTTPS、DB接続を確認します。

チェックリスト
  • .envや秘密鍵をリポジトリへ含めていない
  • 本番のAPP_DEBUGfalseになっている
  • Webサーバーの公開先がプロジェクトのpublicだけになっている
  • 入力検証、CSRF、認証、認可、出力エスケープを確認した
  • マイグレーションとバックアップの手順がある
  • ログにパスワードやトークンを出力していない
  • テストと本番用のメール・ストレージ設定を分けている
本番用キャッシュの例COMMAND
php artisan optimize
php artisan migrate --force
本番コマンドは慎重に migrate --forceは確認なしでDB構造を変更します。バックアップ、適用するマイグレーション、ロールバック方法を確認してから実行します。

テスト、デプロイ、運用の流れをまとめて復習する場合はテストとデプロイを読んでください。

PRACTICE

総合練習:読書メモアプリを作る

ここまでの知識を使い、ログインした利用者が読書メモを登録・一覧・編集・削除できるアプリを作ってください。既存の投稿アプリをそのまま写すのではなく、要件を自分で分解する練習です。

課題1:データ設計

booksテーブルとnotesテーブルを設計します。読書メモは利用者と本に属し、タイトル、本文、評価(1〜5)、読了日を持つものとします。

課題2:画面とCRUD

本の一覧、メモの一覧、メモ作成、編集、削除を実装します。名前付きルート、Resource Controller、Bladeレイアウトを使い、空の一覧とページネーションも表示します。

課題3:安全性

未ログイン利用者は作成・更新・削除できないようにします。さらに、別の利用者のメモをURLへ直接入力しても編集・削除できないPolicyを作り、フォームにはCSRF、保存前にはバリデーションを追加します。

課題4:テスト

「ログイン利用者はメモを作成できる」「タイトルが空なら保存されない」「他人のメモは更新できない」「一覧に自分のメモが表示される」をFeatureテストにしてください。

完成条件と発展課題
  • マイグレーションを最初から適用してDBを作れる
  • SeederまたはFactoryで確認用データを作れる
  • 正常系・入力エラー・権限エラーの画面を確認できる
  • テストを実行して全件成功する
  • 発展として、画像、検索、タグ、多対多リレーション、JSON APIのどれかを追加する
NEXT

次に学ぶこと

Laravelの基本を一周したら、興味のあるテーマを一つ選び、サンプルを改造してテストまで書きます。公式ドキュメントは、インストール、ルーティング、Blade、バリデーション、Eloquent、認証、テストの順で参照すると迷いにくくなります。

詳しい仕様はLaravel公式ドキュメント(13.x)も確認してください。