読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

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

Laravel5.3とVue.jsを組み合わせたページをCodeceptionでテストする

PHP Laravel Codeception Vuejs

LaravelのPHPのみのページのテストは、CodeceptionのFunctionalテストで問題なく動きますが、Vue.jsを利用したようなJavaScriptを使ったページは、Functionalテストでは完全にテストできないようです。そのような場合は、実際のWebブラウザー上でテストするAcceptanceテストを利用します。

Seleniumを利用したり、テスト環境を利用する方法で手間取ったので、動作に必要だった項目をまとめます。


PhpBrowserのテストの欠点

03-AcceptanceTests - Codeception - Documentationによると、以下のような欠点があります。

  • you can click only on links with valid urls or form submit buttons
    • クリックできるのは、有効なURLやフォームのSubmitボタンのみ
  • you can’t fill fields that are not inside a form
    • フォーム内のフィールドに入力することができない
  • you can’t work with JavaScript interactions: modal windows, datepickers, etc.
    • モーダルウィンドウやdate pickerをはじめとするJavaScriptの動作が利用できない

Vue.jsはJavaScriptですので、PhpBrowserによるテストはできません。WebDriverを使う必要があるということです。


CodeceptionとWebDriverの動作環境は別

Codeceptionのテストコードもサービスのサーバ側のPHPプログラムも同じPHPプログラムですが、Acceptanceテストではテストコードとサーバ側プログラムは動作環境が別になります。そのため、以下の2点に注意が必要です。

  • .env.testingファイルは、CodeceptionとWebDriverの双方に読み込ませる設定が必要
  • Acceptanceテストコードから、LaravelやSentinelのヘルパー関数を使ってWeb側のアプリの情報を読み取ることはできない

Functionalテストでは、テストと実行環境が同じなので上記のいずれも問題が起きません。Acceptanceテストでの注意点です。


.envと.env.testingの読み分け

Codeception用の設定

Acceptanceテスト用の設定は以下のようにします。Chromeでテストする例です。

  • tests/acceptance.suite.yml をエディターで開く
  • 以下の内容にする
class_name: AcceptanceTester
modules:
    enabled:
        - WebDriver:
            url: http://localhost:8000
            browser: chrome
        - Laravel5:
            environment_file: .env.testing
            part: ORM
            cleanup: false
        - \Helper\Acceptance

以上で、Acceptanceテストを実行すると、Chromeが起動して、 http://localhost:8000 をベースのURLとしてテストが始まります。テストコードからLaravelのfacadeやヘルパー関数が呼び出せます。

Webブラウザー側の設定

acceptance.suite.yml の設定は、Codeceptionで動作するテスト用のものであり、Webブラウザー側とは別です。Webブラウザー側でも .env.testing を読み込ませるには、以下でサービスを起動します。

php artisan serve --env=testing

--env=tesging オプションにより、 .env.testing を環境ファイルとして読み込むようになります(Configuration - Laravel - The PHP Framework For Web Artisans)。

以上で、テスト環境と実行環境のどちらも .env.testing を読み込むようになります。


データベースの初期化

Acceptanceテストでは、トランザクションがテスト側から管理できないため、テスト中に登録したデータの削除ができません(Codeception for Laravel)。そこで、実行前に tests/acceptance/TestCest.php などの _before メソッドで以下を実行して、データベースを初期化すると良いでしょう。

    public function _before(AcceptanceTester $I)
    {
        // データベースを削除
        Artisan::call('migrate:refresh');
    }



テスト時に必要な準備

テストを実行するには、以下を準備しておく必要があります。

  • Selenium Standalone Server のjarファイルをダウンロードして起動(PhatomJSでも良いようだが、うまく動かなかったのでSeleniumを利用)
    • http://www.seleniumhq.org/download/ から、最新の Selenium Standalone Server をダウンロード
    • 以下で起動(3.0.1の部分は、実際にダウンロードしたバージョン番号に置き換える)
java -jar selenium-server-standalone-3.0.1.jar
  • サービスを起動
php artisan serve --env=testing
  • データベースサーバーを起動。mysqlの場合は以下
sudo mysqld_safe

以上が完了したら、以下でテストを実行できる。

composer exec codecept run



Codeceptionで利用できるメソッド

WebDriverのものが利用できます。

WebDriver - Codeception - Documentation

特に、以下のものが有用です。

  • waitForText()
    • テキストが表示されるのを待つ
  • waitForElementVisible()
    • 要素が表示されるのを待つ
  • acceptPopup()
    • ポップアップのOKなどを押す
  • cancelPopup()
    • ポップアップをキャンセルする
  • seeInPopup()
    • ポップアップに指定の文字が表示されているかを確認
  • typeInPopup()
    • ポップアップに文字を入力



