الترحيب

هنالك العديد من المصادر القديمة على شبكة الإنترنت لتعلم لغة PHP التي تؤثر سلباً على المطورين الجدد، بحيث تقدم لهم عدة مفاهيم وأسس قد انتهى عصرها وصارت قديمة، بالإضافة لشيفرات برمجية غير آمنة. PHP: بالطريقة الصحيحة هو دليل سريع وسهل للطرق القياسية لبرمجة وتطوير PHP الأكثر شهرة، وروابط لدروس ودلائل موثوقة على شبكة الإنترنت وكل ما يعتبره المساهمون كمعايير قياسية حتى هذه اللحظة.

لا يوجد قاعدة موحدة للبرمجة باستخدام PHP. هذا الموقع يهدف إلى تعريف مطوري PHP الجدد إلى بعض المواضيع التي قد لا يعرفها أو يتطرق لها إلا بعد فوات الأوان، ويهدف أيضاً لتوضيح وتنقيح إيجابيات المواضيع التي قد تم التطرق إليها واستخدامها منذ عدة سنين من دون إعادة النظر إليها. لن يدلك هذا الموقع لاستخدام أدوات أو أساليب محددة، ولكن يوفر لك مقتراحات لأكثر من خيار، مع توضيح الفرق ما بينها إن أمكن ذلك مع أمثلة وطرق استخدام.

هذا الموقع حي ومحدث وسوف يتم تحديثه باستمرار بمعلومات وأمثلة مفيدة جديدة حين توافرها.

الترجمات

PHP: بالطريقة الصحيحة متوفر بعدة لغات اخرى:

كتاب

يمكن الحصول على أحدث نسخة من PHP بالطريقة الصحيحة في صيغة PDF ، EPUB ، MOBI. من Leanpub

كيفية المساهمة

قم بالمساعدة لجعل هذا الموقع أفضل مصدر لمبرمجي PHP الجدد قم بالمساهمة في تطوير المحتوى العربي على GitHub أو المحتوى الأصلي باللغة الإنجليزية على GitHub.

ملاحظة: يتم التحديث وإضافة المحتوى وفقاً للمحتوى باللغة الإنجليزية، إذا كان هنالك أي إضافة تود إدراجها فضلاً قم بزيارة المحتوى المحتوى الأصلي باللغة الإنجليزية على GitHub.

قم بالنشر!

PHP: بالطريقة الصحيحة لديه عدة بانرات صور يمكنك أن تضعها في موقعك. قم بالدعم ودع المطورين الجدد يعرفون المصدر لمعرفة معلومات مفيدة!

صور البانرات

للأعلى

لنبدأ

قم باستخدام آخر نسخة مستقرة (7.1)

إذا كنت تريد البدء بالتطوير بلغة PHP، إذاً قم باستخدام آخر نسخة مستقرة من PHP 7.1. PHP 7.1 هي نسخة جديدة، تم إضافة العديد من المميزات المميزات الجديدة مقارنة بالنسخ القديمة 5.×. قد تم إعادة بناء محرك اللغة من جديد، فالآن PHP أصبحت أسرع من سبيقاتها.

في الغالب حتى الآن سوف تجد أن PHP 5.× ما تزال مستخدمة مع العلم أن آخر نسخة من PHP 5.× هي PHP 5.6. وهذه النسخة لا تعتبر خياراً سيئًـا ولكن يتوجب عليك التحديث إلى آخر نسخة مستقرة في أقرب فرصة. سيتوقف الدعم وضخ التحديثات الأمنية على النسخة PHP 5.6 بعد العام 2018. عملية التحديث هي عملية سهلة للغاية، ربما قد تواجه بضع ماشكل توافقية مع الأعمال المكتوبة بالإصدارات السابقة ولكن يمكنك الاطلاع على صفحة التوافقية مع الإصدارات السابقة. إذا كنت غير متأكد من وجود دالة أو ميزة في أي إصدار، يمكنك التأكد عبر زيارة موقع دليل توثيق PHP php.net.

خادم ويب مدمج

منذ إصدار PHP 5.4 بإمكانك تعلم PHP بدون تنصيب أي إضافات أو تخصيصات، مع التمتع بكل خصائص خادم الويب المعتاد. لتشغيل الخادم قم بتنفيذ هذا الأمر من نافذة الأوامر من مجلد مشروعك الرئيس:

> php -S localhost:8000

التنصيب على نظام تشغيل Mac

يحتوي نظام التشغيل OS X على إصدار PHP ولكنها عادة ما تكون غير حديثة. فالنسخة OS X Mavericks تحتوي على الإصدار 5.4.17، والنسخة OS X Yosemite تحتوي على الإصدار 5.5.9، والنسخة OS X El Capitan تحتوي على الإصدار 5.5.29 وSierra 5.6.24. لكن مع انطلاق إصدار PHP 7.1 لم تعد هذه الإصدارات جيدة للاستخدام.

هنالك عدة طرق لتنصيب PHP على نظام تشغيل Mac OS X.

التنصيب بواسطة Homebrew

Homebrew هو عبارة عن مدير تطبيقات لنظام تشغيل Mac OS x، يساعدك على تنصيب PHP وملحقاتها بكل سهولة. Homebrew PHP هو المستودع الذي يحتوي كل ما يتعلق بـ PHP لمدير التطبيقات Homebrew، ويمكنك من تنصيب PHP بكل سهولة.

حتى هذه اللحظة يمكنك تنصيب كل من php53، php54، php55، php56 ،php71، php70 باستخدام الأمر brew install، ويمكنك التحويل فيما بينهم بتعديل متغير PATH. أو يمكنك استخدام brew-php-switcher للتحويل التلقائي.

التنصيب بواسطة Macports

مشروع MacPorts هو مشروع مفتوح المصدر يهدف لتصميم نظام سهل الاستخدام لتجميع وتنصيب وتحديث البرامج المفتوحة المصدر لكل من برامج سطور الأوامر X11، Aqua على نظام تشغيل Mac OS X.

يدعم MacPorts الملفات بلغة الآلة المبنية مسبقاً (Pre-Compiled Binaries) لكيلا تقوم بإعادة بناء كل حزمة من متطلبات النظام من الملفات المصدرية (tarball files)، فهي توفر لك سهولة تنصيب الحزم خصوصاً عندما لا يحتوي نظامك على إحداها.

حتى هذه اللحظة يمكنك تنصيب كل من php54، php55، php56، php70، php71 باستخدام الأمر port install مثلاً:

sudo port install php56
sudo port install php71

ويمكنك تشغيل الأمر select لتَّحويل بين الإصدارات المتوفرة.

sudo port select --set php php71

التنصيب بواسطة phpbrew

phpbrew هي إداة لتنصيب وإدارة أكثر من إصدار PHP. فهي أداة مفيدة جداً عندما يتطلب مشروعان إصدارات مختلفة من PHP وتريد تشغيلهما بدون الحاجة لنظام تشغيل افتراضي.

التنصيب بواسطة Liip’s binary installer

وسيلة أخرى متاحة وهي php-osx.liip.ch فهي توفر وسيلة تنصيب لنسخة واحدة لكل من الإصدارات ابتداء من 5.3 حتى 7.1. تتميز بأنها لا تستبدل ملفات حزمة PHP binaries المدمجة مع نظام Apple، ولكنها تعمل على التنصيب في مكان آخر (/usr/local/php5).

البناء من المصدر Compile from Source

وسيلة أخرى تمكنك من التحكم بإصدارات PHP وتنصيبها، وذلك عن طريق بنائها بنفسك. في هذه الحالة تَأَكَّد من تنصيب Xcode، أو“Command Line Tools for XCode” التابعة لـ Apple ويمكن تحميلها “Command Line Tools for XCode” من قسم Mac للمطورين (Mac Developer Center).

تنصيب حلول متكاملة مجمعة «الكل في واحد»

كل الحلول السابق ذكرها تتلخص بأنك تطبق عملية تنصيب PHP بنفسك، وهي لا تشمل تنصيب برامج مثل Apache, Nginx أو حتى خادم SQL. «الكل في واحد» هي حلول متكاملة مثل MAMP و XAMPP فهي تقوم بتنصيب كل تلك البرامج وضبطهم معاً، ولكن رغم سهولة التنصيب فإن مثل هذه الحلول تفتقر للمرونة.

التنصيب على نظام تشغيل Windows

يمكنك تحميل الملفات التنفيذية من windows.php.net/download. بعد تنصيب PHP يفضل إضافة وضبط مسار التنصيب (المسار الذي يحتوي على ملف php.exe داخله) إلى المتغير العام PATH حتى تتمكن من تشغيل PHP من أي مكان في النظام.

لأغراض التعلم والتطوير المحلي يمكنك استخدام الخادم المُدْمج مع PHP 5.4 أو أعلى، حتى لا تحتاج عملية الضبط مع برامج أخرى للخادم. إذا كنت تفضل «الكل في واحد» وهي برامج تحتوي على كل الحزم التي قد تحتاجها من خادم ويب وخادم قاعدة بيانات بالإضافة لـ PHP، إذاً فبرامج مثل Web Platform Installer، XAMPP، EasyPHP، OpenServer ، WAMP ستساعدك عند تنصيب بيئة تطوير متكاملة بسرعة على نظام تشغيل Windows. الجدير بالذكر أنَّ هذه الأدوات تختلف قليلاً مما هي عليه على خوادم مرحلة التنفيذ النهائية، فيجب عليك توخي الحيطة والحذر ومراعاة الإختلافات ما بين البيئتين. مثلا تقوم بالتطوير على بيئة عمل Windows ويتم التنفيذ في بيئة نهائية Linux.

إذا كنت تريد تشغيل بيئتك النهائية على نظام تشغيل Windows حينها IIS7 سوف يُوَفِّر لك أداءً عالٍ ومستقرا. يمكنك استخدام phpmanager (إضافة واجهة عمل رسومية لـ IIS7) لإدارة وضبط PHP بكل سهولة. IIS7 يكون معه FastCGI مدمج وجاهز للاستخدام، كل ما عليك هو ضبط PHP كمعالج (Handler). للدعم ومصادر أخرى هناك صفحة مخصصة على iis.net خصيصاً لـPHP.

بشكل عام تشغيل برامجك على بيئات متعددة في مرحلة التطوير والتنفيذ قد يؤدي لظهور أخطاء ومشاكل مختلفة وغريبة.. لذلك عندما تقوم بتنصيب بيئة تطوير على Windows ثم تقوم بتنصيبها على بيئة عمل نهائية Linux (أو أي نظام تشغيل آخر) يجب أخذ استخدام بيئة تشغيل افتراضية بعين الاعتبار.

كريس تانكرسلي لديه مقالة ممتازة على مدونته الشخصية للأدوات التي يستخدمها عندما يقوم بتطوير البرامج على بيئة Windows.

للأعلى

دليل أسلوب كتابة الشفرة البرمجية

مجتمع لغة PHP هو مجتمع ضخم ومتنوع، يتألف من عدد لا يعد ولا يحصى من المكتبات، وأطر عمل، و مكونات وعناصر أخرى. من الشائع أن يختار المطورون عدة خيارات منها ودمجها في مشروع واحد. من المهم أن يتم تقييد الشفرة البرمجية للغة PHP (قدر الإمكان) بأسلوب برمجي معين لكي يَسْهُل على المطورين التعامل المشترك وفهم محتوى الشفرة البرمجية في مكتبات مشاريعهم.

قامت Framework Interop Group بتقديم وإجازة سلسلة من الأساليب المُوصَى بها. ليس كل هذه التوصيات هي توصيات تتعلق بأسلوب كتابة الشفرة البرمجية، ولكن تحديداً ما يتعلق منها بأسلوب الكتابة هم: PSR-0، PSR-1، PSR-2، PSR-4. هذه التوصيات هي عبارة عن مجموعة من القواعد التي تستعملها مشاريع وتطبيقات كبرى مثل Drupal، Zend، Symfony، Laravel، CakePHP، phpBB، AWS SDK، FuelPHP، Lithium.. وغيرها. يمكنك استخدام هذه الأساليب والتوصيات في مشروعك الخاص أو الإستمرار في استخدام أسلوبك الخاص في الكتابة.

من الأمثل أن تقوم بكتابة شفرة برمجية تتماشى مع مجموعة قواعد قياسية متعارف عليها. ويتم تطبيق هذه القواعد عن طريق دمج الأساليب سابقة الذِّكر PSR أو أساليب أخرى تم اعتمادها من قبل PEAR أو Zend. هذا يعني أنه يمكن لأي مطور آخر أن يقرأ ويفهم عملك نظراً لأن محتواه يخضع لطريقة كتابة معتمدة وتستخدمها كثير من التطبيقات والتطبيقات الفرعية الأخرى التي قد تستخدمها مع مشروعك.

يمكنك استخدام أدوات فحص الشفرة البرمجية ومعرفة ما إذا كانت تخضع لأي من التوصيات مثل PHP_CodeSniffer، وهنالك إضافات يمكن تنصيبها على محرر النصوص مثل Sublime Text وتتميز بأنها تتفاعل معك في أثناء الكتابة مباشرة.

يمكنك إصلاح الشفرة البرمجية وإخضاعها لأحد الأساليب القياسية باستخدام أي من هذه الأدوات:

ويمكنك أيضاً تشغيل phpcs يدوياً من سطر الأوامر:

phpcs -sw --standard=PSR2 file.php

بعد التنفيذ سوف تظهر أخطاء ووصفٌ لكيفية إصلاحها. أيضاً يمكن الإستفادة من هذا الأمر عبر إضافته كـ git hook بحيث تتحقق من أن جميع الشفرة البرمجية تتبع الأسلوب القياسي وإصلاح الأخطاء حتى يكون بالإمكان إعتمادها في المستودع (Repository).

إذا كنت تمتلك PHP_CodeSniffer عندها يمكنك تصحيح أسلوب الشفرة البرمجية تلقائياً باستخدام PHP Code Beautifier and Fixer.

phpcbf -w --standard=PSR2 file.php

أو عن طريق خيار آخر وهو استخدام PHP Coding Standards Fixer. باستخدام هذا الأخير سيقوم بإظهار نوع الأخطاء قبل إصلاحها.

php-cs-fixer fix -v --level=psr2 file.php

اللغة الإنجليزية هي المفضلة لكل التسميات والرموز والعلامات وجميع بنية الشفرة البرمجية. يمكن كتابة الملاحظات بأي لغة يمكن قراءتها من قبل المطورين الحاليين أو المحتملين.

للأعلى

تسليط الضوء على اللغة

النماذج البرمجية

PHP هي لغة مرنة ومتغيرة حيث أنها تدعم عدة أساليب برمجية. فقد تطورت بشكل ملحوظ خلال السنين الماضية وأضافت البرمجة كائنية التوجه (Object Oriented) في إصدار PHP 5.0 (سنة 2004) وأضافت الدَّوال المجهولة (anonymous functions) ونطاق التسميات (namespaces) في الإصدار PHP 5.3 (سنة 2009) وأضافت السِّمَات (traits) في إصدار PHP 5.4 (سنة 2012).

البرمجة كائنية التوجه (الشيئية) OOP

تحتوي PHP على خَوَاصِ البرمجة الكائنية بشكل كامل وذلك يشمل الأصناف (class) والأصناف المجردة (abstract class) والمنافذ (interface) والوراثة (inheritance) والبنائيات (constructor) والاستنساخ (clone) والاستثناء (exception) والمزيد …

البرمجة الوظيفية

تدعم PHP دوال «المستوى الأول»، بمعنى أنه يمكن إسناد دالة إلى متغير. كل من «دوال المستخدم» والدوال المدمجة مع اللغة يمكن إحالتها باستخدام متغيرات ومناداتها بصورة حيوية. يمكن تمرير الدوال كقيم إلى دوال أخرى (خاصية تسمى الدوال العليا higher-order Functions) والدوال نفسها بإمكانها إرجاع دوال أخرى!

الإستدعاء (recursion) الذاتي، وهي خاصية تتيح للدَّالة أن تنادي نفسها، وهي مدعومة من قبل اللغة ولكن معظم الشفرة بلغة PHP تعتمد على التكرار.

الدوال المجهولة (anonymous functions) خاصية موجودة منذ الإصدار PHP 5.3 (سنة 2009، مع دعم «الإغلاق» closures).

أضافت PHP 5.4 إمكانية إسناد دوال مجهولة وكائنات مغلقة لنطاق الكائن وقد تم التطوير للإستدعاءات حتى يمكن استخدامها مع كل الدوال في أي حال.

البرمجة التحويلية

تدعم PHP أيضاً عدة أشكال من البرمجة التحويلية (Meta programming) عبر عدة وسائل مثل واجهة برمجة تطبيقات الإنعكاس (reflection API) والدوال السحرية (Magic Methods). هناك عدة دوال سحرية مثل __get()، __set()، __clone()، __toString()، __invoke() وغيرها والتي تتيح للمُطَوِّر ربطها بتصرفات الكائنات. يقول مطورو لغة Ruby أن لغة PHP تفتقر إلى الدالة method_missing، ولكنها موجودة وتتمثل في كل من __call() و __callStatic().

فضاءات الاسماء Namespaces

قد سبق ذِكْرُ أنَّ مجتمع PHP يحتوي على عدد كبير من المطورين الذين يقومون بتطوير أعداداً ضخمة من البرمجيات. وهذا يعني أنَّ مكتبة واحدة من برمجية مطورة بلغة PHP قد تستخدم أسماءَ لكائنات مستعملة في مكتبة أخرى، أي قد يحدث تضارب في التَّسْميات. عندما تُستخدم عدة مكاتب في نفس فضاء الاسم (namespace) قد يسبب ذلك (التضارب في التسميات) بعض المشاكل.

