AdminLTE3をLaravelにzipで導入して認証機能を実装するやり方

# 前書き
laravelにAdminLTEを導入する時は通常は[Laravel-AdminLTE](https://github.com/jeroennoten/Laravel-AdminLTE)をcomposer requireすると思うが、諸事情でgithubからzipダウンロードして導入することになった。その際に認証機能の導入で`laravel/ui`と`laravel-adminlte`との融合に苦労したのでまとめておく。

# tl;dr
- zipダウンロードしてview書き換えるのは結構大変
- なので別で`laravel/ui`+`laravel-adminlte`を導入したプロジェクトを作る
- そしてview部分だけコピペで導入するという裏技的な対処で切り抜けた。

# 開発環境
- docker-compose
    - php-fpm
    - nginx
    - mysql
- laravel7

# やり方

##参考のために、laravel/uiとlaravel-adminlteのプロジェクトを作る
参考にするために、普通にcomposer requireとartisanコマンドで実装するプロジェクトを作成します。
`laravel/ui`+`laravel-adminlte`での認証機能の導入は他の記事を参考にしてください。
認証機能が実装できたら次に進みます。

## AdminLTEをzipで導入
それでは本題のプロジェクトの作業に取り掛かります。
[github](https://github.com/ColorlibHQ/AdminLTE/releases)からzipダウンロードします。
解凍したら`/AdminLTE-3.0.5/dist/`配下のファイルと`/AdminLTE-3.0.5/plugins`フォルダをlaravelの`/public/`配下にコピーします。

```
$ cp -r /AdminLTE-3.0.5/dist/* public/vendor/AdminLTE/3.0.5/
$ cp -r /AdminLTE-3.0.5/plugins/* public/vendor/AdminLTE/3.0.5/
```

## laravel/uiインストール
laravel/uiをインストールしますが、叩くコマンドが違います。

```
$ composer require laravel/ui
$ php artisan ui:auth //デフォルトでbootstrapで作成する
```
公式のやり方だと`php artisan ui bootstrap --auth`して`npm run dev`で`/public/app.css`と`/public/app.js`を作るように書いてますが、artisanコマンドのヘルプに沿った上のやり方だとなぜか`npm run dev`を求められない。

## ビューを書き換え
ここが一番苦労した。
`laravel/ui`+`laravel-adminlte`のプロジェクトのコードからview部分だけを導入する
headタグ内のスタイルシート、javascriptの引用先や@extendsの参照先などに気を付けて書き換える。

```php:/resouces/views/auth/login.blade.php
@extends('auth.auth-page', ['auth_type' => 'login'])

@section('adminlte_css_pre')
    <link rel="stylesheet" href="{{ asset('vendor/icheck-bootstrap/icheck-bootstrap.min.css') }}">
@stop

@php( $login_url = View::getSection('login_url') ?? config('adminlte.login_url', 'login') )
@php( $register_url = View::getSection('register_url') ?? config('adminlte.register_url', 'register') )
@php( $password_reset_url = View::getSection('password_reset_url') ?? config('adminlte.password_reset_url', 'password/reset') )

@if (config('adminlte.use_route_url', false))
    @php( $login_url = $login_url ? route($login_url) : '' )
    @php( $register_url = $register_url ? route($register_url) : '' )
    @php( $password_reset_url = $password_reset_url ? route($password_reset_url) : '' )
@else
    @php( $login_url = $login_url ? url($login_url) : '' )
    @php( $register_url = $register_url ? url($register_url) : '' )
    @php( $password_reset_url = $password_reset_url ? url($password_reset_url) : '' )
@endif

@section('auth_header', __('ログイン'))

@section('auth_body')
    <form action="{{ $login_url }}" method="post">
        {{ csrf_field() }}

        {{-- Email field --}}
        <div class="input-group mb-3">
            <input type="email" name="email" class="form-control {{ $errors->has('email') ? 'is-invalid' : '' }}"
                   value="{{ old('email') }}" placeholder="メールアドレス" autofocus>
            <div class="input-group-append">
                <div class="input-group-text">
                    <span class="fas fa-envelope"></span>
                </div>
            </div>
            @if($errors->has('email'))
                <div class="invalid-feedback">
                    <strong>{{ $errors->first('email') }}</strong>
                </div>
            @endif
        </div>

        {{-- Password field --}}
        <div class="input-group mb-3">
            <input type="password" name="password" class="form-control {{ $errors->has('password') ? 'is-invalid' : '' }}"
                   placeholder="パスワード">
            <div class="input-group-append">
                <div class="input-group-text">
                    <span class="fas fa-lock"></span>
                </div>
            </div>
            @if($errors->has('password'))
                <div class="invalid-feedback">
                    <strong>{{ $errors->first('password') }}</strong>
                </div>
            @endif
        </div>

        {{-- Login field --}}
        <div class="row">
            <div class="col-7">
                <div class="icheck-primary">
                    <input type="checkbox" name="remember" id="remember">
                    <label for="remember">ログイン状態を記憶する</label>
                </div>
            </div>
            <div class="col-5">
                <button type=submit class="btn btn-block btn-flat btn-primary">
                    <span class="fas fa-sign-in-alt"></span>
                    ログイン
                </button>
            </div>
        </div>

    </form>
@stop

@section('auth_footer')
    {{-- Password reset link --}}
    @if($password_reset_url)
        <p class="my-0">
            <a href="{{ $password_reset_url }}">
                パスワードをお忘れの方
            </a>
        </p>
    @endif

    {{-- Register link --}} //登録機能は提供しないのでコメントアウト
    {{-- @if($register_url)
        <p class="my-0">
            <a href="{{ $register_url }}">
                {{ __('adminlte::adminlte.register_a_new_membership') }}
            </a>
        </p>
    @endif --}}
@stop

```

```php:/resources/views/auth/auth-page.blade.php
{{-- 認証画面レイアウト --}}

@extends('layout.master')

@php( $dashboard_url = View::getSection('dashboard_url') ?? '/' )

@if (config('adminlte.use_route_url', false))
    @php( $dashboard_url = $dashboard_url ? route($dashboard_url) : '' )
@else
    @php( $dashboard_url = $dashboard_url ? url($dashboard_url) : '' )
@endif

@section('adminlte_css')
    @stack('css')
    @yield('css')
@stop

@section('classes_body'){{ ($auth_type ?? 'login') . '-page' }}@stop

@section('body')
    <div class="{{ $auth_type ?? 'login' }}-box">

        {{-- Logo --}}
        <div class="{{ $auth_type ?? 'login' }}-logo">
            <img src="{{ asset(config('adminlte.logo_img')) }}" height="50">
            <b>AdminLTE</b>
        </div>

        {{-- Card Box --}}
        <div class="card card-outline card-primary">

            {{-- Card Header --}}
            @hasSection('auth_header')
                <div class="card-header">
                    <h3 class="card-title float-none text-center">
                        @yield('auth_header')
                    </h3>
                </div>
            @endif

            {{-- Card Body --}}
            <div class="card-body {{ $auth_type ?? 'login' }}-card-body">
                @yield('auth_body')
            </div>

            {{-- Card Footer --}}
            @hasSection('auth_footer')
                <div class="card-footer">
                    @yield('auth_footer')
                </div>
            @endif

        </div>

    </div>
@stop

@section('adminlte_js')
    @stack('js')
    @yield('js')
@stop

```

```php
{{-- 認証画面レイアウト --}}

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>

    {{-- Base Meta Tags --}}
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    {{-- Custom Meta Tags --}}
    @yield('meta_tags')

    {{-- Title --}}
    <title>AdminLTE 3 | Dashboard</title>
    {{-- Custom stylesheets (pre AdminLTE) --}}
    @yield('adminlte_css_pre')

    <!-- Tell the browser to be responsive to screen width -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Font Awesome -->
    <link rel="stylesheet" href={{ asset('/vendor/AdminLTE/3.0.5/plugins/fontawesome-free/css/all.min.css') }}>
    <!-- Ionicons -->
    <link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
    <!-- Tempusdominus Bbootstrap 4 -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/plugins/tempusdominus-bootstrap-4/css/tempusdominus-bootstrap-4.min.css') }}>
    <!-- iCheck -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/plugins/icheck-bootstrap/icheck-bootstrap.min.css') }}>
    <!-- JQVMap -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/plugins/jqvmap/jqvmap.min.css') }}>
    <!-- Theme style -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/css/adminlte.min.css') }}>
    <!-- overlayScrollbars -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/plugins/overlayScrollbars/css/OverlayScrollbars.min.css') }}>
    <!-- Daterange picker -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/plugins/daterangepicker/daterangepicker.css') }}>
    <!-- summernote -->
    <link rel="stylesheet" href={{ asset('vendor/AdminLTE/3.0.5/plugins/summernote/summernote-bs4.css') }}>
    <!-- Google Font: Source Sans Pro -->
    <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">

    {{-- Custom Stylesheets (post AdminLTE) --}}
    @yield('adminlte_css')
</head>

<body class="@yield('classes_body')" @yield('body_data')>

    {{-- Body Content --}}
    @yield('body')

        <!-- jQuery -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/jquery/jquery.min.js") }}></script>
        <!-- jQuery UI 1.11.4 -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/jquery-ui/jquery-ui.min.js") }}></script>
        <!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
        <!-- Bootstrap 4 -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/bootstrap/js/bootstrap.bundle.min.js") }}></script>
        <!-- ChartJS -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/chart.js/Chart.min.js") }}></script>
        <!-- Sparkline -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/sparklines/sparkline.js") }}></script>
        <!-- JQVMap -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/jqvmap/jquery.vmap.min.js") }}></script>
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/jqvmap/maps/jquery.vmap.usa.js") }}></script>
        <!-- jQuery Knob Chart -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/jquery-knob/jquery.knob.min.js") }}></script>
        <!-- daterangepicker -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/moment/moment.min.js") }}></script>
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/daterangepicker/daterangepicker.js") }}></script>
        <!-- Tempusdominus Bootstrap 4 -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/tempusdominus-bootstrap-4/js/tempusdominus-bootstrap-4.min.js") }}></script>
        <!-- Summernote -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/summernote/summernote-bs4.min.js") }}></script>
        <!-- overlayScrollbars -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/plugins/overlayScrollbars/js/jquery.overlayScrollbars.min.js") }}></script>
        <!-- AdminLTE App -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/js/adminlte.js") }}></script>
        <!-- AdminLTE dashboard demo (This is only for demo purposes) -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/js/pages/dashboard.js") }}></script>
        <!-- AdminLTE for demo purposes -->
        <script src={{ asset("vendor/AdminLTE/3.0.5/js/demo.js") }}></script>

    {{-- Custom Scripts --}}
    @yield('adminlte_js')

</body>

</html>

```