404 Not Found

404 Not Found


nginx

LaravelビューとBladeテンプレートエンジン

BladeはLaravelの「レイアウトエンジン」です。データがユーザーに見えるページに変換され, レイアウト, コンポーネント, 継承によって繰り返しのページコードを排除します。

1. 学ぶ内容


2. フロントエンド開発者のリアルストーリー

(1) ペインポイント:毎ページでナビゲーションバーを繰り返し書く必要がある

CharlieはShopMetricsのために10ページを作成し, 60行のナビゲーションバーHTMLを各ページに手動でコピーしていました。Bobが「料金設定」リンクをナビゲーションバーからドロップダウンメニューに移動するよう依頼したとき, Charlieは10ファイルすべてを1つずつ編集しなければなりませんでした。7番目のファイルでクラスを見落とし, 本番サイトでナビゲーションバーのスタイルが不統一になり, Aliceから3件の苦情が来ました。

(2) Bladeレイアウトのソリューション

Bladeは@extendsでレイアウトを継承し, @sectionでコンテンツを埋めます。ナビゲーションバーは1度だけ定義され, サイト全体で自動的に同期されます。

PHP
// resources/views/layouts/app.blade.php — レイアウトを1度だけ定義
<body>
    <nav>...</nav>  <!-- ナビゲーションは全ページに表示 -->
    @yield('content')  <!-- 各ページがこのスロットを埋める -->
</body>

// resources/views/shops/index.blade.php — 継承して埋める
@extends('layouts.app')
@section('content')
    <h1>All Shops</h1>
@endsection

(3) 成果

CharlieがBladeレイアウトを実装した後, ナビゲーションバーは単一ファイルの保守のみで済むようになりました。Bobの変更要求は「10ファイル修正」から「1ファイル修正」になり, 見落としはゼロになりました。


3. Bladeレイアウト継承

(1) レイアウトの定義

HTML
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'ShopMetrics')</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    @include('partials.navbar')
    <main class="container mx-auto">
        @yield('content')
    </main>
    @include('partials.footer')
</body>
</html>

(2) レイアウトの継承

HTML
<!-- resources/views/shops/index.blade.php -->
@extends('layouts.app')

@section('title', 'All Shops')

@section('content')
    <div class="grid grid-cols-3 gap-4">
        @foreach($shops as $shop)
            <div class="card">{{ $shop->name }}</div>
        @endforeach
    </div>
@endsection
コマンド 機能 例え
@extends 継承するレイアウトを指定 テンプレートを選択
@section コンテンツブロックを定義 テンプレートの空欄を埋める
@yield コンテンツブロックを出力 テンプレート内のプレースホルダー
@include サブビューをインポート 共通スニペットの挿入

(1) ▶ サンプル:ShopMetricsの2カラムレイアウト

HTML
<!-- resources/views/layouts/dashboard.blade.php -->
@extends('layouts.app')

@section('content')
    <div class="flex">
        @include('partials.sidebar')
        <div class="flex-1 p-6">
            @yield('dashboard-content')
        </div>
    </div>
@endsection

<!-- resources/views/dashboard/analytics.blade.php -->
@extends('layouts.dashboard')

@section('title', 'Analytics Dashboard')

@section('dashboard-content')
    <h1>Analytics Overview</h1>
    <div id="chart"></div>
@endsection

出力:

TEXT
// 実行成功

4. 制御構造

(1) 条件文

PHP
@if($shop->is_active)
    <span class="badge-green">Active</span>
@elseif($shop->is_suspended)
    <span class="badge-yellow">Suspended</span>
@else
    <span class="badge-gray">Inactive</span>
@endif

@auth
    <a href="{{ route('dashboard') }}">Dashboard</a>
@endauth

@guest
    <a href="{{ route('login') }}">Login</a>
@endguest

(2) ループ文