فضاءات الأسماء Namespaces تحل هذه المشكلة. كما هو مُوضحٌ في مرجع لغة PHP، أسماء الفضاءات يمكن تشبيهها بمجلدات النظام حيث يمكن لملفين يحملان نفس الاسم أن يوجدا في مُجلدين مختلفين. كذلك بالنسبة لكائنات PHP فيمكن لكائنين حمل نفس الاسم ولكن يجب لأن يكونا في فضاءين مختلفين.

فمن المهم أن تستخدم هذه الفضاءات في عملك، بالتحديد إذا كنت تنوي مشاركة هذا العمل مع مطورين آخرين. فهذا يمنع حدوث التضارب فيما بين المكتبات المتعددة.

هنالك طريقة موصى بها لاستخدام فضاءات الاسماء وهي مدرجة PSR-4، وتهدف إلى تحديد نمط قياسي لكل ملف وكائن و فضاء اسم لسهولة الاستخدام وإمكانية الإدراج والبدء بالاستخدام المباشر للمكتبات.

في أكتوبر 20014 PHP-FIG قامت بإهمال وإيقاف العمل بطريقة الإدراج التلقائي القياسية بالرمز PSR-0. كلا الإصدارين PSR-0 و PSR-4 ما يزالان صالحين للاستخدام. ولكن الثاني يتطلب إصدار PHP 5.3 لذا العديد من المشاريع المبرمجة على إصدار PHP 5.2 تستخدم PSR-0.

إذا كنت تنوي استخدام الإستدعاء التلقائي في تطبيقك أو حزمتك، حينها يجب أن تلقي نظرة على التوصية القياسية PSR-4.

مكتبة PHP القياسية

مكتبة PHP القياسية (Standard PHP Library إختصارا SPL) هي مكتبة مدمجة مع PHP، توفر مجموعة من الكائنات (classes) والواجهات (interfaces). صُمِّمت هذه المكتبة لتلبي الإحتياجات الأساسية من كائنات هيكلة البيانات (المكدسات، الصفوف، الأكوام وغيرها)، ودوال تكرار تختصر عمل هيكلة البيانات في كائناتك التي تطبق واجهات المكتبة القياسية.

واجهة سطور الأوامر

صُمِّمَت لغة PHP لكتابة تطبيقات الويب، ولكن من المفيد أيضاً التعامل مع برامج واجهة سطور الأوامر (CLI). برامج PHP المبنية للعمل على سطور الأوامر تساعد على أداء مهام معتادة مثل تجربة ونشر وإدارة البرامج بصورة تلقائية.

تطبيقات PHP التي تعمل على واجهة سطور الأوامر هي برامج قوية لأنها تستخدم مصدر البرنامج مباشرة دون الحاجة لتطوير واجهة ويب مرئية ومَحْمِيَّة. ولكن تأكد من عدم وضع تطبيقك الذي يعمل بواجهة سطور الأوامر في مجلد موقعك (حيث يمكن للجميع رؤيته)!

جرب تشغيل أمر PHP هذا من واجهة سطور الأوامر:

> php -i

يدل خيار -i على طباعة بيانات ضبط PHP كما تنفذه دالة phpinfo() تماماً.

أيضاً خيار -a يوفر أوامر تفاعلية مماثلة لشبيهاتها عند ruby IRB و python interactive shell. هنالك عدد من الأوامر والخيارات المفيدة التي يمكنك الاطلاع عليها أيضاً command line options.

لنقم بتجربة كتابة برنامج ترحيبي سهل بطريقة واجهة سطور الأوامر، قم بإنشاء ملف باسم hello.php ثم قم بكتابة الآتي:

<?php
if ($argc !== 2) {
    echo "Usage: php hello.php [name].\n";
    exit(1);
}
$name = $argv[1];
echo "Hello, $name\n";

تقوم PHP بإنشاء متغيرين خاصين بناءَ على القيم التي يعمل بها تطبيقك. $argc وهو متغير رقمي يحتوي على عدد القيم. $argv وهو متغير به مصفوفة تحوتي على قيمة كل من القيم. دائما ما يكون الخيار الأول من القيم هو اسم ملف PHP المراد تنفيذه وفي هذه الحالة هو الملف hello.php. تستخدم دالة exit() دون قيمة صفرية لإعلام منفذ الأوامر بأن الأمر قد فشل!. يمكن الإطلاع على شفرات الخروج من هنا.

لتنفيذ الملف، قم بفتح نافذة تنفيذ سطور الأوامر ثم قم بكتابة الآتي:

> php hello.php
Usage: php hello.php [name]
> php hello.php world
Hello, world

المُصَحِّح Xdebug

أحد أهم أدوات التطوير هو: مَصَحح فعال! فهو يتيح لك تعقب تنفيذ المصدر البرمجي ومراقبة محتواه وتدفق عمليته. Xdebug هو مُصَحح لغة PHP، يمكن استعماله من خلال بيئة التطوير المتكاملة (IDE) لتوفير نقاط توقف وتعقب التدفق. أيضاً يتيح لأدوات مثل PHPUnit و KCacheGrind عمل تغطية شاملة على المصدر البرمجي لفحصه وتحليله.

إذا كنت تلجأ لاستخدام var_dump() أو print_r()، لكنهما لم تفيان بالغرض، إذا عليك أن تستخدم مُصَححا حينها!

تنصيب Xdebug قد يكون معَقَّدا بعض الشيء، ولكن واحدة من أهم مميزاته هو التصحيح عن بعد (Remote Debugging)، فإذا كنت تقوم بتطوير برنامجك في بيئة محلية ثم تقوم بتجربته داخل نظام افتراضي أو في خادم آخر عندها التصحيح عن بعد هو الخيار الذي يسهل عليك المتابعة والتصحيح بالطريقة الصحيحة.

يمكنك تعديل Apache VHost أو ملف .htaccess وإعطاؤه القيم الآتية:

php_value xdebug.remote_host 192.168.?.?
php_value xdebug.remote_port 9000

كل من “remote host” و”remote port” يعبران عن بيانات جهازك الخاص والمنفذ الذي تتعامل به بيئة التطوير المتكاملة (IDE) للاستماع إليه. الآن كل ما عليك فعله هو أن تضع برنامج بيئة التطوير IDE لديك على وضعية استماع الإتصالات (Listen for connections Mode) ثم عرض الصفحة باستخدام URL:

http://your-website.example.com/index.php?XDEBUG_SESSION_START=1

سوف يعترض برنامج التطوير لديك الحالة الحالية لتنفيذ المصدر، مما يتيح لك أن تضع نقاط توقف والاطلاع ومتابعة القيم في الذاكرة.

أدوات التصحيح المرئية تسهل استخدامها مع الشفرة، والتحقق من المتغيرات، وتنفيذ الشفرة مباشرة. العديد من برامج بيئة التطوير IDE لديها إضافة مدمجة تدعم أدوات التصحيح المرئية ومتوافقة مع xDebug. MacDGBp هو أداة مجانية ومفتوحة المصدر قائمة بذاتها تعمل على نظام التشغيل Mac.

للأعلى

إدارة التوابع

لغة البرمجة PHP لديها العديد من المكتبات وأطر العمل والمحتويات التي تستطيع اختيار أحدها. ربما قد تستخدم بعضا منها في عملك أو مشروعك، تلك الإضافات تُسَمَّى توابع المشروع. منذ عهد قريب لم تكن لغة البرمجة PHP تملك طريقة مثلى لإدارة تلك التوابع. حتى إن قمت بإدارتها يدويا، مازلت تحتاج ان تعير انتباهاً لتحميلها آليا (autoloaders). ولكن هذا الأمر لم يعد يشكل مشكلة.

حتى الآن رائدي أنظمَة إدارة التوابع في لغة البرمجة PHP هما Composer و PEAR. Composer حالياً هو الأكثر شهرة، لكن لفترة طويلة من الزمن كان PEAR هو النظام الأساسي لإدارة التوابع في PHP. من الجيد معرفة تفاصيل تاريخ PEAR، لأنه يمكن الرجوع إليها ولو لم تستخدمها..

Composer و Packagist

يعتبر Composer أداة ممتازة لإدارة توابع لغة PHP. كل ما عليك هو أن تضع قائمة بالتوابع في ملف composer.json وببعض أوامر سهلة سيقوم Composer بتنزيل كل توابع مشروعك بالإضافة لتنصيب وضبط ملف إدراج تلقائي autoload. يعتبر Composer مماثل لـ NPM المستخدم في node.js، أو Bundler المستخدم في Ruby.

يوجد الكثير من مكتبات لغة PHP متوافقة مع Composer وجاهزة للاستخدام مع مشروعك. هذه المكتبات أو «الحزم» مدرجة في Packagist، المستودع الحصري لكل مكتبات لغة PHP المتوافقة مع Composer.

كيفية تنصيب Composer

الطريقة الآمنة لتحميل Composer هي عن طريق التعليمات الرسمية. سيتم التحقق من ملفات التنصيب كونها سليمة ولم يتم التلاعب بها. يقوم ملف التنصيب بتنصيب Composer محلياً في مجلد العمل الحالي.

ننصح بتنصيبه للإتاحة العامة (مثلا نسخة واحدة في /usr/local/bin)، للقيام بذلك قم بتنفيذ:

mv composer.phar /usr/local/bin/composer

ملاحظة: إذا فشل الأمر السابق بسبب صلاحيات قم بتشغيل الأمر mv مرة أخرى بكتابة الأمر sudo قبله.

لتشغيل Composer الذي تم تنصيبه محلياً قم باستخدام php composer.phar وعامةً قم باستخدام composer فقط.

التنصيب على نظام تشغيل Windows

بالنسبة لمستخدمي نظام التشغيل Windows الطريقة الأسهل هي استخدام ملف تنصيب ComposerSetup، حيث يقوم بتنصيبه بشكل عام على النظام، ويقوم بضبط متغيرات $PATH حتى تتمكن من استدعاء الأمر composer فقط من شاشة سطور الأوامر من أي مجلد في النظام.

كيفية تنصيب Composer (يدوياً)

تنصيب Composer يدوياً هو عملية متقدمة، لذلك هناك العديد من الأسباب تجعل المُطَوِّر يقوم باستخدام هذه الطريقة بدلاً عن الطريقة التلقائية التقليدية. فالطريقة التقليدية التلقائية تقوم بفحص إصدار PHP الحالي لتقوم بالتَّأكد من الآتي:

في المقابل الطريقة التقليدية لا تقوم بأي من العمليات المذكورة آنفاً، يتوجب عليك أن تقوم ببعض أو كل العمليات يدوياً، لتنصيب Composer يدويا قم بالآتي:

curl -s https://getcomposer.org/composer.phar -o $HOME/local/bin/composer
chmod +x $HOME/local/bin/composer

المجلد $HOME/local/bin (أو المجلد الذي تختاره) يجب أن يكون مُدْرَجًا في متغيرات $PATH. وهكذا يمكن الوصول إلى الأمر composer من أي مكان (في نظام التشغيل).

عند استخدام طريقة اليدوية ستتغير طريقة الاستخدام التي قد تجدها في بعض المراجع php composer.phar install ليكتب بدلاً منها الآتي:

composer install

في القسم التالي سوف نفترض أنك قمت بتنصيب Composer بشكل عام في النظام.

كيفية تحديد وإدراج التوابع

يقوم Composer بتتبع وإدارة توابع مشروعك عن طريق ملف يسمى composer.json. يمكنك إدارة هذا الملف يدوياً باستخدام أي محرر نصوص، أو استخدام Composer لإنجاز هذه العملية. يقوم الأمر composer require بإضافة تابع إلى مشروعك، فإذا لم يكن ملف composer.json موجوداً سيقوم تلقائياً بإنشائه. مثال لإدراج مكتبة Twig كتابع لمشروعك.

composer require twig/twig:~1.8

عوضاً عن ذلك يمكنك تنفيذ الأمر composer init الذي سيساعدك لإنشاء ملف composer.json بالكامل ليتناسب مع مشروعك. ويمكنك أيضاً بعد إنشاء ملف composer.json أن تُخْبِر Composer بأن يقوم بتحميل وتنصيب التوابع في مجلد vendor/. وهذا ينطبق أيضاً على المشاريع والمكتبات التي تقوم بتنزيلها ويوجد معها الملف composer.json.

composer install

بعدها، قم بإضافة هذا السطر إلى ملف برنامجك الأساسي، حتى يتمكن من إعلام PHP لتستخدم المدرج التلقائي autoloader التابع لـ Composer ليدرج التوابع لمشروعك.

<?php
require 'vendor/autoload.php';

يمكنك الآن استخدام توابع مشروعك وسيتم إدراجها تلقائياً باستخدام autoloader.

تحديث التوابع

يقوم Composer بإنشاء ملف باسم composer.lock حيث يقوم بحفظ أرقام إصدارات كل تابع يقوم بتحميله عند تنفيذ الأمر composer install أول مرة. إذا كنت تشارك مشروعك مع مطورين آخرين وملف composer.lock هو جزء من ملفاتك، فعندما يقوم أحدهم بتنفيذ الأمر composer install سوف يحصل على نفس الإصدارات الموجودة لديك. لتحديث التوابع قم بتنفيذ الأمر composer update. لا تقم بالتحديث عند التركيب على بيئة العمل النهائية، ولكن قم بالتنصيب باستخدام composer install وإلا سينتهي بك المطاف بأن يكون هناك اختلاف في إصدارات الحزم.

تكون هذه العملية مفيدة عندما تكون متطلباتك من التوابع محددة بمرونة، فمثلا تتطلب لمشروعك تابع بالإصدار ~1.8 بمعنى «كل الإصدارات الأحدث من 1.8.0، والأقل من 2.0.x-dev». يمكنك أيضاً استخدام * 1.8.* لتحديد الكل من خانة واحدة. فالآن عند تنفيذ الأمر composer update سيقوم Composer بتحديث كل توابعك إلى النسخة الأحدث بناءً على ما قد حَدَّدته سلفاً كمطلب.

تنبيهات التحديثات

لكي تصل إليك تنبيهات عند صدور إصدارات جديدة من التوابع يمكنك التسجيل في VersionEye، وهي خدمة يمكنها متابعة حسابك في كل من GitHub و BitBucket للبحث داخل ملفات composer.json، وتقوم بإرسال بريد إلكتروني عند صدور تحديثات جديدة.

فحص مشاكل الأمان في توابعك

Security Advisories Checker هي خدمة وأداة تعمل على سطور الأوامر، حيث تقوم باختبار ملف composer.lock وتقوم بإخبارك إذا ما كان هنالك حاجة لتحديث أي من التوابع.

التحكم في التوابع العامة باستخدام Composer

يمكن لـ Composer التَّحكم في التوابع وملفاتها التنفيذية أيضاً. طريقة الاستخدام واضحة جداً، كل ما عليك فعله هو إدراج كلمة global قبل تنفيذ الأمر. مثلا إذا كنت تريد تنصيب الأداة PHPUnit واستخدامها بشكل عام في نظامك قم بتنفيذ هذا الأمر:

composer global require phpunit/phpunit

هذا الأمر سيقوم بإنشاء مجلد في ~/.composer حيث يقوم بوضع كل التوابع العامة هناك. ولكي تقوم بتنفيذ أوامر وعمليات تلك التوابع من أي مكان، قم بإدراج مسار المجلد ~/.composer/vendor/bin إلى متغير $PATH في النظام لديك.

PEAR

PEAR هو أقدم برنامج لإدارة التوابع في PHP استمتع به كثير من المطورين. فهو يقوم بنفس وظائف Composer تماما ولكن مع بعض الإختلافات الملحوظة.

يحتاج PEAR أن يكون لكل حزمة أو تابع هيكلة محددة، حيث يتوجب على كاتب الحزمة أو التابع أن يقوم بتجهيزها لكي يتم استخدامها عن طريق PEAR. استخدام مشروع غير مجهز للعمل مع PEAR هو شيء مستحيل.

يقوم PEAR بتنصيب كل التوابع بشكل عام، مما يعني بعد تنصيب التوابع مرة واحدة تكون متوفرة بشكل عام لكل المشاريع على نفس الخادم. قد يكون هذا أمرا جيدا إذا كانت عدة مشاريع تعتمد نفس التوابع بنفس الإصدارات ولكن هذا قد يحدث مشاكل مثل تضارب الإعتمادية على إصدارات مختلفة بين المشاريع!

كيفية تنصيب PEAR

يمكنك تنصيب PEAR عن طريق تحميل ملف .phar ثم تشغيله. يحتوي دليل PEAR على تفاصيل التنصيب لكل نظام تشغيل تعليمات التنصيب.

إذا كنت تستخدم Linux، يمكن أن تطلع على مدير الحزم لديك. Debian و Ubuntu لديهما الحزمة عن طريق apt php-pear.

كيفية تنصيب حزمة أو تابع

إذا كانت الحزمة أو التابع مدرجا في قائمة حزم PEAR، يمكنك التنصيب باستخدام الاسم الحصري:

pear install foo

إذا كانت الحزمة مستضافة على قناة أخرى، ما عليك سوى القيام بتنفيذ استكشاف القناة discover أولا، ثم تحديدها عند التنصيب. اطلع على دليل استخدام القنوات لمزيد من التفاصيل في هذا الموضوع.

إدارة حزم وتوابع PEAR باستخدام Composer

إذا كنت تستخدم Composer عندها يمكنك تنصيب بعض حزم PEAR أيضاً، يمكن استخدام Composer لإدارة توابع وحزم PEAR. مثال لتنصيب كود مصدري من pear2.php.net:

{
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear2.php.net"
        }
    ],
    "require": {
        "pear-pear2/PEAR2_Text_Markdown": "*",
        "pear-pear2/PEAR2_HTTP_Request": "*"
    }
}

الجزء الأول "repositories" سوف يستخدم من قبل Composer لكي يقوم بتهيئة (أو استكشاف “discover” بمصطلح PEAR) مستودع PEAR. الجزء الثاني "require" يقوم بإضافة لاحقة لاسم الحزمة هكذا:

pear-channel/Package

