مخطط نيت
مكتبة عملية للتحقق من صحة هياكل البيانات وتطبيعها مقابل مخطط معين باستخدام واجهة برمجة تطبيقات ذكية وسهلة الفهم.
يمكن العثور على الوثائق على الموقع.
تثبيت:
composer require nette/schema
يتطلب إصدار PHP 8.1 ويدعم PHP حتى 8.4.
هل تحب مخطط نيت؟ هل تتطلع إلى الميزات الجديدة؟
شكرًا لك!
في المتغير $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 لتعريفها، فنحن في الواقع نحدد التوقعات الخاصة بالشكل الذي يجب أن تبدو عليه البيانات. لنفترض أن بيانات الإدخال يجب أن تكون بنية (على سبيل المثال، مصفوفة) تحتوي على عناصر processRefund
من النوع bool و refundAmount
من النوع int.
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()
. نقوم بتغيير القيمة الافتراضية إلى false
باستخدام Expect::bool()->default(false)
أو قريبًا باستخدام Expect::bool(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
يمكن أن تكون المعلمة أيضًا مخططًا، حتى نتمكن من كتابة:
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
باستخدام 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()
. لهذا الغرض، تتلقى الدالة كائن NetteSchemaContext باستخدام أسلوب addError()
، والذي يمكن استخدامه لإضافة معلومات حول مشكلات التحقق من الصحة:
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:.* ' ),
]);