フォームの検証

必須の要素

要素は setRequired() メソッドで必須と印を付けます。その引数は、利用者がその要素を埋めなかったときに表示されるエラーのメッセージの文です。引数を渡さなければ、既定のエラーのメッセージが使われます。

$form->addText('name', '名前:')
	->setRequired('名前を入力してください。');

規則

要素には addRule() メソッドで検証の規則を足します。第 1 パラメータは規則、第 2 パラメータはエラーのメッセージ、第 3 パラメータは検証の規則への引数です。

$form->addPassword('password', 'パスワード:')
	->addRule($form::MinLength, 'パスワードは %d 文字以上でなければなりません', 8);

検証の規則は、利用者がその要素を埋めた場合にだけ確かめられます。

Nette にはあらかじめ用意された規則がいくつもあり、その名前は Nette\Forms\Form クラスの定数です。これらの規則はすべての要素に使えます。

定数 説明 引数の型
Required 必須の要素。setRequired() の別名
Filled 必須の要素。setRequired() の別名
Blank 要素は埋められていてはいけません
Equal 値はパラメータと等しくなければなりません mixed
NotEqual 値はパラメータと等しくてはいけません mixed
IsIn 値は配列の要素のどれかでなければなりません array
IsNotIn 値は配列のどの要素でもあってはいけません array
Valid 要素は正しく埋められていますか(addConditionOn()の中でのみ)

テキストの入力

addText()addPassword()addTextArea()addEmail()addInteger()addFloat() の要素には、次の規則も使えます。

MinLength 文字列の最小の長さ int
MaxLength 文字列の最大の長さ int
Length 長さが範囲内、または長さがちょうどその値 [int, int] または int
Email 正しいメールアドレス
URL 絶対 URL
Pattern 正規表現に一致 string
PatternInsensitive Pattern と同じですが大文字小文字を区別しません string
Integer 整数の値
Numeric 0 以上の整数(数字だけ)
Float 数値
Min 数値の要素の最小値 int|float
Max 数値の要素の最大値 int|float
Range 値が範囲内 [int|float, int|float]