تم برمجة اللاحقة “pear” يدوياً للحد من حدوث تضاربات، فقد تستخدم قناة PEAR نفس الاسم لحزمة أو تابع مختلف للموزع مثلا، عندها اسم القناة القصير (أو العنوان الكامل URL) يُستخدم للإرشاد إلى أي قناة تَتْبَع هذه الحزمة.

بعد تنصيب هذا التابع أعلاه سيكون متوفرا في مجلد vendor ويمكن إدراجه تلقائياً باستخدام Composer autoloader:

vendor/pear-pear2.php.net/PEAR2_HTTP_Request/pear2/HTTP/Request.php

لاستخدام حزمة PEAR هذه قم باستخدامها هكذا:

<?php
$request = new pear2\HTTP\Request();

للأعلى

ممارسات برمجية

الأساسيات

PHP هي لغة واسعة تتيح لمُبَرمجي جميع المستويات إمكانية إنتاج برمجيات، ليس بسرعة فقط، بل بكفاءة أيضاً! لكن كلما تعمقنا في اللغة نكتشف أننا قد نسينا الأساسيات التي تعلمناها منذ البداية (أو تغاضينا عنها) لكي نختصر الزمن أو نسلك طريقاً مختصراً لأداء المهام بممارسات سيئة. هذا الفصل يهدف لتجنب هذا النوع من المشاكل المعتادة وتذكير المبرمجين بهذه الممارسات الأساسية في إطار لغة PHP.

الوقت والتاريخ

يوجد صنف (أو class) في لغة البرمجة PHP يُسَمَّى DateTime يساعد في عمليات قراءة وكتابة ومقارنة وحساب الوقت والتاريخ. هناك العديد من الدوال المتعلقة بعمليات الوقت والتاريخ مضمنة في لغة البرمجة PHP، ولكن DateTime يوفرها بواجهة كائنية (أو شيئية Object-Oriented) للعديد من الاستخدامات. يمكن لهذا الصنف التعامل مع النِّطَاقات الزَّمنية، ولكن هذا غير مُدْرَج في هذا الشرح التعريفي القصير.

لكي نبدأ باستخدام DateTime، قم بتحويل صيغة كاملة صحيحة لتاريخ وزمن بصيغة نصية إلى الصنف باستخدام الدالة المصنِعة createFromFormat() أو يمكنك البدء بإنشاء كائن جديد باستخدام new DateTime وينتج من تنفيذ هذا الأخير الوقت والزمن الحاليين. يمكن استخدام الدالة format() لتحويل DateTime إلى صيغة نصية مرة أخرى للتمكن من طباعتها مثلا.

<?php
$raw = '22. 11. 1968';
$start = DateTime::createFromFormat('d. m. Y', $raw);

echo 'Start date: ' . $start->format('Y-m-d') . "\n";

يمكن إجراء عمليات حسابية باستخدام DateTime وذلك باستخدام صنف DateInterval. يحتوي صنف DateTime على دوال مثل add() و sub() التي تَسْتخدم مُخْرَجات صنف DateInterval كمعطيات. لا تقم مطلقاً بكتابة دوال للقيام بعمليةِ حسابِ الثواني في اليوم أو حساب الفترة النهارية أو حتى حساب فرق التوقيت الزمني، ولا تقم باستخدام بدائل أخرى بشكل عام. اسْتَخْدِم DateInterval عوضاً عن ذلك. للقيام بحساب فَرْقِ التاريخ بين تاريخين قم باستخدام الدالة diff() فهي تقوم بإرجاع DateInterval وعندها يمكن عرضه واستخدامه بسهولة.

<?php
// نقوم بإنشاء نسخة من $start ثم نقوم بزيادة شهر وستة أيام
$end = clone $start;
$end->add(new DateInterval('P1M6D'));

$diff = $end->diff($start);
echo 'الفرق هو: ' . $diff->format('%m شهر, %d يوم (المجموع: %a يوم)') . "\n";
// الفرق هو: 1 شهر, 6 يوم (المجموع: 37 يوم)

يمكن لكائن DateTime أن يُستخدم لإجراء عملية مقارنة بالطريقة التقليدية باستخدام:

<?php
if ($start < $end) {
    echo "Start هو قبل end!\n";
}

المثال الأخير يقوم بشرح صنف DatePeriod الذي يستخدم لعمليات التكرار للأحداث المتكررة. يمكنه أخذ كائني DateTime كمعطيات البداية والنهاية وكائن DateInterval لتحديد الحدث الزمني ليقوم بإرجاع كل الأحداث المتطابقة بين التاريخين!

<?php
// اطبع جميع أيام الخميس بين $start و $end
$periodInterval = DateInterval::createFromDateString('first thursday');
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // اطبع التاريخ لكل خميس
    echo $date->format('Y-m-d') . ' ';
}

من لواحق واجهات البرمجة (API) المشهورة للغة PHP Carbon. تقوم بوراثة كل شيء من صنف DateTime، لتَقُوم بتعديل طفيف على الشفرة البرمجية، ولكن بخواص أخرى إضافية تضم ميزة التعريب بالإضافة لعمليات إضافة وطرح التاريخ بصيغ مختلفة مما يعني أنه يمكن اختبار التطبيق باستخدام تاريخ من اختيارك.

نماذج التصميم

عندما تقوم ببناء برنامج من المفيد أن تستخدم الممارسات البرمجية المعروفة أثناء الكتابة، واتباع النماذج المعروفة للهيكلة العامة للمشروع. استخدام هذه النماذج يسهل عملية إدارة المصدر ويسهل على مبرمجين فهم كيفية عمل الأشياء سوياً.

إذا كنت تستخدم إطار عمل Framework عندها سيكون هيكل مشروعك في إطاره. عندها الكثير من القرارات الهيكلية قد تم اتخاذها لك مسبقاً ولكن ما يزال عليك أن تختار النماذج المثلى لتتبعها في المصدر الذي تقوم بكتابته لبناء المشروع في هذا الإطار. إذا كنت لا تستخدم إطار عمل لبناء برنامج يجب عليك أن تجد وتستخدم النماذج المثلى التي تنطبق على نوع وحجم البرنامج الذي تقوم ببنائه.

العمل بترميز UTF-8

هذا الفصل تم كتابته من قبل أليكس كابال في أفضل ممارسات PHP حيث تم استخدامها كأساس لنصائحنا للعمل بترميز UTF-8.

كن منتبهاً، دقيقاً ومهتماً بالتفاصيل. فليس هناك طريق واحد.

حتى الآن لم تقم PHP بدعم ترميز Unicode في مستوياتها الأساسية. هناك طرق للتَّأكد من أن النصوص قد تمت معالجتها بالترميز UTF-8، ولكنها عملية ليست سهلة وتحتاج الكثير من البحث والتنقيب في كل مستويات تطبيق الويب، ابتداءً من HTML إلى SQL وحتى PHP. سوف نقوم بإيجاز نبذة عن تطبيقات عملية.

UTF-8 على مستوى PHP

لا تحتاج عمليات النصوص، مثل دمج النصوص وتعيين قيم نصية إلى متغيرات، إلى أي شيء خاص لكي تتبع ترميز UTF-8. لكن كثيرا من دوال النصوص مثل strpos() وstrlen() تحتاج عناية خاصة. هذه الدوال عادة ما تكون لها نظيراتها باللاَّحِقة mb_* مثلاً: mb_strpos() وmb_strlen(). هذه الدوال عن طريق إضافة Multibyte String Extension وهي مُخَصَّصَة للتعامل مع النصوص بترميز Unicode.

يجب عليك أن تستخدم دوال mb_* كلما أردت التَّعامل مع نص بترميز Unicode. مثلا إذا قمت باستخدام substr() على نص بترميز UTF-8 هناك احتمال أَنَّ النتيجة ستحتوي رموزَا مشوَّهة. في هذه الحالة الطريقة الصحيحة هي استخدام دالة تأخذ النصوص متعددة البايت بالإعتبار (Multibyte) mb_substr().

الصعوبة تَكْمُن في تَذَكُّر استخدام دوال mb_* دائماً عوضاً عن الدوال الأصلية. إذا قمت بنسيان ذلك ذات مرة فأي نص بترميز Unicode يكون مهدَّدًا بالتشوه في أي عملية لاحقة قد تطرأ عليه، مما يعني نتائج غير محمودة!

ليس كل دوال النصوص لديها معالجات في دوال mb_*. إذَا لم يكن هناك واحدة توفر لك الاستخدام المطلوب فأنت غير محظوظ!

يجب استخدام الدَّالة mb_internal_encoding() في بداية كل مصدر PHP تقوم بكتابته (كملف الإستدعاء الرئيس لبقية الملفات مثلاً)، تليها دالة mb_http_output() إذا كنت ستقوم بطباعة نص على المتصفح. تعيين الترميز بشكل صريح في كل نصوصك يَقِيك الكثير مِمَّا لا تُحمد عقباه خلال عملية التطوير.

إضافةً إلى ذلك هناك العديد من الدوال المدمجة تتيح إدارة النصوص عبر معطيات اختيارية لتحديد ترميز النصوص. عندها يجب عليك تحديد UTF-8 عندما يكون هذا الخيار متاحاً. مثلا دالة htmlentities() لديها معطى اختياري لتحديد الترميز، سيتوجب عليك دائما تحديد ترميز UTF-8 للتعامل مع النصوص. ملاحظة: htmlentities() وhtmlspecialchars() تستخدمان الترميز UTF-8 كترميز نصوص افتراضي منذ إصدار PHP 5.4.0.

أخيراً، إذا كنت تقوم ببناء تطبيق تنوي نشره وكنت غير متأكد من أن إضافة mbstring ستكون متوفرة أم لا، يجب عليك التفكير في استخدام حزمة Composer تسمى patchwork/utf8. ستقوم هذه الحزمة بإتاحة استخدام mbstring إذا كانت متوفرة، أو تقوم باستخدام الدوال الإفتراضية إذا لم تكن الأخرى متوفرة.

UTF-8 على مستوى قاعدة البيانات

إذا كان تطبيقك يقوم بالوصول إلى قاعدة البيانات MySQL، عندها هناك احتمال أنَّ النصوص تُحفظ بترميز غير UTF-8 في قاعدة البيانات حتى ولو اتبعت كل التحذيرات أعلاه!

لكي تكون متأكداً من أن النصوص تُنتقل من PHP إلى MySQL بترميز UTF-8، تَأَكَّد من أن جميع الجداول في قاعدة البيانات قد تم ضبط ترميزها لتسخدم الترميز utf8mb4 في كل من الترميز والترتيب (Character set and Collation)، وأن تقوم باستخدام utf8mb4 عند الاتصال باستخدام PDO، قم بالإطلاع على المثال أدناه فهو هام جدا.

ملاحظة: يجب استخدام ترميز utf8mb4 لدعمِ كاملِ لترميز UTF-8 وليس الترميز utf8! أكمل القراءة لمعرفة السبب.

UTF-8 على مستوى المتصفح

استخدم الدَّالة mb_http_output() للتَّأكد من أن تطبيقك يطبع نصوصا بترميز UTF-8 على المتصفح.

سيلزم عندها إخبار المتصفح -باستخدام HTTP Response- أن هذه الصفحة يجب أن تعتبر مُرَمَّزة باستخدام UTF-8. سابقاً كان يتم إدراج هذا الإجراء عن طريق إدراج charset في وسم <meta> بداخل وسم <head> في الصفحة. هذا الإجراء سليم تماماً ولكن إضافة Content-Type كترويسة (header) هو عملياً أكثر سرعة.

<?php
// قم بإخبار PHP أننا نستخدم ترميز UTF-8 حتى النهاية
mb_internal_encoding('UTF-8');
 
// قم بإخبار PHP أننا سنقوم بطبع مخرجات بترميز UTF-8 على المتصفح
mb_http_output('UTF-8');
 
// نص لتجربة عملية الترميز
$string = 'Êl síla erin lû e-govaned vîn.';
 
// قم بتحويل النص إلى شكل آخر باستخدام دالة البايت المتعدد Multibyte
// نلاحظ أنه يمكننا قطع نص من خارج جدول Ascii لأغراض الشرح فقط
$string = mb_substr($string, 0, 15);
 
// نقوم بالاتصال بقاعدة البيانات ثم نقوم بتخزين النص الذي قمنا بتحويله
// قم بالإطلاع على مثال PDP في هذا المستند لمزيد من المعلومات
// لاحظ أننا استخدمنا `charset=utf8mb4` في بيانات اسم مصدر البيانات (Data Source Name) (DSN)
$link = new PDO(
    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
    'your-username',
    'your-password',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false
    )
);
 
// قم بتخزين النص المُحَوَّل بترميز UTF-8 في قاعدة البيانات
// هل قاعدة البيانات والجداول تستخدم ترميز وترتيب `utf8mb4` Charset and collation؟
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();
 
// قم باسترجاع النص الذي قمنا بتخزينه حتى يتم إثبات أنه قد تم تخزينه بطريقة صحيحة
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();
 
// قم بتخزين النتيجة إلى كائن حتى نتمكن من طباعته لاحقاً في HTML
$result = $handle->fetchAll(\PDO::FETCH_OBJ);

header('Content-Type: text/html; charset=UTF-8');
?>
<!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UTF-8 صفحة تجريبية</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            print($row->Body);  // يجب أن يتم طباعة هذا النص المُحَوَّل بترميز UTF-8 بصور صحيحة في المتصفح
        }
        ?>
    </body>
</html>

لمزيد من المصادر

Internationalization (i18n) and Localization (l10n)

Disclaimer for newcomers: i18n and l10n are numeronyms, a kind of abbreviation where numbers are used to shorten words - in our case, internationalization becomes i18n and localization, l10n.

First of all, we need to define those two similar concepts and other related things:

Common ways to implement

The easiest way to internationalize PHP software is by using array files and using those strings in templates, such as <h1><?=$TRANS['title_about_page']?></h1>. This is, however, hardly a recommended way for serious projects, as it poses some maintenance issues along the road - some might appear in the very beginning, such as pluralization. So, please, don’t try this if your project will contain more than a couple of pages.

The most classic way and often taken as reference for i18n and l10n is a Unix tool called gettext. It dates back to 1995 and is still a complete implementation for translating software. It is pretty easy to get running, while it still sports powerful supporting tools. It’s about Gettext we will be talking here. Also, to help you not get messy over the command-line, we will be presenting a great GUI application that can be used to easily update your l10n source files.

Other tools

There are common libraries used that support Gettext and other implementations of i18n. Some of them may seem easier to install or sport additional features or i18n file formats. In this document, we focus on the tools provided with the PHP core, but here we list others for completion:

Other frameworks also include i18n modules, but those are not available outside of their codebases:

If you decide to go for one of the libraries that provide no extractors, you may want to use the gettext formats, so you can use the original gettext toolchain (including Poedit) as described in the rest of the chapter.

Gettext

Installation

You might need to install Gettext and the related PHP library by using your package manager, like apt-get or yum. After installed, enable it by adding extension=gettext.so (Linux/Unix) or extension=php_gettext.dll (Windows) to your php.ini.

Here we will also be using Poedit to create translation files. You will probably find it in your system’s package manager; it’s available for Unix, Mac, and Windows, and can be downloaded for free on their website as well.

Structure

Types of files

There are three files you usually deal with while working with gettext. The main ones are PO (Portable Object) and MO (Machine Object) files, the first being a list of readable “translated objects” and the second, the corresponding binary to be interpreted by gettext when doing localization. There’s also a POT (Template) file, that simply contains all existing keys from your source files, and can be used as a guide to generate and update all PO files. Those template files are not mandatory: depending on the tool you’re using to do l10n, you can go just fine with only PO/MO files. You’ll always have one pair of PO/MO files per language and region, but only one POT per domain.

Domains

There are some cases, in big projects, where you might need to separate translations when the same words convey different meaning given a context. In those cases, you split them into different domains. They’re basically named groups of POT/PO/MO files, where the filename is the said translation domain. Small and medium-sized projects usually, for simplicity, use only one domain; its name is arbitrary, but we will be using “main” for our code samples.
In Symfony projects, for example, domains are used to separate the translation for validation messages.

Locale code

A locale is simply a code that identifies one version of a language. It’s defined following the ISO 639-1 and ISO 3166-1 alpha-2 specs: two lower-case letters for the language, optionally followed by an underline and two upper-case letters identifying the country or regional code. For rare languages, three letters are used.

For some speakers, the country part may seem redundant. In fact, some languages have dialects in different countries, such as Austrian German (de_AT) or Brazilian Portuguese (pt_BR). The second part is used to distinguish between those dialects - when it’s not present, it’s taken as a “generic” or “hybrid” version of the language.

Directory structure

To use Gettext, we will need to adhere to a specific structure of folders. First, you’ll need to select an arbitrary root for your l10n files in your source repository. Inside it, you’ll have a folder for each needed locale, and a fixed LC_MESSAGES folder that will contain all your PO/MO pairs. Example:

<project root>
 ├─ src/
 ├─ templates/
 └─ locales/
    ├─ forum.pot
    ├─ site.pot
    ├─ de/
    │  └─ LC_MESSAGES/
    │     ├─ forum.mo
    │     ├─ forum.po
    │     ├─ site.mo
    │     └─ site.po
    ├─ es_ES/
    │  └─ LC_MESSAGES/
    │     └─ ...
    ├─ fr/
    │  └─ ...
    ├─ pt_BR/
    │  └─ ...
    └─ pt_PT/
       └─ ...

Plural forms

As we said in the introduction, different languages might sport different plural rules. However, gettext saves us from this trouble once again. When creating a new .po file, you’ll have to declare the plural rules for that language, and translated pieces that are plural-sensitive will have a different form for each of those rules. When calling Gettext in code, you’ll have to specify the number related to the sentence, and it will work out the correct form to use - even using string substitution if needed.

Plural rules include the number of plurals available and a boolean test with n that would define in which rule the given number falls (starting the count with 0). For example:

Now that you understood the basis of how plural rules works - and if you didn’t, please look at a deeper explanation on the LingoHub tutorial -, you might want to copy the ones you need from a list instead of writing them by hand.

When calling out Gettext to do localization on sentences with counters, you’ll have to give him the related number as well. Gettext will work out what rule should be in effect and use the correct localized version. You will need to include in the .po file a different sentence for each plural rule defined.

Sample implementation

After all that theory, let’s get a little practical. Here’s an excerpt of a .po file - don’t mind with its format, but instead the overall content, you’ll learn how to edit it easily later:

msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "We're now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"

msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"

msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"

The first section works like a header, having the msgid and msgstr especially empty. It describes the file encoding, plural forms and other things that are less relevant. The second section translates a simple string from English to Brazilian Portuguese, and the third does the same, but leveraging string replacement from sprintf so the translation may contain the user name and visit date.
The last section is a sample of pluralization forms, displaying the singular and plural version as msgid in English and their corresponding translations as msgstr 0 and 1 (following the number given by the plural rule). There, string replacement is used as well so the number can be seen directly in the sentence, by using %d. The plural forms always have two msgid (singular and plural), so it’s advised to not use a complex language as the source of translation.

Discussion on l10n keys

As you might have noticed, we’re using as source ID the actual sentence in English. That msgid is the same used throughout all your .po files, meaning other languages will have the same format and the same msgid fields but translated msgstr lines.

Talking about translation keys, there are two main “schools” here:

  1. msgid as a real sentence.
    The main advantages are:
    • if there are pieces of the software untranslated in any given language, the key displayed will still maintain some meaning. Example: if you happen to translate by heart from English to Spanish but need help to translate to French, you might publish the new page with missing French sentences, and parts of the website would be displayed in English instead;
    • it’s much easier for the translator to understand what’s going on and make a proper translation based on the msgid;
    • it gives you “free” l10n for one language - the source one;
    • The only disadvantage: if you need to change the actual text, you would need to replace the same msgid across several language files.
  2. msgid as a unique, structured key.
    It would describe the sentence role in the application in a structured way, including the template or part where the string is located instead of its content.
    • it’s a great way to have the code organized, separating the text content from the template logic.
    • however, that could bring problems to the translator that would miss the context. A source language file would be needed as a basis for other translations. Example: the developer would ideally have an en.po file, that translators would read to understand what to write in fr.po for instance.
    • missing translations would display meaningless keys on screen (top_menu.welcome instead of Hello there, User! on the said untranslated French page). That’s good it as would force translation to be complete before publishing - but bad as translation issues would be really awful in the interface. Some libraries, though, include an option to specify a given language as “fallback”, having a similar behavior as the other approach.

The Gettext manual favors the first approach as, in general, it’s easier for translators and users in case of trouble. That’s how we will be working here as well. However, the Symfony documentation favors keyword-based translation, to allow for independent changes of all translations without affecting templates as well.

Everyday usage

In a common application, you would use some Gettext functions while writing static text in your pages. Those sentences would then appear in .po files, get translated, compiled into .mo files and then, used by Gettext when rendering the actual interface. Given that, let’s tie together what we have discussed so far in a step-by-step example:

1. A sample template file, including some different gettext calls

<?php include 'i18n_setup.php' ?>
<div id="header">
    <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1>
    <!-- code indented this way only for legibility -->
    <?php if ($unread): ?>
        <h2><?=sprintf(
            ngettext('Only one unread message',
                     '%d unread messages',
                     $unread),
            $unread)?>
        </h2>
    <?php endif ?>
</div>

<h1><?=gettext('Introduction')?></h1>
<p><?=gettext('We\'re now translating some strings')?></p>

2. A sample setup file (i18n_setup.php as used above), selecting the correct locale and configuring Gettext

<?php
/**
 * Verifies if the given $locale is supported in the project
 * @param string $locale
 * @return bool
 */
function valid($locale) {
   return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es']);
}

//setting the source/default locale, for informational purposes
$lang = 'en_US';

if (isset($_GET['lang']) && valid($_GET['lang'])) {
    // the locale can be changed through the query-string
    $lang = $_GET['lang'];    //you should sanitize this!
    setcookie('lang', $lang); //it's stored in a cookie so it can be reused
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
    // if the cookie is present instead, let's just keep it
    $lang = $_COOKIE['lang']; //you should sanitize this!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    // default: look for the languages the browser says the user accepts
    $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
    array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); });
    foreach ($langs as $browser_lang) {
        if (valid($browser_lang)) {
            $lang = $browser_lang;
            break;
        }
    }
}

// here we define the global system locale given the found language
putenv("LANG=$lang");

// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instance
setlocale(LC_ALL, $lang);

// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mo
bindtextdomain('main', '../locales');

// indicates in what encoding the file should be read
bind_textdomain_codeset('main', 'UTF-8');

// if your application has additional domains, as cited before, you should bind them here as well
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');

// here we indicate the default domain the gettext() calls will respond to
textdomain('main');

// this would look for the string in forum.mo instead of main.mo
// echo dgettext('forum', 'Welcome back!');
?>

3. Preparing translation for the first run

To make matters easier - and one of the powerful advantages Gettext has over custom framework i18n packages - is its custom file type. “Oh man, that’s quite hard to understand and edit by hand, a simple array would be easier!” Make no mistake, applications like Poedit are here to help - a lot. You can get the program from their website, it’s free and available for all platforms. It’s a pretty easy tool to get used to, and a very powerful one at the same time - using all powerful features Gettext has available.

In the first run, you should select “File > New Catalog” from the menu. There you’ll have a small screen where we will set the terrain so everything else runs smoothly. You’ll be able to find those settings later through “Catalog > Properties”:

After setting those points you’ll be prompted to save the file - using that directory structure we mentioned as well, and then it will run a scan through your source files to find the localization calls. They’ll be fed empty into the translation table, and you’ll start typing in the localized versions of those strings. Save it and a .mo file will be (re)compiled into the same folder and ta-dah: your project is internationalized.

4. Translating strings

As you may have noticed before, there are two main types of localized strings: simple ones and the ones with plural forms. The first ones have simply two boxes: source and localized string. The source string can’t be modified as Gettext/Poedit do not include the powers to alter your source files - you should change the source itself and rescan the files. Tip: you may right-click a translation line and it will hint you with the source files and lines where that string is being used.
On the other hand, plural form strings include two boxes to show the two source strings, and tabs so you can configure the different final forms.

Whenever you change your sources and need to update the translations, just hit Refresh and Poedit will rescan the code, removing non-existent entries, merging the ones that changed and adding new ones. It may also try to guess some translations, based on other ones you did. Those guesses and the changed entries will receive a “Fuzzy” marker, indicating it needs review, being highlighted in the list. It’s also useful if you have a translation team and someone tries to write something they’re not sure about: just mark Fuzzy and someone else will review later.

Finally, it’s advised to leave “View > Untranslated entries first” marked, as it will help you a lot to not forget any entry. From that menu, you can also open parts of the UI that allow you to leave contextual information for translators if needed.

Tips & Tricks

Possible caching issues

If you’re running PHP as a module on Apache (mod_php), you might face issues with the .mo file being cached. It happens the first time it’s read, and then, to update it, you might need to restart the server. On Nginx and PHP5 it usually takes only a couple of page refreshes to refresh the translation cache, and on PHP7 it is rarely needed.

Additional helper functions

As preferred by many people, it’s easier to use _() instead of gettext(). Many custom i18n libraries from frameworks use something similar to t() as well, to make translated code shorter. However, that’s the only function that sports a shortcut. You might want to add in your project some others, such as __() or _n() for ngettext(), or maybe a fancy _r() that would join gettext() and sprintf() calls. Other libraries, such as oscarotero’s Gettext also provide helper functions like these.

In those cases, you’ll need to instruct the Gettext utility on how to extract the strings from those new functions. Don’t be afraid, it’s very easy. It’s just a field in the .po file, or a Settings screen on Poedit. In the editor, that option is inside “Catalog > Properties > Source keywords”. You need to include there the specifications of those new functions, following a specific format:

After including those new rules in the .po file, a new scan will bring in your new strings just as easy as before.

References

للأعلى

حقن التوابع Dependency Injection

مقتبس من ويكيبيديا الإنجليزية Wikipedia:

حقن التوابع هو تصميم نموذجي برمجي يتيح إزالة التوابع المكتوبة صراحةً ثم جعلها متوفرة للتغيير سواء كان ذلك في مرحلة التشغيل أو في مرحلة البناء.

هذا الإقتباس يجعل هذا المفهوم مُعَقَّدا أكثر مما هو عليه فعلا. عملية حقن تابع هو تجهيز محتوياته وتوابعه سواء باستخدام حقن عند الإنشاء أو تنفيذ الدوال أو في الضبط. إنه بهذه السهولة.

للأعلى

— title: المفهوم الأساسي isChild: true anchor: basic_concept —

المفهوم الأساسي

يمكن شرح هذا المفهوم باستخدام مثال سَهْل.

لدينا صنف Database يعتمد على مُحول للتخاطب مع قاعدة البيانات. نقوم بإنشاء المحول في دالة الإنشاء ثم نقوم بحقن ثابت. هذا يجعل عملية التجربة والإختبار عملية صعبة مما يعني أن الصنف Database مرتبط بشدة مع المحول.

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

يمكن إعادة صياغة هذه الشفرة البرمجية لاستخدام حقن التوابع وفك الإرتباط من التوابع:

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct(MySqlAdapter $adapter)
    {
        $this->adapter = $adapter;
    }
}

class MysqlAdapter {}

الآن يمكننا أن نعطي صنف Database توابعه عن طريق إنشائها بنفسه. ويمكن أيضاً إنشاء دالة تستقبل التابع كقيمة ثم إسنادها مباشرة، أو إذا كان المحول $adapter في وصف عام public property عندها يمكن إسناده مباشرةً.

مشكلة معقدة

إذا كنت قرأت عن حقن التوابع سابقاً قد يكون لفت انتباهك مصطلح “Inversion of Control” انعكاس التحكم أو “Dependency Inversion Principle” مفهوم عكس تحكم التوابع. هذه المشاكل المعقدة هي ما يقوم حقن التوابع بحلها.

انعكاس التحكم

انعكاس التحكم كما يدل عليها الاسم هو إبقاء التحكم التنظيمي بالكامل بمنئى عن العناصر أو الكائنات. في مصطلح حقن التوابع هذا يعني فك الإرتباط مع التوابع وعكس التحكم بهذه التوابع في مكان آخر في النظام.

طوال عدة سنين، أطر النظام في PHP تستخدم عكس التحكم، ولكن يبقى السؤال: أي جزء من التحكم هو الذي تقوم بعكسه؟ وإلى أين؟ مثلا أطر العمل بنموذج MVC تقدم بشكل عام كائن أساسي أو متحكم أساسي يجب على المتحكمات الفرعية الأخرى أن تستمد منه لكي تحصل على وصول لتوابعه. هذا هو عكس التحكم، لكن عوضاً عن فك ارتباط التوابع هذه الطريقة تقوم بترحيلهم.

حقن التوابع يتيح لنا حل هذه المشكلة بسلاسة عن طريق حقن التوابع التي نريد عند الحاجة، دون الحاجة لأي كتابة صريحة لأي تابع في المصدر على الإطلاق.

مفهوم عكس التحكم

مفهوم عكس التحكم هو حرف “D” في مجموعة مفاهيم البرمجة الشيئية S.O.L.I.D وينص على أنه يجب “الإعتماد على التجريد، لا على التحجير أو الإنغلاق”. هذا يعني أن التوابع يجب أن تكون واجهات أو مجردات عوضاً عن التطبيقات الجامدة أو المحجرة. عندها يمكننا إعادة صياغة وكتابة المفهوم أعلاه بسهولة:

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;
    }
}

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}

هنالك عدة فوائد يستفيد منها صنف Database الآن، فهوالآن يعتمد على واجهة بدلاً من كائن جامد.

ضع بالإعتبار أنك تعمل ضمن فريق وأن أحد الزملاء يقوم بالعمل على المحول. في المثال الأول يجب أن نقوم بانتظار زميلنا حتى يخبرنا بأنه قد انتهى من المحول حتى يتسنى لنا الاعتماد عليه في إختبار الوحدة. الآن التابع هو عبارة عن واجهة يمكننا أن نعتمد عليها باعتبار أن زميلنا سيقوم ببناء المحول وفق الواجهة المحددة.

فائدة أكبر وهي أن هذه الطريقة تجعل المصدر قابل للتطوير. فمثلا بعد سنة قررنا أن نقوم بالترحيل إلى نوع آخر من قواعد البيانات، يمكن أن نقوم بكاتبة محول يتبع شروط الواجهة الأصلية ثم يحقن عوضاً عنه. عندها لا يوجد تعديل أو إعادة صياغة ويمكن أن نجزم بأن ذلك المحول الجديد يتبع الشروط الموضوعة من قبل الواجهة.

للأعلى

— title: الحاويات Containers isChild: true anchor: containers —

الحاويات Containers

أول شيء يجب أن تفهمه عن حاويات حقن التوابع هو أنها تختلف عن عملية حقن التوابع. الحاوية هي أداة تسهيلية تساعدنا في بناء حقن توابع، ولكن يمكن أن يساء ٱستخدامها لبناء (ضد النمذجة) تحديد للخدمة. حقن تابع بصفته محدد خدمة في الصنف يقوم بإنشاء ٱرتباط أقوى للتوابع في داخل الحاوية بدلاً من التابع الذي تستبدله. وتقوم بتعقيد المصدر مما يجعل تجربته صعبة.

معظم أطر العمل الحديثة تقوم بٱستخدام حاويات حقن التوابع بحيث تتيح لك كتابة توابعك جميعها عن طريق الضبط. ما يعني أن هذه الممارسة تمكنك من كتابة مصدر نظيف وغير مرتبط كإطار العمل الذي بُـني عليه.

اقرأ المزيد

للأعلى

قواعد البيانات

عادةً ما تقوم بٱستخدام قاعدة بيانات لبرنامجك لتخزين المعلومات. لديك خيارات محدودة للٱتصال والتعامل مع قاعدة البيانات. الطريقة المستحسنة حتى إصدار PHP 5.1.0 هي ٱستخدام لاحقات التشغيل المدمجة مثل mysqli و pgsql و mssql … ألخ.

لاحقات التشغيل المدمجة ممتازة إذا كنت تستخدم قاعدة بيانات واحدة في برنامجك، ولكن مثلاً إذا كنت تستخدم MySQL والقليل من MSSQL، أو ربما تريد أن تتصل مع قاعدة بيانات أوراكل، عندها لن تتمكن من ٱستخدام نفس تلك اللواحق. سوف تحتاج لتعلم طريقة API جديدة لكل لاحقة قاعدة بيانات — وهذا شيء مجهد.

لاحقة MySQL

لاحقة mysql هي لاحقة مدمجة في PHP وقد باتت قديمة وتم الٱستغناء عنها وٱستبدالها بلاحقتين أخريين:

لم يتوقف التطوير على اللاحقة mysql منذ زمن بعيد فحسب، بل وقد تم إهمالها منذ إصدار PHP 5.5.0 ثم تم إزالتها حصرياً في أول إصدار من PHP 7.0

لمعرفة ما إذا كان تطبيق ما يعمل بهذه اللاحقة يمكنك البحث باستخدام محرر النصوص لديك عن دوال مثل mysql_connect() وmysql_query فإذا ظهرت نتائج بحث إيجابية فهذا يعني أن هذه اللاحقة مستخدمة في التطبيق. وهذا عوضاً عن البحث في ملف php.ini للتحقق من وجود اللاحقة.

حتى وإن لم تكن تستخدم إصدار PHP 7.x بعد، عدم الٱهتمام بالتحديث في أقرب فرصة يزيد صعوبة المهمة عندما تحدث الترقية. أفضل حل هو ٱستبدال ٱستخدام mysql بأي من mysqli أو PDO في تطبيقاتك كممارسة أساسية في عملية التطوير حتى لا تضطر لتنفيذها على عجالة في وقت لاحق.

إذا كنت تنوي الترقية من mysql إلى mysqli، هنالك موجهات سهلة تقترح عليك بأن تقوم بالبحث عن mysql_* واستبدالها بـ mysqli_*. هذا المقترح مفرط السهولة، ولكنه يفقد العديد من فوائد وخواص تقدمها mysqli مثل ربط القيم، وهي متوفرة أيضاً في PDO.

لاحقة PDO

تعتبر [PDO] مكتبة مجردة للٱتصال بقاعدة البيانات — تم بنائها في PHP منذ الإصدار 5.1.0 — بحيث توفر واجهات ٱتصال عادية للعديد من قواعد البيانات المختلفة. مثلا يمكن ٱستخدام نفس الشفرة المصدرية للٱتصال والعمل بقاعدة بيانات MySQL أو SQLite:

<?php
// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

لا تقوم PDO بترجمة أو تحويل الٱستعلامات SQL Queries أو مماثلة الخصائص المفقودة، ولكنها بالفعل تتصل بأكثر من نوع من قواعد البيانات بنفس الوجهة البرمجية API.

للأهمية: PDO تتيح بسهولة حقن مدخلات خارجية (كالمفاتح ID مثلا) في ٱستعلام SQL Query من دون القلق بشأن تهديدات SQL Injection. وهذا ممكن بٱستخدام جمل PDO وتقييد وربط القيم PDO Statements.

فلنفترض أن في مصدر PHP نقوم بٱستقبال مفتاح رقمي ID وهو عبارة عن قيمة في ٱستعلام. هذا المفتاح يجب أن يستخدم لٱستخراج بيانات مستخدم من قاعدة البيانات. الطريقة التالية هي الطريقة الخاطئة للقيام بهذه العملية:

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- خطأ!