PHP
@foreach($shops as $shop)
    <tr>
        <td>{{ $shop->name }}</td>
        <td>{{ $shop->revenue }}</td>
    </tr>
    @if($loop->last)
        </tbody></table>
    @endif
@endforeach

@forelse($orders as $order)
    <li>{{ $order->total }}</li>
@empty
    <li>注文はまだありません。</li>
@endforelse
ループ変数 説明
$loop->index 現在のインデックス (0始まり)
$loop->iteration 現在の周回 (1始まり)
$loop->first 最初の要素かどうか
$loop->last 最後の要素かどうか
$loop->count 合計数

(1) ▶ サンプル:ShopMetricsダッシュボードの条件付きレンダリング

HTML
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')

@section('dashboard-content')
    @auth
        <h1>ようこそ, {{ auth()->user()->name }}さん!</h1>
    @endauth

    @if($shops->count() > 0)
        <div class="grid grid-cols-3 gap-4">
            @foreach($shops as $shop)
                <div class="card {{ $loop->first ? 'border-blue-500' : '' }}">
                    <h3>{{ $shop->name }}</h3>
                    <p>売上: ${{ number_format($shop->revenue, 2) }}</p>
                </div>
            @endforeach
        </div>
    @else
        <p>店舗はまだありません。<a href="{{ route('shops.create') }}">作成しましょう!</a></p>
    @endif
@endsection

出力:

TEXT
// 実行成功

5. Bladeコンポーネント

(1) 匿名コンポーネント

HTML
<!-- resources/views/components/shop-card.blade.php -->
@props(['shop', 'highlight' => false])

<div class="card {{ $highlight ? 'border-gold' : '' }}">
    <h3>{{ $shop->name }}</h3>
    <p>{{ $shop->description }}</p>
    {{ $slot }}
</div>

<!-- 使用方法 -->
<x-shop-card :shop="$shop" :highlight="true">
    <p>今週のおすすめ!</p>
</x-shop-card>

(2) クラスコンポーネント

BASH
php artisan make:component Alert
# 作成: app/View/Components/Alert.php
# および: resources/views/components/alert.blade.php
PHP
// app/View/Components/Alert.php
class Alert extends Component
{
    public function __construct(
        public string $type = 'info',
        public ?string $message = null,
    ) {}

    public function render(): View
    {
        return view('components.alert');
    }
}
HTML
<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }}">
    {{ $message ?? $slot }}
</div>

<!-- 使用方法 -->
<x-alert type="success" message="Shop created!" />
<x-alert type="warning">設定を確認してください。</x-alert>
項目 匿名 クラス
ファイル Bladeテンプレートのみ PHPクラス + Bladeテンプレート
ロジック なし/最小限 ビジネスロジックを含む場合あり
Props @props()宣言 コンストラクタパラメータ
適している シンプルな表示コンポーネント ロジックが必要なコンポーネント

(1) ▶ サンプル:ShopMetrics統計カードコンポーネント

HTML
<!-- resources/views/components/stat-card.blade.php -->
@props(['label', 'value', 'icon', 'trend' => null])

<div class="bg-white rounded-lg shadow p-6">
    <div class="flex items-center justify-between">
        <div>
            <p class="text-gray-500 text-sm">{{ $label }}</p>
            <p class="text-2xl font-bold">{{ $value }}</p>
            @if($trend)
                <p class="text-sm {{ $trend > 0 ? 'text-green-500' : 'text-red-500' }}">
                    {{ $trend > 0 ? '+' : '' }}{{ $trend }}%
                </p>
            @endif
        </div>
        <span class="text-3xl">{{ $icon }}</span>
    </div>
</div>

<!-- ダッシュボードでの使用 -->
<x-stat-card label="Total Revenue" value="$12,450" icon="$" :trend="15.3" />
<x-stat-card label="Active Shops" value="23" icon="#" :trend="-2.1" />

出力:

TEXT
// 実行成功

6. View Composer

View Composerはビューに共有データを自動的に注入し, 各コントローラーメソッドで繰り返し渡す必要をなくします。

