Laravelのweb.phpで関数を使用するには
色々試した結果以下のやり方で動くことを確認しました。
web.phpにapp/Http/Controllers
に定義したapp/Http/Controllers/SpecImageController.php
の内容を使用する場合
web.php でルートを定義して SpecImageController を呼び出す。
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\BulletTestCaseController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\SpecificationController;
use App\Http\Controllers\SpecImageController; // ← これを追加!
use App\Http\Controllers\SpecMdController;
use App\Models\Project; // ← プロジェクトモデルを使う
use Illuminate\Support\Facades\Route;
Route::middleware(['auth'])->group(function () {
// / にアクセスしたらプロジェクト選択画面へ
Route::get('/', function () {
$projects = Project::orderBy('key')->get();
return view('projects.select', compact('projects'));
})->name('projects.select');
Route::post('/spec-images/upload', [SpecImageController::class, 'store'])->name('spec-images.upload');
Route::get('/spec-images', [SpecImageController::class, 'index'])->name('spec-images.index');
Route::get('/spec-md', [SpecMdController::class, 'index'])->name('spec-md.index');
Route::post('/spec-md/upload', [SpecMdController::class, 'store'])->name('spec-md.upload');
// routes/web.php
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['verified'])
->name('dashboard');
Route::resource('specifications', SpecificationController::class)->middleware(['auth']);
Route::post('spec-change-requests/{cr}/approve', [SpecificationController::class,'approve'])
->name('spec-change-requests.approve')->middleware(['auth']);
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// プロジェクト単位のテストケース関連
Route::prefix('projects/{project}')->group(function () {
Route::get('bullet-cases', [BulletTestCaseController::class,'index'])->name('bullet-cases.index');
Route::get('bullet-cases/create', [BulletTestCaseController::class,'create'])->name('bullet-cases.create');
Route::post('bullet-cases', [BulletTestCaseController::class,'store'])->name('bullet-cases.store');
});
Route::patch('bullet-case-rows/{row}', [BulletTestCaseController::class,'update'])
->name('bullet-cases.rows.update');
// 行の完了フラグ切替
Route::post('bullet-case-rows/{row}/toggle', [BulletTestCaseController::class,'toggle'])->name('bullet-cases.rows.toggle');
});
require __DIR__.'/auth.php';
書いているのはこの部分
Route::post('/spec-images/upload', [SpecImageController::class, 'store'])->name('spec-images.upload');
Route::get('/spec-images', [SpecImageController::class, 'index'])->name('spec-images.index');
name('spec-images.upload')
がroute() ヘルパー
で使える定義した name() の値となります
Route::post('/spec-images/upload',
ここの記述は 実行するときのエンドポイントを自分で指定しています。
[SpecImageController::class, 'store']
は SpecImageControllerクラスのstore関数を使用するという書き方
まとめ
この今回の書き方の場合
- エンドポイント(実行時のURL) =
/spec-images/upload
- HTTPメソッド = POST
- 処理内容 =
SpecImageController@store
が実行される -
SpecImageControllerクラス
のstoreメソッド
が実行できる
bladeからの呼び出し方
dashboard.blade.php からは route() を使ってフォーム送信、
一覧部分は @include('spec-images.index') で取り込める。
画像保存先は storage/app/public/spec-images → シンボリックリンクで public/storage/spec-images 経由でアクセス。
resources/views/dashboard.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl">ダッシュボード</h2>
</x-slot>
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8 space-y-8">
{{-- フラッシュメッセージ(任意) --}}
@if (session('status'))
<div class="p-3 rounded bg-green-50 border border-green-200 text-green-800">
{{ session('status') }}
</div>
@endif
{{-- サマリーカード --}}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-white shadow rounded-lg p-4">
<div class="text-gray-500 text-sm">プロジェクト</div>
<div class="text-2xl font-semibold">{{ $projectsCount ?? 0 }}</div>
</div>
<div class="bg-white shadow rounded-lg p-4">
<div class="text-gray-500 text-sm">テストグループ</div>
<div class="text-2xl font-semibold">{{ $groupsCount ?? 0 }}</div>
</div>
<div class="bg-white shadow rounded-lg p-4">
<div class="text-gray-500 text-sm">テスト行(総数)</div>
<div class="text-2xl font-semibold">{{ $rowsCount ?? 0 }}</div>
</div>
<div class="bg-white shadow rounded-lg p-4">
<div class="text-gray-500 text-sm">完了行</div>
<div class="text-2xl font-semibold">{{ $rowsDoneCount ?? 0 }}</div>
</div>
</div>
{{-- クイック操作 --}}
<div class="bg-white shadow rounded-lg p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold">クイック操作</h3>
<a href="{{ route('projects.select') }}" class="text-indigo-600 hover:underline">
プロジェクト選択へ
</a>
</div>
<div class="space-y-2"> {{-- プロジェクトごとに行を作る --}}
@foreach(($projects ?? []) as $p)
<div class="flex gap-2 items-center"> {{-- 1プロジェクト=1行 --}}
<a href="{{ route('bullet-cases.index', ['project' => $p]) }}"
class="px-3 py-1.5 border rounded text-sm hover:bg-gray-50">
{{ $p->key }}:{{ $p->name }}
</a>
<a href="{{ route('specifications.index', ['project' => $p]) }}"
class="px-3 py-1.5 border rounded text-sm hover:bg-gray-50 text-indigo-600">
仕様一覧
</a>
<a href="{{ route('specifications.create', ['project' => $p->id]) }}"
class="px-3 py-1.5 border rounded bg-indigo-600 text-black text-sm hover:bg-indigo-700">
仕様追加
</a>
</div>
@endforeach
@if(empty($projects) || count($projects ?? []) === 0)
<span class="text-gray-500 text-sm">プロジェクトがありません。</span>
@endif
</div>
</div>
{{-- 画像アップロード(追加) --}}
<div class="bg-white shadow rounded-lg p-4">
<h3 class="font-semibold mb-3">画像アップロード</h3>
<form action="{{ route('spec-images.upload') }}" method="POST" enctype="multipart/form-data" class="space-y-3">
@csrf
<input type="file" name="image" accept="image/*"
class="block w-full text-sm file:mr-3 file:py-1.5 file:px-3 file:rounded file:border file:bg-gray-50 file:hover:bg-gray-100" required>
@error('image')
<div class="text-red-600 text-sm">{{ $message }}</div>
@enderror
<div class="flex items-center gap-2">
<button type="submit"
class="px-4 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700">
アップロード
</button>
<a href="{{ route('spec-images.index') }}" class="text-indigo-600 hover:underline">
保存済み画像を見る
</a>
</div>
</form>
</div>
{{-- 最近作成したグループ --}}
<div class="bg-white shadow rounded-lg">
<div class="px-4 py-3 border-b">
<h3 class="font-semibold">最近のテストグループ</h3>
</div>
<div class="p-0 overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left">プロジェクト</th>
<th class="px-4 py-2 text-left">グループタイトル</th>
<th class="px-4 py-2 text-left">行(完了/総数)</th>
<th class="px-4 py-2 text-left">作成日時</th>
<th class="px-4 py-2 text-left">操作</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse(($recentGroups ?? []) as $g)
<tr>
<td class="px-4 py-2">
@if(isset($g->project))
<a class="text-indigo-600 hover:underline"
href="{{ route('bullet-cases.index', ['project' => $g->project]) }}">
{{ $g->project->key }}
</a>
@else
<span class="text-gray-500">-</span>
@endif
</td>
<td class="px-4 py-2">{{ $g->title ?? '-' }}</td>
<td class="px-4 py-2">
{{ $g->rows_done_count ?? 0 }}/{{ $g->rows_count ?? 0 }}
</td>
<td class="px-4 py-2">{{ optional($g->created_at)->format('Y-m-d H:i') }}</td>
<td class="px-4 py-2">
@if(isset($g->project))
<a class="px-3 py-1 border rounded text-sm hover:bg-gray-50"
href="{{ route('bullet-cases.index', ['project' => $g->project]) }}">
行を見る
</a>
@else
<span class="text-gray-400 text-sm">-</span>
@endif
</td>
</tr>
@empty
<tr>
<td class="px-4 py-6 text-center text-gray-500" colspan="5">まだデータがありません。</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</x-app-layout>
使用しているのが下記の部分↓
"{{ route('spec-md.index') }}"
で使用する関数のパスをname() の値 を使用して
<form action="{{ route('spec-images.upload') }}" method="POST" enctype="multipart/form-data">
のようにフォームの送信先に指定したり
<a href="{{ route('spec-images.index') }}">
と書いて blade
へのページ遷移を記述することが出来ます。
{{-- 画像アップロード(追加) --}}
<div class="bg-white shadow rounded-lg p-4">
<h3 class="font-semibold mb-3">画像アップロード</h3>
<form action="{{ route('spec-images.upload') }}" method="POST" enctype="multipart/form-data" class="space-y-3">
@csrf
<input type="file" name="image" accept="image/*"
class="block w-full text-sm file:mr-3 file:py-1.5 file:px-3 file:rounded file:border file:bg-gray-50 file:hover:bg-gray-100" required>
@error('image')
<div class="text-red-600 text-sm">{{ $message }}</div>
@enderror
<div class="flex items-center gap-2">
<button type="submit"
class="px-4 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700">
アップロード
</button>
<a href="{{ route('spec-images.index') }}" class="text-indigo-600 hover:underline">
保存済み画像を見る
</a>
</div>
</form>
</div>
web.phpにbladeを引っ張ってくる手順
基本的に web.php
→ controller
という関係性で
controllerクラスは
- 関数を実行
- 関数内にbladeの呼び出しを書いてweb.phpで使用出来るようにする
controllerクラスの関数内に画像保存処理を書いたり、bladeを呼び出すために関数内に return view() を使用することでweb.phpでbladeをコンポーネントととして使用出来るようになる
なので、web.phpから直接bladeを呼び出しているのではなく、
controllerクラスの関数
を経由して呼び出しているようだ。
view関数の使い方
呼び出す書き方は、基本 resources/views/
以降のパスを「.」で繋いでいくという考え方です。
例えば、resources/views/spec-images/index.blade.php
を呼ぶ場合
$images = [...]; // データを準備
return view('spec-images.index', compact('images'));
'spec-images.index'
と書くことで、spec-images/index.blade.php
を呼び出せます
compact()関数について
compactは簡単に言うとbladeを呼び出す際の引数を連想配列で渡す為の関数です。
Laravelの view('spec-md.index', [...]) の第二引数は「ビューに渡す変数一覧」なので、結果的に Blade 側で $docs が使えるようになります。
クラスのコンストラクタを使用するときの引数という考え方でいいと思います。
下記の例の場合
$docs = ['a.md', 'b.md', 'c.md'];
return view('spec-md.index', compact('docs'));
このときの動作は:
- compact('docs') が ['docs' => ['a.md', 'b.md', 'c.md']] を作る
- view('spec-md.index', ['docs' => [...]] ) と同じ意味になる
- resources/views/spec-md/index.blade.php がレンダリングされる
- その Blade 内で $docs が利用可能になる
resources/views/spec-images/index.blade.php
を呼び出す時に
$docs配列を渡している
compact()関数 のイメージ
「Bladeに渡す引数をまとめている」 → この理解は正しいです。
PHPの compact('docs') は ['docs' => $docs] という連想配列を返します。
compact関数を使用せずに書く場合
compact関数自体は、配列を連想配列にすることが役割なので
compact関数を使用せずに書くことも可能です。以下が例になります。
$docs = ['a.md', 'b.md', 'c.md'];
return view('spec-md.index', ['docs' => $docs]);
複数の変数を渡す場合
$docs = ['a.md', 'b.md', 'c.md'];
$images = ['1.png', '2.png'];
return view('dashboard', [
'docs' => $docs,
'images' => $images,
]);
Blade 側では $docs と $images の両方が使えるようになります。