هذا مصدر خاطئ تماماً. فهو يقوم بإدخال قيم خام إلى ٱستعلام SQL. مما يسبب تَعَرُّضك للٱختراق بسهولة باستخدام أسلوب حقن الٱستعلام [SQL Injection]. تصور أن مخترقا قام بإرسال قيمة ID عن طريق تنفيذ عنوان مثل http://domain.com/?id=1%3BDELETE+FROM+users. سوف يقوم بإسناد للمتغير $_GET['id'] القيمة 1;DELETE FROM users مما يتسبب بحذف جميع سجلات المستخدمين! عوضاً عن ذلك يجب أن تقوم (بتعقيم) المفتاح ID المدخل بٱستخدام جمل التقييد في PDO.

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); // <-- قم بترشيح بياناتك أولاً (أنظر [ترشيح البيانات](#data_filtering))، مهم جداً خصوصاً لعمليتي INSERT و UPDATE..
$stmt->bindParam(':id', $id, PDO::PARAM_INT); // <-- تلقائياً يتم تعقيم القيم بٱستخدام PDO
$stmt->execute();

وهذا مصدر صحيح. يتم ٱستخدام تقييد القيم في جملة PDO. فبدورها تقوم تلقائياً بتعقيم المدخل ID قبل أن تقوم بإدراجه لقاعدة البيانات مما يمنع ٱحتمال هجمات SQL Injection.

عند كتابة عمليات مثل INSERT أو UPDATE، يجب (للأهمية القصوى) أن تقوم بترشيح البيانات أولاً ثم بتعقيمها من العناصر الأخرى مثل (حذف أوسم HTML و JavaScript وغيرها..). سيقوم PDO فقط بتعقيم المدخلات لٱستخدامها في SQL وليس لكي تستخدمها أنت في تطبيقك.

يجب عليك أن تكون على دراية بأن ٱتصال قاعدة البيانات يقوم بٱستخدام مصادر وأنه ليس من الغريب ٱستهلاك هذه المصادر بسبب تلك الٱتصالات التي لم تغلق، ولكن هذا شيء معتاد في الكثير من لغات البرمجة الأخرى. بٱستخدام PDO يمكنك إغلاق الٱتصال فقط بحذف الكائن وذلك للتأكد من أن كل الٱرتباطات قد تم حذفها. مثلاً إسنادها للقيم الفارغة NULL. إذا لم تقم بهذا يدوياً فسوف تقوم PHP تلقائياً بإغلاق هذه الٱتصالات عندما يتم تنفيذ المصدر إلا إذا كنت تستخدم ٱتصالات مستمرة بالطبع.

التفاعل مع قواعد البيانات

عندما يبدأ المطورون تَعَلُّم لغة البرمجة PHP للمرة الأولى، عادة ما يقومون بدمج عمليات قواعد البيانات مع الطرح المنطقي، بكتابة مصدر كهذا:

<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
?>
</ul>

هذه ممارسة سيئة من كل النواحي، فهي صعبة الفحص وتصحيح الأخطاء، وصعبة التجربة، وصعبة القراءة وستقوم أيضاً بإخراج العديد من الحقول التي لم تقم بعمل حد لها.

هنالك العديد من الحلول - سواء كنت تفضل ٱستخدام البرمجة الشيئية OOP أو البرمجة الوظيفية Functional Programming- ما يزال يتوجب عليك أن تقوم بالفصل.

مثال لطريقة بدائية:

<?php
function getAllFoos($db) {
    return $db->query('SELECT * FROM table');
}

foreach (getAllFoos($db) as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // خطأ!!
}

هذه بداية جيدة. قم بوضع كل منهما في ملفين مختلفين عندها تحصل على عملية فصل نظيفة.

قم بإنشاء صنف عوضاً عن تلك الطريقة عندها سيكون لديك نموذج “Model”. قم بإنشاء ملف .php ثم ضع فيه محتوى العرض المنطقي، عندها سيكون لديك العرض “View”، ستلاحظ أنه قريب من نموذج MVC - طريقة معتادة في معمارية البرمجة الشيئية لأغلبية أطر العمل frameworks.

foo.php

<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

// قم بإدراج النموذج Model
include 'models/FooModel.php';

// قم بإنشاء كائن
$fooModel = new FooModel($db);
// ٱستخرج قائمة من Foos
$fooList = $fooModel->getAllFoos();

// قم بإدراج العرض View
include 'views/foo-list.php';

models/FooModel.php

<?php
class FooModel
{
    protected $db;

    public function __construct(PDO $db)
    {
        $this->db = $db;
    }

    public function getAllFoos() {
        return $this->db->query('SELECT * FROM table');
    }
}

views/foo-list.php

<?php foreach ($fooList as $row): ?>
    <?= $row['field1'] ?> - <?= $row['field1'] ?>
<?php endforeach ?>

هذا التطبيق هو مماثل جداً لمعظم ما تقوم به أطر العمل الحديثة، ولو تشببها قليلا. قد لا تحتاج أن تقوم بهذه الممارسة في كل مرة، ولكن خلط العرض المنطقي والنموذج التفاعلي لقاعدة البيانات من شأنه أن يُكَوِن مشكلة حقيقية إذا ما أردت أن تقوم بٱختبار وحدات unit-test لتطبيقك.

هنالك مصدر في موقع PHPBridge يسمى إنشاء صنف بيانات Creating a Data Class يقوم بتغطية جميع المواضيع المشابهة، من الجيد للمطورين بأن يتعودوا على مفهوم التفاعل مع قواعد البيانات.

طبقات التجريد

العديد من أطر العمل لديها طبقات التجريد الخاصة بها مما قد يجعلها تكون فوق طبقة أو لا PDO. غالباً ما يتم محاكاة خواص لنظام قاعدة بيانات مفقودة في الأخرى عن طريق ربط الٱستعلامات في دوال PHP، وتعطيك نفس التجريد لقاعدة البيانات عوضاً عن مجرد ٱتصال تجريدي كما تفعل PDO. بالطبع سوف هذه الممارسة تحتاج قليلا من الجهد، ولكن إذا كنت تنوي بناء نظام متنقل يمكنه العمل على كل من MySQL و PostgreSQL و SQLite عندها ستقوم بالقليل من الجهد وكتابة بضع السطور البرمجية.

بعض طبقات التجريد تم بنائها بٱستخدام التوصية PSR-0 أو PSR-4 بأسماء فضاء قياسية حتى تتمكن من تنصيب أي تطبيق تريد، مثل:

للأعلى

النمذجة Templating

توفر النمذجة طريقة مناسبة لفصل التحكم المنطقي من العرض المنطقي. تحتوي النماذج على HTML لتطبيقك، وقد تستخدم صيغ أخرى مثل XML. عادة ما تسمى النماذج بالعرض (أو “views”) فهي تمثل المكون الثاني من النموذج المعماري للبرمجة الكائنية (MVC) model–view–controller.

الفوائد

تكمن الفائدة الأساسية لٱستخدام النماذج في إنشاء فصل «نظيف» بين العرض المنطقي وبقية البرنامج. تكون مسؤولية النماذج الوحيدة هي عرض محتوى مهيء. ولا تحمل أي مسؤولية تنفيذ ٱستخراج بيانات أو عمل مهام معقدة. مما يؤدي إلى مصدر شفرة سهل القراءة ومفيدا خاصة في بيئة العمل في فريق عندها يقوم المطورون بكتابة المصدر البرمجي للعمليات التي تنفذ على الخادم server-side (Controller و Models) ويقوم المصممون بالعمل على جانب العميل client-side.

تقوم النماذج أيضا بتطوير تنظيم شكل ومصدر العرض المنطقي. فالنماذج توضع في مجلد العرض (“Views”) كلٌ محدد بملف واحد. هذا الأسلوب يساعد على وجود مصدر قابل للٱستخدام وإعادة الٱستخدام بحيث أنه يتم تقسيم مجموعات المصدر الكبيرة إلى قطع صغيرة وقابلة للٱستخدام، عادة ما تسمى الأجزاء Partials. مثلاً في الموقع هنالك رأس وذيل كل منهما يمكن أن يعرفا كنماذج يمكن إدراج كل منهما قبل وبعد نموذج أي صفحة.

أخيراً، حسب المكتبة التي تقوم بٱستخدامها، يمكن أن توفر بعض النماذج حماية تلقائياً عن طريق ترشيح المحتوى. بعض المكتبات توفر وضع الحماية sand-boxing بحيث أنه مصممي النماذج لديهم وصول فقط للدوال والمتغيرات في القائمة البيضاء أي في القائمة المسموح بها للٱستخدام في النماذج.

نماذج PHP العادية

نماذج PHP العادية هي عبارة عن النماذج التي تستخدم مصدر PHP لكتابة محتواها. هنالك العديد من الخيارات بما أن PHP هي عبارة عن لغة نمذجة في نفسها. فهذا يعني أنه يمكن دمج مصدر PHP مع مصادر أخرى مثل توسيم HTML. هذا مفيد لمطوري PHP فهو لا يضيف جمل جديدة لتعلمها، وهم يعلمون الدوال المتوفرة لديهم ومحرر النصوص يحتوي على تخطيط PHP والإكمال التلقائي مدمج معه. تصنف نماذج PHP العادية بأنها سريعة فهي لا تتطلب أي عملية تجميع وبناء.

كل أطر العمل الحديثة تقوم بتوظيف شكل من أشكال نظام النمذجة، فالبعض يستخدم نماذج PHP عادية. بعيداً عن أطر العمل، هنالك مكتبات مثل Plates أو Aura.View تقوم بالعمل على نماذج PHP العادية بطريقة أسهل وذلك عن طريق توفير وظائف نمذجة متطورة مثل التوريث والقوالب والإضافات.

مثال لنماذج PHP عادية

باستخدام مكتبة Plates.

<?php // user_profile.php ?>

<?php $this->insert('header', ['title' => 'ملف المستخدم']) ?>

<h1>ملف المستخدم</h1>
<p>مرحباً <?=$this->escape($name)?></p>

<?php $this->insert('footer') ?>

مثال للتوريث في نماذج PHP عادية

باستخدام مكتبة Plates.

<?php // template.php ?>

<html>
<head>
    <title><?=$title?></title>
</head>
<body>

<main>
    <?=$this->section('content')?>
</main>

</body>
</html>
<?php // user_profile.php ?>

<?php $this->layout('template', ['title' => 'ملف المستخدم']) ?>

<h1>ملف المستخدم</h1>
<p>مرحباً <?=$this->escape($name)?></p>

نماذج التجميع Compiled Templates

بالرغم أن PHP تطورت بطبيعتها لتكون لغة برمجة كائنية، ولكنها لم تتطور بكونها لغة نمذجة. نماذج التجميع مثل Twig وBrainy وSmarty تملأ هذا الفراغ بتوفير صيغ كتابة جديدة تمتلك المواصفات الحقيقة مثل كونها نماذج. ابتداءً من الترشيح التلقائي حتى التوريث وسهولة التحكم في الهياكل، نماذج التجميع قد صممت لكي تكون سهلة الكتابة والقراءة وآمنة للإستخدام. نماذج التجميع يمكن أن تنتشر وتستخدم عبر لغات مختلفة، Mustache هو مثال جيد لهذا. بما أن هذه النماذج تستوجب عملية التجميع فهنالك فرق في الأداء ولكنه فرق طفيف إذا تم استخدام تخزين مؤقت بطريقة جيدة (caching).

*بالرغم أن Smarty يوفر ترشيح تلقائي ولكنه غير مفعل بشكل إفتراضي.

مثال لنموذج تجميعي

باستخدام مكتبة Twig.

{% include 'header.html' with {'title': 'ملف المستخدم'} %}

<h1>ملف المستخدم</h1>
<p>مرحباً {{ name }}</p>

{% include 'footer.html' %}

مثال بسيط للتوريث في نموذج تجميعي

باستخدام مكتبة Twig.

// template.html

<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>

<main>
    {% block content %}{% endblock %}
</main>

</body>
</html>
// user_profile.html

{% extends "template.html" %}

{% block title %}ملف المستخدم{% endblock %}
{% block content %}
    <h1>ملف المستخدم</h1>
    <p>مرحباً {{ name }}</p>
{% endblock %}

المزيد من القراءة

مقالات ودروس

مكتبات

للأعلى

الأخطاء والإستثناءات Errors and Exceptions

الأخطاء

في الكثير من لغات البرمجة التي تعتمد بكثافة على الإستثناءات “Exceptions” عندما يحدث خطأ ما سوف يتم القاء إستثناء. فهذه هي الطريقة المعتمدة لعمل الأشياء، ولكن لغة PHP لا تعتمد بكثافة على الإستثناءات. ولكن توجد إستثناءات بداخلها والعديد من الدوال الجوهرية بدأت فعلياً باستخدام الإستثناءات ثم التعامل بالكائنات، فالعديد من PHP نفسها تحاول ان تواصل العمل بغض النظر عما يحدث، إلا إذا حدث خطأ ما.

مثلاً:

$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1

هذه مجرد ملاحظة ، PHP ستواصل العمل ولن تتوقف. وهذا قد يربك المطورين من لغات اخرى معتمدة على الإستثناءات بكثافة، لأنه عند استخدام متغير غير معرف في لغة Python مثلاً سيقوم برمي إستثناء:

$ python
>>> print foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

الإختلاف الحقيقي فقط هو أن لغة Python سوف تتوقف لأي شي حتى ولو كان صغيراً، إذاً سيكون المبرمج متأكداً جداً من أي مشكلة قد تحدث سيتم إكتشافها، بالمقابل PHP ستواصل قدماً في المعالجة إلا إذا حدث شيء كبير، عندها ستقوم برمي خطأ وتقرير هذا الخطاً.

شدة الخطأ

لدى PHP عدة مستويات لشدة الخطأ. يوجد ثلاث أنو معروفة من أنواع رسائل الخطأ والملاحظات والتحذيرات. هنالك عدة مستويات مختلفة من الشدة: E_ERROR و E_NOTICE و E_WARNING. الأولى هي الأخطاء الفادحة التي تحدث أثناء التنفيذ وغالباً ما تسبب فشل في تنفيذ المصدر ويتوجب عليك فحص هذا الخطأ حتى تتمكن من إكمال التنفيذ لأن PHP ستقوم بإيقاف التنفيذ. الثانية وهي رسائل نصحية تسبب المصدر في ظهورها ربما تتسبب أو قد لا تتسبب في حدوث خطأ اثناء تنفيذ المصدر، ولكن لن توقف PHP التنفيذ. والأخيرة هي أخطاء غير فادحة ولن يتوقف التنفيذ

هنالك نوع آخر من رسائل التقرير بالخطأ في مرحلة البناء والتجميع وهي E_STRICT. هذه الرسائل تستخدم لإقتراح تغيير في المصدر لتأكيد انه يقوم بنجاح بالتشغيل البيني و متوافق مع الإصدارات القادمة من PHP

تغيير طريقة PHP لتقرير الأخطاء

يمكن تغيير طريقة تقرير الأخطاء باستخدام ضبط PHP أو/و عن طريق تنفيذ دوال. باستخدام الدالة المدمجة error_reporting() يمكنك أن تحدد مستوى تقرير الأخطاء والمدة التي يتم فيها تنفيذ المصدر بارسال واحدة من ثوابت تقرير الأخطاء، بمعنى أنه يمكنك أن تحدد ما إذا كنت تريد أن تحصل على تقارير أخطاء وتحذيرات من دون ملاحظات فسيكون عليك ان يكون الضبط كالآتي:

<?php
error_reporting(E_ERROR | E_WARNING);

يمكنك أيضاً التحكم ما إن تظهر تلك الأخطاء على الشاشة أم لا (مفيدة في مرحلة التطوير)، وهل يتم تقييدها أم لا (مفيدة في العمل النهائي). للمزيد من المعلومات قم بزيارة تقرير الأخطاء.

كبح الأخطاء الداخلي

يمكنك إخبار PHP لكي يقوم بكبح أخطاء معينة باستخدام رمز التحكم بالأخطاء @. كل ما عليك أن تضع هذه العلامة في بداية الجملة البرمجية، سينتج من هذا أن أي خطأ ينجم من تلك الجملة فسيتم كبحه.

<?php
echo @$foo['bar'];

سيقوم هذا بطباعة $foo['bar'] إذا كان موجوداً، ولكن ببساطة يسقوم بإرجاع القيمة الفارغة NULL ولا شيء سيتم طباعته، وذلك في أي ما ان كان المتغير $foo أو الفهرس 'bar' غير موجود. من غير رمز التحكم هذه الجملة قد تنتج أي من هاتين الرسالتين: PHP Notice: Undefined variable: foo أو PHP Notice: Undefined index: bar.

قد تبدو هذه فكرة جيدة ولكن هنالك مفاضلات غير مرغوب فيها. تقوم PHP بالتعامل مع الجمل التي تحتوي على الرمز @ بطريقة أقل كفاءة من تلك التي لا تحتوي على الرمز @. التمثيل المسبق هو جذر قيم البرمجة ولكن إذا كان الأداء مهم لتطبيقك/مكتبتك فمن المهم أن تعلم النقص الذي يسببه الرمز @ في الأداء.

وأيضاً يقوم رمز كبح الخطأ بإبتلاع الخطأ تماماً. لن يظهر الخطأ ولن يتم تسجيله. في أنظمة PHP على بيئة العمل النهائية لا تستطيع أن تتحكم بتعطيل وظيفة هذا الرمز. قد ترى بعض الأحيان أنه قد يكون خطأ ما غير مؤذٍ أو غير مهم على الإطلاق ولا يؤثر في شيء فتفضل أن يتم كبح تقريره وعرضه.

كلما ابتعدت عن إستخدام كبح الأخطاء كلما كان أفضل. فمثلاً المثال أعلاه يمكن إعادة كتابته كالاتي بطريقة صحيحة:

<?php
echo isset($foo['bar']) ? $foo['bar'] : '';