まとめ

  • JavaScriptのテストには、WebDriverを使ったAcceptanceテストが必要
  • 環境の設定は、Codeception用のものと、Webブラウザー用のものをそれぞれやる必要がある
  • データベースはリセットされないので、必要に応じて自分でクリアする
  • Acceptanceテストには、SeleniumかPhantomJSが必要(PhantomJSで日本語をチェックするには環境設定が必要かもしれない。Seleniumの方が簡単に動く)

おおよそ、以上で動くと思います。


参考URL

Laravel Elixirでwebpackを使ってCodeSplittingをする

PHP Laravel Webpack

Laravel5.3のElixirからwebpack 1.13.3でビルドして、JavaScriptのコードを分割をする手順です。ページごとに用意する Entry Chunk である「app.js」と「app2.js」をビルドすると同時に、どちらのJavaScriptファイルでも利用するコードを「common.js」という名前のファイルに書き出します。(Laravelがインストールするwebpackは、2.1.0以降の指定でした。2016/11/26追記)

前提

以下の環境での例です。

  • macOS Sierra
  • Laravel5.3
  • npm 3.10.8

プロジェクトの作成

  • 以下で、Laravelのプロジェクトを作成して、プロジェクトフォルダーに入る
laravel new laravel-code-splitting
cd laravel-code-splitting
  • 以下で、laravelのプロジェクトを起動する
php artisan serve

以上で、 http://localhost:8000 をWebブラウザーで開くと、Laravelと表示されるページが動きます。


コードの作成

app.jsはそのまま利用します。このコードは、Vueで example というタグを定義しています。ページ中にそのタグを書くと、メッセージが表示されます。

app2.js用には、 app2 というルートを定義して、app2.blade.php というテンプレートを用意します。app2.jsには、jQueryを使って本文に「app2.jsでここを書き換えました。」と表示するコードを作成します。

app.jsを動かす

wecomeページのテンプレートに example タグを追加します。

  • resources/views/welcome.blade.php をエディターで開く
  • 80行目付近の「<div class="content">」という行を探して、以下のようにid属性を追加する
            <div id="app" class="content">
  • 84行目付近(Laravelと表示しているdivブロックの下)に以下のコードを追加
                <example></example>
  • 最後から2行目の「</body>」の上に、以下のコードを追加
        <script type="text/javascript" src="./js/app.js"></script>

以上ができたら、上書き保存をして、 http://localhost:8000 をリロードします。先ほどのページに「Example Component」「I'm an example component!」と表示が追加されれば成功です。

app2.jsのコードを作成

app2.js用のコードを追加します。まずは、Webページの雛形を作成します。

  • resources/views/app2.blade.php ファイルを新規作成して、エディターで開く
  • 以下のコードを入力して保存
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel app2</title>

        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">

        <!-- Styles -->
        <link href="/css/app.css" rel="stylesheet">

    </head>
    <body>
        <div class="container">
            <h1 id="page-body" class="bg-info">
                app2.jsが未作成
            </h1>
        </div>
        <script type="text/javascript" src="./js/common.js"></script>
        <script type="text/javascript" src="./js/app2.js"></script>
    </body>
</html>

app2.jsを作成します。

  • resources/assets/js/app2.js ファイルを新規に作成して、エディターで開く
  • 以下のコードを入力して保存
require('./bootstrap');

$('#page-body').text("app2.jsでここを書き換えました。");

次に、ルートを作成します。

  • routes/web.php をエディターで開く
  • ファイルの最後に以下のコードを追加する
Route::get('/app2', function() {
    return view('app2');
});

以上ができたら、 http://localhost:8000/app2 にアクセスします。「app2.jsが未作成」と表示されればここまで成功です。


webpackの設定

app2.jsが正しく動作すると「app2.jsでここを書き換えました。」とページに表示されるはずです。現在動作しないのは、 public/js フォルダーに app2.js が存在しないからです。 app2.js をwebpackでビルドする設定を作成して、必要なファイルを生成します。

環境の構築

ビルドに利用するツール類をインストールします。

  • ターミナルでプロジェクトフォルダーに移動して、以下を実行
npm install

インストールが完了したら、ターミナルで以下を実行します。

gulp

これでデフォルトの設定でJavaScriptがビルドされます。ビルドされたJavaScriptファイルは public/js フォルダー内に生成されます。まだ初期設定のままなので、 app.js しか生成されません。webpackの設定ファイルを用意して、 app2.js が実行可能なEntry Chunkとしてビルドされるようにします。

