Laravel 5.7 + Tenancy Hyn マルチテナント基盤にlaravel-adminを導入

[Laravel 5.7 でフル機能マルチテナント・SaaS型アプリ基盤をゼロから3時間で構築する](https://qiita.com/hitotch/items/9e22921209eda18d326b)の付録記事。

LaravelでマルチテナントSaaS基盤を作ったはいいけど、管理画面構築を~~手抜き~~効率化したいのでlaravel-adminを導入する話です。

# 方針

- LaravelでマルチテナントSaaS基盤をもとにアプリを開発する前に、各テナントごとに管理画面を持たせる基盤も作っておく。
- テナントを作成したら、管理画面も自動的に準備完了になるようにする。
- 管理画面ユーザーとフロントエンドユーザーは別のテーブルで管理する。ログイン管理も別。
- フロントエンドのRoleやPermission管理画面も作りやすい状態にする。

# 参考記事

[Laravel 5.7 laravel-adminを導入する](https://tac-blog.tech/index.php/2018/09/24/install-laravel-admin/)
[laravel-adminで管理画面を速攻で構築する。](https://qiita.com/pikonori/items/a04066010e7380b3ee38)

# では構築

[Laravel 5.7 でフル機能マルチテナント・SaaS型アプリ基盤をゼロから3時間で構築する Part3](https://qiita.com/hitotch/items/8162d5a0594c72db6fed)が完了している状態が前提です。

- Laravel 5.7が動作している。
- Tenancy Hynが入っている。
- laravel-permissionが入っている。
- テナントを簡単に作ったり消したりできる。

laravel-adminをプロジェクトに入れます。

```
composer require encore/laravel-admin
```

```
php artisan vendor:publish
```

で
**Encore\Admin\AdminServiceProvider**
を選択。すると以下のようにファイルがコピーされます。

```
  Copied Directory [\vendor\encore\laravel-admin\config] To [\config]
  Copied Directory [\vendor\encore\laravel-admin\resources\lang] To [\resources\lang]
  Copied Directory [\vendor\encore\laravel-admin\database\migrations] To [\database\migrations]
  Copied Directory [\vendor\encore\laravel-admin\resources\assets] To [\public\vendor\laravel-admin]
```

さて、次はインストールですが、``php artisan admin:install``してしまうとシステムデータベースにlaravel-admin用データベースが作られてしまいます。
マルチテナントなのでデータベースはテナント作成時にマイグレーションおよびシーディングするべきですから、そのように変更してからインストールします。

laravel-adminのインストールコンソールコマンドを``extend``して自前インストールコマンドを作る作戦を取ります。
laravel-adminで提供される``admin:install``Artisanコマンドでは、データベースの作成（Migration＋Seeding）と、プログラムコードのコピーが行われていますが、データベースの作成はスキップしてコードのインストールだけ実施するよう``admin:install4hyn``を作成します。
``app/Console/Commands/LaravelAdminInstall.php``を新設。その中にArtisanコマンド``admin:install4hyn``を新設します。

```
php artisan make:command LaravelAdminInstall
```

```php:app/Console/Commands/LaravelAdminInstall.php
<?php

namespace App\Console\Commands;

use Encore\Admin\Console\InstallCommand;

class LaravelAdminInstall extends InstallCommand
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $signature = 'admin:install4hyn';     // Our install command

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Install the admin package for Tenancy Hyn';       // Modified description

    /**
     * Install directory.
     *
     * @var string
     */
    protected $directory = '';

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        // $this->initDatabase();       // We do not migrate database at this moment.

        $this->initAdminDirectory();
    }

}
```

これを実行。

```
php artisan admin:install4hyn
```

するとアプリにコードが埋め込まれます。すべて``app/Admin``以下に配置されていますね。

```
  Admin directory was created: \app\Admin
  HomeController file was created: \app\Admin/Controllers/HomeController.php
  AuthController file was created: \app\Admin/Controllers/AuthController.php
  ExampleController file was created: \app\Admin/Controllers/ExampleController.php
  Bootstrap file was created: \app\Admin/bootstrap.php
  Routes file was created: \app\Admin/routes.php
```

次に、テナント作成時にlaravel-adminに必要なテーブルが作られるように、Migrationファイルを``database/migrations``から``database/migrations/tenant``に移動(コピーではなく)します。

``xxxx_xx_xx_xxxxxx_create_admin_tables.php``の1個だけです。

テーブルの中身はlaravel-adminのコアにあるSeederを呼ぶ仕様みたいなので、''CreateTenant.php''（または[親記事](https://qiita.com/hitotch/items/8162d5a0594c72db6fed)のおまけ１改造を行った場合はTenant.php）に追記。

```php:app/Console/Commands/CreateTenant.php
...
use Artisan;
...
        $admin = static::makeAdmin($name, $email, str_random());  //これはもとからある
        static::makeAdminAdmin();  //これを追記
...
    private static function makeAdminAdmin(){
        $userModel = config('admin.database.users_model');

        if ($userModel::count() == 0) {
            Artisan::call('db:seed', ['--class' => \Encore\Admin\Auth\Database\AdminTablesSeeder::class]);
        }
    }
...
```

一応これでテナント作成したら``http://テナント名.ベースURL/admin``にアクセスできます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/315589/c16e0a60-b6d9-482d-a006-7301729b6c4d.png)


User: admin, Password: adminでログインしようとすると、

```
Invalid catalog name: 1046 No database selected (SQL: select * from `admin_users` where `username` = admin limit 1)
```

これはlaravel-adminがテナントデータベースを見に行ってないためです。

今回はRouterの部分で、「laravel-adminのURLに入っていて、なおかつTenancyがテナントを認識していたら」、強制的にテナントデータベースを読みに行くように``app/Admin/routes.php``を編集します。

```php:app/Admin/routes.php
<?php

use Illuminate\Routing\Router;

// Tenancy patch
$hostname  = app(\Hyn\Tenancy\Environment::class)->hostname();
if(Request::is(config('admin.route.prefix')."*") && $hostname){
    Config::set('database.default', 'tenant');
}
...
```

``config('admin.route.prefix')``は、laravel-admin設定でURLの``admin``を好きなスラッグに変更できるので、それに対応させています。

では気を取り直して``http://テナント名.ベースURL/admin``からログインしてみましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/315589/90d846d9-cbc4-417e-53b7-6f74e23580c6.png)

ちゃんとテナント専用管理画面が出ましたね。

あとはユーザー管理画面、権限管理画面を作成し、またアプリに必要な管理画面を作っていけばOKです。

# 残る課題

残念ながらこの方法は完ぺきというわけではなく、１つ問題が残っています。

テナント用に管理画面を開発しても、新しくテナントを作った時には、メニューバーがデフォルトのままになるし、権限設定も空の状態からスタートします。これは、メニューバーの設定がデータベースに記録されているため、開発内容がテナントのデータベースに記録されており、新しく作ったテナントには反映されないためです。もちろんRouteやControllerは存在するので、画面アクセスはできるんですけどね。

対処としては、いったんデフォルト状態を作ったテナントを準備し、そのデータベースからSeederを手作りしなくてはいけなさそうです。まあ、これくらいは仕方ないですね。