tanaka's Programming Memo

プログラミングについてのメモ。

Laravel5.3でSentinelを利用する(3)ユーザー登録(2016/11/8更新)

前へ | 次へ

環境の構築が完了したので、実際に機能を置き換えていきます。まずはユーザー登録を対応させます。

(2016/11/8 エラーを変数でviewに渡していたのを、withErrorに変更)
(2016/12/21 HTMLのidをケバブケースに修正)
(2016/12/21 テストにwantToを追加)


方針

Sentinelのユーザー登録についての公式ドキュメントはこちら

ここでは次のことをします。

  • ユーザー名と、メールアドレスと、パスワードと、確認用のパスワードを受け付ける
  • ユーザー登録を完了するには、メールによる確認作業を必要にする
  • ユーザー名、メールアドレス、パスワードは必須
  • パスワードと確認用パスワードは一致していなければならない



ユーザー登録のテストを作成

ユーザー登録処理をSentinelに組み替えるにあたり、事前にテストコードを作成しておきます。

ボタンを確実に押すために、フォームにIDを設定します。

  • resources/views/auth/register.blade.php をエディターで開く
  • 「<form」から始まる行を見つけて、以下のようにID属性を追加する(idをケバブケースに修正 2016/12/21)
                    <form id="register-user-form" class="form-horizontal" role="form" method="POST" action="{{ url('/register') }}">

続いて、テスト用のCestファイルを作成しいます。

  • ターミナルから以下を実行して、ユーザー登録用のテストコードを生成
composer exec codecept g:cest functional RegisterUser
  • tests/functional/RegisterUserCest.php が出来上がるので、エディターで開く
  • tryToTestメソッドを以下のようにする(submitFormのidをケバブケースに修正 2016/12/21)
    // tests
    public function tryToTest(FunctionalTester $I)
    {
        // 空欄での登録試みが失敗するのを確認
        $I->wantTo(' 空欄での登録試みが失敗するのを確認.');
        $I->amOnPage('/register');
        $I->submitForm('#register-user-form', [
        ]);
        $I->seeInCurrentUrl('register');
        $I->see('name', '.help-block');
        $I->see('email', '.help-block');
        $I->see('password', '.help-block');

        // パスワードの不一致を確認
        $I->wantTo(' パスワードの不一致を確認.');
        $I->amOnPage('/register');
        $I->submitForm('#register-user-form', [
            'name' => 'テスト名前2',
            'email' => 'test2@test.com',
            'password' => 'testpass',
            'password_confirmation' => 'not'
        ]);
        $I->seeInCurrentUrl('register');
        $I->see('password', '.help-block');

        // 登録成功
        $I->wantTo(' ユーザーの登録成功を確認.');
        $I->amOnPage('/register');
        $I->submitForm('#register-user-form', [
            'name' => 'テスト名前',
            'email' => 'test@test.com',
            'password' => 'testpass',
            'password_confirmation' => 'testpass'
        ]);
        $I->seeInCurrentUrl('login');
        $I->see(trans('sentinel.after_register'));
    }

以下をテストするコードです。

  • 空欄での登録試みが失敗するのを確認
  • パスワードの不一致を確認
  • 登録成功

npm test で実行すると、エラーが発生します。まだ、ユーザー登録処理を Sentinel に入れ替えてなく、アサーションするための文字列も用意していません。このテストコードを通すことを目指して作業を進めていきます。

テストに利用できるメソッドなどはこちら → Laravel5 - Codeception - Documentation

メールのテスト環境を構築

メール送信のテスト時に、実際に本当のメールアドレスに送信すると削除するなどが面倒です。そこで、テスト用のメールサービス Mailtrap.io — Fake smtp testing server. Dummy smtp email testing を利用することにします。

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=25
MAIL_USERNAME=<MailtrapのUsername>
MAIL_PASSWORD=<MailtrapのPassword>
MAIL_ENCRYPTION=null
  • 上書き保存する

