1. HOME
  2. ブログ
  3. エンジニアリング
  4. LaravelのBladeを徹底解説

BLOG

ブログ

エンジニアリング

LaravelのBladeを徹底解説

本記事では、LaravelのBladeの使い方をわかりやすく解説します。

チュートリアルの全体像

本チュートリアルを通じて学べる内容は以下のようになります。

  1. Laravelの開発環境セットアップ
  2. Laravel Breezeで認証機能を導入
  3. Laravelのルーティングを徹底解説
  4. Laravelのコントーラーを徹底解説
  5. Laravelのブレイドを使ってみよう (now🐾)
  6. マイグレーションの仕組みを解説
  7. シーディングを使ってみよう
  8. Eloquentの基本と使い方を徹底解説
  9. 画像アップロードを実装する方法
  10. バリデーションを実装してみよう
  11. Laravelで認可処理を実装しよう

動画で学びたい方はこちらから!

解説

Laravelのブレイド(Blade)とは?

ブレイド(Blade)とは、Laravelでビューを作るために用意されたテンプレートエンジン です。
要するに見た目を作る手助けをしてくれる機能です。

ここでテンプレートエンジンという言葉に馴染みのない方に、少しテンプレートエンジンについて説明します。
すでに知ってるよ!って人はブレイド(Blade)の使い方に進んでください!

テンプレートエンジンのイメージ

テンプレートエンジンは、データをテンプレート(ひな型)に流し込んで、最終的にユーザに見せる画面を作り出す工場のような役割をします。

例えばブログ投稿システムの場合、タイトルや本文、コメント欄などのレイアウトはどの投稿でも共通なのでテンプレート化できますが、記事によってタイトルや本文、コメント欄の中身(データ)は違います。そこで、テンプレートエンジンがテンプレートとデータを結合して、ユーザに見せるべき画面を作ってくれるというわけです。

テンプレートエンジンについて少しはイメージが湧いたと思いますので、実際に使い方を学びながら理解を深めていきましょう!

実践

ロゴ変更

assets/logo.png を public/ 配下に移動します。

resources/views/components/application-logo.blade.php

の内容を以下のように変更します。

<img src="/inusta.png" width="64" />

ナビゲーションメニュー追加

/resources/views/layouts/navigation.blade.php

<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
    <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
        {{ __('Dashboard') }}
    </x-nav-link>
    <x-nav-link :href="route('posts.create')" :active="request()->routeIs('posts.create')">
        新規作成
    </x-nav-link>
    <x-nav-link :href="route('posts.index')" :active="request()->routeIs('posts.index')">
        新着投稿
    </x-nav-link>
    <x-nav-link :href="route('users.index')" :active="request()->routeIs('users.index')">
        オーナー
    </x-nav-link>
</div>

レイアウト作成

ダッシュボードを作成

app/Http/Controllers/DashboardController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DashboardController extends Controller
{
    public function index(Request $request)
    {
        $user = (object) [
            'name' => 'user+1',
            'icon' => '/dogs/dog_8.jpg',
            'description' => 'ボクサー🐕とフレンチブル🐶2匹飼っています✨
愛犬たちの日常を投稿していきます🐾',
        ];
        $posts = collect([
            (object) [
                'id' => 1,
                'image_path' => '/dogs/dog_1.jpg',
                'caption' => 'test',
            ],
            (object) [
                'id' => 2,
                'image_path' => '/dogs/dog_2.jpg',
                'caption' => 'test',
            ],
            (object) [
                'id' => 3,
                'image_path' => '/dogs/dog_3.jpg',
                'caption' => 'test',
            ],
            (object) [
                'id' => 4,
                'image_path' => '/dogs/dog_4.jpg',
                'caption' => 'test',
            ],
        ]);

        return view('dashboard', ['user' => $user, 'posts' => $posts]);
    }
}

