LaravelのBladeを徹底解説
本記事では、LaravelのBladeの使い方をわかりやすく解説します。
目次
チュートリアルの全体像
本チュートリアルを通じて学べる内容は以下のようになります。
- Laravelの開発環境セットアップ
- Laravel Breezeで認証機能を導入
- Laravelのルーティングを徹底解説
- Laravelのコントーラーを徹底解説
- Laravelのブレイドを使ってみよう (now🐾)
- マイグレーションの仕組みを解説
- シーディングを使ってみよう
- Eloquentの基本と使い方を徹底解説
- 画像アップロードを実装する方法
- バリデーションを実装してみよう
- 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>
以上ブレイドの使い方でした。
次はマイグレーションです!
この記事へのコメントはありません。