Laravel-adminで顧客管理システムの構築手順【爆速】

Laravelのインストールは下記を参考に済ませておきましょう。
LaravelをLaradockでインストールする手順
【Laravel】Laradockで複数プロジェクトを動かす手順

Laravel-adminインストール

composerでlaravel-adminをインストールします。

composer require encore/laravel-admin

下記のコマンドを実行することでインストールが完了します。

# to publish assets and config
php artisan vendor:publish --provider="Encore\Admin\AdminServiceProvider"

php artisan admin:install

うまくいけば管理画面にアクセスすることができます。確認してみましょう。下記でログインできればokです。(localhostは適宜ご自身の環境に変更してください。)

http://localhost/admin/
ユーザー名: admin
パスワード: admin

データベース作成(migration)

データベースを設計します。下記のようなDBを作成します。

- 顧客(customer)
	- 顧客id(id)
	- 顧客名(name)
	- 担当スタッフid(staff_id)
	- ステータス(status)
- 顧客詳細(customer_details)
	- 顧客詳細id(id)
	- 顧客id(customer_id)
	- 電話番号(tel)
	- 備考(remarks)
	- 契約開始日(contracted_at)
- スタッフ(staffs)
	- スタッフid(id)
	- 名前(name)

Migrationファイルを作成して、テーブルを作っていきましょう。

スタッフテーブル

まずはスタッフテーブルから作成しましょう。

ターミナル

php artisan make:migration create_staffs_table --cretae=staffs

編集ファイル:
database/migrations/2018_xx_xx_xxxxxx_create_staffs_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateStaffsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('staffs', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->comment('従業員名');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('staffs');
    }
}

顧客テーブル

顧客テーブル、顧客詳細テーブルは後回しにして、先にスタッフの画面を作ってもokです。一応ここでは先にテーブルを書いてしまいます。

ターミナル

php artisan make:migration create_customers_table --create=customers

編集ファイル:
database/migrations/2018_xx_xx_xxxxxx_create_customers_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCustomersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('customers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->comment('顧客名');
            $table->integer('staff_id')->unsigned()->nullable()->comment('担当スタッフid');
            $table->enum('status', ['active', 'stop']);
            $table->timestamps();

            $table->foreign('staff_id')->references('id')->on('staffs');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('customers');
    }
}

Statusにenum(列挙型)というのを使っています。予め決められた値(今回であれば「active」か「stop」か)を入れるのに使います。

顧客詳細テーブル

ターミナル

php artisan make:migration create_customer_details_table --create=customer_details

編集ファイル: 2018_xx_xx_xxxxxx_create_customer_details_talbe.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCustomerDetailsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('customer_details', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('customer_id')->unsigned()->comment('顧客id');
            $table->string('tel')->nullable()->comment('電話番号');
            $table->string('contracted_at')->nullable()->comment('契約開始日');
            $table->text('remarks')->nullable()->comment('備考');
            $table->timestamps();

            $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade')->onUpdate('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('customer_details');
    }
}

モデル作成

スタッフモデルを作成します。

php artisan make:model Models\\Staff

上記のコマンドにより、app/Models/Staff.phpが生成されます。中身は規則通りDB名は複数形、モデルは単数形で登録していたら何も書かなくても動きます。

動作しない場合は下記のように使用するテーブル名を記述しましょう。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Staff extends Model
{
    protected $table = 'staffs';
}

続いてコントローラーを作成しましょう(customerについては後ほど)

コントローラー作成

Staff.phpのモデルを使うように指定してコントローラーを作成します。

php artisan admin:make StaffController --model=App\\Models\\Staff

簡単にできあがったControllerについて説明しておくと、下記のようなイメージです。

  • URLとなる部分
    • index(一覧表示)
    • show(詳細表示)
    • edit(編集)
    • create(新規作成)
  • メソッド
    • grid(一覧表示に使うメソッド)
    • detail(詳細表示に使うメソッド)
    • form(編集・作成に使うメソッド)

まず、Admin Facadeを使えるようにuseを追記します。

編集ファイル: app/Admin/Controllers/StaffController.php

use Encore\Admin\Facades\Admin;

次にgrid(一覧表示)の部分を編集します。ここではIDとスタッフの名前を表示して、Viewのアクションをdisableにしています。
編集ファイル: app/Admin/Controllers/StaffController.php

protected function grid()
{
    $grid = Admin::grid(Staff::class, function (Grid $grid) {

        $grid->id('ID')->sortable();
        $grid->column('name')->sortable();
        $grid->actions(function ($actions) {
            // $actions->disableDelete(); // 削除無効
            // $actions->disableEdit(); // 編集無効
            $actions->disableView(); // 詳細表示無効
        });
    });

    return $grid;
}

Id、名前をソート可能なようにして一覧表示、actionは編集・削除のみといった感じです。

ルーティング設定