resources/views/dashboard.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="max-w-5xl mx-auto">
        <div class="bg-white p-4 flex mt-8">
            @if($user->icon)
                <img class="block h-24 rounded-full aspect-square object-cover" src="{{ asset($user->icon) }}">
            @endif
            <div class="pl-4">
                <div>
                    <p class="text-lg text-black font-semibold">
                        {{ $user->name }}
                    </p>
                    <p class="font-medium whitespace-pre-wrap">{{ $user->description }}</p>
                    <div class="flex mt-4">
                        <p class="text-sm text-black font-semibold">
                            投稿{{ $posts->count() }}件
                        </p>
                        <a href="{{route('profile.edit')}}" class="ml-2 px-2 text-sm text-black font-semibold border rounded">プロフィールを編集</a>
                    </div>
                </div>
            </div>
        </div>

        <div class="py-8">
            <div class="bg-white">
                <div class="grid grid-cols-3 gap-1">
                    @foreach ($posts as $post)
                        <a href="/posts/{{$post->id}}/edit">
                            <img class="aspect-square w-full object-cover" src="{{ asset($post->image_path) }}" />
                        </a>
                    @endforeach
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

投稿追加を作成

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            投稿作成
        </h2>
    </x-slot>
    <div class="max-w-5xl mx-auto">
        <form class="bg-white p-4 mt-8">
            <x-input-label value="画像" class="mb-2"/>
            <input type="file" class="w-full text-gray-500 font-medium bg-gray-100 file:border-0 file:py-2.5 file:px-4 file:mr-4 file:bg-gray-800 file:text-white rounded" />
            <x-input-label value="キャプション" class="mt-4 mb-2"/>
            <textarea rows="8" placeholder="キャプションを入力してください..." class="p-2.5 w-full rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500"></textarea>
            <x-primary-button type="submit" class="mt-4">公開</x-primary-button>
        </form>
    </div>
</x-app-layout>

コンポーネント化

  • ファイル選択ボックス
  • テキストエリア

を共通化します。

以下のコマンドでコンポーネントファイルを作成しましょう。

php artisan make:component file-input --view
php artisan make:component textarea --view

/resources/views/components配下にファイルが作成されます。

