網路模式
一個實用的庫,用於透過智慧且易於理解的 API 根據給定模式驗證和規範化資料結構。
文件可以在網站上找到。
安裝:
composer require nette/schema
它需要 PHP 版本 8.1,並支援 PHP 最高版本 8.4。
喜歡 Nette Schema 嗎?您期待新功能嗎?
謝謝你!
在變數$schema
中,我們有一個驗證模式(這到底意味著什麼以及如何創建它,我們稍後會說),在變數$data
中,我們有一個要驗證和規範化的資料結構。例如,這可以是用戶透過 API、設定檔等發送的資料。
此任務由 NetteSchemaProcessor 類別處理,該類別處理輸入並傳回規範化資料或在出錯時引發 NetteSchemaValidationException 異常。
$ processor = new Nette Schema Processor ;
try {
$ normalized = $ processor -> process ( $ schema , $ data );
} catch ( Nette Schema ValidationException $ e ) {
echo ' Data is invalid: ' . $ e -> getMessage ();
}
方法$e->getMessages()
傳回所有訊息字串的數組, $e->getMessageObjects()
將所有訊息作為 NetteSchemaMessage 物件傳回。
現在讓我們建立一個模式。類別 NetteSchemaExpect 用於定義它,我們實際上定義了資料應該是什麼樣子的期望。假設輸入資料必須是一個結構體(例如陣列),其中包含 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
。我們必須透過Expect::bool()->nullable()
明確允許null
。
可以使用Expect::bool()->required()
將項目設為強制。我們使用Expect::bool()->default(false)
或稍後使用Expect::bool(false)
將預設值變更為false
。
如果除了布林值之外我們還想接受1
和0
怎麼辦?然後我們列出允許的值,我們還將其標準化為布林值:
$ 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
現在您了解了架構的定義方式以及結構的各個元素的行為方式的基礎知識。現在,我們將展示可用於定義模式的所有其他元素。
所有標準 PHP 資料類型都可以在架構中列出:
Expect:: string ( $ default = null )
Expect:: int ( $ default = null )
Expect:: float ( $ default = null )
Expect:: bool ( $ default = null )
Expect::null()
Expect:: array ( $ default = [])
然後驗證器透過Expect::type('scalar')
或縮寫Expect::scalar()
支援的所有類型。也接受類別或介面名稱,例如Expect::type('AddressEntity')
。
您也可以使用聯合表示法:
Expect:: type ( ' bool|string|array ' )
預設值始終為null
除了array
和list
之外,它是一個空數組。 (列表是按數字鍵從零開始升序索引的數組,即非關聯數組)。
數組是一種過於通用的結構,準確指定它可以包含哪些元素會更有用。例如,一個數組,其元素只能是字串:
$ 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
第二個參數可用來指定鍵(從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
參數也可以是一個schema,所以我們可以這樣寫:
Expect:: arrayOf (Expect:: bool ())
預設值為空數組。如果您指定預設值並呼叫mergeDefaults()
,它將與傳遞的資料合併。
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(該值必須是字串)。接受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()
限制數組元素的數量:
// 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()
,您可以指定整個輸入字串必須匹配的正則表達式(即,就好像它被包裹在字元^
a $
中一樣):
// just 9 digits
Expect:: string ()-> pattern ( ' d{9} ' );
您可以使用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.
可以重複呼叫該方法來新增多個約束。它可以與對transform()
和castTo()
呼叫混合。
可以使用自訂函數修改成功驗證的資料:
// conversion to uppercase:
Expect:: string ()-> transform ( fn ( string $ s ) => strtoupper ( $ s ));
可以重複呼叫該方法來新增多個轉換。它可以與對assert()
和castTo()
呼叫混合。這些操作將按照聲明的順序執行:
Expect:: type ( ' string|int ' )
-> castTo ( ' string ' )
-> assert ( ' ctype_lower ' , ' All characters must be lowercased ' )
-> transform ( fn ( string $ s ) => strtoupper ( $ s )); // conversion to uppercase
transform()
方法可以同時轉換和驗證值。這通常比連結transform()
和assert()
更簡單且更少冗餘。為此,函數接收帶有addError()
方法的 NetteSchemaContext 對象,該對象可用於添加有關驗證問題的資訊:
Expect:: string ()
-> transform ( function ( string $ s , Nette Schema Context $ context ) {
if (! ctype_lower ( $ s )) {
$ context -> addError ( ' All characters must be lowercased ' , ' my.case.error ' );
return null ;
}
return strtoupper ( $ s );
});
成功驗證的資料可以被轉換:
Expect:: scalar ()-> castTo ( ' string ' );
除了本機 PHP 類型之外,您還可以轉換為類別。它區分是沒有建構子的簡單類別還是有建構子的類別。如果該類別沒有建構函數,則會建立它的實例,並將該結構的所有元素寫入其屬性:
class Info
{
public bool $ processRefund ;
public int $ refundAmount ;
}
Expect:: structure ([
' processRefund ' => Expect:: bool (),
' refundAmount ' => Expect:: int (),
])-> castTo (Info::class);
// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount
如果類別有建構函數,則結構的元素將作為命名參數傳遞給建構函數:
class Info
{
public function __construct (
public bool $ processRefund ,
public int $ refundAmount ,
) {
}
}
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
與標量參數結合的轉換建立一個物件並將該值作為唯一參數傳遞給建構函數:
Expect:: string ()-> castTo (DateTime::class);
// creates new DateTime(...)
在驗證之前,可以使用before()
方法對資料進行標準化。舉個例子,我們有一個元素,它必須是字串數組(例如['a', 'b', 'c']
),但接收字串abc
形式的輸入:
$ explode = fn ( $ v ) => explode ( ' ' , $ v );
$ schema = Expect:: arrayOf ( ' string ' )
-> before ( $ explode );
$ normalized = $ processor -> process ( $ schema , ' a b c ' );
// OK, returns ['a', 'b', 'c']
您可以從類別產生結構模式。例子:
class Config
{
public string $ name ;
public ? string $ 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}
也支援匿名類:
$ schema = Expect:: from ( new class {
public string $ name ;
public ? string $ password ;
public bool $ admin = false ;
});
由於從類別定義中獲取的資訊可能不夠,您可以使用第二個參數為元素添加自訂架構:
$ schema = Expect:: from ( new Config , [
' name ' => Expect:: string ()-> pattern ( ' w:.* ' ),
]);