前回、【Laravel-admin】Laravel-adminで新規テーブルを作成しCRUD画面を追加する方法でLaravel-adminでテーブルのCRUD画面を作成しました。
単一のテーブルであれば、テーブル名の単数形のモデル・コントローラを作成しルートを追加すれば簡単に対象テーブルのCRUD画面を実装することができます。
Laravel-adminではリレーションシップで結ばれたテーブルであっても簡単に、リレーションを定義し編集画面を作成することが可能です。
リレーションシップについてほぼ知識なしの状態からLaravelを通じて学習したので、不足している部分をあるかもしれませんがご容赦ください!
日本語のドキュメントが少なく、かなり苦戦しましたが、以下の記事にむちゅくちゃ助けられました!
参考:Laravel-adminを使って実用的な画面を作る。(1対多のリレーションシップに対応)
今回の実装内容
■Laravel-adminを使用して一対一、一対多、多対多のリレーションシップに対応した以下の画面を作成します。
・一覧画面
・編集画面
・詳細画面
ソースコード
マイグレーションファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTestUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('test_users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('age')->nullable(); $table->string('email')->nullable(); $table->timestamps(); $table->softDeletes(); }); Schema::create('addresses', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('address')->nullable(); $table->string('phone_number')->nullable(); $table->timestamps(); $table->softDeletes(); }); Schema::create('families', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->unsignedInteger('age'); $table->tinyInteger('gender'); $table->timestamps(); $table->softDeletes(); }); Schema::create('items', function (Blueprint $table) { $table->increments('id'); $table->string('item_name')->nullable(); $table->timestamps(); $table->softDeletes(); }); Schema::create('test_user_items', function (Blueprint $table) { $table->unsignedInteger('user_id'); $table->unsignedInteger('item_id'); $table->timestamps(); $table->softDeletes(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('test_users'); Schema::dropIfExists('addresses'); Schema::dropIfExists('families'); Schema::dropIfExists('items'); Schema::dropIfExists('test_user_items'); } } |
コントローラ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
<?php namespace App\Admin\Controllers; use App\Admin\Models\TestUser; use App\Http\Controllers\Controller; use Encore\Admin\Controllers\HasResourceActions; use Encore\Admin\Form; use Encore\Admin\Grid; use Encore\Admin\Layout\Content; use Encore\Admin\Show; use App\Admin\Extensions\Tools; use Goodby\CSV\Import\Standard\Lexer; use Goodby\CSV\Import\Standard\Interpreter; use Goodby\CSV\Import\Standard\LexerConfig; use Illuminate\Http\Request; use Encore\Admin\Facades\Admin; use App\Admin\Models\Item; use Encore\Admin\Layout\Column; use Encore\Admin\Layout\Row; use Encore\Admin\Widgets\Box; use Encore\Admin\Widgets\Tab; class TestUserController extends Controller { use HasResourceActions; /** * Index interface. * * @param Content $content * @return Content */ public function index(Content $content) { return $content ->header('Index') ->description('description') ->body($this->grid()); } /** * Show interface. * * @param mixed $id * @param Content $content * @return Content */ public function show($id) { return Admin::content(function (Content $content) use ($id) { $content->header('Detail'); $content->description('description'); $testUserModel = config('admin.database.test_user_model'); $addressModel = config('admin.database.address_model'); $familyModel = config('admin.database.family_model'); $user = $testUserModel::find($id); $count = 0; $itemList = []; foreach ($user->item as $item) { $itemList[$count] = $item->toArray(); $count += 1; } $testUserTmp = $testUserModel::where('id', $id)->get(['id', 'name','age', 'email']); $addressTmp = $addressModel::where('user_id', $id)->get(['address', 'phone_number']); $familyTmp = $familyModel::where('user_id', $id)->get(['name', 'age', 'gender']); $testUser = $testUserTmp->toArray(); $address = $addressTmp->toArray(); $families = $familyTmp->toArray(); $targetUser = array_merge($testUser[0], $address[0]); $content->row(function (Row $row) use ($targetUser, $itemList, $families) { $row->column(6, function (Column $column) use ($targetUser, $itemList, $families) { $userForm = new \Encore\Admin\Widgets\Form($targetUser); $userForm->display('name', '名前'); $userForm->display('age', '年齢'); $userForm->display('email', 'メールアドレス'); $userForm->display('address', '住所'); $userForm->display('phone_number', '電話番号'); foreach ($itemList as $v) { $userForm->display('', 'アイテム')->default($v['item_name']); } $userForm->disableSubmit(); $userForm->disableReset(); $familyForm = new \Encore\Admin\Widgets\Form(); foreach ($families as $family) { $familyForm->divide(); $familyForm->display('', '名前')->default($family['name']); $familyForm->display('', '年齢')->default($family['age']); $familyForm->display('', '性別')->default($family['gender']); } $familyForm->disableSubmit(); $familyForm->disableReset(); $tab = new Tab(); $tab->add('ユーザ情報', $userForm); $tab->add('家族情報', $familyForm); $tab->render(); $column->append($tab); }); }); }); } /** * Edit interface. * * @param mixed $id * @param Content $content * @return Content */ public function edit($id, Content $content) { return $content ->header('Edit') ->description('description') ->body($this->form()->edit($id)); } /** * Create interface. * * @param Content $content * @return Content */ public function create(Content $content) { return $content ->header('Create') ->description('description') ->body($this->form()); } /** * Make a grid builder. * * @return Grid */ protected function grid() { $grid = new Grid(new TestUser); $grid->id('Id'); $grid->name('Name'); $grid->age('Age'); $grid->email('Email'); $grid->address()->address('住所'); $grid->address()->phone_number('電話番号'); $grid->family('家族')->pluck('name')->label(); $grid->item('アイテム名')->pluck('item_name')->label(); $grid->tools(function ($tools) { $tools->append(new Tools\CsvImport()); }); return $grid; } /** * Make a form builder. * * @return Form */ protected function form() { $testUserModel = config('admin.database.test_user_model'); $itemModel = config('admin.database.item_model'); return Admin::form($testUserModel, function (Form $form) use ($itemModel) { $form->tab('ユーザー情報', function ($form) use ($itemModel) { $form->text('name', 'Name'); $form->number('age', 'Age'); $form->email('email', 'Email'); $form->text('address.address', '住所'); $form->text('address.phone_number', '電話番号'); $form->multipleSelect('item', 'アイテム')->options($itemModel::all()->pluck('item_name', 'id')); })->tab('家族情報', function ($form) { $form->hasMany('family', '家族リスト', function ($form) { $form->text('name'); $form->number('age'); $form->radio('gender')->options([ 0 => '男性', 1 => '女性' ]); }); }); }); } } |
モデル
TestUser.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php namespace App\Admin\Models; use Illuminate\Database\Eloquent\Model; class TestUser extends Model { protected $fillable = ['name', 'age', 'email']; protected $guarded = ['id']; public function address() { $relatedModel = config('admin.database.address_model'); return $this->hasOne($relatedModel, 'user_id'); } public function family() { $relatedModel = config('admin.database.family_model'); return $this->hasMany($relatedModel, 'user_id'); } public function item() { $pivotTable = config('admin.database.user_item_table'); $relatedModel = config('admin.database.item_model'); return $this->belongsToMany($relatedModel, $pivotTable, 'user_id', 'item_id'); } } |
Address.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php namespace App\Admin\Models; use Illuminate\Database\Eloquent\Model; class Address extends Model { public function testUser() { $relatedModel = config('admin.database.test_user_model'); return $this->belongsTo($relatedModel, 'user_id'); } } |
Family.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php namespace App\Admin\Models; use Illuminate\Database\Eloquent\Model; class Family extends Model { protected $fillable = ['user_id', 'name', 'age', 'gender']; protected $guarded = ['id']; public function testUser() { $relatedModel = config('admin.database.test_user_model'); return $this->belongsTo($relatedModel, 'user_id'); } } |
Item.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace App\Admin\Models; use Illuminate\Database\Eloquent\Model; class Item extends Model { public function testUser() { $pivotTable = config('admin.database.user_item_table'); $relatedModel = config('admin.database.test_user_model'); return $this->belongsToMany($relatedModel, $pivotTable, 'item_id', 'user_id'); } } |
一覧画面
出来上がりの画面はこんな感じ↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
protected function grid() { $grid = new Grid(new TestUser); $grid->id('Id'); $grid->name('Name'); $grid->age('Age'); $grid->email('Email'); // 一対一のリレーション $grid->address()->address('住所'); $grid->address()->phone_number('電話番号'); // 一対多のリレーション $grid->family('家族')->pluck('name')->label(); // 多対多のリレーション $grid->item('アイテム名')->pluck('item_name')->label(); // csvインポート用のクラス $grid->tools(function ($tools) { $tools->append(new Tools\CsvImport()); }); return $grid; } |
編集画面
出来上がりはこんな感じ↓
■ユーザー情報タブ
■家族情報タブ
編集画面の内容はform関数内に記述します。
一対一、多対多はこちらも公式ドキュメントとLaravel-adminのソースコードを参考にしています。
一対多のリレーションに関してはこちらを参考にさせていただきました。
感想としてはとっても楽&実用的!
一対多のリレーションシップをもったテーブルのデータを流動的に増減させていくフォームを簡単に作成できます。
UIもいい感じ!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
protected function form() { $testUserModel = config('admin.database.test_user_model'); $itemModel = config('admin.database.item_model'); return Admin::form($testUserModel, function (Form $form) use ($itemModel) { $form->tab('ユーザー情報', function ($form) use ($itemModel) { $form->text('name', 'Name'); $form->number('age', 'Age'); $form->email('email', 'Email'); $form->text('address.address', '住所'); $form->text('address.phone_number', '電話番号'); $form->multipleSelect('item', 'アイテム')->options($itemModel::all()->pluck('item_name', 'id')); })->tab('家族情報', function ($form) { $form->hasMany('family', '家族リスト', function ($form) { $form->text('name'); $form->number('age'); $form->radio('gender')->options([ 0 => '男性', 1 => '女性' ]); }); }); }); } |
詳細画面
出来上がりはこんな感じ↓
ユーザー情報タブ
家族情報タブ
詳細画面の内容はshow関数内に記述します。
先述の参考サイトではBoxとTableを使用しているので、FormとTabを使用しました、
ただ、UI的にはEncore\Admin\Formの方が見栄えもいいし、無理してWidgetsのformを使うことはないかも…
機会があればForm以外のWidgetsを使用して実装したいと思います(コードもかなりへんぽこなので修正を行うかも…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
public function show($id) { return Admin::content(function (Content $content) use ($id) { $content->header('Detail'); $content->description('description'); $testUserModel = config('admin.database.test_user_model'); $addressModel = config('admin.database.address_model'); $familyModel = config('admin.database.family_model'); $user = $testUserModel::find($id); $count = 0; $itemList = []; foreach ($user->item as $item) { $itemList[$count] = $item->toArray(); $count += 1; } $testUserTmp = $testUserModel::where('id', $id)->get(['id', 'name','age', 'email']); $addressTmp = $addressModel::where('user_id', $id)->get(['address', 'phone_number']); $familyTmp = $familyModel::where('user_id', $id)->get(['name', 'age', 'gender']); $testUser = $testUserTmp->toArray(); $address = $addressTmp->toArray(); $families = $familyTmp->toArray(); $targetUser = array_merge($testUser[0], $address[0]); $content->row(function (Row $row) use ($targetUser, $itemList, $families) { $row->column(12, function (Column $column) use ($targetUser, $itemList, $families) { $userForm = new \Encore\Admin\Widgets\Form($targetUser); $userForm->display('name', '名前'); $userForm->display('age', '年齢'); $userForm->display('email', 'メールアドレス'); $userForm->display('address', '住所'); $userForm->display('phone_number', '電話番号'); foreach ($itemList as $v) { $userForm->display('', 'アイテム')->default($v['item_name']); } $userForm->disableSubmit(); $userForm->disableReset(); $familyForm = new \Encore\Admin\Widgets\Form(); foreach ($families as $family) { $familyForm->divide(); $familyForm->display('', '名前')->default($family['name']); $familyForm->display('', '年齢')->default($family['age']); $familyForm->display('', '性別')->default($family['gender']); } $familyForm->disableSubmit(); $familyForm->disableReset(); $tab = new Tab(); $tab->add('ユーザ情報', $userForm); $tab->add('家族情報', $familyForm); $tab->render(); $column->append($tab); }); }); }); } |
感想
日本語のドキュメントが少ないため中々手間取るところもありますが、数少ないドキュメントやソースコードを参考にすると、
リレーションシップに対応するCRUD画面に関してかなり実用的かつ簡単に実装することができそうです!
その他、一覧画面のソートやフィルターなどかなり多機能みたいなので色々と調べてみたいと思います。
おまけ
日本語化
config/app.phpの81行目辺りの「locale」を「en」から「ja」に変更するだけです。
1 2 3 |
'locale' => 'en', ↓ 'locale' => 'ja', |
また、app.php内でタイムゾーンの設定やファクトリーで作成するデータの言語設定もできるので必要であれば変更出来ます。