مكتبة PHP صغيرة لمعالجة الإدخال الآمنة من النوع.
دعنا نقول أن لديك إجراء يسرد بعض الكيانات. ويشمل ذلك الترتيب والترتيب الصاعد أو التنازلي والتصفية الاختيارية بحلول الوقت الذي تم فيه إنشاء الكيان. سيكون لهذا الإجراء نوع من تحليل المدخلات ، والذي يمكن أن يبدو هكذا:
public function index ()
{
$ page = $ this -> request -> query -> get ( ' page ' );
if ( $ page === null || ! is_integer ( $ page )) {
throw new Exception ( " Parameter page not found " );
}
$ order = $ this -> request -> query -> get ( ' order ' );
if ( $ order === null || ! in_array ( $ order , [ ' asc ' , ' desc ' ])) {
throw new Exception ( " Parameter order not found " );
}
// Optional parameter
$ createdAt = $ this -> query -> query -> get ( ' createdAt ' );
if ( is_string ( $ createdAt )) {
$ createdAt = new DateTime ( $ createdAt );
} else {
$ createdAt = null ;
}
}من الواضح أن هذا الرمز ليس من الجيد القراءة لأنه ليس وصفيًا للغاية. كما أنه مطول إلى حد ما على ما تفعله. وعندما لا تولي اهتمامًا عن كثب ، فمن المحتمل أن تفوت شيكًا فارغًا أو شيكًا نوعًا.
قارن الآن الرمز أعلاه مع هذا الإصدار:
public function index ()
{
$ page = $ this -> queryParameter ( ' page ' )-> int ()-> required ();
$ order = $ this -> queryParameter ( ' order ' )-> oneOf ([ ' asc ' , ' desc ' ])-> required ();
$ createdAt = $ this -> queryParameter ( ' createdAt ' )-> dateTime ()-> defaultsTo ( null );
}هذا ما تقدمه هذه المكتبة. يتيح لك التعبير عن " هذا الإجراء يتطلب معلمة صفحة من النوع int " أو " يحتوي هذا الإجراء على معلمة اختيارية تم إنشاؤها من نوع البيانات ، وسيتم تعيينها على قيمة افتراضية إذا كانت غائبة ".
إذا كنت ترغب في الانتقال مباشرة إلى الكود الآن ، فيمكنك فقط اللعب مع الأمثلة.
cd /tmpgit clone [email protected]:mpscholten/request-parser.gitcd request-parsercomposer installcd examplesphp -S localhost:8080هناك أيضًا العديد من ملفات PHP الأخرى داخل دليل الأمثلة. للحصول على يديك متسخًا ، أقترح فقط تعديل الأمثلة قليلاً.
تثبيت عبر الملحن
composer require mpscholten/request-parser
symfony/http-foundation ، فانقر هنا.ServerRequestInterface ، فانقر هنا.Request (أو ربما مجرد $_GET القديم والأصدقاء) ، تحقق من هذا المثال. المثال التالي يفترض أنك تستخدم Request Symfony:
class MyController
{
use MPScholten RequestParser Symfony ControllerHelperTrait;
public function __construct ( Request $ request )
{
$ this -> initRequestParser ( $ request );
}
}ثم يمكنك استخدام المكتبة مثل هذا:
class MyController
{
use MPScholten RequestParser Symfony ControllerHelperTrait;
public function __construct ( Request $ request )
{
$ this -> initRequestParser ( $ request );
}
public function myAction ()
{
$ someParameter = $ this -> queryParameter ( ' someParameter ' )-> string ()-> required ();
}
} عند القيام GET /MyController/myAction?someParameter=example ، سيحتوي متغير $someParameter على السلسلة "example" .
قد تتساءل ماذا يحدث عندما نترك جزءًا من جزء ?someParameter ، مثل GET /MyController/myAction . في هذه الحالة ، ستقوم $this->queryParameter('someParameter')->string()->required() رمي NotFoundException . يمكن التعامل مع هذا الاستثناء من خلال تطبيقك لإظهار رسالة خطأ.
ألق نظرة على الأمثلة.
يفترض المثال التالي أنك تستخدم PSR7 ServerRequestInterface :
class MyController
{
use MPScholten RequestParser Psr7 ControllerHelperTrait;
public function __construct ( ServerRequestInterface $ request )
{
$ this -> initRequestParser ( $ request );
}
}ثم يمكنك استخدام المكتبة مثل هذا:
class MyController
{
use MPScholten RequestParser Psr7 ControllerHelperTrait;
public function __construct ( ServerRequestInterface $ request )
{
$ this -> initRequestParser ( $ request );
}
public function myAction ()
{
$ someParameter = $ this -> queryParameter ( ' someParameter ' )-> string ()-> required ();
}
} عند القيام GET /MyController/myAction?someParameter=example ، سيحتوي متغير $someParameter على السلسلة "example" .
قد تتساءل ماذا يحدث عندما نترك جزءًا من جزء ?someParameter ، مثل GET /MyController/myAction . في هذه الحالة ، ستقوم $this->queryParameter('someParameter')->string()->required() رمي NotFoundException . يمكن التعامل مع هذا الاستثناء من خلال تطبيقك لإظهار رسالة خطأ.
ألق نظرة على الأمثلة.
لجعل someParameter اختياريًا ، يمكننا فقط استبدال required() بـ defaultsTo($someDefaultValue) :
class MyController
{
use MPScholten RequestParser Symfony ControllerHelperTrait;
public function __construct ( Request $ request )
{
$ this -> initRequestParser ( $ request );
}
public function myAction ()
{
$ someParameter = $ this -> queryParameter ( ' someParameter ' )-> string ()-> defaultsTo ( ' no value given ' );
}
} عند القيام GET /MyController/myAction ، سيحتوي متغير $someParameter الآن على السلسلة "no value given" . لن يتم طرح أي استثناء لأننا حددنا قيمة افتراضية.
بشكل عام ، يمكنك أولاً تحديد اسم المعلمة ، متبوعًا بالنوع ، ثم حدد ما إذا كانت المعلمة مطلوبة أم اختيارية ذات قيمة افتراضية.
لمزيد من الأمثلة ، تحقق من examples/ دليل هذا المستودع. أنه يحتوي على العديد من الأمثلة القابلة للتشغيل.
في كثير من الأحيان نحتاج أكثر من مجرد سلاسل. يوفر طلب Prosparser أيضًا طرقًا لأنواع البيانات الأخرى:
class DashboardController
{
public function show ()
{
$ dashboardId = $ this -> queryParameter ( ' id ' )-> int ()-> required ();
// GET /dashboard?name=Hello => $dashboardName == "Hello"
$ dashboardName = $ this -> queryParameter ( ' name ' )-> string ()-> required ();
// Get /dashboard?name= => $dashboardName == "default value"
$ dashboardName = $ this -> queryParameter ( ' name ' )-> string ()-> defaultsToIfEmpty ( " default value " );
// GET /dashboard?status=private => $dashboardStatus == "private"
// GET /dashboard?status=public => $dashboardStatus == "public"
// GET /dashboard?status=invalid => A NotFoundException will be thrown
$ dashboardStatus = $ this -> queryParameter ( ' status ' )-> oneOf ([ ' private ' , ' public ' ])-> required ();
// GET /dashboard?createdAt=01.01.2016 => $dateTime == new DateTime("01.01.2016")
// GET /dashboard?createdAt=invalid_date => A NotFoundException will be thrown
$ dateTime = $ this -> queryParameter ( ' createdAt ' )-> dateTime ()-> required ();
// GET /dashboard?config={"a":true} => $json == ['a' => true]
$ json = $ this -> queryParameter ( ' config ' )-> json ()-> required ();
// GET /dashboard?includeWidgets=true => $includeWidgets == true
// GET /dashboard?includeWidgets=false => $includeWidgets == false
// GET /dashboard?includeWidgets=0 => $includeWidgets == false
// GET /dashboard?includeWidgets=abcde => A NotFoundException will be thrown
$ includeWidgets = $ this -> queryParameter ( ' includeWidgets ' )-> boolean ()-> required ();
// GET /dashboard?includeWidgets=yes => $includeWidgets == true
// GET /dashboard?includeWidgets=no => $includeWidgets == false
$ includeWidgets = $ this -> queryParameter ( ' includeWidgets ' )-> yesNoBoolean ()-> required ();
// GET /image?scale=2.5 => $scale == 2.5
$ scale = $ this -> queryParameter ( ' scale ' )-> float ()-> required ();
}
} كل هذه الأنواع توفر أيضًا متغيرًا defaultsTo .
| يكتب | مثال رمز | مثال الإدخال |
|---|---|---|
| خيط | $this->queryParameter('name')->string()->required(); | 'John Doe' |
| سلسلة مفصولة فاصلة | $this->queryParameter('names')->commaSeparated()->string()->required(); | 'John Doe,John Oliver' |
| عدد صحيح | $this->queryParameter('id')->int()->required(); | '5' |
| عدد صحيح مفصل الفاصلة | $this->queryParameter('groupIds')->commaSeparated()->int()->required(); | '5,6,7,8' |
| يطفو | $this->queryParameter('ratio')->float()->required(); | '0.98' |
| تعويم فاصلة مفصولة | $this->queryParameter('precipitation')->commaSeparated()->float()->required(); | '0.98,1.24,5.21' |
| DateTime | $this->queryParameter('timestamp')->dateTime()->required(); | '2016-07-20' |
| فاصلة فاصلة DateTime | $this->queryParameter('eventTimes')->commaSeparated()->dateTime()->required(); | '2016-07-20 13:10:50,2016-07-21 12:01:07' |
| منطقية | $this->queryParameter('success')->boolean()->required(); | 'true' |
| فاصلة مفصولة منطقية | $this->queryParameter('answers')->commaSeparated()->boolean()->required(); | '1,0,0,1' |
| نعم/لا منطقية | $this->queryParameter('success')->yesNoBoolean()->required(); | 'yes' |
| فاصلة فاصلة نعم/لا منطقية | $this->queryParameter('answers')->commaSeparated()->yesNoBoolean()->required(); | 'y,n,n,y,n' |
| جيسون | $this->queryParameter('payload')->json()->required(); | '{"event":"click","timestamp":"2016-07-20 13:10:50"}' |
| فاصلة مفصولة JSON | $this->queryParameter('events')->commaSeparated()->json()->required(); | '{"event":"click","timestamp":"2016-07-20 13:10:50"},{"event":"add_to_basket","timestamp":"2016-07-20 13:11:01"}' |
$this->queryParameter($name) يخبر وحدة التحكم أننا نريد معلمة استعلام (كل شيء بعد "؟" يسمى سلسلة الاستعلام). هذا عادة ما نريد عند التعامل مع طلبات الحصول على
عندما نتعامل مع طلب نشر ، نحتاج إلى استخدام $this->bodyParameter($name) للوصول إلى حقول النماذج أو حمولة AJAX.
تتيح لك المكتبة الاستفادة المكثفة لميزات الإكمال التلقائي في IDE. على سبيل المثال ، بعد كتابة $this->queryParameter('someParameter)-> ستوفر لك IDE جميع أنواع الإدخال الممكنة ، eg string() أو int() . بعد اختيار نوع ما ، على سبيل string() ، ستقدم IDE الخاص بك required() أو defaultsTo(defaultValue) لتحديد السلوك عند عدم تعيين المعلمة.



تدعم المكتبة التحليل الثابت من قبل IDE الخاص بك. على سبيل المثال ، عند وجود معلمة مثل $createdAt = $this->queryParameter('createdAt')->dateTime()->required(); ، ستعرف IDE الخاص بك أن $createdAt هو كائن DateTime . يتيح لك ذلك اكتشاف أخطاء النوع أثناء التحرير وخفض تكلفة صيانة الإجراء لأن الأنواع تعمل على تحسين الوضوح.
تقلل المكتبة أيضًا من خطر القيم الخالية غير المتوقعة لأن المعلمات لها دائمًا قيمة افتراضية صريحة أو مطلوبة.
عندما تكون المعلمة مطلوبة ولكن لم يتم العثور عليها أو عند فشل التحقق من الصحة ، ستقوم المكتبة بإلقاء استثناء. الاستثناءات الافتراضية هي MPScholtenRequestParserNotFoundException و MPScholtenRequestParserInvalidValueException . الطريقة المقترحة للتعامل مع الأخطاء التي ألقاها المكتبة هي التقاطها داخل وحدة التحكم الأمامية الخاصة بك:
try {
$ controller -> $ action ();
} catch ( NotFoundException $ e ) {
echo $ e -> getMessage ();
} catch ( InvalidValueException $ e ) {
echo $ e -> getMessage ();
} class MyController
{
use MPScholten RequestParser Symfony ControllerHelperTrait;
public function __construct ( Request $ request )
{
$ exceptionFactory = new ExceptionFactory (CustomNotFoundException::class, CustomInvalidValueException::class));
$ config = new MPScholten RequestParser Config ();
$ config -> setExceptionFactory ( $ exceptionFactory );
$ this -> initRequestParser ( $ request , $ config );
}
} إذا كنت بحاجة إلى تجاوز رسالة الاستثناء التي ألقيتها المكتبة مرة أو مرتين فقط ، فيمكنك القيام بذلك عن طريق تمرير رسائل الاستثناء كوسيطة أول وثانية إلى ->required() :
class DashboardController
{
public function show ()
{
$ dashboardId = $ this -> queryParameter ( ' id ' )-> int ()-> required ( " The dashboard id has to be a valid number " , " No dashboard id given " );
}
}إذا كنت لا ترغب في تحديد رسالة استثناء مخصصة لجميع إجراءاتك ، ولكن لا تزال لا ترغب في استخدام رسائل الاستثناء المدمجة ، يمكنك توفير مولد رسالة الاستثناء الخاص بك:
class FriendlyExceptionMessageFactory extends MPScholten RequestParser ExceptionMessageFactory
{
protected function createNotFoundMessage ( $ parameterName )
{
return " Looks like $ parameterName is missing :) " ;
}
protected function createInvalidValueMessage ( $ parameterName , $ parameterValue , $ expected )
{
return " Whoops :) $ parameterName seems to be invalid. We're looking for $ expected but you provided ' $ parameterValue ' " ;
}
}
class MyController
{
use MPScholten RequestParser Symfony ControllerHelperTrait;
public function __construct ( Request $ request )
{
$ config = new MPScholten RequestParser Config ();
$ config -> setExceptionMessageFactory ( new FriendlyExceptionMessageFactory ());
$ this -> initRequestParser ( $ request , $ config );
}
}تحقق من هذا المثال حول الاستثناءات المخصصة.
قطعاً. تم تطوير هذه المكتبة في البداية في خماسي ويتم استخدامها على نطاق واسع في الإنتاج منذ أبريل 2015. إن استخدامها على نطاق واسع في الإنتاج يعني أن هناك تركيزًا كبيرًا على التوافق المتخلف وعدم كسر الأشياء.
composer tests
composer tests-coverage
لا تتردد في إرسال طلبات السحب!