【第四弾】Laravel入門資料

📚 Laravel公式ドキュメント - Views

https://laravel.com/docs/12.x/views

💡 この講座では、上記の公式ドキュメントを基に解説していきます。
公式ドキュメントは初学者には内容が難しいため、エッセンスを優しく噛み砕いて解説していきます。

ビュー(View)とは

ビューとは、HTMLを記述するための専用ファイルのことです。 プログラムのロジック(処理)と表示(HTML)を分離することで、コードの保守性が向上します。

前章のルーティングでは、HTMLをルートファイルに直接書いていましたが、 実際の開発では複雑なHTMLになるため、ビューファイルに分けて管理します。

ビューファイルの場所

ビューファイルは resources/views/ ディレクトリに配置します。

  • ファイル名:○○.blade.php
  • 拡張子:.blade.php(Bladeテンプレートエンジン)
  • 配置場所:resources/views/

基本的なビューの使い方

1. ビューファイルを作成

まず、resources/views/hello.blade.php を作成します。

<!-- resources/views/hello.blade.php -->

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Hello Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
    <p>これはビューファイルから表示されています。</p>
</body>
</html>

2. ルートからビューを返す

次に、ルートファイルでビューを読み込みます。

// routes/web.php

Route::get('/hello', function () {
    return view('hello');
});

view('hello') は、resources/views/hello.blade.php を読み込みます。
ブラウザで http://localhost/hello にアクセスすると、ビューの内容が表示されます。

💡 view('hello') のように、.blade.php は省略します。

ビューにデータを渡す

ビューに変数を渡して、動的な内容を表示することができます。

方法1: 第2引数に配列で渡す

// routes/web.php

Route::get('/greeting', function () {
    return view('greeting', ['name' => '太郎']);
});
<!-- resources/views/greeting.blade.php -->

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Greeting</title>
</head>
<body>
    <h1>こんにちは、{{ $name }}さん!</h1>
</body>
</html>

{{ $name }} のように、二重波括弧で囲むことで変数の値を表示できます。
ブラウザには「こんにちは、太郎さん!」と表示されます。

方法2: with()メソッドを使う

// routes/web.php

Route::get('/greeting', function () {
    return view('greeting')->with('name', '太郎');
});

方法3: compact()を使う

// routes/web.php

Route::get('/greeting', function () {
    $name = '太郎';
    $age = 25;

    return view('greeting', compact('name', 'age'));
});
<!-- resources/views/greeting.blade.php -->

<h1>こんにちは、{{ $name }}さん!</h1>
<p>あなたは{{ $age }}歳ですね。</p>

💡 複数の変数を渡す場合は、compact() が便利です。

Bladeテンプレートの基本構文

Blade(ブレード)は、Laravelが提供するテンプレートエンジンです。 PHPよりもシンプルで読みやすい記法でHTMLを書くことができます。

変数の表示

<!-- Blade構文 -->
{{ $name }}

<!-- 生成されるPHP -->
<?php echo htmlspecialchars($name); ?>

{{ }} は自動的にHTMLエスケープされるため、XSS攻撃を防げます。

エスケープしない出力(注意)

<!-- エスケープせずにHTMLを出力 -->
{!! $html !!}

⚠️ {!! !!} はエスケープされないため、信頼できるデータのみに使用してください。

🚨 XSS攻撃の実例 - 実際に試してみよう

{!! !!} がなぜ危険なのか、実際にLaravelで確認してみましょう。

📝 手順1: ルートを作成

// routes/web.php に追加

Route::get('/xss-demo', function () {
    // 攻撃者が入力したコメント(本来はフォームから受け取る)
    $comment = 'こんにちは<script>alert("XSS攻撃成功!")</script>';

    return view('xss-demo', compact('comment'));
});

📝 手順2: ビューを作成