laravel-adminのルーティング設定はapp/Admin/routing.phpに記述します。staffsにアクセスされたらStaffControllerで処理してねというのを書きます。

編集ファイル: app/Admin/routes.php

<?php

use Illuminate\Routing\Router;

Admin::registerAuthRoutes();

Route::group([
    'prefix'        => config('admin.route.prefix'),
    'namespace'     => config('admin.route.namespace'),
    'middleware'    => config('admin.route.middleware'),
], function (Router $router) {

    $router->get('/', 'HomeController@index');
    $router->resource('staffs', StaffController::class);
});

日本語化

ついでに日本語化しておきましょう。localの部分をenからjaに変更するだけです。
編集ファイル: config/app.php

'local' => 'ja',

確認

ここまできたら一度確認してみましょう。http://localhost/admin/staffsにアクセスして、一覧表示、新規作成、編集、削除が出来たらokです。

リレーションのあるModel作成

顧客テーブルは下記のようなリレーションを持っています。

顧客テーブルのid(customers.id)
= 顧客詳細テーブルの顧客id(costomer_details.customer_id)
(顧客テーブルは顧客詳細テーブルを1つ持っている→hasOne)

顧客テーブルのスタッフid(customers.staff_id)
= スタッフテーブルのid(staffs.id)
(顧客テーブルのスタッフidはスタッフテーブルに属している→belongsTo)

この関係をモデルに記述するとこうなります。

ターミナル

php artisan make:model Models\\Customer

編集ファイル: app/Models/Customer.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Customer extends Model
{
    //顧客詳細情報
    public function detail()
    {
    	return $this->hasOne('App\Models\CustomerDetail');
    }

    // スタッフ情報
    public function staff() {
    	return $this->belongsTo('App\Models\Staff');
    }
}

Laravel-adminでリレーションのあるテーブルを表示する

続いてコントローラーを設定していきましょう。Customerコントローラーを作成して編集していきます。

ターミナル

php artisan admin:make CustomerController --model=App\\Models\\Customer

編集ファイル:
app/Admin/Controllers/CustomerController.php

...(省略)

protected function grid()
{
    $grid = new Grid(new Customer);

    $grid->id('id');
    $grid->name('名前');
    $grid->detail()->tel('TEL');
    $grid->staff()->name('スタッフ名');
    $grid->status('状態');
    $grid->created_at('作成日時');
    $grid->updated_at('更新日時');

    $grid->actions(function ($actions) {
        $actions->disableView();
    });

    return $grid;
}

...(省略)

protected function form()
{
    $form = new Form(new Customer);

    $form->text('name', '顧客名');
    $form->select('staff_id')->options(Staff::pluck('name', 'id'));
    $form->text('detail.tel', 'TEL');
    $form->date('detail.contracted_at', '契約日')->format('YYYY-MM-DD');
    $states = [
        'on'  => ['value' => 'active', 'text' => '稼働', 'color' => 'success'],
        'off' => ['value' => 'stop', 'text' => '停止', 'color' => 'danger'],
    ];
    $form->switch('status', '状態')->states($states)->default('on');

    return $form;
}
...(省略)

ポイントだけ解説します。まずは一覧表示部分について。

// Customerのモデルを渡して、
$grid = new Grid(new Customer);

// Customerで定義してあるdetail()により顧客詳細を取得
$grid->detail()->tel('TEL');

// Customerで定義してあるstaff()によりスタッフを取得
$grid->staff()->name('スタッフ名');

次はフォーム部分について

// Customerのモデルを渡して、
$form = new Form(new Customer);
// ドットでつないでCustomerモデルのdetail()より情報を引っ張ってくる
$form->text('detail.tel', 'TEL');

// 従業員は従業員一覧を取得して選択できるように
// pluckによってnameをkeyにidをvalueとしてselectを生成します
$form->select('staff_id')->options(Staff::pluck('name', 'id'));

これでルートにcustomersを追記してあげれば完成です!
編集ファイル: app/Admin/routes.php

$router->resource('customers', CustomerController::class);

参考URL:

データベース:マイグレーション 5.5 Laravel
Laravelのマイグレーションで使えるカラムタイプについて。onUpdate(‘cascade’)は変更に追随する。

Which is more efficient: Multiple MySQL tables or one large table? - Stack Overflow
1:1のテーブルを分けるかどうかについて

MySQLの外部キー制約RESTRICT,CASCADE,SET NULL,NO ACTIONの違いは?
外部キー制約について

【メモ】【Laravel】外部キー制約付きMigrateが通らない時のチェックポイント(Mysql)
外部キー制約をつけてMigrationが通らない時。ポイントはひっつける方(id=user_idのuser_idの方)にunsignedをつけること。

increments()で作ったカラムには、実は裏でunsined(符号無し)属性が付与される。要は採番項目なので正の値しか登録できないわけだが、役割テーブル側のuser_idとauthority_idには同様の制約を付けていない。

つまり、形式の不一致で落ちているわけですな。