هنالك حالة قد يكون كبح الأخطاء فيها شيء منطقي، كاستخدام الدالة fopen() عندما تفشل في فتح الملف المطلوب. يتوجب عليك فحص وجود الملف أولاً قبل القيام بفتح، ولكن ماذا إذا تم حذف الملف بعد نجاح عملية وقبل أن تنفذ fopen() (ربما تكون مستحيلة، ولكن قد تحدث) عندها ستقوم الدالة بإرجاع القيمة false و رمي خطأ. في الحقيقة هذا شيء يتوجب على PHP أن تقوم بحله ولكن هذه الحالة قد تكون الوحيدة الصالحة لإستخدام كبح الأخطاء.

كما ذكرنا سابقاً انه لا يوجد طريقة لنظام PHP أن يقوم بمنع رمز الكبح ولكن Xdebug بها ضبط xdebug.scream ini وستقوم بتعطيل رمز الكبح. يمكن تفعيل هذا الخيار عن طريق ملف ضبط PHP php.ini بالأتي:

xdebug.scream = On

يمكن أيضاً إسناد القيمة وتفعيل الضبط أثناء التشغيل باستخدام دالة ini_set.

<?php
ini_set('xdebug.scream', '1');

لاحقة PHP “Scream” توفر وظيفة مشابهة لما توفره Xdebug ولكن ضبط الأولى يسمى scream.enabled

هذا مفيد خاصةً عندما تقوم بفحص المصدر وتتوقع أن هنالك أخطاء قد تكون كبحت. قم باستخدام Scream بحرص وكأداة فحص مؤقتة. هنالك الكثير من مكتبات PHP التي لن تعمل برمز كبح الأخطاء.

إستثناء الخطأ

يمكن لPHP أن تكون لغة معتمدة على الإستثناءات وتحتاج للقليل من الأسطر لكي تتم عملية التحويل. فعلياً يمكنك رمي أخطاء على شكل إستثناءات باستخدام كلاس ErrorException فهو يقوم بإستمداد كلاس Exception.

هذه ممارسة متعارف عليها يتم تطبيقها من قبل الكثير من أطر العمل الحديثة مثل Symfony و Laravel. في كل من طور التطوير أو طور كشف الأخطاء. كلا الإطارين يقومان بعرض مسلسل مكدس التنفيذ. هنالك عدة حزم متوافرة لإظهار وإدارة الأخطاء بشكل أفضل مثل حزمة Whoops! فهي تأتي بشكل افتراضي مع إطار عمل Laravel ، ويمكن استخدامها أيضا مع أي اطار عمل اخر.

باستخدام رمي الأخطاء على شكل إستثناءات في عملية التطوير يمكنك التعامل بشكل أفضل من النتائج التقليدية، وإذا رأيت إستثناء في عملية التطوير يمكنك تغطيتها في جملة catch مع تعليمات تفصيلية لكيفية التعامل مع هذه الحالة. كل إستثناء تقوم بإمساكه يجعل من برنامجك أكثر ثباتاً وفعالية.

للمزيد من المعلومات والتفاصيل وكيفية استخدام ErrorException مع طريقة التعامل مع الأخطاء ErrorException Class.

الإستثناءات

الإستثناءات هي جزء أساسي من أغلب لغات البرمجة، ولكن يتم التغاضي عنها من قبل مبرمجي PHP. لغات مثل Ruby معتمدة بكثافة على الإستثناءات، لذلك كلما حدث شيء ما خاطئ كطلب HTTP غير ناجح أو استعلام على قاعدة بيانات لم يتم بالشكل الصحيح، أو حتى صورة لم يتم ايجادها، تقوم Ruby (أو gem المستخدم) برمي إستثناء على الشاشة مما يعني أنه هنالك خطأ ما.

لغة PHP نفسها تفتقر لهذا، وعند تنفيذ file_get_contents() تستخدم إرجاع FALSE و تحذير.

العديد من أطر عمل PHP القديمة مثل CodeIgniter سيقوم بإرجاع القيمة false وتدوين رسالة في سجل المتابعة وربما تتيح لك استخدام دالة مثل $this->upload->get_error() لكي تعرف ماذا حدث. تكمن المشكلة هنا أنه يتوجب عليك الذهاب الى مستندات التوثيق ومعرفة ما هي دالة إظهار الخطأ لهذا الكلاس، بدلا من جعلها ظاهرة بشكل أوضح.

مشكلة اخرى هي عندما يقوم كلاس برمي خطأ على الشاشة وإيقاف التنفيذ بشكل تلقائي. عندما تقوم بهذا فأنت تقوم بمنع المطور الآخر أن يقوم بالتعامل مع هذا الخطأ بشكل حيوي تلقائي. الإستثناءت يجب ان ترمى لكي تجعل المطور على دراية بحدوث الخطأ، ثم بعدها يكون لديه الحرية في طريقة التعامل معه. مثال:

<?php
$email = new Fuel\Email;
$email->subject('العنوان');
$email->body('كيف حالك؟');
$email->to('guy@example.com', 'فلان الفلاني');

try
{
    $email->send();
}
catch(Fuel\Email\ValidationFailedException $e)
{
    // فشل في التحقق
}
catch(Fuel\Email\SendingFailedException $e)
{
    // فشل في إرسال البريد الإلكتروني
}
finally
{
    // يتم تنفيذه بغض النظر عن ما إذا تم رمي استثناء ، وقبل ان يواصل التنفيذ
}

إستثناءات SPL

الكلاس العام Exception يتيح القليل من السياقات للتحقق للمبرمج. ولكن لعلاج هذا يمكن إنشاء Exception مخصصة من الكلاس الأصلي:

<?php
class ValidationException extends Exception {}

هذا يعني أنه يمكن إضافة عدة قطع للإمساك بالإستثناءات catch blocks للتعامل مع عدة إستثناءات بشكل مستقل. هذا يؤدي إلى إنشاء الكثير من الإستثناءات الفرعية، يمكن الإبتعاد عن بعضها باستخدام إستثناءات SPL متوفرة في SPL extension.

فمثلا عندما تستخدم الدالة السحرية __call() وتم طلب دالة غير صالحة فبدلاً من رمي إستثناء عادي أو إنشاء استثناء خاص بك يمكنك رمي: throw new BadMethodCallException;.

للأعلى

الحماية

حماية تطبيق الويب

هنالك العديد من الأناس السيئين متعدين لإستغلال تطبيقك. من المهم أن تتخذ التحوطات اللازمة لزيادة حماية التطبيق. لحسن الحظ الأناس الجيدين في المشروع المفتوح لحماية تطبيقات الويب The Open Web Application Security Project OWASP قاموا بتجميع قائمة شاملة لكل مشاكل الحماية، وطرق الحماية منها. الإطلاع على هذا المشروع هو مطلب لاي مطور مدرك لأهمية الحماية. الديل Survive The Deep End: PHP Security كتبت بواسطة باراديك برادي، وهو أحد الأدلة الجيدة لحماية تطبيقات PHP.

تشفير كلمات المرور

دائماً ما يتم بناء تطبيقات PHP تعتمد على تسجيل دخول مستخدمين. فإسم المستخدم وكلمة المرور يتم حفظهما في قاعدة البيانات حتى يتم استخدامهما للتحقق من هوية المستخدم عن تسجيل الدخول.

من المهم أن تقوم بتشفير كلمات السر قبل حفظها. تشفير كلمة المرور هو عملية تشفير أحادية الاتجاه لا يمكن استرجاعها وتنفذ على كلمة مرور المستخدم. يتم انشاء نص محدد المدى لا يمكن فكه. هذا يعني نه ستقوم بمقارنة المحتوى المشفر بمثله للتأكد من أنهما متطابقين، فسوف يكونا كذلك إذا كانا من نفس المصدر، ولكن لا يمكن معرفة المحتوى الأصلي. إذا لم يتم تشفير كلمات المرور وتم الوصول غير المصرح به لقاعدة البيانات، فسيكون كل حسابات المستخدمين قد تم السيطرة عليها. يجب تشفير كلمات المرور بشكل مستقل عن طريق إضافة نص عشوائي ملح لكل كلمة مرور قبل تشفيرها. فذلك يساعد على تجنب هجمات القوامين التي تقوم باستخدام جداول قوس المطر “Rainbow tables” (وهي قائمة عكسية معدة مسبقاً من مجموعة تشفيرات لشفرات كلمات مرور كثيرة الاستخدام). التشفير والتمليح مهم جدا حيث انه بعض المستخدمين يقوم باستخدام نفس كلمة المرور في عدة أجهزة أو خدمات أخرى وقد تكون جودة كلمات مرورهم غير قوية كفاية. لحسن الحظ، PHP قد سهلت هذه المهمة!

تشفير كلمات المرور باستخدام password_hash

في إصدارة PHP 5.5 تم إضافة دالة password_hash(). حالياً تستخدم هذه الدالة لوغاريثمية BCrypt، وهي اللوغاريثمية الأقوى حتى الآن في PHP. ستيم مستقبلاً إضافة الدعم لعدة لوغاريثمات أخرى حسب الحاجة. تم إنشاء مكتبة password_compat لإتاحة توافقية لإصدارات PHP المتقدمة PHP >= 5.3.7.

أدناه سنقوم بتشفير نص، ثم نقوم بمقارنته بنص آخر. بما أن كلا النصين مختلفين، (كلمة مرور صحيحة مقارنة بـ كلمة مرور خاطئة) ستفشل عملية تسجيل الدخول.

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

password_hash() takes care of password salting for you. The salt is stored, along with the algorithm and “cost”, as part of the hash. password_verify() extracts this to determine how to check the password, so you don’t need a separate database field to store your salts.

تصفية البيانات

لا تقم (أبداً) بالوثوق بأي مدخلات إلى التطبيق أو المصدر. دائماً قم بتعقيم ومراجعة هذه المدخلات قبل استخدامها في المصدر. الدالتان filter_var() و filter_input() يمكنهما تعقيم ومراجعة صيغ النصوص ( كيغة البريد الإلكتروني مثلاً).

المدخلات الأجنبية يمكن أن تكون أي شيء: $_GET و $_POST كمدخلات النماذج، بعض القيم من المتغيرات العامة $_SERVER، وأي محتوى طلب HTTP باستخدام fopen('php://input', 'r'). تذكر بأن المدخلات الأجنبية ليست محصورة على المدخلات من النماذج المرسلة من المستخدم. الملفات المرفوعة والمحملة وقيم المتغيرات وبيانات الكوكيز وبيانات تطبيقات وخدمات الويب هي مدخلات أجنبية أيضاً.

طالما يحتمل أن يتم حفظ البيانات والوصول إليها لاحقاً، فهي ما تزال مدخلات أجنبية. في كل مرة تقوم بمعالجة أو دمج أو أدراج بيانات في المصدر، تسأل ما إذا كانت البيانات تم تصفيها بطريقة صحيحة ويمكن الوثوق بها.

بشكل عام البيانات تصفى بشكل مختلف بناءً على كيفيتها. مثلا، عند تمرير مدخلات أجنبية إلى مخرجات صفحة HTML عندها سيتم تنفيذ HTML و JavaScript في الموقع! هذا ما يسمى Cross-Site Scripting (XSS) ويمكن أن يكون هجوماً خطيراً جداً. طريقة لتفادي هذا الهجوم هو عن طريق تعقيم كل البيانات التي ستعرض للمسخدم قبل عرضها في الصفحة عن طريق حذف كل وسوم HTML باستخدام الدالة strip_tags() أو إسقاط الرموز والوسوم باستخدام دوال ذات معنى خاص بعناصر HTML htmlentities() أو htmlspecialchars().

مثال آخر، تمرير قيم لكي يتم تنفيذها على شكل أمر. فهذا يعتبر من أخطر المهددات (وهي فكرة سيئة)، ولكن يمكن استخدام الدالة escapeshellarg() لتعقيم وتنفيذ قيم الأمر.

مثال أخير وهو قبوم مدخلات أجنبية لتحديد ملف ما لعرضه من الملفات. قد تكون هذه ثغرة عن طريق تغيير اسم الملف إلى مسار ملف ما. فستحتاج أن تقوم بإزالة كل من "/" و "../" وما يسمى null bytes وأي رموز من مسار الملف حتى لا يتم إدراج ملفات خاصة أو محمية من العرض أو ملفات حساسة.

التعقيم

التعقيم هو إزالة أو إسقاط أي رموز غير آمنة أو غير مصرح بها من المدخلات الأجنبية.

مثلاً يجب أن تقوم بتعقيم المدخلات الأجنبية قبل إدراج المدخل في HTML أو قبل إدراجها في إستعلام SQL مباشرة. عندما تستخدم ربط القيم باستخدام PDO فستقوم PDO بتعقيم المدخلات تلقائياً.

بعض الأحيان قد تسمح ببعض وسوم HTML الآمن كمدخلات لتتضمن في محتوى صفحة HTML. تطبيق هذا صعب للغاية والبعض يقومون بتجنبه باستخدام صيغة أكثر إحكاماً مثل Markdown أو BBCode بالرغم بأن هنالك مكتبات مخصصة لهذا السبب HTML Purifier.

Sanitization Filters

Unserialization

من الخطير تنفيذ عملية unserialize() من بيانات مدخلة من المستخدم أو من مصادر غير موثوقة. القيام بذلك يسمح للمستخدم أن يقوم بإنشاء كائنات (بمحتوى مسبق التعريف) فسيتم تنفيذها من قبل destructor حتى ولو لم يتم استخدام هذه الكائنات. عندها يجب أن لا يتم عمل unserialize لأي بيانات غير موثوقة.

إذا كان لابد أن تقوم بعمل unserialize لبيانات من مصادر غير موثوقة، عندها قم باستخدام خيار allowed_classes لتحديد أي نوع من الكائنات يمكن أن يتم عمل unserialize عليه. متوفر في إصدارة PHP7 فقط

التحقق

يتم التحقق من البيانات الأجنبية المدخلة كما تتوقع منها أن تكون. مثلا قد تريد ان تتحقق من بريد إلكتروني و رقم هاتف وعمر عندما تقوم بمعالجة طلب تسجيل.

Validation Filters

ملفات الضبط

عندما تقوم بإنشاء ملف ضبط لتطبيقك، فهنالك أفضل الممارسات التي ينصح بتطبيق إحداها:

Register Globals

وهو خيار ضبط في إعدادات PHP **ملاحظة: منذ الإصدارة PHP 5.4.0 تم إزالة خيار ضبط register_globals تمام. وتم إدراج هذا الفصل بمثابة تحذير لكل من ينوي الترقية لتطبيق متوافق مع إصدار قديم.

عندما يتم تفعيل register_globals، فسيقوم بإنشاء عدد من المتغيرات من متغيرات عامة من: $_POST و $_GET و $_REQUEST وجعلها متوفرة في النطاق العام لتطبيقك. ويتسبب بسهولة إلى مشاكل في الحماية فلا يتمكن تطبيقك من التأكد من مصدر البيانات ومن أين تأتي.

مثلاً: $_GET['foo'] سيكون متوفراً بالإسم $foo مما يتسبب في الكتابة على المتغيرات التي لم يتم تعريفها. فإذا كنت تستخدم إصدارة أقل من PHP 5.4.0 قم بالتأكد من أن الخيار register_globals غير مفعل.

تدوين وتقرير الأخطاء

يفيد تدوين الأخطاء في إيجاد المشكل في تطبيقك، ولكن يمكن أن تعرض معلومات حساسة عن هيكلة التطبيق للعالم الخارجي. لحماية تطبيقك بكفاءة عالية من المشاكل التي قد تحدث من مخرجات هذه الرسائل يجب أن يتم ضبط المخدم بطريقة مختلفة في بيئة العمل النهائية عما هو عليه في بيئة التطوير.

بيئة التطوير

لإظهار أي خطأ يحدث أثناء التطوير، قم بضبط الخيارات في php.ini كالاتي:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

إسناد القيمة -1 يقوم بتفعيل ظهور أي خطأ محتمل وأي مستويات يتم إضافتها في المستقبل على إصدارات PHP مستقبلية. الثابت E_ALL سيقوم أيضاً بنفس الشيء ابتداءً من إسدارة PHP 5.4 php.net

مستوى تقرير الخطأ في الثابت E_STRICT قد تم إدراجه في الإصدارة 5.3.0 وهو خيار منفصل من المستوى E_ALL ولكن صار جزءً من E_ALL في الإصدار 5.4.0. ما معنى هذا؟ هذا يعني أنه إذا كنت تريد تقرير كل الأخطاء في الإصدار 5.3 سيتوجب عليك أن تستخدم -1 أو E_ALL | E_STRICT.

تقرير كل الأخطاء المحتملة بناء على إصدارات PHP

بيئة العمل النهائي

لإخفاء الأخطاء في بيئة العمل النهائية قم بضبط الخيارات في php.ini كالآتي:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

بخيارات الضبط هذه في بيئة العلم النهائية، سيتم تدوين الأخطاء في سجل الأخطاء في المخدم ولكن لن يظهر أي منها للتمسخدم. للمزيد من المعلومات عن هذه الخيارات، قم بالإطلاع عليها في دليل PHP:

للأعلى

التجربة والإختبار

يعتبر كتابة الإختبارات التلقائية لكود PHP هو من أفضل الممارسات التي تؤدي لتطبيقات مبنية بصور صحيحة. الإختبارات التلقائية هي أداة ممتازة للتأكد من أن تطبيقك لن يتوقف عندما تقوم بإدراج أو إضافة خواص وعمليات جديدة ويجب عدم إهمالها!

هنالك أنواع معدودة لإدوات الإختبار (أو أطر عمل) متوفرة للغة PHP، وكل نوع يقوم باستخدام طريقة مختلفة كلها تقوم بتحقيق هدف تفادي القيام بعمليات الإختبار اليدوية وتلبية إحتياج فرق تأكيد الجودة، للتأكد من أن التغييرات الجديدة لم تقم بإيقاف عمل تلك الخواص الموجودة مسبقاً.