以上で設定完了です。テストを実行した時のメールは、実際のメールアドレスではなく、 Mailtrap.io に届きますので、そちらで確認します。送信先に架空のメールアドレスが使えるのも便利です。


ユーザー登録コントローラーの作成

ユーザーを登録処理するコントローラーを作成します。アクティベーションが必要となるので、Laravel が事前に提供している処理は利用できません。独自のものに差し替えます。

  • app/Http/Controllers/Auth/RegisterController.php をエディターで開いて、既存のコードを削除して、以下のものに差し替える
<?php

namespace App\Http\Controllers\Auth;

use Activation;
use Mail;
use Sentinel;
use App\User;
use App\Http\Controllers\Controller;
use App\Notifications\RegisterNotify;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    /**
     * Where to redirect users after login / registration.
     *
     * @var string
     */
    protected $redirectTo = 'login';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * ユーザー登録
     */
    protected function register(Request $request)
    {
        $this->validate($request, [
            // nameは必須で、255文字まで
            'name' => 'required|max:255',
            // emailは必須で、emailの形式で、255文字までで、usersテーブル内でユニーク
            'email' => 'required|email|max:255|unique:users',
            // passwordは必須で、6文字以上255文字以下で、確認欄と一致する必要がある
            'password' => 'required|between:6,255|confirmed',
        ]);

        // 情報に問題がなければ、ユーザー登録
        $credentials = [
            'name' => $request['name'],
            'email' => $request['email'],
            'password' => $request['password'],
        ];
        $user = Sentinel::register($credentials);

        // アクティベーションを作成する
        $activation = Activation::create($user);

        // メールで送信する
        $usermodel = User::where('email', $user->email)->get()[0];
        $usermodel->notify(new RegisterNotify($activation->code));

        // メールを確認して、承認してからログインすることを表示するページへ
        return redirect($this->redirectTo)->with('info', trans('sentinel.after_register'));
    }
}
  • 上書き保存

コード内にある通知のための RegisterNotify クラスと、テスト用のメッセージがまだありません。テストコードを通すために、これらを用意します。

メッセージを作成

メッセージをハードコーディングするのを避けるために、言語ファイルを作成しておきます。

  • resources/langフォルダー内の en フォルダーを複製して、複製したフォルダー名を ja に変更
  • jaフォルダー内に sentinel.php というファイルを作成
  • sentinel.php をエディターで開いて、以下のコードを追加
<?php
return [
    // ユーザー登録関連
    'activate_title' => 'ユーザー登録を完了してください',
    'after_register' => 'ご登録いただいたメールアドレスに、登録を完了するためのリンクを書いたメールを送信しました。メールを開いて、リンクを押して、ユーザー登録を完了させたら、以下からログインしてください。',
    'register_user_title' => 'ユーザー登録',
    'register_user_name' => '',
    'register_user_intro' => 'へのユーザー登録を完了するには、以下のボタン(あるいはリンク)をクリックしてください。',
    'register_user_button' => 'ユーザーを有効にする',
    'register_user_outro' => '----',
    'notify_footer_message' => '*このメールは送信専用アドレスから送信しています。返信はできません。',
    'notify_footer_caution' => 'ボタンを押してもうまく動作しない場合は、以下のURLをコピーして、Webブラウザーのアドレス欄にペーストしてみてください。',
    'system_mail' => 'システムメール',
];
  • 英文が必要な場合は、上記と同じキーに、英語を代入したものを en フォルダーの sentinel.php に追加する
  • config/app.php をエディターで開く
  • 'locale'と'fallback_locale'の2つの値を、'en' から 'ja' に変更する

以上で完了です。以降、sentinel.phpに追加した配列を、PHPのコードからは trans('sentinel.要素名')、bladeからは@lang('sentinel.要素名')で参照できます。

通知のスタブ作成

