【第四弾】Laravel入門資料
- Windows環境構築
- Mac環境構築
- 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/homehttp://localhost/layout/abouthttp://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 |
💡 簡単な覚え方
- ?? と @isset → null と 未定義だけを弾く
- @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を表示する」と勘違いする。
実際は、$price が 0 なら 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フォームは GET と POST しか対応していませんが、
@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. Bladeファイルを書く -
{{ $name }}や@ifなどを使う - 2. 初回アクセス時にコンパイル - LaravelがPHPコードに変換
- 3. キャッシュに保存 -
storage/framework/views/に保存される - 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だと、include や require で複雑になりがちです。
初学者が覚えるべきこと
「Bladeは最終的にPHPに変換される」
これだけ覚えておけばOKです。
内部の詳しい仕組みは、Laravelに慣れてから学ぶ中級者向けの知識です。
📚 詳しく知りたい方は、Laravel公式ドキュメントの「Blade Templates」を参照してください。
https://laravel.com/docs/12.x/blade