PHP
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    // 全ダッシュボードビューにテナント情報を共有
    View::composer('dashboard.*', function ($view) {
        $view->with('tenant', Tenant::current());
    });

    // 特定のビューにナビゲーションデータを共有
    View::composer(['layouts.app', 'shops.*'], NavigationComposer::class);
}
タイプ 実行タイミング 目的
View::composer() ビューがレンダリングされるたび データを動的に計算
View::creator() ビューのインスタンス化時 より早くデータをバインド
View::share() 全ビュー グローバル定数

(1) ▶ サンプル:ShopMetricsナビゲーションView Composer

PHP
// app/View/Composers/NavigationComposer.php
class NavigationComposer
{
    public function compose(View $view): void
    {
        $view->with([
            'navShops' => auth()->check()
                ? auth()->user()->shops()->take(5)->get()
                : collect(),
            'navNotifications' => auth()->check()
                ? auth()->user()->unreadNotifications()->count()
                : 0,
        ]);
    }
}

// AppServiceProviderで登録
View::composer('layouts.app', NavigationComposer::class);

出力:

TEXT
// 実行成功

7. フロントエンドアセット:Vite

Laravel 11はデフォルトでViteを使用してフロントエンドアセットをコンパイルします。

(1) Tailwind CSSとAlpine.jsのインストール

BASH
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install alpinejs

(2) Viteの設定

JAVASCRIPT
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
    ],
});

(3) Bladeでの読み込み

HTML
<!-- レイアウトのhead内 -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
コマンド 機能
npm run dev Vite開発サーバーを起動 (ホットリロード)
npm run build 本番用アセットをコンパイル (圧縮)

(1) ▶ サンプル:Alpine.jsインタラクション付きShopMetricsナビゲーション

HTML
<!-- resources/views/partials/navbar.blade.php -->
<nav class="bg-white shadow" x-data="{ open: false }">
    <div class="container mx-auto px-4">
        <div class="flex justify-between h-16">
            <a href="{{ route('home') }}" class="font-bold text-xl">ShopMetrics</a>

            <div class="flex items-center">
                @auth
                    <div class="relative" @click="open = !open">
                        <button class="flex items-center">
                            {{ auth()->user()->name }}
                            @if($navNotifications > 0)
                                <span class="badge-red">{{ $navNotifications }}</span>
                            @endif
                        </button>
                        <div x-show="open" x-transition class="dropdown">
                            <a href="{{ route('dashboard') }}">Dashboard</a>
                            <a href="{{ route('shops.index') }}">Shops</a>
                            <form action="{{ route('logout') }}" method="POST">
                                @csrf
                                <button type="submit">Logout</button>
                            </form>
                        </div>
                    </div>
                @else
                    <a href="{{ route('login') }}">Login</a>
                @endauth
            </div>
        </div>
    </div>
</nav>

出力:

TEXT
// 実行成功

8. 総合例:ShopMetricsダッシュボードページ

HTML
<!-- ============================================
     総合: ShopMetricsダッシュボードページ
     内容: レイアウト継承, コンポーネント, ディレクティブ, Alpine.js
     ============================================ -->

<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')

@section('title', 'Dashboard — ShopMetrics')