ユーザーをアクティベーションするためのメッセージを送信させる通知を作成します。この段階では、テストを通すことを優先して中身のないスタブクラスとします。

  • ターミナルで、以下を実行して、ユーザー登録、登録完了、リマインダー、リマインダー完了の通知を作成
php artisan make:notification RegisterNotify

このままで、とりあえず指定のユーザーにメールを送信します。中身は後で実装します。

ルートの設定

コントローラーができたので、registerで呼び出したら登録するようにルートを設定します。テスト時に出てくるので、 login のルートも設定しておきます。

  • routes/web.php をエディターで開く
  • Auth::routes(); は、Laravelのauthは利用しないので削除
  • POSTメソッドのregisterで登録を発動させるので、Auth::routes() があった場所に、以下を追加(ログイン用のルートを修正 2016/11/8)
// ログイン用のルート
Route::get('login', function() {return view('auth.login', [
    'info' => session('info')
])->withErrors(session('myerror'));})->name('login');
Route::post('login', 'Auth\LoginController@login');

// ユーザー登録用のルート
Route::get('register', function() {return view('auth.register');});
Route::post('register', 'Auth\RegisterController@register');

登録後のメッセージを表示できるように、loginビューに表示領域を追加します。

  • resources/views/auth/login.blade.php をエディターで開く
  • <div class="panel-body"> という行の下に、以下を追加(エラーの部分を修正 2016/11/8)
                    @if(isset($info))
                    <div class="alert alert-info">
                        {{ $info }}
                    </div>
                    @endif

                    @include('parts.errors')

エラーのビューをJavaScriptやその他の文言に対応できるように修正します(2016/11/8追加)。

  • resources/views/parts/errors.blade.php を作成して、エディターで開く
  • 以下のコードを入力する
<!-- resources/views/parts/errors.blade.php -->

<!-- Form Error List -->
<div id="error-block" class="alert alert-danger" @if (count($errors) == 0) style="display: none;" @endif >
    <strong>以下のエラーが発生しました。</strong>
    <br>
    <ul id="error-list">
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
</div>

以上で、 view から info と myerror を受け取った場合、ログイン画面にメッセージを表示します。

Registerビューの調整

必要な項目は揃っているので変更しなくても構いません。表記を変更したり、表示を日本語にしたい、独自の欄を設けたい場合などの手順です。

  • resources/views/auth/register.blade.php をエディターで開く
  • 変更したい箇所を修正する
  • 上書き保存

テスト

以上で最低限の機能が揃いました。 npm test をターミナルで実行してください。「OK (2 tests, 9 assertions)」と表示されて成功します。

また、 http://mailtrap.io に用意した Inbox の中身を確認してください。通知にあらかじめ用意されているメッセージが届きます。


アクティベーションコードを送るメール

通知時に送るメールのフォームを完成させます。すでに php artisan vendor:publish を実行しているので、必要なメールファイルが resources フォルダー内にありますので、それを編集します。

  • resources/views/vendor/notifications/email.blade.php をエディターで開いて、以下に書き換える
<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <style type="text/css" rel="stylesheet" media="all">
        /* Media Queries */
        @media only screen and (max-width: 500px) {
            .button {
                width: 100% !important;
            }
        }
    </style>
</head>

<?php

