バックエンドエンジニアがAdminLTE + livewireでjsを一切書かずphpだけで動的管理画面を作ってみた livewire編
この記事について
AdminLTE + livewireを使って管理画面を作る AdminLTE編
- AdminLTEについては、上記記事をご参照ください
- 今回はlivewireを導入し、管理者登録、一覧、削除/編集機能を実装します
使用マシン/ツール
macOS Ventura ver13.6
Docker Desktop ver4.23.0
Laravel Framework 10.25.2
Laravel AdminLTE 3.2.0
livewire 3.0.5
※ 手順をスキップして動かしてみたい方はこちらから
改めて、livewireとは
ReactやVueなどを使わず、PHPとHTML、少しのJavaScriptでリアルタイムな更新ができる画面を作成することが可能になります。
機能に応じてコンポーネント化をすることで、再利用性が高めることもできます。
(ちなみに読み方話は「ライブワイア」のようです)
1. livewireをインストール
appコンテナ内で下記コマンドを実行します。
-- appコンテナに入る
$ make app
-- livewireをインストール
root@d2a661cbd6a3:/workspace# composer require livewire/livewire
2. AdminLTEでのlivewireの設定
AdminLTEでlivewireを使うため、config/adminlte.php を下記のように編集します。
-'livewire' => false,
+'livewire' => true,
3. ページ作成
今回は管理者登録、一覧、削除/編集機能を実装します。
全体のイメージは、登録画面、一覧画面、編集画面の3つを作成し、登録画面と一覧画面は左メニューから移動できるようにします。
編集画面と削除機能は、一覧画面から移動/実行できるようにします。
まずは、登録画面と一覧画面のBladeファイルを作成します。
3-1. 登録画面と一覧画面を作成
登録画面と一覧画面を下記内容で作成します。
@extends('adminlte::page')
@section('title', 'Dashboard')
@section('content_header')
<h1>管理者登録</h1> <!-- 一覧画面なら管理者一覧 -->
@stop
@section('content')
<p>管理者登録の本体</p>
@stop
@section('css')
<link rel="stylesheet" href="/css/admin_custom.css">
@stop
@section('js')
<script> console.log('Hi!'); </script>
@stop
中身は前記事でも作成したホーム画面とほぼ同じ、AdminLTEのテンプレートになります。
ただ、ページを作成するたびにこの中身を毎回記載していくのも面倒なので、最低限のものに絞って記載したいと思います。
(次の工程が不要な方はスキップして大丈夫です)
3-2. 共通のテンプレートを作成して読み込む
下記内容でlayouts/app.blade.php
ファイルを作成します。
@extends('adminlte::page')
@section('title', 'Dashboard')
@section('css')
<link rel="stylesheet" href="/css/admin_custom.css">
@stop
次に、create.blade.php
とlist.blade.php
を下記内容で編集します。
@extends('admin.layouts.app')
@section('content_header')
<h1>管理者登録</h1> <!-- 一覧画面なら管理者一覧 -->
@stop
@section('content')
<p>管理者登録の本体</p>
@stop
adminlte::page
、title
, css
は基本変更することはないので、共通化してしまいます。
@section('content_header')
と@section('content')
はページごとに変更するので、そのまま残しておきます。
@section('js')
は使用するまでは削除しておきます。
3-3. コントローラーを作成する
下記コマンドを実行します。
$ docker compose exec app php artisan make:controller Admin/AdministratorController
INFO Controller [app/Http/Controllers/Admin/AdministratorController.php] created successfully.
下記メソッドを作成します。
public function createIndex(): Factory|View
{
return view('admin/administrator/create');
}
public function listIndex(): Factory|View
{
return view('admin/administrator/list');
}
3-4. ルーティングを設定する
routes/web.phpを下記のように編集します。
Route::middleware(['auth:admin'])->group(function () {
Route::get('/admin/home', function () {
return view('/admin/home');
})->name('admin.home');
+ Route::get(
+ '/admin/administrator/create',
+ '\App\Http\Controllers\Admin\AdministratorController@createIndex'
+ )->name('administrator.create');
+ Route::get(
+ '/admin/administrator/list',
+ '\App\Http\Controllers\Admin\AdministratorController@listIndex'
+ )->name('administrator.list');
});
3-5. メニューを作成する
AdminLTEのメニュー追加は、config/adminlte.php
のmenu
に下記のように記載します。
[
'text' => 'change_password',
'url' => 'admin/settings',
'icon' => 'fas fa-fw fa-lock',
],
+ [
+ 'text' => 'administrator',
+ 'icon' => 'fas fa-fw fa-share',
+ 'submenu' => [
+ [
+ 'text' => 'administrator.create',
+ 'url' => 'admin/administrator/create',
+ 'icon' => 'fas fa-fw',
+ ],
+ [
+ 'text' => 'administrator.list',
+ 'url' => 'admin/administrator/list',
+ 'icon' => 'fas fa-fw',
+ ],
+ ]
+ ],
['header' => 'labels'],
text
にはメニューに表示するページ名を記載します。
url
にはroutes/web.phpで設定したルーティングのuri
の部分を記載します。
submenu
に記載することで、クリックするとサブメニューが表示されるようなメニューになります。
icon
については、Bootstrapのアイコンを指定できます。
上記まで実装後、下記のようなメニューが表示され、クリックすると登録画面と一覧画面に移動できます。
4. livewireコンポーネントを生成して登録画面を実装する
ページが作成できたので、次にlivewireでコンポーネントを生成し、登録画面を実装していきます。
4-1. コンポーネントを生成する
appコンテナ内で下記コマンドを実行します。
-- appコンテナに入る
make app
-- 登録画面コンポーネントを生成
php artisan make:livewire Administrator/create
実行後、下記ファイルが生成されます。
app/Livewire/Administrator/Create.php
resources/views/livewire/administrator/create.blade.php
4-2. 生成したコンポーネントを編集する
Create.php
を下記のように編集します。
class Create extends Component
{
public string $name;
public string $email;
public string $password;
protected array $rules = [
'name' => 'required',
'email' => 'required|email|unique:admin_users,email',
'password' => 'required|min:8',
];
protected array $validationAttributes = [
'name' => '名前',
'email' => 'メールアドレス',
'password' => 'パスワード',
];
protected array $message = [
'required' => ':attributeは必須です',
];
public function render()
{
return view('livewire.administrator.create');
}
public function create(): void
{
$this->validate();
$user = new AdminUser();
$user->name = $this->name;
$user->email = $this->email;
$user->password = \Hash::make($this->password);
$user->save();
//フラッシュメッセージ
session()->flash(
'createMessage',
"管理者: {$this->name} を作成しました"
);
$this->name = '';
$this->email = '';
$this->password = '';
}
}
create.blade.php
を下記のように編集します。
<div>
@if (session()->has('createMessage'))
<x-adminlte-alert theme="success" title="Success">
{{ session('createMessage') }}
</x-adminlte-alert>
@endif
<form wire:submit.prevent="create">
@csrf
{{-- Name field --}}
<div class="form-group mb-3">
<x-adminlte-input name="name" label="管理者名" wire:model.lazy="name" />
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
{{-- Email field --}}
<div class="form-group mb-3">
<x-adminlte-input type='email' name="email" label="メール" wire:model.lazy="email" />
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
{{-- Password field --}}
<div class="form-group mb-3">
<x-adminlte-input type='password' name="password" label='パスワード' wire:model.lazy="password" />
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
{{-- Register button --}}
<button type="submit" class="btn btn-block {{ config('adminlte.classes_auth_btn', 'btn-flat btn-primary') }}">
<span class="fas fa-user-plus"></span>
{{ __('adminlte::adminlte.register') }}
</button>
</form>
</div>
テンプレート側のformタグにwire:submit.prevent="create"
を追記することで、Create.php
のcreate
メソッドが実行されます。
4-3. コンポーネントを表示する
3-1で作成したsrc/resources/views/admin/administrator/create.blade.php
のcontent
部分を下記のように編集します。
@section('content')
- <p>Welcome to this beautiful admin panel.</p>
+ <livewire:administrator.create />
@stop
上記まで実装後、下記登録画面が表示され、管理者の登録ができます。
5. 一覧画面を実装する
次は管理者一覧画面を実装します。
画面の上部には検索機能も加える想定なので、一覧表示のコンポーネントと検索機能のコンポーネントを作成して表示したいと思います。
5-1. AdminUserRepositoryを作成する
管理者一覧のデータを取得するためのRepositoryを作成します。
下記内容で、src/app/Repositories/AdminUserRepository.php
を作成します。
namespace App\Repositories;
use App\Models\Admin\AdminUser;
use Illuminate\Support\Collection;
class AdminUserRepository
{
public function getAll(): Collection
{
return AdminUser::all();
}
}
5-2. コンポーネントを生成する
appコンテナ内で下記コマンドを実行します。
-- appコンテナに入る
make app
-- 検索コンポーネントを生成
php artisan make:livewire Administrator/search
-- 一覧コンポーネントを生成
php artisan make:livewire Administrator/members
実行後、下記ファイルが生成されます。
app/Livewire/Administrator/Search.php
resources/views/livewire/administrator/search.blade.php
app/Livewire/Administrator/Members.php
resources/views/livewire/administrator/members.blade.php
5-3. 一覧コンポーネントを表示する
app/Livewire/Administrator/Members.php
のrendor
メソッドを下記のように編集します。
public function render()
{
+ $adminUserRepository = new AdminUserRepository();
+ $adminUsers = $adminUserRepository->getAll()->sortByDesc('updated_at');
+ $with = compact('adminUsers');
- return view('livewire.administrator.members');
+ return view('livewire.administrator.members')->with($with);
}
members.blade.php
を下記のように編集します。
<div>
<p class="font-weight-bold">管理者一覧</p>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>id</th>
<th>管理者名</th>
<th>メール</th>
<th>作成日</th>
<th>編集/削除</th>
</tr>
</thead>
<tbody>
@foreach ($adminUsers as $adminUser)
<tr>
<th scope="row">{{ $adminUser->id }}</th>
<td>{{ $adminUser->name }}</td>
<td>{{ $adminUser->email }}</td>
<td>{{ $adminUser->created_at }}</td>
<td>
<a href="#"
class="btn btn-primary">編集</a>
<button type="button" class="btn btn-danger">削除</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
3-1で作成したsrc/resources/views/admin/administrator/list.blade.php
のcontent
部分を下記のように編集します。
@section('content')
<livewire:administrator.members />
@stop
上記まで実装すると、一覧画面に管理者一覧が表示されます。
5-4. 検索コンポーネントを表示する
一覧画面の上部に検索機能を表示します。
app/Livewire/Administrator/Search.php
のrendor
メソッドを下記のように編集します。
use App\Models\Admin\AdminUser;
use App\Repositories\AdminUserRepository;
use Illuminate\Support\Collection;
use Livewire\Component;
class Search extends Component
{
public string $word = '';
public Collection $searchUsers;
public function render()
{
if ($this->word === '') {
$this->searchUsers = collect();
}
return view('livewire.administrator.search', [
'searchUsers' => $this->searchUsers,
]);
}
public function searching(): void
{
$adminUserRepository = new AdminUserRepository();
$tmpUsers = collect();
if ($this->word !== '') {
$tmpUsers = $adminUserRepository
->getAll()
->filter(function (AdminUser $adminUser) {
return str_contains($adminUser->name, $this->word) ||
str_contains($adminUser->email, $this->word);
})
->sortByDesc('updated_at');
}
$this->searchUsers = $tmpUsers;
}
}
search.blade.php
を下記のように編集します。
<div>
<p class="font-weight-bold">管理者検索</p>
<input type="text" wire:model="word" wire:keydown.enter="searching" class="form-control" id="searchMember"
placeholder="管理者名かメールアドレスの一部を入力してください" />
<button type="submit" wire:click="searching" class="btn btn-primary btn-sm mt-2">検索</button>
@if ($searchUsers->count() > 0)
<table class="table mt-4">
<thead class="thead-inverse">
<tr>
<th>id</th>
<th>管理者名</th>
<th>メール</th>
<th>作成日</th>
<th>編集/削除</th>
</tr>
</thead>
<tbody>
@foreach ($searchUsers as $searchUser)
<tr>
<th scope="row">{{ $searchUser->id }}</th>
<td>{{ $searchUser->name }}</td>
<td>{{ $searchUser->email }}</td>
<td>{{ $searchUser->created_at }}</td>
<td>
<a href="#"
class="btn btn-primary">編集</a>
<button type="button" class="btn btn-danger">削除</button>
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
処理を簡単に説明すると、wire:model="word"
に入力された文字列がバインドされ、wire:click="searching"
でSearch.php
のsearching
メソッドを実行します。
wire:keydown.enter="searching"
は、Enterキーを押したときにsearching
メソッドが実行させる呼び出しです。
searching
メソッドでは、AdminUserRepository
を使って全件取得し、filter
で管理者名またはメールアドレスに検索文字列が含まれるデータを抽出するあいまい検索で処理しています。
抽出データはsearchUsers
に格納され、render
メソッドでsearch.blade.php
に渡しています。
あとは、一覧コンポーネントと同様にsrc/resources/views/admin/administrator/list.blade.php
のcontent
部分に下記を追記します。
一覧機能と見分けやすくするため、<hr />
を追加しています。
@section('content')
+ <livewire:administrator.search />
+ <hr />
<livewire:administrator.members />
@stop
上記実装後、検索機能が表示されます。
検索前
検索後
6. 編集画面を実装する
編集画面は、一覧画面の編集ボタンを押下すると表示されるようにします。
URLは下記のように、編集する管理者のIDを付与するイメージです。
http://localhost/admin/administrator/edit/1
6-1. ルーティングを設定する
routes/web.phpに下記を追記します。
Route::middleware(['auth:admin'])->group(function () {
~省略~
+ Route::get(
+ '/admin/administrator/edit/{id}',
+ '\App\Http\Controllers\Admin\AdministratorController@editIndex'
+ )->name('administrator.edit');
});
6-2. コントローラーを編集する
AdministratorController.php
に下記メソッドを追記します。
+ public function editIndex(int $id): Factory|View
+ {
+ $with = [
+ 'id' => $id,
+ ];
+ return view('admin/administrator/edit', $with);
+ }
6-3. AdminUserRepositoryにメソッドを追加する
下記メソッドを追加します。
public function getById(int $id): AdminUser|null
{
return AdminUser::find($id);
}
public function updateById(
int $id,
string $name,
string $email,
string $password
): void {
$updates = [
'name' => $name,
'email' => $email,
];
if ($password !== '') {
$updates['password'] = \Hash::make($password);
}
AdminUser::query()
->findOrFail($id)
->update($updates);
}
6-4. ページを作成する
下記内容で、src/resources/views/admin/administrator/edit.blade.php
を作成します。
@extends('admin.layouts.app')
@section('content_header')
<h1>管理者一覧 編集</h1>
@stop
@section('content')
<livewire:administrator.edit id="{{ $id }}" />
@stop
6-5. コンポーネントを生成する
appコンテナ内で下記コマンドを実行します。
-- appコンテナに入る
make app
-- 編集画面コンポーネントを生成
php artisan make:livewire Administrator/edit
実行後、下記ファイルが生成されます。
app/Livewire/Administrator/Edit.php
resources/views/livewire/administrator/edit.blade.php
6-6. コンポーネントを編集する
Edit.php
を下記のように編集します。
class Edit extends Component
{
public int $id;
public string $name = '';
public string $email = '';
public string $password = '';
public function mount(int $id): void
{
$adminUserRepository = new AdminUserRepository();
/** @var AdminUser|null $adminUser */
$adminUser = $adminUserRepository->getById($id);
if ($adminUser === null) {
session()->flash(
'errorMessage',
"管理者id: {$id} が確認できません"
);
return;
}
$this->id = $id;
$this->name = $adminUser->name;
$this->email = $adminUser->email;
}
public function render()
{
return view('livewire.administrator.edit');
}
public function update(): void
{
$this->validate();
$user = new AdminUserRepository();
$user->updateById(
$this->id,
$this->name,
$this->email,
$this->password
);
//フラッシュメッセージ
session()->flash(
'updateMessage',
"管理者: {$this->name} の情報を更新しました"
);
$this->password = '';
}
}
edit.blade.php
を下記のように編集します。
<div>
@if (session()->has('updateMessage'))
<x-adminlte-alert theme="success" title="Success">
{{ session('updateMessage') }}
</x-adminlte-alert>
@elseif(session()->has('errorMessage'))
<x-adminlte-alert theme="danger" title="Error">
{{ session('errorMessage') }}
</x-adminlte-alert>
@endif
<form wire:submit.prevent="update">
@csrf
{{-- Name field --}}
<div class="form-group mb-3">
<x-adminlte-input name="name" label="管理者名" wire:model.lazy="name" />
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
{{-- Email field --}}
<div class="form-group mb-3">
<x-adminlte-input type='email' name="email" label="メール" wire:model.lazy="email" />
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
{{-- Password field --}}
<div class="form-group mb-3">
<x-adminlte-input type='password' name="password" label='パスワード' wire:model.lazy="password" />
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
{{-- Register button --}}
<button type="submit" class="btn btn-block {{ config('adminlte.classes_auth_btn', 'btn-flat btn-primary') }}">
<span class="fas fa-user-plus"></span>
update
</button>
</form>
</div>
内容はほとんど登録画面と同じですが、mount
メソッドで編集する管理者のデータを取得しています。
mount
メソッドはlivewire側で用意されたクラスフックと呼ばれるもので、コンストラクタの役割を持ちます。
この処理では、該当の管理者idをもとにデータを抽出して変数に格納し、初期データとして表示するようにしています。
6-7. 編集画面を表示する
一覧画面の編集ボタンをクリックしたら、該当の管理者ユーザーの編集画面に移動するようにします。
members.blade.php
とsearch.blade.php
の編集リンク部分を下記のように編集します。
- <a href="#"
// membersの場合
+ <a href="{{ route('administrator.edit', ['id' => $adminUser->id]) }}"
// searchお場合
+ <a href="{{ route('administrator.edit', ['id' => $searchUser->id]) }}"
class="btn btn-primary">編集</a>
ここまで実装すると、一覧画面の編集ボタンから編集画面に移動、編集ができます。
7. 削除機能を実装する
削除機能は、一覧画面の削除ボタンを押下すると確認モーダルを表示し、実行できるようにします。
削除ボタンは一覧コンポーネントと検索コンポーネントで表示しているため、各コンポーネントで共通の削除確認モーダルを表示できるようにします。
7-1. AdminUserRepositoryに削除メソッドを追加する
public function deleteById(int $id): void
{
AdminUser::query()
->findOrFail($id)
->delete();
}
7-2. コンポーネントを生成する
appコンテナ内で下記コマンドを実行します。
-- appコンテナに入る
make app
-- 削除コンポーネントを生成
php artisan make:livewire Administrator/delete
実行後、下記ファイルが生成されます。
app/Livewire/Administrator/Delete.php
resources/views/livewire/administrator/delete.blade.php
7-3. 削除コンポーネントを編集する
Delete.php
を下記のように編集します。
/** @phpstan-ignore-next-line */
protected $listeners = [
'SetDeleteId' => 'setDeleteId'
];
public string $deleteId = '';
public bool $isDeleted = false;
public function render()
{
return view('livewire.administrator.delete');
}
public function setDeleteId(string $id): void
{
$this->deleteId = $id;
}
public function delete(): void
{
$adminUserRepository = new AdminUserRepository();
$adminUserRepository->deleteById((int) $this->deleteId);
$this->isDeleted = true;
}
public function reloadList()
{
return redirect()->route('administrator.list');
}
$listeners
に定義したメソッドは他のコンポーネントクラスからの呼び出しが可能になります。
これは、後述の一覧/検索コンポーネントの削除ボタン押下時に、各コンポーネントから呼び出すためです。
reloadList
メソッドは、削除完了後に画面再描画のため、一覧画面にリダイレクトしています。
次に、delete.blade.php
を下記のように編集します。
<div>
<form wire:submit.prevent="delete" method="POST">
@csrf
<x-adminlte-modal wire:ignore.self id="deleteConfirmModal" title="管理者削除" size="sm">
@if($isDeleted)
<p>削除しました</p>
<x-slot name="footerSlot">
<button type="button" class="btn btn-secondary" data-dismiss="modal" wire:click="reloadList">閉じる</button>
</x-slot>
@else
<p>削除しますか?</p>
<x-slot name="footerSlot">
<button type="button" class="btn btn-secondary" data-dismiss="modal" >キャンセル</button>
<button type="submit" class="btn btn-danger">削除</button>
</x-slot>
@endif
</x-adminlte-modal>
</form>
</div>
削除ボタンを押下した際に表示するモーダルの記載となります。
<x-adminlte-modal>
は AdminLTE のモーダルコンポーネントです。
id="deleteConfirmModal"
でモーダルにIDを指定し、一覧/検索コンポーネントの削除ボタンで呼び出す際に指定することで、削除確認モーダルを表示できます。
7-4. 一覧/検索コンポーネントを編集する
Members.php
とSearch.php
にそれぞれ下記メソッドを追記します。
public function openModal(string $id): void
{
$this->dispatch('SetDeleteId', id: $id);
}
dispatch
を使うことで、$listeners
に定義したsetDeleteId
メソッドに引数id
を渡して呼び出すことができます。
これによりDelete
コンポーネントに削除対象の管理者IDを渡してします。
members.blade.php
とsearch.blade.php
の削除ボタン部分を下記のように編集します。
- <button type="button" class="btn btn-danger">削除</button>
+ <button type="button" class="btn btn-danger" data-toggle="modal"
+ data-target="#deleteConfirmModal"
+ wire:click="openModal({{ $adminUser->id }})"> // search.blade.phpなら$searchUser->id
+ 削除
+ </button>
各コンポーネントのopenModal
メソッドを呼び出して、管理者IDを渡しています。
ここまで実装すると、一覧/検索コンポーネントから削除ボタンを押下すると、削除確認モーダルが表示されます。
削除完了後はモーダルに削除完了の通知が表示され、モーダルを閉じると一覧画面がリロードされます。
以上で想定した機能の実装が完了しました。
お疲れ様でした。
まとめ
バックエンドを専門にやってきた方だと、jsで動的な画面を作るのも最初は苦労すると思います。(自分がそうでした)
livewireはPHPだけで動的な画面を作れるので、jsが苦手な方にはおすすめかなと思います。
AdminLTE + livewireで画面を作ってみて
さすがにlaravel-adminのような自動生成するブラグインと比べると実装に時間はかかりますが、一から実装するよりは楽だと思います。
特に画面レイアウトに関しては一から作成するには時間がかかりますが、AdminLTEなどの画面テンプレートを使うとすぐに画面を作成できます。
livewireも、Reactなどのjsフレームワークや、そもそもjsを一切書くことなく動的な画面を作成できるので、バックエンド専門の方であれば重宝するかなと思いました。
参考
Discussion