検証の規則 IntegerFloat は、値をそれぞれ整数と浮動小数点数に自動的に変換します。さらに URL の規則は、スキームのないアドレス(たとえば nette.org)も受け付け、スキームを補います(https://nette.org)。PatternPatternInsensitive の式は値の全体に対して当てはまらなければなりません。つまり ^$ の文字で囲まれているかのように扱われます。

項目の数

addMultiUpload()addCheckboxList()addMultiSelect() の要素では、次の規則で選ばれた項目やアップロードされたファイルの数を制限できます。

MinLength 最小の数 int
MaxLength 最大の数 int
Length 数が範囲内、または数がちょうどその値 [int, int] または int

ファイルのアップロード

addUpload()addMultiUpload() の要素では、次の規則も使えます。

MaxFileSize ファイルの大きさの上限(バイト) int
MimeType MIME タイプ。ワイルドカードを使えます('video/*' string|string[]
Image JPEG、PNG、GIF、WebP、AVIF の画像
Pattern ファイル名が正規表現に一致 string
PatternInsensitive Pattern と同じですが大文字小文字を区別しません string

MimeTypeImage には PHP の fileinfo 拡張が要ります。ファイルや画像が求める種類かどうかはその署名から見分けられ、ファイル全体の健全さは確かめられません。 画像が壊れているかどうかは、たとえば読み込んでみることで判断できます。

エラーのメッセージ

PatternPatternInsensitive を除くあらかじめ用意された規則には既定のエラーのメッセージがあるので、省略できます。とはいえ、あなたの用途に合わせて独自のメッセージをすべて用意して言葉を練れば、フォームはもっと使いやすくなります。

既定のメッセージは設定で変えられますし、Nette\Forms\Validator::$messages 配列の文を書き換えても、翻訳器を使っても変えられます。

エラーのメッセージの文では、次のプレースホルダの文字列を使えます。

%d 規則の引数で順に置き換えられます
%n$d 規則の n 番目の引数で置き換えられます
%label 要素のラベルで置き換えられます(コロンなし)
%name 要素の名前で置き換えられます(たとえば name
%value 利用者が入力した値で置き換えられます
$form->addText('name', '名前:')
	->setRequired('%label を入力してください');

$form->addInteger('id', 'ID:')
	->addRule($form::Range, '%d 以上 %d 以下', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::Range, '%2$d 以下 %1$d 以上', [5, 10]);

条件

規則のほかに条件も足せます。書き方は規則と似ていますが、addRule() の代わりに addCondition() メソッドを使い、当然ながらエラーのメッセージは渡しません(条件は尋ねるだけだからです)。

$form->addPassword('password', 'パスワード:')
	// パスワードの長さが 8 を超えないなら
	->addCondition($form::MaxLength, 8)
		// 数字を含まなければなりません
		->addRule($form::Pattern, '数字を含めてください', '.*[0-9].*');

条件は addConditionOn() を使って、今の要素とは別の要素に結び付けられます。第 1 パラメータはその要素への参照です。この例では、チェックボックスが入っている(つまりその値が true の)場合にだけメールが必須になります。

$form->addCheckbox('newsletters', 'ニュースレターを受け取る');

$form->addEmail('email', 'メール:')
	// チェックボックスが入っているなら
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// メールを必須にします
		->setRequired('メールアドレスを入力してください');

条件は elseCondition()endCondition() を使って込み入った構造に組み立てられます。

$form->addText(/* ... */)
	->addCondition(/* ... */) // 最初の条件が満たされ
		->addConditionOn(/* ... */) // 別の要素についての 2 つめの条件も満たされるなら
			->addRule(/* ... */) // この規則を求めます
		->elseCondition() // 2 つめの条件が満たされないなら
			->addRule(/* ... */) // これらの規則を求めます
			->addRule(/* ... */)
		->endCondition() // 最初の条件に戻ります
		->addRule(/* ... */);

addCondition() の第 1 引数には真偽値も渡せます。フォームを組み立てている時点ですでに判断がついている場合、たとえば特定の状況でだけ規則を当てたい場合に便利です。

$form->addText('nickname')
	->addCondition($isRequired) // フォームを組み立てる時点で分かっている値
		->setRequired();

Nette では、条件が満たされたかどうかに JavaScript の側で反応するのが toggle() メソッドでとても簡単です。動的な JavaScriptをご覧ください。

別の要素への参照

規則や条件の引数として、フォームの別の要素を渡すこともできます。そうすると規則は、あとで利用者がブラウザで入力した値を使います。これはたとえば、password の要素が password_confirm の要素と同じ文字列を含むかを動的に確かめるのに使えます。

$form->addPassword('password', 'パスワード');
$form->addPassword('password_confirm', 'パスワードの確認')
    ->addRule($form::Equal, 'パスワードが一致しません', $form['password']);

独自の規則と条件

Nette に組み込まれた検証の規則では足りず、利用者のデータを自分のやり方で検証したい場面に出くわすことがあります。Nette ではこれがとても簡単です。

addRule()addCondition() メソッドの第 1 パラメータには、どんなコールバックでも渡せます。コールバックは第 1 パラメータとしてその要素自身を受け取り、検証が通ったかどうかを表す真偽値を返します。addRule() で規則を足すときは、追加の引数を渡せて、それが第 2 パラメータとして渡されます。

独自の検証器の一式は、静的メソッドを持つクラスとして作れます。

class MyValidators
{
	// 値が引数で割り切れるかを調べます
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// ほかの検証器
	}
}

使い方はごく分かりやすいものです。

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'値は %d の倍数でなければなりません',
		8,
	);

独自の検証の規則は JavaScript にも足せます。条件はその規則が静的メソッドであることです。JavaScript の検証器での名前は、バックスラッシュ \ を取り除いたクラス名、アンダースコア _、メソッド名をつないで作られます。たとえば App\MyValidators::validateDivisibilityAppMyValidators_validateDivisibility と書かれ、Nette.validators オブジェクトに足されます。

Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
	return val % args === 0;
};

onValidate イベント

フォームが送信されると検証が行われ、addRule() で足した個々の規則が確かめられ、続いて onValidate イベントが発火します。そのハンドラは追加の検証に使えます。ふつうは複数のフォームの要素の値の組み合わせが正しいかを確かめます。

エラーが見つかったら、addError() メソッドでフォームに伝えます。これは特定の要素に対しても、フォームそのものに対しても呼べます。

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	// ...
	$form->onValidate[] = $this->validateSignInForm(...);
	return $form;
}

private function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('この組み合わせは指定できません。');
	}
}

エラーの処理

妥当なフォームを処理している最中にはじめてエラーが分かる場面も多くあります。たとえばデータベースに新しい項目を書き込もうとして、キーが重複していた場合です。そんなときも addError() メソッドでエラーをフォームに返します。これは特定の要素に対しても、フォームそのものに対しても呼べます。

try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Home:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
		$form->addError('パスワードが正しくありません。');
	}
}

できるならエラーはフォームの要素に直接足すことをおすすめします。既定の描画器ではその要素の隣に表示されるからです。

$form['date']->addError('申し訳ありません、その日付はすでに埋まっています。');

addError() は繰り返し呼べるので、フォームや要素に複数のエラーのメッセージを渡せます。取り出すには getErrors() を使います。