@section('dashboard-content')
    <div class="space-y-6" x-data="{ period: '7d' }">
        <!-- 期間セレクター -->
        <div class="flex gap-2">
            <button @click="period = '7d'"
                    :class="period === '7d' ? 'btn-primary' : 'btn-secondary'">7日間</button>
            <button @click="period = '30d'"
                    :class="period === '30d' ? 'btn-primary' : 'btn-secondary'">30日間</button>
            <button @click="period = '90d'"
                    :class="period === '90d' ? 'btn-primary' : 'btn-secondary'">90日間</button>
        </div>

        <!-- 統計カード -->
        <div class="grid grid-cols-4 gap-4">
            <x-stat-card label="Total Revenue" value="${{ number_format($totalRevenue, 0) }}" icon="$" :trend="$revenueTrend" />
            <x-stat-card label="Orders" value="{{ number_format($orderCount) }}" icon="#" :trend="$orderTrend" />
            <x-stat-card label="Active Shops" value="{{ $activeShops }}" icon="#" :trend="$shopTrend" />
            <x-stat-card label="Conversion Rate" value="{{ number_format($conversionRate, 1) }}%" icon="%" :trend="$conversionTrend" />
        </div>

        <!-- 最近の注文 -->
        <div class="bg-white rounded-lg shadow">
            <h2 class="p-4 border-b font-semibold">最近の注文</h2>
            @forelse($recentOrders as $order)
                <div class="p-4 border-b flex justify-between">
                    <span>{{ $order->product_name }}</span>
                    <span>${{ number_format($order->total, 2) }}</span>
                </div>
            @empty
                <p class="p-4 text-gray-500">この期間の注文はありません。</p>
            @endforelse
        </div>
    </div>
@endsection

❓ よくある質問

Q Bladeの{{ }}と{!! !!}の違いは何ですか?
A {{ }}はHTMLを自動的にエスケープし (XSSを防ぐ), {!! !!}はエスケープせずに生のHTMLを出力します。{!! !!}はリッチテキストエディタの出力など, コンテンツが信頼できる場合のみ使用してください。
Q @includeと@componentの違いは何ですか?
A @includeはpropの分離なしでビュースニペットを単純にインクルードします。@componentは明示的なprop宣言とスロットメカニズムを持ち, 再利用可能なUIコンポーネントに適しています。シンプルなスニペットには@include, 独立したコンポーネントには@componentを使用してください。
Q BladeコンポーネントとVueコンポーネントの違いは何ですか?
A Bladeコンポーネントはサーバー側でレンダリングされ静的HTMLを生成します。Vueコンポーネントはクライアント側でレンダリングされリアクティブなデータバインディングをサポートします。Laravelプロジェクトでは通常, ページ構造にBladeを使用し, インタラクティビティにAlpine.jsやVueを使用します。
Q ViteとMixの違いは何ですか?
A ViteはLaravel 9以降のデフォルトのフロントエンドビルドツールです。ESモジュール開発サーバーを使用し, 起動が速くホットリロードをサポートします。Mix (Laravel Mix)はWebpackベースで設定は簡単ですが遅いです。新しいプロジェクトにはViteを使用してください。
Q Bladeで変数をデバッグするにはどうすればよいですか?
A {{ dd($variable) }}を使用するとダンプしてページを一時停止できます。またはLaravel Debugbar拡張をインストールして, すべての変数, SQLクエリ, メモリ使用量を確認できます。
Q View Composerとコントローラーのデータ渡しの違いは何ですか?
A コントローラーはview('xxx', ['key' => $value])で明示的にデータを渡し, そのビュー固有のデータに適しています。View Composerは共有データを自動的に注入し, 複数のビューで必要なデータ (ナビゲーションバーの内容や通知数など)に適しています。

📖 まとめ


📝 練習問題

  1. 基本演習 (⭐):ShopMetricsのlayouts/app.blade.phpレイアウトファイルを作成し, ナビゲーションバーとフッターを含め, このレイアウトを継承するホームページhome/index.blade.phpを作成してコンテンツを埋めてください。

  2. 応用演習 (⭐⭐):type (success/warning/error)とスロットコンテンツをサポートする<x-alert> Bladeコンポーネントを作成し, ShopMetricsで作成成功やバリデーション失敗のシナリオで使用してください。

  3. チャレンジ (⭐⭐⭐):Alpine.jsを使って折りたたみ可能なサイドバーコンポーネントを実装し, ダッシュボードレイアウトに統合し, モバイルデバイスでの自動折りたたみをサポートし, 状態をlocalStorageに保存してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%