バリデーションの設定
今回のお題
今回のお題は、バリデーション(後編)です。
前回の作業で、データ型に対してはバリデーションとエラーメッセージが設定できました。
今回は、それ以外のバリデーション(文字数制限など)を設定していきます。
目次
ライブラリの追加
Spring Bootでは、ライブラリを追加することでバリデーションを簡単に実装可能です。
まずは、pom.xmlを編集してライブラリを追加します。
<dependencies>タグ内に以下を追加。
<groupId>org.springframework.boot</dependency>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
アノテーションのimport
Spring Bootでは、フィールド名の前にアノテーションをつけることで、そのアノテーションに対応したバリデーションが設定されます。
今回は、フィールドの定義はSignupForm.javaで行っているので、まずはこのファイルに必要なアノテーションをimportします。
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.validation.constraints.Pattern;
import javax.hibernate.validator.constraints.Length;
バリデーションの適用
次に、importしたアノテーションを用いてそれぞれのフィールドにバリデーションを設定していきます。
以下、代表的なアノテーションと使用例です。
@NotNull
指定したフィールドがnullでないことを要求します。
例)
@NotNull
private String userId;
@NotBlank
指定したフィールドがnull, 空文字、空白スペースでないことを要求します。
例)
@NotBlank
private string userName;
@Min, @Max
Integer型のフィールドに対して、最大値と最小値を設定します。
例)
@Min(20)
@Max(100)
private Integer age;
@Length
文字数の範囲を規定します。
例)
@Length(min = 4, max = 100)
private String password;
@Pattern
正規表現を用いてバリデーションをかけます。
例)
@Pattern(regexp = "^[a-zA-z-0-9]+$")
private String password;
これで、各フィールドに対してバリデーションが設定されました。
コントローラの編集
次に、コントローラを編集してバリデーションを有効にします。
やり方は簡単で、postSignupメソッドの引数部分を修正するだけです。
public String postSignup(Model model, Locale locale, @ModelAttribute @Validated SignupForm signupForm, BindingResult bindingresult){
.....以下略.......
}
@Validatedアノテーションをつけることで、postSignupメソッド内ではSignupFormクラスに対してバリデーションがかかります。
バリデーションメッセージの編集
最後にバリデーションメッセージの編集を行います。
以前にtypeMissmatchのエラーメッセージを記述するためにValidationMessages.propertiesとValidationMessages_en.propertiesを作成しました。
今回はそこにメッセージを追加していきます。
メッセージ例
NotBlank={0}は入力必須です。
NotNull={0}は入力必須です。
Max={0}は{1}以下で入力してください。
Min={0}は{1}以上で入力してください。
Length={0}は{2}文字以上{1}文字以下で入力してください。
メッセージを設定するためのルールは以下の3点です。
- "バリデーション名 = そのバリデーションに設定するメッセージ"の形式で記述する。
- {0}にはフィールド名、{1}以降にはそのバリデーションの引数が入る。
- Lengthなどの引数が複数あるものに関しては、どちらが{1}に対応するかはバリデーションの側であらかじめ決まっている。
Lengthでいえば、{1}は最大値で{2}が最小値。
また、この方法だとフィールド名に関しては変更されません。
例えば、userName→ユーザー名のように変更したければ、messages.propertiesを編集してください。
バリデーションの設定〜データ型編
今回のお題
前回まででフォームに入力ができるようになりました。
今回は、予告通りにバリデーションを設定していきます。
なお、今回バリデーションをかけるのはデータ型のみです。
それ以外の、nullでも通るかどうかや文字数の制限に関してなどは次回取り扱います。
目次
- バリデーションメッセージ用のプロパティファイルの作成
- 読み込み経路の記述
- メッセージの記述
- コントローラの編集
- ビューの編集
プロパティファイルの作成
まずは、バリデーションエラーがでた際のメッセージを管理するプロパティファイルを作成します。
以前にプロパティファイルを作成しているので、その中に
- ValidationMessages.properties
- ValidationMessages_en.properties
を作成します。
読み込み経路の記述
次に、このファイルが読み込まれるようにapplication.propertiesを編集します。
// 緑部分を追加
spring.messages.basename=il8n/messages, il8n/ValidationMessages
メッセージの記述
読み込み経路が設定できたところで、実際のメッセージを記述していきます。
基本文型は、typeMissmatch."モデルのキー名"."フィールド名"です。
例えば今回はsignupFormというキーでSignupFormクラスのインスタンスをビューに渡しているので、ageに対してのメッセージを設定したい場合には以下のように記述します。
typeMissmatch.signupForm.age="数字で入力してください"
これで、フォームの年齢入力欄に数字以外が入力された場合のエラーメッセージを設定できました。
ちなみに、フォームのキー名については省略することも可能です。
その場合、全てのフォームのageフィールドに対して、上記のエラーメッセージが設定されたことになります。
コントローラの編集
次に、コントローラを編集します。
目的は2つで、「バリデーションエラーがあった場合に元の画面に戻るようにすること」、と「エラーメッセージをビューに渡すこと」です。
それでは、以下のようにコントローラを編集します。
import編
import org.springframework.validation.BindingResult;
postSignupメソッド編
// 引数の追加
public String postSignup(Model model, @ModelAttribute SignupForm form, BindingResult bindingResult, Locale locale){
// エラーがあった場合に元の画面に戻るように条件分岐
if(bindingResult.hasErrors()){
return getSignup(Model, Locale, form);
}
....以下略.....
今回のポイントはBindingResultクラスですね。
このクラスのインスタンスを引数として与えることで、バリデーションに関わる様々なメソッドを用いることができるようになります。
今回使用したbindingResult.hasErrors()メソッドは、エラーがある場合にtrueを返すメソッドになります。
これを用いてエラーの有無を判定し、必要に応じてgetSignupメソッドを呼び出しています。
また、その際にModelやLocaleなどが引数として必要になるので、postSignupメソッドを呼び出す際にあらかじめ用意しているというわけです。
ちなみに、postSignupメソッドの引数についてですが、bindingResultは必ずformの直後に来る必要があるそうです。
ビューの編集
最後に、ビューを編集します。
今回新しく登場する知識は、以下のものになります。
- th:errorclass="is-invalid"
- class="invalid-feedback"
- th:errors="*{フィールド名}"
- th:if
- ${#fields.hasErrors('フィールド名')}
これらを用いて編集した内容が以下になります。
<input th:field="*{age}" th:errorclass="is-invalid">
<div class="invalid-feedback" th:errors="*{age}">
<div th:each="item:${genderMap}">
<input type="radio" errorclass="is-invalid" th:field="*{gender}" value="${item.value}">
</div>
<div class="invalid-feedback" th:if="{${#fields.hasErrors('gender')}" errors="*{gender}">
th:errorclass="is-invalid"
エラーが発生した場合にだけ、"is-invalid"クラスが付与されます。
is-invalidはbootstrapで用意されているクラスです。
invalid-feedback
エラーメッセージにつけるためのbootstrapクラスです。
同じタグ内にis-invalidクラスが存在する場合にのみdisplay:block;に、それ以外ではdisplay:none;になります。
th:errors="*{フィールド名}"
指定したフィールド内でエラーが発生していた場合にそのエラーメッセージをテキストとして表示します。
th:if="条件式"
if文をthymeleafの中で使用することができます。
${#fields.hasErrors('フィールド名')}
そのフィールド内でエラーが発生しているかどうかをチェックできます。
ラジオボタンのような、複数のフォーム部品に共通のフィールドがバインドされている場合に使用します。
Formのバインドについて
本日のお題
本日のお題は、フォームのバインドについてです。
目次
- フォームのバインドとは
- クラスの作成
- コントローラの編集〜getSignup編
- ビューの編集
- コントローラの編集〜postSignup編
フォームのバインドとは
フォームのバインドとは、フォームのname属性をキーとして入力内容をコントローラで取り出すための処理のことです。
例えば、RailsやLaravelであれば以下のような処理でフォームの内容を簡単に取り出すことができていました。
Rails: params[:age]
Laravel: $request->input("age");
これは、この記述だけで意図したものを取り出せるようにあらかじめフレームワークの側でプログラムが組まれているためなのですが、Spring Bootではこの記述をある程度自分自身で行う必要があります。
以下、順番にそのための作業をしていきます。
クラスの作成
まず、フォームの内容を管理するためのJavaクラスを作ります。
今回は、サインアップフォームを作成するので、demo(アプリ名)/applicationディレクトリにform/SignupForm.javaを作ります。
ここではまず、フォームで入力してもらいたい要素(ユーザー名や年齢など)を、SignupFormクラスのインスタンスフィールド化します。
ーーSignupForm.javaーー
package com.example.demo.apprication.form.SignupForm;
import lombok.Data;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
@Data
public class SignupForm{
private String userId;
private String userName;
private String password;
@DateTimeFormat(pattern = "yyyy/mm/dd")
private Date birthday;
private Integer age;
private Integer gender;
}
ポイントになってくるのは、赤線部分の2行ですね。
前半の@DateTimeFormat(pattern = "yyyy/mm/dd")は、指定したフォーマットで入力された文字列をDate型に変換してくれる役割を持っています。
例えばフォームで"1990/01/01"と入力しても、そのままだと"1990/01/01"という文字列(Stirng型)として記録されてしまいます。
ですがこのアノテーションをつけることによって、直後に定義しているbirthdayというインスタンスフィールドに対して、"yyyy/mm/dd"形式の文字列がDate型に変換された状態でセットされるようになります。
コントローラの修正〜getSignup編
フォームを定義するモデルが定義できたので、コントローラを修正します。
まずは、フォームのある画面に遷移するためのgetSingupメソッドを修正します。
importの追加
import org.springframework.web.bind.annotation.ModelAttribute;
import com.example.demo.application.form.SignupForm;
import lombok.exterm.slf4j.Slf4j;
getSignupメソッドの編集
public String getSignup(Model model, Locale locale, @ModelAttribute SignupForm form){
// メソッドの中身はそのままでOK
}
ポイント
ポイントは、引数として追加されている@ModelAttribute SignupForm formですね。
これを追加することで、以下の作業を自動で行ってくれています。
model.addAttribute("signupForm", form);
つまり、先程SignupFormクラスで定義した内容からformというインスタンスを作り、それをsignupFormというキーでビューに渡せるようにしてくれているのですね。
次に、このsignupFormの内容をビューで表示できるようにしていきます。
ビューの編集
ビューの編集のポイントは、object属性とfield属性を使うということです。
どちらもthymeleaf専用の属性ですね。
以下、使用例です。
<form th:object="${signupForm}" .....>
<input type="text" th:field="*{userName}" ....>
.....以下略.....
まず、form要素に対してth:object="{$signupForm}"としてこのフォームをどのクラスにバインドするのかを指定します。
次に、<input th:field="*{userName}"として、各フォーム部品をバインド先のクラスのどのインスタンスフィールドに対応させるのかを指定します。
$ではなく*を用いているところに注意してください。
これで、フォームに入力された値をコントローラで受け取れるようになりました。
th:fieldについての補足
th:field="*{userName}"とした場合、name="userName" id="userName"に変換されてビューに表示されます。
コントローラの編集〜postSignupメソッド編
ビューができたので、次にフォームの内容をコントローラで受け取れるようにしていきます。
以下のようにメソッドを編集します。
// 引数を追加
public String postSignup(@ModelAttribute SignupForm form){
一旦は、フォームの内容をログで出力
log.info(form.toString());
return "redirect: /login";
}
本来ならば、受け取ったパラメータをDBに保存する処理を書くのですが、一旦はログ出力してどのような形でパラメータが送信されているのかを見てみます。
ちなみに、このlog.infoメソッドは、@slf4jアノテーションを用いることで使用可能になります。
フォームから受け取った内容
SignupForm(userId=213399, password=0000000a, userName=test, birthday=Mon Jan 01 00:00:00 JST 1990, age=31, gender=1)
フォームから受け取った内容は以上のようになっています。
終わりに
とりあえずこれで、フォームに入力された値を受け取ることが可能になりました。
本来であればこのあとはDBへの登録処理などを記述していくのですが、その前に次回ではバリデーションを設定していきたいと思います。
WithoutMiddlewareを使う場合の注意点
今回のお題
今回のお題は、テストコードのWithoutMiddlewareについてです。
復習〜WithoutMiddlewareとは
まずは、WithoutMiddelwareそのものについての復習です。
これは、Basic認証などのmiddlewareに設定されている認証機能をテスト中だけ無効化するために記述しました。
ですが、これにより本来は働いて欲しいmiddlewareの機能まで無効化されてしまう場合があります。
テスト中も働いて欲しいmiddleware機能の例
テスト中も働いて欲しいmiddleware機能の例は、Auth系のユーザー認証機能ですね。
これらはmiddlewareに定義されている部分もあるので、無効化してしまうとテストコードがうまくいかなくなります。
対策
この場合の対策は単純で、無効にするmiddlewareを指定してあげれば大丈夫です。
テストメソッド定義部分の
use WithoutMiddleware;
を
$this->WithoutMIddleware([BasicAuthMiddleware::class]);
に変更してください。
これで、Basic認証だけを無効にすることができました。
Laravelアプリにおけるテストコード〜基礎編
本日のお題
本日のお題は、Laravelアプリのテストコード(基礎編)です。
目次
- テストコードのファイルについて
- 既存のテストコードファイル
- 新しいファイルの追加
- ファイルの中身の編集
- テストの実行
テストコードのファイルについて
テストコードのファイルに関して、最初に押さえておくことは以下の2つです。
- テストコードは、testsディレクトリに配置する。
- testsディレクトリの中にはFeatureディレクトリとUnitディレクトリがあり、いわゆる単体テストは前者、結合テストは後者の中にファイルを作成する。
まずはこれを押さえた上で、次に進みましょう。
既存のテストコードファイル
上記のFeatureディレクトリとUnitディレクトリには、それぞれ既存のファイルが一つづつ用意されています。
それぞれ、中身を見ていきましょう。
Feature/EampleTest.php
class ExampleTest extends TestCase{
use WithoutMiddleware;
....中略.....
public function test_example(){
$response = $this->get("/");
$response->assertStatus(200);
}
}
$response = $this->get("/");は"/"にgetメソッドでリクエストを送るための記述であり、$response->assertStatus(200);はレスポンスのステータスコードが200であることを確認するためのものです。
なので、"/"にリクエストを送信して成功すれば、このテストも成功ということになります。
ちなみに、もう一つのUnit/ExampleTest.phpには以下の一文しかありません。
public function test_example(){
$this->assertTrue(true);
}
なお、assertTrueメソッドは、引数がtrueかどうかをテストするためのものなので、テストが動けば必ず成功します。
assertXxx系のメソッドについては、いずれまとめると思います。
ファイルの追加
ファイルを追加するには、以下のコマンドを使用します。
これで、指定したファイルがTests/Featureディレクトリに作成されます。
テストコードファイルの編集
基本的には、テスト内で実行したい処理を順番に記述していくだけです。
ですが、そのためのメソッドは知っておかないといけないので、代表的なものを載せておきます。
- get("xxx")
引数として与えたURLに対し、getメソッドでリクエストを送った場合のレスポンスを取得する。そのまま変数に代入することも可能。
例)$response = $this->get("/"); - post("/", "xxx")
上記のgetメソッドのHTTPメソッドをPOSTに変更したバージョンです。
POSTメソッドは何かしらのパラメータをコントローラに送信しているはずなので、それらを第二引数に連想配列の形で指定します。
以下の例では、"items"というパスにname="ゲーム機", price=10000というパラメータを送信しています。
$response = $this->post("/items", [ "name" => "ゲーム機", "price" => 10000 ]); - $response->assertStatus(xxx)
レスポンスが返って来た際のHTTPステータスコードがxxxであるかどうかをテストします。 - $response->assertRedirest("/xxx");
指定したパスにリダイレクトされるかどうかをテストします。
テストの実行
テストの実行は、php artisan testコマンドで実行可能です。
このコマンドを叩くことで、testsディレクトリ以下の全てのテストが実行されます。
まとめ
基礎編についてはこの辺りで。
次回は、テストコード用のメソッドかFactory系についてまとめると思います。
Laravelアプリにおけるテストコード〜準備編
本日のお題
本日のお題は、Laravelアプリのテストコードについてです。
目次
専用DBの作成
まずは、テスト専用のDBを作成します。
例えばRailsアプリでDBを作成した場合、"アプリ名"_developmentと"アプリ名"_testという2つのDBが同時に作成されます。
これは、開発環境で保存したレコードがテストに影響を与えないための措置なのですが、Laravelアプリではテスト環境用のDBを自分で用意しなければなりません。
なので、まずはこれを用意します。
----以下、ターミナルでの作業----
mysql -u root
CREATE DATABASE xxxx_test;
あとはshow databasesでDBが作成されていることが確認できればOKです。
phpunit.xmlの修正
このファイルはテスト環境に関するさまざまな設定を管理しており、ここを編集することで、テスト環境での接続先のDBを自由に決めることができます。
ファイルの後半部分にある<php>タグで囲まれた部分に、以下の一文を追加します。
<server name="DB_DATABASE" value="xxxx_test"/>
"xxxx_text"の部分には、先ほど作成したDBの名前を入れてください。
これで、テスト環境での接続先DBが設定できました。
.env.testingの作成とマイグレーション
新しいDBに接続はできるようになったのですが、まだマイグレーションが行われていません。
まず、.env.testingというファイルを作成してDB_DATABASE=の部分をテスト用のDBに変更します。
これは、このあと--env=testingオプションをつけてマイグレーションする際の参照先のファイルを作っている作業です。
そして、以下のコマンドでマイグレーションを行います。
php artisan migrate --env=testing
これで、テスト用のDBにテーブルが作られます。
Basic認証の解除
次に、Basic認証を解除します。middlewareを用いたBasic認証を設定しているのですが、そのままにしているとテストが全て失敗してしまいます。
なので、テストコード記述ファイルに以下を記述します。
// クラス定義部分より前に
use Illuminate\Foundation\Testing\WitouhtMiddleWare;
// クラス定義部分の最初に
use WithoutMiddleware;
WithoutMiddlewareを用いて、middlewareに設定したBasic認証を無効化しています。
終わりに
とりあえずこれで、テストコードを記述する準備は整いました。
次回以降、本格的にテストを行っていきます。
laravelアプリ作成備忘録〜MySQL編
前置き
コントローラやモデルなどの記述は頻度が多いので徐々に慣れてきたのですが、DB設定やデプロイなどでかなり躓いたので残しておこうと思います。
やはり、実施頻度の低いものはなかなか難しいものがありますね。
何回かに分ける予定ですが、今日のところはプロジェクト作成時のDB作成についてお話しします。
DBはMySQLを利用します。
手順
- ルートユーザーとしてログイン
- ユーザーの作成
- 権限の付与
- 権限ユーザーとしてログイン
- DBの作成
- アプリの設定ファイルに作成したDBの情報を記述
この順番でお話ししていきます。
ルートユーザーとしてのログイン
DBの作成などは