.
|-- resources
|    `-- views
|      `-- components
|          |-- ...
|          |-- file-input.blade.php
|          |-- ...
|          `-- textarea.blade.php
|

file-input.blade.php

<input type="file" {!! $attributes->merge(['class' => 'w-full text-gray-500 font-medium bg-gray-100 file:border-0 file:py-2.5 file:px-4 file:mr-4 file:bg-gray-800 file:text-white rounded']) !!} />

textarea.blade.php

<textarea {!! $attributes->merge(['class' => 'p-2.5 w-full rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500']) !!}>{{ $slot }}</textarea>

posts/create.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            投稿作成
        </h2>
    </x-slot>
    <div class="max-w-5xl mx-auto">
        <form class="bg-white p-4 mt-8">
            <x-input-label value="画像" class="mb-2"/>
            <x-file-input/>
            <x-input-label value="キャプション" class="mt-4 mb-2"/>
            <x-textarea rows="8" placeholder="キャプションを入力してください..."/>
            <x-primary-button type="submit" class="mt-4">公開</x-primary-button>
        </form>
    </div>
</x-app-layout>

投稿編集を作成

app/Http/Controllers/PostController.php

  public function edit(string $id)
  {
      $post = (object) [
          'id' => 1,
          'image_path' => '/dogs/dog_1.jpg',
          'caption' => 'test',
          'user' => (object) [
              'name' => 'r.doi',
              'icon' => '/dogs/dog_8.jpg',
              'description' => 'ボクサー🐕とフレンチブル🐶2匹飼っています✨
  愛犬たちの日常を投稿していきます🐾',
          ],
      ];

      return view('posts.edit', ['post' => $post]);
  }

resources/views/posts/edit.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            投稿編集
        </h2>
    </x-slot>
    <div class="max-w-5xl mx-auto">
        <div class="grid md:grid-cols-2 grid-cols-1 gap-1 bg-white mt-8">
            <img class="aspect-square w-full object-cover" src="{{ asset($post->image_path) }}" />
            <div class="p-2">
                <h3 class="font-semibold mb-2">オーナー</h3>
                <div class="bg-white flex border p-2 rounded">
                    @if($post->user->icon)
                        <img class="block size-12 rounded-full aspect-square object-cover" src="{{ asset($post->user->icon) }}">
                    @endif
                    <div class="pl-4">
                        <div>
                            <p class="text-lg text-black font-semibold">
                                {{ $post->user->name }}
                            </p>
                            <p class="text-sm font-medium whitespace-pre-wrap">{{ $post->user->description }}</p>
                        </div>
                    </div>
                </div>
                <form method="POST" action="/posts/{{$post->id}}">
                    @csrf
                    @method('PUT')
                    <h3 class="font-semibold mt-2">キャプション</h3>
                    <x-textarea name="caption" rows="8" class="mt-2">{{ $post->caption }}</x-textarea>
                    <div class="flex items-center mt-4">
                        <x-primary-button type="submit">更新</x-primary-button>
                        <a href="/posts/{{$post->id}}" class="text-xs border p-2 rounded ml-4">
                            投稿画面へ
                        </a>
                    </div>
                </form>
                <form method="POST" action="/posts/{{$post->id}}">
                    @csrf
                    @method('DELETE')
                    <x-secondary-button type="submit" class="mt-4">
                        削除
                    </x-secondary-button>
                </form>
            </div>
        </div>
    </div>
</x-app-layout>

投稿一覧を作成

app/Http/Controllers/PostController.php

public function index()
{
    $posts = collect([
        (object) [
            'id' => 1,
            'image_path' => '/dogs/dog_1.jpg',
            'caption' => 'test',
            'created_at' => today(),
            'user' => (object) [
                'name' => 't.tanaka',
                'icon' => '/dogs/dog_1.jpg',
                'description' => '',
            ],
        ],
        (object) [
            'id' => 2,
            'image_path' => '/dogs/dog_2.jpg',
            'caption' => 'test',
            'created_at' => today(),
            'user' => (object) [
                'name' => 't.tanaka',
                'icon' => '/dogs/dog_1.jpg',
                'description' => '',
            ],
        ],
        (object) [
            'id' => 3,
            'image_path' => '/dogs/dog_3.jpg',
            'caption' => 'test',
            'created_at' => today(),
            'user' => (object) [
                'name' => 't.tanaka',
                'icon' => '/dogs/dog_1.jpg',
                'description' => '',
            ],
        ],
        (object) [
            'id' => 4,
            'image_path' => '/dogs/dog_4.jpg',
            'caption' => 'test',
            'created_at' => today(),
            'user' => (object) [
                'name' => 't.tanaka',
                'icon' => '/dogs/dog_1.jpg',
                'description' => '',
            ],
        ],
    ]);

    return view('posts.index', ['posts' => $posts]);
}

resources/views/posts/index.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            新着投稿
        </h2>
    </x-slot>

    <div class="max-w-5xl mx-auto">
        <div class="bg-white mt-8">
            <div class="grid grid-cols-3 gap-1">
                @foreach ($posts as $post)
                    <a href="/posts/{{$post->id}}">
                        <img class="aspect-square w-full object-cover" src="{{ asset($post->image_path) }}" />
                        <div class="flex justify-between items-center p-1 border">
                            <div class="flex items-center">
                                @if($post->user->icon)
                                    <img class="block size-6 rounded-full aspect-square object-cover" src="{{ asset($post->user->icon) }}">
                                @endif
                                <p class="text-sm text-black font-semibold ml-2">
                                    {{ $post->user->name }}
                                </p>
                            </div>
                            <p class="text-gray-500 text-xs">{{ $post->created_at->format('Y/m/d') }}</p>
                        </div>
                    </a>
                @endforeach
            </div>
        </div>
    </div>
</x-app-layout>

投稿詳細を作成

app/Http/Controllers/PostController.php

public function show(string $id)
{
    $post = (object) [
        'id' => 1,
        'image_path' => '/dogs/dog_1.jpg',
        'caption' => 'test',
        'created_at' => today(),
        'user' => (object) [
            'id' => 1,
            'name' => 't.tanaka',
            'icon' => '/dogs/dog_1.jpg',
            'description' => 'はじめまして。よろしくお願いします。',
        ],
        'comments' => collect(
            [
                (object) [
                    'text' => 'こんにちは',
                    'created_at' => today(),
                    'user' => (object) [
                        'id' => 2,
                        'name' => 'suzuki',
                        'icon' => '/dogs/dog_2.jpg',
                        'description' => '',
                    ],
                ],
                (object) [
                    'text' => 'こんにちは',
                    'created_at' => today(),
                    'user' => (object) [
                        'id' => 2,
                        'name' => 'suzuki',
                        'icon' => '/dogs/dog_2.jpg',
                        'description' => '',
                    ],
                ],
            ]
        ),
    ];

    return view('posts.show', ['post' => $post]);
}

resources/views/posts/show.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            投稿
        </h2>
    </x-slot>
    <div class="max-w-5xl mx-auto">
        <div class="grid md:grid-cols-2 grid-cols-1 gap-1 bg-white mt-8">
            <img class="aspect-square w-full object-cover" src="{{ asset($post->image_path) }}" />
            <div class="p-2">
                <div class="flex justify-between items-center">
                    <h3 class="font-semibold">オーナー</h3>
                    <div class="text-gray-500 text-xs">公開日: {{ $post->created_at->format('Y/m/d H:i')}}</div>
                </div>
                <a href="/users{{$post->user->id}}">
                    <div class="bg-white flex border p-2 rounded mt-2">
                        @if($post->user->icon)
                            <img class="block size-12 rounded-full aspect-square object-cover" src="{{ asset($post->user->icon) }}">
                        @endif
                        <div class="pl-4">
                            <div>
                                <p class="text-lg text-black font-semibold">
                                    {{ $post->user->name }}
                                </p>
                                <p class="text-sm font-medium whitespace-pre-wrap">{{ $post->user->description }}</p>
                            </div>
                        </div>
                    </div>
                </a>    
                <h3 class="font-semibold mt-2">キャプション</h3>
                <p class="whitespace-pre-wrap py-2">{{ $post->caption }}</p>
                <h3 class="font-semibold mt-2">コメント{{ $post->comments->count() }}件</h3>
                <div class="max-h-96 overflow-y-scroll">
                    @foreach($post->comments as $comment)
                        <div class="py-2 border-b">
                            <div class="flex items-center mb-1">
                                <img src="{{$comment->user->icon}}" class="size-6 rounded-full">
                                <div class="text-sm font-medium text-gray-800 ml-1">{{$comment->user->name}}</div>
                                <div class="text-gray-500 text-xs ml-1">{{$comment->created_at->format('Y/m/d H:i')}}</div>
                            </div>
                            <p class="text-sm">{{$comment->text}}</p>
                        </div>
                    @endforeach
                </div>
                <form method="POST" action="/posts/{{$post->id}}/comments">
                    @csrf
                    <div class="flex mt-2">
                        <x-text-input name="text" class="mr-2 text-sm grow" placeholder="コメントを追加..."/>
                        <x-primary-button type="submit">送信</x-primary-button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</x-app-layout>

オーナー一覧を作成

app/Http/Controllers/UserController.php

public function index()
{
    $users = collect([
        (object) [
            'id' => 1,
            'name' => 't.tanaka',
            'icon' => '/dogs/dog_1.jpg',
            'description' => '',
            'posts' => collect(
                (object) [
                    'image_path' => '/dogs/dog_1.jpg',
                    'caption' => 'test',
                ],
            ),
        ],
        (object) [
            'id' => 2,
            'name' => 't.tanaka',
            'icon' => '/dogs/dog_2.jpg',
            'description' => '',
            'posts' => collect(
                (object) [
                    'image_path' => '/dogs/dog_1.jpg',
                    'caption' => 'test',
                ],
            ),
        ],
        (object) [
            'id' => 3,
            'name' => 't.tanaka',
            'icon' => '/dogs/dog_3.jpg',
            'description' => '',
            'posts' => collect(
                (object) [
                    'image_path' => '/dogs/dog_1.jpg',
                    'caption' => 'test',
                ],
            ),
        ],
    ]);

    return view('users.index', ['users' => $users]);
}

resources/views/users/index.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            オーナー
        </h2>
    </x-slot>

    <div class="max-w-5xl mx-auto">
        <div class="py-8">
            <div class="bg-white shadow-sm">
                <div class="grid grid-cols-2 gap-1">
                    @foreach ($users as $user)
                        <a href="/users/{{$user->id}}">
                            <div class="bg-white p-4 flex">
                                @if($user->icon)
                                    <img class="block h-24 rounded-full aspect-square object-cover" src="{{ asset($user->icon) }}">
                                @endif
                                <div class="pl-4">
                                    <div>
                                        <p class="text-lg text-black font-semibold">
                                            {{ $user->name }}
                                        </p>
                                        <p class="font-medium whitespace-pre-wrap">{{ $user->description }}</p>
                                        <div class="flex mt-4">
                                            <p class="text-sm text-black font-semibold">
                                                投稿{{ $user->posts->count() }}件
                                            </p>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </a>
                    @endforeach
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

オーナー詳細を作成

app/Http/Controllers/UserController.php

    public function show()
    {
        $user = (object) [
            'name' => 't.tanaka',
            'icon' => '/dogs/dog_1.jpg',
            'description' => 'はじめまして。よろしくお願いします。',
        ];
        $posts = collect([
            (object) [
                'id' => 1,
                'image_path' => '/dogs/dog_4.jpg',
                'caption' => 'test',
            ],
            (object) [
                'id' => 2,
                'image_path' => '/dogs/dog_5.jpg',
                'caption' => 'test',
            ],
            (object) [
                'id' => 3,
                'image_path' => '/dogs/dog_6.jpg',
                'caption' => 'test',
            ],
            (object) [
                'id' => 4,
                'image_path' => '/dogs/dog_7.jpg',
                'caption' => 'test',
            ],
        ]);

        return view('users.show', ['user' => $user, 'posts' => $posts]);
    }

resources/views/users/show.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            オーナー
        </h2>
    </x-slot>

    <div class="max-w-5xl mx-auto">
        <div class="bg-white p-4 flex mt-8">
            @if($user->icon)
                <img class="block h-24 rounded-full aspect-square object-cover" src="{{ asset($user->icon) }}">
            @endif
            <div class="pl-4">
                <div>
                    <p class="text-lg text-black font-semibold">
                        {{ $user->name }}
                    </p>
                    <p class="font-medium whitespace-pre-wrap">{{ $user->description }}</p>
                    <div class="flex mt-4">
                        <p class="text-sm text-black font-semibold">
                            投稿{{ $posts->count() }}件
                        </p>
                    </div>
                </div>
            </div>
        </div>

        <div class="py-8">
            <div class="bg-white">
                <div class="grid grid-cols-3 gap-1">
                    @foreach ($posts as $post)
                        <a href="/posts/{{$post->id}}">
                            <img class="aspect-square w-full object-cover" src="{{ asset($post->image_path) }}" />
                        </a>
                    @endforeach
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

プロフィール編集を変更

resources/views/profile/partials/update-profile-information-form.blade.php

        <div>
            <x-input-label for="email" :value="__('Email')" />
            <x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required autocomplete="username" />
            <x-input-error class="mt-2" :messages="$errors->get('email')" />
            <!-- 省略 -->
        </div>

        <div>
            <x-input-label for="name" value="自己紹介" />
            <x-textarea id="description" name="description" rows="6" class="mt-1">{{ old('description', $user->description) }}</x-textarea>
            <x-input-error class="mt-2" :messages="$errors->get('description')" />
        </div>

        <div>
            <x-input-label for="icon" value="アイコン" />
            <x-file-input name="icon" class="mt-1"/>
        </div>
    <form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6" enctype="multipart/form-data">
        @csrf
        @method('patch')

        <div>
            <x-input-label for="name" :value="__('Name')" />
            <x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
            <x-input-error class="mt-2" :messages="$errors->get('name')" />
        </div>
        <!-- 省略 -->   
    </form>

以上ブレイドの使い方でした。

次はマイグレーションです!

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

関連記事