$style = [
    /* Layout ------------------------------ */

    'body' => 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;',
    'email-wrapper' => 'width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;',

    /* Masthead ----------------------- */

    'email-masthead' => 'padding: 25px 0; text-align: center;',
    'email-masthead_name' => 'font-size: 16px; font-weight: bold; color: #2F3133; text-decoration: none; text-shadow: 0 1px 0 white;',

    'email-body' => 'width: 100%; margin: 0; padding: 0; border-top: 1px solid #EDEFF2; border-bottom: 1px solid #EDEFF2; background-color: #FFF;',
    'email-body_inner' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0;',
    'email-body_cell' => 'padding: 35px;',

    'email-footer' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0; text-align: center;',
    'email-footer_cell' => 'color: #AEAEAE; padding: 35px; text-align: center;',

    /* Body ------------------------------ */

    'body_action' => 'width: 100%; margin: 30px auto; padding: 0; text-align: center;',
    'body_sub' => 'margin-top: 25px; padding-top: 25px; border-top: 1px solid #EDEFF2;',

    /* Type ------------------------------ */

    'anchor' => 'color: #3869D4;',
    'header-1' => 'margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;',
    'paragraph' => 'margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;',
    'paragraph-sub' => 'margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;',
    'paragraph-center' => 'text-align: center;',

    /* Buttons ------------------------------ */

    'button' => 'display: block; display: inline-block; width: 200px; min-height: 20px; padding: 10px;
                 background-color: #3869D4; border-radius: 3px; color: #ffffff; font-size: 15px; line-height: 25px;
                 text-align: center; text-decoration: none; -webkit-text-size-adjust: none;',

    'button--green' => 'background-color: #22BC66;',
    'button--red' => 'background-color: #dc4d2f;',
    'button--blue' => 'background-color: #3869D4;',
];
?>

<?php $fontFamily = 'font-family: Arial, \'Helvetica Neue\', Helvetica, sans-serif;'; ?>

<body style="{{ $style['body'] }}">
    <table width="100%" cellpadding="0" cellspacing="0">
        <tr>
            <td style="{{ $style['email-wrapper'] }}" align="center">
                <table width="100%" cellpadding="0" cellspacing="0">
                    <!-- Logo -->
                    <tr>
                        <td style="{{ $style['email-masthead'] }}">
                            <a style="{{ $fontFamily }} {{ $style['email-masthead_name'] }}" href="{{ url('/') }}" target="_blank">
                                {{ config('app.name') }} {{trans('sentinel.register_user_title')}}
                            </a>
                        </td>
                    </tr>

                    <!-- Email Body -->
                    <tr>
                        <td style="{{ $style['email-body'] }}" width="100%">
                            <table style="{{ $style['email-body_inner'] }}" align="center" width="570" cellpadding="0" cellspacing="0">
                                <tr>
                                    <td style="{{ $fontFamily }} {{ $style['email-body_cell'] }}">
                                        <!-- Greeting -->
                                        <h1 style="{{ $style['header-1'] }}">
                                            @if (! empty($greeting))
                                                {{ $greeting }}
                                            @else
                                                @if ($level == 'error')
                                                    Error!
                                                @else
                                                    Hello!
                                                @endif
                                            @endif
                                        </h1>

                                        <!-- Intro -->
                                        @foreach ($introLines as $line)
                                            <p style="{{ $style['paragraph'] }}">
                                                {{ $line }}
                                            </p>
                                        @endforeach

                                        <!-- Action Button -->
                                        @if (isset($actionText))
                                            <table style="{{ $style['body_action'] }}" align="center" width="100%" cellpadding="0" cellspacing="0">
                                                <tr>
                                                    <td align="center">
                                                        <?php
                                                            switch ($level) {
                                                                case 'success':
                                                                    $actionColor = 'button--green';
                                                                    break;
                                                                case 'error':
                                                                    $actionColor = 'button--red';
                                                                    break;
                                                                default:
                                                                    $actionColor = 'button--blue';
                                                            }
                                                        ?>

                                                        <a href="{{ $actionUrl }}"
                                                            style="{{ $fontFamily }} {{ $style['button'] }} {{ $style[$actionColor] }}"
                                                            class="button"
                                                            target="_blank">
                                                            {{ $actionText }}
                                                        </a>
                                                    </td>
                                                </tr>
                                            </table>
                                        @endif

                                        <!-- Outro -->
                                        @foreach ($outroLines as $line)
                                            <p style="{{ $style['paragraph'] }}">
                                                {{ $line }}
                                            </p>
                                        @endforeach

                                        <!-- Salutation -->
                                        <p style="{{ $style['paragraph'] }}">
                                            {{trans('sentinel.notify_footer_message') }}
                                        </p>

                                        <!-- Sub Copy -->
                                        @if (isset($actionText))
                                            <table style="{{ $style['body_sub'] }}">
                                                <tr>
                                                    <td style="{{ $fontFamily }}">
                                                        <p style="{{ $style['paragraph-sub'] }}">
                                                            "{{ $actionText }}"
                                                            {{trans('sentinel.notify_footer_caution')}}
                                                        </p>

                                                        <p style="{{ $style['paragraph-sub'] }}">
                                                            <a style="{{ $style['anchor'] }}" href="{{ $actionUrl }}" target="_blank">
                                                                {{ $actionUrl }}
                                                            </a>
                                                        </p>
                                                    </td>
                                                </tr>
                                            </table>
                                        @endif
                                    </td>
                                </tr>
                            </table>
                        </td>
                    </tr>

                    <!-- Footer -->
                    <tr>
                        <td>
                            <table style="{{ $style['email-footer'] }}" align="center" width="570" cellpadding="0" cellspacing="0">
                                <tr>
                                    <td style="{{ $fontFamily }} {{ $style['email-footer_cell'] }}">
                                        <p style="{{ $style['paragraph-sub'] }}">
                                            &copy; {{ date('Y') }}
                                            <a style="{{ $style['anchor'] }}" href="{{ url('/') }}" target="_blank">{{ config('app.name') }}</a>.
                                            All rights reserved.
                                        </p>
                                    </td>
                                </tr>
                            </table>
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
</body>
</html>
  • resources/views/vendor/notifications/email-plain.blade.php をエディターで開いて、以下に書き換える