設定ファイル webpack.config.js の作成

  • プロジェクトフォルダー直下に webpack.config.js というファイルを作成してエディターで開く
  • 以下のコードを入力して保存
var webpack = require('webpack');

module.exports = {
    entry: {
        app: './resources/assets/js/app',
        app2: './resources/assets/js/app2'
    },
    output: {
        filename: '[name].js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: 'common'
        })
    ]
};

webpack.config.js というファイルに設定を書いておくと、 Elixir が自動的に読み込んで適用してくれます。

上記の設定は、実行するためのコードを持ったChunkである Entry Chunk として app.js と app2.js を作成して、両者に共通のコードは common.js にまとめるというものです。

ターミナルで以下を実行してビルドしてください。

gulp

http://localhost:8000/app2 をリロードすると、「app2.jsでここを書き換えました。」と表示されて、無事に app2.js が動作したことが確認できます。

http://localhost:8000 をリロードしてみてください。先ほどは正しく動作していたのに、 example タグのメッセージが消えてしまいました。これは、app2 との共通部分のコードが common.js に移動してしまったからです。以下の作業をして修正します。

  • resources/views/welcome.blade.php をエディターで開く
  • ファイルの下から3行目にある「<script type="text/」から始まる行の上に、以下の行を追加
        <script type="text/javascript" src="./js/common.js"></script>

以上で保存して、 http://localhost:8000 をリロードすると、正しく動作するようになります。


まとめ

Laravelでページの遷移をするサイトを作成する場合、ページごとに利用するJavaScriptを分けた方がコードが減って便利そうだったので、webpackの Multi Entry Chunk を試してみました。

Laravel5.3のElixirには、最初からwebpackのビルド環境が組み込まれていますので、 npm install で環境をインストールして、 webpack.config.js をプロジェクトフォルダー直下に作成して、設定を書けば webpack の各種機能を利用することができます。

設定ファイルに以下のプラグインの設定を追加したので、 app.js と app2.js の双方に出てくるコードを common.js に分離してくれて、コードの重複が減ります。これができるのが webpack の強みのようです。

    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: 'common'
        })
    ]

JavaScript の読み込みは、まず common.js を読み込んでから、app.js か app2.js を呼び出します。

webpack.config.js が正しく設定できた後は、ターミナルで

gulp watch

を実行すると、コードが変更されるたびに自動的にビルドされるので便利です。


Windows7でComposer

Codeception 勉強メモ Windows7

WindowsでComposerを動かす時にやったことのメモです。

PHPのインストール

以下、c:\Tools\php フォルダーにインストールしたことを前提に書きます

  • c:\Tools\php\php.ini-development をコピーして、ファイル名を php.ini に変更
  • php.iniを、Noeditなどのテキストエディターで開く
  • 以下を検索して、行頭の「;」を削除する
    • extension_dir = "ext"
    • extension=php_curl.dll
    • extension=php_mbstring.dll
    • extension=php_openssl.dll

環境設定をします。

  • [スタートメニュー]>[コンピューター]を右クリックして、[プロパティー]を選択
  • [システムの詳細設定]を選択
  • [環境変数]を押す
  • [システム環境変数]欄から[Path]を探して、[編集]ボタンを押す
  • 行末に、phpをインストールしたフォルダーを追記する。[C:\Tools\php]にインストールした場合は以下を追加
;C:\Tools\php
  • [OK]でウィンドウを全て閉じる

opensslのインストール

環境設定をします。

  • [スタートメニュー]>[コンピューター]を右クリックして、[プロパティー]を選択
  • [システムの詳細設定]を選択
  • [環境変数]を押す
  • [システム環境変数]欄から[Path]を探して、[編集]ボタンを押す
  • 行末に、OpenSSLをインストールしたフォルダーを追記する。[C:\Tools\php]にインストールした場合は以下を追加
;C:\OpenSSL-Win32\bin
  • [OK]でウィンドウを全て閉じる

Composerのインストール

  • Composerから Composer-Setup.exe をダウンロード
  • ダウンロードしたインストーラーを実行して、インストールを完了させる


以上で、コマンドラインを起動して、 composer コマンドが利用できるようになります。

macのCodeceptionを使ってBrowserでテストをする

Codeception mac

CodeceptionのAcceptanceテストではWebDriverを使って、ブラウザー上で実際に動作テストができます。問題は、Seleniumの新しいバージョンだと、Firefoxでテストするのが面倒になったことです。特にFirefoxでテストする必要がない場合は、Chromeで簡単にテストできます。