التطوير الموجه بالاختبار TDD

مقتبس من ويكيبيديا Test Driven Development بالعربية: بالإنجليزية:

تطوير موجه بالاختبار (بالإنجليزية: Test-driven development (TTD))، هو مصطلح يطلق على إحدى عمليات تطوير البرمجيات التي تعتمد على تكرار دورة تطوير قصيرة جداً: بدايةً، يقوم المبرمج بكتابة حالة فحص أوتوماتيكية فاشلة ويجب على حالة الفحص هذه أن تعرّف تحسينا معينا أو وظيفة جديدة. ومن ثم يقوم بكتابة الشيفرة التي تجعل حالة الفحص ناجحة وأخيرا يقوم بإعادة تصنيع الشيفرة كي تتلاءم مع المعايير.

هنالك بضع أنواع مختلفة من الإختبارات التي يمكن تنفيذها على تطبيقك:

إختبار الوحدة

Unit Testing هو طريقة برمجية للتأكد من أن الدوال والكلاسات والعمليات يعملون كما يجب، ابتداءً من نقطة بنائهم وطول فترة وجودهم في دائرة التطوير. وذلك عن طريق إختبار القيم المدخلة والمخرجة من دوال والعمليات لضمان أن المنطق الداخلي يعمل بصورة صحيحة. باستخدام حقن التوابع Dependency Injection وبناء بيانات افتراضية “mock” من كلاسات وصفات، تمكنك من التأكد بأن التوابع مستخدمة بطريقة مثلى في تغطية الإختبار.

عندما تقوم بإنشاء كلاس أو دالة يجب أن تقوم بعمل إختبار وحدة لكل وظيفة يجب أن يقوم بعملها. ابتداءً من انه يجب ان تتأكد من فشل الإختبار عندما تقوم باستخدام قيم خاطئة قم التأكد من نجاحه عند استخدام قيم صحيحة. فهذا سيساعد بانه عندما تقوم بعمل تغييرات أو تطويرات على هذه الكلاس أو الدالة لاحقاً في دورة التطوير بإن الوظائف القديمة لن تتوقف وستعمل معاً كما هو متوقع. البديل الوحيد قد يكون الدالة var_dump() في ملف test.php حيق لا يمكن بناء أي تطبيق صغير كان أو كبير.

الإستخدام الآخر لوحدات الإختبار هو المشاركة في مشاريع مفتوحة المصدر. إذا كان يمكنك كتابة إختبار يظهر فشل وظيفة معينة، عندها قم بإصلاح هذا الخطأ ثم اظهر نجاح الإختبار، فعندها التعديلات يتم قبولها بسرعة. إذا كان لديك مشروع يقبل طلبات الدمج pull requests عندها يجب اقتراح اختبار الوحداك كمطلب أساسي.

PHPUnit هو إطار عمل إختباري لكتابة الإختبارات لتطبيقات PHP ولكن هنالك بضع بدائل أخرى:

الإختبارات المتكاملة

من ويكيبيديا بالإنجليزية:

الإختبارات المتكاملة (يطلق عليها التكامل والإختبار بعض الأحيان ويرمز لها بالإختصار “I&T”) وهو في مرحلة إختبار البرنامج بحيث يتم دمج دول البرنامج وإختبارها كمجموعة متكاملة. وتحدث بعد إختبار الوحدات وقبل إختبار التحقق. الإختبارات المتكاملة تأخذ كمعطيات الدوال التي تم تطبيق إختبار وحدة عليها ثم جمعها معاً في تجميعه تتوائم مع خطة تجميع الوظائف سوياً ثم إخراج مخرجات جاهزة لإختبار النظام.

العديد من الأدوات التي تستخدم في اختبار الوحدات يمكن استخدامها في الإختبارات المتكاملة فهما يتشابهان في عديد من المفاهيم الأساسية.

الإختبار الوظيفي

يعرف أيضاً بإختبار القبول، يعتمد الإختبار الوظيفي على استخدام أدوات لإنشاء اختبارات تلقائية تقوم فعلياً باستخدام البرنامج بدلاً من التحقق من كل وحدة على حدة تقوم بالوظيفة الصحيحة، وأن هذه الوحدات تقوم بالتخاطب والعمل سوياً بشكل صحيح. هذه الأدوات تعمل باستخدام بيانات حقيقية وتقوم بمحاكاة مستخدمين حقيقيين للبرنامج.

أدوات الإختبار الوظيفي

التطوير الموجه بالسلوك BDD

Behavior Driven Development. هنالك نوعان من هذا النمط: SpecBDD و StoryBDD. SpecBDD: يركز على السلوك التقني للكود. StoryBDD: يركز على الخواص والتفاعلات الفعلية. هنالك إطارا عمل لكلا النوعين.

باستخدام StoryBDD يمكن كتابة قصص مقروءة تقوم بوصف سلوك التطبيق. هذه القسس يمكن ان تقوم بتنفيذ إختبارات حقيقية للتطبيق. إطار العمل المستخدم في تطبيقات PHP للنوع StoryBDD Behat وتم استيحائه من مشروع Cucumber في Ruby.

باستخدام SpecBDD يمكن كتابة مواصفات تصف كيف يجب أن يكون سلوك الكود الفعلي. فبدلاً من إختبار الدالة أو العملية، فأنت تقوم بوصف سلوك الدالة أو العملية. يوجد في PHP إطار عمل PHPSpec لنفس الغرض. هذا الإطار تم استيحائه من مشروع RSpec project في Ruby.

روابط BDD

أدوات الإختبار المتكاملة

بجانب اطر العمل الإختبارية والسلوكية، هنالك عدد من الأطر العامة والمكتبات المساعدة المفيدة لأي وسيلة مفضلة.

روابط الأدوات

للأعلى

السيرفرات والنشر

يمكن لتطبيقات PHP ان تنشر وتعمل على سيرفرات عمل نهائية بعدة طرق. PHP applications can be deployed and run on production web servers in a number of ways.

Platform as a Service (PaaS)

أو المنصة كخدمة وتوفر النظام ومعمارية الشبكة اللازمة لتشغيل تطبيق PHP على الويب. هذا يعني انه لا يوجد ضبط لتشغيل تطبيقات PHP أو أطر عمل PHP.

مؤخراً صار PaaS طريقة مشهورة لنشر واستضافة وتطوير تطبيقات PHP بكل الأحجام. يمكن ايجاد قائمة ببعض هذه المنصات PHP PaaS “Platform as a Service” providers في فصل المصادر resources section.

السيرفرات المخصصة أو الإفتراضية

Virtual or Dedicated Servers إذا كنت مرتاح بالعمل كمشرف نظام أو مهتم بتعلمه فالسيرفرات المخصصة والإفتراضية تتيح لك مطلق الحرية والتحكم في بيئة التطبيق النهائية.

nginx و PHP-FPM

تقترن PHP مع nginx باستخدام البنية المدمجة FastCGI Process Manager (FPM) وهو سيرفر خفيف ذو كفاءة عالية. ويقوم باستخدام ذاكرة أقل من نظيره Apache ويقوم بإدارة عدد أكبر من الطلبات المتزامنة. وهو مناسب في السيرفرات الإفتراضية بحيث لا يوجد الكثير من الذاكرة لإهدارها.

Apache و PHP

تملك PHP و Apache تاريخاً طويلاً معاً. فـ Apache واسع الإنتشاء ولديه العديد من الوحدات لتمديد وظائفه. فهو خيار مشهور لكل السيرفرات المشتركة وطريقة سهلة لتشغيل إطر عمل PHP وتطبيقات المصدر المفتوح مثل WordPress. للأسف Apache يستخدم الكثير من المصادر بعكس Nginx ولا يقوم بإدراة العديد من الزوار في نفس الزمن.

يوجد عدة طرق لضبط PHP للعمل مع Apache. فالطريقة الأكثر شيوعاً وأسهلها هي تنصيب prefork MPM مع الوحدة mod_php5. بينما هو ليس بخيار جيد في استهلاك الذاكرة ولكنه الأبسط للإستخدام والتشغيل. ويعتبر أفضل خيار إذا كنت لا تنوي التعمق في إدارة السيرفرات. لاحظ أنه عند استخدام mod_php5 فإنه يجب عليك استخدام prefork MPM.

عوضاً عن ذلك، إذا كنت تريد أن تعتصر المزيد من كفاءة الأداء والإستقرار من Apache عندها يمكن الإستفادة من نفس نظام FPM مثل Nginx وتشغيل worker MPM أو event MPM مع الوحدة mod_fastcgi أو mod_fcgid. هذا الضبط سيكون ذا تأثير واضح وكبير على الذاكرة وسرعة ملحوظة ولكن هنالك جهد أكبر للتنصيب.

إذا كنت تستخدم Apache 2.4 أو احدث فيمكنك استخدام mod_proxy_fcgi لكي تزيد من كفاءة الأداء وهو سهل التنصيب والضبط.

السيرفرات المشتركة

Shared Servers يوجد العديد من السيرفرات المشتركة، والفضل يعود لشهرة PHP نفسها. من الصعب إيجاد استضافة بدون PHP ولكن تأكد من أنها أحدث إصدارة. السيرفرات المشتركة تتيح لك وللمطورين الآخرين بنشر مواقع على جهاز واحد. على الرغم من أنها سلعة رخيصة، ألا أنه لا تعلم ما قد يحدث من “جيرانك” على نفس السيرفر من أذى ومشاكل مشتركة من بطئ السيرفر وفتح ثغرات أمنية تهددك أيضاً معهم وهو من أخطر ما قد يحدث. فإذا كانت ميزانية المشروع تتيح لك أن لا تستأجر في استضافة مشتركة فعندها لا تستخدم السيرفرات المشتركة.

الرجاء التأكد من أن السيرفرات المشتركة توفر آخر إصدارة من PHP، قم بمعرفة إسدارات PHP.

بناء ونشر التطبيقات

إذا وجدت نفسك تقوم بتغيير مخططات قاعدة البيانات يدوياً أو تشغيل الإختبارات يدوياً قبل تحديث الملفات (يدوياً) فعليك التفكير مرة اخرى. في كل مرة تقوم فيها بتحديث تطبيقك إلى نسخة أحدث يدوياً، هنالك فرصة لحدوث خطأ. سواء كنت تتعامل مع وسيلة سهلة للتحديث و أو عملية بناء شاملة أو حتى خطة تكامل مستمرة Continues Integration. عندها البناء التقائي هو صديقك!

من بين المهام التي قد تحتاج أن تكون بصورة تلقائية:

أدوات النشر

يمكن وصف أدوات النشر بأنها مجموعة من الأوامر الكتابية تقوم بإدارة مهام نشر البرنامج. أدوات التطوير لا تعتبر جزء من البرنامج فهي تمثل البرنامج من الخارج فقط.

هنالك العديد من الأدوات المتوفر ومفتوحة المصدر تساعدك للبناء والنشر التلقائي. بعضها كتب بلغة PHP وبعضها لا. وهذا ليس سبب يدعوك لعدم استخدامها، إذا كانت تناسب ما تقوم به. بعض الأمثلة:

Phing تمكنك من التحكم في عملية تحزيم وإختبار ونشر تطبيقك في إطار استخدام ملف XML. Phing (وهو مبني على Apache Ant) يتيح مجموعة مهام عادة ما تستخدم لتنصيب أو تحديث تطبيقات الويب ويمكن تمديدها للمزيد من المهام. وتكتب بلغة PHP. فهي أداة فعالة وثابتة وموجودة منذ مدة طويلة، ولكن يمكن النظر لهذه الأداة بأنها قديمة بعض الشيء بسبب الطريقة التي تتعامل بها مع ملفات الضبط XML.

Capistrano وهو نظام يخص المطورين المتوسطين - المتقدمين لتنفيذ أوامر في بنائية متكررة واحدة منها أو أكثر في أجهزة عن بعد. تم ضبطه لمشر تطبيقات Ruby on Rails، ولكن يمكن بنجاح نشر تطبيقات وأنظمة PHP باستخدامه. الإستخدام الناجح لـ Capistrano يعتمد على خلفية عمل باستخدام Ruby و Rake. نشر ديف جاردنرز مقالة بعنوان PHP Deployment with Capistrano فهي بداية جيدة لمطوري PHP المهتمين باستخدام Capistrano.

Rocketeer إستمدت فلسفتها من إطار عمل لارافيل Laravel. يهدف لأن يكون سريع وسهل الإستخدام مع إفتراضيات ذكية. يقوم بالعمل على عدة سيرفرات عدة منصات و نشر كلي وجزئي يمكن تنفيذهم على التوازي. كل شيء في هذه الأداة يمكن أن يتم تحويله أو تمديده وكل شيء مكتوب بلغة PHP.

Deployer وهي أداة نشر كتبت بلغة PHP فهي بسيطة وعملية. تقوم بتنفيذ مهام على التوازي والنشر الجزئي مع التنسيق ما بين السيرفرات. وصفات لمهام مكررة لأطر عمل مثل Symfony و Laravel و Zend Framework و Yii. مقالة يونس رفيع Easy Deployment of PHP Applications with Deployer تعتبر درس ممتاز لنشر التطبيقات باستخدام هذه الأداة.

Magallanes وهي أداة أخرى مكتوبة بلغة PHP مع ضبط بسيط في ملفات YAML. تدعم بيئات وسيرفرات متعددة و النشر الجزئية ومدمج معها بعض المهام المضمنة والتي تتيح لك النفوذ مع أدوات وأطر عمل مشهورة.

المزيد من القراءة

تموين السيرفر

إدارة وضبط السيرفر هي مهمة شاقة خصوصاً عندما تكون موجهة على عدة سيرفرات. هنالك أدوات تتعامل مع هذا الوضع وأتمتتة البنية التحتية للتأكد من أنه لديك سيرفرات تم ضبطها بطريقة مثلى. عادة ما تتكامل مع استضافات سحابية مثل Amazon Web Service و Heroku و DigitalOcean وغيرها. لإدارة الوحدات التي تقوم بتنسيق التطبيق بشكل أبسط.

Ansible وهي أداة لإدارة البنية التحتية باستخدام ملفات YAML. من السهل البدء بالاستخدام وإدارة وتنسيق تطبيقات كبيرة ومعقدة. هنالك API لإدارة الوحدات السحابية باستخدام بضع أدوات.

Puppet وهي أداة تستخدم ملفات ولغة خاصة بها لإدارة وضبط السيرفرات. يمكن استخدامها في Master/Client وايضا يمكن استخدامها في طور “master-less”. في طور master/client يقوم Client باقتراع الماستر المركزي لطلب ضبط جديد في وحدات زمنية متفرقة لتحديث نفسها إذا لزم الأمر. وفي طور master-less يتم إرسال التحديثات إلى النقاط.

Chef وهو إطار عمل قوي مبني على Ruby يمكن من بناء بيئة السيرفر بالكامل أو صناديق افتراضية وترتبط بشكل جيد باستخدام خدمة في Amazon Web Services تسمى OpsWorks.

المزيد من القراءة:

الربط المستمر Continuous Integration

الربط المستمر هو ممارة في تطوير البرامج حين يكون هنالك أعضاء فريق يقومون بربط أعمالهم بشكل متواصل، عادة ما يكون كل شخص بالربط على الأقل مرة في اليوم - مما يعني العديد من الربط في خلال اليوم الواحد. العديد من الفرق ترى أن استخدام هذه الممارسة يؤدي إلى خفض فعلي لمشاكل الربط وتتيح للفرق أن تقوم بتطوير برامج متماسكة بسرعة.

– مارتين فولر

هنالك طرق مختلفة لتطبيق الربط المستمر في PHP. يعد Travis CI من أفضلهم فهو يقوم بتحقيق الربط المتواصل بشكل حتى للمشاري الصغيرة. وهو عبارة عن خدمة مستضافة لمجتمع المصدر المفتوح. وترتبط مع GitHub وتوفر دعم من الدرجة الأولى للعديد من اللغات من ضمنها PHP.

المزيد من القراءة:

للأعلى

الإفتراضية

تشغيل التطبيق على بيئات مختلفة من تلك التي في بيئة التطوير والعمل النهائي تسبب في ظهور أخطاء غريبة. ومن الصعب إدارة البيئات المختلفة محدثة وتعمل بنفس الإصدارات لكل المكتبات عندما يكون التطوير في فريق.

إذا كنت تقوم بالتطوير على Windows وتقوم بالنشر على Linux أو أي شيء غير ويندوز أو يتم التطوير في فريق يجب عليك استخدام آلات افتراضية. قد يكون غريباً بعض الشء ولكن بجانب الإفتراضية المعروفة للبيئة مثل برامج VMWare و VirtualBox هنالك أدوات إضافية تساعد في إنشاء بيئة إقتراضية بخطوات سهلة وبسيطة.

Vagrant

يساعد Vagrant على بناء صناديق افتراضية فوق ما يعرف بالبيئة الإفتراضية Virtual Environment ويتم ضبط هذه البيئة بناءً على ملف ضبط واحد. هذه الصناديق يمكن تنصيبها بشكل يدوي أو باستخدام أدوات “التموين” مثل Puppet أو Chef. تموين الصناديق هو طريقة فعالة لضمان أن جميع الصناديق تعمل بشكل متساوي وتزيل كل التعقيدات لتنصيب الأوامر. ويمكن أيضاً تدمير الصناديق وإعادة إنشائها بدون أي خطوات يدوية مما يجعل من السهل انشاء نسخ جديدة.

يقوم Vagrant بانشاء مجلدات لكي تقوم بمشاركة الكود مابين الجهاز الحلي والجهاز الإفتراضي مما يعني أنه يمكن إنشاء وتعديل ملفاتك وتنفيذها من الجهاز الحالي من دون الحاجة للدخول للجهاز الإفتراضي.

القليل من المساعدة

إذا كنت تريد المساعدة للبدء في استخدام Vagrant هنالك بعض الخدام التي قد تكون مفيدة لك:

Docker