<?php

if (! empty($greeting)) {
    echo $greeting, "\n\n";
} else {
    echo $level == 'error' ? 'Error!' : 'Hello!', "\n\n";
}

if (! empty($introLines)) {
    echo implode("\n", $introLines), "\n\n";
}

if (isset($actionText)) {
    echo "{$actionText}", "\n", "{$actionUrl}", "\n\n";
}

if (! empty($outroLines)) {
    echo implode("\n", $outroLines), "\n\n";
}

echo trans('sentinel.notify_footer_message'), "\n";
echo config('app.name'), trans('sentinel.system_mail'), "\n";

以上で、通知用のメール書式が設定できました。引き続き、通知を作成します。


通知(Notify)を作成

通知は、連絡先の情報を持ったモデルから notify メソッドを呼び出すことで、メッセージを送ることができる機能です。メール以外にも、データベースやSMS、Slackなどに通知ができます。通知を利用するには、該当するモデルに Notifiable トレイトを宣言します。

ユーザー登録通知を作成する

先に作成した RegisterNotify.php の中身を実装して、ユーザーに確認用のリンクを送信します。

  • app/Notifications/RegisterNotify.php を開いて、以下のようにする
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;

class RegisterNotify extends Notification
{
    use Queueable;

    /** 認証コード*/
    private $code;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($code)
    {
        $this->code = $code;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject(trans('sentinel.register_user_title'))
            ->greeting($notifiable->name.trans('sentinel.register_user_name'))
            ->line(config('app.name').trans('sentinel.register_user_intro'))
            ->action(trans('sentinel.register_user_button'),
                url('activate', [base64_encode($notifiable->email), $this->code]))
            ->line(trans('sentinel.register_user_outro'));
    }
}

以上で、通知が完成です。 npm test でテストを実行して、成功することを確認してください。その後、 https://mailtrap.io を確認するとアクティベーションのためのリンクをつけたメールが届きます。HTMLとTextの中身をそれぞれ確認してみてください。



以上で、ユーザーの登録は出来上がりです。次は、アクティベーションを実装します。



前へ | 次へ