環境

前提

手順

環境セットアップ

  • プロジェクトフォルダーを作成する
  • Selenium公式ページから、Selenium Standalone Serverをダウンロードする
  • ダウンロードしたjarファイルを、プロジェクトフォルダーに移動
  • 以下で、codeceptionをインストールして、環境ファイルを作成
composer require codeception/codeception
composer exec codecept bootstrap
  • tests/acceptance.suite.yml をエディターで開く
  • 以下の内容にする
class_name: AcceptanceTester
modules:
    enabled:
        - WebDriver:
            url: http://www.google.co.jp
            browser: chrome
        - \Helper\Acceptance
  • Downloads - ChromeDriver - WebDriver for Chrome を開いて、最新版のChromeDriverを選択
  • 該当するドライバーをダウンロード。macであれば chromedriver_mac64.zip
  • zipファイルを解凍して、プロジェクトフォルダーにプログラムを置く

テストを作成

  • ターミナルから以下を実行してテストファイルを作成
composer exec codecept g:cept acceptance Welcome
  • tests/acceptance/WelcomeCept.php をエディターで開く
  • 以下のコードを書く
<?php
  $I = new AcceptanceTester($scenario);
  $I->wantTo('ensure that frontpage works');
  $I->amOnPage('/');
  $I->see('Google');

テストの実行

  • ターミナルを開いて、プロジェクトフォルダーに移動して、以下でサーバーを起動
java -jar selenium-server-standalone-*.jar
  • 以下で、テスト実行
composer exec codecept run

以上で、Chromeが起動して、Googleのホームページが開かれて、Googleの文字があるのでテストは成功します。

まとめ

基本的には、Codeceptionの公式ページのWebDriverのヘルプの通りです。新しいFirefoxだと、署名のないAdd-onが使えなくなったことで、そのまま動かなくなっています。方法はあるようですが、面倒なので、browserの部分を「chrome」に変えればとりあえずChromeでのテストはできます。

もしかしたら、Chromeに開発用の設定が必要かも知れません。うちでは上記の通りで動作しました。

Laravel5.3でSentinelを利用する(7)アクティベーションコードの再送

PHP Laravel Sentinel

前へ

ユーザー認証の仕上げに、アクティベーションコードの再送機能を追加します。アクティベーションコードには有効期限があるので、一定時間がすぎると利用できなくなります。また、ユーザーがメールを削除してしまう可能性もあるので、コードの再発行が必要となります。


方針

アクティベーションコードを再送信する方針は以下の通りです。

テストを準備

先に作成していた ActivationUserCest に追加します。

  • tests/functional/ActivationUserCest.php をエディターで開く
  • tryToTest メソッドの最初に、以下のコードを追加(アクティベーション済みの場所ではテストにならないので)
        // アクティベーションの再送チェック
        $I->expect('アクティベーションコードの再送');
        $url = url('register', [base64_encode($this->cre['email'])]);
        $I->amOnPage($url);
        $I->seeInCurrentUrl('/login');
        $I->see(trans('sentinel.after_register'));

        // アクティベーションで無効なemail
        $I->expect('無効なメールアドレスでの要求');
        $url = url('register', [base64_encode('nobody@test.com')]);
        $I->amOnPage($url);
        $I->seeInCurrentUrl('/login');
        $I->see(trans('sentinel.invalid_activation_params'));
  • 登録済みのテストもしたいので、tryToTestメソッドの最後に以下のコードを追加
        // アクティベーションの再送チェック
        $I->expect('アクティベーション済みに対する請求');
        $url = url('register', [base64_encode($this->cre['email'])]);
        $I->amOnPage($url);
        $I->seeInCurrentUrl('/login');
        $I->see(trans('sentinel.activation_done'));

上書きして、 npm test をターミイナルで実行してください。まだルートや処理がないので、エラーが発生します。実装を進めて、エラーをなくします。


ルートの作成

ルートを追加します。

  • routes/web.php をエディターで開く
  • Route::get('register', ・・・);の上に、以下のコードを追加する
Route::get('register/{email}', 'Auth\RegisterController@resendActivationCode');

コントローラーの作成

