Skema Nette
Pustaka praktis untuk validasi dan normalisasi struktur data terhadap skema tertentu dengan API yang cerdas & mudah dipahami.
Dokumentasi dapat ditemukan di situs web.
Instalasi:
composer require nette/schema
Ini membutuhkan PHP versi 8.1 dan mendukung PHP hingga 8.4.
Apakah Anda suka Skema Nette? Apakah Anda menantikan fitur-fitur baru?
Terima kasih!
Dalam variabel $schema
kita memiliki skema validasi (apa sebenarnya artinya dan cara membuatnya akan kita bahas nanti) dan dalam variabel $data
kita memiliki struktur data yang ingin kita validasi dan normalkan. Misalnya, data yang dikirim oleh pengguna melalui API, file konfigurasi, dll.
Tugas ini ditangani oleh kelas NetteSchemaProcessor, yang memproses input dan mengembalikan data yang dinormalisasi atau memunculkan pengecualian NetteSchemaValidationException jika terjadi kesalahan.
$ processor = new Nette Schema Processor ;
try {
$ normalized = $ processor -> process ( $ schema , $ data );
} catch ( Nette Schema ValidationException $ e ) {
echo ' Data is invalid: ' . $ e -> getMessage ();
}
Metode $e->getMessages()
mengembalikan array semua string pesan dan $e->getMessageObjects()
mengembalikan semua pesan sebagai objek NetteSchemaMessage.
Dan sekarang mari kita buat skema. Kelas NetteSchemaExpect digunakan untuk mendefinisikannya, kami sebenarnya mendefinisikan ekspektasi tentang tampilan datanya. Katakanlah data input harus berupa struktur (misalnya array) yang berisi elemen processRefund
bertipe bool dan refundAmount
bertipe int.
use Nette Schema Expect ;
$ schema = Expect:: structure ([
' processRefund ' => Expect:: bool (),
' refundAmount ' => Expect:: int (),
]);
Kami percaya bahwa definisi skema terlihat jelas, bahkan jika Anda melihatnya untuk pertama kali.
Mari kirimkan data berikut untuk validasi:
$ data = [
' processRefund ' => true ,
' refundAmount ' => 17 ,
];
$ normalized = $ processor -> process ( $ schema , $ data ); // OK, it passes
Outputnya, yaitu nilai $normalized
, adalah objek stdClass
. Jika kita ingin outputnya berupa array, kita menambahkan cast ke skema Expect::structure([...])->castTo('array')
.
Semua elemen struktur bersifat opsional dan memiliki nilai default null
. Contoh:
$ data = [
' refundAmount ' => 17 ,
];
$ normalized = $ processor -> process ( $ schema , $ data ); // OK, it passes
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
Fakta bahwa nilai defaultnya adalah null
tidak berarti nilai tersebut akan diterima dalam data input 'processRefund' => null
. Tidak, inputnya harus boolean, yaitu hanya true
atau false
. Kita harus secara eksplisit mengizinkan null
melalui Expect::bool()->nullable()
.
Suatu item dapat dijadikan wajib menggunakan Expect::bool()->required()
. Kami mengubah nilai default menjadi false
menggunakan Expect::bool()->default(false)
atau segera menggunakan Expect::bool(false)
.
Dan bagaimana jika kita ingin menerima 1
dan 0
selain boolean? Lalu kami membuat daftar nilai yang diizinkan, yang juga akan kami normalkan menjadi 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
Sekarang Anda mengetahui dasar-dasar bagaimana skema didefinisikan dan bagaimana masing-masing elemen struktur berperilaku. Sekarang kita akan menunjukkan elemen apa saja yang dapat digunakan dalam mendefinisikan skema.
Semua tipe data PHP standar dapat dicantumkan dalam skema:
Expect:: string ( $ default = null )
Expect:: int ( $ default = null )
Expect:: float ( $ default = null )
Expect:: bool ( $ default = null )
Expect::null()
Expect:: array ( $ default = [])
Dan kemudian semua tipe didukung oleh Validator melalui Expect::type('scalar')
atau disingkat Expect::scalar()
. Nama kelas atau antarmuka juga diterima, misalnya Expect::type('AddressEntity')
.
Anda juga dapat menggunakan notasi gabungan:
Expect:: type ( ' bool|string|array ' )
Nilai defaultnya selalu null
kecuali array
dan list
, yang merupakan array kosong. (Daftar adalah larik yang diindeks dalam urutan kunci numerik dari nol, yaitu larik non-asosiatif).
Struktur array terlalu umum, akan lebih berguna untuk menentukan dengan tepat elemen apa yang dapat dikandungnya. Misalnya, array yang elemennya hanya bisa berupa string:
$ 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
Parameter kedua dapat digunakan untuk menentukan kunci (sejak versi 1.2):
$ schema = Expect:: arrayOf ( ' string ' , ' int ' );
$ processor -> process ( $ schema , [ ' hello ' , ' world ' ]); // OK
$ processor -> process ( $ schema , [ ' a ' => ' hello ' ]); // ERROR: 'a' is not int
Daftarnya adalah array yang diindeks:
$ 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
Parameternya juga bisa berupa skema, jadi kita bisa menulis:
Expect:: arrayOf (Expect:: bool ())
Nilai defaultnya adalah array kosong. Jika Anda menentukan nilai default dan memanggil mergeDefaults()
, nilai tersebut akan digabungkan dengan data yang diteruskan.
anyOf()
adalah sekumpulan nilai atau skema yang dapat berupa suatu nilai. Berikut cara menulis array elemen yang dapat berupa 'a'
, true
, atau 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
Elemen enumerasi juga dapat berupa skema:
$ schema = Expect:: listOf (
Expect:: anyOf (Expect:: string (), true , null ),
);
$ processor -> process ( $ schema , [ ' foo ' , true , null , ' bar ' ]); // OK
$ processor -> process ( $ schema , [ 123 ]); // ERROR
Metode anyOf()
menerima varian sebagai parameter individual, bukan sebagai array. Untuk meneruskan array nilai, gunakan operator pembongkaran anyOf(...$variants)
.
Nilai defaultnya adalah null
. Gunakan metode firstIsDefault()
untuk menjadikan elemen pertama sebagai default:
// default is 'hello'
Expect:: anyOf (Expect:: string ( ' hello ' ), true , null )-> firstIsDefault ();
Struktur adalah objek dengan kunci yang ditentukan. Masing-masing pasangan kunci => nilai ini disebut sebagai "properti":
Struktur menerima array dan objek dan mengembalikan objek stdClass
(kecuali Anda mengubahnya dengan castTo('array')
, dll.).
Secara default, semua properti bersifat opsional dan memiliki nilai default null
. Anda dapat menentukan properti wajib menggunakan 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}
Jika Anda tidak ingin menampilkan properti hanya dengan nilai default, gunakan skipDefaults()
:
$ schema = Expect:: structure ([
' required ' => Expect:: string ()-> required (),
' optional ' => Expect:: string (),
])-> skipDefaults ();
$ processor -> process ( $ schema , [ ' required ' => ' foo ' ]);
// OK, returns {'required' => 'foo'}
Meskipun null
adalah nilai default dari properti optional
, namun tidak diperbolehkan dalam data input (nilainya harus berupa string). Properti yang menerima null
didefinisikan menggunakan 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}
Secara default, tidak boleh ada item tambahan dalam data masukan:
$ schema = Expect:: structure ([
' key ' => Expect:: string (),
]);
$ processor -> process ( $ schema , [ ' additional ' => 1 ]);
// ERROR: Unexpected item 'additional'
Yang bisa kita ubah dengan otherItems()
. Sebagai parameter, kami akan menentukan skema untuk setiap elemen tambahan:
$ schema = Expect:: structure ([
' key ' => Expect:: string (),
])-> otherItems (Expect:: int ());
$ processor -> process ( $ schema , [ ' additional ' => 1 ]); // OK
$ processor -> process ( $ schema , [ ' additional ' => true ]); // ERROR
Anda dapat menghentikan penggunaan properti menggunakan metode deprecated([string $message])
. Pemberitahuan penghentian dikembalikan oleh $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"]
Gunakan min()
dan max()
untuk membatasi jumlah elemen array:
// array, at least 10 items, maximum 20 items
Expect:: array ()-> min ( 10 )-> max ( 20 );
Untuk string, batasi panjangnya:
// string, at least 10 characters long, maximum 20 characters
Expect:: string ()-> min ( 10 )-> max ( 20 );
Untuk angka, batasi nilainya:
// integer, between 10 and 20 inclusive
Expect:: int ()-> min ( 10 )-> max ( 20 );
Tentu saja, dimungkinkan untuk menyebutkan hanya min()
, atau hanya max()
:
// string, maximum 20 characters
Expect:: string ()-> max ( 20 );
Dengan menggunakan pattern()
, Anda dapat menentukan ekspresi reguler yang harus cocok dengan seluruh string masukan (yaitu seolah-olah dibungkus dalam karakter ^
a $
):
// just 9 digits
Expect:: string ()-> pattern ( ' d{9} ' );
Anda dapat menambahkan batasan lain menggunakan 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
Atau
Expect:: string ()-> assert ( ' is_file ' ); // the file must exist
Anda dapat menambahkan deskripsi Anda sendiri untuk setiap pernyataan. Ini akan menjadi bagian dari pesan kesalahan.
$ 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.
Metode ini dapat dipanggil berulang kali untuk menambahkan beberapa batasan. Itu dapat dicampur dengan panggilan ke transform()
dan castTo()
.
Data yang berhasil divalidasi dapat dimodifikasi menggunakan fungsi khusus:
// conversion to uppercase:
Expect:: string ()-> transform ( fn ( string $ s ) => strtoupper ( $ s ));
Metode ini dapat dipanggil berulang kali untuk menambahkan beberapa transformasi. Itu dapat dicampur dengan panggilan ke assert()
dan castTo()
. Operasi akan dieksekusi sesuai urutan deklarasinya:
Expect:: type ( ' string|int ' )
-> castTo ( ' string ' )
-> assert ( ' ctype_lower ' , ' All characters must be lowercased ' )
-> transform ( fn ( string $ s ) => strtoupper ( $ s )); // conversion to uppercase
Metode transform()
dapat mengubah dan memvalidasi nilai secara bersamaan. Hal ini sering kali lebih sederhana dan tidak mubazir dibandingkan merangkai transform()
dan assert()
. Untuk tujuan ini, fungsi menerima objek NetteSchemaContext dengan metode addError()
, yang dapat digunakan untuk menambahkan informasi tentang masalah validasi:
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 );
});
Data yang berhasil divalidasi dapat dikirimkan:
Expect:: scalar ()-> castTo ( ' string ' );
Selain tipe PHP asli, Anda juga dapat melakukan transmisi ke kelas. Ini membedakan apakah kelas sederhana tanpa konstruktor atau kelas dengan konstruktor. Jika kelas tidak memiliki konstruktor, sebuah instance dibuat dan semua elemen struktur ditulis ke propertinya:
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
Jika kelas mempunyai konstruktor, elemen struktur diteruskan sebagai parameter bernama ke konstruktor:
class Info
{
public function __construct (
public bool $ processRefund ,
public int $ refundAmount ,
) {
}
}
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
Transmisi yang dikombinasikan dengan parameter skalar akan membuat objek dan meneruskan nilai sebagai satu-satunya parameter ke konstruktor:
Expect:: string ()-> castTo (DateTime::class);
// creates new DateTime(...)
Sebelum dilakukan validasi sendiri, data dapat dinormalisasi menggunakan metode before()
. Sebagai contoh, mari kita punya elemen yang harus berupa array string (misalnya ['a', 'b', 'c']
), namun menerima input dalam bentuk string abc
:
$ explode = fn ( $ v ) => explode ( ' ' , $ v );
$ schema = Expect:: arrayOf ( ' string ' )
-> before ( $ explode );
$ normalized = $ processor -> process ( $ schema , ' a b c ' );
// OK, returns ['a', 'b', 'c']
Anda dapat menghasilkan skema struktur dari kelas. Contoh:
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}
Kelas anonim juga didukung:
$ schema = Expect:: from ( new class {
public string $ name ;
public ? string $ password ;
public bool $ admin = false ;
});
Karena informasi yang diperoleh dari definisi kelas mungkin tidak cukup, Anda dapat menambahkan skema khusus untuk elemen dengan parameter kedua:
$ schema = Expect:: from ( new Config , [
' name ' => Expect:: string ()-> pattern ( ' w:.* ' ),
]);