Nettes Schema
Eine praktische Bibliothek zur Validierung und Normalisierung von Datenstrukturen anhand eines bestimmten Schemas mit einer intelligenten und leicht verständlichen API.
Die Dokumentation finden Sie auf der Website.
Installation:
composer require nette/schema
Es erfordert PHP-Version 8.1 und unterstützt PHP bis 8.4.
Gefällt dir Nettes Schema? Freust du dich auf die neuen Features?
Danke schön!
In der Variablen $schema
haben wir ein Validierungsschema (was genau das bedeutet und wie man es erstellt, werden wir später sagen) und in der Variablen $data
haben wir eine Datenstruktur, die wir validieren und normalisieren möchten. Dies können beispielsweise Daten sein, die der Benutzer über eine API, eine Konfigurationsdatei usw. sendet.
Die Aufgabe wird von der Klasse „NetteSchemaProcessor“ verarbeitet, die die Eingabe verarbeitet und entweder normalisierte Daten zurückgibt oder bei einem Fehler eine Ausnahme „NetteSchemaValidationException“ auslöst.
$ processor = new Nette Schema Processor ;
try {
$ normalized = $ processor -> process ( $ schema , $ data );
} catch ( Nette Schema ValidationException $ e ) {
echo ' Data is invalid: ' . $ e -> getMessage ();
}
Die Methode $e->getMessages()
gibt ein Array aller Nachrichtenzeichenfolgen zurück und $e->getMessageObjects()
gibt alle Nachrichten als NetteSchemaMessage-Objekte zurück.
Und jetzt erstellen wir ein Schema. Zur Definition wird die Klasse NetteSchemaExpect verwendet. Wir definieren tatsächlich Erwartungen, wie die Daten aussehen sollen. Nehmen wir an, dass die Eingabedaten eine Struktur (z. B. ein Array) sein müssen, die die Elemente processRefund
vom Typ „bool“ und refundAmount
vom Typ „int“ enthält.
use Nette Schema Expect ;
$ schema = Expect:: structure ([
' processRefund ' => Expect:: bool (),
' refundAmount ' => Expect:: int (),
]);
Wir glauben, dass die Schemadefinition klar aussieht, auch wenn Sie sie zum ersten Mal sehen.
Senden wir die folgenden Daten zur Validierung:
$ data = [
' processRefund ' => true ,
' refundAmount ' => 17 ,
];
$ normalized = $ processor -> process ( $ schema , $ data ); // OK, it passes
Die Ausgabe, also der Wert $normalized
, ist das Objekt stdClass
. Wenn die Ausgabe ein Array sein soll, fügen wir eine Umwandlung zum Schema Expect::structure([...])->castTo('array')
hinzu.
Alle Elemente der Struktur sind optional und haben den Standardwert null
. Beispiel:
$ data = [
' refundAmount ' => 17 ,
];
$ normalized = $ processor -> process ( $ schema , $ data ); // OK, it passes
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
Die Tatsache, dass der Standardwert null
ist, bedeutet nicht, dass er in den Eingabedaten 'processRefund' => null
akzeptiert wird. Nein, die Eingabe muss boolesch sein, also nur true
oder false
. Wir müssten null
explizit über Expect::bool()->nullable()
zulassen.
Ein Element kann mit Expect::bool()->required()
obligatorisch gemacht werden. Wir ändern den Standardwert mit Expect::bool()->default(false)
oder kurz mit Expect::bool(false)
auf false
.
Und was wäre, wenn wir neben Booleschen Werten auch 1
und 0
akzeptieren wollten? Dann listen wir die zulässigen Werte auf, die wir ebenfalls auf boolesche Werte normalisieren:
$ 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
Jetzt kennen Sie die Grundlagen, wie das Schema definiert ist und wie sich die einzelnen Elemente der Struktur verhalten. Wir zeigen nun, welche anderen Elemente bei der Definition eines Schemas verwendet werden können.
Alle Standard-PHP-Datentypen können im Schema aufgeführt werden:
Expect:: string ( $ default = null )
Expect:: int ( $ default = null )
Expect:: float ( $ default = null )
Expect:: bool ( $ default = null )
Expect::null()
Expect:: array ( $ default = [])
Und dann alle von den Validatoren unterstützten Typen über Expect::type('scalar')
oder abgekürzt Expect::scalar()
. Auch Klassen- oder Schnittstellennamen werden akzeptiert, z. B. Expect::type('AddressEntity')
.
Sie können auch die Union-Notation verwenden:
Expect:: type ( ' bool|string|array ' )
Der Standardwert ist immer null
außer bei array
und list
, wo es sich um ein leeres Array handelt. (Eine Liste ist ein Array, das in aufsteigender Reihenfolge der numerischen Schlüssel von Null an indiziert ist, d. h. ein nicht-assoziatives Array.)
Da das Array eine zu allgemeine Struktur hat, ist es sinnvoller, genau anzugeben, welche Elemente es enthalten kann. Zum Beispiel ein Array, dessen Elemente nur Zeichenfolgen sein können:
$ 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
Mit dem zweiten Parameter können Schlüssel angegeben werden (seit Version 1.2):
$ schema = Expect:: arrayOf ( ' string ' , ' int ' );
$ processor -> process ( $ schema , [ ' hello ' , ' world ' ]); // OK
$ processor -> process ( $ schema , [ ' a ' => ' hello ' ]); // ERROR: 'a' is not int
Die Liste ist ein indiziertes Array:
$ 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
Der Parameter kann auch ein Schema sein, wir können also schreiben:
Expect:: arrayOf (Expect:: bool ())
Der Standardwert ist ein leeres Array. Wenn Sie einen Standardwert angeben und mergeDefaults()
aufrufen, wird dieser mit den übergebenen Daten zusammengeführt.
anyOf()
ist eine Reihe von Werten oder Schemata, die ein Wert sein kann. So schreiben Sie ein Array von Elementen, die entweder 'a'
, true
oder null
sein können:
$ 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
Die Aufzählungselemente können auch Schemata sein:
$ schema = Expect:: listOf (
Expect:: anyOf (Expect:: string (), true , null ),
);
$ processor -> process ( $ schema , [ ' foo ' , true , null , ' bar ' ]); // OK
$ processor -> process ( $ schema , [ 123 ]); // ERROR
Die Methode anyOf()
akzeptiert Varianten als einzelne Parameter, nicht als Array. Um ihm ein Array von Werten zu übergeben, verwenden Sie den Entpackoperator anyOf(...$variants)
.
Der Standardwert ist null
. Verwenden Sie die Methode firstIsDefault()
um das erste Element zum Standard zu machen:
// default is 'hello'
Expect:: anyOf (Expect:: string ( ' hello ' ), true , null )-> firstIsDefault ();
Strukturen sind Objekte mit definierten Schlüsseln. Jedes dieser Schlüssel => Wertepaare wird als „Eigenschaft“ bezeichnet:
Strukturen akzeptieren Arrays und Objekte und geben Objekte stdClass
zurück (es sei denn, Sie ändern es mit castTo('array')
usw.).
Standardmäßig sind alle Eigenschaften optional und haben den Standardwert null
. Sie können obligatorische Eigenschaften mit required()
definieren:
$ 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}
Wenn Sie Eigenschaften nicht nur mit einem Standardwert ausgeben möchten, verwenden Sie skipDefaults()
:
$ schema = Expect:: structure ([
' required ' => Expect:: string ()-> required (),
' optional ' => Expect:: string (),
])-> skipDefaults ();
$ processor -> process ( $ schema , [ ' required ' => ' foo ' ]);
// OK, returns {'required' => 'foo'}
Obwohl null
der Standardwert der optional
Eigenschaft ist, ist er in den Eingabedaten nicht zulässig (der Wert muss eine Zeichenfolge sein). Eigenschaften, die null
akzeptieren, werden mit nullable()
definiert:
$ 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}
Standardmäßig dürfen die Eingabedaten keine zusätzlichen Elemente enthalten:
$ schema = Expect:: structure ([
' key ' => Expect:: string (),
]);
$ processor -> process ( $ schema , [ ' additional ' => 1 ]);
// ERROR: Unexpected item 'additional'
Was wir mit otherItems()
ändern können. Als Parameter geben wir das Schema für jedes zusätzliche Element an:
$ schema = Expect:: structure ([
' key ' => Expect:: string (),
])-> otherItems (Expect:: int ());
$ processor -> process ( $ schema , [ ' additional ' => 1 ]); // OK
$ processor -> process ( $ schema , [ ' additional ' => true ]); // ERROR
Sie können eine Eigenschaft mit der Methode deprecated([string $message])
verwerfen. Verfallsbenachrichtigungen werden von $processor->getWarnings()
zurückgegeben:
$ 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"]
Verwenden Sie min()
und max()
um die Anzahl der Elemente für Arrays zu begrenzen:
// array, at least 10 items, maximum 20 items
Expect:: array ()-> min ( 10 )-> max ( 20 );
Begrenzen Sie die Länge von Zeichenfolgen:
// string, at least 10 characters long, maximum 20 characters
Expect:: string ()-> min ( 10 )-> max ( 20 );
Begrenzen Sie bei Zahlen ihren Wert:
// integer, between 10 and 20 inclusive
Expect:: int ()-> min ( 10 )-> max ( 20 );
Natürlich ist es möglich, nur min()
oder nur max()
zu erwähnen:
// string, maximum 20 characters
Expect:: string ()-> max ( 20 );
Mit pattern()
können Sie einen regulären Ausdruck angeben, mit dem die gesamte Eingabezeichenfolge übereinstimmen muss (d. h. so, als ob sie in die Zeichen ^
a $
eingeschlossen wäre):
// just 9 digits
Expect:: string ()-> pattern ( ' d{9} ' );
Sie können beliebige weitere Einschränkungen hinzufügen, indem Sie assert(callable $fn)
verwenden.
$ 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
Oder
Expect:: string ()-> assert ( ' is_file ' ); // the file must exist
Sie können für jede Behauptung eine eigene Beschreibung hinzufügen. Es wird Teil der Fehlermeldung sein.
$ 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.
Die Methode kann wiederholt aufgerufen werden, um mehrere Einschränkungen hinzuzufügen. Es kann mit Aufrufen von transform()
und castTo()
vermischt werden.
Erfolgreich validierte Daten können mithilfe einer benutzerdefinierten Funktion geändert werden:
// conversion to uppercase:
Expect:: string ()-> transform ( fn ( string $ s ) => strtoupper ( $ s ));
Die Methode kann wiederholt aufgerufen werden, um mehrere Transformationen hinzuzufügen. Es kann mit Aufrufen von assert()
und castTo()
gemischt werden. Die Operationen werden in der Reihenfolge ausgeführt, in der sie deklariert wurden:
Expect:: type ( ' string|int ' )
-> castTo ( ' string ' )
-> assert ( ' ctype_lower ' , ' All characters must be lowercased ' )
-> transform ( fn ( string $ s ) => strtoupper ( $ s )); // conversion to uppercase
Die transform()
Methode kann den Wert gleichzeitig transformieren und validieren. Dies ist oft einfacher und weniger redundant als die Verkettung von transform()
und assert()
. Zu diesem Zweck erhält die Funktion ein NetteSchemaContext-Objekt mit einer addError()
Methode, mit der Informationen zu Validierungsproblemen hinzugefügt werden können:
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 );
});
Erfolgreich validierte Daten können wie folgt umgewandelt werden:
Expect:: scalar ()-> castTo ( ' string ' );
Zusätzlich zu nativen PHP-Typen können Sie auch in Klassen umwandeln. Dabei wird unterschieden, ob es sich um eine einfache Klasse ohne Konstruktor oder eine Klasse mit Konstruktor handelt. Wenn die Klasse keinen Konstruktor hat, wird eine Instanz davon erstellt und alle Elemente der Struktur werden in ihre Eigenschaften geschrieben:
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
Wenn die Klasse über einen Konstruktor verfügt, werden die Elemente der Struktur als benannte Parameter an den Konstruktor übergeben:
class Info
{
public function __construct (
public bool $ processRefund ,
public int $ refundAmount ,
) {
}
}
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
Durch die Umwandlung in Kombination mit einem Skalarparameter wird ein Objekt erstellt und der Wert als einziger Parameter an den Konstruktor übergeben:
Expect:: string ()-> castTo (DateTime::class);
// creates new DateTime(...)
Vor der eigentlichen Validierung können die Daten mit der Methode before()
normalisiert werden. Nehmen wir als Beispiel ein Element, das ein String-Array sein muss (z. B. ['a', 'b', 'c']
), aber Eingaben in Form eines Strings abc
erhält:
$ explode = fn ( $ v ) => explode ( ' ' , $ v );
$ schema = Expect:: arrayOf ( ' string ' )
-> before ( $ explode );
$ normalized = $ processor -> process ( $ schema , ' a b c ' );
// OK, returns ['a', 'b', 'c']
Sie können ein Strukturschema aus der Klasse generieren. Beispiel:
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}
Es werden auch anonyme Klassen unterstützt:
$ schema = Expect:: from ( new class {
public string $ name ;
public ? string $ password ;
public bool $ admin = false ;
});
Da die aus der Klassendefinition erhaltenen Informationen möglicherweise nicht ausreichen, können Sie mit dem zweiten Parameter ein benutzerdefiniertes Schema für die Elemente hinzufügen:
$ schema = Expect:: from ( new Config , [
' name ' => Expect:: string ()-> pattern ( ' w:.* ' ),
]);