PhalconPHP: حل لواجهات برمجة تطبيقات RESTful عالية التحميل
نشرت: 2022-03-11لنفترض أنك بحاجة إلى إنشاء مشروع عالي التحميل يعتمد على إطار عمل PHP MVC. من المحتمل أن تستخدم التخزين المؤقت حيثما أمكن ذلك. ربما ستنشئ المشروع في ملف واحد ، أو ربما تكتب إطار عمل MVC الخاص بك بأقل قدر من الوظائف ، أو تعيد كتابة بعض أجزاء إطار عمل آخر. بينما ، نعم ، هذا يعمل ، إنه صعب بعض الشيء ، أليس كذلك؟ لحسن الحظ ، هناك حل آخر يجعل معظم هذه التلاعبات غير ضرورية (باستثناء ذاكرة التخزين المؤقت ، ربما) ، ويسمى هذا الحل إطار عمل PhalconPHP.
ما هو PhalconPHP؟
PhalconPHP هو إطار عمل MVC لـ PHP مكتوب بلغة C ويتم توفيره كملحق PHP مترجم. هذا ما يجعله أحد أسرع الأطر المتاحة (لكي نكون صادقين تمامًا ، أسرع واحد هو Yaf ، لكنه إطار صغير ولديه وظائف محدودة أكثر بكثير من Phalcon). لا يحتاج PhalconPHP إلى أي عمليات طويلة مع ملفات PHP ولا يحتاج إلى تفسيره عند كل طلب - يتم تحميله في ذاكرة الوصول العشوائي مرة واحدة عند بدء تشغيل خادم الويب الخاص بك ويستهلك القليل جدًا من الموارد.
تم اعتبار أطر عمل MVC من أفضل الممارسات في تطوير الويب لفترة طويلة - وهي الآن نوع من المعايير الاحترافية ، لذلك فإن معظم مطوري الويب على دراية بإطار عمل MVC واحد على الأقل لـ PHP: Symfony و Yii و Laravel و CodeIgniter و Zend الإطار ، إلخ. لكل منها مزاياها وعيوبها ، ولكن ما هو الشيء المشترك بينها؟ كُتبت جميعها بلغة PHP وتتكون من العديد من ملفات PHP المضمنة مع قدر كبير من المنطق الذي يجب تشغيله بواسطة المترجم الفوري عند كل طلب ، في كل مرة يتم فيها تشغيل الكود الخاص بك. في حين أن هذا يؤدي إلى شفافية كبيرة ، إلا أننا ندفع مقابل الأداء. الكميات الكبيرة من التعليمات البرمجية والكثير من الملفات المضمنة تكلف قدرًا كبيرًا من الذاكرة والوقت ، خاصة في PHP (نظرًا لأنها مفسرة وليست مجمعة). نعم ، لقد أصبح الوضع أفضل بكثير في PHP 7 ، ولكن لا يزال هناك الكثير للتحسين ، و PhalconPHP يجلب هذه التحسينات إلى الجدول.
دعونا نلقي نظرة على بعض المعايير.
معايير PhalconPHP
المعايير الرسمية عمرها خمس سنوات — أقدم من أن تكون صالحة الآن ، ولكن حتى ذلك الحين يمكنك أن ترى بشكل كبير ما يميز PhalconPHP. لنلقِ نظرة على شيء أحدث. في مقارنة عام 2016 ، احتلت Phalcon المراكز الخمسة الأولى - قائد واضح بين الأطر المهنية ، وتقبل فقط لـ PHP الخام وبعض الأطر الصغيرة.
لذا فالكون سريع. يعد Raw PHP سريعًا أيضًا ، لكننا بحاجة إلى جميع التسهيلات التي يقدمها إطار عمل MVC ، ويرتقي Phalcon إلى مستوى التحدي ، بما في ذلك مكونات مثل:
- ORM
- محرك قالب فولت
- حاوية حقن التبعية (DI)
- التخزين المؤقت
- تسجيل
- أنظمة التوجيه
- كتلة الأمان
- اللودر التلقائي
- وحدة النماذج
هذا على سبيل المثال لا الحصر. لقص قصة طويلة ، فإن PhalconPHP لديه كل ما تحتاجه لبناء تطبيق مؤسسي كبير مثل RESTful API لنظام تحميل عالي.
شيء آخر لطيف في Phalcon هو أسلوبه الصغير - فقط قارن Phalcon ORM والعقيدة الهائلة 2.
دعنا نلقي نظرة على إنشاء مشروع PhalconPHP.
نوعان من مشاريع فالكون: Full-stack و Micro
بشكل عام ، هناك نوعان من أطر MVC: أطر عمل مكدس كاملة (مثل Symfony ، Yii) وأطر عمل مصغرة (مثل Lumen ، Slim ، Silex).
تعد أطر العمل الكاملة خيارًا جيدًا لمشروع كبير نظرًا لأنها توفر المزيد من الوظائف ، ولكنها تحتاج إلى مزيد من التأهيل والوقت للتشغيل.
تسمح لك أطر العمل المصغرة بإنشاء نماذج أولية خفيفة الوزن بسرعة كبيرة ، لكنها تفتقر إلى الوظائف ، ولذا فمن الأفضل عمومًا تجنب استخدامها في المشاريع الكبيرة. ومع ذلك ، فإن إحدى مزايا الأطر الصغيرة هي أدائها. عادة ما تكون أسرع بكثير من المكدس الكامل (على سبيل المثال ، يكون إطار عمل Yaf أقل جودة في الأداء من PHP الخام فقط).
يدعم PhalconPHP كليهما: يمكنك إما إنشاء تطبيق مكدس كامل أو تطبيق صغير. والأفضل من ذلك ، عندما تقوم بتطوير مشروعك في PhalconPHP كتطبيق صغير ، فلا يزال بإمكانك الوصول إلى معظم ميزات Phalcon القوية ولا يزال أداؤها أسرع من تطبيق مكدس كامل.
في وظيفة سابقة ، احتاج فريقي إلى بناء نظام RESTful عالي التحميل. كان أحد الأشياء التي قمنا بها هو مقارنة أداء النموذج الأولي بين تطبيق مكدس كامل في Phalcon وتطبيق Phalcon الصغير. وجدنا أن تطبيقات PhalconPHP الصغيرة تميل إلى أن تكون أسرع كثيرًا. لا يمكنني عرض أي معايير مرجعية لأسباب تتعلق باتفاقية عدم الإفشاء ، ولكن في رأيي ، إذا كنت تريد تحقيق أقصى استفادة من أداء Phalcon ، فاستخدم التطبيقات الدقيقة. على الرغم من أنه أقل ملاءمة لتشفير تطبيق مصغر من تطبيق مكدس كامل ، إلا أن تطبيقات PhalconPHP الصغيرة لا تزال تحتوي على كل ما قد تحتاجه لمشروعك وأداء أفضل. للتوضيح ، دعنا نكتب تطبيقًا صغيرًا بسيطًا للغاية RESTful في فالكون.
بناء RESTful API
تشترك جميع التباديل في تطبيق RESTful تقريبًا في شيء واحد: كيان User
. لذلك ، بالنسبة لمشروعنا كمثال ، سننشئ تطبيق REST صغيرًا لإنشاء المستخدمين وقراءتهم وتحديثهم وحذفهم (المعروف أيضًا باسم CRUD).
يمكنك رؤية هذا المشروع مكتملًا بالكامل في مستودع GitLab الخاص بي. يوجد فرعين هناك لأنني قررت تقسيم هذا المشروع إلى جزأين: الفرع الأول ، master
، يحتوي فقط على الوظائف الأساسية دون أي ميزات PhalconPHP محددة ، بينما يحتوي الثاني ، logging-and-cache
والتخزين المؤقت ، على وظائف Phalcon للتسجيل والتخزين المؤقت. يمكنك مقارنتها ومعرفة مدى سهولة تنفيذ مثل هذه الوظائف في فالكون.
تثبيت
لن أتجاوز التثبيت: يمكنك استخدام أي قاعدة بيانات وأي نظام تشغيل وأي خادم ويب تريده. إنه موصوف جيدًا في وثائق التثبيت الرسمية ، لذلك ما عليك سوى اتباع التعليمات اعتمادًا على نظام التشغيل الخاص بك.
ملاحظات تثبيت خادم الويب متاحة أيضًا في وثائق Phalcon الرسمية.
لاحظ أن إصدار PHP الخاص بك يجب ألا يقل عن 5.6.
أستخدم Ubuntu 16.10 و PostgreSQL 9.5.6 و Nginx 1.10.0 و PHP 7 و Phalcon 3.0. لقد قمت بتضمين نموذج تكوين Nginx وملف تفريغ PostgreSQL في المشروع ، لذلك لا تتردد في استخدامها. إذا كنت تفضل تكوينًا آخر ، فلن يكون من الصعب تغييره.
هيكل المشروع والتكوين
بادئ ذي بدء ، قم بإنشاء الهيكل الأولي للمشروع.
بينما يتيح لك Phalcon استخدام أي هيكل تريده ، فإن الهيكل الذي اخترته لهذا التمرين يطبق جزئيًا نمط MVC. ليس لدينا أي طرق عرض لأنه مشروع RESTful ، لكن لدينا وحدات تحكم ونماذج ، لكل منها مجلدها وخدماتها. الخدمات هي الفئات التي تنفذ منطق الأعمال للمشروع ، وتقسيم جزء "النموذج" من MVC إلى جزأين: نماذج البيانات (التي تتصل بقاعدة البيانات) ونماذج منطق الأعمال.
index.php
، الموجود في المجلد public
، هو ملف bootstrap الذي يقوم بتحميل جميع الأجزاء والتكوينات الضرورية. لاحظ أنه يتم وضع جميع ملفات التكوين الخاصة بنا في مجلد config
. يمكننا وضع هذه في ملف التمهيد (وهذه هي الطريقة الموضحة في الوثائق الرسمية) ، ولكن ، في رأيي ، يصبح هذا غير قابل للقراءة في المشاريع الكبيرة ، لذلك أفضل فصل المجلد من البداية.
إنشاء index.php
سيحمل المرور الأول الخاص بنا في index.php
فئات التكوين والتحميل التلقائي ، ثم تهيئة المسارات ، وحاوية حقن التبعية ، وتطبيق PhalconPHP الصغير. وبعد ذلك سيمرر التحكم إلى جوهر التطبيق الصغير ، والذي سيتعامل مع الطلبات وفقًا للطرق ، وتشغيل منطق الأعمال ، وإرجاع النتائج.
دعنا نلقي نظرة على الكود:
<?php try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( function () use ($app) { // Returning a successful response } ); // Processing request $app->handle(); } catch (\Exception $e) { // Returning an error response }
تكوين كائن \Phalcon\Config
هناك عدة طرق لتخزين ملفات التكوين في فالكون:
- ملف YAML
- ملف JSON
- ملف INI
- مصفوفة PHP
يعد تخزين التكوين الخاص بك في مصفوفة PHP هو الخيار الأسرع ، وبما أننا نكتب تطبيقًا عالي التحميل ولا نحتاج إلى إبطاء أدائنا ، فهذا ما سنفعله. على وجه التحديد ، سنستخدم كائن \Phalcon\Config
لتحميل خيارات التكوين الخاصة بنا في المشروع. سيكون لدينا كائن تكوين قصير جدًا:
<?php return new \Phalcon\Config( [ 'database' => [ 'adapter' => 'Postgresql', 'host' => 'localhost', 'port' => 5432, 'username' => 'postgres', 'password' => '12345', 'dbname' => 'articledemo', ], 'application' => [ 'controllersDir' => "app/controllers/", 'modelsDir' => "app/models/", 'baseUri' => "/", ], ] );
يحتوي هذا الملف على تكوينين أساسيين ، أحدهما لقاعدة البيانات والآخر للتطبيق. من الواضح أن تكوين قاعدة البيانات يُستخدم للاتصال بقاعدة البيانات ، أما بالنسبة إلى مصفوفة application
، فسنحتاجها لاحقًا نظرًا لاستخدامها بواسطة أدوات نظام Phalcon. يمكنك أن تقرأ عن تكوينات فالكون بمزيد من التفصيل في الوثائق الرسمية.
تكوين loader.php
لنلقِ نظرة على ملف التكوين التالي ، loader.php
. يسجل ملف loader.php
مساحات الأسماء مع الدلائل المقابلة عبر الكائن \Phalcon\Loader
. إنه أبسط:
<?php $loader = new \Phalcon\Loader(); $loader->registerNamespaces( [ 'App\Services' => realpath(__DIR__ . '/../services/'), 'App\Controllers' => realpath(__DIR__ . '/../controllers/'), 'App\Models' => realpath(__DIR__ . '/../models/'), ] ); $loader->register();
سيتم الآن تحميل جميع الفئات من مساحات الأسماء هذه وإتاحتها تلقائيًا. إذا كنت ترغب في إضافة دليل ومساحة اسم جديدين ، فما عليك سوى إضافة سطر في هذا الملف. يمكنك أيضًا تجنب استخدام مساحات الأسماء من خلال تسجيل أدلة معينة أو ملفات محددة. كل هذه الاحتمالات موصوفة في وثائق محمل PhalconPHP.
تكوين حاوية حقن التبعية
مثل العديد من الأطر المعاصرة الأخرى ، يطبق فالكون نمط حقن التبعية (DI). ستتم تهيئة الكائنات في حاوية DI ويمكن الوصول إليها منها. وبالمثل ، فإن حاوية DI متصلة بكائن التطبيق ، ويمكن الوصول إليها من جميع الفئات التي ترث من فئة \Phalcon\DI\Injectable
، مثل وحدات التحكم والخدمات الخاصة بنا.
نمط Phalcon's DI قوي جدًا. أنا أعتبر هذا المكون واحدًا من أهم المكونات في هذا الإطار وأوصي بشدة بقراءة وثائقه بالكامل لفهم كيفية عمله. يوفر المفتاح للعديد من وظائف فالكون.
دعونا نلقي نظرة على القليل منهم. سيبدو ملف di.php
الخاص بنا بهذا الشكل:
<?php use Phalcon\Db\Adapter\Pdo\Postgresql; // Initializing a DI Container $di = new \Phalcon\DI\FactoryDefault(); /** * Overriding Response-object to set the Content-type header globally */ $di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } ); /** Common config */ $di->setShared('config', $config); /** Database */ $di->set( "db", function () use ($config) { return new Postgresql( [ "host" => $config->database->host, "username" => $config->database->username, "password" => $config->database->password, "dbname" => $config->database->dbname, ] ); } ); return $di;
كما ترى ، ملف حقن التبعية (DI) الخاص بنا أكثر تعقيدًا بعض الشيء وهناك بعض التفاصيل التي يجب أن تكون على دراية بها. أولاً ، ضع في اعتبارك سلسلة التهيئة: $di = new \Phalcon\DI\FactoryDefault();
. نقوم بإنشاء كائن FactoryDefault
الذي يرث \Phalcon\Di
(يسمح لك Phalcon بإنشاء أي مصنع DI تريده). وفقًا للوثائق ، FactoryDefault
"تسجل تلقائيًا جميع الخدمات التي يوفرها إطار العمل. بفضل هذا ، لا يحتاج المطور إلى تسجيل كل خدمة على حدة لتوفير إطار عمل مكدس كامل ". هذا يعني أنه سيتم الوصول إلى الخدمات المشتركة مثل Request
Response
ضمن فئات إطار العمل. يمكنك الاطلاع على القائمة الكاملة لهذه الخدمات في وثائق خدمة فالكون.
الشيء المهم التالي هو عملية الإعداد: هناك عدة طرق لتسجيل شيء ما في حاوية DI ، وكلها موصوفة بالكامل لوثائق تسجيل PhalconPHP. لكن في مشروعنا ، نستخدم ثلاث طرق: دالة مجهولة ومتغير وسلسلة.
تتيح لنا الوظيفة المجهولة القيام بالعديد من الأشياء أثناء تهيئة الفصل. في هذا المشروع على وجه التحديد ، قمنا أولاً بتجاوز كائن Response
لتعيين content-type
كـ JSON
لجميع استجابات المشروع ثم تهيئة محول قاعدة البيانات ، باستخدام كائن التكوين الخاص بنا.
كما ذكرت من قبل ، يستخدم هذا المشروع PostgreSQL. إذا قررت استخدام مشغل قاعدة بيانات آخر ، فما عليك سوى تغيير محول قاعدة البيانات في وظيفة تعيين db
. يمكنك قراءة المزيد حول محولات قاعدة البيانات المتوفرة وعن طبقة قاعدة البيانات في وثائق قاعدة بيانات PhalconPHP.
الشيء الثالث الذي يجب ملاحظته هو أنني قمت بتسجيل متغير $config
والذي يقوم بتنفيذ خدمة \Phalcon\Config
. على الرغم من عدم استخدامه فعليًا ضمن مشروعنا النموذجي ، فقد قررت تضمينه هنا لأنه أحد أكثر الخدمات شيوعًا ؛ قد تحتاج المشاريع الأخرى إلى الوصول إلى التكوين في كل مكان تقريبًا.
الشيء الأخير المثير للاهتمام هنا هو طريقة setShared
الفعلية نفسها. استدعاء هذا يجعل الخدمة "مشتركة" ، مما يعني أنها تبدأ في التصرف كخدمة فردية. وفقًا للوثائق: "بمجرد حل الخدمة لأول مرة ، يتم إرجاع نفس مثيلها في كل مرة يسترد فيها المستهلك الخدمة من الحاوية."
تكوين routes.php
… or Not
الملف المضمن الأخير هو routes.php
. دعنا نتركه فارغًا الآن - سنملأه جنبًا إلى جنب مع وحدات التحكم الخاصة بنا.
تنفيذ RESTful Core
ما الذي يجعل مشروع الويب مريحًا؟ وفقًا لـ Wikipedia ، هناك ثلاثة أجزاء رئيسية لتطبيق RESTful: - عنوان URL الأساسي - نوع وسائط الإنترنت الذي يحدد عناصر بيانات انتقال الحالة - طرق HTTP القياسية ( GET
، POST
، PUT
، DELETE
) ورموز استجابة HTTP القياسية (200 ، 403 ، 400 ، 500 ، إلخ).
في مشروعنا ، سيتم وضع عناوين URL الأساسية في ملف routes.php
وسيتم وصف النقاط الأخرى المذكورة الآن.
سوف نتلقى بيانات الطلب على شكل application/x-www-form-urlencoded
ونرسل بيانات الاستجابة application/json
. على الرغم من أنني لا أعتقد أنه من الجيد استخدام x-www-form-urlencoded
في تطبيق حقيقي (نظرًا لأنك ستكافح لإرسال هياكل بيانات معقدة ومصفوفات ارتباطية باستخدام x-www-form-urlencoded
) ، قررت تنفيذ هذا المعيار من أجل البساطة.
إذا كنت تتذكر ، فقد قمنا بالفعل بتعيين رأس JSON للرد في ملف DI الخاص بنا:
$di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } );
الآن يتعين علينا إعداد رموز الاستجابة وتنسيق الاستجابة. يقترح البرنامج التعليمي الرسمي أننا نشكل استجابات JSON في كل طريقة ، لكنني لا أعتقد أنها فكرة جيدة. يعد إرجاع نتائج طريقة وحدة التحكم كمصفوفات ثم تحويلها إلى استجابات JSON القياسية أكثر عالمية. من الحكمة أيضًا تكوين أكواد استجابة HTTP في مكان واحد داخل المشروع ؛ سنقوم بذلك في ملف index.php
الخاص بنا.
للقيام بذلك ، سنستخدم قدرة Phalcon على تنفيذ التعليمات البرمجية قبل وبعد معالجة الطلب باستخدام $app->before()
و $app->after()
. سنضع رد اتصال في طريقة $app->after()
:
// Making the correct answer after executing $app->after( function () use ($app) { // Getting the return value of method $return = $app->getReturnedValue(); if (is_array($return)) { // Transforming arrays to JSON $app->response->setContent(json_encode($return)); } elseif (!strlen($return)) { // Successful response without any content $app->response->setStatusCode('204', 'No Content'); } else { // Unexpected response throw new Exception('Bad Response'); } // Sending response to the client $app->response->send(); }
هنا نحصل على القيمة المرجعة ونحول المصفوفة إلى JSON. إذا كان كل شيء على ما يرام ، ولكن القيمة المعادة كانت فارغة (على سبيل المثال ، إذا أضفنا مستخدمًا جديدًا بنجاح) ، فسنقدم رمز HTTP 204 ولن نرسل أي محتوى. في جميع الحالات الأخرى ، نطرح استثناء.
معالجة الاستثناءات
أحد أهم جوانب تطبيق RESTful هو الاستجابات الصحيحة والغنية بالمعلومات. عادةً ما تكون التطبيقات عالية التحميل كبيرة ، ويمكن أن تحدث أخطاء من أنواع مختلفة في كل مكان: أخطاء التحقق من الصحة وأخطاء الوصول وأخطاء الاتصال والأخطاء غير المتوقعة وما إلى ذلك. نريد تحويل كل هذه الأخطاء إلى رموز استجابة HTTP موحدة. يمكن القيام بذلك بسهولة بمساعدة الاستثناءات.
في مشروعي ، قررت استخدام نوعين مختلفين من الاستثناءات: هناك استثناءات "محلية" - فئات خاصة موروثة من فئة \RuntimeException
، مفصولة بالخدمات والنماذج والمحولات وما إلى ذلك (يساعد هذا التقسيم في معالجة كل مستوى نموذج MVC كنموذج منفصل) - ثم هناك HttpExceptions
، موروثة من فئة AbstractHttpException
. تتوافق هذه الاستثناءات مع رموز استجابة HTTP ، لذا فإن أسمائها هي Http400Exception
و Http500Exception
وما إلى ذلك.
للفئة AbstractHttpException
ثلاث خصائص: httpCode
و httpMessage
و appError
. تم تجاوز الخاصية الأولى والثانية في وراثتها وتحتوي على معلومات الاستجابة الأساسية مثل httpCode: 400
و httpMessage: Bad request
. الخاصية appError
هي مجموعة من معلومات الخطأ التفصيلية ، بما في ذلك وصف الخطأ.
سيحصل الإصدار الأخير من index.php
على ثلاثة أنواع من الاستثناءات: AbstractHttpExceptions
، كما هو موضح أعلاه ؛ استثناءات طلب فالكون ، التي قد تحدث أثناء تحليل الطلب ؛ وجميع الاستثناءات الأخرى غير المتوقعة. يتم تحويلها جميعًا إلى تنسيق JSON جميل وإرسالها إلى العميل عبر فئة Phalcon Response القياسية:
<?php use App\Controllers\AbstractHttpException; try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( // After Code ); // Processing request $app->handle(); } catch (AbstractHttpException $e) { $response = $app->response; $response->setStatusCode($e->getCode(), $e->getMessage()); $response->setJsonContent($e->getAppError()); $response->send(); } catch (\Phalcon\Http\Request\Exception $e) { $app->response->setStatusCode(400, 'Bad request') ->setJsonContent([ AbstractHttpException::KEY_CODE => 400, AbstractHttpException::KEY_MESSAGE => 'Bad request' ]) ->send(); } catch (\Exception $e) { // Standard error format $result = [ AbstractHttpException::KEY_CODE => 500, AbstractHttpException::KEY_MESSAGE => 'Some error occurred on the server.' ]; // Sending error response $app->response->setStatusCode(500, 'Internal Server Error') ->setJsonContent($result) ->send(); }
إنشاء نماذج باستخدام أدوات فالكون ديف
إذا كنت تستخدم IDE معاصرًا ، فمن المحتمل أن تكون معتادًا على تمييز الكود وإكماله. وبالمثل ، في إطار عمل PHP نموذجي ، يمكنك تضمين مجلد به إطار عمل للانتقال إلى إعلان الوظيفة بنقرة واحدة فقط. نظرًا لأن Phalcon هو امتداد ، فإننا لا نحصل على هذا الخيار تلقائيًا. لحسن الحظ ، هناك أداة تملأ هذه الفجوة تسمى "Phalcon Dev Tools" ، والتي يمكن تثبيتها عبر Composer (إذا كنت لا تزال لا تعرف ماهيتها ، فقد حان الوقت للتعرف على مدير الحزم المذهل هذا). تتكون Phalcon Dev Tools من أجزاء التعليمات البرمجية لجميع الفئات والوظائف في Phalcon ، وتوفر بعض مولدات الأكواد مع كل من إصدارات وحدة التحكم وواجهة المستخدم الرسومية ، الموثقة على موقع PhalconPHP. يمكن أن تساعد هذه الأدوات في إنشاء جميع أجزاء نموذج MVC ، لكننا سنغطي فقط إنشاء النموذج.

حسنًا ، دعنا نثبت Phalcon Dev Tools عبر Composer. سيبدو ملف composer.json
الخاص بنا بهذا الشكل:
{ "require": { "php": ">=5.6.0", "ext-phalcon": ">=3", "ext-pgsql": "*" }, "require-dev": { "phalcon/devtools": "3.*.*@dev" } }
كما ترى ، نحتاج إلى PHP 5.6 و Phalcon 3 وامتداد pgsql
(والذي يمكنك تغييره إلى امتداد قاعدة البيانات أو استبعاده تمامًا).
تأكد من حصولك على إصدارات امتدادات PHP و Phalcon و DB الصحيحة ، وقم بتشغيل الملحن:
$ composer install
الخطوة التالية هي إنشاء قاعدة البيانات الخاصة بنا. إنه بسيط للغاية ويتكون من جدول واحد فقط ، users
. على الرغم من أنني قمت بتضمين ملف pg_dump
في المشروع ، فإليك SQL في لهجة PostgreSQL:
CREATE DATABASE articledemo; CREATE TABLE public.users ( id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('users_id_seq'::regclass), first_name CHARACTER VARYING(255), last_name CHARACTER VARYING(255), pass CHARACTER VARYING(255), login CHARACTER VARYING(255) NOT NULL );
الآن بعد أن تم إنشاء قاعدة البيانات ، يمكننا المتابعة إلى عملية إنشاء النموذج. تستخدم أدوات Phalcon Dev Tools مجلد .phalcon
فارغًا لاكتشاف ما إذا كان التطبيق هو مشروع Phalcon ، لذلك سيتعين عليك إنشاء هذا المجلد الفارغ في جذر مشروعك. يستخدم أيضًا بعض الإعدادات من ملف التكوين الذي أنشأناه — جميع المتغيرات المخزنة ضمن قسم application
adapter
من قسم database
. لإنشاء نموذجنا ، نحتاج إلى تنفيذ الأمر التالي من المجلد الجذر للمشروع:
$ php vendor/phalcon/devtools/phalcon.php model users --namespace="App\Models" --get-set
إذا تم تنفيذ جميع الخطوات السابقة بشكل صحيح ، فستحصل على ملف نموذج عامل ، Users.php
، في مجلد models
الخاص بك ، والذي تم وضعه بالفعل في مساحة اسم مع حروف ومحددات كما هو موضح في سطر الأوامر. التالي هو وحدة التحكم.
أجهزة التحكم والتوجيه
نظرًا لأن تطبيقنا يستخدم CRUDs فقط (ينشئ ويقرأ ويحدث ويحذف) المستخدمين ، فسننشئ وحدة تحكم واحدة فقط ، وحدة تحكم Users
بالعمليات التالية:
- إضافة مستخدم
- عرض قائمة المستخدمين
- تحديث المستخدم
- مسح المستخدم
بينما يمكن إنشاء وحدات التحكم بمساعدة Phalcon Dev Tools ، سنفعل ذلك يدويًا ونطبق AbstractController
UsersController
.
يعد إنشاء AbstractController
قرارًا جيدًا لفالكون لأنه يمكننا وضع جميع الفئات الضرورية التي سنحصل عليها من حقن التبعية في كتلة PHPDoc. سيساعد هذا في وظيفة الإكمال التلقائي لـ IDE. يمكننا أيضًا البرمجة في بعض ثوابت الخطأ الشائعة لجميع وحدات التحكم المحتملة.
في الوقت الحالي ، ستبدو وحدة التحكم المجردة كما يلي:
<?php namespace App\Controllers; /** * Class AbstractController * * @property \Phalcon\Http\Request $request * @property \Phalcon\Http\Response $htmlResponse * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config * @property \App\Services\UsersService $usersService * @property \App\Models\Users $user */ abstract class AbstractController extends \Phalcon\DI\Injectable { /** * Route not found. HTTP 404 Error */ const ERROR_NOT_FOUND = 1; /** * Invalid Request. HTTP 400 Error. */ const ERROR_INVALID_REQUEST = 2; }
مجرد فئة حقن بسيطة من extends
، كما هو محدد في بناء الجملة الموسع ، لا شيء أكثر من ذلك. بعد ذلك ، دعنا ننشئ الهيكل العظمي UsersController
:
<?php namespace App\Controllers; /** * Operations with Users: CRUD */ class UsersController extends AbstractController { /** * Adding user */ public function addAction() { } /** * Returns user list * * @return array */ public function getUserListAction() { } /** * Updating existing user * * @param string $userId */ public function updateUserAction($userId) { } /** * Delete an existing user * * @param string $userId */ public function deleteUserAction($userId) { } }
في الوقت الحالي ، إنها مجرد فئة ذات إجراءات فارغة ستحتوي في النهاية على طلبات HTTP المقابلة.
حان الوقت الآن لملء ملف routes.php
. في تطبيقات Phalcon الصغيرة ، نقوم بإنشاء مجموعات ، واحدة لكل وحدة تحكم ، وإضافة جميع الطلبات التي تمت معالجتها مثل طرق get
، و post
، و put
، و delete
، والتي تأخذ نمط مسار ووظيفة متابعة كوسائط. لاحظ أن دالة المتابعة يجب أن تكون إما دالة مجهولة أو اسم طريقة لوحدة التحكم. هذا هو الشكل الذي يبدو عليه ملف routes.php
الخاص بنا:
<?php $usersCollection = new \Phalcon\Mvc\Micro\Collection(); $usersCollection->setHandler('\App\Controllers\UsersController', true); $usersCollection->setPrefix('/user'); $usersCollection->post('/add', 'addAction'); $usersCollection->get('/list', 'getUserListAction'); $usersCollection->put('/{userId:[1-9][0-9]*}', 'updateUserAction'); $usersCollection->delete('/{userId:[1-9][0-9]*}', 'deleteUserAction'); $app->mount($usersCollection); // not found URLs $app->notFound( function () use ($app) { $exception = new \App\Controllers\HttpExceptions\Http404Exception( _('URI not found or error in request.'), \App\Controllers\AbstractController::ERROR_NOT_FOUND, new \Exception('URI not found: ' . $app->request->getMethod() . ' ' . $app->request->getURI()) ); throw $exception; } );
قمنا أيضًا بتعيين وحدة تحكم معالجة وبادئة URI. على سبيل المثال ، سيبدو URI بالشكل http://article.dev/user/add
، ويجب أن يكون طلب post
. إذا أردنا تغيير بيانات المستخدم ، فيجب أن يكون URI طلب put
وسيبدو مثل http://article.dev/user/12
لتغيير البيانات للمستخدم بمعرف 12
. نحدد أيضًا معالج URL غير موجود ، والذي يتسبب في حدوث خطأ. لمزيد من المعلومات ، راجع وثائق PhalconPHP للتوجيهات في تطبيق مكدس كامل ، وللمسارات في تطبيق مصغر.
دعنا ننتقل إلى جسم وحدة التحكم ، وتحديداً طريقة addAction
(كل الآخرين متشابهون ؛ يمكنك رؤيتهم في كود التطبيق). تقوم طريقة التحكم بخمسة أشياء:
- يحصل ويتحقق من صحة معلمات الطلب
- تعد البيانات لطريقة الخدمة
- يستدعي طريقة الخدمة
- يعالج الاستثناءات
- يرسل الرد
لنستعرض كل خطوة ، بدءًا من التحقق من الصحة. بينما يحتوي Phalcon على مكون تحقق قوي ، إلا أنه من الأنسب بكثير التحقق من صحة البيانات بطريقة قديمة في هذه الحالة ، لذلك ، ستبدو كتلة التحقق الخاصة بنا كما يلي:
$errors = []; $data = []; $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; }
نتحقق هنا مما إذا كانت المعلمة post
عبارة عن سلسلة تطابق تعبيرًا عاديًا. يتم وضع جميع القيم في مصفوفة $data
، والتي يتم تمريرها بعد ذلك إلى فئة UsersService
. يتم وضع جميع الأخطاء في مصفوفة $errors
، والتي تُضاف بعد ذلك إلى مصفوفة تفاصيل الخطأ داخل Http400Exception
، حيث سيتم تحويلها إلى الاستجابة التفصيلية التي تظهر في index.php
:
إليك كود طريقة addAction
الكاملة مع كل عمليات التحقق من صحتها ، والتي تتضمن استدعاءًا للطريقة createUser
في UsersService
(التي لم نقم بإنشائها بعد):
public function addAction() { /** Init Block **/ $errors = []; $data = []; /** End Init Block **/ /** Validation Block **/ $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['password'] = $this->request->getPost('password'); if (!is_string($data['password']) || !preg_match('/^[A-z0-9_-]{6,18}$/', $data['password'])) { $errors['password'] = 'Password must consist of 6-18 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['first_name'] = $this->request->getPost('first_name'); if ((!empty($data['first_name'])) && (!is_string($data['first_name']))) { $errors['first_name'] = 'String expected'; } $data['last_name'] = $this->request->getPost('last_name'); if ((!empty($data['last_name'])) && (!is_string($data['last_name']))) { $errors['last_name'] = 'String expected'; } if ($errors) { $exception = new Http400Exception(_('Input parameters validation error'), self::ERROR_INVALID_REQUEST); throw $exception->addErrorDetails($errors); } /** End Validation Block **/ /** Passing to business logic and preparing the response **/ try { $this->usersService->createUser($data); } catch (ServiceException $e) { switch ($e->getCode()) { case AbstractService::ERROR_ALREADY_EXISTS: case UsersService::ERROR_UNABLE_CREATE_USER: throw new Http422Exception($e->getMessage(), $e->getCode(), $e); default: throw new Http500Exception(_('Internal Server Error'), $e->getCode(), $e); } } /** End Passing to business logic and preparing the response **/ }
كما ترى ، فإننا نتعامل مع استثناءين معروفين في هذا القسم الأخير: user already exists
وغير unable to create user
بسبب بعض المشاكل الداخلية مثل خطأ اتصال قاعدة البيانات. بشكل افتراضي ، سيتم طرح الاستثناءات غير المعروفة على أنها HTTP 500
(خطأ داخلي في الخادم). على الرغم من أننا لا نعطي أي تفاصيل للمستخدم النهائي ، إلا أنه يوصى بشدة بتخزين جميع تفاصيل الخطأ (بما في ذلك التتبع) في السجل.
ورجاءً ، لا تنس use
جميع الفئات الضرورية ، المستعارة من مساحات الأسماء الأخرى:
use App\Controllers\HttpExceptions\Http400Exception; use App\Controllers\HttpExceptions\Http422Exception; use App\Controllers\HttpExceptions\Http500Exception; use App\Services\AbstractService; use App\Services\ServiceException; use App\Services\UsersService;
منطق الأعمال
الجزء الأخير الذي يجب إنشاؤه هو منطق الأعمال. تمامًا كما هو الحال مع وحدات التحكم ، سننشئ فئة خدمة مجردة:
<?php namespace App\Services; /** * Class AbstractService * * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config */ abstract class AbstractService extends \Phalcon\DI\Injectable { /** * Invalid parameters anywhere */ const ERROR_INVALID_PARAMETERS = 10001; /** * Record already exists */ const ERROR_ALREADY_EXISTS = 10002; }
الفكرة هي نفسها تمامًا كما في حظر وحدة التحكم ، لذلك لن أعلق عليها. إليك الهيكل العظمي لفئة UsersService
لدينا:
<?php namespace App\Services; use App\Models\Users; /** * business logic for users * * Class UsersService */ class UsersService extends AbstractService { /** Unable to create user */ const ERROR_UNABLE_CREATE_USER = 11001; /** * Creating a new user * * @param array $userData */ public function createUser(array $userData) { } }
وطريقة createUser
نفسها:
public function createUser(array $userData) { try { $user = new Users(); $result = $user->setLogin($userData['login']) ->setPass(password_hash($userData['password'], PASSWORD_DEFAULT)) ->setFirstName($userData['first_name']) ->setLastName($userData['last_name']) ->create(); if (!$result) { throw new ServiceException('Unable to create user', self::ERROR_UNABLE_CREATE_USER); } } catch (\PDOException $e) { if ($e->getCode() == 23505) { throw new ServiceException('User already exists', self::ERROR_ALREADY_EXISTS, $e); } else { throw new ServiceException($e->getMessage(), $e->getCode(), $e); } } }
هذه الطريقة سهلة بقدر الإمكان. نقوم فقط بإنشاء كائن نموذج جديد ، نسميه واضعيه (الذي يعيد الكائن نفسه ؛ هذا يسمح لنا بعمل سلسلة استدعاء) ServiceException
في حالة حدوث خطأ. هذا هو! يمكننا الآن الشروع في الاختبار.
اختبارات
الآن دعونا نلقي نظرة على النتائج باستخدام Postman. دعنا نختبر بعض بيانات المهملات أولاً:
طلب:
POST http://article.dev/user/add login:1 password:1 first_name:Name last_name:Sourname
الاستجابة (400: طلب غير صالح):
{ "error": 2, "error_description": "Input parameters validation error", "details": { "login": "Login must consist of 3-16 latin symbols, numbers or '-' and '_' symbols", "password": "Password must consist of 6-18 latin symbols, numbers or '-' and '_' symbols" } }
أن يتحقق. الآن لبعض البيانات الصحيحة:
طلب:
POST http://article.dev/user/add login:user4 password:password4 first_name:Name last_name:Sourname
الاستجابة (204):
No content, which is what we expected. Now let's make sure it worked and get the full user list (which we didn't describe in the article, but you can see it in the application example):
Request:
GET http://article.dev/user/list
Response (200 OK):
[ { "id": 1, "login": "user4", "first_name": "Name", "last_name": "Sourname" } ]
Well, it works!
Logging and Caching
It's hard to imagine a high-load application without logging and caching, and Phalcon provides very seductive classes for it. But I'm writing an article here, not a book; I've added logging and caching to the sample application, but I've placed this code into another branch called logging-and-cache
so you can easily look at it and see the difference in the code. Just like the other Phalcon features, these two are well-documented: Logging and Caching.
سلبيات
As you can see, Phalcon is really cool, but like other frameworks, it has its disadvantages, the first of which is the same as its main advantage—it's a compiled C extension. That's why there is no way for you to change its code easily. Well, if you know C, you can try to understand its code and make some changes, run make
and get your own modification of Phalcon, but it is much more complicated than making some tweaks in PHP code. So, generally, if you find a bug inside Phalcon, it won't be so easy to fix.
This is partially solved in Phalcon 2 and Phalcon 3, which let you write extensions to Phalcon in Zephir. Zephir is a programming language designed to ease the creation and maintainability of extensions for PHP with a focus on type and memory safety. Its syntax is very close to PHP and Zephir code is compiled into shared libraries, same as the PHP extension. So, if you want to enhance Phalcon, now you can.
The second disadvantage is the free framework structure. While Symfony makes developers use a firm project structure, Phalcon has very few strict rules; developers can create any structure they like, though there is a structure that is recommended by its authors. This isn't a critical disadvantage, but some people may consider it too raw when you write the paths to all the directories in a bootstrap file manually.
PhalconPHP: Not Just For High-load Apps
I hope you've enjoyed this brief overview of PhalconPHP's killing features and the accompanying simple example of a Phalcon project. Obviously, I didn't cover all the possibilities of this framework since it's impossible to describe all of them in one article, but fortunately Phalcon has brilliantly detailed documentation with seven marvelous tutorials which help you understand almost everything about Phalcon.
You've now got a brand new way to create high load applications easily, and you'll find, if you like Phalcon, it can be a good choice for other types of applications too.