スキーマデータバリデーション
与えられたスキーマに対してデータ構造を検証し、正規化するための実用的なライブラリで、スマートで理解しやすいAPIを備えています。
インストール方法
composer require nette/schema
基本的な使い方
変数$schema
には検証スキーマ(これが何を意味し、どのように作成するかは後述します)があり、変数$data
には検証および正規化したいデータ構造があります。これは例えば、APIや設定ファイルなどを通してユーザーから送られたデータであることができます。
このタスクはNette\Schema\Processor クラスによって処理され、入力を処理して正規化されたデータを返すか、エラー時にNette\Schema\ValidationException 例外を投げます。
$processor = new Nette\Schema\Processor;
try {
$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo 'Data is invalid: ' . $e->getMessage();
}
Method$e->getMessages()
は全てのメッセージ文字列の配列を返し、$e->getMessageObjects()
は全てのメッセージをNetteSchema FilterMessage
スキーマの定義
そして、今度はスキーマを作成してみましょう。Nette\Schema\Expect
というクラスを使って定義します。実際に、データがどのようなものであるべきかという期待値を定義します。入力データは、bool型の要素processRefund
とint型の要素refundAmount
を含む構造体(例えば配列)でなければならないとします。
use Nette\Schema\Expect;
$schema = Expect::structure([
'processRefund' => Expect::bool(),
'refundAmount' => Expect::int(),
]);
このようにスキーマを定義することで、初めて見る人でもわかりやすくなったと思います。
以下のデータを送って検証してみましょう。
$data = [
'processRefund' => true,
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // OK, it passes
出力、つまり値$normalized
は、オブジェクトstdClass
です。出力を配列にしたい場合は、スキーマにキャストを追加します。
Expect::structure([...])->castTo('array')
.
構造体のすべての要素はオプションであり,デフォルト値null
を持ちます.例
$data = [
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // OK, it passes
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
例:デフォルト値がnull
であることは、入力データ'processRefund' => null
で受け入れられることを意味しない。いいえ、入力はブーリアンでなければなりません。つまり、true
またはfalse
だけです。null
をExpect::bool()->nullable()
経由で明示的に許可しなければならないでしょう。
項目はExpect::bool()->required()
を使って必須とすることができる。Expect::bool()->default(false)
を使ってデフォルト値をfalse
に変更したり、Expect::bool(false)
を使ってデフォルト値を間もなく変更したりします。
そして、ブーリアン以外に1
and 0
も受け入れたい場合はどうすればよいでしょうか。そこで、許容される値をリストアップし、これもbooleanに正規化することにします。
$schema = Expect::structure([
'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
'refundAmount' => Expect::int(),
]);
$normalized = $processor->process($schema, $data);
is_bool($normalized->processRefund); // true
これでスキーマがどのように定義され、構造の個々の要素がどのように動作するかの基本がわかったと思います。これから、スキーマを定義する際に、他のすべての要素がどのように使用できるかを紹介します。
データ型:type()
PHP の標準的なデータ型は、すべてスキーマにリストアップすることができます。
Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
そして、Validators
がサポートするすべての型をExpect::type('scalar')
あるいはExpect::scalar()
のように省略した形式で指定します。また、クラス名やインターフェイス名も使用できます。例えばExpect::type('AddressEntity')
のようになります。
また、ユニオン表記も使用できます。
Expect::type('bool|string|array')
デフォルト値は,array
とlist
を除いて,常にnull
で,これは空の配列である.(リストは,ゼロから数値キーの昇順でインデックスが付けられた配列,つまり非結合型配列です).
値の配列: arrayOf() listOf()
配列はあまりにも一般的な構造なので、どのような要素を含むことができるかを正確に指定する方が便利です。例えば、要素が文字列のみ可能な配列。
$schema = Expect::arrayOf('string');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string
2 番目のパラメータで、キーを指定することができます (バージョン 1.2 以降)。
$schema = Expect::arrayOf('string', 'int');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int
リストはインデックス付き配列となります。
$schema = Expect::listOf('string');
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string
$processor->process($schema, ['key' => 'a']); // ERROR: is not a list
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list
パラメータはスキーマでもよいので、次のように書きます。
Expect::arrayOf(Expect::bool())
デフォルト値は空の配列です。default value
を指定すると、渡されたデータにマージされます。これを無効にするには、mergeDefaults(false)
(バージョン 1.1 以降) を使用します。
列挙:anyOf()
anyOf()
は、ある値が取りうる値やスキーマの集合です。以下は、'a'
,true
,null
のいずれかになりうる要素の配列の書き方です。
$schema = Expect::listOf(
Expect::anyOf('a', true, null),
);
$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false does not belong there
列挙された要素はスキーマになることもできます。
$schema = Expect::listOf(
Expect::anyOf(Expect::string(), true, null),
);
$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ERROR
anyOf()
メソッドはバリアントを配列としてではなく、個々のパラメータとして受け取ります。値の配列を渡すには、アンパッキングオペレータanyOf(...$variants)
を使ってください。
デフォルト値はnull
です。最初の要素をデフォルトにするには、firstIsDefault()
メソッドを使います。
// default is 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
構造体
構造体は、定義されたキーを持つオブジェクトである。このキー ⇒ 値のペアをそれぞれ「プロパティ」と呼びます。
構造体は配列やオブジェクトを受け入れ、オブジェクトを返すstdClass
(castTo('array')
, などで変更しない限り)。
デフォルトでは、すべてのプロパティはオプションであり、デフォルト値はnull
です。必須プロパティはrequired()
を使って定義できます。
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // the default value is null
]);
$processor->process($schema, ['optional' => '']);
// ERROR: option 'required' is missing
$processor->process($schema, ['required' => 'foo']);
// OK, returns {'required' => 'foo', 'optional' => null}
デフォルト値のみを持つプロパティを出力したくない場合は、skipDefaults()
を使用します。
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(),
])->skipDefaults();
$processor->process($schema, ['required' => 'foo']);
// OK, returns {'required' => 'foo'}
null
はoptional
プロパティのデフォルト値であるが、入力データでは許されない(値は文字列でなければならない)。null
を受け入れるプロパティは、nullable()
を使って定義します。
$schema = Expect::structure([
'optional' => Expect::string(),
'nullable' => Expect::string()->nullable(),
]);
$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' expects to be string, null given.
$processor->process($schema, ['nullable' => null]);
// OK, returns {'optional' => null, 'nullable' => null}
デフォルトでは、入力データに余分な項目は存在できない。
$schema = Expect::structure([
'key' => Expect::string(),
]);
$processor->process($schema, ['additional' => 1]);
// ERROR: Unexpected item 'additional'
これはotherItems()
で変更可能です。パラメータとして、各追加要素のスキーマを指定します。
$schema = Expect::structure([
'key' => Expect::string(),
])->otherItems(Expect::int());
$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // ERROR
非推奨事項
非推奨のプロパティは
deprecated([string $message])
メソッドを使って非推奨にすることができます。非推奨の通知は$processor->getWarnings()
で返されます。
$schema = Expect::structure([
'old' => Expect::int()->deprecated('The item %path% is deprecated'),
]);
$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["The item 'old' is deprecated"]
範囲: min() max()
min()
とmax()
を使って、配列の要素数を制限します。
// array, at least 10 items, maximum 20 items
Expect::array()->min(10)->max(20);
文字列の場合は、その長さを制限する。
// string, at least 10 characters long, maximum 20 characters
Expect::string()->min(10)->max(20);
数値の場合は、値を制限する。
// integer, between 10 and 20 inclusive
Expect::int()->min(10)->max(20);
もちろん、min()
だけ、あるいはmax()
だけに言及することも可能です。
// string, maximum 20 characters
Expect::string()->max(20);
正規表現: pattern()
pattern()
を使うと、入力文字列の 全体
にマッチしなければならない正規表現を指定できます (つまり、入力文字列が^
a
$
という文字でくくられたようなものです)。
// just 9 digits
Expect::string()->pattern('\d{9}');
カスタムアサーション: assert()
assert(callable $fn)
を使って、その他の制限を追加することができます。
$countIsEven = fn($v) => count($v) % 2 === 0;
$schema = Expect::arrayOf('string')
->assert($countIsEven); // the count must be even
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even
または
Expect::string()->assert('is_file'); // the file must exist
各アサーションに独自の説明を追加することができます。これはエラーメッセージの一部となります。
$schema = Expect::arrayOf('string')
->assert($countIsEven, 'Even items in array');
$processor->process($schema, ['a', 'b', 'c']);
// Failed assertion "Even items in array" for item with value array.
このメソッドは、繰り返しコールしてアサーションを追加することができます。
オブジェクトへのマッピング: from()
クラスから構造体スキーマを生成することができます。例
class Config
{
public string $name;
public string|null $password;
public bool $admin = false;
}
$schema = Expect::from(new Config);
$data = [
'name' => 'jeff',
];
$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false}
PHP 7.4 以降を使用している場合は、ネイティブタイプを使用することができます。
class Config
{
public string $name;
public ?string $password;
public bool $admin = false;
}
$schema = Expect::from(new Config);
匿名クラスもサポートされています。
$schema = Expect::from(new class {
public string $name;
public ?string $password;
public bool $admin = false;
});
クラス定義から得られる情報だけでは十分でない場合があるため、2番目のパラメータで要素にカスタムスキーマを追加することができます。
$schema = Expect::from(new Config, [
'name' => Expect::string()->pattern('\w:.*'),
]);
キャスト: castTo()
バリデーションに成功したデータをキャストすることができます。
Expect::scalar()->castTo('string');
PHP のネイティブな型だけでなく、クラスへのキャストも可能です。
Expect::scalar()->castTo('AddressEntity');
正規化: before()
バリデーションの前に、before()
メソッドを使用してデータを正規化することができます。例として、文字列の配列でなければならない要素(例:³³)があるとします。
['a', 'b', 'c']
が、文字列a b c
という形式で入力を受け取るとします。
$explode = fn($v) => explode(' ', $v);
$schema = Expect::arrayOf('string')
->before($explode);
$normalized = $processor->process($schema, 'a b c');
// OK, returns ['a', 'b', 'c']