<!-- resources/views/xss-demo.blade.php を新規作成 -->

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>XSS攻撃デモ</title>
    <style>
        .danger { border: 3px solid red; padding: 20px; margin: 20px; background: #ffe0e0; }
        .safe { border: 3px solid green; padding: 20px; margin: 20px; background: #e0ffe0; }
    </style>
</head>
<body>
    <h1>XSS攻撃のデモ</h1>

    <div class="danger">
        <h2>❌ エスケープしない場合</h2>
        <p>コメント: {!! $comment !!}</p>
    </div>

    <div class="safe">
        <h2>✅ エスケープする場合</h2>
        <p>コメント: {{ $comment }}</p>
    </div>
</body>
</html>

📝 手順3: ブラウザで確認

http://localhost/xss-demo にアクセスしてください。

🔥 結果

  • 上の赤いボックス({!! !!}):
    → アラートが表示される(JavaScriptが実行された)
  • 下の緑のボックス({{ }}):
    → 「こんにちは<script>alert("XSS攻撃成功!")</script>」と文字列として表示される

💡 実験してみよう

ルートの $comment の内容を変更して、色々試してみてください:

// 例1: 画像タグで攻撃
$comment = '<img src=x onerror="alert(\'画像でも攻撃できる\')">';

// 例2: ページを改ざん
$comment = '<script>document.body.innerHTML = "<h1>乗っ取られました</h1>"</script>';

// 例3: 普通のHTML(安全な使い方)
$comment = '<strong>太字</strong>のテキスト';

⚠️ 重要なルール

  • ユーザー入力は絶対に {!! !!} を使わない
  • ❌ コメント、レビュー、プロフィール → {{ }} を使う
  • ❌ フォーム入力全般 → {{ }} を使う
  • ✅ 管理者が作ったHTMLコンテンツのみ → {!! !!} でOK

💡 {{ }} は自動的にエスケープされるため、デフォルトで安全です。
99%のケースで {{ }} を使えば問題ありません。

if文

@if ($age >= 20)
    <p>成人です</p>
@elseif ($age >= 13)
    <p>未成年です</p>
@else
    <p>子供です</p>
@endif

繰り返し処理(foreach)

// routes/web.php

Route::get('/users', function () {
    $users = ['太郎', '花子', '次郎'];

    return view('users', compact('users'));
});
<!-- resources/views/users.blade.php -->

<ul>
    @foreach ($users as $user)
        <li>{{ $user }}</li>
    @endforeach
</ul>

出力結果:

<ul>
    <li>太郎</li>
    <li>花子</li>
    <li>次郎</li>
</ul>

配列が空の場合の処理(forelse)

@forelse ($users as $user)
    <li>{{ $user }}</li>
@empty
    <p>ユーザーがいません</p>
@endforelse

@forelse は、@foreach に「データが空の場合の処理」を追加したものです。

💡 @empty の意味

@empty は、配列が空(要素が0個)の場合に実行される部分です。

❌ 普通の @foreach で書くと...

@if (count($users) > 0)
    @foreach ($users as $user)
        <li>{{ $user }}</li>
    @endforeach
@else
    <p>ユーザーがいません</p>
@endif

→ 冗長で読みにくい

✅ @forelse で書くと...

@forelse ($users as $user)
    <li>{{ $user }}</li>
@empty
    <p>ユーザーがいません</p>
@endforelse

→ スッキリ!実務ではこちらを使います。

📋 実務でよく使う例

<!-- 商品一覧 -->
@forelse ($products as $product)
    <div class="product">
        <h3>{{ $product->name }}</h3>
        <p>¥{{ $product->price }}</p>
    </div>
@empty
    <p class="text-gray-500">商品がありません</p>
@endforelse

💡 「データがない時のメッセージ」を表示する場合は、@foreach より @forelse を使いましょう。

unless(ifの逆)

@unless ($user->isAdmin())
    <p>あなたは管理者ではありません</p>
@endunless

@unless@if の否定形です。以下のコードと同じ意味になります:

<!-- @unless と同じ意味 -->
@if (!$user->isAdmin())
    <p>あなたは管理者ではありません</p>
@endif

💡 実務では @if (!...) を使う人が多いです。@unless はほとんど使いません。

📚 他のBladeディレクティブについて

Laravelには、この章で紹介した以外にも多くのBladeディレクティブがあります。
例えば、@can(権限チェック)、@include(部分テンプレート)、@push(スタック)、@error(バリデーションエラー)、@switch(switch文)などです。

しかし、今の内容で十分です

この章で学んだディレクティブだけで、実務の90%以上をカバーできます。
他のディレクティブは、必要になったときに公式ドキュメントを見れば理解できます。

💡 まずは今の内容を完璧に使えるようになりましょう。
初学者が覚えるべきBladeディレクティブは、この章の内容で完結しています。

📖 Blade全機能を知りたい方は、公式ドキュメントを参照してください:
https://laravel.com/docs/12.x/blade

サブディレクトリを使った整理

ビューファイルが増えてきたら、サブディレクトリで整理できます。

resources/views/
├── users/
│   ├── index.blade.php
│   ├── show.blade.php
│   └── edit.blade.php
├── posts/
│   ├── index.blade.php
│   └── show.blade.php
└── welcome.blade.php

ドット記法でアクセスします:

// routes/web.php

Route::get('/users', function () {
    return view('users.index');
});

Route::get('/users/{id}', function ($id) {
    return view('users.show', ['id' => $id]);
});

view('users.index') は、resources/views/users/index.blade.php を読み込みます。

レイアウトの共通化

複数のページで共通のヘッダーやフッターを使う場合、レイアウト機能を使います。

💡 Bladeディレクティブの意味

  • @extends - レイアウトファイルを「継承する」
  • @yield - 「ここに子ページの内容が入りますよ」という穴あけ
  • @section - 「この内容を@yieldの穴に埋めます」という指定
  • @endsection - @sectionの終わり

簡単に言うと:親ファイル(レイアウト)に穴を開けて、子ファイル(各ページ)がその穴を埋めるイメージです。

仕組みを図で理解する

┌─────────────────────────────────────┐
│ 親ファイル (layouts/app.blade.php) │
├─────────────────────────────────────┤
│ <header>ヘッダー</header>            │
│                                     │
│ @yield('content')  ← ここに子の内容が入る
│                                     │
│ <footer>フッター</footer>            │
└─────────────────────────────────────┘
           ↑ @extends で継承
           │
┌─────────────────────────────────────┐
│ 子ファイル (pages/about.blade.php) │
├─────────────────────────────────────┤
│ @section('content')                 │
│   <h1>会社概要</h1>                 │
│   <p>内容</p>                      │
│ @endsection                         │
└─────────────────────────────────────┘
           │
           ↓ 結果
┌─────────────────────────────────────┐
│ ブラウザに表示される最終HTML        │
├─────────────────────────────────────┤
│ <header>ヘッダー</header>            │
│ <h1>会社概要</h1>                   │
│ <p>内容</p>                        │
│ <footer>フッター</footer>            │
└─────────────────────────────────────┘

手順1: 親ファイル(レイアウト)を作成

<!-- resources/views/layouts/app.blade.php -->

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>@yield('title')</title>
</head>
<body>
    <header>
        <h1>My Website</h1>
    </header>

    <main>
        @yield('content')
    </main>

    <footer>
        <p>© 2025 My Website</p>
    </footer>
</body>
</html>

ポイント:

  • @yield('title') - タイトル用の穴
  • @yield('content') - メインコンテンツ用の穴
  • ヘッダーとフッターは共通なので直接書く

手順2: 子ファイル(各ページ)を作成

<!-- resources/views/pages/about.blade.php -->

@extends('layouts.app')

@section('title', '会社概要')

@section('content')
    <h2>会社概要</h2>
    <p>私たちは素晴らしい会社です。</p>
@endsection

各ディレクティブの意味:

  • @extends('layouts.app')
    → 「layouts/app.blade.phpを親として使います」という宣言
    → 必ず1行目に書く
  • @section('title', '会社概要')
    → 「親の@yield('title')に『会社概要』を入れます」
    → 短い内容は1行で書ける
  • @section('content') ... @endsection
    → 「親の@yield('content')にこの内容を入れます」
    → 複数行の内容は@endsectionで閉じる

🧪 実際に試してみよう

以下のコードをコピペして動作を確認してください。

1. ディレクトリ作成

mkdir -p resources/views/layouts
mkdir -p resources/views/pages

2. レイアウトファイル作成

resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'My Website')</title>
    <style>
        header { background: #667eea; color: white; padding: 20px; }
        nav { background: #333; padding: 10px; }
        nav a { color: white; margin: 0 15px; text-decoration: none; }
        main { max-width: 1200px; margin: 20px auto; padding: 20px; }
        footer { background: #333; color: white; text-align: center; padding: 20px; margin-top: 40px; }
    </style>
</head>
<body>
    <header>
        <h1>My Laravel Website</h1>
    </header>

    <nav>
        <a href="/layout/home">ホーム</a>
        <a href="/layout/about">会社概要</a>
        <a href="/layout/contact">お問い合わせ</a>
    </nav>

    <main>
        @yield('content')
    </main>

    <footer>
        <p>© 2025 My Website</p>
    </footer>
</body>
</html>

3. 各ページ作成

resources/views/pages/home.blade.php

@extends('layouts.app')

@section('title', 'ホーム')

@section('content')
    <h2>ホームページ</h2>
    <p>extendsとsectionを使ってレイアウトを継承しています。</p>
@endsection

resources/views/pages/about.blade.php

@extends('layouts.app')

@section('title', '会社概要')

@section('content')
    <h2>会社概要</h2>
    <p>会社名: 株式会社サンプル</p>
    <p>設立: 2020年1月1日</p>
@endsection

resources/views/pages/contact.blade.php

@extends('layouts.app')

@section('title', 'お問い合わせ')

@section('content')
    <h2>お問い合わせ</h2>
    <p>メール: contact@example.com</p>
    <p>電話: 03-1234-5678</p>
@endsection

4. ルート追加 (routes/web.php)

Route::get('/layout/home', function () {
    return view('pages.home');
});

Route::get('/layout/about', function () {
    return view('pages.about');
});

Route::get('/layout/contact', function () {
    return view('pages.contact');
});

5. ブラウザで確認

  • http://localhost/layout/home
  • http://localhost/layout/about
  • http://localhost/layout/contact

✅ ナビゲーションで3ページを移動してみてください。
ヘッダー・ナビ・フッターが全ページで共通なのが分かります。

💡 レイアウトを使うことで、ヘッダーやフッターを毎回書く必要がなくなります。 1箇所修正すれば、すべてのページに反映されます。

ディレクティブの順番ルール

@extends@section を書く順番にはルールがあります。

📋 順番のルール

✅ ルール1: @extends は必ず1行目

@extends('layouts.app')  <!-- 必ず1行目 -->

@section('title', '会社概要')
@section('content')
    <h2>会社概要</h2>
@endsection

→ Laravelが「このファイルは親ファイルを継承する」と最初に知る必要があるため

❌ これはエラーになる

@section('title', 'ホーム')
@extends('layouts.app')  <!-- エラー!extendsは1行目じゃないとダメ -->

✅ ルール2: @section の順番は自由

@extends('layouts.app')

<!-- この順番でもOK -->
@section('content')
    <h2>会社概要</h2>
@endsection

@section('title', '会社概要')
@extends('layouts.app')

<!-- この順番でもOK -->
@section('title', '会社概要')
@section('subtitle', 'About Us')
@section('content')
    <h2>会社概要</h2>
@endsection

@section は「この名前の穴に、この内容を入れる」という指示なので、順番は関係ない


💡 推奨される順番(読みやすさのため)

実務では、見やすさのために以下の順番で書くのが一般的です。

@extends('layouts.app')

@section('title', 'ページタイトル')  <!-- 短い内容を上に -->

@section('content')  <!-- 長い内容を下に -->
    <h2>メインコンテンツ</h2>
    <p>たくさんのHTML...</p>
@endsection

理由: 短い @section を上に、長い @section を下に書くと読みやすい

まとめ

ディレクティブ 順番 理由
@extends 必ず1行目 Laravelの仕様
@section 自由 どの順番でもOK
推奨順番 短い→長い 読みやすさのため

コンポーネントの利用

繰り返し使う部品(ボタン、カードなど)は、コンポーネントとして切り出せます。
実務では、デザインの統一性を保つために頻繁に使われる重要な機能です。

📁 重要: コンポーネントの配置場所

コンポーネントは必ず resources/views/components/ ディレクトリ内に作成します。

ファイル配置のルール

resources/views/
├── components/          ← ここにコンポーネントを作る
│   ├── button.blade.php      → <x-button>
│   ├── alert.blade.php       → <x-alert>
│   ├── card.blade.php        → <x-card>
│   └── form/                 ← サブディレクトリもOK
│       ├── input.blade.php   → <x-form.input>
│       └── select.blade.php  → <x-form.select>
└── pages/              ← 通常のビューファイル
    └── home.blade.php

命名規則

ファイルパス コンポーネント名
components/button.blade.php <x-button>
components/user-card.blade.php <x-user-card>
components/form/input.blade.php <x-form.input>

💡 ポイント: ファイル名がそのままコンポーネント名になります。
サブディレクトリを使う場合は、ドット(.)で区切ります。

基本的なコンポーネント

まず、最もシンプルな例から見ていきましょう。

<!-- resources/views/components/button.blade.php -->

<button style="padding: 8px 16px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer;">
    {{ $slot }}
</button>
<!-- 任意のビューファイルで使用 -->

<x-button>送信する</x-button>
<x-button>キャンセル</x-button>

<x-コンポーネント名> の形式で使用します。
{{ $slot }} には、タグの間の内容(「送信する」など)が入ります。

💡 $slot とは?

$slot は、コンポーネントタグの間に書かれた内容を受け取る特別な変数です。

<x-button>この部分が $slot に入る</x-button>

これにより、同じデザインのボタンを簡単に量産できます。

プロパティ付きコンポーネント(実務例)

コンポーネントに属性を渡して、動作を変えることができます。
実務でよく使う「アラート表示」の例を見てみましょう。

<!-- resources/views/components/alert.blade.php -->

@props(['type' => 'info'])

@php
    $styles = [
        'success' => 'background: #d1fae5; color: #065f46;',
        'error' => 'background: #fee2e2; color: #991b1b;',
        'warning' => 'background: #fef3c7; color: #92400e;',
        'info' => 'background: #dbeafe; color: #1e40af;',
    ];
    $icons = [
        'success' => '✅',
        'error' => '❌',
        'warning' => '⚠️',
        'info' => 'ℹ️',
    ];
@endphp

<div style="padding: 16px; border-radius: 4px; margin-bottom: 16px; {{ $styles[$type] ?? $styles['info'] }}">
    <span style="font-size: 1.2em; margin-right: 8px;">{{ $icons[$type] ?? $icons['info'] }}</span>
    <span>{{ $slot }}</span>
</div>
<!-- 使用例 -->

<x-alert type="success">
    保存しました!
</x-alert>

<x-alert type="error">
    エラーが発生しました
</x-alert>

<x-alert type="warning">
    この操作は取り消せません
</x-alert>

<x-alert>
    お知らせ:メンテナンスを実施します
</x-alert>

💡 @props(['type' => 'info']) で、デフォルト値を設定できます。
type を指定しない場合は、自動的に 'info' になります。

複数の属性を持つコンポーネント(カード例)

実務でよく使う「カード」コンポーネントの例です。

<!-- resources/views/components/card.blade.php -->

@props(['title', 'footer' => null])

<div style="border: 1px solid #ddd; border-radius: 8px; padding: 24px; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
    <!-- ヘッダー部分 -->
    <h3 style="font-size: 1.5em; font-weight: bold; margin-bottom: 16px;">{{ $title }}</h3>

    <!-- メインコンテンツ -->
    <div>
        {{ $slot }}
    </div>

    <!-- フッター部分(オプション) -->
    @if ($footer)
        <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #ddd;">
            {{ $footer }}
        </div>
    @endif
</div>
<!-- 使用例 -->

<x-card title="ユーザー情報">
    <p>名前: 太郎</p>
    <p>メール: taro@example.com</p>

    <x-slot:footer>
        <button>編集</button>
        <button>削除</button>
    </x-slot:footer>
</x-card>

💡 名前付きスロットとは?

コンポーネントに複数の領域を渡したいときに使います。

通常のスロット($slot)の問題

通常の $slot は1つしかないので、「本文(コンテンツ)」と「フッター」を別々に渡せません。

名前付きスロットの解決策

<x-card title="ユーザー情報">
    <!-- ここは $slot に入る(本文・メインコンテンツ) -->
    <p>名前: 太郎</p>
    <p>メール: taro@example.com</p>

    <!-- ここは $footer に入る(名前付きスロット) -->
    <x-slot:footer>
        <button>編集</button>
        <button>削除</button>
    </x-slot:footer>
</x-card>

まとめ:
$slot → 本文(メインコンテンツ)
<x-slot:footer> → フッター部分を別で指定
・コンポーネント側では {{ $footer }} のように変数名で受け取る

⚠️ 属性(footer=)では複雑なHTMLを渡せない

❌ これは無理(属性では文字列しか渡せない)

<x-card title="ユーザー情報" footer="<button>編集</button>">
    <p>名前: 太郎</p>
</x-card>

<!-- 属性 footer= では、文字列しか渡せない -->
<!-- HTMLタグは文字列として表示されてしまう -->

✅ これが正しい(名前付きスロットを使う)

<x-card title="ユーザー情報">
    <p>名前: 太郎</p>

    <x-slot:footer>
        <button>編集</button>
        <button>削除</button>
    </x-slot:footer>
</x-card>

<!-- 名前付きスロットなら、HTMLをそのまま渡せる -->

ルール:
title="文字列" → 文字列のみ渡せる(属性)
<x-slot:footer>HTML</x-slot:footer> → HTMLを渡せる(名前付きスロット)
ボタンなど複雑なHTMLを渡すなら、名前付きスロット必須

💡 コンポーネント側の定義

@props(['title', 'footer' => null])

この 'footer' => null は:
・属性 footer="文字列" でも受け取れる
・名前付きスロット <x-slot:footer> でも受け取れる
・何も渡されなければ null になる

ポイント:

  • title 属性で見出しを設定
  • $slot でメインコンテンツを挿入
  • <x-slot:footer> で名前付きスロットを使用(フッター部分を別管理)

実際に動かしてみよう - 完全なコード例

✅ 以下のコードをコピペして実際に試してください

📝 手順1: ボタンコンポーネントを作成

ファイル: resources/views/components/button.blade.php

@props(['type' => 'primary'])

@php
    $styles = [
        'primary' => 'background: #3b82f6; color: white;',
        'secondary' => 'background: #6b7280; color: white;',
        'danger' => 'background: #ef4444; color: white;',
        'success' => 'background: #10b981; color: white;',
    ];
    $buttonStyle = $styles[$type] ?? $styles['primary'];
@endphp

<button style="padding: 8px 16px; border-radius: 4px; border: none; cursor: pointer; {{ $buttonStyle }}">
    {{ $slot }}
</button>

📝 手順2: アラートコンポーネントを作成

ファイル: resources/views/components/alert.blade.php

@props(['type' => 'info'])

@php
    $styles = [
        'success' => 'background: #d1fae5; border-left: 4px solid #10b981; color: #065f46;',
        'error' => 'background: #fee2e2; border-left: 4px solid #ef4444; color: #991b1b;',
        'warning' => 'background: #fef3c7; border-left: 4px solid #f59e0b; color: #92400e;',
        'info' => 'background: #dbeafe; border-left: 4px solid #3b82f6; color: #1e40af;',
    ];
    $icons = [
        'success' => '✅',
        'error' => '❌',
        'warning' => '⚠️',
        'info' => 'ℹ️',
    ];
    $alertStyle = $styles[$type] ?? $styles['info'];
    $icon = $icons[$type] ?? $icons['info'];
@endphp

<div style="padding: 16px; margin-bottom: 16px; {{ $alertStyle }}">
    <span style="font-size: 1.2em; margin-right: 8px;">{{ $icon }}</span>
    <span>{{ $slot }}</span>
</div>

📝 手順3: カードコンポーネントを作成

ファイル: resources/views/components/card.blade.php

@props(['title', 'footer' => null])

<div style="border: 1px solid #ddd; border-radius: 8px; padding: 20px; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
    <h3 style="font-size: 1.5em; font-weight: bold; margin-bottom: 15px; color: #333;">
        {{ $title }}
    </h3>

    <div style="color: #666; line-height: 1.6;">
        {{ $slot }}
    </div>

    @if ($footer)
        <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee;">
            {{ $footer }}
        </div>
    @endif
</div>

📝 手順4: ルートを作成

ファイル: routes/web.php に追加

Route::get('/component-demo', function () {
    return view('component-demo');
});

📝 手順5: ビューファイルを作成

ファイル: resources/views/component-demo.blade.php

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>コンポーネントデモ</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
            background: #f5f5f5;
        }
        h1 { color: #333; }
        h2 { color: #666; margin-top: 30px; }
    </style>
</head>
<body>
    <h1>コンポーネントデモ</h1>

    <h2>1. ボタンコンポーネント</h2>
    <x-button type="primary">プライマリー</x-button>
    <x-button type="secondary">セカンダリー</x-button>
    <x-button type="danger">削除</x-button>
    <x-button type="success">保存</x-button>

    <h2>2. アラートコンポーネント</h2>
    <x-alert type="success">
        保存に成功しました!
    </x-alert>

    <x-alert type="error">
        エラーが発生しました。もう一度お試しください。
    </x-alert>

    <x-alert type="warning">
        この操作は取り消せません。本当に削除しますか?
    </x-alert>

    <x-alert type="info">
        お知らせ: システムメンテナンスを実施します。
    </x-alert>

    <h2>3. カードコンポーネント</h2>
    <x-card title="ユーザー情報">
        <p><strong>名前:</strong> 山田太郎</p>
        <p><strong>メール:</strong> yamada@example.com</p>
        <p><strong>登録日:</strong> 2024年1月1日</p>

        <x-slot:footer>
            <x-button type="primary">編集</x-button>
            <x-button type="danger">削除</x-button>
        </x-slot:footer>
    </x-card>

    <x-card title="商品情報">
        <p><strong>商品名:</strong> Laravel入門書</p>
        <p><strong>価格:</strong> 3,000円</p>
        <p><strong>在庫:</strong> 100冊</p>
    </x-card>
</body>
</html>

✅ 手順6: アクセスして確認

ブラウザで以下にアクセス:

http://localhost/component-demo

💡 コンポーネントの仕組み

  • @props - コンポーネントが受け取る属性を定義
  • $slot - タグの間に書かれた内容が入る
  • <x-slot:名前> - 名前付きスロット(複数の領域に分ける)
  • <x-コンポーネント名> - コンポーネントの呼び出し

実務でよく使うコンポーネント例

📦 実務でよく作るコンポーネント

  • button.blade.php - ボタン(primary、secondary、dangerなど)
  • alert.blade.php - 通知メッセージ(成功、エラー、警告)
  • card.blade.php - カードレイアウト
  • modal.blade.php - モーダルダイアログ
  • form-input.blade.php - フォーム入力フィールド
  • badge.blade.php - ステータスバッジ(公開中、下書きなど)
  • loading.blade.php - ローディングスピナー

💡 コンポーネントを使うことで:
・デザインの統一性が保たれる
・修正が1箇所で済む(全ページに反映される)
・コードの重複が減り、保守性が向上する

コンポーネントを使うメリット まとめ

  • 再利用性 - 同じコードを何度も書かなくて良い
  • 一貫性 - デザインやUIが統一される
  • 保守性 - 修正が1箇所で済む
  • 可読性 - <x-card> のように意味が分かりやすい

⚠️ 最初は「ちょっと難しい」と感じるかもしれませんが、慣れると手放せなくなります。
まずは簡単なボタンやアラートから試してみてください。

便利なBlade機能

コメント

{{-- これはBladeコメントです(HTMLに出力されません) --}}

<!-- これはHTMLコメント(HTMLに出力されます) -->

isset / empty チェック

@isset($name)
    <p>名前: {{ $name }}</p>
@endisset

@empty($users)
    <p>ユーザーがいません</p>
@endempty

null合体演算子(??)

<p>{{ $user->name ?? 'ゲスト' }}</p>

<!-- $user->name がnullまたは存在しなければ「ゲスト」 -->

⚠️ 超重要: ??、@isset、@empty の違い(実務でよく間違える)

📊 動作の違い一覧表

?? @isset @empty
'太郎' 表示 true false
''(空文字) 表示 true true
null 右側 false true
0 表示 true true
'0' 表示 true true
false 表示 true true
存在しない 右側 false true

💡 簡単な覚え方

  • ??@issetnull と 未定義だけを弾く
  • @empty空文字・0・null・false・未定義すべてを弾く

📝 実務での使い分け

パターン1: 0や空文字もそのまま表示したい

<!-- 価格が0円でも「0円」と表示したい -->
{{ $product->price ?? '未設定' }}

<!-- 0円の場合: 「0」と表示される ✅ -->
<!-- nullの場合: 「未設定」と表示される -->

パターン2: 空文字や0も「空」として扱いたい

<!-- 名前が空文字でも「名前未設定」と表示したい -->
@empty($user->name)
    <p>名前未設定</p>
@else
    <p>{{ $user->name }}</p>
@endempty

<!-- 空文字の場合: 「名前未設定」と表示される ✅ -->
<!-- nullの場合: 「名前未設定」と表示される -->

⚠️ よくある間違い:
{{ $price ?? 0 }} と書いて、「価格が0円の時もデフォルト値の0を表示する」と勘違いする。
実際は、$price0 なら 0 がそのまま表示される(右側の 0 は使われない)。

ループ変数

@foreach ($users as $user)
    <p>{{ $loop->iteration }}番目: {{ $user }}</p>

    @if ($loop->first)
        <p>最初の要素です</p>
    @endif

    @if ($loop->last)
        <p>最後の要素です</p>
    @endif
@endforeach

$loop 変数で、ループの情報にアクセスできます:

  • $loop->index - 0から始まるインデックス
  • $loop->iteration - 1から始まる回数
  • $loop->first - 最初の要素か
  • $loop->last - 最後の要素か
  • $loop->count - 要素の総数

フォーム関連のディレクティブ(予告)

フォーム送信では、以下のディレクティブを使います。
今は「こういうのがあるんだ」程度の理解でOKです。詳細は後の章で解説します。

<form method="POST" action="/users">
    @csrf
    <!-- ↑ CSRF保護(セキュリティ対策で必須) -->

    <input type="text" name="name" placeholder="名前">
    <button type="submit">送信</button>
</form>

@csrf とは?

CSRF(クロスサイトリクエストフォージェリ)という攻撃を防ぐためのトークンを自動生成します。
Laravelでは、POSTフォームには必ず @csrf を付ける必要があります。

💡 実務で使う例

PUT/DELETEメソッドの指定

HTMLフォームは GETPOST しか対応していませんが、 @method を使うことで、PUTやDELETEを擬似的に指定できます。

<!-- ユーザー情報の更新(PUT) -->
<form method="POST" action="/users/1">
    @csrf
    @method('PUT')
    <!-- 更新フォーム -->
</form>

<!-- ユーザーの削除(DELETE) -->
<form method="POST" action="/users/1">
    @csrf
    @method('DELETE')
    <button>削除</button>
</form>

認証状態の確認

@auth
    <p>ログイン中です</p>
    <a href="/logout">ログアウト</a>
@endauth

@guest
    <p>ログインしてください</p>
    <a href="/login">ログイン</a>
@endguest

⚠️ これらのディレクティブは、フォームや認証の章で詳しく学びます。
今は「Bladeにはこういう便利な機能がある」ということを知っておけば十分です。

まとめ

この章では、Laravelのビュー(View)について学びました。

重要なポイント

  • ビューは resources/views/.blade.php で作成
  • view('ファイル名') でビューを読み込む
  • {{ $変数 }} で安全に変数を表示(自動エスケープ)
  • @if@foreach など、Blade構文でロジックを記述
  • @extends@section でレイアウトを共通化
  • <x-コンポーネント> で再利用可能な部品を作成

次のステップ

次の章では、コントローラ(Controller)について学びます。

これまでルートファイル(routes/web.php)では、ビューを直接返すだけのシンプルな書き方をしてきました。

// routes/web.php(これまで学んだ方法)
Route::get('/about', function () {
    return view('about');
});

Route::get('/contact', function () {
    return view('contact');
});

Route::get('/users', function () {
    return view('users.index');
});
// ↓ ページが増えるとルートファイルが見づらくなる...

しかし、実際のアプリケーションでは、データベースからデータを取得したり、計算処理をしたりといった 複雑な処理が必要になります。
そのような処理を全部ルートファイルに書くと、コードが見づらくなってしまいます。

コントローラを使うと、処理を整理して管理しやすくなります:

// routes/web.php(コントローラ使用)
Route::get('/users', [UserController::class, 'index']);
Route::get('/users/{id}', [UserController::class, 'show']);
// ↓ スッキリ!複雑な処理はコントローラに書く

💡 次の章で、MVCアーキテクチャに沿った設計方法を習得しましょう。

補足:Bladeの仕組み(読まなくてもOK)

※ この説明は理解しなくても、Bladeは使えます。興味がある方だけお読みください。

.blade.php の正体

.blade.php ファイルは、実は普通のPHPファイルです。
ただし、Laravelの「Bladeテンプレートエンジン」が、実行前にPHPコードに変換してくれます。

💡 Bladeの処理の流れ

  1. 1. Bladeファイルを書く - {{ $name }}@if などを使う
  2. 2. 初回アクセス時にコンパイル - LaravelがPHPコードに変換
  3. 3. キャッシュに保存 - storage/framework/views/ に保存される
  4. 4. 次回以降は高速実行 - キャッシュされたPHPを直接実行

つまり、毎回変換するわけではなく、キャッシュを使うため高速です。

Blade構文がPHPに変換される例

実際にどうPHPに変換されているか見てみましょう。

例1: 変数の表示

<!-- Blade構文 -->
{{ $name }}
<!-- 変換後のPHP -->
<?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8', false); ?>

{{ }} は自動的に htmlspecialchars() でエスケープされます。
これにより、XSS(クロスサイトスクリプティング)攻撃を防げます。

例2: if文

<!-- Blade構文 -->
@if ($age >= 20)
    <p>成人です</p>
@else
    <p>未成年です</p>
@endif
<!-- 変換後のPHP -->
<?php if ($age >= 20): ?>
    <p>成人です</p>
<?php else: ?>
    <p>未成年です</p>
<?php endif; ?>

@if は、PHPの <?php if (): ?> に変換されます。
Bladeの方が見やすく、HTMLとの親和性が高いですね。

例3: foreach

<!-- Blade構文 -->
@foreach ($users as $user)
    <li>{{ $user }}</li>
@endforeach
<!-- 変換後のPHP -->
<?php foreach ($users as $user): ?>
    <li><?php echo htmlspecialchars($user, ENT_QUOTES, 'UTF-8', false); ?></li>
<?php endforeach; ?>

例4: @csrf

<!-- Blade構文 -->
@csrf
<!-- 変換後のPHP -->
<?php echo csrf_field(); ?>

<!-- さらに実行されると、以下のHTMLが生成される -->
<input type="hidden" name="_token" value="ランダムなトークン文字列">

@csrf はたった1行ですが、内部でトークンを生成してhiddenフィールドを出力しています。

キャッシュファイルを確認してみよう

実際に変換されたPHPファイルは、storage/framework/views/ に保存されています。

# Laravelプロジェクトのルートで実行
ls storage/framework/views/

# 結果例
# a3b4c5d6e7f8g9h0.php
# b1c2d3e4f5g6h7i8.php
# ...

このファイルを開くと、Bladeが変換した「生のPHP」を見ることができます。

💡 キャッシュのクリア方法

Bladeファイルを編集しても反映されない場合、キャッシュをクリアします:

php artisan view:clear

開発中は自動的にキャッシュが更新されるので、通常は不要です。
ただし、本番環境では手動でクリアが必要な場合があります。

なぜBladeを使うのか?

普通のPHPでも書けるのに、なぜBladeを使うのでしょうか?

📋 Bladeのメリット

1. コードが読みやすい

<!-- ❌ 普通のPHP(読みにくい) -->
<?php if ($user->isAdmin()): ?>
    <p><?php echo htmlspecialchars($user->name, ENT_QUOTES, 'UTF-8'); ?></p>
<?php endif; ?>

<!-- ✅ Blade(読みやすい) -->
@if ($user->isAdmin())
    <p>{{ $user->name }}</p>
@endif

2. 自動エスケープでセキュア

{{ }} は自動的にエスケープされるため、XSS攻撃のリスクが減ります。
普通のPHPだと、htmlspecialchars() を毎回書き忘れる危険があります。

3. 便利なディレクティブ

@csrf@auth@foreach など、よく使う処理が短く書けます。

4. レイアウト継承が簡単

@extends@section で、共通レイアウトを簡単に作れます。
普通のPHPだと、includerequire で複雑になりがちです。

初学者が覚えるべきこと

「Bladeは最終的にPHPに変換される」

これだけ覚えておけばOKです。
内部の詳しい仕組みは、Laravelに慣れてから学ぶ中級者向けの知識です。

📚 詳しく知りたい方は、Laravel公式ドキュメントの「Blade Templates」を参照してください。
https://laravel.com/docs/12.x/blade