コントローラーを作成します。メールは、先に作成済みの RegisterNotify をそのまま利用します。

  • app/Http/Controller/Auth/RegisterController.php をエディターで開く
  • 以下の関数を、クラスに追加する
    /**
     * 指定のメールアドレスのアクティベーションコードを再送する
     */
    protected function resendActivationCode(Request $request) {
        // 古いアクティベーションコードを削除
        Activation::removeExpired();

        // ユーザーを確認
        $user = Sentinel::findByCredentials(['email' => base64_decode($request->email)]);
        if (is_null($user)) {
            return redirect('login')->with(['myerror' => trans('sentinel.invalid_activation_params')]);
        }

        // すでにアクティベート済みの時は、何もせずにログインへ
        if (Activation::completed($user)) {
            return redirect('login')->with(['info' => trans('sentinel.activation_done')]);
        }

        // アクティベーションの状況を確認
        $exists = Activation::exists($user);
        if (!$exists) {
            // 存在しない場合は、再生成して、そのコードを送信する
            $activation = Activation::create($user);
        }
        else {
            // 現在のコードを
            $activation = $exists;
        }

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

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

ビューの調整

アクティベーションが済んでいない時に、アクティベーションコードを再送信するためのリンクを表示するようにします。

  • resources/views/auth/login.blade.php をエディターで開く
  • パスワード欄と Remember Me のチェックボックスの間(53行目付近)に、以下のコードを追加
                        @if(isset($resend_code))
                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <a class="btn btn-link" href="{{ url('register', base64_encode($resend_code))}}">
                                    ユーザー登録を完了させるメールを再送
                                </a>
                            </div>
                        </div>
                        @endif

以上で完了です。 npm test でテストを実行すると成功するようになります。

手動で動きを確認する場合は、 http://localhost:8000 に接続して、ユーザー登録をした後、アクティベーションをメールで行う前に、ログインを試してみてください。アクティベートが未完了である旨、メッセージが表示されて、アクティベートコードの再送信のリンクが表示されます。リンクをクリックすると、アクティベートコードが再送信されます。その時点で、アクティベートコードが古くなっていたら、新しいアクティベートコードを生成して、そのコードをメールします。


      • -

以上で、Laravelの認証の機能を全て置き換えることができました。ユーザー登録にメールによる確認が必要になるのはメリットが大きいです。

編集可能なロールやパーミッションを使った認可機能がつけば、さらにSentinelに変更した効果が高まります。

Laravel5.3対応のものは、後日作成します。



前へ


Laravel5.3でSentinelを利用する(6)パスワードのリセット

PHP Sentinel Laravel

前へ | 次へ

パスワードを忘れた時の対応を実装します。


はじめに

login画面のパスワードリセットに対応します。SentinelとLaravelでは手続きの内容が異なるので、自前で実装します。流れは以下の通りです。

  1. GET password/reset
    1. リセット用の画面を表示
    2. メールアドレスを入力させて、POSTで送信
  2. POST password/email
    1. メールアドレスを受け取る
    2. SentinelのReminderを作成して、リセットトークンを生成
    3. コードを添えたメールを送信
  3. GET password/reset/{token}
    1. メールから上記にアクセスさせて、リセットトークンを送信内容に含める
    2. メールアドレスとパスワード、パスワードの確認の3つを入力してPOST送信
  4. POST password/reset
    1. 上記のデータを受け取って、パスワードのリセットを実施する

テストの作成

パスワードの再発行のためのテストを作成します。

ボタンを押すフォームを指定するためのIDをビューに追加します。

  • resources/views/auth/passwords/email.blade.php を開く
  • 「<form」から始まる行を探して、以下のように id 属性を追加する
                    <form id="sendResetForm" class="form-horizontal" role="form" method="POST" action="{{ url('/password/email') }}">
  • resources/views/auth/passwords/reset.blade.php を開く
  • <form から始まる行を探して、以下のように id 属性を追加する
                    <form id="resetPasswordForm" class="form-horizontal" role="form" method="POST" action="{{ url('/password/reset') }}">

必要であれば、ページの内容も書き換えてください。

続いて、テストコードを作成します。

  • ターミナルで以下を実行して、テスト用のCestファイルを作成
composer exec codecept g:cest functional ResetPassword
  • tests/functional/ResetPasswordCest.php をエディターで開く
  • 以下のようにする
<?php

use Sentinel;
use Reminder;

class ResetPasswordCest
{
    private $cre = [
        'name' => 'パスワード再発行',
        'email' => 'reset@test.com',
        'password' => 'password'
    ];

    public function _before(FunctionalTester $I)
    {
        // ユーザーを作成
        Sentinel::registerAndActivate($this->cre);
    }

    public function _after(FunctionalTester $I)
    {
    }

    // tests
    public function tryToTest(FunctionalTester $I)
    {
        // 存在しないメールへのリクエスト
        $I->amOnPage('/password/reset');
        $I->submitForm('#sendResetForm', [
            'email' => 'nobody@test.com'
        ]);
        $I->seeInCurrentUrl('/login');

        // メール発行(resetとemailのテスト)
        $I->amOnPage('/password/reset');
        $I->submitForm('#sendResetForm', [
            'email' => $this->cre['email']
        ]);
        $I->seeInCurrentUrl('login');
        $I->see(trans('sentinel.password_reset_sent'));

        // メール発行テスト
        $user = Sentinel::findByCredentials($this->cre);
        \PHPUnit_Framework_Assert::assertEquals($user->email, $this->cre['email']);
        $reminder = Reminder::exists($user);
        \PHPUnit_Framework_Assert::assertNotFalse($reminder);

        // 不正なトークンでのアクセスして、パスワードの変更を試みる
        $I->amOnPage('/password/reset/01234567891123456789212345678931');
        $I->submitForm('#resetPasswordForm', [
            'email' => $this->cre['email'],
            'password' => $this->cre['password'],
            'password_confirmation' => $this->cre['password'],
        ]);
        $I->see(trans('sentinel.password_reset_failed'));

        // 正しいトークンでのアクセスして、パスワード確認のミス
        $I->amOnPage('/password/reset/'.$reminder->code);
        $I->submitForm('#resetPasswordForm', [
            'email' => $this->cre['email'],
            'password' => $this->cre['password'],
            'password_confirmation' => 'invalidinvalid',
        ]);
        $I->see('does not match');

        // 正しいトークンでのアクセスして、パスワード変更
        $I->amOnPage('/password/reset/'.$reminder->code);
        $I->submitForm('#resetPasswordForm', [
            'email' => $this->cre['email'],
            'password' => 'resetpassword',
            'password_confirmation' => 'resetpassword',
        ]);
        $I->see(trans('sentinel.password_reset_done'));

        // パスワードが変更されたことを確認
        $check = [
            'email' => 'reset@test.com',
            'password' => 'resetpassword'
        ];
        \PHPUnit_Framework_Assert::assertNotFalse(Sentinel::authenticate($check));
        // パスワードが変更されたことを確認
        $check = [
            'email' => 'reset@test.com',
            'password' => $this->cre['password']
        ];
        \PHPUnit_Framework_Assert::assertFalse(Sentinel::authenticate($check));
    }
}

ルートの作成

パスワードリセット画面の呼び出しと、パスワードリセットを実行するPOSTルートを定義します。

// パスワードのリセット用のルート
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
Route::post('password/reset', 'Auth\ResetPasswordController@reset');

リマインダーに必要なメッセージを追加

リマインダーに関連する文言をメッセージに追加しましょう。

  • resources/lang/ja/sentinel.php をエディターで開く
  • 以下のメッセージを配列に追加する
    // パスワードの再設定関連
    'password_reset_sent' => 'パスワードを再設定するためのメールを送信しました。届いたメールに記載のリンクをクリックして、パスワードの再設定画面を開いてください。',
    'reminder_title' => 'パスワードをリセットできます。',
    'password_reset_done' => 'パスワードをリセットしました。',
    'password_reset_failed' => 'リセットコードが古くなっていました。もう一度、リセットし直してください。',

メールのビューを変更する場合

パスワードをリセットするためのメールの本文は、Laravelのものをそのまま利用します。変更したい場合は、以下のファイルを書き換えてください。

  • resources/views/auth/passwords/email.blade.php

パスワードのリセットメールを送付するメソッドの作成

既存の ForgotPasswordController.php の該当するメソッドを上書きします。

  • app/Http/Controllers/Auth/ForgotPasswordController.php をエディターで開く
  • SentinelとReminder、既存のメール通知、Requestを使えるように、冒頭に以下の4つのuseを追加する。既存のuse文も必要なので、そのまま残しておく
use Reminder;
use Sentinel;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
use Illuminate\Http\Request;

リマインダーメールを送信する処理を追加します。

  • クラス内に以下のコードを追加。既存のコードは残しておくこと
    /**
     * パスワードを再設定するための処理
     * ユーザーが有効で、パスワードが条件に合致していたら、SentinelのReminderを使って処理する
     */
    protected function sendResetLinkEmail(Request $request) {
        // 古いリマインダーコードを削除
        Reminder::removeExpired();

        // チェック
        $this->validate($request, [
            // emailは必須で、emailの形式で、255文字まで
            // メールアドレスの有無は、不正を避けるためにチェックしない
            'email' => 'required|email|max:255'
        ]);

        // ユーザーを検索
        $user = Sentinel::findByCredentials(['email'=>$request->email]);
        if (is_null($user)) {
            // ユーザーがいなければ成功したような感じにしてログイン画面へ
            return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
        }

        // リマインダーが作成済みなら、それを再送信する
        $code = "";
        $exists = Reminder::exists($user);
        if ($exists) {
            // すでに設定されているので、リマインダーコードを設定
            $code = $exists->code;
        }
        else {
            // 新規にリマインダーを作成して、コードを返す
            $reminder = Reminder::create($user);
            $code = $reminder->code;
        }

        // メールを送信
        $user->notify(new ResetPasswordNotification($code));

        // 成功したら、login画面へ移動
        return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
    }

以上で、既存の ResetPasswordNotification を使って、リセット用のコードをメールで送付します。 npm test を実行すると、エラーが出ますが、メールは送信されます。 http://mailtrap.io にメールをリセットするためのメールが届いていることを確認してください。


パスワードのリセットの実行

パスワードのリセット画面の表示は、既存のままでOKです。最後に、実際にリセットを実行するコントロールメソッドを作成します。

  • app/Http/Controllers/Auth/ResetPasswordController.php をエディターで開く
  • ファイルの最初の方に、利用したいFacadeのための以下のuseを追加
use Sentinel;
use Reminder;
use Illuminate\Http\Request;
  • 以下の reset メソッドを、クラス内に追加する
    /**
     * パスワードを再設定するための処理
     * ユーザーが有効で、パスワードが条件に合致していたら、SentinelのReminderを使って処理する
     */
    protected function reset(Request $request) {
        // 古いリマインダーコードを削除
        Reminder::removeExpired();

        // チェック
        $this->validate($request, [
           // emailは必須で、emailの形式で、255文字まで
           // メールアドレスの有無は、不正を避けるためにチェックしない
           'email' => 'required|email|max:255',
           // passwordは必須で、6文字以上255文字以下で、確認欄と一致する必要がある
           'password' => 'required|between:6,255|confirmed',
           // トークンは必須で、32文字
           'token' => 'required|size:32'
       ]);

       // ユーザーを検索
       $user = Sentinel::findByCredentials(['email'=>$request->email]);
       if (is_null($user)) {
           // ユーザーがいなければ成功したような感じにしてログイン画面へ
           return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
       }

       // リマインダー実行
       $reminder = Reminder::complete($user, $request->token, $request->password);
       if ($reminder) {
           // 成功
           return redirect('login')->with(['info' => trans('sentinel.password_reset_done')]);
       }

       // 失敗
       return redirect('login')->with(['myerror'=>trans('sentinel.password_reset_failed')]);
   }

以上で完了です。 npm test でテストが成功します。 http://localhost:8000/ を開いて、手動でパスワードのリセットができることも確認してみてください。



前へ | 次へ

Laravel5.3でSentinelを利用する(5)ログインとログアウト(2016/11/8更新)

PHP Sentinel Laravel

前へ | 次へ

ログイン処理を、Sentinelに変更します。ルートは前の手順ですでに作成済みなので、postに対するコントローラーの作成を行います。

(2016/11/8 エラーを withErrors で渡すように修正)


ログイン

ログインのテストコードの作成

ログインに関する処理のテストコードを用意します。

まずは、ボタンを押すフォームを指定するためのIDをformタグに追加します。

  • resources/views/auth/login.blade.php をエディターで開く
  • 「<form」から始まる行を探して、以下のようにID属性を追加する
                    <form id="loginForm" class="form-horizontal" role="form" method="POST" action="{{ url('/login') }}">

続けて、Cestファイルを作成します。

  • ターミナルから以下を実行して、ログイン用のCestファイルを作成
composer exec codecept g:cest functional Login
  • tests/functional/LoginCest.php をエディターで開いて、以下のコードにする
<?php

use Activation;
use Sentinel;

class LoginCest
{
    // ユーザー情報
    private $cre = [
        'name' => 'ログイン',
        'email' => 'login@test.com',
        'password' => 'password'
    ];

    public function _before(FunctionalTester $I)
    {
        // ユーザーを登録
        Sentinel::registerAndActivate($this->cre);

    }

    public function _after(FunctionalTester $I)
    {
    }

    // tests
    public function tryToTest(FunctionalTester $I)
    {
        // 無効なログインテスト
        $I->expect('未登録ユーザーのログイン失敗');
        $I->amOnPage('/login');
        $I->submitForm('#loginForm', [
            'email' => 'nobody@test.com',
            'password' => 'notentry'
        ]);
        $I->see(trans('sentinel.login_failed'));

        // ログインの成功テスト
        $I->expect('ログインの実行');
        $I->amOnPage('/login');
        $I->submitForm('#loginForm', $this->cre);
        $I->seeInCurrentUrl('/home');
    }
}

npm test でテストを走らせると、まずはエラーになります。機能を実装してテストが通るようにします。

必要なメッセージの追加

ログインに関連するメッセージを追加します。

  • resources/lang/ja/sentinel.php をエディターで開く
  • 以下のメッセージを追加
    // ログイン関連
    'not_activation' => 'ユーザー登録が完了していません。登録したメールアドレスに、登録確認用のメールを送信してあります。メールを開いて、リンクをクリックしてください。メールを紛失した場合は、下のリンクからメールを再送できます。',
    'login_failed' => 'ログインに失敗しました。正しいメールアドレスとパスワードでログインしてください。',
    'login_throttling' => 'ログイン失敗が規定回数を越えました。一定時間、IPを凍結します。',

ログイン処理の実装

既存のログイン処理のコードを、Sentinelに合わせて書き換えます。

アクティベーションされていない時や、ログイン失敗時の処理もまとめて作成します。アクティベーションされていない時には、アクティベーションコードの再送信をする指示をビューに投げていますが、この辺りの実装は後ほど行います。

  • app/Http/Controllers/Auth/LoginController.php をエディターで開く
  • ファイルの中身を、以下のように書き換える(エラーをwithErrorsで渡すように修正)
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Cartalyst\Sentinel\Checkpoints\NotActivatedException;
use Cartalyst\Sentinel\Checkpoints\ThrottlingException;
use Sentinel;

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

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

    /**
     * ログイン処理
     */
    public function login(Request $request) {
        // バリデーション
        $this->validate($request, [
            'email' => 'required|email|max:255',
            'password' => 'required|between:6,255',
            'remember' => 'boolean',
        ]);

        // 認証処理
        try {
            $this->userInterface = Sentinel::authenticate([
                'email' => $request['email'],
                'password' => $request['password']
            ], $request['remember']);
        } catch (NotActivatedException $notactivated) {
            return view('auth.login', [
                'resend_code' => $request['email']
            ])->withErrors([trans('sentinel.not_activation')]);
        } catch (ThrottlingException $throttling) {
            return view('auth.login')->withErrors([trans('sentinel.login_throttling')."[あと".$throttling->getDelay()."秒]"]);
        }

        if (!$this->userInterface) {
            // エラー
            return view('auth.login')->withErrors([trans('sentinel.login_failed')]);
        }

        return redirect($this->redirectTo);
    }
}