Docker هو بديل اخف ثقلاً من استخدام جهاز كامل في بيئة تشغيل افتراضية وذلك لأنه يعتمد على ما يسمى الحاويات “Containers”. الحاوية هي عبارة عن بناء من مجموعة أجزاء تقوم في ابسط صورها بوظيفة واحدة فقط، كأن تقوم بتشغيل خادم ويب. الصورة “Image” وهي الحزمة التي تقوم باستخدامها لبناء حاوية. وDocker لديه مستودع مليء بالصور المبنية مسبقاً. تطبيقاً على حزمة بيئة التطوير LAMP سيكون هناك ثلاثة حاويات: حاوية الخادم، وحاوية مترجم PHP وحاوية قاعدة البيانات MySQL. كما هو الحال مع Vagrant يمكنك تحديد الملفات المسموحة بالمشاركة لكي تستخدمها الحاوية. بالإمكان ان تقوم بتوليد حاويات باستخدام سطر الأوامر (انظر المثال ادناه) أو لتسهيل عملية التطوير والصيانة يمكن انشاء ملف docker-compose.yml مع ملفات مشروعك، وتوصف فيه المتطلبات وكيفية تواصل الحاويات فيما بينها. يساعد Docker في تطوير عدة مواقع مع الحفاظ على العزلة التامة التي يصعب الحصول عليها بدون انشاء أجهزة افتراضية لكل موقع على حدة، ولكن هذا قد يتعارض مع مصادر الجهاز المتوفر، كالمساحة المتوفرة في القرص الصلب أو الزمن المستهلك في إبقاء كل الأجهزة محدثة. فالكفاءة تتمثل أيضا في انه سهل الاستخدام ويتم حفظ نسخة واحدة من كل صورة، بالإضافة لذلك ان الحاويات تستهلك كمية قليلة من ذاكرة النظام وتقوم بمشاركة لب نظام التشغيل الحالي. إذا يمكن انشاء وتشغيل عدد أكبر من الخوادم في نفس الوقت. وعملية انشاء وتشغيل وإيقاف وتدمير الحاويات يتطلب بضع ثوان فقط، ولا داعي لانتظار زمن اقلاع لنظام تشغيل.

مثال: تنفيذ تطبيق PHP في Docker

بعد تنصيب dockerعلى الجهاز، يمكن تشغيل خادم ويب باستخدام امر واحد. الامر التالي سيقوم بتحميل مخدم Apache كامل الوظائف مع اخر نسخة PHP واسناد المسار المحلي /path/to/your/php/files كالدليل الجذر له، ويمكن استعراض الصفحات بزيارة http://localhost:8080.

docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apache

بعد تنفيذ docker run عندها الحاوية تكون إنشئت وقيد العمل في الخلفية. إذا كنت تريد إيقاف أو تشغيل الحاوية مرة أخرى يمكن استخدام الإسم المقرون وببساطة تنفيذ docker stop my-php-webserver و docker start my-php-webserver بدون إدراج الأوامر المذكورة أعلاه مرة أخرى.

إعرف المزيد عن Docker

الأمر أعلاه يوضح كيفية انشاء خادم بصورة سريعة. هنالك الكثير ما يمكن تطبيقه، وهنالك آلاف الصور التي يمكن استخدامها على Docker Hub. قم بإمضاء بعض الوقت في تعلم وفهم المصطلحات وقراءة الدليل Docker User Guide لكي تستفيد بكل ما هو متاح. لا تقم ابدا بتنفيذ أي أوامر قمت بتحميلها او وجدتها في مكان ما بدون التأكد من امانها، الصور غير الرسمية لبعض التطبيقات قد لا تحتوي على اخر الرقع والتحديثات الأمنية، فان كان لديك شك، قم باستخدام فقط المستودعات الرسمية. موقع PHPDocker.io يقوم بتوليد كل الملفات التي تحتاجها للاستخدام في حزمة LAMP/LEMP مضمنة بنسخة PHP من اختيارك مع اللاحقات.

للأعلى

التخزين المؤقت

تعتبر PHP سريعة في نفسها، ولكن بعض الإختناقات قد تحدث عندما تقوم بعمل إتصالات خارجية أو إدراج ملفات أو غيرها. لحسن الحظ هنالك عدة إدوات متوفرة لتقوم بتسريع أجزاء محددة في التطبيقات أو تقليل عدد مرات استهلاك تنفيذ هذه المهام في كل مرة.

التخزين المؤقت لشفرة التشغيل

عندما يتم تنفيذ ملف PHP يجب أن يتم تجميع الملف وتحويله لشيفرة تشغير opcodes (وهي لغة أوامر للمعالج). إذا لم يتغير المصدر البرمجي فإن الشيفرة المصدرية ستظل كما هي، إذاً هذه الخطوة التجميعية ستكون مضيعة لمصدر معالجة.

التخزين المؤقت للشيفرة التشغيلية تمنع تكرار التجميعات عبر حفظ الشيفرة في الذاكرة واستخدامها عند الإستدعاء. فهي فعلياً ستقوم بفحص التوقيع أو زمن التعديل للملف أولاً للتأكد من أي تعديلات قد طرأت عليه.

من الواضح أن تخزين شيفرة التشغيل سيؤثر بشكل ايجابي على سرعة التطبيق. منذ الإصدارة PHP 5.5 هنالك محرك مدمج واحد وهو Zend OPcache. باختلاف توزيعة PHP لديك عادة ما تكون هذه الخاصية مفعلة بشكل افتراضي - ابحث عن opcache.enable في مخرجات الدالة phpinfo() للتأكد. للإصدارات القديمة هنالك لاحقة PECL يمكن استخدامها.

إقرأ المزيد عن التخزين المؤقت للشيفرة البرمجية opcode caches:

تخزين الكائنات

هنالك العديد من الأحيان يكون فيها من المفيد القيام بتخزين كائنات بشكل مؤقت في مصدرك مثل البيانات التي من الصعب الحصول عليها أو إتصالات قاعدة البيانات عندما تكون النتائج من الإستعلامات نادراً ما تتغير. يمكن استخدام التخزين المؤقت للكائنات لمخزن بيانات بعد استرجاعها ثم وضعها مباشرة من المخزن المؤقت للإستعلامات القادمة يمكن الحصول عندها على تطور ملحوظ في الأداء بجانب تقليل الحمل على سيرفرات قاعدة البيانات.

العديد من حلول المخازن المؤقتة المشهورة من النوع bytecode تتيح إمكانية تخزين بيانات أخرى أيضاً مما يعني سبب آخر يجعلك تقوم بالإستفادة منها. يتيح كل من APCuو XCache و WinCache يقوموا بتوفير API لحفظ البيانات من كود PHP لمخازن بياناتهم على الذاكرة.

أشهر أنظمة تخزين الكائنات هما APCu و memcached. APCu هو خيار ممتاز لتخزين الكائنات وتضم واجهة API سهلة الإستخدام لإضافة بياناتك الخاصة إلى الذاكرة وسهل التنصيب والإستخدام. التقييد الوحيدهو أنه مقيد بالسيرفر المنصب عليه. Memcached يتم تنصيبه كخدمة منفصلة ويمكن أن يتم الوصول إليه عبر الشبكة، مما يعني أنه يمكن تخزين الكائنات في مخازن بيانات فائقة السرعة في أماكن مركزية والعديد من الأنظمة تتمكن من الإستخدام منها.

لاحظ أنه عند تشغيل PHP في طور Fast-CGI داخل سيرفر الويب، عندها كل عمليات PHP سيكون لديها المخزن المؤقت الخاص بها، مثلاً بيانات APCu سيتكون منفصلة تماماً ولن يتم مشاركتها مع العمليات الأخرى. في هذه الحالات قد تضطر لإستخدام memcached عوضاً عن الأول. نظراً لانه غير مقترن بأي عملية PHP.

في التواصل الشبكي يحصل APCu على أعلى أداء نظراً للطريقة التي تتيح سرعة الوصول، ولكن بالمقابل memcached قابلة للتطوير بشكل أسرع وأكبر. إذا كنت لا تتوقع انه سيكون لديك عدة سيرفرات تخدم تطبيقك عندها لن تستفيد من خواص memcached، إذاً APCu هو أفضل خيار لتخزين الكائنات.

أمثلة إستخدام منطقية لـ APCu:

<?php
// قم بالتأكد من أن هنالك بيانات مخزنة مؤقتاً باسم 'expensive_data'
$data = apc_fetch('expensive_data');
if ($data === false) {
    // البيانات غير موجودة في المخزن المؤقت، قم بحفظ النتائج المكلفة لإستخدام لاحق
    apc_add('expensive_data', $data = get_expensive_data());
}

print_r($data);

لاحظ أنه قبل إصدارة PHP 5.5 تتيح APC كل من مخازن كائنات ومخازن bytecode. APCu هو مشروع لجلب مخازن الكائنات إلى PHP 5.5+ لأنه الآن PHP لديها مخازن bytecode مدمج (OPcache).

تعرف المزيد عن أنظمة تخزين الكائنات المشهورة:

للأعلى

توثيق المصدر البرمجي

PHPDoc

يعتبر PHPDoc هو معيار قياسي غير رسمي لتوثيق كود PHP. هنالك العديد من والوسوم المختلفة tags. القائمة الكاملة لهذه الوسوم مع الأمثلة موجودة في الدليل الرسمي PHPDoc manual.

أدناه مثال لكيفية توثيق كلاس بداخله بضع عمليات:

<?php
/**
 * @author A Name <a.name@example.com>
 * @link http://www.phpdoc.org/docs/latest/index.html
 */
class DateTimeHelper
{
    /**
     * @param mixed $anything Anything that we can convert to a \DateTime object
     *
     * @throws \InvalidArgumentException
     *
     * @return \DateTime
     */
    public function dateTimeFromAnything($anything)
    {
        $type = gettype($anything);

        switch ($type) {
            // Some code that tries to return a \DateTime object
        }

        throw new \InvalidArgumentException(
            "Failed Converting param of type '{$type}' to DateTime object"
        );
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     *
     * @return void
     */
    public function printISO8601Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('c');
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     */
    public function printRFC2822Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('r');
    }
}

هذا التوثيق للكلاس بأكمله به وسم @author و @link. فوسم @author يستخدم لتوثيق محرر الكود ويمكن تكراره لتوثيق عده محررين. والوسم @link يستخدم لربط موقع يوضح العلاقة ما بين الموقع والكود.

بداخل الكلاس في العملية الأولى بها وسم @param لتوثيق نو واسم ووصف المعطى الممر للعملية. بالإضافة لوسمي @return و @throws لتوثيق نوع الإرجاع وتوضيح أي إستثناءات يمكن رميها.

العملية الثانية والثالثة متماثلتين فلديهما الوسم @param كما في العملية الأولى. الإختلاف المهم ما بين العملية الثانية والثالثة هي وجود/عدم وجودة وسم@return. @return void تعدي تنبيهنا بأنه لا يوجد مخرجات، وأيضاً إهمال كتابة @return void يعطي نفس النتيجة (لا يوجد مرتجعات من العملية).

للأعلى

المصادر

من المصادر الأصلية

أشخاص تتابعهم

من الصعب إيجاد أعضاء في مجتمع PHP ذوي خبرة وعلم كبيرين في البداية. يمكن إيجاد قائمة جيدة من أعضاء مجتمع PHP وحساباتهم تويتر في:

الإشراف والتوجيه

مزودي PHP PaaS

للإطلاع على أي نسخة تعمل هذه الخدمات قم بالذهاب الى هنا PHP Versions.

أطر العمل

بدلاً من إعادة اختراع العجلة ، العديد من مطوري PHP يستخدمون إطر عمل لبنا تطبيقاتهم. أطر العمل تجرد العديد من المستويات الدنيا ومشاكلها وتعقيداتها وتقوم بتوفير واجهة مفيدة وسهلة الإستخدام لإنجاز المهام المعتادة.

لا يتوجب عليك استخدام إطار عمل لكل مشروع. فبعض الأحيان تكون الطريقة التقليدية للكتابة هي الوسيلة المثلى للعمل، ولكن إذا كنت تريد إطار عمل، عندها هنالك ثلاث أنواع متوفرة:

فأطر العمل الصغير هي عبارة عن ربط لطلبات HTTP إلى متحكمات ودوال وغيرها وعادة ما تكون سهلة وسريعة جداً وبعض الأحيان تكون مع بضع مكتبات للمساعدة في التطوير كمساعدات للإتصال والعمل علىقاعدة البيانات وما شابهها. عادة ما تستخدم لبناء خدمات HTTP عن بعد.

العديد من أطر العمل تضيف عدد من المميزات على قالب أطر العمل الصغير وهذه الأطر هي أطر العمل الكاملة. وعادة ما يكون مدمجاً فيها حزم ORM و مصداقة وتحقق.

أطر عمل المكونات عادة ما تكون تجميعة من مكتبات مخصصة لعمل وظيفة محددة. يمكن استخدام بضع منها سوياً لعمل أطار عمل صغير أو كامل.

المكونات

كما هو مذكور أعلاه “المكونات” هي هدف معروف لإنشاء ونشر وزرع ومشاركة الكود. العديد من حاويات المكونات موجودة بالفعل اشهرها:

كلاهما حاويات لديها أدوات سطور أوامر تساعدك لتنصيب وتحديث وترقية المكونات وقد تم شرحها في فصل Dependency Management.

هنالك أيضاً مكونات خاصة بأطر عمل ومزودين والتي لا تتيح أي أطار عمل على الإطلاق. هذه المشاريع توفع مصادر اخرى من الحزم المتطابقة مع القليل من الإعتمادية على حزم أخرى أو أطر عمل معينة.

مثلاً يمك استخدام FuelPHP Validation package “حزمة للتحقق” من دون الحاجة لاستخدام أطار عمل FuelPHP.

Illuminate components قريباً سيتم عزله من إطار عمل Laravel. حتى الآن أفضل المكونات المعزولة من إطار عمل Laravel قد تم سردها أعلاه.

مصادر أخرى مفيدة

أوراق غش

المزيد من أفضل الممارسات

اخبار PHP ومجتمعات تطوير الويب

يمكن ان تشترك في القوائم البريدية الأسبوعية لتبقى مواكبا بالمكتبات والاخبار والاحداث الجديدة، والتنبيهات العامة، كما هنالك مصادر يتم نشرها بين الحين والأخر:

عالم PHP

دروس فيديو

قنوات YouTube

دروس فيديو مدفوعة

كتب

هنالك العديد من كتب PHP للأسف بات بعضها قديم جداً وغير دقيق. بالتحديد الكتب التي تتحدث عن “PHP 6” فهو إصدار غير موجود ولن يكون موجوداً اصلاً. لأن الإصدار الكبير بعد PHP 5.6 هو “PHP7” partly because of this.

هذا الفصل يهدف إلى أن يكون مصدر للكتب الموصى بها في تطوير PHP بشكل عام. إذا كان لديك كتاب تريد إضافته قم بنشر طلب دمج أو Pull-request، وسيتم مراجعة محتواه.

كتب مجانية

كتب مدفوعة

للأعلى

المجتمع

مجمع PHP هو مجتمع متنوع وكبير، وأعضائها جاهزون ومستعدون لخدمة مبرمجي PHP الجدد. تشجع للإنضمام إلى مجموعة مستخدمي PHP وحضور مؤتمرات PHP لتعلم الكثير عن أفضل الممارسات موجودة. يمكنك التواجد في قناة #phpc في IRC على irc.freenode.com وقم بمتابعة حساب تويتر @phpc. قم بالذهاب إلى هناك وقم بمقابلة مطورين جدد وتعلم مواضيع جديدة، وفوق كل هذا قم تكوين صداقات! مصادر لمجتمعات اخرى تضم مجموعة Google+ PHP Programmer community و StackOverflow.

قراءة المزيد عن الأحداث في رزنامة أحداث PHP

مجموعات مستخدمي PHP

إذا كنت تعيش في مدينة كبيرة، هنالك العديد من مجموعات PHP حولك. يمكنك ببساطة إيجاد مجموعات محلية PUG في قائمة usergroup-list at php.net والتي بنيت على على PHP.ug. مصدر بديل قد يكون Meetup.com أو قم بالبحث عن php user group near me باستخدام محرك البحث المفضل لديك، مثلا Google. إذا كنت تعيش في مدينة صغيرة قد لا يكون هنالك مجموعة محلية، فلم لا تقوم ببدء بواحدة!

من الجدير بالذكر مجموعتين عالمية: NomadPHP و PHPWomen. NomadPHP تقوم بتوفير إجتماع مستخدمين أونلاين مرتين شهرياً يقوم بالتحدث فيها بضع مشاهير مجتمع PHP. PHPWomen هي مجموعة غير حصرية تستهدف النساء في عالم PHP. العضوية مفتوحة للجميع ولكل من يدعم المجتمع. وتوفر شبكة من الدعم والإرشاد والتعلم وبشكل عام يروجون لإنشاء بيئة “نسائية” إحترافية.

قراءة المزيد عن المجموعات في ويكي PHP

مؤتمرات PHP

مجتمع PHP يقوم باستضافة مؤتمرات عالمية ومحلية في العديد من المدن حول العالم. عادة ما يتحدث فيها اعضاء مجتمع PHP المعروفين وهي فرصة ممتازة للتعلم مباشرة من قائدي المنشأة.

قم بإيجاد مؤتمر PHP

فيلة PHP

ElePHPant وهي جالبة الحظ لكل مشروع PHP مع الفيل في تصميمها. تم تصميمها في الأساس لمشروع PHP في عام 1998 من قبل فاينسنت بونتير وهو الأب لكل فيلة PHP حول العالم بعد عشر سنوات تم تجسيمها كدمية. الآن فيلة PHP موجودة في العديد من مؤتمرات PHP ويمتلكها العديد من مطوري PHP في أجهزتهم لمجرد المرح والإيحاء.

مقابلة مع فاينسنت بونتير