$form->getErrors() は、フォームに直接渡されたものだけでなく、個々の要素に渡されたものも含めたすべてのエラーのメッセージをまとめて返すことに注意してください。フォームにだけ渡されたエラーのメッセージは $form->getOwnErrors() で取り出せます。

入力された値を変える

addFilter() メソッドで、利用者が入力した値を変えられます。この例では、郵便番号の空白を大目に見て取り除きます。

$form->addText('zip', '郵便番号:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // 郵便番号から空白を取り除きます
	})
	->addRule($form::Pattern, '郵便番号が 5 桁ではありません', '\d{5}');

フィルタは検証の規則や条件の中に組み込まれるので、メソッドの順序が意味を持ちます。つまりフィルタと規則は、addFilter()addRule() メソッドを並べた順に呼ばれます。

JavaScript での検証

条件と規則を書くための言語はとても力強いものです。すべての書き方はサーバー側でも、クライアント側の JavaScript でも働きます。それらは HTML の data-nette-rules 属性に JSON として運ばれます。検証そのものは、フォームの submit イベントを捕まえて、個々の要素を順に回り、それぞれの検証を行うスクリプトが受け持ちます。

そのスクリプトが netteForms.js で、いくつかの入手先があります。

CDN から HTML のページに直接読み込めます。

<script src="https://unpkg.com/nette-forms@3"></script>

あるいはプロジェクトの公開フォルダにコピーします(たとえば vendor/nette/forms/src/assets/netteForms.min.js から)。

<script src="/path/to/netteForms.min.js"></script>

あるいは npmで入れます。

npm install nette-forms

そして読み込んで動かします。

import netteForms from 'nette-forms';
netteForms.initOnLoad();

あるいは vendor のフォルダから直接読み込めます。

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();

フォームに novalidate 属性を足せば、クライアント側の検証をまるごと切れます。すると netteForms.js のスクリプトは送信時にそのフォームの検証を飛ばすので、検証はサーバーでだけ行われます。

$form->setHtmlAttribute('novalidate');

動的な JavaScript

利用者が商品を郵送で受け取ることを選んだときにだけ、住所の項目を表示したいですか。お安いご用です。鍵になるのは addCondition()toggle() のメソッドの組み合わせです。

$form->addCheckbox('send_it')
	->addCondition($form::Equal, true)
		->toggle('#address-container');

このコードは、条件が満たされたとき(つまりチェックボックスが入ったとき)に HTML 要素 #address-container が見えるようになり、その逆も起きると伝えています。ですから受取人の住所のフォームの要素をこの ID のコンテナに入れておけば、チェックボックスをクリックしたときに隠れたり現れたりします。これは netteForms.js のスクリプトが受け持ちます。

toggle() メソッドの引数にはどんなセレクタでも渡せます。歴史的な事情から、文字、数字、アンダースコアで始まり、文字、数字、アンダースコア、ハイフン、ドット、コロンだけを含む文字列は要素の ID として扱われ、# の文字が前に付いているのと同じになります。第 2 の省略できるパラメータで振る舞いを逆にできます。たとえば toggle('#address-container', false) とすれば、その要素はチェックボックスが入っていないときにだけ表示されます。

既定の JavaScript の実装は要素の hidden プロパティを変えます。とはいえ、たとえばアニメーションを足すなど、振る舞いは簡単に変えられます。JavaScript で Nette.toggle メソッドを独自の解で上書きするだけです。

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// 'visible' の値に応じて 'el' を隠したり見せたりします
	});
};

検証を切る

検証を切ると便利な場面もあります。送信ボタンを押しても検証を行うべきでないなら(キャンセルプレビュー のボタンに向いています)、$submit->setValidationScope([]) メソッドで切ります。一部だけ検証すべきなら、どの項目やフォームのコンテナを検証するかを指定できます。

$form->addText('name')
	->setRequired();

$details = $form->addContainer('details');
$details->addInteger('age')
	->setRequired('age');
$details->addInteger('age2')
	->setRequired('age2');

$form->addSubmit('send1'); // フォーム全体を検証します
$form->addSubmit('send2')
	->setValidationScope([]); // 何も検証しません
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // 'name' の要素だけを検証します
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // 'age' の要素だけを検証します
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // 'details' のコンテナを検証します

setValidationScope はフォームの onValidate イベントには影響せず、それはいつでも呼ばれます。コンテナの onValidate イベントは、そのコンテナが一部の検証の対象に印を付けられている場合にだけ発火します。

一部だけの検証は getValues() が返す値にも影響します。結果には検証の対象に入る要素の値だけが含まれます。その外にある要素の値は外されます。

バージョン: 4.x