以上で、ログイン、未アクティベーション、IP凍結に対応しました。 npm test で実行したテストが成功します。また、 http://localhost:8000 を開いて、先に登録したユーザーでログインを試すと、今度は home ページに移動して、ログインが成功します。


ログアウト

ログアウトに対応させます。

テストの作成

テストはシンプルなので、Loginのものに実装します。

  • tests/functional/LoginCest.php をエディターで開く
  • tryToTest メソッドのログイン用のテストコードの下に、以下のコードを追加する
        // POSTでログアウトテスト
        $I->expect('ログアウトのテスト');
        $I->amOnPage('/login');
        $I->submitForm('#loginForm', $this->cre);
        $I->submitForm('#logout-form', []);
        $I->seeInCurrentUrl('/login');

        // GETでログアウトテスト
        $I->amOnPage('/logout');
        $I->seeInCurrentUrl('/login');

ルートの追加

ログアウトのルートを設定します。フォームからのリクエストは、postで送信されるので、getとpostの双方にルートを設定します。

  • routes/web.php をエディターで開く
  • 以下のルートをログイン関連の下あたりに追加する
// ログアウト用のルート
Route::match(['get', 'post'], 'logout', 'Auth\LoginController@logout')->name('logout');

コントローラーを追加する

コントローラーはシンプルなので、Loginのものに追加します。

  • app/Http/Auth/LoginController.php
  • 以下のlogoutメソッドをクラスに追加する
    /**
     * ログアウト処理
     */
    protected function logout(Request $request) {
        Sentinel::logout();

        return redirect($this->redirectTo);
    }

ログアウトの実装完了です。 npm test を実行して、成功することを確認して下さい。

以上で、ログインとログアウトが実装できました。 http://localhost:8000/ を開いて、ログインとログアウトを試してください。テストが通っているので、問題なく成功します。



これで最低限の流れは実装できました。残りは、アクティベーションコードの再送や、パスワードの対応などです。



前へ | 次へ