اذهب إلى المحتوى

سارة محمد2

الأعضاء
  • المساهمات

    86
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • عدد الأيام التي تصدر بها

    2

كل منشورات العضو سارة محمد2

  1. الملحِّن أو composer (كومبوزر) هو مدير حزم/اعتماديات PHP، يمكن استخدامه لتثبيت وتتبع وتحديث اعتماديات مشروعك، ويهتم أيضًا بالتحميل التلقائي للاعتماديات التي يعتمد عليها تطبيقك مما يجعلك تستخدم الاعتمادية بسهولة داخل مشروعك دون القلق بشأن تضمينها في بداية كل ملف. يعدّ المُلحِّن composer أشهر مدير اعتمادية، وهو يماثل npm في Node وpip في Python وNuGet في ‎.NET. توجد اعتماديات مشروعك ضمن ملف composer.json الموجود عادةً في جذر المشروع، ويحمل هذا الملف معلومات الإصدارات المطلوبة للحزم في مرحلتي تطوير المشروع وإنتاجه، يمكن تعديل هذا الملف بشكلٍ يدوي باستخدام أي محرر نصوص أو تلقائيًا باستخدام أوامر سطر الأوامر مثل composer require <package>‎ أو composer require-dev <package>‎. تحتاج لبدء استخدام المُحلِّن في مشروعك إلى إنشاء ملف composer.json، يمكنك إنشاؤه يدويًا أو بتنفيذ الأمر composer init وبعد تنفيذ هذا الأمر في الطرفية ستسألك عن بعض المعلومات الأساسية لمشروعك: اسم الحزمة (vendor/package مثل laravel/laravel) - الوصف وهو اختياري - الكاتب وبعض المعلومات الأخرى مثل حد الاستقرار الأدنى والرخصة والحزم المطلوبة. يصف المفتاح require في ملف composer.json الحزم التي يعتمد عليها مشروعك، ويأخذ كائنًا يربط اسم الحزمة (مثل monolog/monolog) بقيود الإصدار (مثل ‎1.0.*‎). { "require": { "composer/composer": "1.2.*" } } تحتاج لتثبيت الاعتماديات المعرّفة إلى تنفيذ الأمر composer install ثم يجد composer الحزم المعرّفة المطابقة لقيد version الموجود ويحمّلها في مجلد vendor، هذا اصطلاح لوضع شيفرة من طرف ثالث في مجلد اسمه vendor. ستلاحظ أنّ الأمر install يُنشئ الملف composer.lock تلقائيًا، يستخدم هذا الملف لتتبع الإصدارات المنصَّبة حاليًا وحالة اعتمادياتك، وسيثبّت تنفيذ الأمر composer install الحزم إلى الحالة المخزّنة في ملف القفل. المعامل تفاصيل license يعرّف نوع الرخصة التي تريد استخدامها في المشروع authors يعرّف كاتبي المشروع وتفاصيل عنهم support يعرّف البريد الإلكتروني للدعم وقناة الدردشة على الإنترنت والروابط المختلفة require يعرّف الاعتماديات الفعلية بالإضافة إلى إصدارات الحزمة require-dev يعرّف الحزم الضرورية لتطوير المشروع suggest يعرّف اقتراحات الحزمة، مثال الحزم التي تساعد في التثبيت autoload يعرّف سياسات التحميل التلقائي للمشروع autoload-dev يعرّف سياسات التحميل التلقائي لتطوير المشروع table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } التحميل التلقائي مع المحلن بينما يوفر المُلحِّن نظامًا لإدارة اعتماديات مشروعك من Packagist مثلًا، يمكن أن يعمل أيضًا كمحمّل تلقائي يصف أين يبحث عن فضاء أسماء محددة أو يضمّن ملفات الدوال العامة. يبدأ مع ملف composer.json: { // ... "autoload": { "psr-4": { "MyVendorName\\MyProject": "src/" }, "files": [ "src/functions.php" ] }, "autoload-dev": { "psr-4": { "MyVendorName\\MyProject\\Tests": "tests/" } } } تضمن شيفرة الإعداد هذه أنّ كل الأصناف في فضاء الاسم MyVendorName\MyProject رُبطت إلى مجلد src وكل الأصناف في MyVendorName\MyProject\Tests رُبطت إلى مجلد tests (نسبةً إلى المجلد الجذر) وسيضمّن تلقائيًا ملف functions.php. بعد إضافة هذا الإعداد إلى ملف composer.json ننفذ الأمر composer update في الطرفية ليحدّث المُحلِّن الاعتماديّات وملف القفل ويولّد ملف autoload.php، ستستخدم عند النشر إلى بيئة الإنتاج الأمر install --no-dev، ويمكن إيجاد ملف autoload.php في مجلد vendor الذي يجب أن يتولّد في نفس المجلد الموجود به composer.json، ويجب تضمينه مبكرًا عند نقطة إعداد في دورة حياة تطبيقك باستخدام سطر مشابه للتالي: require_once __DIR__ . '/vendor/autoload.php'; بعد التضمين سيهتم ملف autoload.php بتحميل كل الاعتماديّات المذكورة في ملف composer.json. بعض أمثلة ربط مسار الصنف بالمجلد: MyVendorName\MyProject\Shapes\Square ➔ src/Shapes/Square.php. MyVendorName\MyProject\Tests\Shapes\Square ➔ tests/Shapes/Square.php. الفرق بين 'composer install' و'composer update' composer update يحدّث هذا الأمر الاعتماديات المحددة في ملف composer.json، فإذا كان المشروع يستخدم هذا الإعداد مثلًا: "require": { "laravelcollective/html": "2.0.*" } بفرض كان الإصدار 2.0.1 لهذه الحزمة مثبتًا، سيؤدي تنفيذ الأمر composer update إلى ترقية هذه الحزمة (إلى 2.0.2 مثلًا إذا كانت قد أُصدرت)، أي أنّ هذا الأمر سوف: يقرأ ملف composer.json يزيل الحزم المثبتة التي لم تعد مطلوبة في composer.json يتحقق من توافرية الإصدارات الأخيرة من الحزم المطلوبة يوفر الإصدارات الأخيرة من الحزم يحدّث ملف composer.lock لتخزين إصدار الحزم المثبتة composer install سيثبت الأمر composer install كل الاعتماديات المحددة في ملف composer.lock وفقًا للنسخة المحددة دون تحديث أي شيء، أي أنّ هذا الأمر: يقرأ ملف composer.lock ينصّب الحزم المحددة في ملف composer.lock متى تثبِّت ومتى نحدِّث؟ نستخدم composer update غالبًا في مرحلة التطوير لتحديث حزم المشروع ونستخدم composer install بشكلٍ أساسي في مرحلة النشر لتثبيت التطبيق على خادم الإنتاج أو على بيئة الاختبار باستخدام نفس الاعتماديات المخزنة في ملف composer.lock المُنشأ من قِبل composer update. أوامر المحلن composer المتاحة الأمر الاستخدام about معلومات قليلة حول المُحلِّن archive ينشئ أرشيفًا من حزم المُحلِّن browse يفتح رابط مستودع الحزمة أو الصفحة الرئيسية في المتصفح clear-cache يمسح ذاكرة التخزين المؤقت الداخلية للمُلحِّن clear-cache يمسح ذاكرة التخزين المؤقت الداخلية للمُلحِّن config يضبط خيارات الإعداد create-project ينشئ مشروعًا جديدًا من حزمة في المجلد المحدد depends يعرض الحزم التي تتسبب في تثبيت الحزمة الحالية diagnose يشخّص النظام لتحديد الأخطاء المعروفة dump-autoload يفرّغ المحمّل التلقائي dumpautoload يفرّغ المحمّل التلقائي exec ينفّذ سكربت/ثنائي مقدم global ($COMPOSER_HOME) يسمح بتنفيذ الأوامر في مجلد المُلحِّن العام help يعرض المساعدة لأمر ما home يفتح رابط مستودع الحزمة أو الصفحة الرئيسية في المتصفح info يعرض معلومات الحزم init أساسي في المجلد الحالي composer.json ينشئ ملف install composer.json إذا وجد أو يعود إلى composer.lock ينصّب اعتماديات المشروع من licenses يعرض معلومات رِخص الاعتماديات list يعرض الأوامر في قائمة outdated يعرض قائمة بالحزم المنصّبة والتي تتوفر تحديثات لها مع إصدارها الأخيرة prohibits يعرض الحزم التي تمنع تثبيت الحزمة الحالية remove ‏require-dev أو requireيزيل حزمة من require وينصّبها composer.json يضيف الحزم المطلوبة إلى run-script composer.json ينفّذ السكربتات المعرفة في‏ search يبحث عن الحزم self-update إلى الإصدار الأخير composer.phar يحدّث‏ selfupdate إلى الإصدار الأخير composer.phar يحدّث‏ show يعرض معلومات حول الحزم status يعرض قائمة بالحزم المعدّلة محليًا suggests يعرض اقتراحات الحزم update ‏composer.lock ويحدّث composer.jsonيحدّث الاعتماديات إلى آخر إصدار وفقًا لملف ‏‏‏ validate composer.lock‏و‏ composer.json يتحقق من صحة‏ why يعرض الحزم التي تتسبب في تثبيت الحزمة الحالية why-not يعرض الحزم التي تمنع تثبيت الحزمة الحالية فوائد استخدام الملحن composer يتتبع المُحلِّن إصدارات الحزم المنصّبة في ملف يسمّى composer.lock والذي يهدف إلى التحكم في الإصدار، لذا عندما يُنسخ مشروعك مستقبلًا فإنّ تنفيذ الأمر composer install سينصّب ويحمّل كل اعتماديات المشاركة. يتعامل المُلحِّن composer مع اعتماديات PHP على أساس المشروع، مما يجعل من السهل وجود عدة مشاريع على جهاز واحد يعتمد على إصدارات منفصلة لحزمة PHP واحدة. يتتبع المُحلِّن الاعتماديات المخصصة لبيئات التطوير فقط. composer require --dev phpunit/phpunit يوفر المُحلِّن محمّلًا تلقائيًا مما يجعل من السهل أن تبدأ بالعمل مع أي حزمة، مثلًا بعد تثبيت Goutte باستخدام الأمر ‎composer require fabpot/goutte,‎ يمكنك البدء مباشرةً باستخدام Goutte في مشروع جديد: <?php require __DIR__ . '/vendor/autoload.php'; $client = new Goutte\Client(); // Goutte البدء باستخدام يسمح لك المُحلِّن بتحديث مشروعك بسهولة إلى الإصدار الأخير الذي يسمح به composer.json مثل composer update fabpot/goutte أو بتحديث كل من اعتماديات مشروعك: composer update. التثبيت قد تثبّت المُحلِّن محليًا كجزء من مشروعك أو عامًا كنظام عريض قابل للتنفيذ. محليًا عن طريق تنفيذ هذه الأوامر في الطرفية: php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php composer-setup.php php -r "unlink('composer-setup.php');" للتأكد من صلاحية المنصِّب المحمّل افحصه عبر هذا الرابط ضد SHA-384. ستؤدي الشيفرة السابقة إلى تحميل composer.phar (ملف أرشيف PHP) إلى المجلد الحالي، ويمكنك الآن تنفيذ الأمر php composer.phar لاستخدام المُلحِّن، مثال: php composer.phar install عامًّا ضع ملف composer.phar في مجلد يكون جزء من مسارك لاستخدام المُلحِّن بشكلٍ عام. mv composer.phar /usr/local/bin/composer يمكنك الآن استخدام المُلحِّن في أي مكان بدلًا من php composer.phar مثال: composer install ترجمة -وبتصرف- للفصل [Composer Dependency Manager] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: التوابع السحرية (Magic Methods) في PHP المقال السابق: مفهوم السمات (Traits) في PHP
  2. ما هي السمة (Traits)؟ تسمح PHP بالوراثة المفردة فقط أي أنّ الصنف يمكن أن يرث صنفًا واحدًا فقط، لكن ماذا لو احتجت لتضمين شيء ما لا ينتمي للصنف الأب؟ قبل الإصدار PHP 5.4 كان عليك أن تكون مبدعًا أما الإصدار PHP 5.4 فقد قدم السمات التي تسمح لك بنسخ ولصق جزء من صنف ما إلى الصنف الذي تريده. trait Talk { /** @var string */ public $phrase = 'Well Wilbur...'; public function speak() { echo $this->phrase; } } class MrEd extends Horse { use Talk; public function __construct() { $this->speak(); } public function setPhrase($phrase) { $this->phrase = $phrase; } } لدينا في الشيفرة السابقة الصنف MrEd يوسّع الصنف Horse، لكن ليس كل كائنات الصنف Horse لديها السمة Talk، لنرى ما تأثير ذلك. نعرّف أولًا السمة (Traits) ويمكننا استخدامها مع التحميل التلقائي وفضاء الأسماء ثمّ نضمنها في الصنف MrEd باستخدام الكلمة المفتاحية use، ستلاحظ أنّ MrEd يستخدم دوال ومتغيرات Talk دون تعريفها أي أنّ هذه الدوال والمتغيرات كلها معرفة في الصنف MrEd الآن وكأننا عرفناها داخل الصنف (نسخنا ولصقنا المتغيرات والدوال). ترتبط السمات ارتباطًا وثيقًا بالأصناف المجردة في أنّه يمكنك تعريف متغيرات ودوال، ولا يمكنك نسخ السمة أيضًا بشكلٍ مباشر (أي كتابة new Trait()‎)، ولا يمكن للسمات أن تفرض على الصنف تعريف دالة بشكلٍ ضمني مثلما يفعل الصنف المجرد أو الواجهة إذ أنّ السمات للتعاريف الصريحة فقط. متى يجب أن أستخدم السمة؟ عندما تريد استخدام السمة يجب أن تسأل نفسك السؤال المهم: هل يمكنني تجنب استخدام السمة بإعادة هيكلة شيفرتي؟ غالبًا سيكون الجواب نعم، السمات هي حالات هامشيّة تسببها الوراثة المفردة ويمكن أن يكون استخدامها بشكل مفرط مغريًا لكنها تقدم مصدرًا آخر لشيفرتك مما يعني أنّ هناك طبقة أخرى من التعقيد. تعاملنا في المثال السابق مع 3 أصناف فقط لكن السمات تعني أنّه يمكنك التعامل مع أكثر من ذلك بكثير، ومن أجل كل سمة يصبح التعامل مع صنفك أصعب بكثير إذ يجب عليك الرجوع إلى كل سمة لمعرفة ما الذي تعرّفه، كنتيجة يجب أن تحافظ على عدد سمات قليل قدر الإمكان. السمات لتسهيل إعادة استخدام الشيفرة الأفقية بفرض أنّه لدينا واجهة للتسجيل: interface Logger { function log($message); } ولدينا صنفين ينفذان هذه الواجهة: class FileLogger implements Logger { public function log($message) { // إضافة رسالة تسجيل إلى ملف ما } } class ConsoleLogger implements Logger { public function log($message) { // رسالة تسجيل إلى الطرفية } } إذا عرّفت الآن صنفًا آخر Foo والذي تريده أيضًا أن يؤدي مهام التسجيل باستخدام الشيفرة التالية: class Foo implements Logger { private $logger; public function setLogger(Logger $logger) { $this->logger = $logger; } public function log($message) { if ($this->logger) { $this->logger->log($message); } } } ينفّذ الآن الصنف Foo الواجهة Logger لكنه يعتمد وظيفيًا على تنفيذ Logger الممرر إليه عبر الدالة setLogger()‎، إذا أردنا الآن أن يكون لدينا الصنف Bar وله نفس آلية التسجيل هذه يجب نسخ هذا الجزء من المنطق داخله، لكن نعرّف سمة بدلًا من تكرار الشيفرة: trait LoggableTrait { protected $logger; public function setLogger(Logger $logger) { $this->logger = $logger; } public function log($message) { if ($this->logger) { $this->logger->log($message); } } } عرّفنا الآن المنطق في سمة ويمكننا استخدامها لإضافته إلى الأصناف Foo وBar: class Foo { use LoggableTrait; } class Bar { use LoggableTrait; } ويمكننا استخدام الصنف Foo كما في الشيفرة التالية مثلًا: $foo = new Foo(); $foo->setLogger( new FileLogger() ); $foo->log('my beautiful message'); لاحظ كيف استخدمنا السمة كوكيل (proxy) لاستدعاء تابع التسجيل على نسخة Foo. حل التضارب يمكن أن تؤدي محاولة استخدام عدة سمات في صف واحد إلى مشاكل تتعلق بالتوابع المتضاربة، عندها تحتاج لحل هذه التضاربات يدويًا، إليك المثال التالي: trait MeowTrait { public function say() { print "Meow \n"; } } trait WoofTrait { public function say() { print "Woof \n"; } } abstract class UnMuteAnimals { abstract function say(); } class Dog extends UnMuteAnimals { use WoofTrait; } class Cat extends UnMuteAnimals { use MeowTrait; } لنحاول الآن إنشاء الصنف التالي: class TalkingParrot extends UnMuteAnimals { use MeowTrait, WoofTrait; } سيرجع مفسر PHP خطأً فادحًا: Fatal error: Trait method say has not been applied, because there are collisions with other trait methods on TalkingParrot لحل مشكلة التضارب يمكننا القيام بما يلي: نستخدم الكلمة المفتاحية insteadof لاستخدام تابع من سمة بدلًا من تابع من سمة أخرى. ننشئ كنية للتابع مع الباني مثل WoofTrait::say as sayAsDog;‎ class TalkingParrotV2 extends UnMuteAnimals { use MeowTrait, WoofTrait { MeowTrait::say insteadof WoofTrait; WoofTrait::say as sayAsDog; } } $talkingParrot = new TalkingParrotV2(); $talkingParrot->say(); $talkingParrot->sayAsDog(); ستنتج هذه الشيفرة الخرج التالي: Meow Woof تنفيذ نمط مفردة (Singleton) باستخدام السمات ملاحظة: لا يؤيد هذا المثال استخدام نمط المفردة كثيرًا إنما يجب استخدامها بكثير من الحذر. يوجد في PHP طريقة معيارية لتنفيذ المفردة: public class Singleton { private $instance; private function __construct() { }; public function getInstance() { if (!self::$instance) { // new Singleton() تكافئ الشيفرة new self() self::$instance = new self(); } return self::$instance; } // منع نسخ الكائن protected function __clone() { } // منع سَلسَلة الكائن protected function __sleep() { } // منع عدم سَلسَلة الكائن protected function __wakeup() { } } من الجيد استخلاص هذا السلوك ضمن سمة لمنع تكرار الشيفرة: trait SingletonTrait { private $instance; protected function __construct() { }; public function getInstance() { if (!self::$instance) { // إلى الصنف الذي يستخدم السمة new self() يشير self::$instance = new self(); } return self::$instance; } protected function __clone() { } protected function __sleep() { } protected function __wakeup() { } } يمكن الآن لأي صنف يريد أن يعمل كمفردة أن يستخدم السمة: class MyClass { use SingletonTrait; } // خطأ! مرئية الباني ليست عامة $myClass = new MyClass(); $myClass = MyClass::getInstance(); // كل الاستدعاءات التالية ستفشل بسبب مرئية التابع $myClassCopy = clone $myClass; $serializedMyClass = serialize($myClass); $myClass = deserialize($serializedMyclass); على الرغم من أنّه من المستحيل الآن سَلسَلة المفردة إلا أنّه لا يزال من المفيد حجب تابع عدم السَلسلة. السمات للمحافظة على الأصناف نظيفة قد تنفّذ أصنافنا بمرور الوقت المزيد والمزيد من الواجهات، وعندما يكون لهذه الواجهات عدة توابع فإنّ العدد الكلي للتوابع في الصنف يصبح كبيرًا جدًا. لنفرض مثلًا أنّه لدينا واجهتين وصف ينفّذ هاتين الواجهتين: interface Printable { public function print(); // ...توابع الواجهة الأخرى } interface Cacheable { // توابع الواجهة } class Article implements Cachable, Printable { // يجب أن ننفذ هنا كل توابع الواجهة public function print(){ { /* شيفرة لطباعة المقالة */ } } يمكن استخدام سمات مستقلة لتنفيذ هذه الواجهة بدلًا من تنفيذ كل توابع الواجهة في الصنف Article للحفاظ على الصنف أصغر وفصل شيفرة تنفيذ الواجهة من الصنف. مثلًا لتنفيذ الواجهة Printable يمكننا إنشاء هذه السمة: trait PrintableArticle { // هنا تنفيذ توابع الواجهة public function print() { /* شيفرة لطباعة المقالة */ } } ثم نجعل الصنف يستخدم السمة: class Article implements Cachable, Printable { use PrintableArticle; use CacheableArticle; } تتمثل الفوائد الأساسية في أنّ توابع تنفيذ الواجهة ستنفصل عن بقية الصنف وتُخزَّن في سمة وهذه السمة تتحمل وحدها مسؤولية تنفيذ الواجهة من أجل هذا النوع المعين من الكائنات. استخدام عدة سمات trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World'; } } class MyHelloWorld { use Hello, World; public function sayExclamationMark() { echo '!'; } } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); $o->sayExclamationMark(); خرج المثال السابق: Hello World! تغيير مرئية التابع trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } // sayHello تغيير مرئية class MyClass1 { use HelloWorld { sayHello as protected; } } // كنية تابع مع تغيير مرئية // لم تتغير sayHello مرئية class MyClass2 { use HelloWorld { sayHello as private myPrivateHello; } } تنفيذ هذا المثال: (new MyClass1())->sayHello(); // Fatal error: Uncaught Error: Call to protected method MyClass1::sayHello() (new MyClass2())->myPrivateHello(); // Fatal error: Uncaught Error: Call to private method MyClass2::myPrivateHello() (new MyClass2())->sayHello(); // Hello World! انتبه أنّه في المثال الأخير في MyClass2 التابع الأصلي غير المُكنّى من trait HelloWorld يبقى كما هو قابلًا للوصول. ترجمة -وبتصرف- للفصل [Traits] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: مدخل إلى الملحن composer: مدير الاعتماديات والحزم في PHP المقال السابق: التعابير النمطية (regexp/PCRE) في PHP
  3. المعامل تفاصيل ‎$pattern ‎سلسلة نصية مع تعبير منطقي (نمط PCRE) table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } مطابقة تعبير نمطي عام يمكن إجراء المطابقة باستخدام الدالة preg_match_all التي تعيد كل النتائج المطابقة في السلسلة النصية المُدخلة (على عكس الدالة preg_match التي تعيد النتيجة الأولى فقط). تعيد الدالة preg_match_all عدد التطابقات، وسيحتوي المعامل الثالث ‎$matches على التطابقات بتنسيق تتحكم به الرايات التي يمكن وضعها في المعامل الرابع. إذا كان لديك مصفوفة سيحتوي المعامل ‎$matches على مصفوفة بتنسيق مشابه لمصفوفتك مع الدالة preg_match، باستثناء أن preg_match تتوقف عند التطابق الأول، حيث تكرر preg_match_all عبر السلسلة النصية حتى تنتهي وتعرض نتيجة كل تكرار في مصفوفة متعددة الأبعاد والتي يمكن التحكم بتنسيقها باستخدام الراية في المعامل الرابع. يتحكم المعامل الرابع ‎$flags في بنية مصفوفة ‎$matches، النمط الافتراضي هو PREG_PATTERN_ORDER والرايات الممكنة PREG_SET_ORDER وPREG_PATTERN_ORDER. توضح الشيفرة التالية استخدام preg_match_all: $subject = "a1b c2d3e f4g"; $pattern = '/[a-z]([0-9])[a-z]/'; var_dump(preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER)); // int(3) var_dump($matches); preg_match_all($pattern, $subject, $matches); var_dump($matches); // preg_match نفس التعبير المنطقي يُنفَّذ بالدالة preg_match($pattern, $subject, $matches); var_dump($matches); خرج الشيفرة var_dump($matches);‎ الأولى: array(3) { [0]=> array(2) { [0]=> string(3) "a1b" [1]=> string(1) "1" } [1]=> array(2) { [0]=> string(3) "c2d" [1]=> string(1) "2" } [2]=> array(2) { [0]=> string(3) "f4g" [1]=> string(1) "4" } } لدى ‎$matches ثلاث مصفوفات متداخلة تمثل كل منها تطابقًا لها نفس تنسيق القيمة المعادة من preg_match. خرج الشيفرة var_dump($matches);‎ الثانية حيث الراية هي الراية الافتراضية PREG_PATTERN_ORDER: array(2) { [0]=> array(3) { [0]=> string(3) "a1b" [1]=> string(3) "c2d" [2]=> string(3) "f4g" } [1]=> array(3) { [0]=> string(1) "1" [1]=> string(1) "2" [2]=> string(1) "4" } } عندما يُنفّذ نفس التعبير المنطقي باستخدام preg_match تكون المصفوفة المُعادة: array(2) { [0] => string(3) "a1b" [1] => string(1) "1" } تطابق سلسلة نصية مع تعبير نمطي تتحقق الدالة preg_match من تطابق سلسلة نصية مع تعبير نمطي. $string = 'This is a string which contains numbers: 12345'; $isMatched = preg_match('%^[a-zA-Z]+: [0-9]+$%', $string); var_dump($isMatched); // bool(true) إذا مررت معاملًا ثالثًا سيُملأ بالبيانات المُطابقة من التعبير النمطي: preg_match('%^([a-zA-Z]+): ([0-9]+)$%', 'This is a string which contains numbers: 12345', $matches); // الآن على نتائج مطابقة التعبير النمطي في مصفوفة $matches تحتوي echo json_encode($matches); // ["numbers: 12345", "numbers", "12345"] تحتوي ‎$matches على مصفوفة لكل المطابقات ثم السلاسل النصية الجزئية في التعبير النمطي محاطة بقوسين، بترتيب إزاحة الأقواس المفتوحة. أي إذا كان لديك التعبير المنطقي ‎/z(a(b))/‎ سيحتوي الفهرس 0 على كامل السلسلة النصية zab ويحتوي الفهرس 1 على السلسلة النصية الجزئية مُحاطة بالأقواس الخارجية ab ويحتوي الفهرس 2 على السلسلة النصية الجزئية المحاطة بالأقواس الداخلية b. تقسيم سلسلة نصية إلى مصفوفة باستخدام تعبير نمطي إليك معاني أجزاء التعبير المنطقي ‎/[0-9]+\|/‎ في الشيفرة أدناه: [0-9]: أي محرف مفرد في المجال من 0 إلى 9. +: واحد أو أكثر من 0 إلى 9. ومعاني أجزاء التعبير المنطقي ‎/[\d]+\|/‎: []: صنف المحارف ‎\d‎: أي رقم +: رقم أو أكثر $string = "0| PHP 1| CSS 2| HTML 3| AJAX 4| JSON"; $array = preg_split("/[0-9]+\|/", $string, -1, PREG_SPLIT_NO_EMPTY); // أو $array = preg_split("/[\d]+\|/", $string, -1, PREG_SPLIT_NO_EMPTY); الخرج: Array ( [0] => PHP [1] => CSS [2] => HTML [3] => AJAX [4] => JSON ) يمكنك ببساطة تقسيم سلسلة نصية إلى مصفوفة بتمرير السلسلة النصية والتعبير النمطي للدالة preg_split()‎، وللمطابقة والبحث نضيف المعامل الثالث (limit) الذي يسمح لنا بضبط عدد التطابقات التي نريدها وتُضاف السلسلة النصية الباقية إلى نهاية المصفوفة. ضبطنا المعامل الرابع (flags) في مثالنا إلى القيمة PREG_SPLIT_NO_EMPTY التي تمنع المصفوفة من الاحتواء على أي قيم / مفاتيح فارغة. استبدال السلسلة النصية مع التعبير النمطي يمثل ‎$1 و‎$2 و‎‎$3 في الشيفرة التالية المجموعات الملتقطة الأولى والثانية والثالثة. $string = "a;b;c\nd;e;f"; echo preg_replace("(^([^;]+);([^;]+);([^;]+)$)m", "$3;$2;$1", $string); الخرج: c;b;a f;e;d تبحث عن كل ما هو بين الفواصل المنقوطة وتعكس الترتيب. استبدال سلسلة نصية مع رد النداء تعمل الدالة preg_replace_callback بإرسال كل مجموعة ملتقطة مطابقة إلى رد النداء المعرَّف وتستبدلها مع القيمة المعادة من رد النداء، وهذا يسمح لنا باستبدال سلسلة نصية اعتمادًا على أي نوع من المنطق. $subject = "He said 123abc, I said 456efg, then she said 789hij"; $regex = "/\b(\d+)\w+/"; // تستبدل هذه الدالة المداخل المطابقة شرطيًا بالاعتماد على المحرف الأول للمجموعة الملتقطة function regex_replace($matches){ switch($matches[1][0]){ case '7': $replacement = "<b>{$matches[0]}</b>"; break; default: $replacement = "<i>{$matches[0]}</i>"; } return $replacement; } $replaced_str = preg_replace_callback($regex, "regex_replace", $subject); print_r($replaced_str); // He said <i>123abc</i>, I said <i>456efg</i>, then she said <b>789hij</b> ترجمة -وبتصرف- للفصل [Regular Expressions (regexp/PCRE)‎] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: مفهوم السمات (Traits) في PHP المقال السابق: الانعكاس (Reflection) وحقن الاعتمادية في PHP
  4. الانعكاس كشف ميزة الأصناف أو الكائنات يمكن إجراء كشف ميزة الأصناف جزئيًا مع الدالتين property_exists وmethod_exists. class MyClass { public $public_field; protected $protected_field; private $private_field; static $static_field; const CONSTANT = 0; public function public_function() {} protected function protected_function() {} private function private_function() {} static function static_function() {} } // التحقق من الخاصيات $check = property_exists('MyClass', 'public_field'); // true $check = property_exists('MyClass', 'protected_field'); // true // PHP 5.3.0 بدءًا من true $check = property_exists('MyClass', 'private_field'); $check = property_exists('MyClass', 'static_field'); // true $check = property_exists('MyClass', 'other_field'); // false // التحقق من التوابع $check = method_exists('MyClass', 'public_function'); // true $check = method_exists('MyClass', 'protected_function'); // true $check = method_exists('MyClass', 'private_function'); // true $check = method_exists('MyClass', 'static_function'); // true $check = property_exists('MyClass', 'CONSTANT'); // false $check = property_exists($object, 'CONSTANT'); // false يمكننا باستخدام الصنف ReflectionClass كشف الثوابت أيضًا: $r = new ReflectionClass('MyClass'); $check = $r->hasProperty('public_field'); // true $check = $r->hasMethod('public_function'); // true $check = $r->hasConstant('CONSTANT'); // true تعمل الشيفرة السابقة أيضًا مع أعضاء الصنف الخاصة والمحمية و/أو الساكنة. ملاحظة: يمكن استخدام الدالتين property_exists وmethod_exists مع كائن من الصنف بدلًا من الصنف أما لاستخدام الانعكاس مع الكائنات نستخدم الصنف ReflectionObject بدلًا من ReflectionClass. اختبار التوابع الخاصة/المحمية من المفيد أحيانًا اختبار التوابع الخاصة والمحمية والعامة أيضًا. class Car { protected function drive($argument) { return $argument; } private static function stop() { return true; } } أسهل طريقة لاختبار تابع قيادة (drive method) هي استخدام الانعكاس. class DriveTest { /** * @ اختبار */ public function testDrive() { // التهيئة $argument = 1; $expected = $argument; $car = new \Car(); $reflection = new ReflectionClass(\Car::class); $method = $reflection->getMethod('drive'); $method->setAccessible(true); // منطق الاستدعاء $result = $method->invokeArgs($car, [$argument]); // الاختبار $this->assertEquals($expected, $result); } } نمرر null مكان نسخة الصنف إذا كان التابع ساكنًا. class StopTest { /** * @test */ public function testStop() { // التهيئة $expected = true; $reflection = new ReflectionClass(\Car::class); $method = $reflection->getMethod('stop'); $method->setAccessible(true); // منطق الاستدعاء $result = $method->invoke(null); // الاختبار $this->assertEquals($expected, $result); } } الوصول إلى متغيرات الأعضاء الخاصة والمحمية يستخدم الانعكاس غالبًا كجزء من عملية تطوير البرمجيات مثل إنشاء/استنساخ كائنات وهمية وقت التنفيذ، وهو ممتاز أيضًا لفحص حالة كائن عند أي نقطة زمنية، إليك مثال لاستخدام الانعكاس في اختبار وحدة لتأكيد أنّ عضو الصنف المحمي يتضمن القيمة المتوقعة. الصنف التالي هو صنف بسيط يعبر عن سيارة، فيه عضو متغير محمي سيحتوي على القيمة الممثلة للون السيارة وبما أنّ المتغير محمي لا نستطيع الوصول إليه مباشرةً ويجب أن نستخدم التوابع الجالبة والضابطة لاستعادة وضبط قيمته. class Car { protected $color public function setColor($color) { $this->color = $color; } public function getColor($color) { return $this->color; } } ينشئ العديد من المطورين كائنًا من الصنف السابق لاختباره، يضبطون لون السيارة باستخدام Car::setColor()‎ ويستعيدونه باستخدام Car::getColor()‎ ويوازنون القيمة المُستعادة مع القيمة المضبوطة: /** * @اختبار * @ \Car::setColor يغطي */ public function testSetColor() { $color = 'Red'; $car = new \Car(); $car->setColor($color); $getColor = $car->getColor(); $this->assertEquals($color, $reflectionColor); } قد تبدو الأمور جيدة، يعيد كل Car::getColor()‎ قيمة المتغير المحمي Car::$color لكن هذا الاختبار خاطئ بطريقتين: ينفذ Car::getColor()‎ الذي هو خارج نطاق هذا الاختبار. يعتمد على Car::getColor()‎ الذي قد يكون فيه خطأ مما يجعل الاختبار إيجابيًا أو سلبيًا بشكلٍ خاطئ. لنلقي نظرة لماذا يجب أن نستخدم الانعكاس بدلًا من Car::getColor()‎ في وحدة الاختبار، بفرض أُسندت مهمة للمطور لإضافة "Metallic" لكل لون سيارة لذا سيحاول تعديل Car::getColor()‎ لإضافة "Metallic" قبل لون السيارة: class Car { protected $color public function setColor($color) { $this->color = $color; } public function getColor($color) { return "Metallic "; $this->color; } } لاحظ أنّ المطور استخدم فاصلة منقوطة بدلًا من عامل الدمج لإضافة "Metallic" قبل لون السيارة، وفي النتيجة كلما استُدعي التابع Car::getColor()‎ ستكون القيمة المعادة "Metallic" بغض النظر عن لون السيارة الفعلي وستفشل نتيجة اختبار الوحدة حتى لو عمل التابع Car::setColor()‎ بشكلٍ صحيح ولم يتأثر بهذا التغيير. إذن كيف نتحقق من أنّ Car::$color يتضمن القيمة التي ضبطناها باستخدام Car::setColor()‎؟ يمكننا استخدام الانعكاس لفحص العضو المتغير المحمي بشكلٍ مباشر بأن نجعل هذا العضو قابلًا للوصول إلى شيفرتنا ثم يمكننا استعادة القيمة. لنطّلع على الشيفرة ثم نقسّمها: /** * @ اختبار * @ \Car::setColor يغطي */ public function testSetColor() { $color = 'Red'; $car = new \Car(); $car->setColor($color); $reflectionOfCar = new \ReflectionObject($car); $protectedColor = $reflectionOfForm->getProperty('color'); $protectedColor->setAccessible(true); $reflectionColor = $protectedColor->getValue($car); $this->assertEquals($color, $reflectionColor); } إليك الآن كيف نستخدم الانعكاس لنحصل على قيمة Car::$color في الشيفرة السابقة: ننشئ كائن ReflectionObject يمثّل كائن السيارة لدينا. نحصل على ReflectionProperty من أجل Car::$color (يمثل هذا المتغير Car::$color) نجعل Car::$color قابلًا للوصول. نحصل على القيمة من Car::$color. كما لاحظت يمكننا الحصول على قيمة Car::$color باستخدام الانعكاس دون استدعاء Car::getColor()‎ أو أي دالة مساعدة أخرى مما قد يسبب نتائج اختبار غير صالحة، الأن أصبحت وحدة اختبار Car::setColor()‎ آمنة ودقيقة. حقن الاعتمادية حقن الاعتمادية (Dependency Injection) هو مصطلح زائف "لتمرير الأشياء"، وما يعنيه حقًا هو تمرير اعتماديات كائن عبر الباني و/أو التوابع الضابطة بدلًا من إنشائهم عند إنشاء كائن داخل كائن، وقد يشير حقن الاعتمادية إلى حاويات حقن الاعتمادية التي تشغّل البناء والحقن. حقن الباني تعتمد الكائنات عادةً على كائنات أخرى، بدلًا من إنشاء الاعتمادية في الباني تُمرَّر إليه كمعامل، يضمن هذا عدم وجود ترابط محصور بين الكائنات ويتيح تغيير الاعتمادية عند استنساخ الصنف وهذا له عدة فوائد منها جعل الشيفرة أسهل في القراءة من خلال جعل الاعتماديات واضحة بالإضافة إلى جعل الاختبار أبسط بما أنّ الاعتماديات يمكن تبديلها ومحاكاتها بسهولة. في المثال التالي يعتمد الصنف Component على نسخة من الصنف Logger لكنه لا ينشئ واحدة، بدلًا من ذلك يتطلب نسخة لتمريرها كمعامل إلى الباني: interface Logger { public function log(string $message); } class Component { private $logger; public function __construct(Logger $logger) { $this->logger = $logger; } } تبدو الشيفرة مشابهًا لما يلي دون حقن الاعتمادية: class Component { private $logger; public function __construct() { $this->logger = new FooLogger(); } } يشير استخدام الكلمة المفتاحية new لإنشاء كائنات جديدة في الباني إلى أنّ حقن الاعتمادية لم يُستخدم (أو اُستخدم بشكل غير كامل) وأنّ الشيفرة أصبحت مترابطة بشكلٍ محصور، وهو أيضًا إشارة إلى أنّ الشيفرة اُختبرت بشكلٍ غير كامل أو باختبارات ضعيفة أنتجت افتراضات خاطئة حول حالة البرنامج. في المثال السابق حيث نستخدم حقن الاعتمادية بدلًا من ذلك، يمكننا التغيير ببساطة إلى Logger مختلفة إذا اضطررنا لذلك، فمثلًا قد نستخدم تنفيذ Logger يسجل الدخول إلى موقع مختلف أو يستخدم تنسيق تسجيل دخول مختلف أو يسجل الدخول إلى قاعدة البيانات بدلًا من ملف. حقن التابع الضابط يمكن أن تحقن التوابع الضابطة أيضًا الاعتماديات. interface Logger { public function log($message); } class Component { private $logger; private $databaseConnection; public function __construct(DatabaseConnection $databaseConnection) { $this->databaseConnection = $databaseConnection; } public function setLogger(Logger $logger) { $this->logger = $logger; } public function core() { $this->logSave(); return $this->databaseConnection->save($this); } public function logSave() { if ($this->logger) { $this->logger->log('saving'); } } } يعدّ هذا أمرًا مهمًا عندما لا تعتمد الوظيفة الأساسية للصنف على الاعتمادية للعمل. الاعتمادية الوحيدة المطلوبة هنا هي DatabaseConnection لذا فهي في الباني، الاعتمادية Logger اختيارية لذا لا نحتاج لتكون جزءًا من الباني مما يجعل الصنف أسهل في الاستخدام. لاحظ أنّه عند استخدام حقن التابع الضابط من الأفضل توسيع الوظيفة بدلًا من استبدالها، عند ضبط اعتمادية لا يوجد ما يؤكد أنّ الاعتمادية لن تتغير عند نقطة معينة مما قد يؤدي إلى نتائج غير متوقعة، فمثلًا يمكن ضبط FileLogger في البداية ثم ضبط MailLogger وهذا يكسر التغليف ويجعل من الصعب العثور على تسجيلات الدخول لأننا نستبدل التبعية. يجب أن نضيف اعتمادية مع حقن التابع الضابط لمنع حدوث هذا كما في التالي: interface Logger { public function log($message); } class Component { private $loggers = array(); private $databaseConnection; public function __construct(DatabaseConnection $databaseConnection) { $this->databaseConnection = $databaseConnection; } public function addLogger(Logger $logger) { $this->loggers[] = $logger; } public function core() { $this->logSave(); return $this->databaseConnection->save($this); } public function logSave() { foreach ($this->loggers as $logger) { $logger->log('saving'); } } } في مثل هذا المثال كلما سنستخدم الوظيفة الأساسية فإنّها لن تنكسر حتى لو لم تُضاف اعتمادية مسجل وأي مسجل سيُضاف سيُستخدم مع أنّه يمكن إضافة مسجل آخر، نحن نوسع الوظيفة بدلًا من استبدالها. حقن الحاوية يمكن أن ننظر إلى حقن الاعتمادية في سياق استخدام حاوية حقن الاعتمادية (DIC) على أنّه مجموعة عليا من حقن الباني، تحلل حاوية حقن الاعتمادية تلميحات نوع باني الصنف وتعالج احتياجاته، نحتاج حقن الاعتماديات بشكلٍ فعال من أجل تنفيذ النسخ. يتجاوز التنفيذ الدقيق نطاق هذا الملف ولكن في أصله تعتمد حاوية حقن الاعتمادية على استخدام بصمة الصنف. namespace Documentation; class Example { private $meaning; public function __construct(Meaning $meaning) { $this->meaning = $meaning; } } لنسخه بطريقة تلقائية يعتمد معظم الوقت على نظام التحميل التلقائي. // القديمة PHP إصدارات $container->make('Documentation\Example'); // PHP 5.5 بدءًا من الإصدار $container->make(\Documentation\Example::class); إذا كنت تستخدم إصدار PHP 5.5 على الأقل وتريد الحصول على اسم صنف كما في الطريقة في الشيفرة السابقة فإنّ الطريقة الصحيحة هي الثانية، بهذه الطريقة يمكنك إيجاد استخدامات الصنف بسرعة باستخدام بيئة تطوير متكاملة (IDE) حديثة مما يساعدك بشكلٍ كبير مع عملية إعادة التصميم (potential) المحتملة. يعلم الصنف Documentation\Example في هذه الحالة أنّه يحتاج Meaning وستنشئ حاوية حقن الاعتمادية بدورها نسخة من النوع Meaning، لا يحتاج التنفيذ الفعلي إلى الاعتماد على النسخة المستهلَكة. بدلًا من ذلك نضع قواعد في الحاوية قبل إنشاء الكائن توضح كيفية نسخ أنواع محددة عند الحاجة وهذا له عدة إيجابيات، كما أنّ حاوية حقن الاعتمادية تستطيع: مشاركة النسخ المشتركة توفير مصنع (factory) لحل بصمة النوع حل بصمة واجهة إذا عرّفنا قواعد تصف كيف يحتاج نوع محدد للإدارة يمكننا تحقيق تحكم دقيق على الأنواع المشتركة أو المنسوخة أو المُنشأة من مصنع. ترجمة -وبتصرف- للفصول [Reflection - Dependency Injection] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: التعابير النمطية (regexp/PCRE) في PHP المقال السابق: التخزين المؤقت للخرج في PHP
  5. الدالة تفاصيل ob_start()‎ تبدأ إظهار المخزن المؤقت وأي خرج يوضع بعده سيُلتقط ولن يُعرض ob_get_contents()‎ تُرجع كل المحتوى المُلتقط بالدالة ob_start()‎ ob_end_clean()‎ تُفرغ المخزن المؤقت للخرج وتوقف تشغيله للمستوى المتداخل الحالي ob_get_clean()‎ ينفّذ كل من ob_end_clean()‎ و ob_get_contents()‎ ob_get_level()‎ يُرجع مستوى التداخل الحالي للمخزن المؤقت للخرج ob_flush()‎ تُفرّغ محتوى المخزن المؤقت وترسله إلى المتصفح دون إنهاء المخزن المؤقت ob_implicit_flush()‎ تُمكّن التفريغ الضمني بعد كل استدعاء للخرج ob_end_flush()‎ تُفرّغ محتوى المخزن المؤقت وترسله إلى المتصفح مع إنهاء المخزن المؤقت table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } الاستخدام الأساسي للحصول على المحتوى بين المخازن المؤقتة والتفريغ يسمح لك التخزين المؤقت للخرج بتخزين أي محتوى نصي (نص، HTML) في متغير وإرساله إلى المتصفح كجزء واحد في نهاية السكربت، بشكلٍ افتراضي تُرسل PHP المحتوى كما تترجمه. <?php // تشغيل التخزين المؤقت للخرج ob_start(); // طباعة خرج ما للمخزن المؤقت print 'Hello '; // يمكنك أيضًا الخروج من شيفرة PHP ?> <em>World</em> <?php // إرجاع المخزن المؤقت وإفراغه $content = ob_get_clean(); // إرجاع المخزن المؤقت ثم إفراغه # $content = ob_get_contents(); # $did_clear_buffer = ob_end_clean(); print($content); #> "Hello <em>World</em>" أي محتوى خرج بين ob_start()‎ وob_get_clean()‎ سيُلتقط ويوضع في المتغير ‎$content. ينفذ استدعاء ob_get_clean()‎ كل من ob_get_contents()‎ وob_end_clean()‎. معالجة المخزن المؤقت عبر رد النداء يمكنك تطبيق أي نوع من المعالجة الإضافية على الخرج بتمرير معامل قابل للاستدعاء للدالة ob_start()‎. <?php function clearAllWhiteSpace($buffer) { return str_replace(array("\n", "\t", ' '), '', $buffer); } ob_start('clearAllWhiteSpace'); ?> <h1>Lorem Ipsum</h1> <p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. <a href="#">Donec non enim</a> in turpis pulvinar facilisis.</p> <h2>Header Level 2</h2> <ol> <li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li> <li>Aliquam tincidunt mauris eu risus.</li> </ol> <?php /* ob_end_flush(); سيُفرّغ الخرج ويُعالج عندما ينتهي السكربت أو تُستدعى */ الخرج: <h1>LoremIpsum</h1><p><strong>Pellentesquehabitantmorbitristique</strong>senectusetnetusetmalesuada famesacturpisegestas.<ahref="#">Donecnonenim</a>inturpispulvinarfacilisis.</p><h2>HeaderLevel2</h2> <ol><li>Loremipsumdolorsitamet,consectetueradipiscingelit.</li><li>Aliquamtinciduntmauriseurisus.</ li></ol> المخازن المؤقتة للخرج المتداخل يمكنك أن تحصل على تداخل المخازن المؤقتة للخرج ومستواها لتوفير محتوى مختلف باستخدام الدالة ob_get_level()‎. <?php $i = 1; $output = null; while( $i <= 5 ) { // `level` تُنشئ كل حلقة مخزن مؤقت خرج جديد ob_start(); print "Current nest level: ". ob_get_level() . "\n"; $i++; } // نحن الآن في المستوى 5 print 'Ended up at level: ' . ob_get_level() . PHP_EOL; // محتويات المستوى الأعلى (5) وتحذفه ob_get_clean() ستفرّغ الدالة $output .= ob_get_clean(); print $output; print 'Popped level 5, so we now start from 4' . PHP_EOL; // نحن الآن في المستوى 4 // كلما أردنا أن نزيد مستوى نعود للأسفل ونحصل على المخزن المؤقت while( $i > 2 ) { print "Current nest level: " . ob_get_level() . "\n"; echo ob_get_clean(); $i--; } الخرج: Current nest level: 1 Current nest level: 2 Current nest level: 3 Current nest level: 4 Current nest level: 5 Ended up at level: 5 Popped level 5, so we now start from 4 Current nest level: 4 Current nest level: 3 Current nest level: 2 Current nest level: 1 تشغيل المخزن المؤقت للخرج قبل أي محتوى ob_start(); $user_count = 0; foreach( $users as $user ) { if( $user['access'] != 7 ) { continue; } ?> <li class="users user-<?php echo $user['id']; ?>"> <a href="<?php echo $user['link']; ?>"> <?php echo $user['name'] ?> </a> </li> <?php $user_count++; } $users_html = ob_get_clean(); if( !$user_count ) { header('Location: /404.php'); exit(); } ?> <html> <head> <title>Level 7 user results (<?php echo $user_count; ?>)</title> </head> <body> <h2>We have a total of <?php echo $user_count; ?> users with access level 7</h2> <ul class="user-list"> <?php echo $users_html; ?> </ul> </body> </html> نفرض في هذا المثال أنّ ‎$users‎ مصفوفة متعددة الأبعاد ونريد تمريرها عبر حلقة لإيجاد كل المستخدمين مع الوصول للمستوى 7، إذا لم توجد نتائج سيُعاد توجيهنا إلى صفحة خطأ. نستخدم هنا المخزن المؤقت للخرج لأننا نفذنا إعادة توجيه header()‎ بالاعتماد على نتيجة الحلقة. خرج مجرى التدفق إلى العميل تمكّن الشيفرة التالية مجرى تدفق المخزن المؤقت للخرج، يفرّغ استدعاء هذه الدالة مباشرةً المخزن المؤقت للعميل وأي خرج لاحق سيُرسل مباشرةً له. function _stream() { ob_implicit_flush(true); ob_end_flush(); } استخدام المخزن المؤقت للخرج لتخزين المحتويات في ملف يعدّ هذا مفيدًا للتقارير والفواتير وغير ذلك. <?php ob_start(); ?> <html> <head> <title>Example invoice</title> </head> <body> <h1>Invoice #0000</h1> <h2>Cost: £15,000</h2> ... </body> </html> <?php $html = ob_get_clean(); $handle = fopen('invoices/example-invoice.html', 'w'); fwrite($handle, $html); fclose($handle); يأخذ هذا المثال كامل المستند ويكتبه إلى ملف وهذا لا يُظهر المستند في المتصفح إنما نحتاج لكتابة echo $html;‎ لعرضه. أسباب استخدام ob_start وكيفية الاستخدام النموذجي لها يعدّ استخدام ob_start مفيدًا بشكلٍ خاص عندما يكون لديك عمليات إعادة توجيه في صفحتك. الشيفرة التالية لن تعمل عملًا صحيحًا: Hello! <?php header("Location: somepage.php"); ?> سيظهر خطأ مشابه لما يلي: headers already sent by <xxx> on line <xxx>. لإصلاح هذه المشكلة نكتب الشيفرة التالية في بداية الصفحة: <?php ob_start(); ?> ونضيف ما يلي إلى نهايتها: <?php ob_end_flush(); ?> يخزّن ذلك كل المحتوى المتولد في المخزن المؤقت للخرج ويعرضه دفعةً واحدة، لذا إن كان لديك أي استدعاءات إعادة توجيه في صفحتك ستُنفّذ قبل إرسال أي بيانات وعندها تتجنب الخطأ headers already sent. التقاط المخزن المؤقت للخرج لإعادة الاستخدام لاحقًا لدينا في هذا المثال مصفوفة تتضمن بعض البيانات، نلتقط المخزن المؤقت للخرج في المتغير ‎$items_li_html ونستخدمه مرتين في الصفحة. <?php // ابدأ التقاط الخرج ob_start(); $items = ['Home', 'Blog', 'FAQ', 'Contact']; foreach($items as $item): // PHP يمكننا الآن الخروج من شيفرة ?> <li><?php echo $item ?></li> <?php // PHP العودة إلى شيفرة endforeach; // على كل المحتوى المُلتقط من المخزن المؤقت للخرج $items_lists سيحتوي المتغير $items_li_html = ob_get_clean(); ?> <!-- Menu 1: يمكننا الآن إعادة استخدام هذه القوائم عدة مرات في صفحتنا --> <ul class="header-nav"> <?php echo $items_li_html ?> </ul> <!-- Menu 2 --> <ul class="footer-nav"> <?php echo $items_li_html ?> </ul> احفظ الشيفرة السابقة في ملف output_buffer.php ونفذّه باستخدام php output_buffer.php، يجب أن تحصل على قائمتي العناصر اللتين أنشأناهما في الشيفرة السابقة مع نفس عناصر القائمة المتولدة في PHP باستخدام المخزن المؤقت للخرج: <!-- Menu 1: يمكننا الآن إعادة استخدام هذه القوائم عدة مرات في صفحتنا --> <ul class="header-nav"> <li>Home</li> <li>Blog</li> <li>FAQ</li> <li>Contact</li> </ul> <!-- Menu 2 --> <ul class="footer-nav"> <li>Home</li> <li>Blog</li> <li>FAQ</li> <li>Contact</li> </ul> ترجمة -وبتصرف- للفصل [Output Buffering] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: الانعكاس (Reflection) وحقن الاعتمادية في PHP المقال السابق: فضاء الأسماء (namespaces) في PHP
  6. التصريح عن فضاء اسم يُصرَّح عن فضاء الاسم كالتالي: للتصريح عن فضاء الاسم MyProject نكتب الشيفرة التالية: namespace MyProject; للتصريح عن فضاء اسم متداخل نكتب: namespace MyProject\Security\Cryptography; للتصريح عن فضاء اسم مع أقواس مغلقة نكتب: namespace MyProject { ... } من الأفضل أن تصرّح عن فضاء اسم واحد في الملف بالرغم من أنّه يمكنك التصريح عن العدد الذي تريده من فضاءات الأسماء في ملف واحد: namespace First { // First في فضاء الاسم A تعريف الصنف class A { ... }; } namespace Second { // Second في فضاء الاسم B تعريف الصنف class B { ... }; } namespace { // في فضاء الاسم الجذر C تعريف الصنف class C { ... }; } عندما تصرّح عن فضاء اسم فإنّ كل الأصناف التي تعرّفها بعده تنتمي إليه: namespace MyProject\Shapes; class Rectangle { ... } class Square { ... } class Circle { ... } يمكن أن يستخدم التصريح عن فضاء اسم عدة مرات في ملفات مختلفة، يعرّف المثال السابق ثلاثة أصناف في فضاء الاسم MyProject\Shapes في ملف واحد لكن يُفضَّل أن يُقسم إلى ثلاث ملفات يبدأ كل منها بشيفرة فضاء الاسم MyProject\Shapes. الإشارة إلى صنف أو دالة في فضاء الاسم يمكننا التعريف عن صنف في فضاء الاسم كما وضحنا في الفقرة السابقة بالشكل التالي: namespace MyProject\Shapes; class Rectangle { ... } نحتاج للحصول على المسار الكامل (المتضمن فضاء الاسم) للإشارة إلى هذا الصنف: $rectangle = new MyProject\Shapes\Rectangle(); يمكن أن نختصر الشيفرة السابقة باستيراد الصنف باستخدام التعليمة use: // MyProject\Shapes\Rectangle كنية Rectangle تصبح use MyProject\Shapes\Rectangle; $rectangle = new Rectangle(); يمكنك بدءًا من الإصدار PHP 7.0 تجميع تعليمات use مختلفة في تعليمة واحدة مستخدمًا الأقواس: use MyProject\Shapes\{ // `use MyProject\Shapes\Rectangle` نفس تعليمة Rectangle, // `use MyProject\Shapes\Circle` نفس تعليمة Circle, // `use MyProject\Shapes\Triangle` نفس تعليمة Triangle, // التجميعية use يمكنك أيضًا استيراد فضاءات الأسماء الفرعية في تعليمة Polygon\FiveSides, Polygon\SixSides }; $rectangle = new Rectangle(); قد يكون لديك صنفين بنفس الاسم وهذه ليست مشكلة إذا كان كل منهما في فضاء اسم مختلف، لكن هذا قد يسبب مشكلة عند محاولة استيرادهما باستخدام تعليمة use: use MyProject\Shapes\Oval; use MyProject\Languages\Oval; // Error! يمكنك حل هذه المشكلة بتعريف كنية باستخدام الكلمة المفتاحية as بالشكل التالي: use MyProject\Shapes\Oval as OvalShape; use MyProject\Languages\Oval as OvalLanguage; يجب الهروب بالمحرف \ للإشارة إلى صنف خارج فضاء الاسم الحالي وإلا سيُفترَض فضاء الاسم النسبي من فضاء الاسم الحالي: namespace MyProject\Shapes; // (1) $a = new Rectangle(); // (2) $a = new \MyProject\Shapes\Rectangle(); // (3) $a = new MyProject\Shapes\Rectangle(); // (4) $a = new \StdClass(); // (5) $a = new StdClass(); في الموضع (1) أُشير إلى MyProject\Shapes\Rectangle بشكلٍ صحيح. في الموضع (2) أُشير إلى MyProject\Shapes\Rectangle بشكلٍ صحيح لكن لا يوجد ضرورة لذكر المسار الكامل. في الموضع (3) أُشير إلى MyProject\Shapes\MyProject\Shapes\Rectangle بشكلٍ خاطئ. في الموضع (4) تتطلب الإشارة إلى الصنف StdClass من ضمن فضاء الاسم استخدام البادئة \ لأنّ هذا الصنف غير معرّف في فضاء اسم أي أنّه عام وهذه الطريقة الصحيحة للإشارة إليه. في الموضع (5) أُشير إلى MyProject\Shapes\StdClass بشكلٍ خاطئ. التصريح عن فضاء اسم فرعي يمكننا التصريح عن فضاء اسم ضمن هرمية بالشكل التالي: namespace MyProject\Sub\Level; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } يُنشئ المثال السابق ما يلي: constant MyProject\Sub\Level\CONNECT_OK class MyProject\Sub\Level\Connection and function MyProject\Sub\Level\connect ما هي فضاءات الأسماء؟ يوجد في مجتمع PHP الكثير من المطورين يكتبون الكثير من الشيفرات فمن الممكن أن يوجد نفس اسم الصنف في مكتبتين مختلفتين وقد يسبب هذا مشكلة عند استخدامهما في فضاء اسم واحد، لذا فإن فضاءات الأسماء تحلّ هذه المشكلة. كما هو موضح في دليل PHP المرجعي يمكن موازنة فضاءات الأسماء بمجلدات نظام التشغيل، فإذا كان لدينا ملفين بنفس الاسم موجودين في مجلدين مختلفين يشبه ذلك صنفي PHP لهما نفس الاسم وموجودين في فضاءي اسمين مختلفين. من الضروري بالنسبة لك أن يُستخدم فضاء اسم شيفرتك من قِبل المطورين الآخرين دون الخوف من التصادم مع مكتبات أخرى. ترجمة -وبتصرف- للفصل [Namespaces] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: التخزين المؤقت للخرج في PHP المقال السابق: الجلسات وملفات تعريف الارتباط ومكتبة cURL في PHP
  7. الجلسات خيارات session_start()‎ يمكننا تمرير مصفوفة من خيارات الملف php.ini المعتمدة على الجلسة (session) إلى الدالة session_start، مثال: <?php if (version_compare(PHP_VERSION, '7.0.0') >= 0) { // php >= 7 session_start([ 'cache_limiter' => 'private', 'read_and_close' => true, ]); } else { // php < 7 session_start(); } ?> تقدّم أيضًا هذه الميزة إعداد php.ini جديد يسمى lazy_write قيمته الافتراضية true ويعني أن بيانات الجلسة يُعاد كتابتها فقط إذا تغيرت. قفل الجلسة تكتب PHP بيانات الجلسة في ملف موجود على الخادم، عندما يُرسَل طلب لبدء الجلسة باستخدام session_start()‎ إلى سكربت PHP تقفل PHP ملف الجلسة لمنع/انتظار طلبات أخرى قادمة لنفس session_id للمتابعة‌ وبسبب ذلك ستبقى الطلبات الأخرى عالقة عند session_start()‎ حتى يُلغى قفل ملف الجلسة. يبقى ملف الجلسة مقفلًا حتى ينتهي السكربت أو تُغلق الجلسة يدويًا، لتجنب هذه الحالة أي تجنب منع تعطيل عدة طلبات يمكننا أن نبدأ الجلسة ونغلقها مما سيحرر القفل من ملف الجلسة ويسمح بمتابعة الطلبات الباقية. // php < 7.0 // بدء الجلسة session_start(); // كتابة بيانات في ملف الجلسة $_SESSION['id'] = 123; // عندها يُقفل ملف الجلسة لذا تتعطل الطلبات // إغلاق الجلسة وتحرير القفل session_write_close(); قد تتساءل الآن كيف ستُقرأ قيم الجلسة إذا كانت الجلسة مغلقة؟ تبقى الجلسة متاحة حتى بعد إغلاقها لذا بإمكاننا قراءة بياناتها. echo $_SESSION['id']; // 123 يتوفر بدءًا من الإصدار php 7.0 خيارات الجلسة read_only وread_write وlazy_write لذا من الممكن ألا نستخدم session_write_close()‎. معالجة بيانات الجلسة إنّ المتغير ‎$_SESSION عبارة عن مصفوفة وبإمكانك استعادتها أو معالجتها مثل أي مصفوفة عادية. <?php // بدء الجلسة session_start(); // تخزين قيمة في الجلسة $_SESSION['id'] = 342; // استخدام تقليدي لقيم جلسة ربما قد ضُبطت في جلسة سابقة if(!isset($_SESSION["login"])) { echo "Please login first"; exit; } // login يمكننا الآن أن نستخدم $user = $_SESSION["login"]; // PHP 7 في Null الحصول على قيمة من بيانات الجلسة أو القيمة الافتراضية باستخدام عامل تجميع $name = $_SESSION['name'] ?? 'Anonymous'; لاحظ أنّه إذا خزّنت كائن في الجلسة فيمكنك استعادته بأمان إذا كان لديك محمّل تلقائي للصنف أو إذا كنت قد حمّلت الصنف مسبقًا، وإلا سيخرج الكائن كالنوع ‎__PHP_Incomplete_Class مما قد يسبب الأعطال لاحقًا. تحذير: يمكن أن تتعرض بيانات الجلسة لهجوم اختطاف الجلسة (session hijacking) وهذا موضّح في كتاب أمن PHP المتقدم: من مبادئ أمن التطبيق إلى تنفيذ حماية xss - الفصل السابع: منع هجوم اختطاف الجلسة لذا يُنصح بعدم تخزين أي معلومات شخصية في المصفوفة ‎$_SESSION وخاصةً أرقام بطاقات الائتمان والمعرّفات الحكومية وكلمات المرور إنما يمكن تخزين المعلومات الأقل خطرًا مثل الأسماء وعناوين البريد الإلكتروني وأرقام الهاتف وغيرها مما يسمح للمخترق بانتحال شخصية مستخدم شرعي. كقاعدة عامة استخدم الجلسة لتخزين المعلومات الشخصية قليلة الأهمية. تدمير كامل الجلسة يمكنك استخدام الدالة session_destroy()‎ لتدمير الجلسة، بفرض لدينا الجلسة التالية: Array([firstname] => Jon, [id] => 123) ابدأ الجلسة أولًا بعدها يمكنك حذف كل القيم الموجودة في المتغير ذي النطاق العام العالي SESSION، إذا أهملت هذا الحذف فإنّ كل المتغيرات العامة المخزنة في المتغير SESSION ستبقى موجودة حتى لو دُمِّرت الجلسة. session_start(); $_SESSION = array(); // إذا كنت ترغب في تدمير الجلسة يجب حذف ملف ارتباط الجلسة أيضًا // الشيفرة التالية ستُدمر الجلسة وليس فقط بياناتها if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } // يمكنك الآن تدمير الجلسة session_destroy(); إنّ استخدام session_destroy()‎ مختلف عن استخدام ‎$_SESSION = array();‎ التي ستزيل كل القيم الموجودة في المتغير ذي النطاق العام العالي SESSION ولن تدمر نسخة الجلسة المخزّنة الفعلية. ملاحظة: نستخدم ‎$_SESSION = array();‎ بدلًا من session_unset()‎ لأنّ توثيق PHP ينص على أنّ استخدام session_unset()‎ فقط للشيفرة القديمة المهملة التي لا تستخدم ‎$_SESSION. بدء جلسة آمنة دون أخطاء يواجه العديد من المطورين هذه المشكلة عند عملهم على مشاريع كبيرة خاصةً إذا كانوا يعملوا على إضافات أو مكونات لنظام إدارة محتوى مثلًا، عندها فإنّ الحل لبدء جلسة آمنة يكون أولًا في التحقق من أنّ إصدار PHP يغطي كل الإصدارات ثمّ التحقق من بداية الجلسة. إذا لم توجد الجلسة عندها يمكن بدء الجلسة بأمان وإذا وجدت الجلسة لا يحدث أي شيء. if (version_compare(PHP_VERSION, '7.0.0') >= 0) { if(session_status() == PHP_SESSION_NONE) { session_start(array( 'cache_limiter' => 'private', 'read_and_close' => true, )); } } else if (version_compare(PHP_VERSION, '5.4.0') >= 0) { if (session_status() == PHP_SESSION_NONE) { session_start(); } } else { if(session_id() == '') { session_start(); } } يمكن أن يساعدك هذا كثيرًا لتجنب خطأ session_start. اسم الجلسة التحقق من إنشاء ملفات تعريف ارتباط الجلسة اسم الجلسة هو الاسم الذي يستخدمه ملف تعريف الارتباط (cookie) لتخزين الجلسات، يمكنك أن تستخدم هذا لتفحص فيما إذا اُنشئت ملفات تعريف الارتباط لجلسة ما: if(isset($_COOKIE[session_name()])) { session_start(); } لاحظ أنّ هذه الطريقة لا تفيد بشكلٍ عام إلا إذا كنت لا تريد إنشاء ملفات تعريف الارتباط دون ضرورة. تغيير اسم الجلسة يمكنك استدعاء الدالة session_name()‎ لتغيير اسم الجلسة. // ضبط اسم الجلسة session_name('newname'); // بدء الجلسة session_start(); إذا لم يُمرَّر وسيط إلى الدالة session_name()‎ سيُرجع اسم الجلسة الحالي، يجب أن يحتوي الاسم على محارف أبْجَعَددية فقط وأن يكون قصيرًا ووصفيًا (للمستخدمين الذين مكّنوا تحذيرات ملفات تعريف الارتباط)، لا يمكن أن يحتوي الاسم على أرقام فقط إنما يجب وجود حرف واحد على الأقل وإلا سيُنشأ معرّف جلسة جديد في كل مرة. ملفات تعريف الارتباط تأخذ الدالة setcookie()‎ المعاملات التالية: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } المعامل التفاصيل name اسم ملف تعريف الارتباط وهو المفتاح الذي يمكنك استخدامه لاستعادة قيمة من المتغير ‎$_COOKIE ذو النطاق العام العالي‏‏ وهذا المعامل الإلزامي الوحيد value القيمة التي تريد تخزينها في ملف تعريف الارتباط، هذه البيانات يمكن أن يصل إليها المتصفح لذا لا تحفظ أي بيانات حساسة expire طابع يونكس زمني‏ (Unix timestamp) يمثّل وقت انتهاء ملف تعريف الارتباط، إذا ضُبط إلى الصفر فستنتهي مدة ملف تعريف الارتباط مع نهاية الجلسة وإذا ضُبط إلى وقت أصغر من الوقت الحالي فستنتهي مدة الملف مباشرةً path نطاق ملف تعريف الارتباط، إذا عُيّنت قيمته / سيكون ملف تعريف الارتباط متوفرًا على كامل المجال وإذا عُيّنت قيمته /مسار معين/ فسيكون الملف متوفرًا في هذا المسار ومساراته الفرعية. قيمته الافتراضية هي مسار ملف تعريف الارتباط الحالي domain النطاق أو النطاق الفرعي المتوفر عليه ملف تعريف الارتباط، إذا ضُبط إلى النطاق ‏stackoverflow.com ‏فإنّ الملف سيكون متوفرًا في هذا النطاق ونطاقاته الفرعية وإذا ضُبط إلى النطاق الفرعي meta.stackoverflow.com فإنّ الملف سيكون متوفرًا في هذا النطاق الفرعي والنطاقات الفرعية منه secure ‏إذا ضُبط إلى القيمة‏ TRUE فسيُضبط ملف تعريف الارتباط فقط إذا وُجد اتصال HTTPS آمن بين الخادم والعميل httponly يحدد فيما إذا كان ملف تعريف الارتباط يجب أن يتوفر عبر بروتوكول HTTP/S‏‏‏‏ ويجب ألا يتوفر للغات البرمجة من جهة العميل مثل جافاسكربت، وهذا الخيار متوفر من الإصدار 5.2 ومابعده‏‏ ملف تعريف ارتباط HTTP هو جزء بيانات صغير يُرسل من موقع ما ويُخزَّن على حاسوب المستخدم من قبل متصفح ويب العميل أثناء تصفح المستخدم لمواقع الإنترنت. تعديل ملف تعريف الارتباط يمكن أن تُعدَّل قيمة ملف تعريف الارتباط (cookie) بإعادة ضبط الملف، بفرض لدينا ملف تعريف ارتباط باسم "user": setcookie("user", "John", time() + 86400, "/"); ملفات تعريف الارتباط هي جزء من ترويسة HTTP، لذا يجب أن تُستدعى setcookie()‎ قبل أن يُرسل أي خرج إلى المتصفح. تأكد عند تعديل ملف الارتباط أنّ المعاملات path وdomain للدالة setcookie()‎ تطابق ملف تعريف الارتباط الموجود وإلا سيُنشأ ملف جديد. سيُشفَّر قسم القيمة من ملف تعريف الارتباط تلقائيًا عند إرساله وعندما يُستقبل سيُفك تشفيره ويُسند إلى متغير بنفس اسم الملف. ضبط ملف تعريف الارتباط يُضبط ملف تعريف الارتباط باستخدام الدالة setcookie()‎، وبما أنّ ملفات تعريف الارتباط جزء من ترويسة HTTP فيجب أن تُضبط قبل إرسال أي خرج إلى المتصفح، مثال: setcookie("user", "Tom", time() + 86400, "/"); الوصف: أنشئ ملف تعريف ارتباط بالاسم user قيمة الملف Tom (معامل اختياري) ستنتهي صلاحية هذا الملف بعد يوم واحد (86400 ثانية) (معامل اختياري) يتوفر الملف على كامل الموقع / (معامل اختياري) يُرسل الملف عبر HTTPS فقط (معامل اختياري) لا يمكن للغات البرمجة من جهة العميل مثل جافاسكربت الوصول لهذا الملف (معامل اختياري) يمكن الوصول لملف تعريف الارتباط المُنشأ أو المُعدَّل في الطلبات اللاحقة فقط (عند تطابق المسار والمجال) لأنّ المتغير ذو النطاق العام العالي ‎$_COOKIE لا يُملأ بالبيانات الجديدة مباشرةً. التحقق من ضبط ملف تعريف ارتباط يمكن استخدام الدالة isset()‎ للمتغير ذو النطاق العام العالي ‎$_COOKIE للتحقق من ضبط ملف تعريف ارتباط. // PHP <7.0 if (isset($_COOKIE['user'])) { // ضُبط ملف تعريف الارتباط echo 'User is ' . $_COOKIE['user']; else { // لم يُضبط ملف تعريف الارتباط echo 'User is not logged in'; } // PHP 7.0+ echo 'User is ' . $_COOKIE['user'] ?? 'User is not logged in'; حذف ملف تعريف ارتباط يمكنك ضبط الختم الزمني لصلاحية ملف تعريف الارتباط إلى وقت ما في الماضي ليُحذف هذا الملف: setcookie('user', '', time() - 3600, '/'); تأكد عند حذف ملف تعريف ارتباط من توافق المعاملات path وdomain للدالة setcookie()‎ مع معلومات الملف الذي تريد حذفه وإلا سيُنشأ ملف تعريف ارتباط جديد تنتهي صلاحيته مباشرةً. ومن الجيّد أيضًا أن تلغي قيمة المتغير ‎$_COOKIE في حال كانت الصفحة الحالية تستخدمه: unset($_COOKIE['user']); استعادة ملف تعريف ارتباط استعادة وعرض ملف تعريف ارتباط اسمه User يمكن أن نستعيد قيمة ملف تعريف ارتباط باستخدام المتغير العام ‎$_COOKIE، فمثلًا إذا كان لدينا ملف تعريف ارتباط اسمه user نستعيده بالشكل التالي: echo $_COOKIE['user']; عميل بروتوكول SOAP (‏Simple Object Access Protocol) المعاملات التي تأخذها الدالة SoapClient هي: المعامل تفاصيل ‎$wsdl رابط WSDL (اختصار إلى Web Services Description Language أو NULL إذا كنا نستخدم النمط non-WSDL ‎$options مصفوفة خيارات للصنف SoapClient‏‏. يتطلب النمط Non-WSDL‎ ضبط كل من location و uri، وكل الخيارات الأخرى اختيارية النمط WSDL ننشئ كائنًا جديدًا من الصنف SoapClient ونمرر رابط ملف WSDL ومصفوفة خيارات اختيارية. // WSDL إنشاء كائن عميل جديد باستخدام رابط $soap = new SoapClient('https://example.com/soap.wsdl', [ // هذه المصفوفة وقيمها اختيارية 'soap_version' => SOAP_1_2, 'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP, 'cache_wsdl' => WSDL_CACHE_BOTH, # تساعد هذه الخيارات في تنقيح الأخطاء 'trace' => TRUE, 'exceptions' => TRUE ]); ثم نستخدم الكائن ‎$soap لاستدعاء توابع SOAP. $result = $soap->requestData(['a', 'b', 'c']); النمط Non-WSDL يشبه هذا النمط النمط السابق إلا أننا نمرر NULL مكان ملف WSDL ويجب أن نتأكد من ضبط الخيارات location وuri. $soap = new SoapClient(NULL, [ 'location' => 'https://example.com/soap/endpoint', 'uri' => 'namespace' ]); خرائط الصنف (Classmaps) يمكنك عند إنشاء عميل SOAP أن تضبط المفتاح classmap في مصفوفة الإعدادات، يعرّف هذا المفتاح الأنواع المعرفة في WSDL والتي يجب أن تُربط إلى الأصناف الفعلية بدلًا من الصنف الافتراضي StdClass، وعندها تحصل على استكمال تلقائي للحقول واستدعاءات التوابع على هذه الأصناف بدلًا من الاضطرار إلى تخمين الحقول المضبوطة للصنف StdClass الاعتيادي. class MyAddress { public $country; public $city; public $full_name; public $postal_code; // or zip_code public $house_number; } class MyBook { public $name; public $author; // SOAP بإضافة دوال مفيدة إلى الكائنات المُعادة من عمليات classmap يسمح لنا public function getShortDescription() { return "{$this->name}, written by {$this->author}"; } } $soap_client = new SoapClient($link_to_wsdl, [ // Other parameters "classmap" => [ // ‫ يعيد الصنف كسلسلة نصية ‎::class "Address" => MyAddress::class, "Book" => MyBook::class, ] ]); بعد ضبط خريطة الصنف، عندما تجري عملية معينة تعيد النوع Address أو Book فإنّ SoapClient ينشئ نسخة من ذلك الصنف ويملأ الحقول بالبيانات ويعيده عند استدعاء العملية. بفرض لدينا التابع getAddress(1234)‎ يعيد كائن Address وفقًا للمعرّف (ID) الموجود في قاعدة البيانات والتابع getBook(1234)‎ يعيد كائن Book وفقًا للمعرّف (ID) الموجود في قاعدة البيانات. $address = $soap_client->getAddress(1234); // (1) echo $address->country; $book = $soap_client->getBook(124); // (2) echo $book->getShortDescription(); // (3) $author = $soap_client->getAuthor(1234); // (4) echo $author->name; في الموضع (1) أصبح المتغير ‎$address‎ من النوع MyAddress وذلك حسب classmap. في الموضع (2) لا يمكننا استخدام دوال أخرى معرفة في الصنف MyBook. في الموضع (3) أي نوع معرف في WSDL غير معرف في classmap سيصبح كائن StdClass عادي. في الموضع (4) لا يوجد خريطة صنف للنوع Author لذا فإنّ ‎$author هو StdClass عادي، أي لا يزال بإمكاننا الوصول للحقول لكن دون استكمال تلقائي ولا دوال مخصصة لتعريف الكائنات. تتبع طلب ورد SOAP قد نحتاج أحيانًا لتتبع ما الذي يُرسل ويُستقبل في طلب SOAP، تعيد التوابع التالية صيغة XML الموجودة في الطلب والرد: SoapClient::__getLastRequest() SoapClient::__getLastRequestHeaders() SoapClient::__getLastResponse() SoapClient::__getLastResponseHeaders() لنفرض مثلًا أنه لدينا الثابت ENVIRONMENT وعندما تُضبط قيمته لتكون DEVELOPMENT فإننا نريد طباعة كل المعلومات إذا رمى استدعاء التابع getAddress خطأً، يمكن أن يكون الحل كالتالي: try { $address = $soap_client->getAddress(1234); } catch (SoapFault $e) { if (ENVIRONMENT === 'DEVELOPMENT') { var_dump( $soap_client->__getLastRequestHeaders() $soap_client->__getLastRequest(), $soap_client->__getLastResponseHeaders(), $soap_client->__getLastResponse() ); } ... } خادم SOAP أساسي function test($x) { return $x; } $server = new SoapServer(null, array('uri' => "http://test-uri/")); $server->addFunction("test"); $server->handle(); استخدام مكتبة cURL المعامل تفاصيل curl_init يهيئ جلسة cURL url الرابط الذي سيُستخدم في طلب cURL curl_setopt يضبط خيارًا لنقل cURL ch مُعالج cURL (يعيد قيمة من curl_init()‎) ‏ option خيارات CURLOPT_XXX التي تريد ضبطها، يمكنك الاطلاع على لائحة الخيارات من هنا والقيم المقبولة value القيمة التي ستُضبط على معالِج cURL‎‎ للخيار المُعطى curl_exec يؤدي جلسة cURL ch مُعالج cURL (يعيد قيمة من curl_init()‎) ‏ curl_close يغلق جلسة cURL ch مُعالج cURL (يعيد قيمة من curl_init()‎) ‏ الاستخدام الأساسي (طلبات GET) تعد cURL أداة لنقل البيانات بصياغة رابط (URL)، وتدعم HTTP وFTP وSCP والعديد من البروتوكولات الأخرى (بدءًا من الإصدار 7.19.4). تذكر أنّك تحتاج إلى تنصيب وتمكين إضافة cURL لاستخدامها. // (1) if(!extension_loaded("curl")) { die("cURL extension not loaded! Quit Now."); } // بدء السكربت // (2) $curl = curl_init(); // ضبط الرابط والخيارات الأخرى curl_setopt($curl, CURLOPT_URL, "http://www.example.com"); // تنفيذ وتمرير النتيجة إلى المتصفح curl_exec($curl); // cURL إغلاق مورد curl_close($curl); في الموضع (1) سكربت بسيط يفحص إن كانت إضافة cURL محمّلة أم لا. في الموضع (2) إنشاء مورد cURL جديد و‎$curl هو معالج المورد. طلبات POST يمكنك استخدام cURL إذا أردت محاكاة إجراء نموذج HTML يستخدم الطريقة POST: // في مصفوفة POST بيانات $post = [ 'a' => 'apple', 'b' => 'banana' ]; // POST جديد مع رابط إلى cURL ننشئ مورد $ch = curl_init('http://www.example.com'); // ليقرأ الخرج CURLOPT_RETURNTRANSFER نضبط المعامل curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // POST نمرر بيانات curl_setopt($ch, CURLOPT_POSTFIELDS, $post); // $response ننفذ الطلب ونحصل على الخرج في المتغير $response = curl_exec($ch); // نغلق الاتصال curl_close($ch); استخدام ملفات تعريف الارتباط يمكن لمكتبة cURL أن تحافظ على ملفات تعريف الارتباط المستقبَلة في الردود للاستخدام مع الطلبات اللاحقة، نحقق هذا بسطر شيفرة واحد من أجل ملف تعريف ارتباط جلسة بسيط في الذاكرة: curl_setopt($ch, CURLOPT_COOKIEFILE, ""); في الحالات التي يكون مطلوب فيها الحفاظ على ملفات تعريف الارتباط بعدأن يُدمَّر مُعالِج cURL يمكنك وصف ملف لتخزينها: curl_setopt($ch, CURLOPT_COOKIEJAR, "/tmp/cookies.txt"); وعندما تحتاج استخدامها ثانيةً تمررها كملف تعريف ارتباط: curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/cookies.txt"); ومع ذلك تذكر أنّك لا تحتاج هاتين الخطوتين إذا لم تكن تريد حمل ملفات تعريف الارتباط بين معالجات cURL المختلفة، إذ أنّ ما تحتاجه في معظم الحالات هو ضبط CURLOPT_COOKIEFILE إلى سلسلة فارغة. يمكن أن تستخدم معالجة ملف تعريف الارتباط لاستعادة موارد من موقع إنترنت يتطلب تسجيل دخول مثلًا، يكون هذا الإجراء من خطوتين عادةً، الأولى إرسال البيانات بالطريقة POST إلى صفحة تسجيل الدخول: <?php // cURL إنشاء معالج $ch = curl_init(); // (curl_init() ضبط الرابط (أو يمكن تمريره إلى curl_setopt($ch, CURLOPT_URL, "https://www.example.com/login.php"); // POST لتكون HTTP ضبط طريقة curl_setopt($ch, CURLOPT_POST, true); // يمكّن ضبط هذا الخيار إلى سلسلة فارغة معالجة ملف تعريف الارتباط لكنه لا يحمّله من ملف curl_setopt($ch, CURLOPT_COOKIEFILE, ""); // ضبط القيم التي نريد إرسالها curl_setopt($ch, CURLOPT_POSTFIELDS, array( "username"=>"joe_bloggs", "password"=>"$up3r_$3cr3t", )); // إعادة جسم الرد curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // إرسال الطلب $result = curl_exec($ch); الخطوة الثانية (بعد انتهاء فحص الخطأ القياسي) عادةً هي رد GET بسيط، الشيء المهم هو إعادة استخدام معالِج cURL الموجود للطلب الثاني، يضمن هذا أنّ ملفات إعادة الارتباط من الرد الأول ستُضمَّن تلقائيًا في الطلب الثاني. لن نستدعي curl_init()‎ في الشيفرة التالية: // تغيير الرابط curl_setopt($ch, CURLOPT_URL, "https://www.example.com/show_me_the_foo.php"); // GET تغيير الطريقة إلى curl_setopt($ch, CURLOPT_HTTPGET, true); // إرسال الطلب $result = curl_exec($ch); // cURL النهاية مع curl_close($ch); // $result تستطيع هنا إضافة الشيفرة التي تريدها لمعالجة الشيفرة السابقة هي مجرد مثال عن معالجة ملف تعريف ارتباط ?أما في التطبيقات العملية تكون الأمور أكثر تعقيدًا، يجب أن تنفّذ عادةً طلب GET أولي لصفحة تسجيل الدخول لتسحب مفتاح تسجيل الدخول الذي تحتاج تضمينه في طلب POST. قد تعطّل بعض المواقع عميل cURL المعتمد على سلسلة نصية لوكيل المستخدم مما يتطلب منك تغييرها. استخدام multi_curl لإرسال عدة طلبات POST قد نحتاج أحيانًا إجراء عدة طلبات POST لنقطة نهائية (endpoint) واحدة أو أكثر، يمكننا استخدام multi_curl للتعامل مع هذه الحالة. ننشئ في البداية عدد الطلبات الذي نحتاجه بنفس طريقة المثال البسيط السابق ونضعها في مصفوفة، نستخدم curl_multi_init ونضيف معالِج لكل منها. نستخدم في المثال التالي نقطتين نهائيتين: // POST مصفوفة بيانات لإرسالها بالطريقة $request_contents = array(); // مصفوفة روابط $urls = array(); // cURL مصفوفة معالجات $chs = array(); //الأول $request_contents محتوى المصفوفة $request_contents[] = [ 'a' => 'apple', 'b' => 'banana' ]; // الثاني $request_contents محتوى المصفوفة $request_contents[] = [ 'a' => 'fish', 'b' => 'shrimp' ]; // ضبط الروابط $urls[] = 'http://www.example.com'; $urls[] = 'http://www.example2.com'; // multi_curl وإضافتهم إلى cURL إنشاء مصفوفة من معالجات $mh = curl_multi_init(); foreach ($urls as $key => $url) { $chs[$key] = curl_init($url); curl_setopt($chs[$key], CURLOPT_RETURNTRANSFER, true); curl_setopt($chs[$key], CURLOPT_POST, true); curl_setopt($chs[$key], CURLOPT_POSTFIELDS, $request_contents[$key]); curl_multi_add_handle($mh, $chs[$key]); } بعدها نستخدم curl_multi_exec لإرسال الطلبات: // تنفيذ الطلبات $running = null; do { curl_multi_exec($mh, $running); } // الحصول على الردود foreach(array_keys($chs) as $key){ $error = curl_error($chs[$key]); $last_effective_URL = curl_getinfo($chs[$key], CURLINFO_EFFECTIVE_URL); $time = curl_getinfo($chs[$key], CURLINFO_TOTAL_TIME); $response = curl_multi_getcontent($chs[$key]); // get results if (!empty($error)) { echo "The request $key return a error: $error" . "\n"; } else { echo "The request to '$last_effective_URL' returned '$response' in $time seconds." . "\n"; } curl_multi_remove_handle($mh, $chs[$key]); } // إغلاق المعالِج الحالي curl_multi_close($mh); يمكن أن يعيد هذا المثال التالي: يعيد الطلب إلى 'http://www.example.com' القيمة 'fruits' في ثانيتين. يعيد الطلب إلى 'http://www.example2.com' القيمة 'seafood' في 5 ثواني. إرسال بيانات متعددة الأبعاد وعدة ملفات في طلب واحد باستخدام cURL بفرض لدينا نموذج كما في الصورة، ونريد إرسال البيانات إلى خادم الويب الخاص بنا عبر AJAX ومن هناك إلى سكربت يُنفَّذ على خادم خارجي. لدينا مدخلات عادية وحقل متعدد الخيارات وحقل إرفاق ملف حيث يمكننا تحميل عدة ملفات. بفرض نجاح طلب AJAX POST نحصل على البيانات التالية في الموقع: // print_r($_POST) Array ( [first_name] => John [last_name] => Doe [activities] => Array ( [0] => soccer [1] => hiking ) ) وتبدو الملفات كما يلي: // print_r($_FILES) Array ( [upload] => Array ( [name] => Array ( [0] => my_photo.jpg [1] => my_life.pdf ) [type] => Array ( [0] => image/jpg [1] => application/pdf ) [tmp_name] => Array ( [0] => /tmp/phpW5spji [1] => /tmp/phpWgnUeY ) [error] => Array ( [0] => 0 [1] => 0 ) [size] => Array (‎ [0] => 647548 [1] => 643223 ) ) ) نريد الآن إرسال هذه البيانات والملفات إلى خادم خارجي باستخدام cURL مع الصنف CurlFile، وبما أنّ cURL تقبل مصفوفة بسيطة فقط وليس مصفوفة متعددة الأبعاد فيجب أن نستخدم مصفوفة ‎$_POST أولًا. نستخدم هذه الدالة مثلًا للقيام بذلك مما يعطيك الخرج: // print_r($new_post_array) Array ( [first_name] => John [last_name] => Doe [activities[0]] => soccer [activities[1]] => hiking ) الخطوة التالية هي إنشاء كائنات CurlFile للملفات المحمّلة باستخدام الحلقة التالية: $files = array(); foreach ($_FILES["upload"]["error"] as $key => $error) { if ($error == UPLOAD_ERR_OK) { $files["upload[$key]"] = curl_file_create( $_FILES['upload']['tmp_name'][$key], $_FILES['upload']['type'][$key], $_FILES['upload']['name'][$key] ); } } الدالة curl_file_create مساعدة للصنف CurlFile وتنشئ كائنات CurlFile، نحفظ هذه الكائنات في المصفوفة ‎$files مع مفاتيح بالأسماء "upload[0]‎" و"upload[1]‎" لملفينا. نحتاج الآن لجمع المصفوفة المعدَّلة مع مصفوفة الملفات ونحفظها في ‎$data: $data = $new_post_array + $files; الخطوة الأخيرة هي إرسال طلب cURL: $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_POST => 1, CURLOPT_URL => "https://api.externalserver.com/upload.php", CURLOPT_RETURNTRANSFER => 1, CURLINFO_HEADER_OUT => 1, CURLOPT_POSTFIELDS => $data )); $result = curl_exec($ch); curl_close ($ch); بما أنّ ‎$data هي مصفوفة بسيطة (مسطّحة) فإنّ cURL ترسل هذا الطلب بالطريقة POST مع نوع المحتوى multipart/form-data ويمكنك الحصول على المعلومات والملفات المرسلة مع ‎$_POST و‎$_FILES‎ في الملف upload.php على الخادم الخارجي كما تفعل عادةً. إنشاء وإرسال طلب مع طريقة مخصصة تدعم cURL افتراضيًا طلبات GET وPOST ومن الممكن أيضًا إرسال طلبات بطرق مخصصة مثل DELETE أوPUT أوPATCH (أو حتى طرق غير معيارية) باستخدام المعامل CURLOPT_CUSTOMREQUEST. // DELETE إنشاء طلب $method = 'DELETE'; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); $content = curl_exec($ch); curl_close($ch); ترويسات HTTP المخصصة Get وSet في PHP إرسال ترويسة الطلب $uri = 'http://localhost/http.php'; $ch = curl_init($uri); curl_setopt_array($ch, array( CURLOPT_HTTPHEADER => array('X-User: admin', 'X-Authorization: 123456'), CURLOPT_RETURNTRANSFER =>true, CURLOPT_VERBOSE => 1 )); $out = curl_exec($ch); curl_close($ch); // طباعة خرج الرد echo $out; قراءة الترويسة المخصصة print_r(apache_request_headers()); الخرج: Array ( [Host] => localhost [Accept] => */* [X-User] => admin [X-Authorization] => 123456 [Content-Length] => 9 [Content-Type] => application/x-www-form-urlencoded ) يمكننا أيضًا إرسال الترويسة باستخدام الصياغة التالية: curl --header "X-MyHeader: 123" www.google.com ترجمة -وبتصرف- للفصول [SOAP Client - SOAP Server - Using cURL in PHP - Sessions - Cookies] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: فضاء الأسماء (namespaces) في PHP المقال السابق: السلسلة Serialization في PHP
  8. الدالة serialize()‎ في PHP لها الشكل العام التالي: serialize ( mixed $value ) يعبّر المعامل value عن القيمة التي نريد سَلسَلتها، تعالج الدالة serialize()‎ كل الأنواع، عدا الأنواع الموردية (resource-type)، كما يمكنك سَلسَلة المصفوفات التي تحتوي مراجع إلى نفسها، المراجع الدائرية داخل المصفوفة/الكائن التي تُسلسلها ستُخزَّن أيضًا وأي مرجع آخر سيُفقد. تحاول PHP عند سَلسَلة الكائن استدعاء الدالة ‎__sleep()‎ قبل القيام بالسَلسَلة وذلك للسماح للكائن بالقيام بالتنظيف حتى اللحظة الأخيرة قبل بدء السَلسَلة. وكذلك عند استعادة كائن باستخدام unserialize()‎ تُستدعى الدالة ‎__wakeup()‎. يكون لأعضاء الكائن الخاصة اسم الصنف ملحقًا باسم العضو، وللأعضاء المحمية العلامة '*' ملحقًا باسم العضو. تحتوي هذه القيم المُلحقة على وحدات بايت فارغة على كلا الجانبين. سَلسَلة أنواع مختلفة تولّد تمثيلًا قابلًا للتخزين لقيمة ما، ويعدّ هذا مفيدًا لتخزين أو تمرير قيم PHP دون خسارة نوعها وبنيتها. نستخدم unserialize()‎ لنعيد السلسلة النصية المُسَلسَلة إلى قيمة PHP من جديد. سَلسَلة سلسلة نصية $string = "Hello world"; echo serialize($string); // s:11:"Hello world"; سَلسَلة قيمة عشرية $double = 1.5; echo serialize($double); // d:1.5; سَلسَلة عدد صحيح $integer = 65; echo serialize($integer); // i:65; سَلسَلة قيمة منطقية $boolean = true; echo serialize($boolean); // b:1; $boolean = false; echo serialize($boolean); // b:0; سَلسَلة القيمة null $null = null; echo serialize($null); // N; سَلسَلة مصفوفة $array = array( 25, 'String', 'Array'=> ['Multi Dimension','Array'], 'boolean'=> true, // ‫‎⁢$obj من المثال في الأعلى 'Object'=>$obj, null, 3.445 ); /* سيرمي هذا خطأً فادحًا $array['function'] = function() { return "function"; }; */ echo serialize($array); // a:7:{i:0;i:25;i:1;s:6:"String";s:5:"Array";a:2:{i:0;s:15:"Multi Dimension";i:1;s:5:"Array";}s:7:"boolean";b:1;s:6:"Object";O:3:"abc":1:{s:1:"i";i:1;}i:2;N;i:3;d:3.4449999999999998;} سَلسَلة كائن تحاول PHP عند سَلسَلة كائن استدعاء الدالة ‎__sleep()‎ قبل القيام بالسَلسَلة وذلك للسماح للكائن بالقيام بالتنظيف حتى اللحظة الأخيرة قبل بدء السَلسَلة. وكذلك عند استعادة كائن باستخدام unserialize()‎ تُستدعى الدالة ‎__wakeup()‎. class abc { var $i = 1; function foo() { return 'hello world'; } } $object = new abc(); echo serialize($object); // O:3:"abc":1:{s:1:"i";i:1;} لاحظ أنّه لا يمكن سَلسَلة المغلِّفات (Closures) $function = function () { echo 'Hello World!'; }; $function(); // prints "hello!" $serializedResult = serialize($function); // Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed' مشاكل الحماية مع فك السَلسَلة يمكن أن يكون استخدام الدالة unserialize لفك سَلسَلة دخل المستخدم خطيرًا. تحذير: لا تمرّر دخل المستخدم غير الموثوق به للدالة unserialize، يمكن أن يؤدي فك السَلسَلة إلى تحميل التعليمات البرمجية وتنفيذها بسبب استنساخ الكائن والتحميل التلقائي وقد يتمكن المستخدم الضار من استغلال ذلك. استخدم تنسيق تبادل بيانات قياسي مثل JSON (باستخدام json_decode()‎ وjson_encode()‎) إذا كنت تحتاج إلى تمرير بيانات مسَلسَلة إلى المستخدم. الهجمات الممكنة: حقن كائن PHP ثغرة على مستوى التطبيق يمكن أن تسمح للمهاجم بأداء أنواع مختلفة من الهجمات الخبيثة مثل حقن الشيفرة وحقن SQL واجتياز المسار (Path Traversal) وتطبيق حجب الخدمة (Denial of Service) بالاعتماد على السياق. تحدث الثغرة عندما لا يُعقَّم دخل المستخدم قبل تمريره إلى دالة unserialize. بما أنّ PHP تسمح بسَلسَلة الكائن، يمكن للمهاجمين تمرير سلاسل نصية مسَلسَلة مخصصة إلى استدعاء الدالة unserialize المعرضة للخطر مما يؤدي إلى حقن كائن (أو كائنات) PHP عشوائي في نطاق التطبيق. يجب تحقيق شرطين لاستغلال ثغرة حقن كائن PHP بنجاح: أن يكون للتطبيق صنف ينفّذ تابع PHP سحري (مثل ‎__wakeup أو ‎__destruct) يمكن أن يُستخدم لتنفيذ هجمات ضارة أو بدء سلسلة POP (‏Property Oriented Programming). أن يُصرَّح عن جميع الأصناف المستخدمة خلال الهجوم عند استدعاء الدالة unserialize المعرضة للخطر وإلا يجب دعم التحميل التلقائي للصنف لمثل هذه الكائنات. مثال1 - هجوم اجتياز المسار (Path Traversal Attack): يظهر المثال التالي صنف PHP مع تابع ‎__destruct القابل للاستغلال: class Example1 { public $cache_file; function __construct() { // ‫بعض الشيفرة هنا... } function __destruct() { $file = "/var/www/cache/tmp/{$this->cache_file}"; if (file_exists($file)) @unlink($file); } } // ‫بعض الشيفرة هنا… $user_data = unserialize($_GET['data']); // ‫بعض الشيفرة هنا... قد يكون المهاجم في هذا المثال قادرًا على حذف ملف عشوائي عبر هجوم اجتياز المسار مثل طلب المسار التالي: http://testsite.com/vuln.php?data=O:8:"Example1":1:{s:10:"cache_file";s:15:"../../index.php";} مثال2 - هجوم حقن الشيفرة (Code Injection attack): يظهر المثال التالي صنف PHP مع تابع ‎__wakeup القابل للاستغلال: class Example2 { private $hook; function __construct() { // ‫بعض الشيفرة هنا... } function __wakeup() { if (isset($this->hook)) eval($this->hook); } } // ‫بعض الشيفرة هنا… $user_data = unserialize($_COOKIE['data']); // ‫بعض الشيفرة هنا... يمكن أن يكون المهاجم في هذا المثال قادرًا على القيام بهجوم حقن الشيفرة بإرسال طلب HTTP مثل الطلب التالي: GET /vuln.php HTTP/1.0 Host: testsite.com Cookie: data=O%3A8%3A%22Example2%22%3A1%3A%7Bs%3A14%3A%22%00Example2%00hook%22%3Bs%3A10%3A%22phpinfo%28%29% 3B%22%3B%7D Connection: close حيث أُنشئ معامل ملف تعريف الارتباط "data" من قِبل السكربت التالي: class Example2 { private $hook = "phpinfo();"; } print urlencode(serialize(new Example2)); سَلسَلة/ فك سَلسَلة كائن تعيد الدالة serialize()‎ سلسلة نصية تتضمن تمثيل مجرى بايتات لأي قيمة يمكن تخزينها في PHP. يمكنك استخدام الدالة unserialize()‎ لإعادة تكوين قيمة المتغير الأصلية. نكتب الشيفرة التالية لسَلسَلة (serialize) كائن: serialize($object); نكتب الشيفرة التالية لفك سَلسَلة (Unserialize) كائن: unserialize($object) مثال: $array = array(); $array["a"] = "Foo"; $array["b"] = "Bar"; $array["c"] = "Baz"; $array["d"] = "Wom"; $serializedArray = serialize($array); echo $serializedArray; /* a:4:{s:1:"a";s:3:"Foo";s:1:"b";s:3:"Bar";s:1:"c";s:3:"Baz";s:1:"d";s:3:"Wom";} */ الواجهة القابلة للسَلسَلة لم تعد الأصناف التي تنفّذ هذه الواجهة تدعم التوابع السحرية ‎__sleep()‎ و‎__sleep()‎. يُستدعى تابع السَلسَلة عندما يحتاج كائن إلى السَلسَلة، وهذا لا يشغّل التابع ‎__destruct()‎ وليس له آثار جانبية أخرى ما لم يُبرمج داخل تابع السَلسَلة، عند فك سَلسَلة البيانات يُعرف الصف ويُستدعى تابع unserialize()‎ المناسب كبانٍ بدلًا من استدعاء ‎__construct()‎. إذا كنت تحتاج إلى تنفيذ الباني القياسي فيمكنك القيام بذلك في التابع. الاستخدام الأساسي class obj implements Serializable { private $data; public function __construct() { $this->data = "My private data"; } public function serialize() { return serialize($this->data); } public function unserialize($data) { $this->data = unserialize($data); } public function getData() { return $this->data; } } $obj = new obj; $ser = serialize($obj); var_dump($ser); // string(38) "C:3:"obj":23:{s:15:"My private data";}" $newobj = unserialize($ser); var_dump($newobj->getData()); // string(15) "My private data" ترجمة -وبتصرف- للفصول [Object Serialization - Serialization] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: الجلسات وملفات تعريف الارتباط ومكتبة cURL في PHP المقال السابق: الأصناف (Classes) والكائنات (Objects) في PHP
  9. تستخدم الأصناف والكائنات لجعل الشيفرة أكثر فعالية وأقل تكرارًا عن طريق تجميع الوظائف المتشابهة. يستخدم الصنف لتعريف الوظائف وبنى المعطيات المستخدمة لإنشاء كائنات، ثم تُنشأ الكائنات باستخدام البنية المعرّفة مسبقًا. ثوابت الصنف توفر ثوابت الصنف (Class constants) آلية لحمل القيم الثابتة في البرنامج، أي أنّها توفر طريقة لإعطاء اسم (وتربطه بالتحقق وقت التصريف) لقيمة ما مثل 3.14 أو "Apple"، يمكن أن تُعرَّف ثوابت الصنف باستخدام الكلمة المفتاحية const فقط ولا يمكن استخدام الدالة define لهذا الغرض. فمثلًا قد يكون من المناسب أن يكون لديك تمثيل مختصر للقيمة π في برنامجك، إليك الصنف التالي مع قيم const يوفر طريقة بسيطة لحمل مثل هذه القيم. class MathValues { const PI = M_PI; const PHI = 1.61803; } $area = MathValues::PI * $radius * $radius; يمكن الوصول لثوابت الصنف مثل المتغيرات الساكنة باستخدام عامل العمود المزدوج :: (الذي يسمى عامل دقة النطاق) على الصنف، لكن على عكس المتغيرات الساكنة فإنّ متغيرات الصنف لها قيم ثابتة وقت التصريف ولا يمكن إعادة إسناد قيم لها، (أي أنّ إضافة MathValues::PI = 7 للشيفرة السابقة ستنتج خطأً فادحًا). تفيد ثوابت الصنف أيضًا في تعريف الأشياء الداخلية للصنف التي قد تحتاج للتغيير لاحقًا (لكن لا تتغير بشكلٍ مستمر بما يكفي لتخزينها في قاعدة بيانات مثلًا)، يمكن الرجوع إليها داخليًا باستخدام عامل تحليل النطاق (Scope Resolution Operator) ‏self الذي يعمل في التنفيذات الساكنة والمستنسخة. class Labor { // الوقت اللازم لبناء عنصر مقدّرًا بالساعات const LABOR_UNITS = 0.26; // المبلغ الذي يتقاضاه العمّال في الساعة const LABOR_COST = 12.75; public function getLaborCost($number_units) { return (self::LABOR_UNITS * self::LABOR_COST) * $number_units; } } يمكن أن تحتوي ثوابت الصنف في إصدارات PHP السابقة للإصدار 5.6 على قيم عددية فقط، بينما يمكننا استخدام التعابير مع الثوابت بدءًا من الإصدار 5.6 أي أنّه يمكننا استخدام التعليمات الرياضية والسلاسل النصية مع الربط. class Labor { /** ندفع للعمال في الساعة: الأجر في الساعة * عدد الساعات لإنجاز العمل */ const LABOR_COSTS = 12.75 * 0.26; public function getLaborCost($number_units) { return self::LABOR_COSTS * $number_units; } } بدءًا من الإصدار PHP 7.0، يمكن للثوابت المعرّفة باستخدام define أن تحتوي مصفوفات. define("BAZ", array('baz')); تعدّ ثوابت الصنف مفيدة ليس فقط لتخزين المفاهيم الرياضية، فمثلًا إذا كنت تحضّر شطيرة قد يكون من المناسب أن يكون لديك صنف Pie واحد قادر على أخذ أنواع مختلفة من الفاكهة. class Pie { protected $fruit; public function __construct($fruit) { $this->fruit = $fruit; } } بعدها يمكننا استخدام الصنف Pie كالتالي: $pie = new Pie("strawberry"); المشكلة التي تظهر هنا هي أنّه عند استنساخ الصنف Pie لا يوجد إرشادات للقيم المقبولة، فمثلًا عند صنع شطيرة "boysenberry" قد تُكتب مع خطأ إملائي "boisenberry"، بالإضافة إلى ذلك قد لا تريد دعم صنع شطيرة خوخ. لذا سيكون من المفيد أن يكون لدينا قائمة معرّفة مسبقًا تحوي أنواع الفاكهة المقبولة موجودة في مكان ما بحيث يمكن ملاحظتها وليكن صنف نسميه Fruit: class Fruit { const APPLE = "apple"; const STRAWBERRY = "strawberry"; const BOYSENBERRY = "boysenberry"; } $pie = new Pie(Fruit::STRAWBERRY); يوفر وضع القيم المقبولة في قائمة ثوابت الصنف دليلًا للقيم التي يقبلها التابع، كما يضمن أن لا يقبل المصرّف الأخطاء الإملائية التي قد تحدث، فبينما يقبل المصرّف new Pie('aple')‎ وnew Pie('apple')‎ ستنتج الشيفرة new Pie(Fruit::APLE)‎ خطأ تصريف (compiler error). كما إنّ استخدام ثوابت الصنف يعني أنّ القيمة الفعلية للثابت يمكن تعديلها في مكان واحد وأيّ شيفرة تستخدم هذا الثابت ستتأثر بهذا التغيير تلقائيًا. إنّ الطريقة الشائعة للوصول إلى ثابت الصنف هي MyClass::CONSTANT_NAME، لكن يمكن الوصول إليها بالطريقة التالية: echo MyClass::CONSTANT; $classname = "MyClass"; // PHP 5.3.0 بدءًا من echo $classname::CONSTANT; تسمى عادةً ثوابت الصنف بأحرف كبيرة مع استخدام الشرطة السفلية _ للفصل بين الكلمات، بالرغم من أنّه يمكن استخدام أي تسمية صحيحة لتسمية ثابت الصنف. بدءًا من الإصدار PHP 7.1 يمكن تعريف ثوابت الصنف بمرئيات مختلفة عن النطاق العام الافتراضي، أي يمكن تعريف ثوابت صنف خاصة (private) ومرئية (protected) لمنعها من التسرّب غير الضروري إلى النطاق العام، مثال: class Something { const PUBLIC_CONST_A = 1; public const PUBLIC_CONST_B = 2; protected const PROTECTED_CONST = 3; private const PRIVATE_CONST = 4; } define مقابل ثوابت الصنف بالرغم من أنّ هذه البنية صحيحة: function bar() { return 2; }; define('BAR', bar()); لكن سيظهر خطأ إذا حاولت القيام بذلك مع ثوابت الصنف: function bar() { return 2; }; class Foo { const BAR = bar(); // Constant expression contains invalid operations } لكن يمكنك كتابة التالي: function bar() { return 2; }; define('BAR', bar()); class Foo { const BAR = BAR; } استخدام ‎::class‎ للحصول على اسم الصنف قدمت PHP 5.5 الصياغة ‎::class للحصول على اسم الصنف الكامل?? مع مراعاة نطاق فضاء الاسم وتعليمات use. namespace foo; use bar\Bar; echo json_encode(Bar::class); // "bar\\Bar" echo json_encode(Foo::class); // "foo\\Foo" echo json_encode(\Foo::class); // "Foo" تعمل الشيفرة السابقة بشكلٍ صحيح حتى لو لم تُعرَّف الأصناف، وتعدّ هذه الصياغة مفيدة للدوال التي تتطلّب اسم صنف فيمكن استخدامها مع الدالة class_exists مثلًا للتحقق من أنّ صنف ما معرّف. لن تظهر أخطاء في الشيفرة التالية بغض النظر عن القيمة المعادة: class_exists(ThisClass\Will\NeverBe\Loaded::class, false); الأصناف المجردة الصنف المجرد (abstract class) هو الصنف الذي لا يمكن استنساخه، ويمكن له أن يعرّف توابعًا مجردة أي توابع لها تعريف فقط من دون جسم: abstract class MyAbstractClass { abstract public function doSomething($a, $b); } يمكن أن توسَّع الأصناف المجردة بصنف ابن يحقق تنفيذ هذه التوابع المجردة، إذ أنّ الهدف الأساسي للصنف المجرد هو تعريف قالب يسمح للأصناف الأبناء بالوراثة منه وفق بنية معينة، إليك المثال التالي الذي ننفذ فيه واجهة Worker بعد تعريفها: interface Worker { public function run(); } لتسهيل تطوير المزيد من تطبيقات الواجهة Worker ننشئ الصنف المجرد worker المزود بالتابع ‎run()‎ من الواجهة ويحدد بعض التوابع المجردة لتُملأ في الأصناف الأبناء: abstract class AbstractWorker implements Worker { protected $pdo; protected $logger; public function __construct(PDO $pdo, Logger $logger) { $this->pdo = $pdo; $this->logger = $logger; } public function run() { try { $this->setMemoryLimit($this->getMemoryLimit()); $this->logger->log("Preparing main"); $this->prepareMain(); $this->logger->log("Executing main"); $this->main(); } catch (Throwable $e) { // تلتقط وتعيد رمي كل الأخطاء حتى يتمكن العامل من تسجيلها $this->logger->log("Worker failed with exception: {$e->getMessage()}"); throw $e; } } private function setMemoryLimit($memoryLimit) { ini_set('memory_limit', $memoryLimit); $this->logger->log("Set memory limit to $memoryLimit"); } abstract protected function getMemoryLimit(); abstract protected function prepareMain(); abstract protected function main(); } وضعنا في البداية تابعًا مجردًا getMemoryLimit()‎، يحتاج أي صنف يرث الصنف AbstractWorker أن ينفذ هذا التابع ويعيد قيد الذاكرة. ثم يضبط AbstractWorker قيد الذاكرة ويسجّله. يستدعي الصنف AbstractWorker بعد ذلك التوابع prepareMain()‎ وmain()‎، ثم تُجمّع كل استدعاءات التوابع هذه في كتلة try-catch فإذا رمى أي من التوابع المجردة المعرفة من الصنف الابن استثناءً سنلتقط هذا الاستثناء ونسجّله ونعيد رميه، وهذا يمنع كل الأصناف الأبناء من الاضطرار إلى تنفيذ هذا الإجراء. لنعرّف صنف ابن يوسّع الصنف AbstractWorker: class TranscactionProcessorWorker extends AbstractWorker { private $transactions; protected function getMemoryLimit() { return "512M"; } protected function prepareMain() { $stmt = $this->pdo->query("SELECT * FROM transactions WHERE processed = 0 LIMIT 500"); $stmt->execute(); $this->transactions = $stmt->fetchAll(); } protected function main() { foreach ($this->transactions as $transaction) { // (1) $stmt = $this->pdo->query("UPDATE transactions SET processed = 1 WHERE id ={$transaction['id']} LIMIT 1"); $stmt->execute(); } } } قد يُرمى استثناء PDO أو MYSQL في الموضع (1) لكن يُعالج من قبل AbstractWorker. كما تلاحظ فإنّ TransactionProcessorWorker كان سهل التنفيذ حيث كان علينا فقط تحديد قيد الذاكرة والقلق بشأن الإجراءات الفعلية التي نحتاج القيام بها. لا نحتاج إلى معالجة الأخطاء في TransactionProcessorWorker لأنّها تُعالج في الصنف AbstractWorker. ملاحظة مهمة: عند الوراثة من صنف مجرد فإنّ كل التوابع المجردة الموجودة في الصنف الأب يجب أن يعرّفها الابن (أو يعيد التصريح عنها بأنّها توابع مجردة)، كما يجب تعريف هذه التوابع بنفس المرئية (أو بمرئية أقل تحديدًا)، فمثلًا إذا عُرِّف التابع المجرد أنه محمي فإنّ تنفيذه يجب أن يعرّف أنّه محمي أو عام (ولا يمكن تعريفه أنّه خاص). سيُرمى خطأً فادحًا إذا لم تعرّف توابع الأصناف الآباء المجردة في الصنف الابن كالخطأ التالي: Fatal error: Class X contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (X::x) in الربط الساكن المتأخر يمكن استخدام الربط الساكن المتأخر بدءًا من الإصدار PHP 5.3 للتحكم في الصنف الذي اُستدعيت منه خاصيّة ساكنة أو تابع ساكن. وأُضيف للتغلب على مشكلة عامل تحليل النطاق ‎self::‎، إليك الشيفرة التالية: class Horse { public static function whatToSay() { echo 'Neigh!'; } public static function speak() { self::whatToSay(); } } class MrEd extends Horse { public static function whatToSay() { echo 'Hello Wilbur!'; } } قد تتوقع أنّ الصنف MrEd سيعيد تعريف الدالة whatToSay()‎ في الصنف الأب لكن عند التنفيذ ستكون النتيجة غير متوقعة: Horse::speak(); // Neigh! MrEd::speak(); // Neigh! المشكلة هي أنّ self::whatToSay();‎ يمكن أن يشير إلى الصنف Horse فقط أي أنّه لن يتأثر بالصنف MrEd، لن نواجه هذه المشكلة إذا بدلنا إلى عامل تحليل النطاق static::‎، وبهذه الطريقة سيتأثر الصنف بالنسخة التي تستدعيه فنحصل على الوراثة التي نتوقعها: class Horse { public static function whatToSay() { echo 'Neigh!'; } public static function speak() { // ربط ساكن متأخر static::whatToSay(); } } Horse::speak(); // Neigh! MrEd::speak(); // Hello Wilbur! فضاء التسمية والتحميل التلقائي يعمل التحميل التلقائي (Autoloading) تقنيًا بتنفيذ رد نداء عندما يُطلب صنف PHP ولا يُعثر عليه، عادةً ما تحاول ردود النداء تحميل هذه الأصناف. يمكن أن يُفهم التحميل التلقائي بشكلٍ عام أنّه محاولة لتحميل ملفات PHP (خاصةً ملفات الصنف حيث يكون الملف المصدري مخصصًا لصنف معين) من المسارات المناسبة حسب الاسم الكامل المؤهل (FQN) عند الحاجة إلى الصنف. بفرض لدينا الأصناف التالية: ملف الصنف application\controllers\Base: <?php namespace application\controllers { class Base {...} } ملف الصنف application\controllers\Control: <?php namespace application\controllers { class Control {...} } ملف الصنف application\models\Page: <?php namespace application\models { class Page {...} } تتوضع هذه الأصناف في المجلد المصدر وفقًا لأسمائها الكاملة المؤهلة كالتالي: المجلد المصدر applications controllers Base.php Control.php models Page.php تجعل هذه الطريقة من الممكن الحصول على مسار ملف الصنف برمجيًا وفق الاسم الكامل المؤهل (FQN) باستخدام هذه الدالة: function getClassPath(string $sourceFolder, string $className, string $extension = ".php") { return $sourceFolder . "/" . str_replace("\\", "/", $className) . $extension; } لاحظ أنّ / هو الفاصل بين المجلدات في ويندوز. تسمح لنا الدالة spl_autoload_register بتحميل الصنف الذي نحتاجه باستخدام دالة معرفة من قبل المستخدم: const SOURCE_FOLDER = __DIR__ . "/src"; spl_autoload_register(function (string $className) { $file = getClassPath(SOURCE_FOLDER, $className); if (is_readable($file)) require_once $file; }); يمكن لهذه الدالة أن توسَّع لاستخدام توابع قيم تراجعية (fallback) للتحميل: const SOURCE_FOLDERS = [__DIR__ . "/src", "/root/src"]); spl_autoload_register(function (string $className) { foreach(SOURCE_FOLDERS as $folder) { $extensions = [ // ؟src/Foo/Bar.php5_int64 هل يوجد لدينا ".php" . PHP_MAJOR_VERSION . "_int" . (PHP_INT_SIZE * 8), // ؟src/Foo/Bar.php7 هل يوجد لدينا ".php" . PHP_MAJOR_VERSION, // ؟src/Foo/Bar.php_int64 هل يوجد لدينا ".php" . "_int" . (PHP_INT_SIZE * 8), // ؟src/Foo/Bar.phps هل يوجد لدينا ".phps" // ؟src/Foo/Bar.php هل يوجد لدينا ".php" ]; foreach($extensions as $ext) { $path = getClassPath($folder, $className, $extension); if(is_readable($path)) return $path; } } }); لاحظ أنّ PHP لا تحاول تحميل الصنف كلما حُمِّل ملف يستخدمه، فقد تحمّله في وسط السكربت أو عند توقف الدوال وهذا إحدى الأسباب التي تجعل المطورين، خاصة الذين يستخدمون التحميل التلقائي، يتجنبوا استبدال ملفات المصدر التنفيذية في وقت التنفيذ خاصةً في ملفات phar. مرئية التوابع والخاصيّات لدينا ثلاث أنواع للمرئية يمكن استخدامها للتوابع (دوال الصنف/الكائن) والخاصيّات (متغيرات الصنف/الكائن) داخل الصنف مما يوفر تحكم بالوصول لهذه التوابع والخاصيّات. public: المرئية العامة يعني التصريح عن تابع أو خاصيّة بأنّهما public أنّه يمكن الوصول إليهما من الصنف الذي صرّح عنهما والأصناف التي ترثه وأيّ كائنات أو أصناف خارجية أو شيفرة خارج هرمية الصنف، مثال: class MyClass { // خاصيّة public $myProperty = 'test'; // تابع public function myMethod() { return $this->myProperty; } } $obj = new MyClass(); echo $obj->myMethod(); // test echo $obj->myProperty; // test Protected: المرئية المحمية يعني التصريح عن تابع أو خاصيّة بأنّهما protected أنّه يمكن الوصول إليهما من الصنف الذي صرّح عنهما والأصناف التي ترثه، ولا يمكن الوصول إليهما من الكائنات أو الأصناف الخارجية أو الشيفرة خارج هرمية الصنف. إذا استخدمتهما شيفرة لا تملك إمكانية الوصول إليهما سيُرمى خطأ. مثال: class MyClass { protected $myProperty = 'test'; protected function myMethod() { return $this->myProperty; } } class MySubClass extends MyClass { public function run() { echo $this->myMethod(); } } $obj = new MySubClass(); // MyClass::myMethod(); يستدعي السطر التالي $obj->run(); // test $obj->myMethod(); // خطأ // Fatal error: Call to protected method MyClass::myMethod() from context '' يوضح المثال السابق أنّه يمكن الوصول إلى العناصر المحمية من ضمن نطاقها، أي "الذي يوجد في المنزل يمكن الوصول إليه من داخل المنزل فقط". Private: المرئية الخاصة يعني التصريح عن تابع أو خاصيّة بأنّهما private أنّه يمكن الوصول إليهما من الصنف الذي صرّح عنهما فقط (ولا يمكن الوصول إليهما من الأصناف الفرعية). لاحظ أنّ الكائنات من نفس النوع ستصل إلى العناصر الخاصة والمحمية لبعضها لبعض حتى لو لم تكن نفس النسخ. class MyClass { private $myProperty = 'test'; private function myPrivateMethod() { return $this->myProperty; } public function myPublicMethod() { return $this->myPrivateMethod(); } public function modifyPrivatePropertyOf(MyClass $anotherInstance) { $anotherInstance->myProperty = "new value"; } } class MySubClass extends MyClass { public function run() { echo $this->myPublicMethod(); } public function runWithPrivate() { echo $this->myPrivateMethod(); } } $obj = new MySubClass(); $newObj = new MySubClass(); $obj->run(); // (1) // test $obj->modifyPrivatePropertyOf($newObj); $newObj->run(); // new value echo $obj->myPrivateMethod(); // خطأ // Fatal error: Call to private method MyClass::myPrivateMethod() from context '' echo $obj->runWithPrivate(); // خطأ // Fatal error: Call to private method MyClass::myPrivateMethod() from context 'MySubClass' في الموضع (1) سيُستدعى MyClass::myPublicMethod()‎ ثم MyClass::myPrivateMethod()‎. كما تلاحظ يمكن الوصول إلى التابع/الخاصيّة الخاصة فقط من الصنف الذي عرفهما. الواجهات الواجهات (Interfaces) هي تعريفات لواجهة برمجة التطبيقات (API) العامة ويجب أن تنفذها الأصناف، فهي تعمل "كعقود" تحدد ما الذي يجب أن تفعله الأصناف دون تحديد كيفية فعله. يشبه تعريف الواجهة تعريف الصنف لكن نستبدل كلمة interface بكلمة class: interface Foo { } يمكن أن تحتوي الواجهات توابع و/أو ثوابت ولكن ليس سمات، ثوابت الواجهة لها نفس قيود ثوابت الصنف، وتوابع الواجهة مجردة ضمنيًّا: interface Foo { const BAR = 'BAR'; public function doSomething($param1, $param2); } ملاحظة: يجب ألا تصرّح الواجهات عن بواني (constructors) أو هوادم (destructors) لأن تفاصيل التنفيذ هذه على مستوى الصنف. التطبيق أي صنف يحتاج لتنفيذ واجهة يجب أن يستخدم الكلمة المفتاحية implements، للقيام بذلك يحتاج الصنف أن يوفر تنفيذًا لكل تابع مصرَّح عنه في الواجهة مع الحفاظ على نفس البصمة، ويمكن للصنف الواحد أن ينفذ أكثر من واجهة واحدة في نفس الوقت. interface Foo { public function doSomething($param1, $param2); } interface Bar { public function doAnotherThing($param1); } class Baz implements Foo, Bar { public function doSomething($param1, $param2) { // ... } public function doAnotherThing($param1) { // ... } } لا تحتاج الأصناف المجردة عندما تنفذ الواجهات إلى تنفيذ كل التوابع، وأي تابع لا يُنفَّذ في الصنف الأساسي يجب تنفيذه في الصنف الذي يوسّعه: abstract class AbstractBaz implements Foo, Bar { // تنفيذ جزئي للواجهة المطلوبة... public function doSomething($param1, $param2) { // ... } } class Baz extends AbstractBaz { public function doAnotherThing($param1) { // ... } } لاحظ أنّ تطبيق الواجهة ميزة موروثة، عند توسيع صنف ينفذ واجهة فأنت لا تحتاج لإعادة التصريح عنها في الصنف الابن لأنّ هذا محقق ضمنيًّا. ملاحظة: قبل الإصدار PHP 5.3.9 لم يكن بإمكان الصنف أن ينفذ واجهتين بهما تابع بنفس الاسم لأنّ هذا قد يسبب التباسًا، بينما تسمح الإصدارات الأحدث بذلك طالما أنّ التوابع المكررة ليس لها نفس البصمة. الوراثة يمكن إنشاء علاقة وراثة بين الواجهات مثل الأصناف باستخدام الكلمة المفتاحية extends، الفرق الأساسي هو أنّ الوراثة المتعددة مسموحة في الواجهات: interface Foo { } interface Bar { } interface Baz extends Foo, Bar { } أمثلة إليك مثال بسيط لواجهة مركبة، يمكن للمركبات أن تتحرك إلى الأمام والخلف. interface VehicleInterface { public function forward(); public function reverse(); ... } class Bike implements VehicleInterface { public function forward() { $this->pedal(); } public function reverse() { $this->backwardSteps(); } protected function pedal() { ... } protected function backwardSteps() { ... } ... } class Car implements VehicleInterface { protected $gear = 'N'; public function forward() { $this->setGear(1); $this->pushPedal(); } public function reverse() { $this->setGear('R'); $this->pushPedal(); } protected function setGear($gear) { $this->gear = $gear; } protected function pushPedal() { ... } ... } ثم ننشئ صنفين ينفذان الواجهة: الدراجة والسيارة، يوجد اختلاف كبير داخليًا بين الدراجة والسيارة لكن كلّ منهما مركبة ويجب أن تنفذ نفس التوابع العامة التي توفرها واجهة المركبة VehicleInterface. تسمح ميزة التلميح عن النوع (Typehinting) للتوابع والدوال أن تطلب واجهات. بفرض أنّه لدينا صنف مرآب للسيارات يحتوي كل أنواع المركبات. class ParkingGarage { protected $vehicles = []; public function addVehicle(VehicleInterface $vehicle) { $this->vehicles[] = $vehicle; } } بما أنّ الدالة addVehicle تتطلب متغيرًا ‎$vehicle من النوع واجهة VehicleInterface - ليس تنفيذ حقيقي - يمكننا أن نجعل الدخل من النوع دراجة أو سيارة مما يمكّن الصنف ParkingGarage من معالجتها واستخدامها. الكلمة المفتاحية Final تمنع الكلمة المفتاحية Final الأصناف الأبناء من إعادة تعريف تابع مسبوقة بالكلمة final، إذا عُرِّف الصنف أنّه نهائي (final) فلا يمكن توسيعه. التابع النهائي class BaseClass { public function test() { echo "BaseClass::test() called\n"; } final public function moreTesting() { echo "BaseClass::moreTesting() called\n"; } } class ChildClass extends BaseClass { public function moreTesting() { echo "ChildClass::moreTesting() called\n"; } } // Fatal error: Cannot override final method BaseClass::moreTesting() الصنف النهائي final class BaseClass { public function test() { echo "BaseClass::test() called\n"; } // من غير الهام هنا إن عُرِّفت الدالة أنّها نهائية أو لم تُعرَّف final public function moreTesting() { echo "BaseClass::moreTesting() called\n"; } } class ChildClass extends BaseClass { } // Fatal error: Class ChildClass may not inherit from final class (BaseClass) الثوابت النهائية لا تستخدم الكلمة المفتاحية final لثوابت الصنف في PHP، على عكس لغة Java. ويمكن استخدام الكلمة المفتاحية const بدلًا منها. لماذا يجب أن أستخدم الكلمة المفتاحية final؟ منع حدوث سلسلة وراثة ضخمة. تشجيع التكوّن إجبار المطور على التفكير بواجهة برمجة التطبيقات العامة للمستخدم إجبار المطور على تقليص واجهة برمجة التطبيقات العامة للكائن يمكن أن نجعل الصف المعرّف أنّه final موسِّعًا تحطم الكلمة المفتاحية extends التغليف لا تحتاج إلى المرونة بإمكانك تغيير الشيفرة بحريّة لماذا يجب أن أتجنب استخدام الكلمة المفتاحية final؟ تعمل الأصناف النهائية بشكلٍ فعال فقط في وجود الافتراضات التالية: يوجد تجريد (واجهة) ينفذه الصنف النهائي كل واجهة برمجة التطبيقات العامة للصنف النهائي هي جزء من تلك الواجهة التحميل التلقائي ‏بالطبع أنت لا ترغب في استخدام include وrequire في كل مرة تريد استخدام صنف أو وراثته، لأنّ هذا قد يكون سيئًا وسهل النسيان لذا وفرت PHP التحميل التلقائي (autoloading). ما هو التحميل التلقائي بالضبط؟ يعني التحميل التلقائي أنّك لا تحتاج للحصول على الملف المخزّن فيه الصنف المطلوب إنّما تقوم PHP "تلقائيًا" بتحميله. كيف يمكن أن أقوم بالتحميل التلقائي في PHP دون شيفرة من طرف ثالث؟ يمكننا استخدام الدالة ‎_autoload‎_ للقيام بالتحميل التلقائي لكن يفضّل استخدام الدالة spl_autoload_register، تستخدم PHP هذه الدوال عندما لا تجد صنف ما معرفًا ضمن النطاق المعطى. إنّ إضافة التحميل التلقائي إلى مشروع موجود لا يشكل مشكلة بما أنّ الأصناف المعرّفة (باستخدام require) ستعمل كما في السابق. ستستخدم الأمثلة التالية دوال مجهولة فإذا كنت تستخدم إصدار PHP ‎‎< 5.3 يمكنك تعريف الدالة وتمرير اسمها كوسيط للدالة spl_autoload_register. أمثلة spl_autoload_register(function ($className) { $path = sprintf('%s.php', $className); if (file_exists($path)) { include $path; } else { // الملف غير موجود } }); تحاول الشيفرة السابقة تضمين اسم الملف الذي هو نفس اسم الصنف ومضافًا إليه اللاحقة "‎.php" باستخدام sprintf، إذا احتجنا لتحميل FooBar فإنّها تبحث عن FooBar.php وتضمّنه. وبالطبع يمكن توسيع هذه الشيفرة لتناسب احتياجات مشروعنا، إذا اُستخدمت _ في اسم الصف للتجميع مثل User_Post وUser_Image فمن الممكن تخزين هذه الأصناف في مجلد اسمه "User" كالتالي: spl_autoload_register(function ($className) { // استبدال بالمحرف / أو \ وذلك حسب نظام التشغيل $path = sprintf('%s.php', str_replace('_', DIRECTORY_SEPARATOR, $className) ); if (file_exists($path)) { include $path; } else { // الملف غير موجود } }); سيُحمّل الصنف User_Post الآن من المسار "User/Post.php" وهكذا. يمكن أن تتناسب الدالة spl_autoload_register مع الاحتياجات المختلفة، فإذا كانت كل ملفات أصنافك مُسمّاة بالشكل "class.CLASSNAME.php" ستوجد تداخلات مختلفة (UserPostContent => "User/Post/Content.php"‎). إذا كنت تريد تقنية تحميل تلقائي أكثر تفصيلًا ولا تريد استخدام composer يمكنك أن تعمل دون إضافة مكتبات من طرف ثالث. spl_autoload_register(function ($className) { $path = sprintf('%1$s%2$s%3$s.php', realpath(dirname(__FILE__)), DIRECTORY_SEPARATOR, strtolower( // استبدال بالمحرف / أو \ وذلك حسب نظام التشغيل str_replace('_', DIRECTORY_SEPARATOR, $className) ) ); if (file_exists($path)) { include $path; } else { throw new Exception( sprintf('Class with name %1$s not found. Looked in %2$s.', $className, $path ) ); } }); في الشيفرة السابقة ‎%1$s للحصول على المسار المطلق و‎%2$s إما / أو \ وذلك حسب نظام التشغيل و‎%3$s لعدم الاهتمام فيما إذا كانت الأحرف صغيرة أو كبيرة عند إنشاء الملفات. باستخدام المحمّلات التلقائية مثل هذه يمكنك كتابة الشيفرة التالية: // spl_autoload_register المكان الذي عُرِّف فيه require_once './autoload.php'; $foo = new Foo_Bar(new Hello_World()); استخدام الأصناف: class Foo_Bar extends Foo {} class Hello_World implements Demo_Classes {} ستضمّن هذه الأمثلة الأصناف من foo/bar.php، ‏foo.php، ‏hello/world.php وdemo/classes.php. ‎استدعاء باني أب عند استنساخ ابن من المشاكل الشائعة للأصناف الأبناء هي أنّه إذا احتوى كل من الأب والابن تابعًا بانيًا ‎__construct()‎ فسيُنفَّذ باني الصنف الابن فقط، قد تحتاج أحيانًا إلى تنفيذ باني الصنف الأب من داخل الصنف الابن عندها يمكنك استخدام عامل تحليل النطاق ‎::parent بالشكل التالي: parent::__construct(); إليك مثالًا واقعيًا: class Foo { function __construct($args) { echo 'parent'; } } class Bar extends Foo { function __construct($args) { parent::__construct($args); } } سيؤدي تنفيذ الشيفرة السابقة إلى تنفيذ باني الصنف الأب وبالتالي ستُنفَّذ تعليمة echo. الربط الديناميكي يعدّ الربط الديناميكي (ويسمىّ أيضًا إعادة تعريف التابع) مثالًا للتعددية الشكلية وقت التنفيذ والتي تحدث عندما تحتوي عدة أصناف على تنفيذات مختلفة لنفس التابع لكن الكائن الذي سيستدعيه التابع لا يُعرف حتى وقت التنفيذ، ويعد هذا مفيدًا إذا حدد شرط معين أيّ صنف سيُستخدم لأداء وظيفة ما حيث هذه الوظيفة لها نفس الاسم في كلا الصنفين. interface Animal { public function makeNoise(); } class Cat implements Animal { public function makeNoise { $this->meow(); } ... } class Dog implements Animal { public function makeNoise { $this->bark(); } ... } class Person { const CAT = 'cat'; const DOG = 'dog'; private $petPreference; private $pet; public function isCatLover(): bool { return $this->petPreference == self::CAT; } public function isDogLover(): bool { return $this->petPreference == self::DOG; } public function setPet(Animal $pet) { $this->pet = $pet; ‏ } public function getPet(): Animal { return $this->pet; } } if($person->isCatLover()) { $person->setPet(new Cat()); } else if($person->isDogLover()) { $person->setPet(new Dog()); } $person->getPet()->makeNoise(); في المثال السابق لا يُعرف الصنف Animal ‏(Dog|Cat) الذي سينفذ التابع makeNoise إلى وقت التنفيذ وفقًا للخاصيّة ضمن الصنف User. ‎$this، ‏self، ‏static ونمط المفردة (singleton) نستخدم ‎$this للإشارة إلى الكائن الحالي وself للإشارة إلى الصنف الحالي، وبمعنى آخر نستخدم ‎$this->member للعناصر غير الساكنة وself::$member‎ للعناصر الساكنة. لاحظ الفرق في المثال التالي الذي تستخدم فيه الدالة sayHello()‎ ‏‎$this وتستخدم الدالة ‎sayGoodbye()‎ ‏self: class Person { private $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } public function getTitle() { return $this->getName()." the person"; } public function sayHello() { echo "Hello, I'm ".$this->getTitle()."<br/>"; } public function sayGoodbye() { echo "Goodbye from ".self::getTitle()."<br/>"; } } class Geek extends Person { public function __construct($name) { parent::__construct($name); } public function getTitle() { return $this->getName()." the geek"; } } $geekObj = new Geek("Ludwig"); $geekObj->sayHello(); $geekObj->sayGoodbye(); تشير الكلمة المفتاحية static إلى أي صنف في الهرمية تستدعي عليه التابع، وتسمح بإعادة استخدام أفضل لخصائص الصنف الساكنة عندما تورَّث الأصناف. إليك المثال التالي: class Car { protected static $brand = 'unknown'; public static function brand() { return self::$brand."\n"; } } class Mercedes extends Car { protected static $brand = 'Mercedes'; } class BMW extends Car { protected static $brand = 'BMW'; } echo (new Car)->brand(); echo (new BMW)->brand(); echo (new Mercedes)->brand(); نتيجة الشيفرة السابقة: unknown unknown unknown وذلك لأنّ self تشير إلى الصنف Car بغض النظر من أين اُستدعت brand()‎، لذا فأنت تحتاج لاستخدام static للإشارة إلى الصنف الصحيح وذلك بالشكل التالي: class Car { protected static $brand = 'unknown'; public static function brand() { return static::$brand."\n"; } } class Mercedes extends Car { protected static $brand = 'Mercedes'; } class BMW extends Car { protected static $brand = 'BMW'; } echo (new Car)->brand(); echo (new BMW)->brand(); echo (new Mercedes)->brand(); ستنتج هذه الشيفرة الخرج المطلوب والذي هو: unknown BMW Mercedes نمط المفردة (singleton) إذا كان لديك كائن مكلّف بإنشاء اتصال مع مورد خارجي أو يمثّل اتصال إلى مورد خارجي معين فأنت تحتاج إلى إعادة استخدام اتصال بقاعدة البيانات حيث لا يوجد تجمّع اتصال أو مقبس إلى نظام آخر، عندها يمكنك أن تستخدم الكلمات المفتاحية static وself في الصنف ليصبح بنمط المفردة. يوجد آراء مختلفة حول نمط المفردة فيما إذا كان يجب استخدامه أو لا ولكن يبقى له استخداماته. class Singleton { private static $instance = null; public static function getInstance(){ if(!isset(self::$instance)){ self::$instance = new self(); } return self::$instance; } private function __construct() { …. } } عرّفنا في الشيفرة السابقة خاصيّة ساكنة خاصة ‎$instance لتحمل مرجعية الكائن، وبما أنّها ساكنة فستُشارك بين كل كائنات هذا النوع. يستخدم التابع getInstance()‎ طريقة التهيئة المُرجأة (lazy instantiation) لتأخير إنشاء الكائنات إلى آخر لحظة ممكنة لأنك لا تريد أن تمتلئ الذاكرة بكائنات غير مستخدمة، وأيضًا تحافظ على الوقت وعلى المعالج عند تحميل الصفحة إذ لا تُحمَّل كائنات غير ضرورية. يتحقق التابع فيما إذا ضُبط الكائن وأُنشئ أم لا وتعيده وهذا يضمن إنشاء كائن واحد فقط من هذا النوع. كما عرّفنا الباني أنه خاص (private) حتى نضمن أنه من غير الممكن إنشاؤه من الخارج باستخدام الكلمة المفتاحية new. إذا كنت تحتاج للوراثة من هذا الصنف فقط غيّر الكلمات المفتاحية private إلى protected. لاستخدام هذا الكائن تكتب الشيفرة التالية: $singleton = Singleton::getInstance(); تعريف صنف أساسي يتألف الكائن في PHP من متغيرات ودوال، وتنتمي الكائنات إلى صنف يعرّف المتغيرات والدوال التي تحتويها كل كائنات هذا الصنف، صياغة تعريف الصنف هي كالتالي: class Shape { public $sides = 0; public function description() { return "A shape with $this->sides sides."; } } عندما يُعرَّف صنف يمكنك الاستنساخ منه بالشكل التالي: $myShape = new Shape(); يمكن الوصول لمتغيرات ودوال الكائن بالطريقة التالية: $myShape = new Shape(); $myShape->sides = 6; print $myShape->description(); // A shape with 6 sides الباني يمكن أن تعرّف الأصناف تابع باني خاص ‎__construct()‎ يُنفَّذ كجزء من إنشاء الكائن، ويستخدم عادةً لإعطاء القيم الابتدائية لكائن: class Shape { public $sides = 0; public function __construct($sides) { $this->sides = $sides; } public function description() { return "A shape with $this->sides sides."; } } $myShape = new Shape(6); print $myShape->description(); // A shape with 6 sides توسيع صنف آخر يمكن لتعريفات الصنف أن توسّع تعريفات صنف موجود وتضيف المزيد من المتغيرات والدوال وتعدّل في المتغيرات والدوال المعرفة في الصنف الأب، إليك صنف يوسّع الصنف في المثال السابق: class Square extends Shape { public $sideLength = 0; public function __construct($sideLength) { parent::__construct(4); $this->sideLength = $sideLength; } public function perimeter() { return $this->sides * $this->sideLength; } public function area() { return $this->sideLength * $this->sideLength; } } يتضمن الصنف Square متغيرات ودوال لكل من الصنفين Square وShape. $mySquare = new Square(10); print $mySquare->description()/ // A shape with 4 sides print $mySquare->perimeter() // 40 print $mySquare->area() // 100 الأصناف المجهولة قُدِّمت الأصناف المجهولة في الإصدار PHP 7 لتمكين إنشاء نسخة واحدة بسيطة من الصنف، ويمكن لها أن تأخذ وسائط الباني وتوسّع أصناف أخرى وتنفّذ واجهات وتستخدم السمات (traits) مثل الأصناف العادية تمامًا، والنموذج الأساسي لها كالتالي: new class("constructor argument") { public function __construct($param) { var_dump($param); } }; // string(20) "constructor argument" لا يوفر تداخل صنف مجهول ضمن صنف آخر إمكانية الوصول إلى التوابع أو الخاصيّات الخاصة أو المحمية للصنف الخارجي، يمكن الوصول إلى الخاصيات والتوابع المحمية للصنف الخارجي بتوسيعه من قِبل صنف مجهول ويمكن الوصول إلى الخاصيات والتوابع الخاصة للصنف الخارجي بتمريرها إلى باني الصنف المجهول. مثال: class Outer { private $prop = 1; protected $prop2 = 2; protected function func1() { return 3; } public function func2() { // الخاصة $this->prop التمرير عبر الخاصيّة return new class($this->prop) extends Outer { private $prop3; public function __construct($prop) { $this->prop3 = $prop; } public function func3() { // (1) return $this->prop2 + $this->func1() + $this->prop3; } }; } } echo (new Outer)->func2()->func3(); // 6 في الموضع (1) الوصول إلى الخاصيّة المحمية Outer::$prop2 والتابع المحمي Outer::func1()‎ والخاصية المحلية self::$prop3 الخاصة من Outer::$prop. ترجمة -وبتصرف- للفصل [Classes and Objects] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: السلسلة Serialization في PHP المقال السابق: صيغة JSON وXML في PHP
  10. الدالة json_encode تأخذ المعاملات التالية: المعامل تفاصيل value القيمة المطلوب ترميزها، يمكن أن تكون من أي نوع عدا الموارد ويجب ترميز جميع بيانات السّلاسل UTF-8 النصيّة بصيغة options خيارات على شكل قناع بتي وهي: JSON_HEX_AMP JSON_HEX_APOS JSON_HEX_QUOT JSON_HEX_TAG JSON_NUMERIC_CHECK JSON_PRETTY_PRINT JSON_UNESCAPED_SLASHES JSON_FORCE_OBJECT JSON_PRESERVE_ZERO_FRACTION JSON_UNESCAPED_UNICODE JSON_PARTIAL_OUTPUT_ON_ERROR ويوجد وصف لسلوك هذه الثوابت في صفحة JSON ثوابت‎ depth يحدد العمق الأقصى، ويجب أن يكون أكبر من الصّفر table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } الدالة json_decode فتأخذ المعاملات التالية: المعامل تفاصيل json السلسلة النصية المُراد فك ترميزها بصيغة ‏ فقط UTF-8 تعمل هذه الدّالّة مع سلاسل نصّيّة مُرمَّزة بترميز JSON assoc تُحوَّل الكائنات المُعادة من الدالة إلى مصفوفات ترابطية عندما تكون قيمته TRUE options خيارات لفك ترميز JSON على شكل قناع بتي يوجد خيار واحد مدعوم حاليًا وهو JSON_BIGINT_AS_STRING ويسمح بقلب الأعداد الصحيحة الكبيرة إلى سلاسل نصّيّة بدلًا من أعداد عشرية فك ترميز سلسلة نصية بصيغة JSON تأخذ الدالة json_decode المعامل الأول على شكل سلسلة نصية مرمّزة بصيغة JSON وتحلله إلى متغير PHP، إذا كان كائن JSON هو مصفوفة فهي تعيد كائنًا من ‎\stdClass إذا كان عنصر المستوى الأعلى في الكائن هو قاموس (dictionary) أو مصفوفة مفهرسة، وتعيد قيمًا عددية أو NULL من أجل قيم عددية معينة مثل السلاسل النصية البسيطة "true" و"false" و"null"، وتعيد NULL عند أي خطأ. تعيد الشيفرة التالية كائنًا بما أنّ عنصر المستوى الأعلى في السلسلة النصية بصيغة JSON هو قاموس JSON: $json_string = '{"name": "Jeff", "age": 20, "active": true, "colors": ["red", "blue"]}'; $object = json_decode($json_string); printf('Hello %s, You are %s years old.', $object->name, $object->age); // Hello Jeff, You are 20 years old. تعيد الشيفرة التالية مصفوفة بما أنّ عنصر المستوى الأعلى في السلسلة النصية بصيغة JSON هو مصفوفةJSON: $json_string = '["Jeff", 20, true, ["red", "blue"]]'; $array = json_decode($json_string); printf('Hello %s, You are %s years old.', $array[0], $array[1]); يمكنك استخدام الدالة var_dump()‎ للاطلاع على أنواع وقيم كل خاصيّة للكائن الذي فُكَّ ترميزه، فإذا استخدمناها للكائن ‎$object في مثالنا السابق: var_dump($object); يكون الخرج: class stdClass#2 (4) { ["name"] => string(4) "Jeff" ["age"] => int(20) ["active"] => bool(true) ["colors"] => array(2) { [0] => string(3) "red" [1] => string(4) "blue" } } ملاحظة: حُوِّلت أنواع المتغيرات في JSON إلى مكافئاتها في PHP. نقوم بتمرير true معاملًا ثانيًا للدالة json_decode()‎ لإرجاع مصفوفة ترابطية من كائنات JSON بدلًا من إرجاع كائن. مثال: $json_string = '{"name": "Jeff", "age": 20, "active": true, "colors": ["red", "blue"]}'; $array = json_decode($json_string, true); var_dump($array); خرج الشيفرة السابقة: array(4) { ["name"] => string(4) "Jeff" ["age"] => int(20) ["active"] => bool(true) ["colors"] => array(2) { [0] => string(3) "red" [1] => string(4) "blue" } } ليس للمعامل الثاني ‎$assoc تأثير إذا كان المتغير الذي سيُرجع ليس كائنًا. ملاحظة: ستفقد التمييز بين مصفوفة فارغة وكائن فارغ إذا كنت تستخدم المعامل ‎$assoc، أي أنّ تنفيذ json_encode()‎ على الخرج المفكوك ترميزه مرةً ثانية سينتج بنية JSON مختلفة. تعيد الدالة json_decode()‎ القيمة NULL إذا كان عمق السلسلة النصية بصيغة JSON أكثر من 512 عنصر (20 عنصر في إصدارات PHP أقدم من 5.2.3 أو 128 في الإصدار 5.2.3) بالتعاود (recursion)، يمكن التحكم بهذا الحد في الإصدار 5.3 وما بعده باستخدام المعامل الثالث ‎$depth كما هو موضح في الدليل الرسمي للغة PHP: تنفذ PHP مجموعة عليا من JSON كما ذُكر في RFC 4627 - سترمّز أيضًا وتفك ترميز الأنواع العددية وNULL، تدعم RFC 4627 فقط هذه القيم عندما تكون متداخلة ضمن مصفوفة أو كائن. بالرغم من أن هذه المجموعة العليا متوافقة مع التعريف الموسع لنص JSON في RFC 7159 الأحدث (الذي يهدف إلى التفوق على RFC 4627) وECMA-404 وهذا قد يسبب مشاكل في قابلية التشغيل المتبادل مع محللات JSON التي تلتزم بدقة بالنوع RFC 4627 عند تشفير قيمة عددية. يعني هذا أنّ السلسلة النصية البسيطة ستعدّ كائن JSON صحيح في PHP مثال: $json = json_decode('"some string"', true); var_dump($json, json_last_error_msg()); الخرج: string(11) "some string" string(8) "No error" لكن السلاسل النصية البسيطة التي ليست في مصفوفة أو كائن ليست جزءًا من معيار RFC 4627، أي أن المتفحصات (checkers) على الإنترنت مثل JSLint وJSON Formatter & Validator التي تعمل بالنمط RFC 4627 ستعطيك خطأً. المعامل الثالث للدالة json_decode()‎ هو ‎$depth يحدد العمق التعاودي (القيمة الافتراضية 512)، أي عدد الكائنات المتداخلة ضمن الكائن الأصلي الذي سيُفَك تشفيره. المعامل الرابع ‎$options يقبل قيمة واحدة حاليًا وهي JSON_BIGINT_AS_STRING، أي أنّ السلوك الافتراضي للدالة (مع عدم وجود هذا المعامل) يحول القيم الصحيحة الكبيرة إلى أعداد عشرية بدلًا من سلاسل نصية. لا تُعدّ المتغيرات غير الصحيحة ذات الأحرف الكبيرة للكلمات true و‏false وnull دخلًا صحيحًا. لدينا المثال التالي: var_dump(json_decode('tRue'), json_last_error_msg()); var_dump(json_decode('tRUe'), json_last_error_msg()); var_dump(json_decode('tRUE'), json_last_error_msg()); var_dump(json_decode('TRUe'), json_last_error_msg()); var_dump(json_decode('TRUE'), json_last_error_msg()); var_dump(json_decode('true'), json_last_error_msg()); الخرج في الإصدار قبل PHP 5.6: bool(true) string(8) "No error" bool(true) string(8) "No error" bool(true) string(8) "No error" bool(true) string(8) "No error" bool(true) string(8) "No error" bool(true) string(8) "No error" أما في الإصدارات اللاحقة: NULL string(12) "Syntax error" NULL string(12) "Syntax error" NULL string(12) "Syntax error" NULL string(12) "Syntax error" NULL string(12) "Syntax error" bool(true) string(8) "No error" ينطبق الأمر أيضًا على false وnull. لاحظ أنّ الدالة json_decode()‎ تُعيد NULL إذا كان من غير الممكن تحويل السلسلة النصية. // غير صحيحة json صيغة $json = "{'name': 'Jeff', 'age': 20 }" ; $person = json_decode($json); // null ملاحظة: محاولة الوصول إلى خاصيّة عنصر ليس كائنًا تُرجع echo $person->name; echo json_last_error(); # 4 (JSON_ERROR_SYNTAX) echo json_last_error_msg(); # unexpected character ليس من الآمن أن نعتمد فقط على القيمة المُرجعة بأن تكون NULL لاكتشاف الأخطاء، فمثلًا إذا كانت السلسلة النصية بصيغة JSON لا تتضمن شيء إلا "null" فإنّ الدالة json_decode()‎ ستُرجع null حتى في حال عدم حدوث أخطاء. ترميز سلسلة نصية بصيغة JSON تحوّل الدالة json_encode مصفوفة PHP (أو كائن ينفذ الواجهة JsonSerializable بدءًا من الإصدار PHP 5.4) إلى سلسلة نصية بترميز صيغة JSON وتعيدها، وفي حال الفشل تعيد FALSE. $array = [ 'name' => 'Jeff', 'age' => 20, 'active' => true, 'colors' => ['red', 'blue'], 'values' => [0=>'foo', 3=>'bar'], ]; تُحوَّل عند الترميز أنواع بيانات PHP (السلاسل النصية والأعداد الصحيحة والقيم المنطقية) إلى مكافئاتها في JSON، وتُرمَّز المصفوفات الترابطية ككائنات JSON وعند الاستدعاء مع الوسطاء الافتراضيين تُرمَّز المصفوفات المفهرسة كمصفوفات JSON، ما لم تكن مفاتيح المصفوفة سلسلة عددية مستمرة تبدأ من الصفر، فعندها ستُرمَّز المصفوفة ككائن JSON. echo json_encode($array); الخرج: {"name":"Jeff","age":20,"active":true,"colors":["red","blue"],"values":{"0":"foo","3":"bar"}} الوسائط بدءًا من الإصدار PHP 5.3 فإنّ المعامل الثاني للدالة json_encode هو قناع بتي والذي يمكن أن يكون واحد أو أكثر من التالي، ويمكن دمجه كما هو الحال مع أي قناع بتي مع العامل الثنائي OR |. الإصدار PHP 5.3 ومابعده JSON_FORCE_OBJECT: يفرض إنشاء كائن بدلًا من مصفوفة. $array = ['Joel', 23, true, ['red', 'blue']]; echo json_encode($array); echo json_encode($array, JSON_FORCE_OBJECT); الخرج: ["Joel",23,true,["red","blue"]] {"0":"Joel","1":23,"2":true,"3":{"0":"red","1":"blue"}} JSON_HEX_TAG، ‏JSONHEXAMP، ‏JSON_HEX_APOS، ‏JSON_HEX_QUOT. تضمن الخيارات السابقة التحويلات التالية عند الترميز: الثابت الدخل الخرج JSON_HEX_TAG < \u003C JSON_HEX_TAG > \u003E JSON_HEX_AMP & \u0026 JSON_HEX_APOS ' \u0027 JSON_HEX_QUOT " \u0022 $array = ["tag"=>"<>", "amp"=>"&", "apos"=>"'", "quot"=>"\""]; echo json_encode($array); echo json_encode($array, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); الخرج: {"tag":"<>","amp":"&","apos":"'","quot":"\""} {"tag":"\u003C\u003E","amp":"\u0026","apos":"\u0027","quot":"\u0022"} الإصدار PHP 5.3 ومابعده JSON_NUMERIC_CHECK: يضمن تحويل السلاسل النصية العددية إلى أعداد صحيحة. $array = ['23452', 23452]; echo json_encode($array); echo json_encode($array, JSON_NUMERIC_CHECK); الخرج: ["23452",23452] [23452,23452] الإصدار PHP 5.4 ومابعده JSON_PRETTY_PRINT: يجعل صيغة JSON مقروءة بسهولة. $array = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]; echo json_encode($array); echo json_encode($array, JSON_PRETTY_PRINT); الخرج: {"a":1,"b":2,"c":3,"d":4} { "a": 1, "b": 2, "c": 3, "d": 4 } JSON_UNESCAPED_SLASHES: يضمّن هذا الخيار خطوط الهروب المائلة الأمامية / في الخرج: $array = ['filename' => 'example.txt', 'path' => '/full/path/to/file/']; echo json_encode($array); echo json_encode($array, JSON_UNESCAPED_SLASHES); الخرج: {"filename":"example.txt","path":"\/full\/path\/to\/file"} {"filename":"example.txt","path":"/full/path/to/file"} JSON_UNESCAPED_UNICODE: يضمّن محارف بترميز UTF8 في الخرج بدلًا من السلاسل النصية بترميز يونيكود. $blues = ["english"=>"blue", "norwegian"=>"blå", "german"=>"blau"]; echo json_encode($blues); echo json_encode($blues, JSON_UNESCAPED_UNICODE); الخرج: {"english":"blue","norwegian":"bl\u00e5","german":"blau"} {"english":"blue","norwegian":"blå","german":"blau"} الإصدار PHP 5.5 ومابعده JSON_PARTIAL_OUTPUT_ON_ERROR: يسمح بمتابعة الترميز حتى لو وُجدت بعض القيم غير قابلة للترميز. $fp = fopen("foo.txt", "r"); $array = ["file"=>$fp, "name"=>"foo.txt"]; echo json_encode($array); // no output echo json_encode($array, JSON_PARTIAL_OUTPUT_ON_ERROR); الخرج: {"file":null,"name":"foo.txt"} الإصدار PHP 5.6 ومابعده JSON_PRESERVE_ZERO_FRACTION: يضمن ترميز الأعداد العشرية كأعداد عشرية دومًا. $array = [5.0, 5.5]; echo json_encode($array); echo json_encode($array, JSON_PRESERVE_ZERO_FRACTION); الخرج: [5,5.5] [5.0,5.5] الإصدار PHP 7.1 ومابعده JSON_UNESCAPED_LINE_TERMINATORS: عندما يُستخدم مع JSON_UNESCAPED_UNICODE يعود إلى سلوك إصدارات PHP القديمة ولا يتجاوز المحرف الفاصل للأسطر U+2028 والمحرف الفاصل للفقرات U+2029. بالرغم من أن هذه المحارف صحيحة في JSON فهي غير صحيحة في جافاسكربت لذا فقد تغيّر السلوك الافتراضي للخيار JSON_UNESCAPED_UNICODE في الإصدار PHP 7.1. $array = ["line"=>"\xe2\x80\xa8", "paragraph"=>"\xe2\x80\xa9"]; echo json_encode($array, JSON_UNESCAPED_UNICODE); echo json_encode($array, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS); الخرج: {"line":"\u2028","paragraph":"\u2029"} {"line":" ","paragraph":" "} تنقيح أخطاء JSON عندما تفشل إحدى الدالتين json_encode أوjson_decode في التحليل إلى السلسلة النصية المطلوبة فإنّها تعيد false، ولا تنشر PHP أي أخطاء أو تحذيرات في هذه الحالة إنما تقع المسؤولية على المستخدم الذي يجب أن يستخدم الدالتين json_last_error()‎ وjson_last_error_msg()‎ للتحقق من حدوث أخطاء ومعالجتها وفق التطبيق (تنقيحها، عرض رسالة خطأ وغير ذلك). يظهر المثال التالي خطأً مشهورًا عند العمل مع JSON وهو الفشل في ترميز/فك ترميز سلسلة نصية بصيغة JSON (بسبب تمرير سلسلة نصية بترميز UTF-8 غير صحيح مثلًا). // غير صحيحة JSON سلسلة نصية بصيغة $jsonString = json_encode("{'Bad JSON':\xB1\x31}"); if (json_last_error() != JSON_ERROR_NONE) { printf("JSON Error: %s", json_last_error_msg()); } // JSON Error: Malformed UTF-8 characters, possibly incorrectly encoded json_last_error_msg تعيد هذه الدالة رسالة مقروءة من قبل الإنسان للخطأ الأخير الذي حدث عند محاولة ترميز/فك ترميز سلسلة نصية. تعيد هذه الدالة دومًا سلسلة نصية حتى لو لم يحدث خطأ والرسالة الافتراضية في حال عدم حدوث خطأ هي No Error، وتعيد false إذا حدث خطأ ما (غير معروف). يجب الحذر عند استخدام هذه الدالة في الحلقات لأنّه سيتم تجاوزها في كل حلقة، ويجب استخدامها فقط للحصول على الرسالة للعرض وليس لاستخدامها في تعليمات التحكم. // لا تكتب هذه الشيفرة // لأن نتيجة الدالة سلسلة نصية true النتيجة دائمًا if (json_last_error_msg()){} // ممارسة سيئة if (json_last_error_msg() != "No Error"){} // (يمكنك كتابة هذا (اختبار العدد الصحيح مع إحدى الثوابت المعرفة مسبقًا‎‏ if (json_last_error() != JSON_ERROR_NONE) { // لعرض رسالة فقط وليس للاختبار json_last_error_msg استخدمنا printf("JSON Error: %s", json_last_error_msg()); } لم تكن هذه الدالة موجودة في الإصدارات السابقة للإصدار PHP 5.5. إليك تنفيذ الترقيع المتعدد (polyfill): if (!function_exists('json_last_error_msg')) { function json_last_error_msg() { static $ERRORS = array( JSON_ERROR_NONE => 'No error', JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)', JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', JSON_ERROR_SYNTAX => 'Syntax error', JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded' ); $error = json_last_error(); return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error'; } } json_last_error()‎ تعيد هذه الدالة عددًا صحيحًا مربوطًا بإحدى ثوابت PHP المعرفة مسبقًا. الثابت المعنى JSON_ERROR_NONE لم يحدث أي خطأ JSON_ERROR_DEPTH حدث تجاوز للحد الأقصى لعمق المكدس JSON_ERROR_STATE_MISMATCH صيغة JSON غير صحيحة أو تالفة JSON_ERROR_CTRL_CHAR خطأ محرف التحكم ربما مرمَّز بشكلٍ خاطئ JSON_ERROR_SYNTAX خطأ في الصياغة (منذ الإصدار ‎PHP 5.3.3) JSON_ERROR_UTF8 محارف UTF-8 تالفة ربما مرمَّزة بشكلٍ خاطئ (منذ الإصدار‎‎PHP 5.5.0) JSON_ERROR_RECURSION مرجع تعاودي أو أكثر في القيمة المطلوب ترميزها JSON_ERROR_INF_OR_NAN قيمة غير رقمية أو لا نهائية في القيمة المطلوب ترميزها JSON_ERROR_UNSUPPORTED_TYPE القيمة المُعطاة من نوع لا يمكن ترميزه استخدام الواجهة JsonSerializable في كائن الإصدار PHP 5.4 وما بعده قد تحتاج عند بناء واجهة برمجة التطبيقات REST (‏Representational state transfer) إلى تقليل بيانات كائن ليُمرَّر إلى تطبيق العميل، ولهذا الهدف يوضّح المثال التالي كيفية استخدام الواجهة JsonSerializable. في هذا المثال يوسّع الصنف User كائن DB model من تقنية ORM الافتراضية (تقنية ربط الكائنات بالعلاقات). class User extends Model implements JsonSerializable { public $id; public $name; public $surname; public $username; public $password; public $email; public $date_created; public $date_edit; public $role; public $status; public function jsonSerialize() { return [ 'name' => $this->name, 'surname' => $this->surname, 'username' => $this->username ]; } } نضيف تنفيذ JsonSerializable للصنف بتوفير التابع jsonSerialize()‎. public function jsonSerialize() الآن عند تمرير الكائن User في وحدة تحكم التطبيق (controller) أو السكربت إلى الدالة json_encode()‎ ستحصل على مصفوفة معادة بصيغة JSON للتابع jsonSerialize()‎ بدلًا من الكائن بأكمله. json_encode($User); تعيد الشيفرة السابقة: {"name":"John", "surname":"Doe", "username" : "TestJson"} مثال قيم الخاصيات سيقلل هذا كمية البيانات المُعادة من النقطة النهائية (endpoint) لواجهة برمجة التطبيقات RESTful ويسمح باستبعاد خاصيّات الكائن من تمثيل JSON. استخدام الخاصيّات الخاصة والمحمية مع الدالة json_encode()‎ إذا أردت تجنّب استخدام JsonSerializable فمن الممكن استخدام الخاصيّات الخاصة أو المحمية لإخفاء بيانات الصنف من خرج الدالة json_encode()‎ وعندها لا يحتاج الصنف إلى تنفيذ الواجهة JsonSerializable. ترمّز الدالة json_encode()‎ الخاصيّات العامة فقط من الصنف إلى صيغة JSON. <?php class User { // الخاصيّات الخاصة تُستخدم فقط ضمن هذا الصنف private $id; private $date_created; private $date_edit; // الخاصيّات المستخدمة في الأصناف الموسّعة protected $password; protected $email; protected $role; protected $status; // مشاركة هذه الخاصيّات مع المستخدم النهائي public $name; public $surname; public $username; // هنا jsonSerialize() لا نحتاج لاستخدام } $theUser = new User(); var_dump(json_encode($theUser)); الخرج: string(44) "{"name":null,"surname":null,"username":null}" ترويسة JSON والرد المُعاد نضيف في الشيفرة التالية ترويسة مع نوع المحتوى JSON: <?php $result = array('menu1' => 'home', 'menu2' => 'code php', 'menu3' => 'about'); // json إرجاع الرد بصيغة header('Content-Type: application/json'); // تصريح الترويسة // الترميز echo json_encode($result, true); exit(); الترويسة موجودة لذا يتمكن التطبيق من اكتشاف ما البيانات المُعادة وكيف يجب معالجتها، ولا حظ أنّ ترويسة المحتوى هي فقط معلومات حول نوع البيانات المُعادة. إذا كنت تستخدم UTF-8 يمكنك كتابة: header("Content-Type: application/json;charset=utf-8"); مثال jQuery: $.ajax({ url:'url_your_page_php_that_return_json' }).done(function(data){ console.table('json ',data); console.log('Menu1: ', data.menu1); }); إنشاء ملف XML باستخدام DomDocument نحتاج لإنشاء ملف XML باستخدام الصنف DOMDocument إلى إنشاء كل الوسوم والخاصيّات باستخدام التابعين createElement()‎ وcreateAttribute()‎ اللذين ينشئان بنية XML باستخدام التابع appendChild()‎. يتضمن المثال التالي وسوم وخاصيات وقسم CDATA (‏Character Data) وفضاء اسم مختلف للوسم الثاني: $dom = new DOMDocument('1.0', 'utf-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; // إنشاء الوسوم الأساسية دون القيم $books = $dom->createElement('books'); $book_1 = $dom->createElement('book'); // إنشاء بعض الوسوم مع قيم $name_1 = $dom->createElement('name', 'PHP - An Introduction'); $price_1 = $dom->createElement('price', '$5.95'); $id_1 = $dom->createElement('id', '1'); // إنشاء وإضافة خاصية $attr_1 = $dom->createAttribute('version'); $attr_1->value = '1.0'; // إضافة الخاصية $id_1->appendChild($attr_1); // الثاني بفضاء اسم مختلف book إنشاء $namespace = 'www.example.com/libraryns/1.0'; // books تضمين بادئة فضاء الاسم في الوسم $books->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns', $namespace); $book_2 = $dom->createElementNS($namespace,'ns:book'); $name_2 = $dom->createElementNS($namespace, 'ns:name'); // name أخرى ووضعه ضمن الوسم DOMNode الذي هو نسخة CDATA إنشاء قسم $name_cdata = $dom->createCDATASection('PHP - Advanced'); $name_2->appendChild($name_cdata); $price_2 = $dom->createElementNS($namespace, 'ns:price', '$25.00'); $id_2 = $dom->createElementNS($namespace, 'ns:id', '2'); // XML إنشاء ملف $books->appendChild($book_1); $book_1->appendChild($name_1); $book_1->appendChild($price_1); $book_1->appendChild($id_1); $books->appendChild($book_2); $book_2->appendChild($name_2); $book_2->appendChild($price_2); $book_2->appendChild($id_2); $dom->appendChild($books); // في سلسلة نصية XML ملف saveXML() يعيد التابع print_r ($dom->saveXML()); ستنتج الشيفرة السابقة ملف XML التالي: <?xml version="1.0" encoding="utf-8"?> <books xmlns:ns="www.example.com/libraryns/1.0"> <book> <name>PHP - An Introduction</name> <price>$5.95</price> <id version="1.0">1</id> </book> <ns:book> <ns:name><![CDATA[PHP - Advanced]]></ns:name> <ns:price>$25.00</ns:price> <ns:id>2</ns:id> </ns:book> </books> قراءة ملف XML باستخدام DOMDocument يمكن استخدام الصنف DOMDocument بطريقة مشابهة لاستخدام SimpleXML لتحليل XML من سلسلة نصية أو من ملف XML. من سلسلة نصية: $doc = new DOMDocument(); $doc->loadXML($string); من ملف: $doc = new DOMDocument(); // استخدم مسار الملف الفعلي النسبي أو المطلق $doc->load('books.xml'); مثال عن التحليل: بفرض لدينا صيغة XML التالية: <?xml version="1.0" encoding="UTF-8"?> <books> <book> <name>PHP - An Introduction</name> <price>$5.95</price> <id>1</id> </book> <book> <name>PHP - Advanced</name> <price>$25.00</price> <id>2</id> </book> </books> هذا مثال شيفرة لتحليل الصيغة السابقة: $books = $doc->getElementsByTagName('book'); foreach ($books as $book) { $title = $book->getElementsByTagName('name')->item(0)->nodeValue; $price = $book->getElementsByTagName('price')->item(0)->nodeValue; $id = $book->getElementsByTagName('id')->item(0)->nodeValue; print_r ("The title of the book $id is $title and it costs $price." . "\n"); } سينتج عنها الخرج التالي: The title of the book 1 is PHP - An Introduction and it costs $5.95. The title of the book 2 is PHP - Advanced and it costs $25.00. الاستفادة من XML مع مكتبة SimpleXML في PHP تعد SimpleXML مكتبة قوية تحوّل السلاسل النصية بصيغة XML إلى صيغة سهلة لاستخدام كائن PHP. بفرض لدينا بنية XML التالية: <?xml version="1.0" encoding="UTF-8"?> <document> <book> <bookName>StackOverflow SimpleXML Example</bookName> <bookAuthor>PHP Programmer</bookAuthor> </book> <book> <bookName>Another SimpleXML Example</bookName> <bookAuthor>Stack Overflow Community</bookAuthor> <bookAuthor>PHP Programmer</bookAuthor> <bookAuthor>FooBar</bookAuthor> </book> </document> قراءة بياناتنا في SimpleXML نحتاج بدايةً إلى قراءة بياناتنا في SimpleXML، يمكننا ذلك باستخدام 3 طرق مختلفة. يمكننا أولًا تحميل البيانات من عقدة DOM. $xmlElement = simplexml_import_dom($domNode); الخيار الثاني لدينا هو تحميل البيانات من ملف XML. $xmlElement = simplexml_load_file($filename); أخيرًا يمكننا تحميل البيانات من متغير. $xmlString = '<?xml version="1.0" encoding="UTF-8"?> <document> <book> <bookName>StackOverflow SimpleXML Example</bookName> <bookAuthor>PHP Programmer</bookAuthor> </book> <book> <bookName>Another SimpleXML Example</bookName> <bookAuthor>Stack Overflow Community</bookAuthor> <bookAuthor>PHP Programmer</bookAuthor> <bookAuthor>FooBar</bookAuthor> </book> </document>'; $xmlElement = simplexml_load_string($xmlString); سواء اخترنا التحميل من عنصر DOM أو من ملف أو من سلسلة نصية ستحصل على متغير من الصنف SimpleXMLElement يدعى ‎$xmlElement ويمكننا الآن البدء باستخدام XML في PHP. الوصول إلى بيانات SimpleXML أسهل طريقة للوصول إلى البيانات في عنصر SimpleXMLElement هي استدعاء الخاصيّات مباشرةً، يمكننا الوصول إلى bookName كما يلي: echo $xmlElement->book->bookName; تفترض SimpleXML عند هذه النقطة أننا نريد الكتاب الأول لأننا لم نذكر بشكلٍ صريح أي كتاب نريده، ويمكننا اختيار كتاب غير الكتاب الأول بالطريقة التالية: echo $xmlElement->book[1]->bookName; ويجب ملاحظة أنّ استخدام [0] يكافئ عدم استخدام شيء أي أنّ: $xmlElement->book تعمل نفس عمل: $xmlElement->book[0] تمرير حلقة عبر XML يوجد عدة أسباب لرغبتك في تكرار حلقة عبر XML مثل أن يكون عندك عدد كبير من العناصر، الكتب في مثالنا، تريد عرضه في صفحة ويب، عندها يمكنك استخدام حلقة foreach أوحلقة for المعيارية مستفيدين من إيجابيات دالة SimpleXMLElement للعد. foreach ( $xmlElement->book as $thisBook ) { echo $thisBook->bookName } أو $count = $xmlElement->count(); for ( $i=0; $i<$count; $i++ ) { echo $xmlElement->book[$i]->bookName; } معالجة الأخطاء وصلنا الآن إلى النهاية ومن المهم أن ندرك أننا نخطئ كبشر ومن المحتمل أن نواجه أخطاء خاصةً إذا كنا نتعامل مع ملفات XML مختلفة لذا نريد معالجة هذه الأخطاء. بفرض أننا أنشأنا ملف XML ستلاحظ إذا كان هذا الملف يشبه الملف الذي أنشأناه سابقًا ستكون فيه مشكلة وهي أنّ وسم الإغلاق ‎/doc‎ بدلًا من ‎/document. <?xml version="1.0" encoding="UTF-8"?> <document> <book> <bookName>StackOverflow SimpleXML Example</bookName> <bookAuthor>PHP Programmer</bookAuthor> </book> <book> <bookName>Another SimpleXML Example</bookName> <bookAuthor>Stack Overflow Community</bookAuthor> <bookAuthor>PHP Programmer</bookAuthor> <bookAuthor>FooBar</bookAuthor> </book> </doc> بفرض أننا حمّلناه ضمن شيفرة PHP كمتغير ‎$file. libxml_use_internal_errors(true); $xmlElement = simplexml_load_file($file); if ( $xmlElement === false ) { $errors = libxml_get_errors(); foreach ( $errors as $thisError ) { switch ( $thisError->level ) { case LIBXML_ERR_FATAL: echo "FATAL ERROR: "; break; case LIBXML_ERR_ERROR: echo "Non Fatal Error: "; break; case LIBXML_ERR_WARNING: echo "Warning: "; break; } echo $thisError->code . PHP_EOL . 'Message: ' . $thisError->message . PHP_EOL . 'Line: ' . $thisError->line . PHP_EOL . 'Column: ' . $thisError->column . PHP_EOL . 'File: ' . $thisError->file; } libxml_clear_errors(); } else { echo 'Happy Days'; } ستكون النتيجة: FATAL ERROR: 76 Message: Opening and ending tag mismatch: document line 2 and doc Line: 13 Column: 10 File: filepath/filename.xml لكن بمجرد حل هذه المشكلة سنرى الخرج "Happy Days". إنشاء ملف XML باستخدام الصنف XMLWriter ننشئ نسخة كائن XMLWriter: $xml = new XMLWriter(); ثم نفتح الملف الذي نريد الكتابة فيه فمثلًا إذا أردنا الكتابة في ‎/var/www/example.com/xml/output.xml نكتب: $xml->openUri('file:///var/www/example.com/xml/output.xml'); نكتب الشيفرة التالية للبدء بالملف (إنشاء وسم بداية ملف XML): $xml->startDocument('1.0', 'utf-8'); ستؤدي إلى الخرج: <?xml version="1.0" encoding="UTF-8"?> يمكننا الآن البدء بكتابة العناصر: $xml->writeElement('foo', 'bar'); سينتج XML التالي: <foo>bar</foo> إذا كنت تحتاج شيئًا أكثر تعقيدًا من العقد البسيطة والقيم الواضحة يمكنك أن تبدأ عنصر وتضيف خاصيّات إليه قبل إنهائه: $xml->startElement('foo'); $xml->writeAttribute('bar', 'baz'); $xml->writeCdata('Lorem ipsum'); $xml->endElement(); سينتج الخرج التالي: <foo bar="baz"><![CDATA[Lorem ipsum]]></foo> قراءة ملف XML باستخدام SimpleXML يمكنك تحليل XML من سلسلة نصية أو من ملف XML. من سلسلة نصية: $xml_obj = simplexml_load_string($string); من ملف: $xml_obj = simplexml_load_file('books.xml'); مثال عن التحليل: بفرض لدينا ملف XML التالي: <?xml version="1.0" encoding="UTF-8"?> <books> <book> <name>PHP - An Introduction</name> <price>$5.95</price> <id>1</id> </book> <book> <name>PHP - Advanced</name> <price>$25.00</price> <id>2</id> </book> </books> هذا مثال شيفرة لتحليله: $xml = simplexml_load_string($xml_string); $books = $xml->book; foreach ($books as $book) { $id = $book->id; $title = $book->name; $price = $book->price; print_r ("The title of the book $id is $title and it costs $price." . "\n"); } سينتج عنها الخرج التالي: The title of the book 1 is PHP - An Introduction and it costs $5.95. The title of the book 2 is PHP - Advanced and it costs $25.00. مكتبة SimpleXML تحميل بيانات XML إلى simplexml التحميل من سلسلة نصية نستخدم simplexml_load_string لإنشاء SimpleXMLElement من سلسلة نصية: $xmlString = "<?xml version='1.0' encoding='UTF-8'?>"; $xml = simplexml_load_string($xmlString) or die("Error: Cannot create object"); لاحظ أنّه يجب استخدام or وليس || لأنّ أولوية or أعلى من =، وستُنفَّذ الشيفرة بعد or فقط إذا كانت نتيجة ‎$xml هي false. التحميل من ملف نستخدم simplexml_load_file لتحميل بيانات XML من ملف أو رابط: $xml = simplexml_load_string("filePath.xml"); $xml = simplexml_load_string("https://example.com/doc.xml"); يمكن أن يكون الرابط من أي مخطط تدعمه PHP ?أو مُغلِّف مجرى مخصص. تحليل HTML تحليل HTML من سلسلة نصية تنفّذ PHP محللًا متوافقًا مع DOM المستوى 2 مما يسمح لنا بالعمل مع HTML باستخدام التوابع المعروفة مثل getElementById()‎ أوappendChild()‎. $html = '<html><body><span id="text">Hello, World!</span></body></html>'; $doc = new DOMDocument(); libxml_use_internal_errors(true); $doc->loadHTML($html); echo $doc->getElementById("text")->textContent; الخرج: Hello, World! لاحظ أنّ PHP ستصدر تحذيرات بشأن مشاكل HTML التي قد تحدث، خاصةً إذا كنت تستورد جزء من ملف، لتجنب هذه التحذيرات نخبر مكتبة DOM ‏(libxml) أن تعالج أخطائها قبل استيراد HTML باستدعاء libxml_use_internal_errors()‎، ويمكنك بعدها استخدام libxml_get_errors()‎ لمعالجة الأخطاء عند الحاجة. استخدام XPath $html = '<html><body><span class="text">Hello, World!</span></body></html>'; $doc = new DOMDocument(); $doc->loadHTML($html); $xpath = new DOMXPath($doc); $span = $xpath->query("//span[@class='text']")->item(0); echo $span->textContent; الخرج: Hello, World! SimpleXML SimpleXML هي مكتبة PHP توفر طريقة سهلة للتعامل مع ملفات XML (خاصةً القراءة والتكرار عبر بيانات XML)، القيد الوحيد هو أنّ ملف XML يجب أن يكون مُصاغ جيدًا (well-formed). تحليل XML باستخدام المنهجية الإجرائية // XML تحميل سلسلة نصية $xmlstr = file_get_contents('library.xml'); $library = simplexml_load_string($xmlstr); // XML تحميل ملف $library = simplexml_load_file('library.xml'); لتحميل ملف XML يمكنك كتابة مسار الملف المحلي أو رابط صالح إذا كان الإعداد allow_url_fopen مضبوطًا إلى "On" في php.ini. تحليل XML باستخدام المنهجية الكائنية التوجه // XML تحميل سلسلة نصية $xmlstr = file_get_contents('library.xml'); $library = new SimpleXMLElement($xmlstr); // XML تحميل ملف $library = new SimpleXMLElement('library.xml', NULL, true); الوصول إلى الأبناء والخاصيّات عندما تحلل SimpleXML ملف XML تحوّل كل عناصر XML فيه، أو العقد، إلى خاصيّات لكائن SimpleXMLElement ناتج، وتحوّل خاصيّات XML إلى مصفوفة ترابطية يمكن الوصول إليها من الخاصيّة التي تعود لها. عندما تعرف أسمائهم: $library = new SimpleXMLElement('library.xml', NULL, true); foreach ($library->book as $book){ echo $book['isbn']; echo $book->title; echo $book->author; echo $book->publisher; } العيب الأساسي لهذه المنهجية أنّه من الضروري معرفة اسم كل عنصر وخاصية في ملف XML. عندما لا تعرف أسمائهم (أو لا تريد معرفتهم): foreach ($library->children() as $child){ echo $child->getName(); // الحصول على خاصيّات هذا العنصر foreach ($child->attributes() as $attr){ echo ' ' . $attr->getName() . ': ' . $attr; } // الحصول على الأبناء foreach ($child->children() as $subchild){ echo ' ' . $subchild->getName() . ': ' . $subchild; } } ترجمة -وبتصرف- للفصول [JSON - XML - SimpleXML - Parsing HTML] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: الأصناف (Classes) والكائنات (Objects) في PHP المقال السابق: تنسيق النصوص وتحليلها في PHP
  11. استيفاء السلسلة النصية يمكنك استخدام الاستيفاء لتستوفي (تُدرج) متغير في سلسلة نصية (string)، يعمل الاستيفاء مع السلاسل النصية ذات الاقتباس المزدوج وصياغة heredoc فقط. $name = 'Joel'; // Joel بالاسم $name سيُستبدل المتغير echo "<p>Hello $name, Nice to see you.</p>"; // "<p>Hello Joel, Nice to see you.</p>" // كنص خام $name السلاسل النصية ذات الاقتباس المفرد تُظهر المتغير echo 'Hello $name, Nice to see you.'; // "Hello $name, Nice to see you." يوفر تنسيق الصياغة المعقدة (صياغة الأقواس المعقوصة) خيارًا آخر يتطلّب أن تغلّف المتغير بأقواس معقوصة {}، يمكن أن يكون هذا مفيدًا عند تضمين المتغيرات في المحتوى النصي للمساعدة في منع الالتباس المحتمل بين المحتوى النصي والمتغيرات. $name = 'Joel'; // $name مثال باستخدام الأقواس المعقوصة للمتغير echo "<p>We need more {$name}s to help us!</p>"; // "<p>We need more Joels to help us!</p>" // غير معرّف `$names` السطر التالي سيرمي خطأً لأنّ echo "<p>We need more $names to help us!</p>"; // "Notice: Undefined variable: names" تستوفي صياغة الأقواس المعقوصة {} المتغيرات التي تبدأ بالرمز $ داخل سلسلة نصية، ولا تقيّم تعابير PHP العشوائية. // PHP مثال يحاول استيفاء تعبير echo "1 + 2 = {1 + 2}"; // "1 + 2 = {1 + 2}" // مثال لاستخدام ثابت define("HELLO_WORLD", "Hello World!!"); echo "My constant is {HELLO_WORLD}"; // "My constant is {HELLO_WORLD}" // مثال لاستخدام دالة function say_hello() { return "Hello!"; }; echo "I say: {say_hello()}"; // "I say: {say_hello()}" لكن تقيّم هذه الصياغة الوصول إلى المصفوفة والوصول إلى الخاصيّات واستدعاءات الدالة/التابع على المتغيرات أو عناصر المصفوفة أو الخاصيّات: // مثال للوصول إلى قيمة من مصفوفة متعددة الأبعاد $companions = [0 => ['name' => 'Amy Pond'], 1 => ['name' => 'Dave Random']]; echo "The best companion is: {$companions[0]['name']}"; // "The best companion is: Amy Pond" // مثال لاستدعاء تابع على كائن class Person { function say_hello() { return "Hello!"; } } $max = new Person(); echo "Max says: {$max->say_hello()}"; // "Max says: Hello!" // مثال لاستدعاء دالة مغلِّفة حيث تسمح قائمة المعاملات بتعابير مخصصة $greet = function($num) { return "A $num greetings!"; }; echo "From us all: {$greet(10 ** 3)}"; // "From us all: A 1000 greetings!" لاحظ أنّ إشارة الدولار $ يمكن أن توجد بعد فتح القوس المعقوص ‎{‎ كما في الأمثلة السابقة، لكن يمكن لها أن توجد قبله كما في سكربت الصدفة (Shell Script) أو لغة Perl: $name = 'Joel'; // مثال لاستخدام القوس المعقوص مع إشارة الدولار قبل فتح القوس المعقوص echo "<p>We need more ${name}s to help us!</p>"; // "<p>We need more Joels to help us!</p>" استخلاص/استبدال أجزاء من السلسلة النصية يمكن أن تُستخلص المحارف المفردة باستخدام صياغة المصفوفة (الأقواس المعقوفة []) وصياغة الأقواس المعقوصة، تُعيد هاتين الصياغتين محرفًا واحدًا فقط من السلسلة النصية وإذا كنا نريد أكثر من محرف فنحتاج إلى استخدام دالة مثل substr. تُفهرَس المصفوفات في PHP بدءًا من الصفر. $foo = 'Hello world'; $foo[6]; // 'w' $foo{6}; // 'w' substr($foo, 6, 1); // 'w' substr($foo, 6, 2); // 'wo' كما يمكن أن نغيّر محرفًا واحدًا في السلسلة النصية باستخدام صياغتي الأقواس المعقوفة والأقواس المعقوصة أما لتغيير أكثر من محرف نحتاج إلى استخدام دالة مثل substr_replace: $foo = 'Hello world'; $foo[6] = 'W'; // $foo = 'Hello World' $foo{6} = 'W'; // $foo = 'Hello World' substr_replace($foo, 'W', 6, 1); // $foo = 'Hello World' substr_replace($foo, 'Whi', 6, 2); // 'Hello Whirld' لاحظ أنّ الجزء السلسلة النصية الجديد ليس بالضرورة أن تكون بنفس طول جزء السلسلة النصيّة المُستبدَل. تحليل السلسلة النصية تقسيم سلسلة نصية بفواصل أسهل التوابع لتقسيم سلسلة نصية هي explode وstrstr. يمكن تقسيم السلسلة النصية التي تحتوي عدة أجزاء نصيّة يفصل بينها محرف الفاصلة باستخدام الدالة explode. $fruits = "apple,pear,grapefruit,cherry"; print_r(explode(",",$fruits)); // ['apple', 'pear', 'grapefruit', 'cherry'] توفر أيضًا هذه الدالة المعامل limit يمكن استخدامه كالتالي: $fruits= 'apple,pear,grapefruit,cherry'; إذا كانت قيمة المعامل limit تساوي الصفر فيُتَعَامَل معها على أنها تساوي 1. print_r(explode(',',$fruits,0)); // ['apple,pear,grapefruit,cherry'] أما إذا كانت قيمته موجبةً، فإنّ المصفوفة المُعادة ستحتوي على عناصر بعدد limit، مع احتواء آخر عنصر في المصفوفة على بقية السلسلة النصية: print_r(explode(',',$fruits,2)); // ['apple', 'pear,grapefruit,cherry'] وإذا كانت قيمته سالبةً فستحتوي المصفوفة المعادة على كل محارف السلسلة النصية ما عدا آخر ‎-limit عنصر. print_r(explode(',',$fruits,-1)); // ['apple', 'pear', 'grapefruit'] يمكن أن نجمع بين الدالتين explode وlist لتحويل سلسلة نصية إلى متغيرات في سطرٍ واحد: $email = "user@example.com"; list($name, $domain) = explode("@", $email); لكن يجب أن تكون متأكدًا من أنّ نتيجة الدالة explode تحتوي عدد عناصر كافٍ وإلا سينشأ تحذير undefined index. تزيل الدالة strstr جزء السلسلة النصية الموجود قبل الظهور الأول للمعامل needle المُعطى. $string = "1:23:456"; echo json_encode(explode(":", $string)); // ["1","23","456"] var_dump(strstr($string, ":")); // string(7) ":23:456" var_dump(strstr($string, ":", true)); // string(1) "1" أجزاء سلسلة نصية تُعيد الدالة substr جزء السلسلة النصية المحدد بمعاملات موقع البداية وعدد المحارف المطلوبة. var_dump(substr("Boo", 1)); // string(2) "oo" من الأفضل استخدام mb_substr إذا كان هناك احتمال أن يكون في السلسلة محرفًا متعدد البايتات. $cake = "cakeæøå"; var_dump(substr($cake, 0, 5)); // string(5) "cake�" var_dump(mb_substr($cake, 0, 5, 'UTF-8')); // string(6) "cakeæ" ولدينا أيضًا الدالة substr_replace تستبدل نصًا ضمن جزء من سلسلة نصية. var_dump(substr_replace("Boo", "0", 1, 1)); // string(3) "B0o" var_dump(substr_Replace("Boo", "ts", strlen("Boo"))); // string(5) "Boots" بفرض أنّك تريد إيجاد كلمة معينة ضمن سلسلة نصية دون استخدام تعبير نمطي. $hi = "Hello World!"; $bye = "Goodbye cruel World!"; var_dump(strpos($hi, " ")); // int(5) var_dump(strpos($bye, " ")); // int(7) var_dump(substr($hi, 0, strpos($hi, " "))); // string(5) "Hello" var_dump(substr($bye, -1 * (strlen($bye) - strpos($bye, " ")))); // string(13) " cruel World!" // لموازنة السلاسل النصية strtolower إذا لم تكن حالة الأحرف هامة في النص نستخدم الدالة var_dump(substr($hi, 0, strpos($hi, " ")) == 'hello'); // bool(false) var_dump(strtolower(substr($hi, 0, strpos($hi, " "))) == 'hello'); // bool(true) خيار آخر أساسي لتحليل بريد إلكتروني. $email = "test@example.com"; $wrong = "foobar.co.uk"; $notld = "foo@bar"; $at = strpos($email, "@"); // int(4) $wat = strpos($wrong, "@"); // bool(false) $nat = strpos($notld , "@"); // int(3) $domain = substr($email, $at + 1); // string(11) "example.com" $womain = substr($wrong, $wat + 1); // string(11) "oobar.co.uk" $nomain = substr($notld, $nat + 1); // string(3) "bar" $dot = strpos($domain, "."); // int(7) $wot = strpos($womain, "."); // int(5) $not = strpos($nomain, "."); // bool(false) $tld = substr($domain, $dot + 1); // string(3) "com" $wld = substr($womain, $wot + 1); // string(5) "co.uk" $nld = substr($nomain , $not + 1); // string(2) "ar" // string(25) "test@example.com is valid" if ($at && $dot) var_dump("$email is valid"); else var_dump("$email is invalid"); // string(21) "foobar.com is invalid" if ($wat && $wot) var_dump("$wrong is valid"); else var_dump("$wrong is invalid"); // string(18) "foo@bar is invalid" if ($nat && $not) var_dump("$notld is valid"); else var_dump("$notld is invalid"); // string(27) "foobar.co.uk is an UK email" if ($tld == "co.uk") var_dump("$email is a UK address"); if ($wld == "co.uk") var_dump("$wrong is a UK address"); if ($nld == "co.uk") var_dump("$notld is a UK address"); أو حتى وضع عبارة "قراءة المزيد" أو "…" في نهاية إعلان ما. $blurb = "Lorem ipsum dolor sit amet"; $limit = 20; var_dump(substr($blurb, 0, $limit - 3) . '...'); // string(20) "Lorem ipsum dolor..." البحث عن جزء من سلسلة نصية باستخدام التابع strpos يمكن فهم الدالة strpos على أنّها عدد البايتات في المعامل haystack قبل الظهور الأول للمعامل needle. var_dump(strpos("haystack", "hay")); // int(0) var_dump(strpos("haystack", "stack")); // int(3) var_dump(strpos("haystack", "stackoverflow"); // bool(false) التحقق من وجود جزء من سلسلة نصية يجب الانتباه عند التحقق للحصول على نتيجة منطقية TRUE أو FALSE، إذ أنّه إذا أرجعت الدالة الفهرس 0 ستُعاملها التعليمة if على أنّها FALSE. $pos = strpos("abcd", "a"); // $pos = 0; $pos2 = strpos("abcd", "e"); // $pos2 = FALSE; // needle مثال خاطئ للتحقق من وجود المعامل if($pos) { // (1) echo "1. I found your string\n"; } else { echo "1. I did not found your string\n"; } // needle مثال صحيح للتحقق من وجود المعامل if($pos !== FALSE) { echo "2. I found your string\n"; } else { echo "2. I did not found your string\n"; } // needle التحقق من عدم وجود المعامل if($pos2 === FALSE) { echo "3. I did not found your string\n"; } else { echo "3. I found your string\n"; } في الموضع (1) لا تتطابق النتيجة 0 مع TRUE. خرج الشيفرة السابقة: 1. I did not found your string 2. I found your string 3. I did not found your string البدء بالبحث بعد إزاحة معينة // يمكننا مع الإزاحة البدء بالبحث متجاهلين كل ما يأتي قبل الإزاحة $needle = "Hello"; $haystack = "Hello world! Hello World"; $pos = strpos($haystack, $needle, 1); // $pos = 13, not 0 الحصول على كل مرات ظهور جزء من سلسلة نصية $haystack = "a baby, a cat, a donkey, a fish"; $needle = "a "; $offsets = []; // البدء بالبحث من بداية السلسلة النصية for($offset = 0;$offset < strlen($haystack); ){ // (1) $pos = strpos($haystack, $needle, $offset); // إذا لم يبقى لدينا أي سلسلة نصية جزئية if($pos === false) break; $offsets[] = $pos; // (2) $offset = $pos + 1; } echo json_encode($offsets); // [0,8,15,25] في الموضع (1) إذا كانت الإزاحة خارج السلسلة النصية فلا تبحث بعد الآن، إذا لم يوضع هذا الشرط سيظهر تحذير إذا انتهى المعامل ‎$haystack بالمعامل ‎$needle وكان طول المعامل ‎$needle بايت واحد. في الموضع (2) قد ترغب بإضافة strlen($needle)‎ وهذا يعتمد فيما إذا أردت أن تعدّ "aaa" على أنّها سلسلة نصية واحدة أو سلسلتي "aa". تحليل سلسلة نصية باستخدام التعابير النمطية (regular expressions) يمكن استخدام الدالة preg_match لتحليل سلسلة نصية باستخدام تعبير نمطي، تُحاط أجزاء التعبير النمطي بأقواس تُدعى أنماطًا فرعية ومعهم يمكنك الحصول على أجزاء فردية من السلسلة النصية. $str = "<a href=\"http://example.org\">My Link</a>"; $pattern = "/<a href=\"(.*)\">(.*)<\/a>/"; $result = preg_match($pattern, $str, $matches); if($result === 1) { // السلسلة النصية تطابق التعبير النمطي print_r($matches); } else if($result === 0) { // لا يوجد تطابق } else { // حدث خطأ } الخرج: Array ( [0] => <a href="http://example.org">My Link</a> [1] => http://example.org [2] => My Link ) ترجمة -وبتصرف- للفصول [String formatting - String Parsing] من كتاب PHP Notes for Professionals book اقرأ ايضًا المقال التالي: صيغة JSON وXML في PHP المقال السابق: الدوال في PHP تعلم PHP
  12. قائمة الوسطاء ذات الطول المتغير النسخة 5.6 وما بعدها: قدم الإصدار PHP 5.6 لائحة وسطاء ذات طول متغير (وتعرف أيضًا باسم العدد المتغيّر للوسائط (varargs) أو الوسائط المتغيّرة) بإضافة ... قبل اسم الوسيط للإشارة إلى أنّ المعامل متغيّر، أي أنّها مصفوفة فيها كل المعاملات بدءًا من هذا الوسيط. function variadic_func($nonVariadic, ...$variadic) { echo json_encode($variadic); } variadic_func(1, 2, 3, 4); // [2,3,4] يمكن أن يُضاف اسم النوع قبل ...: function foo(Bar ...$bars) {} يمكن أن يُضاف عامل المرجعية & قبل ... وبعد اسم النوع (إن وجد)، مثال: class Foo{} function a(Foo &...$foos){ $i = 0; foreach($a as &$foo){ $foo = $i++; } } $a = new Foo; $c = new Foo; $b =& $c; a($a, $b); var_dump($a, $b, $c); /* int(0) int(1) int(1) */ من ناحية أخرى يمكن تفريغ مصفوفة الوسائط لتُمرر إلى دالة بصيغة لائحة وسطاء: var_dump(...hash_algos()); /* string(3) "md2" string(3) "md4" string(3) "md5" ... */ وازن الخرج السابق مع خرج الشيفرة التالية: var_dump(hash_algos()); /* array(46) { [0]=> string(3) "md2" [1]=> string(3) "md4" ... } */ لذا يمكن إعادة توجيه الدوال إلى دوال متعددة الوسائط بسهولة، مثال: public function formatQuery($query, ...$args){ return sprintf($query, ...array_map([$mysqli, "real_escape_string"], $args)); } يمكن أيضًا استخدام واجهات Traversable مثل Iterator (خاصةً العديد من أصنافها الفرعية من مكتبة SPL)، مثال: $iterator = new LimitIterator(new ArrayIterator([0, 1, 2, 3, 4, 5, 6]), 2, 3); echo bin2hex(pack("c*", ...$it)); // 020304 إذا كان المُكرِّر يكرر عددًا غير محدود من المرات، مثال: $iterator = new InfiniteIterator(new ArrayIterator([0, 1, 2, 3, 4])); var_dump(...$iterator); ستسلك الإصدارات المختلفة من PHP سلوكًا مختلفًا: من الإصدار PHP 7.0.0 وحتى الإصدار PHP 7.1.0 (بيتا 1): سيحدث خطأ تجزئة (segmentation fault) وسيتوقف التنفيذ مع الرمز 139. في PHP 5.6: سيُعرَض خطأً فادحًا بسبب استهلاك الذاكرة ("Allowed memory size of %d bytes exhausted") وسيتوقف التنفيذ مع الرمز 255. ملاحظة: لا تدعم الآلة الافتراضية هيب هوب HHVM (الإصدار 3.10 وحتى 3.12) تفريغ واجهات Traversable وستظهر رسالة تحذير "Only containers may be unpacked" عند المحاولة. المعاملات الاختيارية يمكن أن يكون للدوال معاملات اختيارية، مثال: function hello($name, $style = 'Formal') { switch ($style) { case 'Formal': print "Good Day $name"; break; case 'Informal': print "Hi $name"; break; case 'Australian': print "G'day $name"; break; default: print "Hello $name"; break; } } hello('Alice'); // Good Day Alice hello('Alice', 'Australian'); // G'day Alice تمرير الوسائط بالمرجعية يمكن تمرير وسائط الدالة بالمرجعية (By Reference) مما يسمح للدالة بتعديل المتغير خارج الدالة: function pluralize(&$word) { if (substr($word, -1) == 'y') { $word = substr($word, 0, -1) . 'ies'; } else { $word .= 's'; } } $word = 'Bannana'; pluralize($word); print $word; // Bannanas تُمرَّر وسائط الكائن دائمًا بالمرجعية: function addOneDay($date) { $date->modify('+1 day'); } $date = new DateTime('2014-02-28'); addOneDay($date); print $date->format('Y-m-d'); // 2014-03-01 يجب نسخ الكائن لتجنّب تمريره بالمرجعية بشكلٍ ضمني، ويمكن أن يُستخدم التمرير بالمرجعية كطريقة بديلة لإرجاع معاملات، مثل الدالة socket_getpeername: bool socket_getpeername ( resource $socket , string &$address [, int &$port ] ) يهدف هذا التابع لإرجاع عنوان ومنفذ النظير (peer) ولكن بما أنّه يوجد قيمتين للإرجاع فهو يختار معاملات المرجعية، يمكن أن يُستدعى كالتالي: if(!socket_getpeername($socket, $address, $port)) { throw new RuntimeException(socket_last_error()); } echo "Peer: $address:$port\n"; لا تحتاج المتغيرات ‎$address و‎$port للتعريف مسبقًا فهي ستُعرَّف على أنّها null في البداية ثمّ تُمرَّر إلى الدالة بالقيمة null وتُعدَّل داخل الدالة ثمّ تُعرَّف في النهاية على أنّها العنوان والمنفذ في المحتوى المُستدعي. استخدام الدالة البسيط تُعرّف الدالة وتُنفَّذ بشكلها البسيط كما يلي: function hello($name) { print "Hello $name"; } hello("Alice"); نطاق الدالة المتغيرات داخل الدوال هي متغيرات معرَّفة داخل نطاق محلي، مثال: $number = 5 function foo(){ $number = 10 return $number } foo(); خرج الشيفرة السابقة هو 10 لأنّ المتغير المعرَّف داخل الدالة ذو نطاق محلي. البرمجة الوظيفية (functional programming) تعتمد البرمجة الوظيفية على الدوال، إذ توفر الدوال شيفرة منظمة وقابلة لإعادة الاستخدام تؤدي مجموعة وظائف، تبسّط الدوال عملية كتابة الشيفرة وتمنع تكرار المنطق وتجعل الشيفرة سهلة التتبع. سنتحدث في الفقرات التالية عن التصريح عن الدوال، واستخدامها، والوسائط، والمعاملات، وتعليمات الإرجاع، والنطاق في PHP. الدوال المغلِّفة (Closures) الدالة المغلِّفة هي دالة مجهولة (anonymous function) لا يمكن الوصول إليها من خارج النطاق، عندما تعرّف دالة مجهولة فأنت تنشئ فضاء اسم لهذه الدالة، يمكن لها حاليًا أن تصل إلى فضاء الاسم هذا فقط. $externalVariable = "Hello"; $secondExternalVariable = "Foo"; $myFunction = function() { var_dump($externalVariable, $secondExternalVariable); // تُرجع ملاحظتي خطأ بما أنّ المتغيرات لم تُعرَّف } لا يمكن لهذه الدالة الوصول إلى أيّ متغيرات خارجية ولتمنح فضاء الاسم هذا إمكانية الإذن بالوصول فأنت تحتاج للتعريف عن ذلك باستخدام use()‎. $myFunction = function() use($externalVariable, $secondExternalVariable) { var_dump($externalVariable, $secondExternalVariable); // Hello Foo } يعود هذا بشكل كبير إلى نطاق المتغير الضيق في PHP، فإذا لم يكن المتغير معرّفًا ضمن النطاق أو لم يُجلب باستخدام الكلمة المفتاحية global فإنّه غير موجود. لاحظ أيضًا أنّ وراثة المتغيرات من النطاق الأب ليس مثل استخدام المتغيرات العامة، توجد المتغيرات العامة في النطاق العام وهو نفسه بغض النظر عن الدالة التي تُنفَّذ، أما النطاق الأب لدالة مغلِّفة هو الدالة التي عُرِّفت ضمنها الدالة المغلِّفة (وليس بالضرورة الدالة التي اُستدعيت منها). تستخدم الدوال المغلِّفة منهجية الربط المبكر (early-binding)، أي أنّ المتغيرات التي تُمرَّر إلى فضاء اسم الدالة المغلِّفة باستخدام الكلمة المفتاحية use سيكون لها نفس القيم عند تعريف الدالة المغلِّفة ولتغيير هذا السلوك يجب تمرير القيمة بالمرجعية. $rate = .05; // تصدير متغير إلى نطاق الدالة المغلِّفة $calculateTax = function ($value) use ($rate) { return $value * $rate; }; $rate = .1; print $calculateTax(100); // 5 $rate = .05; // تصدير متغير إلى نطاق الدالة المغلِّفة $calculateTax = function ($value) use (&$rate) { // (1) return $value * $rate; }; $rate = .1; print $calculateTax(100); // 10 لاحظ في الموضع (1) استخدام & قبل ‎$rate. إنّ الوسطاء الافتراضيين غير مطلوبين ضمنيًّا عند تعريف دوال مجهولة مع/دون دوال مغلِّفة. $message = 'Im yelling at you'; $yell = function() use($message) { echo strtoupper($message); }; $yell(); // IM YELLING AT YOU الإسناد إلى متغيرات يمكن أن تُسنَد الدوال المجهولة إلى متغيرات للاستخدام كمعاملات حيث يُتوقّع رد النداء. $uppercase = function($data) { return strtoupper($data); }; $mixedCase = ["Hello", "World"]; $uppercased = array_map($uppercase, $mixedCase); print_r($uppercased); يمكن استخدام هذه المتغيرات مثلما نستدعي دالة مستقلة: echo $uppercase("Hello world!"); // HELLO WORLD! الكائنات كدالة class SomeClass { public function __invoke($param1, $param2) { // أضف الشيفرة هنا } } $instance = new SomeClass(); // __invoke() استدعاء التابع $instance('First', 'Second'); يمكن استخدام كائن مع التابع ‎__invoke‎ تمامًا مثل أي دالة أخرى، سيكون بإمكان التابع ‎__invoke‎ الوصول إلى كل خاصيّات الكائن واستدعاء أيّ تابع. استخدام المتغيرات الخارجية تُستخدم البنية use لاستيراد المتغيرات إلى داخل نطاق الدالة المجهولة: $divisor = 2332; $myfunction = function($number) use ($divisor) { return $number / $divisor; }; echo $myfunction(81620); // 35 يمكن أيضًا استيراد المتغيرات بالمرجع: $collection = []; $additem = function($item) use (&$collection) { $collection[] = $item; }; $additem(1); $additem(2); //$collection = [1,2] الدالة المجهولة الدالة المجهولة هي دالة دون اسم، مثال: function() { return "Hello World!"; }; تُعامل الدالة المجهولة في PHP كأنها تعبير لذا يجب أن تنتهي بفاصلة منقوطة ;، ويجب أن تُسند الدالة المجهولة إلى متغير: // دالة مجهولة مُسندة إلى متغير $sayHello = function($name) { return "Hello $name!"; }; print $sayHello('John'); // Hello John أو يجب أن تُمرَّر كمعامل دالة أخرى. $users = [ ['name' => 'Alice', 'age' => 20], ['name' => 'Bobby', 'age' => 22], ['name' => 'Carol', 'age' => 17] ]; // تنفيذ دالة مجهولة على عناصر المصفوفة $userName = array_map(function($user) { return $user['name']; }, $users); print_r($usersName); // ['Alice', 'Bobby', 'Carol'] أو تُرجَع من دالة أخرى. التنفيذ الذاتي للدوال المجهولة: // PHP 7.x (function () { echo "Hello world!"; })(); // PHP 5.x call_user_func(function () { echo "Hello world!"; }); تمرير معامل إلى الدوال المجهولة أثناء التنفيذ الذاتي: // PHP 7.x (function ($name) { echo "Hello $name!"; })('John'); // PHP 5.x call_user_func(function ($name) { echo "Hello $name!"; }, 'John'); الدوال النقية (Pure functions) الدالة النقية هي الدالة التي تعطي نفس الخرج دومًا مهما كان الدخل وخالية من التأثير الجانبي. // دالة نقية function add($a, $b) { return $a + $b; } تغيّر بعض التأثيرات الجانبية نظام الملفات وتتفاعل مع قواعد البيانات وتطبع على الشاشة. // دالة غير نقيّة function add($a, $b) { echo "Adding..."; return $a + $b; } توابع وظيفية شائعة في PHP الربط تطبيق دالة على جميع عناصر المصفوفة: array_map('strtoupper', $array); يجب أن تنتبه أنّ هذا التابع الوحيد الذي يأتي فيه رد النداء أولًا. الاختزال (أو الطي) اختزال المصفوفة إلى قيمة واحدة: $sum = array_reduce($numbers, function ($carry, $number) { return $carry + $number; }); الترشيح تُرجَع فيه عناصر المصفوفة التي يكون نتيجة رد النداء لها true فقط. $onlyEven = array_filter($numbers, function ($number) { return ($number % 2) === 0; }); استخدام الدوال المدمجة كردود نداء يمكن أن تضع سلسلة نصية مع دالة PHP مدمجة في الدوال التي تأخذ معاملًا من النوع callable، من الشائع استخدام trim معاملًا للدالة array_map لإزالة المسافات البيضاء من بداية ونهاية السلاسل النصية في المصفوفة. $arr = [' one ', 'two ', ' three']; var_dump(array_map('trim', $arr)); /* array(3) { [0] => string(3) "one" [1] => string(3) "two" [2] => string(5) "three" } */ النطاق الدالة المجهولة في PHP لها نطاقها الخاص مثل أيّ دالة أخرى، فيمكن للدالة المجهولة في جافاسكربت مثلًا الوصول إلى متغير خارج النطاق أما في PHP فإنّ هذا غير ممكن. $name = 'John'; // دالة مجهولة تحاول الوصول إلى متغير خارج النطاق $sayHello = function() { return "Hello $name!"; } print $sayHello('John'); // Hello ! // إذا كانت الملاحظات ممكّنة Undefined variable $name ستظهر لنا الملاحظة تمرير دالة رد نداء كمعامل يوجد العديد من الدوال في PHP تقبل دوال رد نداء (callback) معرَّفة من قبل المستخدم كمعامل مثل الدوال: calluserfunc()‎ وusort()‎ وarray_map‎()‎. يوجد طرائق مختلفة لتمرير دوال رد النداء المعرَّفة من قبل المستخدم كمعاملات وتختلف هذه الطرائق وفقًا للمكان الذي عُرِّفَت فيه هذه الدوال: النمط الإجرائي: function square($number) { return $number * $number; } $initial_array = [1, 2, 3, 4, 5]; $final_array = array_map('square', $initial_array); var_dump($final_array); // array(5) { [0]=> int(1) [1]=> int(4) [2]=> int(9) [3]=> int(16) [4]=> int(25) } النمط الكائني التوجه: class SquareHolder { function square($number) { return $number * $number; } } $squaredHolder = new SquareHolder(); $initial_array = [1, 2, 3, 4, 5]; $final_array = array_map([$squaredHolder, 'square'], $initial_array); var_dump($final_array); // array(5) { [0]=> int(1) [1]=> int(4) [2]=> int(9) [3]=> int(16) [4]=> int(25) } النمط الكائني التوجه باستخدام تابع ساكن: class StaticSquareHolder { public static function square($number) { return $number * $number; } } $initial_array = [1, 2, 3, 4, 5]; $final_array = array_map(['StaticSquareHolder', 'square'], $initial_array); // أو $final_array = array_map('StaticSquareHolder::square', $initial_array); // PHP >= 5.2.3 في var_dump($final_array); // array(5) { [0]=> int(1) [1]=> int(4) [2]=> int(9) [3]=> int(16) [4]=> int(25) } ترجمة -وبتصرف- للفصول [Functions - Functional Programming] من كتاب PHP Notes for Professionals book اقرأ أيضًا تعلم PHP المقال التالي: تنسيق النصوص وتحليلها في PHP المقال السابق: بنى التحكم والحلقات التكرارية في PHP
  13. التعليمات الشرطية if else تسمح لنا تعليمة if بتنفيذ شيفرة معينة إذا تحقق شرط معين، ونستخدم else لتنفيذ شيفرة ما عندما لا يتحقق هذا الشرط. if ($a > $b) { echo "a is greater than b"; } else { echo "a is NOT greater than b"; } العامل الثلاثي كصياغة مختصرة لعبارة if-else يقيّم العامل الثلاثي تعبيرًا بالاعتماد على شرط قد تكون نتيجته true أو false، وهو عامل موازنة يُستخدم ليعبّر عن شرط if-else بصيغة مختصرة فهو يسمح باختبار سريع للشرط ويختزل تعليمة if المكونة من عدّة أسطر إلى سطر واحد، سنعيد كتابة الشيفرة السابقة باستخدام العامل الثلاثي وبفرض ‎$a=1‎، ‏‎$b=2: echo ($a > $b) ? "a is greater than b" : "a is NOT greater than b"; /* a is NOT greater than b. */ صياغة بديلة لبنى التحكم توفر PHP صياغة بديلة لبعض بنى التحكم: if، ‏while، ‏for، ‏foreach و‏switch. بالموازنة مع الصياغة العادية، الفرق هو أنّ القوس المفتوح يُستبدل بنقطتين : والقوس المفتوح يُستبدل ب endif;‎، ‏endwhile;‎، ‏endfor;‎، ‏endforeach;‎ أو endswitch;‎ على الترتيب، مثال: if ($a == 42): echo "The answer to life, the universe and everything is 42."; endif; تعليمات elseif باستخدام الصياغة المختصرة: if ($a == 5): echo "a equals 5"; elseif ($a == 6): echo "a equals 6"; else: echo "a is neither 5 nor 6"; endif; if elseif else elseif تجمع elseif بين if وelse، وسِّعت تعليمة if لتنفيذ تعليمة مختلفة في حال كانت نتيجة تعبير if الأساسي هي FALSE، وينفَّذ التعبير البديل فقط في حال كانت نتيجة تعبير elseif الأساسي هي TRUE. تعرض الشيفرة التالية إحدى العبارات "a is bigger than b" أو "a is equal to b" أو "a is smaller than b": if ($a > $b) { echo "a is bigger than b"; } elseif ($a == $b) { echo "a is equal to b"; } else { echo "a is smaller than b"; } تعليمات elseif متعددة يمكن استخدام تعليمات elseif متعددة ضمن تعليمة if نفسها: if ($a == 1) { echo "a is One"; } elseif ($a == 2) { echo "a is Two"; } elseif ($a == 3) { echo "a is Three"; } else { echo "a is not One, not Two nor Three"; } if تسمح لك بنية if بتنفيذ أجزاء من الشيفرة اعتمادًا على شروط معينة. if ($a > $b) { echo "a is bigger than b"; } switch تؤدي بنية switch نفس وظيفة سلسلة من تعليمات if لكن بعدد أسطر شيفرة أقل. تُقارَن القيمة التي نريد اختبارها والمعرَّفة في تعليمة switch مع كل القيم الموجودة في عبارات case وعند المساواة ستنُنفَّذ الشيفرة الخاصة بتلك العبارة وإذا لم تتساوى مع أي قيمة تُنفّّذ كتلة default إن وجدت. كل كتلة شيفرة في تعليمة case أو default يجب أن تنتهي بتعليمة break، توقف هذه التعليمة تنفيذ بنية switch ويتابع تنفيذ الشيفرة مباشرةً بعد ذلك، إذا تجاهلت تعليمة break ستُنفَّذ تعليمة case التالية حتى لو لم تحدث مساواة، ويمكن أن يسبب لك هذا تنفيذ غير متوقع للشيفرة لكن أيضًا قد يكون مفيدًا عند الحاجة إلى مشاركة عدة تعليمات case لنفس الشيفرة. switch ($colour) { case "red": echo "the colour is red"; break; case "green": case "blue": echo "the colour is green or blue"; break; case "yellow": echo "the colour is yellow"; // لذا ستُنفَّذ كتلة الشيفرة التالية break لاحظ عدم وجود case "black": echo "the colour is black"; break; default: echo "the colour is something else"; break; } بالإضافة لاختبار القيم الثابتة يمكن للبنية اختبار التعليمات الديناميكية بإعطاء تعليمة switch قيمة منطقية وإعطاء تعليمة case أي تعبير، مثال: $i = 1048; switch (true) { case ($i > 0): echo "more than 0"; break; case ($i > 100): echo "more than 100"; break; case ($i > 1000): echo "more than 1000"; break; } // more than 100 الحلقات تعدّ الحلقات جانبًا أساسيًا من البرمجة فهي تسمح للمبرمجين بإنشاء شيفرة تُكرَّر لعدد من المرات أو التكرارات، قد يكون عدد التكرارات محددًا بشكلٍ صريح (6 تكرارات مثلًا) أو يستمر حتى حدوث شرط ما نحدده. for تُستخدم حلقات for عندما يكون لديك كتلة شيفرة تريد تكرارها عددًا محددًا من المرات.? for ($i = 1; $i < 10; $i++) { echo $i; } // 123456789 نستخدم المُهيئ لضبط القيمة الابتدائية لعداد تكرارات الحلقة، قد يُصرَّح عنه هنا لهذا الغرض واسمه التقليدي ‎$i. يُكرر المثال التالي 10 مرات ويعرض الأعداد من 0 إلى 9: for ($i = 0; $i <= 9; $i++) { echo $i, ','; } # مثال 2 for ($i = 0; ; $i++) { if ($i > 9) { break; } echo $i, ','; } # مثال 3 $i = 0; for (; ; ) { if ($i > 9) { break; } echo $i, ','; $i++; } # مثال 4 for ($i = 0, $j = 0; $i <= 9; $j += $i, print $i. ',', $i++); /* خرج كل مثال من الأمثلة السابقة 0,1,2,3,4,5,6,7,8,9, */ foreach تسمح لنا بنية foreach باستخدام التكرار على المصفوفات والكائنات بسهولة. $array = [1, 2, 3]; foreach ($array as $value) { echo $value; } // 123 ولاستخدامها مع الكائنات نحتاج إلى تنفيذ الواجهة Iterator. لاستخدام التعليمة foreach للتنقل بين عناصر المصفوفات، تُسند قيمة عنصر المصفوفة الحالي في كل تكرار إلى متغير ‎$value‎ ويتحرك مؤشر المصفوفة خطوة واحدة وفي التكرار التالي سيُعالج العنصر التالي، إليك مثال يعرض عناصر المصفوفة: $list = ['apple', 'banana', 'cherry']; foreach ($list as $value) { echo "I love to eat {$value}. "; } /* I love to eat apple. I love to eat banana. I love to eat cherry. */ يمكنك أيضًا أن تحصل على مفتاح/فهرس القيمة باستخدام foreach: foreach ($list as $key => $value) { echo $key . ":" . $value . " "; } // 0:apple 1:banana 2:cherry إنّ ‎$value بشكلٍ افتراضي هي نسخة من القيمة في ‎$list لذا فإنّ التغييرات التي تحدث داخل الحلقة لن تؤثر على ‎$list بعد ذلك: foreach ($list as $value) { $value = $value . " pie"; } echo $list[0]; // "apple" نستخدم العامل & لتعديل قيم المصفوفة داخل حلقة foreach إذ تُسند القيمة ‎$value بالمرجع، ويجب بعد ذلك استخدام unset للمتغير حتى لا تسبب إعادة استخدام ‎$value في مكانٍ آخر الكتابة فوق المصفوفة. foreach ($list as &$value) { // foreach ($list as $key => &$value) { أو $value = $value . " pie"; } unset($value); echo $list[0]; // "apple pie" يمكنك أيضًا تعديل عناصر المصفوفة داخل حلقة foreach بمرجعية مفتاح المصفوفة للعنصر الحالي. foreach ($list as $key => $value) { $list[$key] = $value . " pie"; } echo $list[0]; // "apple pie" ونستطيع استخدامها لتكرار مصفوفة ترابطية كما في الشيفرة التالية: $array = ['color'=>'red']; foreach($array as $key => $value){ echo $key . ': ' . $value; } // color: red while تكرّر حلقة while كتلة شيفرة طالما أنّ الشرط محقق. $i = 1; while ($i < 10) { echo $i; $i++; } /* 123456789 */ يُكرَّر المثال التالي حتى تصبح قيمة المجموع 100: $i = true; $sum = 0; while ($i) { if ($sum === 100) { $i = false; } else { $sum += 10; } } echo 'The sum is: ', $sum; /* The sum is: 100 */ do-while تنفّذ حلقة do-while كتلة الشيفرة لمرة واحدة بدايةً ثمّ تكرر كتلة الشيفرة طالما أنّ الشرط محقق. $i = 0; do { $i++; echo $i; } while ($i < 10); /* `12345678910` */ سيزيد المثال التالي قيمة المتغير ‎$i مرة واحدة على الأقل ويستمر بزيادة قيمته ما دامت أصغر من 25. $i = 0; do { $i++; } while($i < 25); echo 'The final value of i is: ', $i; // The final value of i is: 25 تعليمات التحكم goto يسمح لنا العامل goto بالانتقال إلى جزء آخر من البرنامج ومتوفر منذ الإصدار PHP 5.3، ويتبعه اسم لافتة (label) الهدف المطلوب: goto MyLabel;‎. يوصف الهدف المطلوب الانتقال إليه باسم اللافتة يتبعه نقطتين مثل: MyLabel:‎. <?php goto MyLabel; echo 'This text will be skipped, because of the jump.'; MyLabel: echo 'Hello World!'; ?> /* Hello World! */ return تُرجع تعليمة return تحكم البرنامج إلى الوحدة التي قامت بالاستدعاء، وسيستمر التنفيذ إلى التعبير الذي يلي تعبير الاستدعاء. عندما تُستدعى return من ضمن دالة فإنّ تنفيذها سينتهي. function returnEndsFunctions() { echo 'This is executed'; return; echo 'This is not executed.'; } // This is executed إذا استدعيت return داخل دالة لها وسيط فإنّها ستنهي تنفيذها على الفور وتعيد الوسيط كقيمة لاستدعاء الدالة. continue توقف الكلمة المفتاحية continue التكرار الحالي للحلقة وينتقل إلى التكرار التالي لها، وتوضع مثل تعليمة break داخل جسم الحلقة. تطبع الحلقة في المثال التالي رسالة بالاعتماد على القيم في مصفوفة لكنها تتجاوز قيمة معينة: $list = ['apple', 'banana', 'cherry']; foreach ($list as $value) { if ($value == 'banana') { continue; } echo "I love to eat {$value} pie.".PHP_EOL; } /* I love to eat apple pie. I love to eat cherry pie. */ قد تُستخدم تعليمة continue لينتقل التنفيذ بشكلٍ مباشر إلى مستوى خارجي من الحلقة بوصف عدد مستويات الحلقة التي نريد تجاوزها. بفرض لدينا المعلومات التالية: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } Fruit Color Cost Apple Red 1 Banana Yellow 7 Cherry Red 2 Grape Green 4 نريد تنفيذ شيفرة معينة عند الفاكهة التي تكلف أقل من 5: $data = [ [ "Fruit" => "Apple", "Color" => "Red", "Cost" => 1 ], [ "Fruit" => "Banana", "Color" => "Yellow", "Cost" => 7 ], [ "Fruit" => "Cherry", "Color" => "Red", "Cost" => 2 ], [ "Fruit" => "Grape", "Color" => "Green", "Cost" => 4 ] ]; foreach($data as $fruit) { foreach($fruit as $key => $value) { if ($key == "Cost" && $value >= 5) { continue 2; } /* الشيفرة التي نريد تنفيذها */ } } عندما تُنفَّذ التعليمة continue 2 سينتقل التنفيذ مباشرةً إلى ‎$‎data as $fruit‎ لتستمر الحلقة الخارجية (وستتجاهل باقي الشيفرة بما في ذلك الشرط في الحلقة الداخلية). break تُنهي الكلمة المفتاحية break تنفيذ الحلقة الحالية مباشرةً، بشكلٍ مشابه للتعليمة continue لكن الفرق هو أنّ break توقف الحلقة ولا تنفذ التعليمة الشرطية بعد ذلك. $i = 5; while(true) { echo 120/$i.PHP_EOL; $i -= 1; if ($i == 0) { break; } } /* 24 30 40 60 120 */ لن تُنفَّذ الحلقة عندما يكون ‎$i == 0 إذ أنّ تنفيذها سيرمي خطأً فادحًا بسبب القسمة على 0. قد تُستخدم تعليمة break للخروج عدة مستويات من الحلقة ويعدّ هذا مفيدًا عند تنفيذ الحلقات المتداخلة، فمثلًا لنسخ مصفوفة سلاسل نصية إلى السلسلة النصية الخرج، بعد حذف رموز # ?حتى تصبح السلسلة النصية في الخرج 160 محرفًا تمامًا. $output = ""; $inputs = array( "#soblessed #throwbackthursday", "happy tuesday", "#nofilter", /* المزيد من الدخل */ ); foreach($inputs as $input) { for($i = 0; $i < strlen($input); $i += 1) { if ($input[$i] == '#') continue; $output .= $input[$i]; if (strlen($output) == 160) break 2; } $output .= ' '; } توقف التعليمة break 2 تنفيذ الحلقتين الداخلية والخارجية مباشرةً. بنى تحكم أخرى declare تُستخدم بنية declare لتعيين موجّه التنفيذ (execution directive) لكتلة شيفرة. التوجيهات المعرَّفة: ticks. الترميز encoding. strict_types. بفرض أننا نريد تعيين قيمة ticks هي 1: declare(ticks=1); لتمكين نمط الكتابة الصارم نستخدم تعليمة declare التالية: declare(strict_types=1); include وrequire require العبارة require مطابقة للعبارة include باستثناء أنّها تطلق خطأ من نوع E_COMPLE_ERROR في حالة الفشل أي أنّها توقف عمل الشيفرة تمامًا أما include تطلق تحذيرًا E_WARNING والذي لا يؤدي إلى إيقاف الشيفرة عن العمل. require 'file.php'; include تعمل تعليمة include على تضمين ومعالجة الملف المحدّد. بفرض لدينا الملفين variables.php وmain.php في نفس المجلد، محتوى الملف variables.php: $a = 'Hello World!'; ومحتوى الملف main.php: include 'variables.php'; echo $a; // `Hello World!` يجب أن تكون حذرًا عند اتّباع هذه الطريقة فهي يمكن أن تؤدي إلى مشاكل في الشيفرة عند تضمين ملف قد يغيّر في كمية ومحتوى المتغيّرات المعرّفة ضمن النطاق المُعطى. يمكنك أن تضمّن ملفًا يُرجع قيمة وهذا مفيد بشكلٍ خاص في معالجة مصفوفات الإعدادات، مثال لملف configuration.php: <?php return [ 'dbname' => 'my db', 'user' => 'admin', 'pass' => 'password', ]; نضمّنه ضمن ملف آخر بنفس المجلد الموجود فيه: <?php $config = include 'configuration.php'; ستمنع هذه الطريقة الملف المضمَّن من إفساد نطاقك الحالي بالمتغيرات المتغيرة أو المُضافة. يمكن أن نستخدم include وrequire لإسناد قيم إلى متغير عند إرجاع قيمة ما من ملف. مثال: بفرض لدينا الملف include1.php فيه الشيفرة التالية: <?php $a = "This is to be returned"; return $a; ?> والملف index.php: $value = include 'include1.php'; // $value = "This is to be returned" الصياغة البديلة لبنى التحكم تعليمة if/else بديلة <?php if ($condition): do_something(); elseif ($another_condition): do_something_else(); else: do_something_different(); endif; ?> الشيفرة السابقة مكافئة لما يلي: <?php if ($condition): ?> <p>Do something in HTML</p> <?php elseif ($another_condition): ?> <p>Do something else in HTML</p> <?php else: ?> <p>Do something different in HTML</p> <?php endif; ?> تعليمة for بديلة <?php for ($i = 0; $i < 10; $i++): do_something($i); endfor; ?> الشيفرة السابقة مكافئة لما يلي: <?php for ($i = 0; $i < 10; $i++): ?> <p>Do something in HTML with <?php echo $i; ?></p> <?php endfor; ?> تعليمة while بديلة <?php while ($condition): do_something(); endwhile; ?> الشيفرة السابقة مكافئة لما يلي: <?php while ($condition): ?> <p>Do something in HTML</p> <?php endwhile; ?> تعليمة foreach بديلة <?php foreach ($collection as $item): do_something($item); endforeach; ?> الشيفرة السابقة مكافئة لما يلي: <?php foreach ($collection as $item): ?> <p>Do something in HTML with <?php echo $item; ?></p> <?php endforeach; ?> شيفرة switch بديلة <?php switch ($condition): case $value: do_something(); break; default: do_something_else(); break; endswitch; ?> الشيفرة السابقة مكافئة لما يلي: <?php switch ($condition): ?> <?php case $value: ?> <p>Do something in HTML</p> <?php break; ?> <?php default: ?> <p>Do something else in HTML</p> <?php break; ?> <?php endswitch; ?> ترجمة -وبتصرف- للفصول [Control Structures - Loops - Alternative Syntax for Control Structures] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: الدوال في PHP المقال السابق: التعامل مع الوقت والتاريخ في PHP تعلم PHP
  14. الصنف Datetime إنشاء نسخة Datetime ثابتة من متغير يمكنك استخدام الشيفرة التالية لإنشاء ‎\DateTimeImmutable‎ في PHP الإصدار 5.6 وما بعده: \DateTimeImmutable::createFromMutable($concrete); أما في الإصدارات السابقة يمكنك استخدام: \DateTimeImmutable::createFromFormat(\DateTime::ISO8601, $mutable->format(\DateTime::ISO8601), $mutable->getTimezone()); إضافة أو طرح تاريخ نستخدم الصنف DateInterval لإضافة أو طرح فترة زمنية في كائن DateTime، إليك المثال التالي الذي نطرح فيه 7 أيام ونطبع رسالة على الشاشة: // التعليمة التالية بدون وسيط تُرجع التاريخ الحالي $now = new DateTime(); // الكائن التالي يمثل فترة زمنية مدتها 7 أيام $interval = new DateInterval('P7D'); // DateTime التعليمة التالية ستُرجع كائن $lastDay = $now->add($interval); // ويُرجع سلسلة نصية DateTime ينسّق هذا التابع كائن $formatedLastDay = $lastDay->format('Y-m-d'); echo "Samara says: Seven Days. You'll be happy on $formatedLastDay."; /* عند تنفيذ هذه الشيفرة في 1-8-2016 يكون الخرج Samara says: Seven Days. You'll be happy on 2016-08-08. */ نستطيع استخدام التابع الفرعي بطريقة مشابهة لطرح التواريخ: $now->sub($interval); echo "Samara says: Seven Days. You were happy last on $formatedLastDay."; /* عند تنفيذ هذه الشيفرة في 1-8-2016 يكون الخرج Samara says: Seven Days. You were happy last on 2016-07-25. */ getTimestamp getTimeStemp هو تمثيل unix لكائن من الصنف datetime. $date = new DateTime(); echo $date->getTimestamp(); سيؤدي ذلك إلى وضع مؤشر صحيح للثواني التي انقضت منذ 00:00:00 UTC، الخميس، 1 كانون الثاني 1970. setDate تضبط هذه الدالة التاريخ لكائن من الصنف datetime. $date = new DateTime(); $date->setDate(2016, 7, 25); تضبط هذه الشيفرة التاريخ ليصبح 25 تموز، 2015 وخرجها: 2016-07-25 17:52:15.819442 إنشاء DateTime من تنسيق معين إنّ PHP قادرة على تحليل عدة تنسيقات للتاريخ، يمكنك استخدام التابع الساكن DateTime::createFromFormat إذا أردت تحليل تنسيق غير معياري أو إذا أردت أن تحدد في شيفرتك تنسيق التاريخ المُستخدم بشكلٍ صريح: نمط كائني التوجه $format = "Y,m,d"; $time = "2009,2,26"; $date = DateTime::createFromFormat($format, $time); نمط إجرائي $format = "Y,m,d"; $time = "2009,2,26"; $date = date_create_from_format($format, $time); طباعة DateTime توفر PHP من الإصدار 4 وما بعده تابع تنسيق كائني التوجه يحول كائن DateTime إلى سلسلة نصية بالتنسيق الذي تريده: public string DateTime::format ( string $format ) تأخذ الدالة date()‎ معاملًا واحدًا format بشكل سلسلة نصية ويستخدم محارف مفردة لتعريف التنسيق: Y: أربعة أرقام تمثل السنة (2016 مثلًا). y: رقمين يمثلان السنة (16 مثلًا). m: يمثّل الشهر بتنسيق رقم (من 01 إلى 12). M: يمثّل الشهر بثلاثة أحرف (مثل …Jan, Feb, Mar). j: يمثّل يوم في الشهر دون أصفار بادئة (من 1 إلى 31). D: يمثّل يوم في الأسبوع بثلاثة أحرف (مثل …Mon, Tue, Wed). h: يمثّل الساعة بتنسيق 12-ساعة (01 إلى 12). H: يمثّل الساعة بتنسيق 24-ساعة (00 إلى 23). A: إما AM أو PM. i: يمثّل الدقائق دون أصفار بادئة (من 00 إلى 59). s: يمثّل الثواني دون أصفار بادئة (من 00 إلى 59). يمكنك الاطلاع على اللائحة الكاملة هنا. الاستخدام يمكن استخدام هذه المحارف في تركيبات مختلفة لعرض الوقت بأي تنسيق تريده، إليك بعض الأمثلة: $date = new DateTime('2000-05-26T13:30:20'); /* Friday, May 26, 2000 at 1:30:20 PM */ $date->format("H:i"); /* 13:30 */ $date->format("H i s"); /* 13 30 20 */ $date->format("h:i:s A"); /* 01:30:20 PM */ $date->format("j/m/Y"); /* 26/05/2000 */ $date->format("D, M j 'y - h:i A"); /* Fri, May 26 '00 - 01:30 PM */ التنسيق الكائني التوجه: $date->format($format) التنسيق الإجرائي المشابه: date_format($date, $format) الحصول على الفرق بين تاريخين/وقتين يعدّ استخدام الصنف DateTime الطريقة الأكثر عمليّة لنحصل على الفرق بين تاريخين أو وقتين، مثال: <?php // DateTime إنشاء كائن من الصنف $twoYearsAgo = new DateTime("2014-01-18 20:05:56"); // DateTime إنشاء كائن ثاني من الصنف $now = new DateTime("2016-07-21 02:55:07"); // حساب الفرق $diff = $now->diff($twoYearsAgo); // فرق السنوات بين التاريخين $diff->y يحتوي $yearsDiff = $diff->y; // فرق الدقائق بين التاريخين $diff->m يحتوي $monthsDiff = $diff->m; // فرق الأيام بين التاريخين $diff->d يحتوي $daysDiff = $diff->d; // فرق الساعات بين التاريخين $diff->h يحتوي $hoursDiff = $diff->h; // فرق الدقائق بين التاريخين $diff->i يحتوي $minsDiff = $diff->i; // فرق الثواني بين التاريخين $diff->s يحتوي $secondsDiff = $diff->s; // فرق الأيام بين التاريخين $diff->days يحتوي $totalDaysDiff = $diff->days; // $diff استخلاص معلومات المتغير var_dump($diff); يمكننا أيضًا استخدام عوامل الموازنة للموازنة بين تاريخين بشكلٍ أسهل: <?php // DateTime إنشاء كائن من الصنف $twoYearsAgo = new DateTime("2014-01-18 20:05:56"); // DateTime إنشاء كائن ثاني من الصنف $now = new DateTime("2016-07-21 02:55:07"); var_dump($now > $twoYearsAgo); // bool(true) var_dump($twoYearsAgo > $now); // bool(false) var_dump($twoYearsAgo <= $twoYearsAgo); // bool(true) var_dump($now == $now); // bool(true) تغيير التاريخ إلى تنسيق آخر أسهل طريقة لتغيير تنسيق التاريخ إلى تنسيق آخر هي استخدام strtotime()‎ مع date()‎، إذ أنّ strtotime()‎ ستغير التاريخ إلى تنسيق الختم الزمني unix ثمّ يُمرَّر إلى date()‎ لتغييره إلى تنسيق جديد. $timestamp = strtotime('2008-07-01T22:35:17.02'); $new_date_format = date('Y-m-d H:i:s', $timestamp); يمكن كتابة الشيفرة السابقة بسطرٍ واحد: $new_date_format = date('Y-m-d H:i:s', strtotime('2008-07-01T22:35:17.02')); تذكّر أنّ الدالة strtotime()‎ تحتاج أن يكون التاريخ بتنسيقٍ صالح وإلا ستُرجع false وتصبح قيمة التاريخ 1969-12-31. استخدام DateTime()‎ توفر PHP بدءًا من الإصدار 5.2 الصنف DateTime()‎ الذي يمنح أدوات قوية للعمل مع التاريخ والوقت، يمكننا استخدامه لكتابة الشيفرة السابقة كالتالي: $date = new DateTime('2008-07-01T22:35:17.02'); $new_date_format = $date->format('Y-m-d H:i:s'); العمل مع الأختام الزمنية unix تأخذ الدالة date()‎ معاملها الثاني بصيغة ختم زمني unix وتُرجع تاريخ منسّق: $new_date_format = date('Y-m-d H:i:s', '1234567890'); يعمل DateTime()‎ مع الختم الزمني unix بإضافة @ قبل الختم الزمني: $date = new DateTime('@1234567890'); $new_date_format = $date->format('Y-m-d H:i:s'); إذا كان الختم الزمني لديك مقدّر بأجزاء الثانية (قد تكون نهايته 000 و/أو يتكون من 13 محرف) ستحتاج أن تحوّله إلى الثواني قبل أن تستطيع تغيير تنسيقه، يوجد طريقتين للقيام بذلك: حذف آخر 3 أرقام باستخدام substr()‎: يمكن حذف الأرقام الثلاثة الأخيرة بعدة طرق لكن أسهلها استخدام substr()‎: $timestamp = substr('1234567899000', -3); القسمة على 1000: يمكنك أيضًا تحويل الختم الزمني إلى الثواني عن طريق القسمة على 1000، يمكنك استخدام المكتبة BCMath للقيام بالعمليات الرياضية كسلاسل نصية لأنّ الختم الزمني كبير جدًا على أنظمة 32 بت: $timestamp = bcdiv('1234567899000', '1000'); يمكنك استخدام strtotime()‎ للحصول على ختم زمني unix: $timestamp = strtotime('1973-04-18'); يمكنك استخدام DateTime::getTimestamp()‎ مع DateTime()‎: $date = new DateTime('2008-07-01T22:35:17.02'); $timestamp = $date->getTimestamp(); يمكنك بدلًا من ذلك استخدام خيار التنسيق U إذا كنت تستخدم الإصدار PHP 5.2: $date = new DateTime('2008-07-01T22:35:17.02'); $timestamp = $date->format('U'); العمل مع تنسيق التاريخ الغامض وغير المعياري لسوء الحظ ليست كل التواريخ التي يحتاج المطور أن يتعامل معها تكون منسّقة بطريقة معيارية، لكن لحسن الحظ وفرت PHP 5.3 حلًّا لذلك، تسمح لنا DateTime::createFromFormat()‎ بأن نخبر PHP عن تنسيق التاريخ لذا يمكن تحليلها بنجاح إلى كائن DateTime لمزيد من المعالجة. $date = DateTime::createFromFormat('F-d-Y h:i A', 'April-18-1973 9:48 AM'); $new_date_format = $date->format('Y-m-d H:i:s'); أعطتنا PHP 5.4 قابلة الوصول إلى عنصر في الصنف عند إنشاء نسخة مما يسمح لنا أن نكتب الشيفرة السابقة في سطر واحد: $new_date_format = (new DateTime('2008-07-01T22:35:17.02'))->format('Y-m-d H:i:s'); لكن هذا لا يعمل مع DateTime::createFromFormat()‎ بعد. تحليل وصف التاريخ باللغة الانكليزية إلى تنسيق تاريخ يمكن تحليل نصوص مختلفة باللغة الانكليزية إلى تواريخ باستخدام الدالتين strtotime()‎ وdate()‎ معًا.‍ // طباعة التاريخ الحالي echo date("m/d/Y", strtotime("now")), "\n"; // m/d/Y طباعة تاريخ 10 أيلول، 2000 بالتنسيق echo date("m/d/Y", strtotime("10 September 2000")), "\n"; // طباعة تاريخ الأمس echo date("m/d/Y", strtotime("-1 day")), "\n"; // طباعة نتيجة تاريخ اليوم + أسبوع echo date("m/d/Y", strtotime("+1 week")), "\n"; // طباعة نتيجة تاريخ اليوم + أسبوع ويومين و4 ساعات وثانيتين echo date("m/d/Y", strtotime("+1 week 2 days 4 hours 2 seconds")), "\n"; // طباعة تاريخ يوم الخميس القادم echo date("m/d/Y", strtotime("next Thursday")), "\n"; // طباعة تاريخ الاثنين الماضي echo date("m/d/Y", strtotime("last Monday")), "\n"; // طباعة تاريخ اليوم الأول من الشهر القادم echo date("m/d/Y", strtotime("First day of next month")), "\n"; // طباعة تاريخ اليوم الأخير من الشهر القادم echo date("m/d/Y", strtotime("Last day of next month")), "\n"; // طباعة تاريخ اليوم الأول من الشهر الماضي echo date("m/d/Y", strtotime("First day of last month")), "\n"; // طباعة تاريخ اليوم الأخير من الشهر الماضي echo date("m/d/Y", strtotime("Last day of last month")), "\n"; استخدام ثوابت معرّفة مسبقًا لتنسيق التاريخ بدءًا من الإصدار PHP 5.1.0 يمكننا استخدام ثوابت معرّفة مسبقًا لتنسيق التاريخ في الدالة date()‎ بدلًا من السلاسل النصية. ثوابت تنسيق التاريخ المعرّفة مسبقًا المتاحة: DATE_ATOM // (2016-07-22T14:50:01+00:00) (ATOM) الذرة DATE_COOKIE // ‏(Friday, 22-Jul-16 14:50:01 UTC) HTTP ملفات تعريف الارتباط DATE_RSS // (Fri, 22 Jul 2016 14:50:01 +0000) ‏RSS DATE_W3C // (2016-07-22T14:50:01+00:00) اتحاد شبكة الويب العالمية DATE_ISO8601 // ‏(2016-07-22T14:50:01+0000) ISO-8601 DATE_RFC822 // (Fri, 22 Jul 16 14:50:01 +0000) RFC 822 DATE_RFC850 // ‏(Friday, 22-Jul-16 14:50:01 UTC) RFC 850 DATE_RFC1036 // ‏(Fri, 22 Jul 16 14:50:01 +0000) RFC 1036 DATE_RFC1123 // ‏(Fri, 22 Jul 2016 14:50:01 +0000) RFC 1123 DATE_RFC2822 // (Fri, 22 Jul 2016 14:50:01 +0000) RFC 2822 DATE_RFC3339 // ‏(2016-07-22T14:50:01+00:00) DATE_ATOM ‏‏نفس أمثلة: echo date(DATE_RFC822); // Fri, 22 Jul 16 14:50:01 +0000 echo date(DATE_ATOM,mktime(0,0,0,8,15,1947)); // 1947-08-15T00:00:00+05:30 ترجمة -وبتصرف- للفصول [Datetime Class - Working with Dates and Time] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: بنى التحكم والحلقات التكرارية في PHP المقال السابق: التنفيذ على المصفوفات والتعامل معها في PHP تعلم PHP
  15. تطبيق دالة على كل عنصر من عناصر المصفوفة نستخدم الدالة array_map()‎ لتطبيق دالة على جميع عناصر مصفوفة ما، وتُرجع لنا مصفوفة جديدة: $array = array(1,2,3,4,5); // يتم المرور على كل عنصر من عناصر المصفوفة ويُخزَّن في معامل الدالة $newArray = array_map(function($item) { return $item + 1; }, $array); // array(2,3,4,5,6) الآن $newArray قيمة يمكنك استخدام دالة ذات اسم بدلًا من استخدام دالة مجهولة الاسم، فنكتب الشيفرة السابقة كالتالي: function addOne($item) { return $item + 1; } $array = array(1, 2, 3, 4, 5); $newArray = array_map('addOne', $array); إذا كانت الدالة المسمّاة هي تابع صنف فيجب تضمين مرجع إلى كائن الصنف الذي يعود التابع له عند استدعاء الدالة: class Example { public function addOne($item) { return $item + 1; } public function doCalculation() { $array = array(1, 2, 3, 4, 5); $newArray = array_map(array($this, 'addOne'), $array); } } الطريقة الثانية لتطبيق دالة على عناصر المصفوفة هي استخدام الدالة array_walk()‎ والدالة array_walk_recursive()‎، يُمرَّر رد النداء (callback) إلى هاتين الدالتين ويأخذ مفتاح/فهرس وقيمة كل عنصر من عناصر المصفوفة، لا ترجع هذه الدوال دوالًا جديدة إنّما تُرجع قيمة منطقية تدل على نجاح تنفيذ الدالة، يمكننا مثلًا طباعة كل عنصر في المصفوفة بكتابة الشيفرة التالية: $array = array(1, 2, 3, 4, 5); array_walk($array, function($value, $key) { echo $value . ' '; }); // "1 2 3 4 5" يمكن تمرير قيمة معامل رد النداء بالمرجعية مما يسمح لك بتمرير القيمة مباشرةً في المصفوفة الأصلية: $array = array(1, 2, 3, 4, 5); array_walk($array, function(&$value, $key) { $value++; }); // array(2,3,4,5,6) الآن $array قيمة نستخدم الدالة array_walk_recursive()‎ مع المصفوفات المتداخلة (nested) للتعامل مع كل مصفوفة فرعية: $array = array(1, array(2, 3, array(4, 5), 6); array_walk_recursive($array, function($value, $key) { echo $value . ' '; }); // "1 2 3 4 5 6" لاحظ أنّ الدالتين array_walk()‎ وarray_walk_recursive()‎ يسمحان لك بتغيير قيمة عناصر المصفوفة وليس المفاتيح، كما أنّه يمكنك تمرير المفاتيح بالمرجع في رد النداء لكن هذا ليس له تأثير. تقسيم المصفوفة إلى أجزاء (chunks) تقسّم الدالة array_chunk()‎ المصفوفة إلى أجزاء صغيرة. بفرض لدينا الدالة أحادية البعد التالية: $input_array = array('a', 'b', 'c', 'd', 'e'); وطبّقنا الدالة array_chunk()‎ عليها: $output_array = array_chunk($input_array, 2); تقسّم الشيفرة السابقة المصفوفة إلى مصفوفات أصغر تحتوي كلّ منها عنصرين وتنشئ مصفوفة جديدة متعددة الأبعاد: Array ( [0] => Array ( [0] => a [1] => b ) [1] => Array ( [0] => c [1] => d ) [2] => Array ( [0] => e ) ) إذا لم يكن من الممكن تقسيم عناصر المصفوفة بالتساوي وفقًا للحجم المحدد فإنّ المصفوفة الأخيرة ستحمل العناصر الباقية وتكون أصغر من باقي المصفوفات. إذا مررنا للوسيط الثاني عددًا أصغر من 1 سيُرمى E_WARNING وستكون مصفوفة الخرج NULL. المعامل تفاصيل ‎$array (array)‎ مصفوفة الدخل التي نريد العمل عليها ‎$size (int)‎ حجم المصفوفة الصغيرة (عدد صحيح)‏ ‎$preserve_keys (boolean)‎ (اختياري) FALSE وإلا TRUE إذا أردت أن تحافظ المصفوفات الصغيرة على المفاتيح تكون قيمته table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } تجميع عناصر مصفوفة لتشكيل سلسلة نصية تجمع الدالة implode()‎ كل قيم المصفوفة لكنها تفقد معلومات المفاتيح: $arr = ['a' => "AA", 'b' => "BB", 'c' => "CC"]; echo implode(" ", $arr); // AA BB CC نستطيع تجميع المفاتيح باستدعاء الدالة array_keys()‎: $arr = ['a' => "AA", 'b' => "BB", 'c' => "CC"]; echo implode(" ", array_keys($arr)); // a b c يعدّ تجميع القيم مع المفاتيح أكثر تعقيدًا لكن يمكن القيام به بنمط الدالة: $arr = ['a' => "AA", 'b' => "BB", 'c' => "CC"]; echo implode(" ", array_map(function($key, $val) { // دالة تربط المفتاح بالقيمة return "$key:$val"; }, array_keys($arr), $arr)); // a:AA b:BB c:CC تفكيك مصفوفة باستخدام list()‎ يمكن استخدام الدالة list()‎ لإسناد قائمة من قيم المتغيرات إلى مصفوفة. كما يمكنك الاطلاع على الدالة compact()‎. // بمفاتيح مرقمة تبدأ من الصفر $array إلى عناصر المصفوفة $a,$b,$c إسناد قيم المتغيرات list($a, $b, $c) = $array; بدءًا من الإصدار 7.1 يمكنك استخدام الصيغة المختصرة: // بمفاتيح مرقمة تبدأ من الصفر $array إلى عناصر المصفوفة $a,$b,$c إسناد قيم المتغيرات [$a, $b, $c] = $array; //"a", "b", "c" والمفاتيح $array إلى عناصر المصفوفة $a,$b,$c إسناد قيم المتغيرات ["a" => $a, "b" => $b, "c" => $c] = $array; الدالة array_reduce تختزل الدالة array_reduce المصفوفة إلى قيمة واحدة. تمرّ الدالة على كل عنصر من عناصر المصفوفة بنتيجة مُعادة من التكرار السابق وتنتج قيمة جديدة تحتفظ بها للتكرار التالي. الاستخدام: array_reduce ($array, function($carry, $item){...}, $defaul_value_of_first_carry) ‎$carry‎ هي النتيجة المعادة من التكرار السابق و‎$item يحمل قيمة التكرار الحالي. مجموع مصفوفة $result = array_reduce([1, 2, 3, 4, 5], function($carry, $item){ return $carry + $item; }); // 15 أكبر عدد في المصفوفة $result = array_reduce([10, 23, 211, 34, 25], function($carry, $item){ return $item > $carry ? $item : $carry; }); // 211 هل كل العناصر أكبر من 100 $result = array_reduce([101, 230, 210, 341, 251], function($carry, $item){ return $carry && $item > 100; }, true); // true يجب أن تكون القيمة الافتراضية // true هل يوجد عنصر أصغر من 100 $result = array_reduce([101, 230, 21, 341, 251], function($carry, $item){ return $carry || $item < 100; }, false); // false يجب أن تكون القيمة الافتراضية // true تجميع مصفوفة $result = array_reduce(["hello", "world", "PHP", "language"], function($carry, $item){ return !$carry ? $item : $carry . "-" . $item ; }); // "hello-world-PHP-language" يمكنك أيضًا كتابة تابع للتجميع: function implode_method($array, $piece){ return array_reduce($array, function($carry, $item) use ($piece) { return !$carry ? $item : ($carry . $piece . $item); }); } $result = implode_method(["hello", "world", "PHP", "language"], "-"); // "hello-world-PHP-language" دفع عنصر إلى نهاية مصفوفة يوجد طريقتين لدفع عنصر إلى مصفوفة: الدالة array_push()‎ و‎$array[] =‎. نستخدم الدالة array_push()‎ كما يلي: $array = [1,2,3]; $newArraySize = array_push($array, 5, 6); // يُرجع التابع الحجم الجديد للمصفوفة print_r($array); // تُمرّر المصفوفة بالمرجع لذا تُعدّل المصفوفة الأساسية لتحتوي العناصر الجديدة ستطبع الشيفرة: Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 5 [4] => 6 ) نستخدم ‎$array[] =‎ كالتالي: $array = [1,2,3]; $array[] = 5; $array[] = 6; print_r($array); تطبع الشيفرة السابقة: Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 5 [4] => 6 ) ترشيح (Filtering) مصفوفة نستخدم الدالة array_filter لترشيح قيم من المصفوفة والحصول على مصفوفة جديدة تحقق كل شروط المُرشِّح. ترشيح القيم غير الفارغة أفضل حالة للترشيح هي إزالة جميع القيم الفارغة: $my_array = [1,0,2,null,3,'',4,[],5,6,7,8]; $non_empties = array_filter($my_array); // $non_empties will contain // [1,2,3,4,5,6,7,8] هو $non_empties يصبح محتوى المصفوفة الترشيح برد النداء بفرض أننا نريد الحصول على القيم الزوجية فقط: $my_array = [1,2,3,4,5,6,7,8]; $even_numbers = array_filter($my_array, function($number) { return $number % 2 === 0; }); تستقبل الدالة array_filter المصفوفة التي نريد ترشيحها في الوسيط الأول أما الوسيط الثاني فهو المُرشِّح الذي نريد تطبيقه. الإصدار PHP 5.6 وما بعده الترشيح تبعًا للفهرس يمكننا تمرير معامل ثالث للدالة array_filter يسمح بتعديل القيم الممررة إلى رد النداء، يمكن ضبط هذا المعامل على أنّه ARRAY_FILTER_USE_KEY أو ARRAY_FILTER_USE_BOTH، وهو يسمح أن يستقبل رد النداء المفاتيح بدلًا من القيم أو كلًا من المفاتيح والقيم كوسطاء. فإذا أردت التعامل مع الفهارس بدلًا من القيم مثلًا: $numbers = [16,3,5,8,1,4,6]; $even_indexed_numbers = array_filter($numbers, function($index) { return $index % 2 === 0; }, ARRAY_FILTER_USE_KEY); الفهارس في المصفوفة المرشَّحة لاحظ أنّ الدالة array_filter تحافظ على مفاتيح المصفوفة الأصلية، ومن الخطأ أن تقوم بتنفيذ حلقة for على المصفوفة المُرشَّحة: $my_array = [1,0,2,null,3,'',4,[],5,6,7,8]; $filtered = array_filter($my_array); // إظهار كل الأخطاء والملاحظات error_reporting(E_ALL); // for من الخطأ استخدام حلقة for ($i = 0; $i < count($filtered); $i++) { print $filtered[$i]; } /* :الخرج 1 Notice: Undefined offset: 1 2 Notice: Undefined offset: 3 3 Notice: Undefined offset: 5 4 Notice: Undefined offset: 7 */ حدث هذا لأن قيم الفهارس 1، 3، 5، 7 التي هي 0، null، سلسلة فارغة ''، مصفوفة فارغة [] على الترتيب قد حُذفت مع فهارسها. إذا كنت تحتاج تمرير حلقة على نتيجة مُرشِّح طُبِّق على مصفوفة مفهرسة، يجب أن تستدعي الدالة array_values لنتيجة الدالة array_filter لتُنشئ مصفوفة جديدة مع فهارس صحيحة: $my_array = [1,0,2,null,3,'',4,[],5,6,7,8]; $filtered = array_filter($my_array); $iterable = array_values($filtered); error_reporting(E_ALL); // إظهار كل الأخطاء والملاحظات for ($i = 0; $i < count($iterable); $i++) { print $iterable[$i]; } // لا يوجد تحذيرات إزالة عناصر من مصفوفة لإزالة عنصر من مصفوفة مثلًا العنصر الذي فهرسه 1: $fruit = array("bananas", "apples", "peaches"); unset($fruit[1]); ستزيل الشيفرة السابقة العنصر apples من المصفوفة لكن لاحظ أنّ الدالة unset لا تغيّر فهارس باقي العناصر لذا فإنّ المصفوفة ‎$fruit تحتوي الآن الفهارس 0 و2. يمكنك الحذف في المصفوفات الترابطية كما يلي: $fruit = array('banana', 'one'=>'apple', 'peaches'); print_r($fruit); /* Array ( [0] => banana [one] => apple [1] => peaches ) */ unset($fruit['one']); تصبح الآن المصفوفة ‎$fruit: print_r($fruit); /* Array ( [0] => banana [1] => peaches ) */ لاحظ أنّ الشيفرة التالية: unset($fruit); تزيل تعيين المتغير لذا تحذف كامل المصفوفة أي أنّه لم يعد بإمكاننا الوصول إلى أي عنصر من عناصرها. إزالة عناصر الأطراف تُزيل الدالة array_shift()‎ عنصرًا من بداية المصفوفة. مثال: $fruit = array("bananas", "apples", "peaches"); array_shift($fruit); print_r($fruit); الخرج: Array ( [0] => apples [1] => peaches ) تُزيل الدالة array_pop()‎ عنصرًا من نهاية المصفوفة. مثال: $fruit = array("bananas", "apples", "peaches"); array_pop($fruit); print_r($fruit); الخرج: Array ( [0] => bananas [1] => apples ) ترتيب مصفوفة يوجد عدة دوال لترتيب المصفوفات في PHP: الدالة sort()‎ ترتّب هذه الدالة عناصر المصفوفة ترتيبًا تصاعديًّا وفقًا لقيمها. $fruits = ['Zitrone', 'Orange', 'Banane', 'Apfel']; sort($fruits); print_r($fruits); /* Array ( [0] => Apfel [1] => Banane [2] => Orange [3] => Zitrone ) */ الدالة rsort()‎ ترتّب هذه الدالة عناصر المصفوفة ترتيبًا تنازليًّا وفقًا لقيمها. $fruits = ['Zitrone', 'Orange', 'Banane', 'Apfel']; rsort($fruits); print_r($fruits); /* Array ( [0] => Zitrone [1] => Orange [2] => Banane [3] => Apfel ) */ الدالة asort()‎ تُرتِّب هذه الدالة عناصر المصفوفة ترتيبًا تصاعديًّا وتحافظ على اقتران الفهارس مع القيم المرتبطة بها. $fruits = [1 => 'lemon', 2 => 'orange', 3 => 'banana', 4 => 'apple']; asort($fruits); print_r($fruits); /* Array ( [4] => apple [3] => banana [1] => lemon [2] => orange ) */ الدالة arsort()‎ تُرتِّب هذه الدالة عناصر المصفوفة ترتيبًا تنازليًّا وتحافظ على اقتران الفهارس مع القيم المرتبطة بها. $fruits = [1 => 'lemon', 2 => 'orange', 3 => 'banana', 4 => 'apple']; arsort($fruits); print_r($fruits); /* Array ( [2] => orange [1] => lemon [3] => banana [4] => apple ) */ الدالة ksort()‎ تُرتّب هذه الدالة المصفوفة ترتيبًا تصاعديًّا حسب المفتاح. $fruits = ['d'=>'lemon', 'a'=>'orange', 'b'=>'banana', 'c'=>'apple']; ksort($fruits); print_r($fruits); /* Array ( [a] => orange [b] => banana [c] => apple [d] => lemon ) */ الدالة krsort()‎ تُرتّب هذه الدالة المصفوفة ترتيبًا تنازليًّا حسب المفتاح. $fruits = ['d'=>'lemon', 'a'=>'orange', 'b'=>'banana', 'c'=>'apple']; krsort($fruits); print_r($fruits); /* Array ( [d] => lemon [c] => apple [b] => banana [a] => orange ) */ الدالة natsort()‎ ترتّب هذه الدالة المصفوفة بنفس الطريقة التي يرتّب بها الإنسان (ترتيب طبيعي). $files = ['File8.stack', 'file77.stack', 'file7.stack', 'file13.stack', 'File2.stack']; natsort($files); print_r($files); /* Array ( [4] => File2.stack [0] => File8.stack [2] => file7.stack [3] => file13.stack [1] => file77.stack ) */ الدالة natcasesort()‎ ترتّب هذه الدالة المصفوفة بنفس الطريقة التي يرتّب بها الإنسان (ترتيب طبيعي) لكن غير حساسة لحالة الأحرف. $files = ['File8.stack', 'file77.stack', 'file7.stack', 'file13.stack', 'File2.stack']; natcasesort($files); print_r($files); /* Array ( [4] => File2.stack [2] => file7.stack [0] => File8.stack [3] => file13.stack [1] => file77.stack ) */ الدالة shuffle()‎ تخلط هذه الدالة عناصر المصفوفة (ترتيب عشوائي). $array = ['aa', 'bb', 'cc']; shuffle($array); print_r($array); بما أنّ الترتيب عشوائي فليس هناك نتيجة واحدة محددة للشيفرة السابقة، فقد تكون مثلًا: Array ( [0] => cc [1] => bb [2] => aa ) الدالة usort()‎ ترتّب هذه الدالة المصفوفة باستخدام دالة موازنة معرّفة من قبل المستخدم. function compare($a, $b) { if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1; } $array = [3, 2, 5, 6, 1]; usort($array, 'compare'); print_r($array); /* Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 5 [4] => 6 ) */ الدالة uasort()‎ ترتّب هذه الدالة المصفوفة باستخدام دالة موازنة معرّفة من قبل المستخدم وتحافظ على ارتباط الفهارس بالقيم المقابلة. function compare($a, $b) { if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1; } $array = ['a' => 1, 'b' => -3, 'c' => 5, 'd' => 3, 'e' => -5]; uasort($array, 'compare'); print_r($array); /* Array ( [e] => -5 [b] => -3 [a] => 1 [d] => 3 [c] => 5 ) */ الدالة uksort()‎ ترتّب هذه الدالة المصفوفة حسب المفاتيح باستخدام دالة موازنة معرّفة من قبل المستخدم. function compare($a, $b) { if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1; } $array = ['ee' => 1, 'g' => -3, '4' => 5, 'k' => 3, 'oo' => -5]; uksort($array, 'compare'); print_r($array); /* Array ( [ee] => 1 [g] => -3 [k] => 3 [oo] => -5 [4] => 5 ) */ إضافة بعض مفاتيح المصفوفة إلى اللائحة البيضاء يمكنك استخدام الدالتين array_intersect_key وarray_flip معًا عندما تريد السماح بوجود مفاتيح محددة فقط في المصفوفة خاصةً عندما تأتي المفاتيح من معاملات طلب ما. $parameters = ['foo' => 'bar', 'bar' => 'baz', 'boo' => 'bam']; $allowedKeys = ['foo', 'bar']; $filteredParameters = array_intersect_key($parameters, array_flip($allowedKeys)); // $filteredParameters = ['foo' => 'bar', 'bar' => 'baz] إذا لم يحتوي متغير المعاملات على أي مفتاح مسموح به فإنّ المتغير filteredParameters سيكون مصفوفة فارغة. ويمكنك أيضًا استخدام الدالة array_filter لنفس الوظيفة بدءًا من الإصدار PHP 5.6، ممررًا الراية ARRAY_FILTER_USE_KEY كمعامل ثالث. $parameters = ['foo' => 1, 'hello' => 'world']; $allowedKeys = ['foo', 'bar']; $filteredParameters = array_filter( $parameters, function ($key) use ($allowedKeys) { return in_array($key, $allowedKeys); }, ARRAY_FILTER_USE_KEY ); يوفر استخدام الدالة arrayfilter مرونةً إضافيةً لإجراء اختبار عشوائي على المفاتيح، فمثلًا يمكن أن تحتوي المصفوفة ‎$allowedKeys أنماطًا تعبيرية بدلًا من السلاسل النصية العادية، كما أنّها تذكر الهدف من الشيفرة بشكلٍ أكثر صراحة من الدالتين array_intersect_key وarray_flip. إضافة عنصر إلى بداية المصفوفة يمكنك استخدام الدالة array_unshift()‎ عند الحاجة إلى إضافة عنصر في بداية المصفوفة دون تغيير في ترتيب العناصر الحالية، إذ تضيف هذه الدالة العناصر الممررة بالكامل إلى بداية المصفوفة فتبقى العناصر الممررة بنفس الترتيب، وتعدَّل جميع المفاتيح العددية لتبدأ من الصفر بينما تبقى المفاتيح التي تكون بشكل سلسلة نصية كما هي. $myArray = array(1, 2, 3); array_unshift($myArray, 4); print_r($myArray); // Array ( [0] => 4 [1] => 1 [2] => 2 [3] => 3 ) بما أنّ الدالة array_unshift()‎ تُجبر المصفوفة على إعادة تعيين أزواج مفتاح - قيمة والعنصر الجديد يجعل مفاتيح المداخل التالية تبدأ من n+1 فمن الأسهل إنشاء مصفوفة جديدة وإضافة المصفوفة الحالية إلى المصفوفة المُنشأة حديثًا. مثال: $myArray = array('apples', 'bananas', 'pears'); $myElement = array('oranges'); $joinedArray = $myElement; foreach ($myArray as $i) { $joinedArray[] = $i; } // Array ( [0] => oranges [1] => apples [2] => bananas [3] => pears ) يمكنك الاطلاع على المثال هنا تبادل القيم مع المفاتيح تستبدل الدالة array_flip()‎ كل مفاتيح المصفوفة بالقيم المرتبطة معها. $colors = array( 'one' => 'red', 'two' => 'blue', 'three' => 'yellow', ); array_flip($colors); /* array( 'red' => 'one', 'blue' => 'two', 'yellow' => 'three' ) */ دمج مصفوفتين في مصفوفة واحدة تدمج الدالة array_merge()‎ مصفوفتين أو أكثر. $a1 = array("red","green"); $a2 = array("blue","yellow"); print_r(array_merge($a1,$a2)); /* Array ( [0] => red [1] => green [2] => blue [3] => yellow ) */ المصفوفة الترابطية: $a1=array("a"=>"red","b"=>"green"); $a2=array("c"=>"blue","b"=>"yellow"); print_r(array_merge($a1,$a2)); /* Array ( [a] => red [b] => yellow [c] => blue ) */ تُدمج العناصر من مصفوفة أو واحدة أكثر فتُضاف قيم إحداها إلى نهاية المصفوفة الأخرى وتُرجع المصفوفة النهائية. إذا كان لمصفوفات الدخل نفس المفاتيح المكونة من سلاسل نصية فإنّ القيمة اللاحقة للمفتاح ستكتب فوق القيمة السابقة له أما إذا كانت المفاتيح عددية فإنّ القيمة اللاحقة ستُضاف ولن تكتب فوق القيمة السابقة للمفتاح. إذا كانت مفاتيح مصفوفة الدخل رقمية سيُعاد ترقيم القيم في مصفوفة النتيجة بمفاتيح تزايدية ابتداءً من الصفر. معالجة عدة مصفوفات معًا تقاطع (intersection) المصفوفات تُرجع الدالة array_intersect مصفوفة من القيم الموجودة في جميع المصفوفات الممررة إليها وتحافظ على المفاتيح المرتبطة مع القيم المعادة. $array_one = ['one', 'two', 'three']; $array_two = ['two', 'three', 'four']; $array_three = ['two', 'three']; $intersect = array_intersect($array_one, $array_two, $array_three); // $intersect = ['two', 'three'] تتحقق الدالة array_intersect من قيم المصفوفات فقط أما الدالة array_intersect_assoc تُرجع تقاطع القيم مع المفاتيح المرتبطة بها. $array_one = [1 => 'one',2 => 'two',3 => 'three']; $array_two = [1 => 'one', 2 => 'two', 3 => 'two', 4 => 'three']; $array_three = [1 => 'one', 2 => 'two']; $intersect = array_intersect_assoc($array_one, $array_two, $array_three); // $intersect = [1 =>'one',2 => 'two'] تتحقق الدالة array_intersect_key من التقاطع بتفحص المفاتيح فقط وتُرجع المفاتيح الموجودة في جميع المصفوفات الممررة. $array_one = [1 => 'one',2 => 'two',3 => 'three']; $array_two = [1 => 'one', 2 => 'two', 3 => 'four']; $array_three = [1 => 'one', 3 => 'five']; $intersect = array_intersect_key($array_one, $array_two, $array_three); // $intersect = [1 =>'one',3 => 'three'] دمج أو ضم مصفوفتين $fruit1 = ['apples', 'pears']; $fruit2 = ['bananas', 'oranges']; $all_of_fruits = array_merge($fruit1, $fruit2); //$all_of_fruits = [0 => 'apples', 1 => 'pears', 2 => 'bananas', 3 => 'oranges'] لاحظ أنّ الدالة array_merge()‎ ستغيّر الفهارس الرقمية وتكتب فوق الفهارس النصية. $fruit1 = ['one' => 'apples', 'two' => 'pears']; $fruit2 = ['one' => 'bananas', 'two' => 'oranges']; $all_of_fruits = array_merge($fruit1, $fruit2); //$all_of_fruits = ['one' => 'bananas', 'two' => 'oranges'] تعيد الدالة array_merge()‎ كتابة قيم المصفوفة الثانية فوق قيم المصفوفة الأولى إذا لم تستطع إعادة ترقيم فهارسها. يمكنك استخدام العامل + لدمج مصفوفتين بطريقة لا يمكن فيها للقيم أن يُكتب فوقها لكنها لا تُعيد ترقيم الفهارس العددية لذا تخسر قيم المصفوفات التي لها فهرس مُستخدم في المصفوفة الأولى. $fruit1 = ['one' => 'apples', 'two' => 'pears']; $fruit2 = ['one' => 'bananas', 'two' => 'oranges']; $all_of_fruits = $fruit1 + $fruit2; // $all_of_fruits = ['one' => 'apples', 'two' => 'pears'] $fruit1 = ['apples', 'pears']; $fruit2 = ['bananas', 'oranges']; $all_of_fruits = $fruit1 + $fruit2; // $all_of_fruits = [0 => 'apples', 1 => 'pears'] تغيير مصفوفة متعددة الأبعاد إلى مصفوفة ترابطية إذا كان لديك مصفوفة متعددة الأبعاد مثل: [ ['foo', 'bar'], ['fizz', 'buzz'], ] وتريد تغييرها إلى مصفوفة ترابطية مثل: [ 'foo' => 'bar', 'fizz' => 'buzz', ] فيمكنك استخدام الشيفرة التالية: $multidimensionalArray = [ ['foo', 'bar'], ['fizz', 'buzz'], ]; $associativeArrayKeys = array_column($multidimensionalArray, 0); $associativeArrayValues = array_column($multidimensionalArray, 1); $associativeArray = array_combine($associativeArrayKeys, $associativeArrayValues); أو يمكنك تخطي الإعداد ‎$associativeArrayKeys و‎$associativeArrayValues واستخدام الشيفرة البسيطة التالية: $associativeArray = array_combine(array_column($multidimensionalArray, 0), array_column($multidimensionalArray, 1)); جمع مصفوفتين (المفاتيح من واحدة والقيم من الأخرى) تنشئ الدالة array_combine()‎ مصفوفةً جديدةً عن طريق استخدام المفاتيح من أحد المصفوفات والقيم من مصفوفة أخرى. يبين المثال التالي كيفية دمج مصفوفتين في مصفوفة واحدة ترابطية فيها المفاتيح من المصفوفة الأولى والقيم من المصفوفة الثانية: $array_one = ['key1', 'key2', 'key3']; $array_two = ['value1', 'value2', 'value3']; $array_three = array_combine($array_one, $array_two); var_export($array_three); /* array ( 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', ) */ مكتبة PHP المعيارية لهياكل البيانات (SPL) SplFixedArray الاختلاف عن مصفوفة PHP يُنفَّذ نوع المصفوفة الافتراضي في PHP كخرائط hash مرتبة مما يسمح لنا بإنشاء مصفوفات تحتوي على الأزواج قيمة/مفتاح، يمكن أن تكون القيم من أي نوع والمفاتيح إما أرقام أو سلاسل نصية، ومع ذلك فإنّ هذه ليست الطريقة التقليدية لإنشاء المصفوفات. كما تلاحظ في الرسم التوضيحي فإنّه من الممكن أن تُعرض مصفوفة PHP مثل مجموعة مرتبة من الأزواج قيمة/مفتاح حيث يُربَط كل مفتاح إلى أي قيمة. لاحظ أنّه في هذه المصفوفة لدينا مفاتيح عددية ومفاتيح بشكل سلاسل نصية بالإضافة إلى قيم من أنواع مختلفة والمفتاح ليس له تأثير على ترتيب العناصر. $arr = [ 9 => "foo", 1 => 4.2, "bar" => null, ]; foreach($arr as $key => $value) { echo "$key => $value\n"; } تعطينا الشيفرة السابقة ما نتوقعه تمامًا: 9 => foo 1 => 4.2 bar => يُحدد حجم مصفوفات PHP العادية ديناميكيًا، ويزداد وينقص هذا الحجم بإضافة أو حذف عناصر إلى ومن المصفوفة بشكلٍ تلقائي، أما في المصفوفات التقليدية يكون الحجم ثابتًا وتتألف المصفوفة بالكامل من نفس نوع القيم ويمكننا الوصول إلى أي قيمة باستخدام الفهارس بدلًا من المفاتيح ويمكن استنتاج الفهرس من إزاحة القيمة في المصفوفة. وبما أننا سنعرف حجم نوع معين والحجم الثابت للمصفوفة فإنّ الإزاحة هي حجم النوع * n، إذ تمثّل n موضع القيمة في المصفوفة، يعطينا ‎$arr[0]‎ في المثال السابق القيمة 1 ويعطينا العنصر الأول في المصفوفة ‎$arr[1]‎ القيمة 2 وهكذا… ومع ذلك لا يقيّد الصنف SplFixedArray نوع القيم إنما يقيد نوع المفاتيح فقط والتي يجب أن تكون عددية، ?وأيضًا ذات حجم ثابت، وهذا يجعل المصفوفات من النوع SplFixedArray أكثر فعاليةٍ من المصفوفات العادية بطريقةٍ معينة فهي أصغر وتتطلب ذاكرة أقل. تهيئة المصفوفة تُنفَّذ المصفوفة من النوع SplFixedArray ككائن لكن يمكن الوصول إليها بنفس الصياغة التي نصل فيها إلى مصفوفة PHP العادية بما أنّها تنفّذ الواجهة ArrayAccess وتنفّذ أيضًا الواجهات Countable وIterator لذا لها نفس سلوك مصفوفات PHP العادية الذي اعتدت عليه (أي الأشياء مثل ‎count(‎$arr)‎ وforeach($arr as $k => $v)‎ تعمل بنفس الطريقة في المصفوفات من النوع SplFixedArray والمصفوفات العادية). يأخذ باني المصفوفة من النوع SplFixedArray وسيطًا واحدًا يعبّر عن حجم المصفوفة. $arr = new SplFixedArray(4); $arr[0] = "foo"; $arr[1] = "bar"; $arr[2] = "baz"; foreach($arr as $key => $value) { echo "$key => $value\n"; } تعطيك الشيفرة السابقة النتيجة: 0 => foo 1 => bar 2 => baz 3 => كما أنّ الشيفرة: var_dump(count($arr)); تعطينا ما نتوقع: int(4) لاحظ أنه في المصفوفات من النوع SplFixedArray يصف المفتاح ترتيب العنصر في المصفوفة لأنه فهرس حقيقي وليس مجرد خريطة كما في المصفوفات العادية.? تغيير حجم المصفوفة تذكر دائمًا بما أنّ المصفوفة ذات حجم ثابت فإنّ عدّها سيعيد دائمًا القيمة نفسها، لذا طالما أنّ نتيجة unset($arr[1])‎ هي ‎$arr[1] === null‎ تبقى نتيجة count($arr)‎ هي 4، وتحتاج لتغيير حجم المصفوفة إلى استدعاء التابع setSize: $arr->setSize(3); var_dump(count($arr)); foreach($arr as $key => $value) { echo "$key => $value\n"; } الآن نحصل على النتيجة: int(3) 0 => foo 1 => 2 => baz الاستيراد من SplFixedArray والتصدير إلى SplFixedArray يمكنك استيراد/تصدير مصفوفة عادية إلى/من مصفوفة SplFixedArray باستخدام التوابع fromArray وtoArray. $array = [1,2,3,4,5]; $fixedArray = SplFixedArray::fromArray($array); foreach($fixedArray as $value) { echo $value, "\n"; } // 1 2 3 4 5 $fixedArray = new SplFixedArray(5); $fixedArray[0] = 1; $fixedArray[1] = 2; $fixedArray[2] = 3; $fixedArray[3] = 4; $fixedArray[4] = 5; $array = $fixedArray->toArray(); foreach($array as $value) { echo $value, "\n"; } // 1 2 3 4 5 ترجمة -وبتصرف- للفصول [Executing Upon an Array - Manipulating an Array - Processing Multiple Arrays Together - SPL data structures] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: التعامل مع الوقت والتاريخ في PHP المقال السابق: المصفوفات (Arrays) في PHP
  16. المصفوفة (array) هي بنية بيانات تخزّن عدة قيم في قيمة واحدة، وهي خريطة مرتبة تربط بين القيم والمفاتيح. تُنشَأ مصفوفة مباشرة عبر الأقواس المعقوفة [] أو عبر الدالة Array()‎ التي تأخذ المعاملات التالية: المعامل التفاصيل key (المفتاح) المفتاح هو المعرّف الفريد وفهرس المصفوفة. من الممكن أن يكون سلسلة نصية أو عدد صحيح مثل ‎'foo'‎، '5'، 10، 'a2b'‎ value (القيمة) ‏يوجد لكل مفتاح قيمة مقابلة (وإلا تكون null ويمكن أن تكون القيمة من أي نوع‎، وستظهر رسالة خطأ إذا حاولت الوصول إليه)‏ table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } تهيئة مصفوفة يمكن أن نُهيأ مصفوفة فارغة عبر الدالة Array()‎ أو الأقواس [] مباشرةً: // مصفوفة فارغة $foo = array(); // PHP 5.4 يمكن استخدام هذا التدوين المختزل بدءًا من الإصدار $foo = []; ويمكن أن نُهيأ مصفوفة ونعطيها قيم في نفس الوقت: // إنشاء مصفوفة بسيطة من 3 سلاسل نصية $fruit = array('apples', 'pears', 'oranges'); // PHP 5.4 يمكن استخدام هذا التدوين المختزل بدءًا من الإصدار $fruit = ['apples', 'pears', 'oranges']; ويمكن أيضًا أن نهيأ مصفوفة مع فهارس مخصصة (تسمى مصفوفة ترابطية associative array): // مصفوفة ترابطية بسيطة $fruit = array( 'first' => 'apples', 'second' => 'pears', 'third' => 'oranges' ); // يمكن أن نضبط القيمة والمفتاح كالتالي $fruit['first'] = 'apples'; // PHP 5.4 يمكن استخدام هذا التدوين المختزل بدءًا من الإصدار $fruit = [ 'first' => 'apples', 'second' => 'pears', 'third' => 'oranges' ]; ستنشئ PHP المتغير تلقائيًا إذا لم يكن قد اُستخدم سابقًا، وهذا يجعل الشيفرة ملائمة لكنها صعبة القراءة: $foo[] = 1; // Array( [0] => 1 ) $bar[][] = 2; // Array( [0] => Array( [0] => 2 ) ) تتابع PHP الفهرسة من حيث توقفت وتستخدم سلاسل نصية عددية كأعداد صحيحة للفهرسة: $foo = [2 => 'apple', 'melon']; // Array( [2] => apple, [3] => melon ) $foo = ['2' => 'apple', 'melon']; // نفس المصفوفة السابقة $foo = [2 => 'apple', 'this is index 3 temporarily', '3' => 'melon']; // نفس المصفوفة السابقة، سيقوم العنصر الأخير بالكتابة فوق العنصر الثاني يمكنك استخدام الصنف splfixedarray لتهيئة المصفوفة بحجم ثابت: $array = new SplFixedArray(3); $array[0] = 1; $array[1] = 2; $array[2] = 3; $array[3] = 4; // RuntimeException // زيادة حجم المصفوفة إلى 10 $array->setSize(10); ملاحظة: المصفوفة المنشأة باستخدام الصف SplFixedArray لها مساحة ذاكرة منخفضة لمجموعات كبيرة من البيانات، ولكن يجب أن تكون المفاتيح أعدادًا صحيحة. لتهيئة مصفوفة بحجم ديناميكي وبعدة عناصر غير فارغة يمكنك استخدام حلقة كالتالي: $myArray = array(); $sizeOfMyArray = 5; $fill = 'placeholder'; for ($i = 0; $i < $sizeOfMyArray; $i++) { $myArray[] = $fill; } print_r($myArray); // Array ( [0] => placeholder [1] => placeholder [2] => placeholder [3] => placeholder [4] => placeholder ) يمكنك استخدام الدالة array_fill()‎ لإنشاء مصفوفة وإعطاء مفاتيحها نفس القيم. array array_fill ( int $start_index , int $num , mixed $value ) تنشأ التعليمة السابقة وتُرجع مصفوفة فيها العدد num من العناصر التي قيمتها value بدءًا من الفهرس start_index، وإذا كانت قيمة start_index سالبة فستبدأ المصفوفة بفهرس سالب ثم تبدأ قيم الفهارس التالية من الصفر. $a = array_fill(5, 6, 'banana'); // Array ( [5] => banana, [6] => banana, ..., [10] => banana) $b = array_fill(-2, 4, 'pear'); // Array ( [-2] => pear, [0] => pear, ..., [2] => pear) نتيجة: عندما تستخدم الدالة array_fill()‎ تكون أكثر محدودية فيما تودّ فعله أما الحلقة فهي أكثر مرونة وتتيح لك الكثير من الخيارات. إذا أردت ملء المصفوفة بمجال أرقام (من 0 إلى 4 مثلًا) يمكنك إما إضافة كل عنصر بشكلٍ مفرد أو استخدام الدالة range()‎: array range ( mixed $start , mixed $end [, number $step = 1 ] ) تنشأ هذه الدالة مصفوفة تحوي مجال من العناصر، وفيها المعاملان الأول والثاني إلزاميان إذ يحددان بداية ونهاية المجال أما المعامل الثالث فهو خياري ويحدد مقدار زيادة العناصر في المجال. مثال: إنشاء مجال من 0 إلى 4 بمقدار زيادة 1 ستكون نتيجته مصفوفة عناصرها: 0، 1، 2، 3، 4 أما إذا كان مقدار الزيادة 2 فستكون عناصر المصفوفة 0، 2، 4. $array = []; $array_with_range = range(1, 4); for ($i = 1; $i <= 4; $i++) { $array[] = $i; } print_r($array); // Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 ) print_r($array_with_range); // Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 ) تعمل الدالة range بشكلٍ صحيح مع الأعداد الصحيحة والعشرية والمنطقية (التي حُوّلت إلى أعداد صحيحة) والسلاسل النصية، ولكن يجب أخذ الحذر عند استخدام الأعداد العشرية كوسيط بسبب مشكلة دقة الفاصلة العائمة. التحقق من وجود مفتاح يمكنك استخدام الدالة array_key_exists()‎ أو isset()‎ أو ‎!empty()‎ للتحقق من وجود مفتاح: $map = [ 'foo' => 1, 'bar' => null, 'foobar' => '', ]; array_key_exists('foo', $map); // true isset($map['foo']); // true !empty($map['foo']); // true array_key_exists('bar', $map); // true isset($map['bar']); // false !empty($map['bar']); // false لاحظ أن الدالة isset()‎ تعامل العنصر الذي قيمته null على أنّه غير موجود، وتعامل ‎!empty()‎ بالمثل أي عنصر قيمته false (استخدام موازنة ضعيفة مثل null، السلسلة الفارغة ' '، ‏0 تعاملها كلها على أنها false)، أي أن نتيجة isset($map['foobar'])‎ هي true أما نتيجة ‎!empty($map['foobar'])‎ هي false، يمكن أن يؤدي هذا إلى مشاكل (فمن الممكن أن تنسى مثلًا أنّ السلسلة النصية '0' تُعامَل على أنّها false) لذا فإنّ استخدام ‎!empty()‎ يمكن أن يكون غير مقبول. لاحظ أيضًا أنّ isset()‎ و‎!empty()‎ سيعملان ويعيدان false إذا كانت ‎$map غير معرفة أبدًا وهذا يجعلهم عرضة للخطأ نوعًا ما عند الاستخدام: // "long"و "lang" الفرق بين اسمي المتحولين هو في الكلمتين $my_array_with_a_long_name = ['foo' => true]; array_key_exists('foo', $my_array_with_a_lang_name); // يظهر تحذير isset($my_array_with_a_lang_name['foo']); // false يمكنك أيضًا التحقق في المصفوفات العادية: $ord = ['a', 'b']; // [0 => 'a', 1 => 'b'] المصفوفة السابقة تكافئ array_key_exists(0, $ord); // true array_key_exists(2, $ord); // false لاحظ أنّ أداء isset()‎ أفضل من array_key_exists()‎ لأنّ الأخيرة هي دالة والأولى هي بنية لغوية، ويمكنك أيضًا استخدام الدالة key_exists()‎ التي تعد كنية للدالة array_key_exists()‎. التحقق من نوع المصفوفة تُرجع الدالة is_array()‎ القيمة true إذا كان المتغير مصفوفة. $integer = 1337; $array = [1337, 42]; is_array($integer); // false is_array($array); // true يمكنك كتابة تلميح في الدالة أنّ نوع المعامل المفروض هو مصفوفة أي أنّ تمرير أي نوع آخر سيسبب خطأً فادحًا. function foo (array $array) { /* هو مصفوفة $array نوع المتغير */ } يمكنك أيضًا استخدام الدالة gettype()‎. $integer = 1337; $array = [1337, 42]; gettype($integer) === 'array'; // false gettype($array) === 'array'; // true إنشاء مصفوفة من المتغيرات $username = 'Hadibut'; $email = 'hadibut@example.org'; $variables = compact('username', 'email'); // ['username' => 'Hadibut', 'email' => 'hadibut@example.org'] هي $variables قيمة يُستخدم هذا التابع غالبًا في إطارات العمل لتمرير مصفوفة متغيرات بين مكونين. التحقق من وجود قيمة في مصفوفة تُرجع الدالة in_array()‎ القيمة true إذا كان العنصر موجودًا في المصفوفة. $fruits = ['banana', 'apple']; $foo = in_array('banana', $fruits); //true هي $foo قيمة $bar = in_array('orange', $fruits); //false هي $bar قيمة ويمكنك أن تستخدم الدالة array_search()‎ لتحصل على مفتاح عنصر ما من المصفوفة. $userdb = ['Sandra Shush', 'Stefanie Mcmohn', 'Michael']; $pos = array_search('Stefanie Mcmohn', $userdb); if ($pos !== false) { echo "Stefanie Mcmohn found at $pos"; } الإصدار PHP 5.x من 5.5 وما فوق يمكنك بدءًا من الإصدار PHP 5.5 وما بعده استخدام array_column()‎ بالتزامن مع array_search()‎ ويعدّ هذا مفيدًا بشكلٍ خاص للتحقق من وجود قيمة في المصفوفة الترابطية: $userdb = [ [ "uid" => '100', "name" => 'Sandra Shush', "url" => 'urlof100', ], [ "uid" => '5465', "name" => 'Stefanie Mcmohn', "pic_square" => 'urlof100', ], [ "uid" => '40489', "name" => 'Michael', "pic_square" => 'urlof40489', ] ]; $key = array_search(40489, array_column($userdb, 'uid')); واجهات ArrayAccess وIterator الميزة المفيدة الأخرى هي الوصول إلى تجميعات الكائن المخصص كمصفوفات في PHP، يوجد واجهتين في PHP بدءًا من الإصدار PHP 5.0.0 لدعم ذلك وهما: ArrayAccess وIterator يتيحان لك الوصول إلى الكائنات المخصصة على أنّها مصفوفات. ArrayAccess بفرض لدينا الصنف user يعبر عن مستخدم وجدول في قاعدة البيانات يخزن كل المستخدمين ونريد إنشاء الصنف UserCollection ليقوم بما يلي: يسمح لنا بمخاطبة مستخدم عن طريق معرف الاسم الفريد. أداء العمليات الأساسية (ليس كل عمليات CRUD - الإنشاء والقراءة والتحديث والحذف - إنما على الأقل الإنشاء والاستعادة والحذف) على مجموعة المستخدمين لدينا. انتبه أننا نستخدم الصياغة القصيرة لإنشاء مصفوفة [] والمتاحة بدءًا من الإصدار 5.4: class UserCollection implements ArrayAccess { protected $_conn; protected $_requiredParams = ['username','password','email']; public function __construct() { $config = new Configuration(); $connectionParams = [ // معلومات الاتصال بقاعدة البيانات ]; $this->_conn = DriverManager::getConnection($connectionParams, $config); } protected function _getByUsername($username) { $ret = $this->_conn->executeQuery('SELECT * FROM `User` WHERE `username` IN (?)', [$username])->fetch(); return $ret; } // ArrayAccess بدء التوابع المطلوبة من public function offsetExists($offset) { return (bool) $this->_getByUsername($offset); } public function offsetGet($offset) { return $this->_getByUsername($offset); } public function offsetSet($offset, $value) { if (!is_array($value)) { throw new \Exception('value must be an Array'); } $passed = array_intersect(array_values($this->_requiredParams), array_keys($value)); if (count($passed) < count($this->_requiredParams)) { throw new \Exception('value must contain at least the following params: ' .implode(',', $this->_requiredParams)); } $this->_conn->insert('User', $value); } public function offsetUnset($offset) { if (!is_string($offset)) { throw new \Exception('value must be the username to delete'); } if (!$this->offsetGet($offset)) { throw new \Exception('user not found'); } $this->_conn->delete('User', ['username' => $offset]); } // ArrayAccess بدء التوابع المطلوبة من } بعد ذلك يمكننا كتابة: $users = new UserCollection(); var_dump(empty($users['testuser']),isset($users['testuser'])); $users['testuser'] = ['username' => 'testuser', 'password' => 'testpassword', 'email' => 'test@test.com']; var_dump(empty($users['testuser']), isset($users['testuser']), $users['testuser']); unset($users['testuser']); var_dump(empty($users['testuser']), isset($users['testuser'])); بفرض أنه لا يوجد لدينا المستخدم testuser قبل تنفيذ الشيفرة فسيكون لدينا الخرج التالي: bool(true) bool(false) bool(false) bool(true) array(17) { ["username"]=> string(8) "testuser" ["password"]=> string(12) "testpassword" ["email"]=> string(13) "test@test.com" } bool(true) bool(false) ملاحظة: لا تُستدعى الدالة offsetExists عندما تريد التحقق من وجود مفتاح مع دالة array_key_exists، لذا فإنّ الشيفرة التالية سيكون خرجها false مرتين: var_dump(array_key_exists('testuser', $users)); $users['testuser'] = ['username' => 'testuser', 'password' => 'testpassword', 'email' => 'test@test.com']; var_dump(array_key_exists('testuser', $users)); Iterator سنوسع الصنف في الأعلى بإضافة عدة دوال من الواجهة Iterator لنستطيع استخدام التكرار مع foreach وwhile. نحتاج أولًا إلى خاصيّة تحتوي الفهرس الحالي للمكرِّر، سنسميها ‎$_position ونضيفها إلى خاصيّات الصنف: // Iterator موقع المكرِّر الحالي مطلوب من توابع الواجهة protected $_position = 1; ثم نضيف الواجهة Iterator إلى قائمة الواجهات التي سينفذها صنفنا: class UserCollection implements ArrayAccess, Iterator { ثم نضيف المطلوب من دوال الواجهة Iterator: // Iterator بدء التوابع المطلوبة من الواجهة public function current () { return $this->_getById($this->_position); } public function key () { return $this->_position; } public function next () { $this->_position++; } public function rewind () { $this->_position = 1; } public function valid () { return null !== $this->_getById($this->_position); } // Iterator نهاية التوابع المطلوبة من الواجهة أصبح لدينا مصدر كامل لتنفيذ الصنف لكِلا الواجهتين، لاحظ أنّ هذا المثال ليس مثاليًا لأنّ المعرفات في قاعدة البيانات قد لا تكون تسلسلية لكن كتبناه لإعطائك فكرةً أساسيةً وهي أنّه بإمكانك مخاطبة تجميعات الكائنات بأي طريقة ممكنة عن طريق تنفيذ الواجهتين ArrayAccess وIterator. class UserCollection implements ArrayAccess, Iterator { // Iterator موقع المكرِّر الحالي المطلوب من توابع الواجهة protected $_position = 1; // <إضافة التوابع القديمة من الشيفرة السابقة هنا> // Iterator بدء التوابع المطلوبة من الواجهة public function current () { return $this->_getById($this->_position); } public function key () { return $this->_position; } public function next () { $this->_position++; } public function rewind () { $this->_position = 1; } public function valid () { return null !== $this->_getById($this->_position); } // Iterator نهاية التوابع المطلوبة من الواجهة } ولكتابة حلقة تمر على كل الكائنات من نوع user باستخدام foreach: foreach ($users as $user) { var_dump($user['id']); } سينتج عن الشيفرة السابقة الخرج التالي: string(2) "1" string(2) "2" string(2) "3" string(2) "4" ... تكرار عدة مصفوفات معًا قد نحتاج أحيانًا إلى تكرار مصفوفتين لهما نفس الطول معًا مثال: $people = ['Tim', 'Tony', 'Turanga']; $foods = ['chicken', 'beef', 'slurm']; أبسط طريقة لتنفيذ ذلك هي استخدام الدالة array_map: array_map(function($person, $food) { return "$person likes $food\n"; }, $people, $foods); سينتج عن ذلك الخرج التالي: assert(count($people) === count($foods)); for ($i = 0; $i < count($people); $i++) { echo "$people[$i] likes $foods[$i]\n"; } إذا لم يكن للمصفوفتين مفاتيح تكرارية يمكننا استخدام array_values($array)[$i]‎ بدلًا من ‎$array[$i]‎، وإذا كان لهما نفس ترتيب المفاتيح يمكننا استخدام حلقة foreach مع مفتاح على إحداهما: foreach ($people as $index => $person) { $food = $foods[$index]; echo "$person likes $food\n"; } يمكن تكرار المصفوفات المستقلة فقط إذا كان لها نفس الطول ونفس اسم المفتاح، أي أنّه إذا لم يكن للمصفوفات مفاتيحًا فالمفاتيح مرقمة وهذا جيّد أو يمكنك تسمية المفاتيح ووضعهم في نفس الترتيب لكل مصفوفة، ويمكنك أيضًا استخدام الدالة array_combine. $combinedArray = array_combine($people, $foods); // $combinedArray = ['Tim' => 'chicken', 'Tony' => 'beef', 'Turanga' => 'slurm']; بعدها يمكنك كتابة حلقة تكرار كما في السابق: foreach ($combinedArray as $person => $meal) { echo "$person likes $meal\n"; } استخدام فهرس تزايدي تعمل هذه الطريقة على زيادة عدد صحيح من 0 وحتى أكبر فهرس في المصفوفة. $colors = ['red', 'yellow', 'blue', 'green']; for ($i = 0; $i < count($colors); $i++) { echo 'I am the color ' . $colors[$i] . '<br>'; } يمكنك أيضًا المرور على جميع عناصر المصفوفة بترتيب عكسي دون استخدام الدالة array_reverse التي قد تزيد الحِمل إذا كانت المصفوفة كبيرة. $colors = ['red', 'yellow', 'blue', 'green']; for ($i = count($colors) - 1; $i >= 0; $i--) { echo 'I am the color ' . $colors[$i] . '<br>'; } يمكنك تخطي أو إرجاع الفهرس بسهولة مستخدمًا هذه الطريقة. $array = ["alpha", "beta", "gamma", "delta", "epsilon"]; for ($i = 0; $i < count($array); $i++) { echo $array[$i], PHP_EOL; if ($array[$i] === "gamma") { $array[$i] = "zeta"; $i -= 2; } elseif ($array[$i] === "zeta") { $i++; } } الخرج: alpha beta gamma beta zeta epsilon لا يمكن أن نقوم بهذا بشكلٍ مباشر في المصفوفات التي لا تحوي فهارس تزايدية (بما في ذلك المصفوفات التي تكون فهارسها بترتيب عكسي مثل: ‎[1 => "foo", 0 => "bar"], ["foo" => "f", "bar" => "b"]‎)، لذا نستخدم الدوال array_values أو array_keys: $array = ["a" => "alpha", "b" => "beta", "c" => "gamma", "d" => "delta"]; $keys = array_keys($array); for ($i = 0; $i < count($array); $i++) { $key = $keys[$i]; $value = $array[$key]; echo "$value is $key\n"; } استخدام مؤشرات المصفوفة الداخلية تحوي كل نسخة مصفوفة مؤشرًا داخليًا، يمكن باستخدام هذا المؤشر استعادة عناصر مختلفة من المصفوفة عن طريق نفس الاستدعاء في مرات مختلفة. استخدام الدالة each يُرجع كل استدعاء للدالة مفتاح وقيمة عنصر المصفوفة الحالي ويزيد مؤشر المصفوفة الداخلي. $array = ["f" => "foo", "b" => "bar"]; while (list($key, $value) = each($array)) { echo "$value begins with $key"; } استخدام الدالة next $array = ["Alpha", "Beta", "Gamma", "Delta"]; while (($value = next($array)) !== false) { echo "$value\n"; } لاحظ أنّ هذا المثال يفترض أنّه لا يوجد عناصر في المصفوفة معرّفة بالقيمة المنطقية false، لمنع هذا الافتراض نستخدم الدالة key للتحقق من وصول المؤشر الداخلي إلى نهاية المصفوفة: $array = ["Alpha", "Beta", "Gamma", "Delta"]; while (key($array) !== null) { echo current($array) . PHP_EOL; next($array); } ويسهّل هذا أيضًا تكرار المصفوفة دون حلقة مباشرة: class ColorPicker { private $colors = ["#FF0064", "#0064FF", "#64FF00", "#FF6400", "#00FF64", "#6400FF"]; public function nextColor() : string { $result = next($colors); // إذا وصلت لنهاية المصفوفة if (key($colors) === null) { reset($colors); } return $result; } } استخدام foreach حلقة مباشرة foreach ($colors as $color) { echo "I am the color $color<br>"; } حلقة مع مفاتيح $foods = ['healthy' => 'Apples', 'bad' => 'Ice Cream']; foreach ($foods as $key => $food) { echo "Eating $food is $key"; } حلقة بالمرجعية إنّ تعديل قيمة (‎$color أو ‎$food) في الأمثلة السابقة بشكلٍ مباشر لا يغيّر قيمتها في المصفوفة إنّما يجب استخدام العامل & لتصبح القيمة هي مؤشر مرجعي للعنصر في المصفوفة. $years = [2001, 2002, 3, 4]; foreach ($years as &$year) { if ($year < 2000) $year += 2000; } الشيفرة السابقة مماثلة للشيفرة التالية: $years = [2001, 2002, 3, 4]; for($i = 0; $i < count($years); $i++) { $year = &$years[$i]; if($year < 2000) $year += 2000; } التزامن يمكن تعديل مصفوفات PHP أثناء التكرار بدون مشاكل تزامن، إذا كانت المصفوفة تُكرَّر بالمرجع فإنّ التكرارات اللاحقة ستتأثر بتغييرات المصفوفة وإلا فإنّها لن تتأثر (كما لو أنّك تكرر نسخة من المصفوفة نفسها)، وازن التكرار بالقيمة: $array = [0 => 1, 2 => 3, 4 => 5, 6 => 7]; foreach ($array as $key => $value) { if ($key === 0) { $array[6] = 17; unset($array[4]); } echo "$key => $value\n"; } الخرج: 0 => 1 2 => 3 4 => 5 6 => 7 أما إذا كانت المصفوفة تُكرَّر بالمرجع: $array = [0 => 1, 2 => 3, 4 => 5, 6 => 7]; foreach ($array as $key => &$value) { if ($key === 0) { $array[6] = 17; unset($array[4]); } echo "$key => $value\n"; } الخرج: 0 => 1 2 => 3 6 => 17 لن تُكرَّر مجموعة القيمة-المفتاح 4‎ => 5 وستتغير 6‎ => 7 إلى ‎6 => 17‎. استخدام المكرِّر ArrayObject يسمح لك الصنف arrayiterator بتعديل وإلغاء ضبط القيم أثناء تكرار المصفوفات والكائنات. مثال: $array = ['1' => 'apple', '2' => 'banana', '3' => 'cherry']; $arrayObject = new ArrayObject($array); $iterator = $arrayObject->getIterator(); for($iterator; $iterator->valid(); $iterator->next()) { echo $iterator->key() . ' => ' . $iterator->current() . "</br>"; } الخرج: 1 => apple 2 => banana 3 => cherry ترجمة -وبتصرف- للفصول [Arrays - Array iteration] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: التنفيذ على المصفوفات والتعامل معها في PHP المقال السابق: المراجع (references) في PHP
  17. الإسناد بالمرجعية (reference) هذه المرحلة الأولى للمراجع، عندما تُسند بالمرجعية فإنّك تسمح بمشاركة متغيرين لنفس القيمة. $foo = &$bar; ‎$foo و‎$bar متساويان هنا ولا يشيران إلى بعضهما إنما يشيران إلى نفس المكان (القيمة). يمكنك أيضًا الإسناد بالمرجعية ضمن بنية اللغة array‎()‎ بالرغم من أنه ليس إسناد بالمرجعية بشكلٍ دقيق. $foo = 'hi'; $bar = array(1, 2); $array = array(&$foo, &$bar[0]); لاحظ أنّ المراجع داخل المصفوفة قد تكون خطرة، القيام بإسناد عادي (ليس بالمرجعية) مع مرجع على الجانب الأيمن لا يحوّل الجانب الأيسر إلى مرجع، لكن يُحتفظ بالمراجع داخل المصفوفات في هذه الإسنادات العادية، يُطبّق هذا أيضًا على استدعاءات الدالة عندما تُمرّر المصفوفة بالقيمة. لا يقتصر الإسناد بالمرجعية على المتغيرات والمصفوفات فقط إنما يمكن استخدامه للدوال وكل ارتباطات التمرير بالمرجعية. function incrementArray(&$arr) { foreach ($arr as &$val) { $val++; } } function &getArray() { static $arr = [1, 2, 3]; return $arr; } incrementArray(getArray()); var_dump(getArray()); // array [2, 3, 4] في الشيفرة السابقة الإسناد هو مفتاح ضمن تعريف الدالة. لا يمكنك تمرير تعبير بالمرجع ويمكنك تمرير قيمة/متغير فقط. الإعادة بالمرجعية قد تحتاج أحيانًا الإعادة بالمرجعية بشكلٍ ضمني، وتعدّ الإعادة بالمرجعية ضروريةً عندما تريد استخدام دالة لإيجاد المتغير المرتبط بمرجع ما، ولا يستخدم لتحسين الأداء إنما يقوم المحرك بفعل هذا بمفرده، استخدم الإعادة بالمرجعية عندما يكون لديك سببًا تقنيًا واضحًا للقيام بذلك. يوجد عدة نماذج للإعادة بالمرجعية متضمنةً المثال التالي: function parent(&$var) { echo $var; $var = "updated"; } function &child() { static $a = "test"; return $a; } parent(child()); // "test" parent(child()); // "updated" لا تقتصر الإعادة بالمرجعية على الدوال، يمكنك أيضًا القيام بالاستدعاء الضمني: function &myFunction() { static $a = 'foo'; return $a; } $bar = &myFunction(); $bar = "updated" echo myFunction(); لا يمكنك أن ترجع استدعاء دالة بشكلٍ مباشر إنما يجب إسنادها إلى متغير قبل استخدامها، يمكنك أن تجرّب ذلك بكتابة echo &myFunction()‎;‎. ملاحظات: يجب تحديد مرجع (&) في كِلا المكانين اللذين تريد استخدامه بهما، أي في تعريف الدالة: (function &myFunction() {...)‎ وفي استدعاء المرجع: ‎(function callFunction(&$variable) {... or &myFunction();) يمكنك أن ترجع متغير فقط بالمرجع لذا اُستنسخ المتغير ‎$a في المثال السابق، أي أنّه لا يمكنك أن ترجع تعبيرًا وإلا سيُنشأ خطأ E_NOTICE: ‏(Notice: Only variable references should be returned by reference in ......)‎‏‏ الإعادة بالمرجعية له حالات استخدام رسمية لكن يجب التحذير من أنّه يجب استخدامه بعد استكشاف جميع الخيارات المحتملة الأخرى لتحقيق نفس الهدف. التمرير بالمرجعية يسمح لك التمرير بالمرجعية أن تمرر متغيرًا بالمرجع إلى دالة أو عنصر وهذا يساعدك على تعديل المتغير الأصلي. لا يقتصر التمرير بالمرجعية على المتغيرات فقط، يمكن تمرير ما يلي بالمرجع: تعليمات new مثل foo(new SomeClass)‎. المراجع المُعادة من الدوال المصفوفات الاستخدام الشائع للتمرير بالمرجعية هو أن تعدّل القيم الابتدائية ضمن مصفوفة دون أن تضطر لإنشاء مصفوفات جديدة أو تملأ فضاء الاسم الخاص بك، يمكنك أن تمرر بالمرجع بأن تضيف & قبل المتغير. إليك مثال لاستخدام عنصر من مصفوفة وإضافة 1 إلى قيمته الابتدائية ببساطة: $arr = array(1, 2, 3, 4, 5); foreach($arr as &$num) { $num++; } لاحظ أنّه عندما تستخدم أي عنصر من ‎$arr فإنّ العنصر الأصلي سيتغير مع زيادة المرجع يمكنك أن تتأكد بالشيفرة التالية: print_r($arr); // Array ( [0] => 2 [1] => 3 [2] => 4 [3] => 5 [4] => 6 ) ملاحظة: يجب أن تلاحظ عند استخدام التمرير بالمرجع ضمن الحلقات، في الشيفرة السابقة في نهاية الحلقة سيحمل المتغير ‎$num مرجعًا إلى عنصر المصفوفة الأخير، يمكن إسنادها بعد انتهاء الحلقة لمعالجة عنصر المصفوفة الأخير، يمكنك التأكد من أن هذا لن يحصل باستخدام unset‎()‎ بعد الحلقة مباشرةً: $myArray = array(1, 2, 3, 4, 5); foreach($myArray as &$num) { $num++; } unset($num); سيضمن لك هذا عدم حدوث أيّة مشاكل، يمكنك الاطلاع على مشكلة متعلقة بهذا الموضوع من هنا. الدوال الاستخدام الشائع الآخر للتمرير بالمرجعية هو في الدوال إذ يصبح تعديل القيمة الأساسية بسيطًا: $var = 5; // تعريف الدالة function add(&$var) { $var++; } // استدعاء الدالة add($var); يمكنك التحقق بطباعة القيمة الأساسية: echo $var; // 6 هناك قيود مختلفة حول الدوال وهي حسب توثيق PHP الرسمي: لا توجد إشارة مرجعية عند استدعاء الدالة، فقط عند تعريف الدالة، إذ أنّ تعريفات الدالة وحدها كافية لتمرير الوسيط بالمرجع. ستحصل على التحذير "call-time pass-by-reference" is deprecated عند استخدام & في foo(&$a);‎ بدءًا من PHP 5.3.0 والتحذير call-time pass-by-reference was removed بدءًا من PHP 5.4.0 لذا فإنّ استخدام هذه الإشارة سيرمي خطأً فادحًا (fatal error). ترجمة -وبتصرف- للفصل [References] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: المصفوفات (Arrays) في PHP المقال السابق: أهم العوامل (operators) الموجودة في PHP تعلم PHP
  18. العامل هو عنصر يأخذ قيمة واحدة أو أكثر (أو تعابير في المصطلحات البرمجية) ويُنتج قيمة جديدة (لذا تصبح البنية بحد ذاتها تعبيرًا)، ويمكن تجميع العوامل وفقًا لعدد القيم التي تأخذها. عامل تجميع Null (‏Null coalescing) (??) عُرِّف هذا العامل الجديد في الإصدار PHP 7، يُرجع هذا العامل معامَله (operand) الأول إذا كان ذو قيمة ليست null وإلا فإنّه يرجع معامَله الثاني. إليك المثال التالي: $name = $_POST['name'] ?? 'nobody'; وهذه الشيفرة مكافئة لكل من: if (isset($_POST['name'])) { $name = $_POST['name']; } else { $name = 'nobody'; } والشيفرة: $name = isset($_POST['name']) ? $_POST['name'] : 'nobody'; يمكن أيضًا أن تسلسل هذا العامل (مع دلالات ارتباط صحيحة): $name = $_GET['name'] ?? $_POST['name'] ?? 'nobody'; وهذه الشيفرة مكافئة للشيفرة التالية: if (isset($_GET['name'])) { $name = $_GET['name']; } elseif (isset($_POST['name'])) { $name = $_POST['name']; } else { $name = 'nobody'; } ملاحظة: عند استخدام عامل التجميع لضمّ السلاسل النصية يجب استخدام الأقواس (). $firstName = "John"; $lastName = "Doe"; echo $firstName ?? "Unknown" . " " . $lastName ?? ""; خرج الشيفرة السابقة هو John، لو كانت قيمة المتغير ‎$firstName‎ هي null وقيمة المتغير ‎$lastName هي Doe عندها سيكون الخرج Unknown Doe، ليكون لدينا الخرج John Doe يجب استخدام الأقواس كما يلي: $firstName = "John"; $lastName = "Doe"; echo ($firstName ?? "Unknown") . " " . ($lastName ?? ""); عامل السفينة الفضائية (<=>) عرّفت PHP نوعًا جديدًا من العوامل وهو عامل السفينة الفضائية (Spaceship) يمكن استخدامه للموازنة بين التعابير، يُرجع هذا العامل 1-، 0 أو 1 إذا كان التعبير الأول أقل من، يساوي أو أكبر من التعبير الثاني. // الأعداد الصحيحة print (1 <=> 1); // 0 print (1 <=> 2); // -1 print (2 <=> 1); // 1 // الأعداد العشرية print (1.5 <=> 1.5); // 0 print (1.5 <=> 2.5); // -1 print (2.5 <=> 1.5); // 1 // السلاسل النصية print ("a" <=> "a"); // 0 print ("a" <=> "b"); // -1 print ("b" <=> "a"); // 1 لا يمكن الموازنة بين الكائنات وسيؤدي ذلك إلى سلوك غير معرّف. يعدّ هذا العامل مفيدًا بشكلٍ خاص عند تعريف المستخدم لدالة موازنة باستخدام usort أو uasort أو uksort. يمكن لدالة مجهولة استخدام <=> إذا كان الهدف منها ترتيب مصفوفة من الكائنات وفقًا لخاصيّة الوزن بالنظر إلى مصفوفة كائنات يجب ترتيبها وفقًا لخاصية الوزن، يمكن لدالة مجهولة مثلًا استخدام <=> لتُرجع القيمة المتوقعة من دوال الترتيب. usort($list, function($a, $b) { return $a->weight <=> $b->weight; }); يتطلب هذا تعبير أكثر تفصيلًا في PHP 5. usort($list, function($a, $b) { return $a->weight < $b->weight ? -1 : ($a->weight == $b->weight ? 0 : 1); }); عامل التنفيذ (``) يتألف عامل التنفيذ في PHP من علامتي اقتباس مائلتين (``) ويستخدم لتنفيذ أوامر الصدفة، ويرجع خرج الأمر وبالتالي يمكن تخزينه في متغير. // أمر الحصول على قائمة بأسماء الملفات $output = `ls`; echo "<pre>$output</pre>"; لاحظ أنّ معامل التنفيذ والدالة shell_exec()‎ يعطيان نفس النتيجة. عوامل الزيادة (++) والنقصان (--) يمكن زيادة المتغيرات أو نقصانها بمقدار 1 باستخدام ++، -- على التوالي، ويمكن أن يأتي كل منهما قبل المتغير أو بعده ولكل حالة دلالتها كما سنوضح في الأمثلة التالية: $i = 1; echo $i; // 1 // $i بمقدار 1 ثم يرجع $i عامل الزيادة السابقة يزيد المتغير echo ++$i; // 2 // $i بمقدار 1 ثم يرجع $i عامل النقصان السابق ينقص المتغير echo --$i; // 1 //ثم يزيده بمقدار 1 $i عامل الزيادة اللاحقة يرجع المتغير echo $i++; // 1 (2 الآن هي $i لكن قيمة المتغير) //ثم ينقصه بمقدار 1 $i عامل النقصان اللاحق يرجع المتغير echo $i--; // 2 (1 الآن هي $i لكن قيمة المتغير) يمكنك الاطلاع على المزيد حول عوامل الزيادة والنقصان من هنا. العامل الثلاثي (?:) يمكن أن تعدّ العامل الثلاثي تعليمة if سطرية، يتألف من 3 أجزاء: العامل وخرجين وله الصياغة العامة التالية: $value = <operator> ? <true value> : <false value> إذا قُيِّم العامل على أنّه true ستُرجَع القيمة الموجودة في الكتلة الأولى (<true value>) وإلا ستُرجَع القيمة الموجودة في الكتلة الثانية (<false value>)، وبما أنّه أسندنا نتيجة العامل الثلاثي للمتغير ‎$value فستُخزّن القيمة المُرجعة فيه. مثال: $action = empty($_POST['action']) ? 'default' : $_POST['action']; سيحتوي المتغير ‎$action السلسلة 'default' إذا كانت نتيجة التعبير empty($_POST['action'])‎ هي true وإلا سيحتوي على قيمة ‎$_POST['action']‎. يُقيَّم التعبير (expr1) ? (expr2) : (expr3) إلى expr2 إذا قُيِّم expr1 على أنّه true وإلى expr3 إذا قُيِّم expr1 على أنّه false. من الممكن أن تحذف الجزء الأوسط من العامل الثلاثي، يرجع التعبير expr1 ?: expr3 قيمة expr1 إذا كانت نتيجته هي true وإلا تُرجع قيمة expr3. يُشار عادةً للعامل ‎?:‎‎ على أنّه عامل إلفيس (Elvis operator). يتصرف هذا العامل مثل عامل تجميع null ?? باستثناء أنّ الأخير يتطلّب أن يكون المعامَل اليساري null بينما العامل ‎?:‎‎ يحاول أن يحلل المعامَل اليساري إلى قيمة منطقية والتحقق فيما إذا كانت قيمته false أو لا. مثال: function setWidth(int $width = 0){ $_SESSION["width"] = $width ?: getDefaultWidth(); } في هذا المثال تقبل الدالة setWidth المعامل width أو ستكون قيمته الافتراضية 0 لتغيير قيمة width في الجلسة. إذا كانت قيمة ‎$width هي 0 (أي لم تُعطى قيمة للمتغير ‎$width) فستكون قيمته المنطقية false وستُستخدم قيمة getDefaultWidth()‎ بدلًا منه، بينما لو كانت قيمة المتغير ‎$width المنطقية هي true لن نحتاج لاستدعاء الدالة getDefaultWidth()‎. العوامل المنطقية (&&/AND و ||/OR) يوجد في PHP صيغتان مختلفتان للعوامل المنطقية AND وOR. المعامل إذا كان true تكون النتيجة $a and $b قيمة كل من $a و $b هي true $a && $b قيمة كل من $a و $b هي true $a or $b قيمة كل من $a أو $b هي true $a || $b قيمة كل من $a أو $b هي true لاحظ أن العوامل && و|| لها أولوية أعلى من and وor. لاحظ الجدول التالي: التقييم نتيجة $e طريقة التقييم $e = false || true True $e = (false || true)‎ $e = false or true False ‎($e = false) or true لذا من الأفضل استخدام && و|| بدلًا من and وor. عوامل السلاسل النصية (String Operators) هناك عاملان للسلاسل النصية: عامل الربط (concatenation) (.): $a = "a"; $b = "b"; $c = $a . $b; // $c => "ab" عامل الربط الإسنادي (=.): $a = "a"; $a .= "b"; // $a => "ab" عوامل الكائن والصنف يمكن الوصول لعناصر الكائنات أو الأصناف باستخدام عامل الكائن ‎(->)‎ وعامل الصنف (::). class MyClass { public $a = 1; public static $b = 2; const C = 3; public function d() { return 4; } public static function e() { return 5; } } $object = new MyClass(); var_dump($object->a); // int(1) var_dump($object::$b); // int(2) var_dump($object::C); // int(3) var_dump(MyClass::$b); // int(2) var_dump(MyClass::C); // int(3) var_dump($object->d()); // int(4) var_dump($object::d()); // int(4) var_dump(MyClass::e()); // int(5) $classname = "MyClass"; var_dump($classname::e()); // int(5) لاحظ أنّه ليس ضروريًا كتابة $ بعد عامل الكائن (‎$object->a بدلًا من ‎$object->$a‎)، وذلك لا ينطبق على عامل الصف أي أنّه من الضروري كتابة $. أما بالنسبة للثابت المعرّف في الصف لا نستخدم $ أبدًا. من الممكن أن نكتب التعليمة var_dump(MyClass::d());‎ إذا كانت الدالة function d()‎ لا تشير إلى الكائن: class MyClass { private $a = 1; public function d() { return $this->a; } } $object = new MyClass(); var_dump(MyClass::d()); // Error! يسبب هذا ظهور الخطأ: PHP Fatal error: Uncaught Error: Using $this when not in object context لهذه العوامل ارتباط يساري يمكن استخدامه للتسلسل: class MyClass { private $a = 1; public function add(int $a) { $this->a += $a; return $this; } public function get() { return $this->a; } } $object = new MyClass(); var_dump($object->add(4)->get()); // int(5) ولها أيضًا الأولوية الأعلى، حتى أعلى من النسخ (clone): class MyClass { private $a = 0; public function add(int $a) { $this->a += $a; return $this; } public function get() { return $this->a; } } $o1 = new MyClass(); $o2 = clone $o1->add(2); var_dump($o1->get()); // int(2) var_dump($o2->get()); // int(2) تُضاف قيمة ‎$o1 قبل أن يُنسَخ الكائن. لاحظ أنّ استخدام الأقواس للتأثير على الأولوية لا يعمل في PHP الإصدار 5 والأقدم منه (لكنه يؤثر في PHP 7): // من الشيفرة السابقة MyClass استخدام الصف $o1 = new MyClass(); $o2 = (clone $o1)->add(2); // PHP 7 لكنه يعمل بشكل صحيح في PHP 5 السطر السابق يؤدي إلى خطأ في var_dump($o1->get()); // int(0) in PHP 7 var_dump($o2->get()); // int(2) in PHP 7 عوامل الإسناد المجمّعة (=+ وغيرها) عوامل الإسناد المجمّعة هي اختصار لعملية على متغيرٍ ما ثم إسناد قيمة جديدة لهذا المتغير. العمليات الحسابية // إسناد بسيط $a = 1; // (1 + 2) => 3 أي النتيجة '$a = $a + 2'; يُقرأ السطر التالي كأنّه $a += 2; // (3 - 1) => 2 في السطر التالي $a قيمة $a -= 1; // (2 * 2) => 4 في السطر التالي $a قيمة $a *= 2; // (16 / 2) => 8 في السطر التالي $a قيمة $a /= 2; // (باقي القسمة‎) (8 % 5) => 3 في السطر التالي $a قيمة $a %= 5; // المعامل + في المصفوفات $arrOne = array(1); $arrTwo = array(2); $arrOne += $arrTwo; var_dump($arrOne); // array(1) { [0]=> int(1) } ملاحظة: عند استخدام العامل + في المصفوفات فإنّه يُرجع المصفوفة في الطرف اليميني مضافة إلى نهاية المصفوفة في الطرف اليساري، في حال كانت المفتاح نفسه موجودًا في المصفوفتين فإنّه يستخدم القيمة الموجودة في المصفوفة اليسارية ويتجاهل قيمة هذا المفتاح في المصفوفة اليمينية. معالجة عدة مصفوفات معًا $a **= 2; // (2مرفوعة للقوة ‏‎ 4) (4 ** 2) => 16 هي $a تصبح قيمة الربط والإسناد المجمّع لسلسلة نصية: $a = "a"; $a .= "b"; // $a => "ab" عوامل الإسناد المجمّعة لعمليات الأعداد الثنائية: $a = 0b00101010; // 42 الآن هي $a قيمة $a &= 0b00001111; // (and عملية الأعداد الثنائية) ‎(00101010 & 00001111) => 00001010 الآن هي $a قيمة $a |= 0b00100010; // (or عملية الأعداد الثنائية) ‎(00001010 | 00100010) => 00101010 الآن هي $a قيمة $a ^= 0b10000010; // (xor عملية الأعداد الثنائية) ‎(00101010 ^ 10000010) => 10101000 الآن هي $a قيمة $a >>= 3; // (3 انزياح يميني بمقدار) ‎(10101000 >> 3) => 00010101 الآن هي $a قيمة $a <<= 1; // (1 انزياح يساري بمقدار) ‎(00010101 << 1) => 00101010 الآن هي $a قيمة تغيير أولوية عامل (مع الأقواس) يُحدّد عامل الأولوية (operator precedence) الترتيب الذي تُقيَّم فيه العوامل، ففي المثال التالي: $a = 2 * 3 + 4; يأخد المتغير ‎$a القيمة 10، في البداية يُقيّم 2‎ * 3 (الضرب له أولوية أعلى من الجمع) ومحصلة النتيجة الفرعية 6 مضافًا إليها 4 تساوي 10. يمكن أن تتغير الأولوية باستخدام الأقواس: $a = 2 * (3 + 4); في الشيفرة السابقة قيمة المتغير ‎$a تصبح 14 لأنّ ‎(3 + 4)‎ يُقيّم أولًا. الإسناد البسيط (=) $a = "some string"; للمتغير ‎$a الآن القيمة some string. نتيجة تعبير الإسناد هي أنّ القيمة تُصبح مُسنَدة، لاحظ أننا استخدمنا الإشارة = للإسناد وليس للمساواة. $a = 3; $b = ($a = 5); تقوم الشيفرة السابقة بالتالي: يُسند السطر 1 القيمة 3 للمتغير ‎$a. يُسند السطر 2 القيمة 5 للمتغير ‎$a. ينتج عن هذا التعبير القيمة 5 أيضًا. ثم يُسند السطر 2 نتيجة التعبير بين القوسين (5) للمتغير ‎$b. لذا لدينا الآن قيمة كل من ‎$a و‎$b هي 5. الارتباط (Association) الارتباط اليساري إذا تساوت أولوية عاملين فإنّ الترابط يحدد التجميع: $a = 5 * 3 % 2; // (5 * 3) % 2 => (15 % 2) => 1 الآن هي $a قيمة إنّ * و% لهما الأولوية نفسها وترابط يساري، يُجمَّع الضرب لأنه يحدث أولًا (يسارًا). $a = 5 % 3 * 2; // (5 % 3) * 2 => (2 * 2) => 4 الآن هي $a قيمة لاحظ أنّ عامل باقي القسمة يحدث أولًا (يسارًا) ولذا يُجمّع. الارتباط اليميني $a = 1; $b = 1; $a = $b += 1; لكل من المتغيرين ‎$a و‎$b القيمة 2 لأنّ ‎$b += 1 جُمّعت ثم أُسندت النتيجة (‎$b = 2) للمتغير ‎$a. عوامل الموازنة المساواة نستخدم عامل المساواة == لاختبار المساواة الأساسي، ونستخدم عامل التطابق === لاختبار أكثر شمولية، إذ يعمل عامل التطابق نفس عمل عامل المساواة أي يجب أن يكون للطرفين نفس القيمة ولكن يتطلب أيضًا أن يكون لهما نفس نوع البيانات. لدينا في المثال التالي المتغيرين ‎$a و‎$b متساويين ولكنهما غير متطابقين. $a = 4; $b = '4'; if ($a == $b) { // هذه العبارة ستُطبع echo 'a and b are equal'; } if ($a === $b) { // هذه العبارة لن تُطبع echo 'a and b are identical'; } تُحوَّل السلاسل النصية العددية إلى أعداد صحيحة عند استخدام عامل المساواة. موازنة الكائنات يوازن === بين كائنين بالتأكد من إذا كان الطرفان نفس النسخة تمامًا من صنف ما، هذا يعني أنّ نتيجة new stdClass() === ‎new stdClass()‎ هي false حتى لو أنشئوا بنفس الطريقة وكان لهما نفس القيم. يوازن === بين كائنين بشكلٍ متكرر متحققًا من كونهم متساويين (تساويًا عميقًا) (deep equals)، وهذا يعني أنّه ‎$a == ‎$b إذا كان ‎$a و‎$b: من نفس الصنف. لهم نفس مجموعة الخاصيّات بما في ذلك الخاصيّات الديناميكية. من أجل كل ‎$property فإنّ نتيجة كل ‎$a->property == ‎$b->property‎‎‎ هي true (لذا التحقق يتكرر). العوامل الأخرى الشائعة الاستعمال تتضمن: أكبر من ‎(>)‎. أصغر من ‎(<)‎. أكبر من أو يساوي ‎(>=)‎. أصغر من أو يساوي ‎(<=)‎. لا يساوي ‎(!=)‎. لا يطابق ‎‎(!==)‎. أكبر من: ‎$a > $b‎: تُرجع true إذا كانت قيمة ‎$a أكبر من قيمة ‎$b، وإلا ترجع false. مثال: var_dump(5 > 2); // bool(true) var_dump(2 > 7); // bool(false) أصغر من: ‎$a < $b: تُرجع true إذا كانت قيمة ‎$a أصغر من قيمة ‎$b، وإلا ترجع false. مثال: var_dump(5 < 2); // bool(false) var_dump(1 < 10); // bool(true) أكبر من أو يساوي: ‎$a >= $b: تُرجع true إذا كانت قيمة ‎$a أكبر من قيمة ‎$b أو تساويها، وإلا ترجع false. مثال: var_dump(2 >= 2); // bool(true) var_dump(6 >= 1); // bool(true) var_dump(1 >= 7); // bool(false) أصغر من أو يساوي: ‎$a <= $b: تُرجع true إذا كانت قيمة ‎$a أصغر من قيمة ‎$b أو تساويها، وإلا ترجع false. مثال: var_dump(5 <= 5); // bool(true) var_dump(5 <= 8); // bool(true) var_dump(9 <= 1); // bool(false) لا يساوي/يطابق: نعيد صياغة المثال السابق حول المساواة لكن في هذا المثال سيكون لدينا ‎$a و‎$b غير متطابقين: $a = 4; $b = '4'; if ($a != $b) { // هذه العبارة لن تُطبع echo 'a and b are not equal'; } if ($a !== $b) { // هذه العبارة ستُطبع echo 'a and b are not identical'; } عوامل الأعداد الثنائية (Bitwise Operators) عوامل الأعداد الثنائية البادئة‎ تشبه العوامل الثنائية العوامل المنطقية لكنها تُنفَّذ لكل بت بدلًا من كل قيمة منطقية. // يعكس قيمة البتات NOT ~ العامل printf("%'06b", ~0b110110); // 001001 عوامل قناع بتّي-قناع بتّي (Bitmask-bitmask) العامل الثنائي AND‎ & يُعيد 1 مكان كل بت له القيمة 1 في كِلا المعامَلين. printf("%'06b", 0b110101 & 0b011001); // 010001 العامل الثنائي OR | يُعيد 1 مكان كل بت له القيمة 1 في أحد المعامَلين أو كلاهما. printf("%'06b", 0b110101 | 0b011001); // 111101 العامل الثنائي XOR ^ يُعيد 1 مكان كل بت له القيمة 1 في أحد المعامَلين فقط. printf("%'06b", 0b110101 ^ 0b011001); // 101100 أمثلة لاستخدام الأقنعة البتّية يمكن استخدام هذه العوامل للتعامل مع الأقنعة البتّية، مثال على ذلك: file_put_contents("file.log", LOCK_EX | FILE_APPEND); اُستخدم هنا العامل | لدمج اثنين من الأقنعة البتّية، بالرغم من أنّ العامل + له نفس التأثير أيضًا إلا أنّ | تؤكد أنّك تدمج الأقنعة البتّية ولست فقط تجمع رقمين صحيحين عاديين. class Foo{ const OPTION_A = 1; const OPTION_B = 2; const OPTION_C = 4; const OPTION_A = 8; private $options = self::OPTION_A | self::OPTION_C; public function toggleOption(int $option){ $this->options ^= $option; } public function enable(int $option){ // بغض النظر عن حالته الأصلية $option تمكين $this->options |= $option; } public function disable(int $option){ // بغض النظر عن حالته الأصلية دون التأثير على البتات الأخرى $option تمكين $this->options &= ~$option; } // تُرجع الدالة التالية فيما إذا كانت إحدى الخيارت ممكّنة public function isOneEnabled(int $options) : bool{ return $this->options & $option !== 0; // بت مرتفع فقد نعالج عدد صحيح سالب $options استخدمنا ==! بدلًا من > لأنّه إذا كانت قيمة } // تُرجع الدالة التالية فيما إذا كانت كل الخيارت ممكّنة public function areAllEnabled(int $options) : bool{ return ($this->options & $options) === $options; // لاحظ استخدام الأقواس للحذر من أولوية العامل } } يستخدم هذا المثال (بفرض ‎$option يحوي بت واحد دومًا): العامل ^ لتبديل الأقنعة البتّية بشكلٍ ملائم. العامل | لضبط بت بغض النظر عن حالته الأصلية أو باقي البتات. العامل ~ لتحويل العدد الصحيح الذي فيه بت واحد مضبوط (قيمته 1) إلى عدد صحيح فيه بت واحد غير مضبوط (قيمته 0). العامل & لإلغاء ضبط بت باستخدام خصائص & التالية: بما أنّ ‎&=‎ مع بت مضبوط لن تفعل شيء ‎((1 & 1) === 1, (0 & 1) === 0)‎ فإنّ استخدام ‎&=‎ مع عدد صحيح له بت واحد غير مضبوط لن يضبط هذا البت بدون التأثير على البتات الأخرى. استخدام ‎&=‎ مع بت غير مضبوط لن يضبط ذلك البت ‎((1 & 0) === 0, (0 & 0) === 0)‎. استخدام العامل & مع قناع بتّي آخر سيصفّي كل البتات الباقية غير المضبوطة في القناع البتّي هذا. إذا كان في الخرج أي بت مضبوط فهذا يعني أن الخياران ممكّنان. إذا كان في الخرج كل بتات القناع البتّي مضبوطة، هذا يعني أنّ كل خيارات القناع البتّي ممكّنة. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } تذكر دومًا أنّ عوامل الموازنة هذه ‎(< > <= >= == === != !== <> <=>)‎ لها أولوية أعلى من عوامل قناع بتّي-قناع بتّي (& ^ |)، وبما أنّ نتائج العمليات الثنائية تُوازن عادةً باستخدام عوامل الموازنة هذه فيجب الانتباه إلى هذه النقطة. عوامل إزاحة البت (Bit-shifting) عامل الإزاحة إلى اليسار ‎<<‎ يزيح كل البتات إلى اليسار (الأكثر أهمية) وفق عددٍ معطى، ويتجاهل كل البتات المزاحة. يكافئ هذا العامل عدم ضبط بتات ‎$x العليا والضرب بالقوة رقم ‎$x للعدد 2.? printf("%'08b", 0b00001011<< 2); // 00101100 assert(PHP_INT_SIZE === 4); // نظام 32 بت printf("%x, %x", 0x5FFFFFFF << 2, 0x1FFFFFFF << 4); // 7FFFFFFC, FFFFFFFF عامل الإزاحة إلى اليمين ‎>>‎ يزيح البتات إلى اليمين (الأقل أهمية) ويتجاهل البتات المزاحة، يكافئ هذا العامل القسمة على القوة رقم ‎$x للعدد 2 ويتجاهل الجزء غير الصحيح. printf("%x", 0xFFFFFFFF >> 3); // 1FFFFFFF أمثلة لاستخدام عوامل إزاحة البت القسمة السريعة على 16 (أداء أفضل من ‎‎/= 16) $x >>= 4; يتجاهل هذا العامل في أنظمة 32 بت كل البتات في العدد الصحيح ويضبط قيمتها إلى 0، وفي أنظمة 64 بت يؤدي إلى عدم ضبط البتات 32 الأكثر أهمية ويحافظ على الأقل أهمية. $x = $x << 32 >> 32; 32 بت الأكثر أهمية تكافئ ‎$x & 0xFFFFFFFF. لاحظ أنّه في هذا المثال استخدام printf("%'06b")‎ يُرجع القيمة بشكل 6 أرقام ثنائية. عوامل الأنواع instanceof (‏type operator) يمكننا استخدام العامل instanceof الثنائي بدءًا من PHP الإصدار 5 للتحقق فيما إذا كانت الكائنات من نفس الصنف. المعامل الأول (اليساري) هو الكائن الذي نريد التحقق منه، إذا لم يكن هذا المتغير كائن فإنّ العامل instanceof سيرجع false، إذا اُستخدم تعبير ثابت سيُرمى خطأ. المعامل الثاني (اليميني) هو الصنف الذي نريد الموازنة معه، يمكن أن نعبر عن الصنف باسم الصنف نفسه أو بمتغير من نوع سلسلة نصية يعبر عن اسم الصنف (وليس سلسلة نصية ثابتة) أو بكائن من ذلك الصنف. class MyClass { } $o1 = new MyClass(); $o2 = new MyClass(); $name = 'MyClass'; // true القيمة $a في الحالات التالية يأخذ المتغير $a = $o1 instanceof MyClass; $a = $o1 instanceof $name; $a = $o1 instanceof $o2; $b = 'b'; $a = $o1 instanceof 'MyClass'; // parse error: constant not allowed $a = false instanceof MyClass; // fatal error: constant not allowed $a = $b instanceof MyClass; // false (ليس كائن $b المتغير) يمكن أيضًا أن نستخدم العامل instanceof للتحقق فيما إذا كان كائن ما نسخة من صنف يوسّع صنف آخر أو يحقق واجهة ما: interface MyInterface { } class MySuperClass implements MyInterface { } class MySubClass extends MySuperClass { } $o = new MySubClass(); // true القيمة $a في الحالات التالية يأخذ المتغير $a = $o instanceof MySubClass; $a = $o instanceof MySuperClass; $a = $o instanceof MyInterface; يمكننا استخدام العامل not ! للتحقق فيما إذا كان الكائن ليس من صنف ما: class MyClass { } class OtherClass { } $o = new MyClass(); $a = !$o instanceof OtherClass; // true لاحظ أننا لا نحتاج الأقواس حول ‎$o instanceof MyClass لأنّ العامل instanceof له أولوية أعلى من ! بالرغم من أنّ إضافة الأقواس تجعل الشيفرة أكثر قابلية للقراءة. تحذيرات إذا لم يكن الصنف موجودًا تُستدعى دوال التحميل التلقائي المسجلّة لمحاولة إيجاد الصنف، في الإصدارات السابقة للإصدار PHP 5.1.0 فإنّ العامل instanceof قد يقوم بهذه الاستدعاءات وبالتالي يحدد الصنف فعليًا (وإذا لم يحدَّد الصنف سيحدث خطأ فادح (fatal error)). // 5.1.0 الإصدارات السابقة للإصدار class MyClass { } $o = new MyClass(); $a = $o instanceof OtherClass; // غير معرّف OtherClass الصنف /* معرّف في المحمّل التلقائي المسجل فإنّه سيُحمّل OtherClass إذا كان الصنف وإذا لم يكن هذا الصنف معرّفًا (OtherClass ليس من $a المتغير) false القيمة $a ويأخذ المتغير سيُرمى خطأً فادحًا */ $name = 'YetAnotherClass'; $a = $o instanceof $name; // غير معرّف YetAnotherClass الصنف //غير معرّف YetAnotherClass ويبقى الصنف false القيمة المنطقية $a يأخذ المتغير بدءًا من الإصدار 5.1.0 فإنّ المحمّلات التلقائية المسجلّة لن تُستدعى في مثل هذه الحالات. الإصدارات الأقدم من PHP (قبل الإصدار 5.0) يمكن استخدام الدالة is_a في الإصدارات السابقة للإصدار 5.0 لتحديد فيما إذا كان الكائن من صنف معين، أُهملت هذه الدالة في PHP 5 ولم تُهمل في PHP 5.3.3. ترجمة -وبتصرف- للفصل [Operators] من كتاب PHP Notes for Professionals اقرأ أيضًا المقال التالي: المراجع (references) في PHP المقال السابق: طباعة القيم وعرضها في PHP
  19. من المفيد لبناء برنامج PHP ديناميكي وتفاعلي أن تحصل على خرج من المتغيرات وقيمتها، توفر PHP عدة توابع لعرض قيمة ما، يغطي هذا الفصل التوابع الأساسية لطباعة قيمة في PHP وأين يمكن استخدامها. echo وprint تعدّ echo وprint من بنى اللغة وليست دوالًا، لذلك لا حاجة لاستخدام الأقواس لتمرير الوسائط إليها كما نفعل في الدوال (بالرغم من أنه يمكننا دائمًا إضافة أقواس حول أي تعبير PHP لذا يمكننا كتابة echo‎("test‎")‎ دون أيّة مشكلة)، وخرجهما عبارة عن تمثيل سلسلة لمتغير أو ثابت أو تعبير ولا يمكن استخدامهما لطباعة مصفوفات أو كائنات. إسناد السلسلة Joel للمتغير ‎$name: $name = "Joel"; خرج قيمة المتغير ‎$name باستخدام echo وprint: echo $name; // Joel print $name; // Joel الأقواس غير ضرورية إلا أنّه يمكنك استخدامها: echo($name); // Joel print($name); // Joel استخدام عدة معاملات (مع echo فقط): echo $name, "Smith"; // JoelSmith echo($name, " ", "Smith"); // Joel Smith print هي تعبير يرجع القيمة 1 لذا يمكن أن يكون استخدامها مفيدًا أكثر من echo في بعض الحالات: print("hey") && print(" ") && print("you"); // you11 تكافئ الشيفرة السابقة مايلي: print ("hey" && (print (" " && print "you"))); // you11 الصياغة المختصرة للبنية echo من الممكن افتراضيًا استخدام الصياغة المختصرة عند استخدام وسوم PHP خارج شيفرة PHP، وذلك بدءًا بوسم البداية ‎<?=‎ وانتهاءً بوسم النهاية ‎?>‎، مثال عن ذلك: <p><?=$variable?></p> <p><?= "This is also PHP" ?></p> لاحظ أنّه لم يتم الإنهاء باستخدام الفاصلة المنقوطة ;، إلا أنّ هذه الشيفرة تعمل بشكلٍ صحيحٍ لأن وسم نهاية PHP ينهي كل تعليمة، لذلك تُحذف عادةً الفاصلة المنقوطة في الصياغة المختصرة. أولوية البنية print على الرغم من أن print بنية لغة إلا أنّ لها أولوية مثل العامل (operator)، وتوضع بين = += -= *= **= /= .= %= &= والعوامل وتترك ارتباطها. مثال: echo '1' . print '2' + 3; // 511 نفس المثال مع أقواس: echo '1' . print ('2' + 3); // 511 الفروقات بين echo وprint باختصار، هناك فارقين أساسيّين بينهما: تأخذ print معاملًا واحدًا فقط بينما يمكن أن تأخذ echo عدة معاملات. ترجع print قيمة لذا يمكن استخدامها تعبيرًا (expression). إظهار عرض بنيوي للمصفوفات والكائنات الدالة ‎print_r()‎: تعرض المصفوفات والكائنات لتنقيح الأخطاء (debugging) وتُظهر المصفوفة أو الكائن بتنسيقٍ مقروءٍ من قِبل الإنسان. إذا كان لديك متغير عبارة عن كائن أو مصفوفة وحاولت عرضه مستخدمًا echo، فإنّ ذلك سيرمي الخطأ Notice: Array to string conversion، يمكنك استخدام الدالة print_r بدلًا منها لإظهار المتغير بتنسيقٍ مقروء من قبل الإنسان، ويمكنك تمرير true معاملًا ثانيًا للحصول على المحتوى بشكل سلسلة نصية. $myobject = new stdClass(); $myobject->myvalue = 'Hello World'; $myarray = [ "Hello", "World" ]; $mystring = "Hello World"; $myint = 42; // لعرض بيانات مصفوفة print_r نستخدم print_r($myobject); print_r($myarray); print_r($mystring); print_r($myint); خرج الشيفرة السابقة هو: stdClass Object ( [myvalue] => Hello World ) Array ( [0] => Hello [1] => World ) Hello World 42 بالإضافة إلى ذلك يمكن أن نحصل على خرج الدالة print_r بشكل سلسلة نصية بدلًا من طباعته بشكلٍ بسيط، مثلًا تعرض الشيفرة التالية النسخة المنسّقة من ‎$myarray في متغير جديد: $formatted_array = print_r($myarray, true); لاحظ أنّه إذا كنت تعرض خرج PHP في متصفح ويتم ترجمته إلى HTML فلن يتم عرض فواصل الأسطر وسيكون الخرج أقل وضوحًا إلا إذا كتبت كالتالي: echo '<pre>' . print_r($myarray, true) . '</pre>'; لاحظ أنّ فتح الشيفرة المصدرية لصفحة ما سيكتب متغيرك بنفس التنسيق أيضًا بدون استخدام الوسم <pre>. يمكنك بدلًا من ذلك أن تخبر المتصفح أن ما تعرضه هو نص عادي وليس HTML: header('Content-Type: text/plain; charset=utf-8'); print_r($myarray); الدالة var_dump()‎: خرجها عبارة عن معلومات تنقيح قابلة للقراءة من قِبل الإنسان حول محتوى الوسيط (الوسطاء) متضمنةً نوعه وقيمته، وخرج هذه الدالة أكثر تفصيلًا من خرج الدالة print_r لأنّ خرجها أيضًا نوع المتغير وقيمته ومعلومات أخرى مثل معرّف الكائن، حجم المصفوفة، طول السلسلة النصية، مؤشر المرجع وغير ذلك…، لذا يمكنك استخدام var_dump لإخراج نسخة أكثر تفصيلًا للتنقيح. var_dump($myobject, $myarray, $mystring, $myint); لاحظ أنّ الخرج أكثر تفصيلًا: object(stdClass)#12 (1) { ["myvalue"]=> string(11) "Hello World" } array(2) { [0]=> string(5) "Hello" [1]=> string(5) "World" } string(11) "Hello World" int(42) لاحظ أنّه إذا استخدمت المنقّح xDebug في بيئة التطوير فإنّ خرج var_dump سيكون محدودًا بشكلٍ افتراضي. يمكنك الاطلاع على التوثيق الرسمي لمزيدٍ من المعلومات حول خيارات تغيير هذا الإعداد الافتراضي. var_export()‎: تُخرج هذه الدالة شيفرة PHP صالحة، وتعيد تمثيل المتغير. يمكنك أن تمرر true معاملًا ثانيًا لترجع المحتويات في متغير. var_export($myarray); var_export($mystring); var_export($myint); الخرج هو شيفرة PHP صالحة: array ( 0 => 'Hello', 1 => 'World', ) 'Hello World' 42 يمكنك أن تفعل التالي لتضع المحتوى في متغير: $array_export = var_export($myarray, true); $string_export = var_export($mystring, true); $int_export = var_export($myint, 1); // أي قيمة توكيد بعد ذلك يمكنك إخراجها كالتالي: printf('$myarray = %s; %s', $array_export, PHP_EOL); printf('$mystring = %s; %s', $string_export, PHP_EOL); printf('$myint = %s; %s', $int_export, PHP_EOL); ستخرج هذه الشيفرة الخرج التالي: $myarray = array ( 0 => 'Hello', 1 => 'World', ); $mystring = 'Hello World'; $myint = 42; ربط السلاسل النصية باستخدام echo يمكنك استخدام الربط (concatenation) لربط السلاسل "نهاية إلى نهاية" عند إخراجها (باستخدام echo أو print مثلًا)، يمكنك دمج المتغيرات باستخدام . (نقطة). //متغير بشكل سلسلة نصية $name = 'Joel'; // ربط عدة سلاسل نصية (3 سلاسل في مثالنا) في سلسلة نصية واحدة ثم طباعتها echo '<p>Hello ' . $name . ', Nice to see you.</p>'; // "<p>Hello Joel, Nice to see you.</p>" وبطريقة مشابهة للربط يمكننا استخدام echo (بدون أقواس) لربط السلاسل والمتغيرات معًا (بالإضافة إلى التعابير العشوائية الأخرى) باستخدام الفاصلة (,). $itemCount = 1; echo 'You have ordered ', $itemCount, ' item', $itemCount === 1 ? '' : 's'; // "You have ordered 1 item" موازنة بين دمج السلاسل وتمرير عدة وسائط للبنية echo يعدّ تمرير عدة وسائط للبنية echo أفضل من دمج السلاسل في بعض الحالات، تُكتب الوسائط في الخرج بنفس الترتيب الذي تُمرّر به. echo "The total is: ", $x + $y; المشكلة في الربط هي أنّ النقطة . لها الأولوية في التعبير، فإذا استخدمناها في المثال أعلاه سنحتاج لأقواس إضافية ليتم الربط بالشكل الصحيح لأنّ أولوية النقطة تؤثر على العوامل الثلاثية أيضًا. echo "The total is: " . ($x + $y); printf أو sprintf الدالة printf تعرض سلسلة منسقة باستخدام بدائل. الدالة sprintf ترجع سلسلة منسقة. $name = 'Jeff'; // أن تتوقع سلسلة نصية PHP `%s` تخبر printf("Hello %s, How's it going?", $name); // Hello Jeff, How's it going? // بدلًا من إخراجها مباشرة ($greeting) وضعها في متغير $greeting = sprintf("Hello %s, How's it going?", $name); echo $greeting; // Hello Jeff, How's it going? من الممكن أيضًا تنسيق عدد باستخدام هاتين الدالتين، مثلًا عند تنسيق قيمة عشرية لتمثيل المال إذ يوجد لدينا دائمًا رقمين عشريين. $money = 25.2; printf('%01.2f', $money); // 25.20 الخرج تعمل الدالتان vprintf وvsprintf مثل printf وsprintf لكنهما تقبلان سلسلة تنسيق ومصفوفة من القيم بدلًا من المتغيرات الفردية. إظهار أعداد صحيحة كبيرة تُحوّل الأعداد الصحيحة الكبيرة الأكبر من PHPINTMAX تلقائيًا إلى أعداد عشرية (float) في أنظمة 32 بت، يمكن إظهار هذه القيم الصحيحة (أي التدوين غير العلمي) بالدالة printf مستخدمًا التمثيل العشري كما هو موضح في الشيفرة التالية: foreach ([1, 2, 3, 4, 5, 6, 9, 12] as $p) { $i = pow(1024, $p); printf("pow(1024, %d) > (%7s) %20s %38.0F", $p, gettype($i), $i, $i); echo " ", $i, "\n"; } // الخرج pow(1024, 1) integer 1024 1024 1024 pow(1024, 2) integer 1048576 1048576 1048576 pow(1024, 3) integer 1073741824 1073741824 1073741824 pow(1024, 4) double 1099511627776 1099511627776 1099511627776 pow(1024, 5) double 1.1258999068426E+15 1125899906842624 1.1258999068426E+15 pow(1024, 6) double 1.1529215046068E+18 1152921504606846976 1.1529215046068E+18 pow(1024, 9) double 1.2379400392854E+27 1237940039285380274899124224 1.2379400392854E+27 pow(1024, 12) double 1.3292279957849E+36 1329227995784915872903807060280344576 1.3292279957849E+36 ملاحظة: انتبه من دقة الأعداد العشرية اللانهائية. يمكن في هذا المثال المبتكر تمثيل جميع الأعداد كعدد ثنائي لأنّها كلها قوى العدد 1024 (وبالتالي 2)، مثلًا: $n = pow(10, 27); printf("%s %.0F\n", $n, $n); // 1.0E+27 1000000000000000013287555072 إظهار مصفوفة متعددة الأبعاد مع الفهرس والقيمة وطباعتها ضمن جدول Array ( [0] => Array ( [id] => 13 [category_id] => 7 [name] => Leaving Of Liverpool [description] => Leaving Of Liverpool [price] => 1.00 [virtual] => 1 [active] => 1 [sort_order] => 13 [created] => 2007-06-24 14:08:03 [modified] => 2007-06-24 14:08:03 [image] => NONE ) [1] => Array ( [id] => 16 [category_id] => 7 [name] => Yellow Submarine [description] => Yellow Submarine [price] => 1.00 [virtual] => 1 [active] => 1 [sort_order] => 16 [created] => 2007-06-24 14:10:02 [modified] => 2007-06-24 14:10:02 [image] => NONE ) ) إظهار مصفوفة متعددة الأبعاد مع الفهرس والقيمة في جدول <table> <?php foreach ($products as $key => $value) { foreach ($value as $k => $v) { echo "<tr>"; echo "<td>$k</td>"; // Get index. echo "<td>$v</td>"; // Get value. echo "</tr>"; } } ?> </table> ترجمة -وبتصرف- للفصل [Outputting the Value of a Variable] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: أهم العوامل (operators) الموجودة في PHP المقال السابق: المتغيرات ذات النطاق العام العالي (Superglobal Variables) في PHP
  20. هي متغيرات مدمجة معرّفة مسبقًا في لغة PHP على أنّها متغيرات ذات نطاق عام عالي، وهذا يعني أنّها متاحة دائمًا في جميع نطاقات السكربت دون الحاجة لتعريفها باستخدام الكلمة المفتاحية global للوصول إليها من داخل الدوال أو التوابع. شرح المتغيرات ذات النطاق العام العالي ببساطة هي المتغيرات المتاحة في كل نطاقات ملفات السكربت لديك، وهذا يعني أنّه لا حاجة لتمريرها كمعاملات للدوال أو تخزينها خارج كتلة الشيفرة لتصبح متاحة في مختلف النطاقات. ما هو المتغير ذو النطاق العام العالي؟ بدءًا من الإصدار 7.1.3 PHP هناك 9 متغيرات ذات نطاق عام عالي وهي: ‎$GLOBALS‎: تشير إلى كل المتغيرات المتاحة في النطاق العام ‎$_SERVER‎: معلومات الخادم وبيئة التنفيذ ‎$_GET: متغيرات طلب HTTP باستخدام GET ‎$_POST: متغيرات طلب HTTP باستخدام POST ‎$_FILES: متغيرات طلب HTTP لتحميل ملف ‎$_COOKIE: ملفات تعريف ارتباط HTTP ‏(HTTP Cookies) ‎$_SESSION: متغيرات الجلسة ‎$_REQUEST: متغيرات طلب HTTP ‎:$_ENV متغيرات البيئة يمكنك الاطلاع عليها في التوثيق الرسمي إليك المزيد من المعلومات حول هذه المتغيرات: ‎$GLOBALS مصفوفة ترابطية (associative array) تحوي مراجعًا لكل المتغيرات المعرّفة في النطاق العام للسكربت، وتمثّل المفاتيح فيها أسماء المتغيرات وقيمة عناصرها هي محتويات تلك المتغيرات. // تعريف متغير خارج النطاق $myGlobal = "global"; function test() { // تعريف متغير داخل النطاق $myLocal = "local"; // طباعة كلا المتغيرين var_dump($myLocal); var_dump($GLOBALS["myGlobal"]); } // تنفيذ الدالة test(); لم يعرّف ضمن النطاق العام $myLocal ‏‎‏فقط بما أنّ $myGlobal ستُطبَع قيمة المتغير var_dump($myLocal); var_dump($myGlobal); خرج الشيفرة السابقة: string 'local' (length=5) string 'global' (length=6) null string 'global' (length=6) في المثال السابق لم تُعرض قيمة المتغير ‎$myLocal في المرة الثانية لأنّه معرّف داخل الدالة test‎‎()‎ ودُمِّر بعد إغلاقها. تعريف متغير عام هناك طريقتان للتعريف: الطريقة الأولى: استخدام الكلمة المفتاحية global: function test() { global $myLocal; $myLocal = "local"; var_dump($myLocal); var_dump($GLOBALS["myGlobal"]); } تسبق الكلمة المفتاحية global اسم المتغير ليصبح جزءًا من النطاق العام، ولا يمكن إسناد قيمة للمتغير في نفس التعليمة التي تعرّفه متغيرًا عامًا لذا أُسندَت القيمة له في سطرٍ منفصلٍ من الشيفرة السابقة، يمكن ذلك فقط في حال كتبت الشيفرة في سطرٍ واحد بالشكل التالي: global $myLocal; $myLocal = "local"; لكن سيؤثر ذلك بالتأكيد على أناقة الشيفرة. الطريقة الثانية: مصفوفة ‎$GLOBALS: function test() { $GLOBALS["myLocal"] = "local"; $myLocal = $GLOBALS["myLocal"]; var_dump($myLocal); var_dump($GLOBALS["myGlobal"]); } في هذا المثال أعدت إسناد القيمة ["‎$GLOBAL["myLocal للمتغير ‎$myLocal إذ وجدت أن كتابة اسم المتغير أسهل من كتابة المصفوفة الترابطية. ‎$_SERVER ‎$_SERVER هي مصفوفة تتضمن معلومات مثل الترويسات والمسارات وأماكن وجود السكربت. ينشئ خادم الويب مداخل هذه المصفوفة لكن ليس هناك ما يضمن أن خادم الويب سيزودنا بهذه المعلومات فقد يحذف خادم الويب بعضها أو قد يزودنا بمعلومات إضافية، يمكنك الاطلاع على جميع المتغيرات المتوقعة هنا. إليك مثال خرج يمكن أن يكون كالتالي (يمكنك تشغيله عبر جهاز بنظام تشغيل ويندوز على الخادم WAMP): C:\wamp64\www\test.php:2: array (size=36) 'HTTP_HOST' => string 'localhost' (length=9) 'HTTP_CONNECTION' => string 'keep-alive' (length=10) 'HTTP_CACHE_CONTROL' => string 'max-age=0' (length=9) 'HTTP_UPGRADE_INSECURE_REQUESTS' => string '1' (length=1) 'HTTP_USER_AGENT' => string 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' (length=110) 'HTTP_ACCEPT' => string 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' (length=74) 'HTTP_ACCEPT_ENCODING' => string 'gzip, deflate, sdch, br' (length=23) 'HTTP_ACCEPT_LANGUAGE' => string 'en-US,en;q=0.8,en-GB;q=0.6' (length=26) 'HTTP_COOKIE' => string 'PHPSESSID=0gslnvgsci371ete9hg7k9ivc6' (length=36) 'PATH' => string 'C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\ProgramData\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem ;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;E:\Program Files\ATI Technologies\ATI.ACE\Core- Static;E:\Program Files\AMD\ATI.ACE\Core-Static;C:\Program Files (x86)\AMD\ATI.ACE\Core- Static;C:\Program Files (x86)\ATI Technologies\ATI.ACE\Core-Static;C:\Program Files\Intel\Intel(R) Managemen'... (length=1169) 'SystemRoot' => string 'C:\WINDOWS' (length=10) 'COMSPEC' => string 'C:\WINDOWS\system32\cmd.exe' (length=27) 'PATHEXT' => string '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY' (length=57) 'WINDIR' => string 'C:\WINDOWS' (length=10) 'SERVER_SIGNATURE' => string '<address>Apache/2.4.23 (Win64) PHP/7.0.10 Server at localhost Port 80</address>' (length=80) 'SERVER_SOFTWARE' => string 'Apache/2.4.23 (Win64) PHP/7.0.10' (length=32) 'SERVER_NAME' => string 'localhost' (length=9) 'SERVER_ADDR' => string '::1' (length=3) 'SERVER_PORT' => string '80' (length=2) 'REMOTE_ADDR' => string '::1' (length=3) 'DOCUMENT_ROOT' => string 'C:/wamp64/www' (length=13) 'REQUEST_SCHEME' => string 'http' (length=4) 'CONTEXT_PREFIX' => string '' (length=0) 'CONTEXT_DOCUMENT_ROOT' => string 'C:/wamp64/www' (length=13) 'SERVER_ADMIN' => string 'wampserver@wampserver.invalid' (length=29) 'SCRIPT_FILENAME' => string 'C:/wamp64/www/test.php' (length=26) 'REMOTE_PORT' => string '5359' (length=4) 'GATEWAY_INTERFACE' => string 'CGI/1.1' (length=7) 'SERVER_PROTOCOL' => string 'HTTP/1.1' (length=8) 'REQUEST_METHOD' => string 'GET' (length=3) 'QUERY_STRING' => string '' (length=0) 'REQUEST_URI' => string '/test.php' (length=13) 'SCRIPT_NAME' => string '/test.php' (length=13) 'PHP_SELF' => string '/test.php' (length=13) 'REQUEST_TIME_FLOAT' => float 1491068771.413 'REQUEST_TIME' => int 1491068771 هناك الكثير من الأشياء التي يمكن أن نتحدث عنها لكن سنختار الأهم من بينها، يمكنك الاطلاع عليها كلها من خلال قسم الفهارس من التوثيق. لنوضح بعضها نفرض أنّ لدينا الرابط التالي http://www.example.com/index.php HTTP_HOST: عنوان المضيف، يرجع www.example.com. HTTP_USER_AGENT: محتويات وكيل المستخدم (user agent)، عبارة عن سلسلة نصية تحوي كل معلومات متصفح العميل (client's browser) بما في ذلك نظام التشغيل. HTTP_COOKIE: جميع ملفات تعريف الارتباط (cookies) في سلسلة متسلسلة يفصل بين عناصرها فاصلة منقوطة. SERVER_ADDR: عنوان IP الخادم الذي ينفذ السكربت، يرجع القيمة 93.184.216.34. PHP_SELF: اسم ملف السكربت المنفّذ حاليًا نسبةً إلى المستند الجذر، يرجع في حالتنا القيمة /index.php. REQUEST_TIME_FLOAT: الختم الزمني (timestamp) لبداية الطلب بدقة أجزاء الثانية، متاح من الإصدار PHP 5.4.0. REQUEST_TIME: الختم الزمني لبداية الطلب، متاح من الإصدار PHP 5.1.0. ‎:$_GET مصفوفة ترابطية من المتغيرات الممررة عبر معاملات الرابط إلى السكربت الحالي، أي ما يأتي بعد ? في الرابط. بفرض لدينا الرابط http://www.example.com/index.php?myVar=myVal، يمكننا الحصول من الرابط على قيمة ‎$_GET["myVar"]‎ وستكون النتيجة هي myVal، يمكننا التعبير عن ذلك بالشيفرة التالية: // URL = http://www.example.com/index.php?myVar=myVal echo $_GET["myVar"] == "myVal" ? "true" : "false"; // true يستخدم المثال السابق العامل الثلاثي (ternary operator)، ويبيّن كيفية الوصول للقيمة من الرابط باستخدام المتغير ذو النطاق العام العالي ‎$_GET. إليك مثال آخر: // URL = http://www.example.com/index.php?myVar=myVal&myVar2=myVal2 echo $_GET["myVar"]; // "myVal" echo $_GET["myVar2"]; // "myVal2" يمكن إرسال عدة متغيرات عبر الرابط ويُفصل بينها بالمحرف (&). الخطر الأمني من المهم جدًا عدم إرسال معلومات حساسة عبر الرابط لأنها تبقى في سجل الحاسوب وستكون مرئية لأي شخص يمكنه الوصول للمتصفح. ‎$_POST مصفوفة ترابطية من المتغيرات الممررة للسكربت الحالي بطريقة HTTP POST عند استخدام نوع المحتوى (Content-Type) لطلب HTTP ‏application/x-www-form-urlencoded أو multipart/form-data، وتشبه مصفوفة ‎$_GET أنّ البيانات ممررة من مكان لآخر. سنبدأ فورًا بمثال (تجاهلت الخاصيّة attribute حتى تُرسل المعلومات إلى نفس صفحة النموذج). <form method="POST"> <input type="text" name="myVar" value="myVal" /> <input type="submit" name="submit" value="Submit" /> </form> في الأعلى شكل أساسي لنموذج إرسال البيانات، إنّ عدم وجود الخاصيّة value في التطبيق الحقيقي يعني أنّ النموذج سيكون فارغًا، أما وجودها سيرسل المعلومات المُدخلة من قِبل المستخدم. echo $_POST["myVar"]); // "myVal" الخطر الأمني إنّ إسال البيانات باستخدام POST ليس آمنًا دائمًا، يجب استخدام HTTPS لضمان أمن المعلومات بشكلٍ أكبر. ‎$_FILES مصفوفة ترابطية من العناصر المحمّلة إلى السكربت الحالي بطريقة HTTP POST، بنية هذه المصفوفة مبينة هنا. لنبدأ مع نموذج أساسي: <form method="POST" enctype="multipart/form-data"> <input type="file" name="myVar" /> <input type="submit" name="Submit" /> </form> لاحظ أنّني تجاهلت خاصيّة action أيضًا (لنفس السبب السابق)، وأضفت enctype="multipart/form-data"‎ وهذا ضروري لأيّ نموذج يتعامل مع تحميل الملفات. // التأكد من عدم وجود خطأ if ($_FILES["myVar"]["error"] == UPLOAD_ERR_OK) { $folderLocation = "myFiles"; // مثلًا "path/to/file" مسار منسوب يمكن أن يكون // إذا لم يوجد المجلد المطلوب أنشئه if (!file_exists($folderLocation)) mkdir($folderLocation); // انقل الملف داخل المجلد move_uploaded_file($_FILES["myVar"]["tmp_name"], "$folderLocation/" . basename($_FILES["myVar"]["name"])); } يُستخدم هذا لتحميل ملف واحد، يوجد الخاصيّة multiple تساعدك في تحميل أكثر من ملف. إليك مثال لإرسال نموذج بعدة ملفات: <form method="POST" enctype="multipart/form-data"> <input type="file" name="myVar[]" multiple="multiple" /> <input type="submit" name="Submit" /> </form> لاحظ أنّ التغييرات قليلة جدًا: خاصيّة name للحقل input لها أقواس معقوفة []. ذلك لأنّه نريد إخبار النموذج أنّه لدينا مصفوفة ملفات مُختارة لإرسالها وتجاهل هذه الأقواس سيؤدي لإرسال الملف الأخير فقط إلى ‎$_FILES["myVar"]‎. الخاصيّة multiple="multiple"‎، تُخبر المتصفح أنّه يمكن للمستخدم اختيار أكثر من ملف. // حساب عدد الملفات المُرسلة $total = isset($_FILES["myVar"]) ? count($_FILES["myVar"]["name"]) : 0; // تكرار لكل ملف من الملفات for ($i = 0; $i < $total; $i++) { // إذا لم يوجد خطأ if ($_FILES["myVar"]["error"][$i] == UPLOAD_ERR_OK) { // مثلًا "path/to/file" مسار منسوب يمكن أن يكون $folderLocation = "myFiles"; // إذا لم يوجد المجلد أنشئه if (!file_exists($folderLocation)) mkdir($folderLocation); // انقل الملف إلى المجلد move_uploaded_file($_FILES["myVar"]["tmp_name"][$i], "$folderLocation/" . basename($_FILES["myVar"]["name"][$i])); } // وإلّا أخبر عن الخطأ else switch ($_FILES["myVar"]["error"][$i]) { case UPLOAD_ERR_INI_SIZE: echo "Value: 1; The uploaded file exceeds the upload_max_filesize directive in php.ini."; break; case UPLOAD_ERR_FORM_SIZE: echo "Value: 2; The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form."; break; case UPLOAD_ERR_PARTIAL: echo "Value: 3; The uploaded file was only partially uploaded."; break; case UPLOAD_ERR_NO_FILE: echo "Value: 4; No file was uploaded."; break; case UPLOAD_ERR_NO_TMP_DIR: echo "Value: 6; Missing a temporary folder. Introduced in PHP 5.0.3."; break; case UPLOAD_ERR_CANT_WRITE: echo "Value: 7; Failed to write file to disk. Introduced in PHP 5.1.0."; break; case UPLOAD_ERR_EXTENSION: echo "Value: 8; A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with phpinfo() may help. Introduced in PHP 5.2.0."; break; default: echo "An unknown error has occurred."; break; } } هذا مثال بسيط جدًا ولا يعالج مشاكل مثل امتدادات الملفات الغير مسموح بها أو الملفات المسمّاة بأسماء شيفرة PHP (مثل ما يعادل حقن SQL في PHP). الخطوة الأولى هي التأكد من وجود ملفات وإذا وجدت أسند عددهم للمتغير ‎$total. ثم نستخدم الحلقة for للمرور على عناصر المصفوفة ‎$_FILES الواحد تلو الآخر، عند كل ملف إذا لم يواجه مشاكل عندها تكون نتيجة الشرط if هي true ويُحمّل الملف، أما إذا وجدت مشكلة ما تُنفّذ الكتلة switch ويتم إخبارنا بالخطأ الحاصل الذي أوقف عملية تحميل هذا الملف. ‎$_COOKIE مصفوفة ترابطية من المتغيرات الممررة للسكربت الحالي باستخدام ملفات تعريف ارتباط HTTP (‏HTTP Cookies‏‏‏)‏. ملفات تعريف الارتباط هي متغيرات تحوي بيانات وتخزّن في حاسوب العميل، وعلى عكس المتغيرات ذات النطاق العام العالي فإنّه يجب إنشاؤها مع وظيفة (وليس إسناد قيمة لها)، إليك المثال التالي: setcookie("myVar", "myVal", time() + 3600); في المثال السابق حُدِّد اسم ملف تعريف الارتباط ("myVar")، وإعطاء قيمة له ("myVal")، ويمكن تمرير متغير لإسناد قيمته لملف تعريف الارتباط، ثم وقت انتهاء الصلاحية (في مثالنا هو ساعة بما أنّ 3600 ثانية تعادل ساعة). يمكن الوصول إلى ملف تعريف الارتباط بنفس الطرق السابقة بالرغم من أنّ طريقة إنشائه مختلفة. echo $_COOKIE["myVar"]; // "myVal" لتدمير ملف تعريف الارتباط يجب إعادة استدعاء الدالة setcookie لكن تُعطى قيمة وقت انتهاء الصلاحية أي وقت ماضٍ. كما في المثال: setcookie("myVar", "", time() - 1); var_dump($_COOKIE["myVar"]); // null سيلغي هذا تعيين ملف الارتباط ويزيله من حاسوب العميل. ‎$_SESSION مصفوفة ترابطية تحوي متغيرات الجلسة المتاحة للسكربت الحالي، يمكنك الاطلاع على توثيق دوال الجلسات لمزيدٍ من المعلومات. تشبه الجلسات ملفات تعريف الارتباط إلى حد كبير باستثناء أنّها من جانب الخادم. يجب استدعاء الدالة session_start()‎ في بداية السكربت للسماح باستخدام الجلسات، وتعيين متغير الجلسة مثل تعيين أي متغير آخر، لاحظ المثال التالي: $_SESSION["myVar"] = "myVal"; يُعيَّن في بداية الجلسة معرّف عشوائي (random ID) في ملف تعريف ارتباط يسمّى "PHPSESSID" سيحتوي معرّف الجلسة الحالية، ويمكن الوصول له باستدعاء الدالة session_id()‎. يمكن تدمير متغيرات الجلسة باستدعاء الدالة unset، مثل (unset($_SESSION["myVar"]‎) سيدمر هذا المتغير. الحل البديل هو استدعاء الدالة session_destory()‎، سيدمر هذا كامل الجلسة أي لن يبقى لدينا أيّ من متغيراتها. ‎$_REQUEST مصفوفة ترابطية تحوي افتراضيًا محتويات المصفوفات ‎ $GET و‎$POST و‎$_COOKIE. بما أنّه من الممكن أن تحوي هذه المصفوفات الثلاثة على فهرس بنفس الاسم فإنّه يوجد إعداد في ملف php.ini يسمى request_order يحدد الأولوية لهذه المصفوفات، مثلًا إذا ضُبط للقيمة "GPC" عندها ستُستخدم قيمة ‎$_COOKIE، فقراءتها من اليسار لليمين تعني أنّ ‎$_REQUEST ستضبط قيمتها ل‎$_GET ثم ‎$_POST ثم ‎$_COOKIE وبما أنّ ‎$_COOKIE هي الأخيرة فهي القيمة الموجودة في ‎$_REQUEST. ‎$_ENV مصفوفة ترابطية من المتغيرات الممررة إلى السكربت الحالي عن طريق البيئة. تُستورد هذه المتغيرات إلى مجال الأسماء العام في PHP من البيئة التي يُنفَّذ فيها محلل PHP. وتزوَّد العديد منها من الصدفة التي تُنفَّذ فيها PHP إذ أنّ الأنظمة المختلفة تشغّل أنواع مختلفة من الصدف ومن الصعب حصرها في قائمة محددة، يمكنك الاطلاع على توثيق صدفتك لتشاهد قائمة من متغيرات البيئة المعرّفة. تتضمن متغيرات البيئة الأخرى متغيرات CGI، بغض النظر إن كانت PHP تُنفّذ كوحدة خادم أو معالج CGI. كل ما يُخزّن ضمن ‎$_ENV يأتي من البيئة التي تُنفّذ فيها PHP، وتُملأ فقط إذا سُمح بها ضمن الملف php.ini. 3.2: متغيرات النطاق العام العالي في PHP5 هذه المتغيرات هي: ‎$GLOBALS ‎$_REQUEST ‎$_GET ‎$_POST ‎$_FILES ‎$_SERVER ‎$_ENV ‎$_COOKIE ‎$_SESSION ‎$GLOBALS يُستخدم هذا المتغير ذو النطاق العام العالي للوصول إلى المتغيرات العامة. <?php $a = 10; function foo(){ echo $GLOBALS['a']; } // وهي 10 a سيطبع هذا قيمة المتغير العام ?> ‎$_REQUEST يُستخدم هذا المتغير ذو النطاق العام العالي لجمع البيانات المرسلة من نموذج HTML. <?php if(isset($_REQUEST['user'])){ echo $_REQUEST['user']; } // POST و/أو GET والمُرسل بطريقة `name = user` الذي لديه الخاصية HTML ستُطبع قيمة حقل ‎$_GET يُستخدم هذا المتغير ذو النطاق العام العالي لجمع البيانات المرسلة من نموذج HTML بالطريقة GET. <?php if(isset($_GET['username'])){ echo $_GET['username']; } // (1) ?> في الموضع (1) ستُطبع قيمة حقل HTML الذي له الخاصية name = username والمُرسل بطريقة GET. ‎$_POST يُستخدم هذا المتغير ذو النطاق العام العالي لجمع البيانات المرسلة من نموذج HTML بالطريقة POST. <?php if(isset($_POST['username'])){ echo $_POST['username']; } // (1) في الموضع (1) ستُطبع قيمة حقل HTML الذي له الخاصية name = username والمُرسل بطريقة POST. ‎$_FILES يحمل هذا المتغير ذو النطاق العام العالي معلومات الملفات المحمّلة عبر HTTP بالطريقة POST. <?php if($_FILES['picture']){ echo "<pre>"; print_r($_FILES['picture']); echo "</pre>"; } ?> ستطبع الشيفرة السابقة تفاصيل الملف مع اسم الصورة المحمّلة عبر النموذج الذي له الخاصيّات method="post"‎ وenctype="multipart/form-data"‎ تتضمن التفاصيل اسم الملف، نوع الملف، موقع الملف المؤقت وشيفرة الخطأ (في حال حصل أي خطأ أثناء تحميل الملف) وحجم الملف بالبايتات، مثال: Array ( [picture] => Array ( [0] => Array ( [name] => 400.png [type] => image/png [tmp_name] => /tmp/php5Wx0aJ [error] => 0 [size] => 15726 ) ) ) ‎$_SERVER يحمل هذا المتغير ذو النطاق العام العالي معلومات حول السكربتات وترويسات HTTP ومسارات الخادم. <?php echo "<pre>"; print_r($_SERVER); echo "</pre>"; /** ستطبع هذه الشيفرة التفاصيل التالية على الخادم المحلي (XAMPP مثل) Array ( [MIBDIRS] => C:/xampp/php/extras/mibs [MYSQL_HOME] => \xampp\mysql\bin [OPENSSL_CONF] => C:/xampp/apache/bin/openssl.cnf [PHP_PEAR_SYSCONF_DIR] => \xampp\php [PHPRC] => \xampp\php [TMP] => \xampp\tmp [HTTP_HOST] => localhost [HTTP_CONNECTION] => keep-alive [HTTP_CACHE_CONTROL] => max-age=0 [HTTP_UPGRADE_INSECURE_REQUESTS] => 1 [HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*;q=0.8 [HTTP_ACCEPT_ENCODING] => gzip, deflate, sdch [HTTP_ACCEPT_LANGUAGE] => en-US,en;q=0.8 [PATH] => C:\xampp\php;C:\ProgramData\ComposerSetup\bin; [SystemRoot] => C:\Windows [COMSPEC] => C:\Windows\system32\cmd.exe [PATHEXT] => .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC [WINDIR] => C:\Windows [SERVER_SIGNATURE] => Apache/2.4.16 (Win32) OpenSSL/1.0.1p PHP/5.6.12 Server at localhost Port 80 [SERVER_SOFTWARE] => Apache/2.4.16 (Win32) OpenSSL/1.0.1p PHP/5.6.12 [SERVER_NAME] => localhost [SERVER_ADDR] => ::1 [SERVER_PORT] => 80 [REMOTE_ADDR] => ::1 [DOCUMENT_ROOT] => C:/xampp/htdocs [REQUEST_SCHEME] => http [CONTEXT_PREFIX] => [CONTEXT_DOCUMENT_ROOT] => C:/xampp/htdocs [SERVER_ADMIN] => postmaster@localhost [SCRIPT_FILENAME] => C:/xampp/htdocs/abcd.php [REMOTE_PORT] => 63822 [GATEWAY_INTERFACE] => CGI/1.1 [SERVER_PROTOCOL] => HTTP/1.1 [REQUEST_METHOD] => GET [QUERY_STRING] => [REQUEST_URI] => /abcd.php [SCRIPT_NAME] => /abcd.php [PHP_SELF] => /abcd.php [REQUEST_TIME_FLOAT] => 1469374173.88 [REQUEST_TIME] => 1469374173 ) */ ?> ‎$_ENV يحمل هذا المتغير ذو النطاق العام العالي معلومات متغيرات بيئة الصدفة التي تُنفّذ فيها شيفرة PHP. ‎$_COOKIE يُستخدم هذا المتغير ذو النطاق العام العالي لاسترجاع قيمة ملف تعريف الارتباط مع مفتاح مُعطى. <?php $cookie_name = "data"; $cookie_value = "Foo Bar"; setcookie($cookie_name, $cookie_value, time() + (86400 * 30), "/"); // 86400 = يوم واحد if(!isset($_COOKIE[$cookie_name])) { echo "Cookie named '" . $cookie_name . "' is not set!"; } else { echo "Cookie '" . $cookie_name . "' is set!<br>"; echo "Value is: " . $_COOKIE[$cookie_name]; } /** Cookie 'data' is set! Value is: Foo Bar */ ?> ‎$_SESSION يُستخدم هذا المتغير ذو النطاق العام العالي لضبط واسترجاع قيمة الجلسة المخزنة على الخادم. <?php // ابدأ الجلسة session_start(); /** ضبط متغيرات الجلسة التي يمكن الوصول إليها من صفحات مختلفة من الخادم الذي خزنها */ $_SESSION["username"] = "John Doe"; $_SESSION["user_token"] = "d5f1df5b4dfb8b8d5f"; echo "Session is saved successfully"; /** Session is saved successfully */ ?> ترجمة -وبتصرف- للفصول [Superglobal Variables PHP] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: طباعة القيم وعرضها في PHP المقال السابق: المتغيرات والثوابت في PHP
  21. الوصول الديناميكي إلى المتغير عن طريق الاسم (المتغيرات المتغيرة) يمكن الوصول إلى المتغيرات عبر أسماء المتغيرات الديناميكية. يمكن تخزين اسم المتغير في متغير آخر، مما يسمح بالوصول إليه بشكلٍ ديناميكي وتُعرف هذه المتغيرات باسم المتغيرات المتغيرة (variable variables). لتغيير المتغير إلى متغير قابل للتغيير نضع إشارة $ إضافية أمام المتغير. $variableName = 'foo'; $foo = 'bar'; // "bar" فيما يلي كل التعليمات متكافئة وكل الخرج سيكون echo $foo; echo ${$variableName}; echo $$variableName; // بشكلٍ مماثل $variableName = 'foo'; $$variableName = 'bar'; // 'bar' التعليمات التالية ستنتج أيضًا الخرج echo $foo; echo $$variableName; echo ${$variableName}; المتغيرات المتغيرة مفيدة لتحديد استدعاءات الدالة: function add($a, $b) { return $a + $b; } $funcName = 'add'; echo $funcName(1, 2); // outputs 3 سيكون هذا مفيدًا في أصناف (classes) لغة PHP: class myClass { public function __construct() { $functionName = 'doSomething'; $this->$functionName('Hello World'); } private function doSomething($string) { echo $string; // Outputs "Hello World" } } من الممكن، ولكن ليس من الضروري وضع ‎$variableName بين {}: ${$variableName} = $value; الأمثلة التالية متكافئة والخرج "baz": $fooBar = 'baz'; $varPrefix = 'foo'; echo $fooBar; // "baz" الخرج echo ${$varPrefix . 'Bar'}; // "baz" الخرج أيضًا استخدام القوسين {} يكون إلزاميًا فقط عندما يكون اسم المتغير في حد ذاته تعبيرًا، مثل: ${$variableNamePart1 . $variableNamePart2} = $value; ومع ذلك يُنصح دائمًا باستخدام القوسين {}، لأنهما أكثر قابلية للقراءة. على الرغم من أنه لا يوصى بذلك، إلا أنه من الممكن وضع سلسلة لهذا السلوك: $$$$$$$$DoNotTryThisAtHomeKids = $value; من المهم ملاحظة أنّ العديد من المطورين يعدون الاستخدام المفرط للمتغيرات المتغيرة ممارسةً سيئةً، لأنّها غير مناسبة تمامًا للتحليل الثابت من قِبل بيئات التطوير المتكاملة (IDE)، إذ يمكن أن تصبح الشيفرات الأساسية (codebases) الكبيرة مع الكثير من المتغيرات المتغيرة (أو استدعاءات التوابع الديناميكية) صعبة الصيانة. الفروقات بين PHP5 وPHP7‎ السبب الآخر لاستخدام القوسين {} أو () دائمًا هو أنّ PHP5 وPHP7 يختلفان قليلًا في طريقة التعامل مع المتغيرات الديناميكية وقد يؤدي ذلك في بعض الحالات إلى خرجٍ مختلف. ‫ في PHP7، ستُعالج المتغيرات الديناميكية (dynamic variables) والخاصيات (properties) والتوابع (methods) بالترتيب من اليسار إلى اليمين (بما أن الشيفرة تُكتَب من اليسار إلى اليمين) على عكس PHP5 التي تتضمن العديد من الحالات الخاصة، تُظهر الأمثلة أدناه كيف يتغير ترتيب المعالجة الحالة 1: ‎$$foo['bar']['baz']‎‎ ترجمة PHP5: ${$foo['bar']['baz']} ترجمة PHP7: ($$foo)['bar']['baz'] الحالة 2: ‎$foo->$bar['baz']‎ ترجمة PHP5: $foo->{$bar['baz']} ترجمة PHP7: ($foo->$bar)['baz'] الحالة 3: ‎$foo->$bar['baz']()‎‎ ترجمة PHP5: $foo->{$bar['baz']}() ترجمة PHP7: ($foo->$bar)['baz']() الحالة 4: Foo::$bar['baz']()‎ ترجمة PHP5: Foo::{$bar['baz']}() ترجمة PHP7: (Foo::$bar)['baz']() أنواع البيانات يوجد أنواع مختلفة من البيانات في PHP لتحقيق الأغراض المختلفة، ولا نحتاج إلى تعريف صريح لنوع البيانات المحتواة في المتغير بل تعيّنه اللغة بالاعتماد على نوع القيمة التي أُسنِدت إليه أو على النوع الذي حُوِّل إليه. هذه نظرة عامة مختصرة عن الأنواع، راجع موضوع أنواع PHP للحصول على توثيق وأمثلة مفصلة. يوجد في PHP أنواع البيانات التالية: null boolean: القيم المنطقية integer: الأعداد الصحيحة float: الأعداد العشرية string: السلاسل النصية object: الكائنات resources: الموارد array: المصفوفات NULL تُسنَد القيمة المعدومة Null إلى أي متغيّر وتمثل متغيرًا دون قيمة أو عديم القيمة. $foo = null; تُبطل null المتغير وترجع قيمة غير معرّف (undefined) أو void إذا استُدعيَ. النوع boolean: القيم المنطقية هذا أبسط نوع وله قيمتين ممكنتين فقط. $foo = true; $bar = false; يمكن أن تُستخدم القيم المنطقية للتحكم بتدفق الشيفرة. $foo = true; if ($foo) { echo "true"; } else { echo "false"; } النوع integer: الأعداد الصحيحة العدد الصحيح هو عدد سالب أو موجب، يمكن استخدامه مع أيّ أساس عددي ويختلف حجمه حسب المنصة. لا تدعم PHP الأعداد الصحيحة دون إشارة (unsigned). // عدد صحيح سالب $foo = -3; // (‎كقيمة منطقية) false أو null الصفر يمكن أن يكون $foo = 0; // عدد عشري موجب $foo = 123; // ‏عدد بالنظام الثماني = 83 بالنظام العشري $bar = 0123; // ‏عدد بالنظام الست عشري = 171 بالنظام العشري $bar = 0xAB; // عدد بالنظام الثنائي = 10 بالنظام العشري $bar = 0b1010; var_dump(0123, 0xAB, 0b1010); // int(83), int(171), int(10) النوع float: الأعداد العشرية الأعداد ذات الفاصلة العشرية التي تتمثل بالنوعين: "doubles" وهو عدد عشري مضاعف الدقة أو "floats" وهو عدد عشري عادي (دقة افتراضية). $foo = 1.23; $foo = 10.0; $bar = -INF; $bar = NAN; النوع array: المصفوفات تشبه المصفوفة لائحة قيم، وأبسط صيغة لها هي المصفوفة المُفهرسة (indexed) بأعداد صحيحة ومرتبة حسب الفهرس مع ارتباط العنصر الأول بالفهرس 0. // مصفوفة من الأعداد الصحيحة $foo = array(1, 2, 3); // وما بعدها PHP5.4 صياغة مختصرة للمصفوفة في ‎$bar = ["A", true, 123 => 5]; ‎ echo $bar[0]; // "A" echo $bar[1]; // true echo $bar[123]; // 5 echo $bar[1234]; // null يمكن أيضًا أن يكون المفتاح مختلفًا عن العدد الصحيح، في PHP كل المصفوفات خلف الكواليس هي مصفوفات ترابطية (associative) لكن عندما نذكر "مصفوفة ترابطية" (associative arrays) بشكلٍ صريح فإننا نعني أنّ أغلب المفاتيح ليست أعدادًا صحيحةً. ملاحظة: خصصت بعض لغات البرمجة أنواعًا خاصة بالمصفوفة الترابطية مثل Object أو Dictionary أو Map. $array = array(); $array["foo"] = "bar"; $array["baz"] = "quux"; $array[42] = "hello"; echo $array["foo"]; // "bar" الخرج echo $array["bar"]; // "quux" الخرج echo $array[42]; // "hello" الخرج النوع string: السلاسل النصية تشبه السلسلة النصية مصفوفة محارف. $foo = "bar"; يمكن فهرسة السلسلة النصية لتُرجع محارفها الإفرادية تمامًا مثل المصفوفة: $foo = "bar"; echo $foo[0]; // الحرف الأول من السلسلة النصية 'b'‎ يطبع النوع object: الكائنات الكائن هو نسخة من الصنف، يمكن الوصول إلى متغيراته وتوابعه بالعامل ‎->‎. // المعرّف مسبقًا صنف فارغ stdClass إنشاء كائن جديد من الصنف $foo = new stdClass(); $foo->bar = "baz"; echo $foo->bar; // "baz" الخرج // أو يمكننا تحويل المصفوفة إلى كائن $quux = (object) ["foo" => "bar"]; echo $quux->foo; // "bar" الخرج النوع resources: الموارد تحمل الموارد مقابض (handles) لفتح الملفات، اتصالات قاعدة البيانات، مجاري الدخل والخرج، مناطق الصورة وما يشبه ذلك (كما ذُكر في المرجع). //الدالة التي تفتح ملفًا على القرص على أنّه مورد fopen() $fp = fopen('file.ext', 'r'); var_dump($fp); // resource(2) of type (stream) نستخدم الدالة gettype()‎ للحصول على نوع المتغير على شكل سلسلة نصية. echo gettype(1); // "integer" الخرج echo gettype(true); // "boolean" التلميح عن النوع ميزة التلميح عن نوع الأصناف والواجهات أُضيفت ميزة التلميح عن نوع (Type hinting) الأصناف والواجهات في الإصدار PHP 5. التلميح عن نوع الصنف <?php class Student { public $name = 'Chris'; } class School { public $name = 'University of Edinburgh'; } function enroll(Student $student, School $school) { echo $student->name . ' is being enrolled at ' . $school->name; } $student = new Student(); $school = new School(); enroll($student, $school); خرج السكربت السابق: Chris is being enrolled at University of Edinburgh التلميح عن نوع الواجهة <?php interface Enrollable {}; interface Attendable {}; class Chris implements Enrollable { public $name = 'Chris'; } class UniversityOfEdinburgh implements Attendable { public $name = 'University of Edinburgh'; } function enroll(Enrollable $enrollee, Attendable $premises) { echo $enrollee->name . ' is being enrolled at ' . $premises->name; } $chris = new Chris(); $edinburgh = new UniversityOfEdinburgh(); enroll($chris, $edinburgh); خرج الشيفرة السابقة هو نفس خرج المثال الأول: Chris is being enrolled at University of Edinburgh التلميح باستخدام الكلمة المفتاحية self يمكن استخدام الكلمة المفتاحية self كتلميح عن النوع للإشارة إلى أنّ القيمة يجب أن تكون نسخة من الصنف الذي يصرّح عن التابع. التلميح عن النوع للأنواع العددية والمصفوفات والأنواع القابلة للاستدعاء أُضيفَ دعم التلميح عن نوع معاملات المصفوفة في الإصدار PHP 5.1 (والقيم المعادة بعد الإصدار PHP 7.1) باستخدام الكلمة المفتاحية array، تعدّ أيّة مصفوفة مهما كان نوعها وأبعادها وحتى المصفوفات الصفرية قيمةً صحيحةً. أُضيف دعم التلميح عن النوع القابل للاستدعاء (callable) في الإصدار PHP 5.4، وتعدّ أي قيمة قابلة للاستدعاء صحيحةً للمعاملات والقيم المعادة الملمّح عنها أنّها قابلة للاستدعاء، مثل كائنات المُغلِّف والسلاسل النصية التي تعبّر عن اسم الدالة والمصفوفة ‎array(class_name|object,method_name)‎. إذا حدث خطأ كتابي في اسم الدالة وأصبحت غير قابلة للاستدعاء تظهر رسالة خطأ أقل وضوحًا: Fatal error: Uncaught TypeError: Argument 1 passed to foo() must be of the type callable, string/array given function foo(callable $c) {} // شيفرة صحيحة foo("count"); // شيفرة صحيحة foo("Phar::running"); // شيفرة صحيحة foo(["Phar", "running"); // شيفرة صحيحة foo([new ReflectionClass("stdClass"), "getName"]); // شيفرة صحيحة foo(function() {}); foo("no_such_function"); // callable expected, string given يمكن أيضًا تمرير التوابع غير الساكنة على أنّها قابلة للاستدعاء بصيغة ثابتة مما يؤدي إلى ظهور تحذير مُهمَل وخطأ من المستوى E_STRICT في كل من PHP 7 وPHP 5 على الترتيب. يجب أن ننتبه أيضًا إلى مرئية التابع، إذا كان سياق التابع مع المعامل القابل للاستدعاء لا يمكنهما الوصول إلى المعامل القابل للاستدعاء سينتهي الأمر كما لو أنّ التابع غير موجود. class Foo{ private static function f(){ echo "Good" . PHP_EOL; } public static function r(callable $c){ $c(); } } function r(callable $c){} Foo::r(["Foo", "f"]); r(["Foo", "f"]); الخرج: Fatal error: Uncaught TypeError: Argument 1 passed to r() must be callable, array given أُضيف دعم التلميح عن الأنواع العددية في PHP 7، أي حصلنا على دعم التلميح عن booleans (القيم المنطقية) وintegers (الأعداد الصحيحة) وfloats (الأعداد العشرية) وstrings (السلاسل النصية). <?php function add(int $a, int $b) { return $a + $b; } var_dump(add(1, 2)); // "int(3)" تحاول PHP بشكلٍ افتراضي تحويل أي وسيط مُعطى ليطابق تلميحه عن النوع، إذا غيّرنا الاستدعاء للشيفرة add(1.5, 2)‎ سنحصل على نفس الخرج تمامًا لأن PHP تحول العدد العشري 1.5 إلى عدد صحيح، لنوقف هذا السلوك نضيف الشيفرة التالية declare(strict_types=1);‎ في بداية كل ملف PHP مصدري يتطلب ذلك، مثال: <?php declare(strict_types=1); function add(int $a, int $b) { return $a + $b; } var_dump(add(1.5, 2)); تنتج الشيفرة السابقة خطأً فادحًا: Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, float given استثناء: أنواع خاصة قد تعيد بعض دوال PHP قيمة من النوع resource، وبما أنّ هذه ليست قيمة عددية إنّما نوع خاص فمن غير الممكن التلميح عن نوعها، فمثلًا تعيد كل من الدالتين curl_init()‎ وfopen()‎ موردًا وهذين الموردين غير متوافقين بالطبع لذا ترمي PHP 7 خطأ من الصنف TypeError عند كتابة تلميح عن النوع مورد بشكلٍ صريح: TypeError: Argument 1 passed to sample() must be an instance of resource, resource given التلميح عن النوع Nullable المعاملات أُضيف التلميح عن النوع Nullable في الإصدار PHP 7.1 باستخدام العامل ? قبل التلميح عن النوع. function f(?string $a) {} function g(string $a) {} // شيفرة صحيحة f(null); g(null); // TypeError: Argument 1 passed to g() must be of the type string, null given قبل الإصدار PHP 7.1 إذا كان للمعامل تصريح عن النوع يجب أن يصرّح عن قيمة افتراضية null لقبول هذه القيمة. function f(string $a = null) {} function g(string $a) {} // شيفرة صحيحة f(null); g(null); // TypeError: Argument 1 passed to g() must be of the type string, null given القيم المعادة في الإصدار PHP 7.0 لا يمكن للدوال مع قيمة معادة أن تعيد قيمة فارغة null، وبدءًا من الإصدار PHP 7.1 يمكن للدوال التصريح عن تلميح نوع القيمة المعادة أنّها nullable وعندها يجب أن تعيد الدالة قيمة فارغة null وليس void (أي من غير الممكن عدم كتابة تعليمة return أو كتابتها فارغة). function f() : ?string { return null; } function g() : ?string {} function h() : ?string {} // شيفرة صحيحة f(); g(); // TypeError: Return value of g() must be of the type string or null, none returned h(); // TypeError: Return value of h() must be of the type string or null, none returned التصريح عن نوع الكائنات العامة بما أنّ كائنات PHP لا ترث من أي صنف أساسي (بما في ذلك الصنف stdClass) فلا يوجد دعم للتصريح عن نوع الكائنات العامة، مثلًا الشيفرة التالية غير صحيحة: <?php function doSomething(object $obj) { return $obj; } class ClassOne {} class ClassTwo {} $classOne= new ClassOne(); $classTwo= new ClassTwo(); doSomething($classOne); doSomething($classTwo); وسترمي الخطأ الفادح: Fatal error: Uncaught TypeError: Argument 1 passed to doSomething() must be an instance of object, instance of OperationOne given الحل البديل لذلك هو التصريح عن واجهة لا تعرّف أي توابع ثم نجعل كل الكائنات تنفّذ هذه الواجهة. <?php interface Object {} function doSomething(Object $obj) { return $obj; } class ClassOne implements Object {} class ClassTwo implements Object {} $classOne = new ClassOne(); $classTwo = new ClassTwo(); doSomething($classOne); doSomething($classTwo); التصريح عن نوع دون قيمة معادة (Void) أُضيف نوع القيمة المعادة void في الإصدار PHP 7.1، على الرغم من أنّ PHP ليس فيها قيمة void فعلية إلا أنّه من المعروف عمومًا في عالم البرمجة أنّ الدالة التي لا تعيد شيئًا فإنّ القيمة المعادة هي void. يجب ألّا نخلط بين القيمة المعادة void والقيمة المعادة الفارغة null إذ أنّ القيمة null هي قيمة يمكن إعادتها. function lacks_return(): void { // شيفرة صحيحة } لاحظ أنّه إذا صرّحت عن القيمة المعادة أنّها void لا يمكنك أن تعيد أي قيم وإلا ستحصل على خطأ فادح: function should_return_nothing(): void { return null; } // Fatal error: A void function must not return a value إلا أنّه يمكن استخدام return للخروج من الدالة: function returns_nothing(): void { // شيفرة صحيحة return; } تحويل الأنواع ومشاكل الموازنة غير الصارمة ما هو تحويل النوع (Type Juggling)؟ تعدّ PHP لغة متهاونة في تحديد النوع (loosely typed) أي أنّها لا تتطلب أن يكون المعامَلات (operands) في تعبير ما من نفس النوع (أو من أنواع متوافقة)، فمثلًا يمكنك أن تتوقع أنّ إضافة رقم إلى سلسلة نصية ستعمل بشكلٍ صحيح. var_dump ("This is example number " . 1); الخرج: string(24) "This is example number 1" تحقق PHP هذا عن طريق التحويل بين أنواع المتغيرات غير المتوافقة بشكلٍ تلقائي مما يسمح بإجراء العملية المطلوبة، ستحوّل PHP في مثالنا السابق العدد 1 إلى سلسلة نصية وتصبح قابلة للدمج مع السلسلة النصية التي تسبقها، هذا ما يسمى التحويل بين الأنواع (type juggling) وهذه ميزة قوية جدًا في PHP لكنها في نفس الوقت يمكن أن تؤدي إلى الكثير من الحيرة إذا لم تنتبه لها ومن الممكن أن تؤدي إلى مشاكل في الحماية أيضًا. بفرض لدينا الشيفرة التالية: if (1 == $variable) { // القيام بشيء ما } يبدو أنّ المبرمج يقصد من الشيفرة السابقة التأكّد من أنّ قيمة المتغير هي 1،لكن ما الذي يحدث إذا كانت قيمته "1‎ and a half"، قد يفاجئك الجواب: $variable = "1 and a half"; var_dump (1 == $variable); النتيجة هي: bool(true) يحدث هذا لأنّ PHP تدرك أنّ السلسلة النصية "1‎ and a half" ليست عددًا صحيحًا، لكنها تحتاج للموازنة مع العدد الصحيح 1، فتقوم بمحاولة تحويل المتغير إلى عدد صحيح، وذلك بأخذ كل المحارف في بداية السلسلة النصية التي يمكن تحويلها إلى عدد صحيح وتحوّلها ثم تتوقف عندما تواجه محرفًا لا يمكنها التعامل معه كرقم، لذا ستُحوَّل "1‎ and a half" إلى 1. يوضّح لنا المثال السابق هذه القضية، ستغطي الأمثلة القليلة التالية بعض الحالات التي قد يؤدي تحويل النوع فيها إلى أخطاء في التطبيقات الحقيقية. القراءة من ملف نحتاج عند القراءة من ملف أن نكون قادرين على معرفة متى نصل إلى نهايته، يمكننا استخدام الدالة fgets()‎ كشرط في حلقة إذ أنّها تُرجع القيمة false عند نهاية الملف، لكن ذلك قد يسبب قطع الحلقة في وقت سابق لأوانه إذا كانت البيانات المُرجعَة من القراءة الأخيرة تُقيَّم على أنّها القيمة المنطقية false. $handle = fopen ("/path/to/my/file", "r"); if ($handle === false) { throw new Exception ("Failed to open file for reading"); } while ($data = fgets($handle)) { echo ("Current file line is $data\n"); } fclose ($handle); إذا كان الملف المقروء يحتوي سطرًا فارغًا فإنّ حلقة while ستنتهي عند تلك النقطة لأنَّ السلسلة النصية الفارغة تُقيَّم على أنّها القيمة المنطقية false. يمكننا بدلًا من ذلك التحقق من القيمة المنطقية false بشكلٍ صريح باستخدام عامل المساواة الصارمة: while (($data = fgets($handle)) !== false) { echo ("Current file line is $data\n"); } لاحظ أنّ هذا المثال نظري أما في التطبيقات العملية نستخدم الحلقة التالية: while (!feof($handle)) { $data = fgets($handle); echo ("Current file line is $data\n"); } أو استبدال كل ذلك بالشيفرة: $filedata = file("/path/to/my/file"); foreach ($filedata as $data) { echo ("Current file line is $data\n"); } مفاجآت Switch تستخدم تعليمات switch الموازنة غير الصارمة لتحديد التطابقات، يمكن أن يؤدي هذا إلى بعض المفاجآت السيئة، ليكن لدينا الشيفرة التالية مثلًا: switch ($name) { case 'input 1': $mode = 'output_1'; break; case 'input 2': $mode = 'output_2'; break; default: $mode = 'unknown'; break; } الشيفرة السابقة بسيطة جدًا وتعمل كما هو متوقع عندما يكون المتغير ‎$name سلسلة نصية وإلا ستسبب بعض المشاكل، فمثلًا إذا كان المتغير عددًا صحيحًا 0 سيتحول النوع عند الموازنة، لكن يتم التعامل مع القيمة الحرفية المُحوّلة في تعليمة case وليس في شرط تعليمة switch، تُحوَّل السلسلة النصية "input 1" إلى العدد الصحيح 0 والذي يُطابق القيمة المدخلة للعدد الصحيح 0. النتيجة النهائية إذا كانت قيمة العدد الصحيح 0 هي تنفيذ الحالة الأولى دائمًا. يوجد بعض الحلول لهذه المشكلة: التحويل الصريح بين الأنواع يمكن تحويل نوع القيمة إلى سلسلة نصية قبل الموازنة: switch ((string)$name) { ... } أو يمكننا استخدام دالة تُرجع سلسلة نصية كما في الشيفرة التالية: switch (strval($name)) { ... } تضمن كِلا الطريقتين أن تكون القيمة من نفس نوع القيمة في تعليمات case. تجنب switch يوفر لنا استخدام تعليمة if التحكم في كيفية حدوث الموازنة مما يسمح لنا باستخدام عوامل الموازنة الصارمة: if ($name === "input 1") { $mode = "output_1"; } elseif ($name === "input 2") { $mode = "output_2"; } else { $mode = "unknown"; } الكتابة الصارمة (Strict typing) يمكن التخفيف من بعض الآثار الضارة لتحويل النوع بدءًا من الإصدار PHP 7.0 باستخدام الكتابة الصارمة، تفرض PHP التصريح عن نوع المعامل والقيمة المعادة برمي استثناء TypeError من خلال تضمين تعليمة declare في السطر الأول من الملف. declare(strict_types=1); فمثلًا في الشيفرة التالية استخدام تعريفات نوع المعامل سيرمي استثناءً قابلًا للالتقاط من النوع TypeError عند التنفيذ: <?php declare(strict_types=1); function sum(int $a, int $b) { return $a + $b; } echo sum("1", 2); وبالتالي تستخدم هذه الشيفرة التصريح عن نوع القيمة المُعادة وسترمي استثناءً إذا كانت القيمة من أي نوع غير العدد الصحيح: <?php declare(strict_types=1); function returner($a): int { return $a; } returner("this is a string"); أفضل ممارسات المتغيرات العامة (Global variable) سنوضح مشكلةً مع هذه الشيفرة الزائفة: function foo() { global $bob; $bob->doSomething(); } سؤالك الأول هنا بالطبع هو: مين أين أتى المتغير ‎$bob؟ هل شعرت بالحيرة؟ إذن قد عرفت لماذا تسبب المتغيرات العامة حيرةً وتعد ممارسةً سيئةً. إذا كان هذا برنامجًا حقيقيًا فستكون خطوتك التالية تتبع كل المتغيرات ‎$bob وآمل أن تجد المتغير الصحيح (من السيء أن يكون اُستعمل بشكلٍ متكرر)، والأسوأ أن يكون شخصًا ما قد عرّفه (أو نسيت وأعدت استخدام هذا المتغير) وعندها ستتوقف شيفرتك، ففي مثال الشيفرة السابق، سيسبب وجود كائن خاطئ أو عدم وجود كائن على الإطلاق خطأً فادحًا (fatal error). جميع برامج PHP الفعلية تستخدم شيفرة مثل include('file.php');‎ لذا كلما ضُمّنت المزيد من الملفات فإنّ مهمتك في إصلاح الشيفرة تصحيح أصعب، وهذا أيضًا يجعل مهمة اختبار التطبيق أصعب، افترض أنّك استخدمت متغيرًا عامًا ليحمل اتصالك بقاعدة البيانات: $dbConnector = new DBConnector(...); function doSomething() { global $dbConnector; $dbConnector->execute("..."); } يجب تجاوز المتغير العام ‎$dbConnector‎ لكتابة وحدة اختبار لهذه الدالة ونفّذ الاختبار ثمّ أعد تعيينه بقيمته الأصلية وهذا شيء معرّض للخطأ بشكلٍ كبير: /** * @اختبار */ function testSomething() { global $dbConnector; // قُم بنسخ احتياطي $bkp = $dbConnector; // تجاوز $dbConnector = Mock::create('DBConnector'); assertTrue(foo()); // أعد التخزين $dbConnector = $bkp; } كيف نتجنب المتغيرات العامة؟ تُدعى أفضل طريقة لتجنب المتغيرات العامة فلسفيًا "إضافة الاعتماديّات" (Dependency Injection)، تسمح لنا هذه الطريقة بإضافة الأدوات التي نحتاجها داخل الدالة أو الصنف. function foo(\Bar $bob) { $bob->doSomething(); } هذه الطريقة أسهل للفهم والإصلاح، إذ ليس هناك تخمين أين أعُدَّ المتغير ‎$bob لأنّ المستدعي مسؤول عن معرفة ذلك (يمرر لنا ما نحتاج معرفته). والأفضل من ذلك أنّه يمكننا استخدام تصريحات النوع لتقييد ما يتم تمريره. لذا نعلم أنّ المتغير ‎$bob إما نسخةً من الصنف Bar أو من الصنف الابن له وهذا يعني أنّه يمكننا استخدام توابع هذا الصنف، نستطيع الآن بالاشتراك مع محمّل آلي قياسي (متوفر من الإصدار PHP 5.3) تتبّع أين عُرِّف Bar. يشتمل الإصدار PHP 7.0 وما بعده على تصريحات نوع موسّعة حيث يمكنك أن تستعمل الأنواع العددية (مثل int أو string). المتغيرات ذات النطاق العام العالي (Superglobal variables) المتغيرات ذات النطاق العام العالي في PHP هي متغيرات معرّفة مسبقًا ومتوفرة دائمًا ويمكن الوصول إليها من أي نطاق في أي مكان من الملف البرمجي (ضمن الدوال أو الأصناف أو الملفات) دون الحاجة لأن تعرّفها بالكلمة المفتاحية global. المتغيرات ذات النطاق العام العالي في PHP هي: ‎$_GLOBALS ‎$_SERVER ‎$_REQUEST ‎$_POST ‎$_GET ‎$_FILES ‎$_ENV ‎$_COOKIE ‎$_SESSION القيم الافتراضية للمتغيرات غير المُهيَّأة (uninitialized) تهيئة المتغيرات في PHP ليس أمرًا ضروريًا لكنه يعدّ ممارسةً جيّدةً، للمتغيرات غير المُهيَّأة قيمةً افتراضيةً لنوعها وفقًا للسياق الذي اُستخدموا به. لنرى ما هي قيمة متغيِّر لم تُسنَد له قيمة وليس مرجعًا: var_dump($unset_var); // ‏NULL الخرج سنهيئ المتغير بالأنواع التالية: boolean: القيم المنطقية echo($unset_bool ? "true\n" : "false\n"); // 'false' الخرج string: السلاسل النصية $unset_str .= 'abc'; var_dump($unset_str); // 'string(3) "abc"' الخرج integer: الأعداد الصحيحة $unset_int += 25; // 0 + 25 => 25 var_dump($unset_int); // 'int(25)' الخرج Float/double: الأعداد العشرية $unset_float += 1.25; var_dump($unset_float); // 'float(1.25)' array: المصفوفات $unset_arr[3] = "def"; var_dump($unset_arr); // array(1) { [3]=> string(3) "def" } object: الكائنات $unset_obj->foo = 'bar'; var_dump($unset_obj); // object(stdClass)#1 (1) { ["foo"]=> string(3) "bar" } يعدّ الاعتماد على القيمة الافتراضية لمتغير غير مُهيّأ مشكلةً إذا ضُمِّن ملف داخل ملف آخر يستعمل نفس اسم المتغير. توكيد قيمة المتغير وعامل التطابق تملك قيمة المتغير في PHP "توكيدًا" مرتبطًا حتى أنّ القيم غير المنطقية ستساوي true أو false، وهذا سيجعل من الممكن استخدام أي متغيّر في كتلة الشيفرة الشرطية، مثل: if ($var == true) { /* مثال واضح */ } if ($var) { /* ضمنيًأ $var == true */ } بعض القواعد الأساسية لأنواع مختلفة من قيم المتغير: السلاسل النصية ذات الطول غير الصفري تساوي true متضمنةً السلاسل التي تحوي فراغًا فقط مثل ' '. السلاسل الفارغة '' تساوي false. $var = ''; $var_is_true = ($var == true); // false $var_is_false = ($var == false); // true $var = ' '; $var_is_true = ($var == true); // true $var_is_false = ($var == false); // false الأعداد الصحيحة تساوي true إذا كانت غير صفرية، أما الصفر يساوي false. $var = -1; $var_is_true = ($var == true); // true $var = 99; $var_is_true = ($var == true); // true $var = 0; $var_is_true = ($var == true); // false القيمة null تساوي false. $var = null; $var_is_true = ($var == true); // false $var_is_false = ($var == false); // true السلاسل الفارغة '' والسلسلة الصفرية '0' تساوي false. $var = ''; $var_is_true = ($var == true); // false $var_is_false = ($var == false); // true $var = '0'; $var_is_true = ($var == true); // false $var_is_false = ($var == false); // true الأعداد العشرية تساوي true إذا كانت غير صفرية وتساوي false إذا كانت صفرية. القيمة NAN (‏Not-A-Number) تساوي true، أي نتيجة NAN == true هي true لأنّ NAN هي قيمة عشرية غير صفرية. القيم الصفرية تتضمن 0- و0+ كما عرّفها معيار IEEE 754. لا تميّز PHP بين 0- و0+ في الأعداد العشرية ذات الدقة المضاعفة أي نتيجة floatval('0') == floatval('-0')‎ هي true. إنّ floatval('0') === floatval('-0')‎. بالإضافة إلى ذلك floatval('0') == false وfloatval('-0') == false. $var = NAN; $var_is_true = ($var == true); // true $var_is_false = ($var == false); // false $var = floatval('-0'); $var_is_true = ($var == true); // false $var_is_false = ($var == false); // true $var = floatval('0') == floatval('-0'); $var_is_true = ($var == true); // false $var_is_false = ($var == false); // true عامل التطابق يوجد عامل التطابق === في التوثيق الرسمي لعوامل الموازنة في لغة PHP، يمكن أن يُستخدم هذا العامل للتحقق إذا كان المتغير مطابقًا لقيمة مرجعية. $var = null; $var_is_null = $var === null; // true $var_is_true = $var === true; // false $var_is_false = $var === false; // false ويوجد عامل مقابل له وهو عامل عدم التطابق ==!: $var = null; $var_is_null = $var !== null; // false $var_is_true = $var !== true; // true $var_is_false = $var !== false; // true يمكن أن يُستخدم عامل التطابق بدلًا عن بعض دوال اللغة مثل دالة is_null()‎. حالة استخدام مع دالة strpos()‎ تحدد الدالة strpos($haystack, $needle)‎ موقع أول ظهور لمحارف المعامل ‎$needle‎ ضمن السلسلة النصية ‎$haystack أو عدم ظهوره أبدًا في السلسلة وهي دالة حساسة لحالة الأحرف، يمكنك استخدام الدالة stripos($haystack, $needle)‎ إذا أردت دالةً تؤدي نفس العمل ولكن غير حساسة لحالة الأحرف. تتضمن الدالتان strpos وstripos معاملًا ثالثًا offset (int)‎ وهو اختياري يحدد إزاحة المؤشر قبل أن يبدأ البحث وعلى عكس دالتي strrpos وstrripos لا يمكن أن يكون هذا العدد سالبًا. يمكن أن تُرجع الدالة: 0 إذا وُجِد ‎$needle‎ في بداية السلسلة النصية ‎$haystack. عددًا صحيحًا غير صفريًا يحدد موقع وجود ‎$needle‎ إذا وُجد في مكان غير بداية السلسلة النصية ‎$haystack. القيمة false إذا لم يوجد ‎$n‎eedle في السلسلة النصية ‎$haystack. إنّ كلّ من 0 وfalse لهما توكيد false في PHP ولكنهما يمثلان حالات مختلفة بالنسبة لدالة strpos لذا من المهم أن نميز بينهما باستخدام عامل التطابق === لنميز بين القيمة false والقيمة 0 التي تساوي false. $idx = substr($haystack, $needle); if ($idx === false) { // $haystack ضمن $needle‎‎ ‏‎‏منطقية عندما لا يوجد المعامل for تعليمة } else { // $haystack ضمن $needle‎‎ ‏‎‏منطقية عندما يوجد المعامل for تعليمة } يمكن استخدام عامل عدم التطابق بدلًا من ذلك: $idx = substr($haystack, $needle); if ($idx !== false) { // $haystack ضمن $needle‎‎ ‏‎‏منطقية عندما يوجد المعامل for تعليمة } else { // $haystack ضمن $needle‎‎ ‏‎‏منطقية عندما لا يوجد المعامل for تعليمة } نطاق المتغيرات يشير نطاق المتغير (Variable scope) إلى مناطق الشيفرة التي يمكن منها الوصول للمتغير، وهذا ما يُسمّى أيضًا بالمرئية (visibility)، يُعرّف نطاق كتل الشيفرة في PHP بالدوال، الأصناف، ونطاق عام متاح عبر التطبيق. المتغيرات ذات النطاق العام العالي (Superglobal variables) إنّ المتغيرات ذات النطاق العام العالي (Superglobal variables) معرّفة في PHP ويمكن استخدامها في أي مكان بدون الكلمة المفتاحية global. <?php function getPostValue($key, $default = NULL) { // ‎متغير ذو نطاق عام عالي‎‌ $_POST // ‎'global $_POST;'‎ لذا يمكن استخدامه دون ذكر if (isset($_POST[$key])) { return $_POST[$key]; } return $default; } // $_POST['username'] يسترجع echo getPostValue('username'); // $default ويسند سلسلة فارغة للمتغير $_POST['email'] يسترجع echo getPostValue('email', ''); الخاصيّات الساكنة والمتغيرات تعدّ خاصيّات الصنف الساكنة المعرّفة مع المرئية public وظيفيًا نفس المتغيرات العامة، يمكن الوصول إليها من أيّ مكان في الصنف الذي عُرّفت ضمنه. class SomeClass { public static int $counter = 0; } // من أي مكان $counter يمكن قراءة/كتابة المتغير الساكن // ولا يتطلب ذلك إنشاء نسخة من الصنف SomeClass::$counter += 1; يمكن أن تعرّف الدوال أيضًا متغيرات ساكنة داخل نطاقها الخاص، تبقى هذه المتغيرات ثابتةً عند استدعاءات الدالة المتعددة على عكس المتغيرات العادية المعرّفة ضمن نطاق الدالة، ويمكن أن يكون هذا أسلوبًا سهلًا وبسيطًا جدًا لتحقيق نمط التصميم المتفرّد (Singleton design pattern). class Singleton { public static function getInstance() { // عندما تنتهي الدالة $instance لا يُحذف المتغير الساكن static $instance; // (1) if (!$instance) { // (2) $instance = new Singleton(); } return $instance; } } $instance1 = Singleton::getInstance(); $instance2 = Singleton::getInstance(); // (3) var_dump($instance1 === $instance2); في الموضع (1) لن يدخل الاستدعاء الثاني إلى تعليمة if لأنّ نسخة من الصنف Singleton خُزِّنت في المتغير ‎$instance وتبقى ثابتةً عبر الاستدعاءات المتعددة. سيصل الاستدعاء الأول لهذه الدالة إلى الموضع (2) لأنّ المتغير ‎$instance صُرِّح عنه ولم يُهيَّئ. بموازنة الكائنات في الموضع (3) باستخدام العامل '===' الذي يتحقق من تطابق الكائنات ستُطبع true لأنّ المتغير الساكن ‎$instance يبقى ثابتًا عبر الاستدعاءات المتعددة للتابع getInstance()‎. المتغيرات العامة التي يعرّفها المستخدم النطاق العام هو النطاق الموجود خارج أيّ دالة أو صنف، ويبقى النطاق نفسه عندما يتضمن النص البرمجي المكتوب بلغة PHP نصًا برمجيًا آخر (باستخدام include أو require)، إذا ضُمِّن الملف البرمجي خارج الدوال والأصناف فإنّ متغيراته العامة تكون أيضًا بالنطاق العام للملف المُضمِّن أمّا إذا ضُمِّن داخل دالة فإنّ متغيراته تبقى ضمن نطاق الدالة. يمكن استخدام الكلمة المفتاحية global لينشئ المستخدم متغيرات عامة ضمن نطاق الدالة أو تابع الصنف. <?php $amount_of_log_calls = 0; function log_message($message) { // الوصول للمتغير العام من نطاق الدالة يتطلب هذه التعليمة الصريحة global $amount_of_log_calls; // هذا التعديل على المتغير العام ثابت $amount_of_log_calls += 1; echo $message; } // عندما تكون في النطاق العام يمكن استخدام المتغيرات العامة العادية // 'global $variable;' بدون التعليمة الصريحة echo $amount_of_log_calls; // 0 log_message("First log message!"); echo $amount_of_log_calls; // 1 log_message("Second log message!"); echo $amount_of_log_calls; // 2 الطريقة الثانية للوصول للمتغيرات من النطاق العام هي باستخدام مصفوفة PHP المعرّفة ‎$GLOBALS، وهي مصفوفة ترابطية (associative) فيها المفتاح هو اسم المتغير العام وقيمة عنصر المصفوفة هي محتوى المتغير وهي مصفوفة ذات نطاق عام عالي لذا يمكن الوصول إليها من أي نطاق. هذا يعني أنه يمكن إعادة كتابة الدالة log_message()‎ بالشكل التالي: function log_message($message) { // $amount_of_log_calls الوصول إلى المتغير العام // 'global $GLOBALS;' لا يحتاج إلى التصريح $GLOBALS باستخدام المصفوفة // لأنها ذات نطاق عام عالي $GLOBALS['amount_of_log_calls'] += 1; echo $messsage; قد تتساءل لماذا نستخدم المصفوفة ‎ $GLOBALS‎‎بينما يمكننا استخدام الكلمة المفتاحية global للحصول على قيمة المتغير العام؟ السبب الرئيسي هو أنّ استخدام الكلمة المفتاحية global ستجلب المتغير إلى النطاق وعندها لا يمكنك إعادة استخدام نفس اسم المتغير في النطاق المحلي (local scope). تعريف الثوابت تُنشأ الثوابت بالتعليمة const مع الدالة define، ومن الشائع استخدام الأحرف الكبيرة في تسميتها. تعريف ثابت باستخدام القيم الصريحة // عدد عشري const PI = 3.14; // قيمة منطقية define("EARTH_IS_FLAT", false); // null const "UNKNOWN" = null; // سلسلة نصية define("APP_ENV", "dev"); // عدد صحيح باستخدام تعبير عددي const MAX_SESSION_TIME = 60 * 60; // مصفوفة const APP_LANGUAGES = ["de", "en"]; define("BETTER_APP_LANGUAGES", ["lu", "de"]); تعريف ثابت باستخدام ثابت آخر يمكنك الاعتماد على ثابت موجود لديك لتسمية ثابت جديد: const TAU = PI * 2; define("EARTH_IS_ROUND", !EARTH_IS_FLAT); define("MORE_UNKNOWN", UNKNOWN); // يمكن أيضًا استخدام توابع تعديل السلاسل النصية // const المثال التالي (استدعاء دالة) لا يعمل مع define("APP_ENV_UPPERCASE", strtoupper(APP_ENV)); // لأن الوقت ليس تعبير عددي ثابت (fatal error) التعليمة التالية ترمي خطأً فادحًا // const TIME = time(); define("MAX_SESSION_TIME_IN_MINUTES", MAX_SESSION_TIME / 60); // تعديل المصفوفات const APP_FUTURE_LANGUAGES = [-1 => "es"] + APP_LANGUAGES; define("APP_BETTER_FUTURE_LANGUAGES", array_merge(["fr"], APP_BETTER_LANGUAGES)); الثوابت المحجوزة يوجد بعض أسماء الثوابت المحجوزة في PHP والتي لا يمكن إعادة تعريفها، إليك بعض الأمثلة: // ثابت محلي define("true", false); // ثابت محلي define("false", true); // محمّلة curl سيحدث خطأ إذا كانت الإضافة define("CURLOPT_AUTOREFERER", "something"); وسيظهر لك الخطأ Constant ... already defined in ...‎ تعريف الثوابت الشرطي إذا كان لديك عدة ملفات تعرّف فيها نفس المتغير (مثلًا في ملف الإعدادات الرئيسي وملف الإعدادات المحلي) عندها فإنّ الصيغة التالية ستساعدك لتجنّب التضارب: // إذا لم يُعرَّف من قبل PI عرّف الثابت defined("PI") || define("PI", 3.1415); const مقابل define define تعبير يُستدعى أثناء التنفيذ (runtime expression) أما const يُستدعى عند التفسير، لذا يسمح define بالقيم الديناميكية (مثل استدعاءات الدالة، المتغيرات…) وبالأسماء الديناميكية والتعريف الشرطي، لكن يُعرَّف عادةً نسبةً إلى فضاء اسم الجذر. بينما const هو ثابت (كما هو مسموح في العمليات مع الثوابت الأخرى والأعداد أو المصفوفات ومجموعة محدودة منها فقط ما يسمى التعابير العددية الثابتة أي العوامل الحسابية والمنطقية والموازنة والمصفوفة) ولكن يُسبق تلقائيًا بفضاء الاسم الفعال حاليًا. وتدعم const الثوابت الأخرى والأعداد كالقيم ولا توجد عمليات. ثوابت الصنف تُعرّف الثوابت داخل الأصناف باستخدام الكلمة المفتاحية const. class Foo { const BAR_TYPE = "bar"; // self:: مرجع من داخل الصنف باستخدام public function myMethod() { return self::BAR_TYPE; } } // <اسم الصنف‎>:: مرجع من خارج الصنف باستخدام‎‎ echo Foo::BAR_TYPE; يعدّ هذا مفيدًا لتخزين أنواع من العناصر: <?php class Logger { const LEVEL_INFO = 1; const LEVEL_WARNING = 2; const LEVEL_ERROR = 3; // يمكن أن نسند للثابت قيمةً افتراضيةً public function log($message, $level = self::LEVEL_INFO) { echo "Message level " . $level . ": " . $message; } } $logger = new Logger(); // استخدام القيمة الافتراضية $logger->log("Info"); // استخدام المتغير $logger->log("Warning", $logger::LEVEL_WARNING); // استخدام الصنف $logger->log("Error", Logger::LEVEL_ERROR); // using class التحقق من تعريف الثابت تحقق بسيط نستخدم الدالة defined للتحقق من تعريف ثابت ما، لا تهتم هذه الدالة لقيمة الثابت إنما فقط هل الثابت موجود أم لا حتى إذا كانت قيمته null أو false فسترجع true. <?php define("GOOD", false); if (defined("GOOD")) { // "GOOD is defined" يطبع السطر التالي print "GOOD is defined" ; if (GOOD) { // false هي GOOD لا يطبع السطر التالي شيئًا بما أنّ print "GOOD is true" ; } } if (!defined("AWESOME")) { // لذا يجب تعريفه AWESOME لم يُعرّف الثابت define("AWESOME", true); } لاحظ أنّ الثوابت تصبح مرئيةً في شيفرتك في السطر التالي مباشرةً لتعريفها: <?php if (defined("GOOD")) { // لم يُعرّف بعد GOOD لا يطبع السطر التالي شيئًا بما أنّ print "GOOD is defined"; } define("GOOD", false); if (defined("GOOD")) { // "GOOD is defined" يطبع السطر التالي print "GOOD is defined"; } الحصول على كل الثوابت المعرفة نستخدم الدالة get_defined_constants لمعرفة كل الثوابت المعرّفة حتى تلك المُنشأة من قِبل PHP: <?php $constants = get_defined_constants(); var_dump($constants); // ستظهر لك قائمة كبيرة يمكنك استدعاء الدالة في بداية ونهاية السكربت (عادةً بعد استدعاء bootstrap) لتحصل على الثوابت المعرّفة في تطبيقك فقط: <?php $constants = get_defined_constants(); define("HELLO", "hello"); define("WORLD", "world"); $new_constants = get_defined_constants(); $myconstants = array_diff_assoc($new_constants, $constants); var_export($myconstants); /* الخرج array ( 'HELLO' => 'hello', 'WORLD' => 'world', ) */ إنّ هذه الدالة مفيدة أحيانًا للتنقيح. استخدام الثوابت تستخدم الثابت ببساطة بواسطة اسمه: if (EARTH_IS_FLAT) { print "Earth is flat"; } print APP_ENV_UPPERCASE; ويمكنك استخدام الدالة constant إذا لم تكن تعرف اسم الثابت مسبقًا، إليك الشيفرة التالية المكافئة للشيفرة السابقة: $const1 = "EARTH_IS_FLAT"; $const2 = "APP_ENV_UPPERCASE"; if (constant($const1)) { print "Earth is flat"; } print constant($const2); 5.5: المصفوفات الثابتة يمكن أن تُستخدم المصفوفات كثوابت بسيطة أو ثوابت صنف من الإصدار PHP 5.6 وما بعده: مثال على ثابت صنف class Answer { const C = [2,4]; } print Answer::C[1] . Answer::C[0]; // 42 مثال على ثابت بسيط const ANSWER = [2,4]; print ANSWER[1] . ANSWER[0]; // 42 نُقلت هذه الوظيفة إلى الدالة define منذ الإصدار PHP 7.0 من أجل الثوابت البسيطة: define('VALUES', [2, 3]); define('MY_ARRAY', [ 1, VALUES, ]); print MY_ARRAY[1][1]; // 3 الثوابت السحرية الفرق بين FUNCTION وMETHOD ترجع __FUNCTION__ اسم الدالة فقط بينما تُرجع __METHOD__ اسم الصنف مع اسم الدالة: <?php class trick { public function doit() { echo __FUNCTION__; } public function doitagain() { echo __METHOD__; } } $obj = new trick(); $obj->doit(); // doit $obj->doitagain(); // trick::doitagain الفرق بين CLASS وget_class()‎ وget_called_class()‎ يُرجع الثابت السحري __CLASS__ نفس نتيجة استدعاء الدالة get_class()‎ بدون معاملات ويُرجع كلاهما اسم الصنف حيث يُعرّف (أي حيث كتبت استدعاء الدالة/ اسم الثابت). وفي المقابل فإنّ كلًا من استدعاء الدوال get_class($this)‎ وget_called_class()‎ يُرجع اسم الصنف الفعلي الذي اُستنسخ: <?php class Definition_Class { public function say(){ echo '__CLASS__ value: ' . __CLASS__ . "\n"; echo 'get_called_class() value: ' . get_called_class() . "\n"; echo 'get_class($this) value: ' . get_class($this) . "\n"; echo 'get_class() value: ' . get_class() . "\n"; } } class Actual_Class extends Definition_Class {} $c = new Actual_Class(); $c->say(); // الخرج // __CLASS__ value: Definition_Class // get_called_class() value: Actual_Class // get_class($this) value: Actual_Class // get_class() value: Definition_Class ثوابت الملف والمجلد الملف الحالي: يمكنك الحصول على ملف PHP الحالي (مع المسار المطلق) باستخدام الثابت السحري __FILE__، يستخدم هذا غالبًا للتنقيح/ التسجيل (logging): echo "We are in the file:" , __FILE__ , "\n"; المجلد الحالي: يمكنك استخدام الثابت السحري __DIR__ للحصول على المسار المطلق للمجلد الذي يوجد فيه الملف الحالي: echo "Our script is located in the:" , __DIR__ , "\n"; يمكنك استخدام dirname(__FILE__)‎ للحصول على المسار المطلق للمجلد الذي يوجد فيه الملف الحالي: echo "Our script is located in the:" , dirname(__FILE__) , "\n"; تُستخدم ثوابت الحصول على المجلد الحالي غالبًا من قِبل أطر عمل PHP لضبط المجلد الأساسي (base directory): // لإطار العمل index.php // استخدام الثابت السحري لتعريف ثابت عادي define(BASEDIR, __DIR__); // somefile.php looks for views: // views إذا أردت الوصول لمسار ملف ما في مجلد $view = 'page'; $viewFile = BASEDIR . '/views/' . $view; الفواصل يفهم نظام ويندوز الخط المائل / في المسارات لذا يُستخدم DIRECTORY_SEPARATOR بشكلٍ أساسي عند تحليل المسارات. وبالإضافة إلى الثوابت السحرية يوجد في PHP بعض الثوابت الثابتة للعمل مع المسارات مثل الثابت DIRECTORY_SEPARATOR لفصل المجلدات في مسار، يأخد القيمة / في ‎*nix‎ و\ في ويندوز، يمكن كتابة المثال السابق بالشكل التالي: $view = 'page'; $viewFile = BASEDIR . DIRECTORY_SEPARATOR .'views' . DIRECTORY_SEPARATOR . $view; نادرًا ما يستخدم الثابت PATH_SEPARATOR لفصل المسارات في متغير البيئة ‎$PATH وقيمته ; في ويندوز و: في غيره. ترجمة -وبتصرف- للفصول [Variables - Variable Scope - Constants - Magic Constants] من كتاب PHP Notes for Professionals book اقرأ أيضًا المقال التالي: المتغيرات ذات النطاق العام العالي (Superglobal Variables) في PHP المقال السابق: البدء مع PHP والتعرف على أنواع البيانات فيها
  22. إصدارات PHP نسخة PHP 7.x: النسخة مدعومة حتى تاريخ تاريخ الإصدار 7.4 2021-11-28 2019-11-28 7.3 2020-12-06 2018-12-06 7.2 2019-11-30 2017-11-30 7.1 2019-12-01 2016-12-01 7.0 2019-01-10 2015-12-03 نسخة PHP 5.x: النسخة مدعومة حتى تاريخ تاريخ الإصدار 5.6 2018-12-31 2014-08-28 5.5 2016-07-21 2013-06-20 5.4 2015-09-03 2012-03-01 5.3 2014-08-14 2009-06-30 5.2 2011-01-06 2006-11-02 5.1 2006-08-24 2005-11-24 5.0 2005-09-05 2004-07-13 نسخة PHP 4.x: النسخة مدعومة حتى تاريخ تاريخ الإصدار 4.4 2008-08-07 2005-07-11 4.3 2005-03-31 2002-12-27 4.2 2002-09-06 2002-04-22 4.1 2002-03-12 2001-12-10 4.0 2001-06-23 2000-05-22 النسخ القديمة: النسخة مدعومة حتى تاريخ تاريخ الإصدار 3.0 2000-10-20 1998-06-06 2.0 1997-11-01 1.0 1995-06-08 table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } تثبيت بيئة PHP نظام ويندوز تنزيل وتثبيت واستخدام wamp WampServer بيئة تطوير ويب على نظام ويندوز، يتيح لنا إنشاء تطبيقات ويب باستخدام Apache2 وPHP وقاعدة البيانات MySQL كما أنّ PhpMyAdmin يسمح لنا بإدارة قواعد البيانات بسهولة. WampServer متوفر مجانًا (بموجب ترخيص GPML) بإصدارين مختلفين: 32 و64 بت، Wampserver 2.5 غير متوافق مع ويندوز XP وSP3 وويندوز سيرفر 2003، وتتوفر إصدارات أقدم منه في SourceForge. توفر WampServer حاليًا: Apache MySQL PHP تثبيت WampServer بسيط جدًا، نزل آخر إصدار من الموقع الرسمي له WampServer، فقط نفّذ المثبت واختر الموقع ثم أنهِ، وبعدها يمكنك بدء تشغيل WampServer وسيبدأ في لوحة النظام (شريط المهام) باللون الأحمر بدايةً ثم يتغير للون الأخضر عندما يصبح الخادم جاهزًا، ويمكنك بعدها الذهاب إلى المتصفح وكتابة localhost أو 127.0.0.1 لتحصل على صفحة WAMP الرئيسية. يمكنك من الآن بدء العمل على PHP محليًا بتخزين الملفات في المسار <PATH_TO_WAMP>/www/<php_or_html_file> والتحقق من النتيجة على الرابط http://localhost/‎. تثبيت PHP واستخدامها مع IIS تحتاج بدايةً أن يكون عندك IIS (‏Internet Information Service) مثبّتًا وتنفّذه على جهازك، لا يتوفر لديك افتراضيًا إنما تحتاج أن تضيف خصائصه من: لوحة التحكم -> البرامج -> خصائص ويندوز ثبّت نسخة PHP التي تريدها من هذا الموقع وتأكد من تنزيل إصدارات آمنة غير خيطية (NTS). استخرج الملفات في المسار C:\PHP\‎. شغّل IIS (‏Internet Information Service). اختر العنصر الجذر من اللوحة اليسارية. انقر مرتين على Handler Mappings. انقر على Add Module Mapping في اللوحة الجانبية اليمينية. اضبط القيم كالتالي: Request Path: *.php Module: FastCgiModule Executable: C:\PHP\php-cgi.exe Name: PHP_FastCGI Request Restrictions: Folder or File, All Verbs, Access: Script ثبّت vcredist_x64.exe أو vcredist_x86.exe (حزمة Visual C++ 2012 قابلة لإعادة التوزيع) من هذا الرابط. اضبط ملف C:\PHP\php.ini وخاصةً extension_dir ="C:\PHP\ext"‎. أعد ضبط IIS، اكتب في طرفية الأوامر DOS الأمر IISRESET. يمكنك اختياريًا تثبيت مدير PHP من أجل IIS والذي يساعدك بشكلٍ كبير لضبط ملف ini وتتبع تسجيلات الأخطاء (لا يعمل على نظام التشغيل ويندوز 10)، وتذكر أن تضبط index.php على أنها إحدى صفحات IIS الافتراضية. إذا اتبعت دليل التثبيت فأنت جاهز الآن لاختبار PHP. يملك IIS مثل لينوكس هيكلية مجلدات على الخادم، جذر هذه الشجرة هو C:\inetpub\wwwroot\‎ وهنا نقطة الدخول لكل ملفاتك العامة وسكربتات PHP.? استخدم الآن محررك الخاص أو المحرر Notepad الموجود في ويندوز واكتب التالي: <?php header('Content-Type: text/html; charset=UTF-8'); echo '<html><head><title>Hello World</title></head><body>Hello world!</body></html>'; احفظ هذا الملف في المسار C:\inetpub\wwwroot\index.php باستخدام الترميز UTF-8 (بدون BOM)، ثم افتح موقعك الجديد على العنوان التالي http://localhost/index.php. تنزيل وتثبيت XAMPP ما هو XAMPP؟ XAMPP هو بيئة تطوير PHP الأشهر، مجاني بالكامل ومفتوح المصدر وسهل لتثبيت توزيعة Apache متضمنةً MariaDB وPHP وPerl. من أين يجب تنزيله؟ نزّل الإصدار المناسب المستقر من صفحة التنزيل، واختره بناءً على نوع نظامك التشغيل (32 بت أو 64 بت) وإصدار PHP الذي يجب أن يدعمه. أو يمكنك اتباع الخطوات التالية: يتوفر XAMPP من أجل نظام ويندوز في 3 أشكال: مثبِّت: ربما تكون صياغة ‎.‎exe أسهل طريقة لتثبيت XAMPP. ZIP: (للمتخصصين: XAMPP كأرشيف عادي بصياغة ‎.zip). 7zip: (للمتخصصين مع عرض حزمة أقل، XAMPP كأرشيف بصياغة ‎.7zip). كيف أثبّته وأين يجب أن أضع ملفات PHP/html؟ التثبيت باستخدام المثبِّت الموفَّر: نفّذ مثبِّت خادم XAMPP بالنقر مرتين على ملف ‎.exe المنزَّل. التنزيل من ZIP: فك ضغط الأرشيف المضغوط في المجلد الذي تختاره. سيُستخلص XAMPP إلى المجلد الفرعي C:\xampp داخل المجلد الهدف المُختار. افتح الآن الملف setup_xampp.bat لتعديل إعدادات XAMPP بما يناسب نظامك. ملاحظة: إذا اخترت المجلد الجذر C‎:\‎ كمجلد هدف عندها يجب ألا تبدأ ملف setup_xampp.bat. ما بعد التثبيت استخدم لوحة تحكم XAMPP للقيام بمهام إضافية، مثل بدء/إيقاف Apache، ‏MySQL، ‏FileZilla وMercury أو تثبيتها كخدمات. معالجة الملف التثبيت عملية سهلة مباشرة وعندما ينتهي التثبيت يمكنك إضافة ملفات html/php لتُستضاف على الخادم في المسار XAMPP-root/htdocs/‎ ثم شغّل الخادم وافتح الرابط http://localhost/file.php في المتصفح لمشاهدة الصفحة. ملاحظة: الجذر الافتراضي للخادم XAMPP في ويندوز هو C:/xampp/htdocs/‎. اكتب إحدى الرابطين التاليين في المتصفح: http://localhost/ http://127.0.0.1/ يجب أن تشاهد صفحة البدء للخادم. [01.PNG] أنظمة لينكس/يونكس تثبيت سطر الأوامر باستخدام APT من أجل PHP 7 سيثبّت هذا PHP فقط، إذا أردت عرض ملف PHP على الويب ستحتاج أيضًا تثبيت خادم ويب مثل Apache أو Nginx أو استخدام خادم الويب المدمج الخاص بلغة PHP (في الإصدار 5.4 وما بعده). إذا كنت تستخدم إصدار أبونتو أقل من 16.04 وتريد استخدام PHP 7 فيمكنك إضافة مستودع Ondrej PPA بكتابة الأمر: sudo add-apt-repository ppa:ondrej/php تأكد من أنّ كل مستودعاتك محدَّثة: sudo apt-get update بعد تحديث مستودعات نظامك، ثبّت PHP: sudo apt-get install php7.0 لنختبر التثبيت بالتحقق من نسخة PHP: php --version يجب أن يكون الخرج مشابهًا للتالي: PHP 7.0.8-0ubuntu0.16.04.1 (cli) ( NTS ) Copyright (c) 1997-2016 The PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies with Zend OPcache v7.0.8-0ubuntu0.16.04.1, Copyright (c) 1999-2016, by Zend Technologies with Xdebug v2.4.0, Copyright (c) 2002-2016, by Derick Rethans يمكنك الآن تشغيل PHP من سطر الأوامر. التثبيت في توزيعات إنتربرايس لينكس (CentOS، ‏Scientific Linux وغير ذلك) استخدم الأمر dnf لإدارة الحزم في أنظمة التشغيل المعتمدة على توزيعات إنتربرايس لينوكس: dnf install php يثبت هذا نسخة PHP محدودة متضمنةً بعض الميزات الشائعة، وإذا احتجت وحدات إضافية ستحتاج تثبيتها بشكلٍ منفصل ويمكنك استخدام dnf للبحث عن هذه الحزم: dnf search php-* مثال عن الخرج: ================================= Name & Summary Matched: php-* ================================= php-di-symfony2-bridge.noarch : PHP-DI integration with Symfony php-fedora-autoloader-devel.noarch : php-fedora-autoloader devel php-mock.noarch : PHP-Mock can mock built-in PHP functions php-mock2.noarch : PHP-Mock can mock built-in PHP functions php-pecl-propro-devel.x86_64 : php-pecl-propro developer files (header) php-pecl-psr-devel.x86_64 : php-pecl-psr developer files (header) php-pecl-raphf-devel.x86_64 : php-pecl-raphf developer files (header) php-punic.noarch : PHP-Unicode CLDR php-zstd-devel.x86_64 : php-zstd developer files (header) php-bcmath.x86_64 : A module for PHP applications for using the bcmath library php-cli.x86_64 : Command-line interface for PHP php-common.x86_64 : Common files for PHP php-dba.x86_64 : A database abstraction layer module for PHP applications php-devel.x86_64 : Files needed for building PHP extensions php-embedded.x86_64 : PHP library for embedding in applications php-enchant.x86_64 : Human Language and Character Encoding Support php-gd.x86_64 : A module for PHP applications for using the gd graphics library php-imap.x86_64 : A module for PHP applications that use IMAP لتثبيت مكتبة gd: dnf install php-gd تعد توزيعات إنتربرايس لينكس متحفظة دومًا بخصوص التحديثات، ولا تُحدَّث عادةً بعد الإصدار الذي تُرسل معه. توفر عدة مستودعات خارجيةمن طرف ثالث الإصدارات الحالية من PHP: IUS Remi Colette Webtatic يوفر المستودعان IUS وWebtatic حزمًا بديلة بأسماء مختلفة (مثل php56u أو php56w لتثبيت PHP 5.6) بينما يوفر مستودع Remi ترقيات موضعية باستخدام نفس أسماء حزم النظام. فيما يلي تعليمات تثبيت PHP 7.0 من مستودع Remi، هذا أبسط مثال لإلغاء تثبيت حزم النظام غير المطلوبة. # ‫تنزيل حزم RPM، استبدل 6 بالرقم 7 في حالة استخدام EL 7 wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm wget http://rpms.remirepo.net/enterprise/remi-release-6.rpm # تثبيت معلومات المستودع rpm -Uvh remi-release-6.rpm epel-release-latest-6.noarch.rpm # تمكين المستودع dnf-config-manager --enable epel --enable remi --enable remi-safe --enable remi-php70 # ‫تثبيت الإصدار الجديد من PHP # ملاحظة: إذا كانت حزمة النظام مثبّتة بالفعل سابقًا فإنّ هذا الأمر سيحدّثها dnf install php خرج HTML من خادم الويب يمكن أن تُستخدم PHP لإضافة محتوى إلى ملفات HTML. تُعالج صفحات HTML مباشرةً في متصفح الويب أما سكربتات PHP ينفذها خادم الويب ثم يرسل ملف HTML الناتج إلى المتصفح. تحوي صفحة HTML التالية تعليمة PHP ستضيف عبارة Hello world!‎ إلى الخرج: <!DOCTYPE html> <html> <head> <title>PHP!</title> </head> <body> <p><?php echo "Hello world!"; ?></p> </body> </html> عندما تُحفظ بتنسيق ملف برمجي بلغة PHP وينفذه خادم الويب فإنّه يرسل صفحة HTML التالية إلى المتصفح: <!DOCTYPE html> <html> <head> <title>PHP!</title> </head> <body> <p>Hello world!</p> </body> </html> PHP 5.x: الإصدار 5.4 وما بعده تتميز تعليمة echo بدءًا من الإصدار 5.4 ومابعده بصياغة مختصرة تتيح لك طباعة قيمة على الفور، أما قبل الإصدار PHP 5.4.0 كانت الصياغة المختصرة تعمل فقط عند تمكين الإعداد short open tag. مثلًا، إنّ الشيفرة التالية: <p><?= "Hello world!" ?></p> خرجها مطابق للخرج الناتج عن الشيفرة: <p><?php echo "Hello world!"; ?></p> في بيئة الإنتاج يجب أن تهرّب كل بيانات الخرج الناتجة عن سكربت PHP بشكل صفحة HTML لمنع هجمات البرمجة عبر المواقع (XSS) أو تلف النص (text corruption). يمكن الاطلاع أيضًا على: السلاسل النصية (strings) وPSR-1‎‏، التي تصف قواعد التنسيق بما في ذلك الاستخدام الأمثل للوسوم القصيرة (<‎?= ... ?‎>). مرحبًا بالعالم! البنية الأكثر استخدامًا لطباعة الخرج في لغة PHP هي echo: echo "Hello, World!\n"; ويمكن استخدام print بدلًا من ذلك: print "Hello, World!\n"; يؤدي تنفيذ كلا التعليمتين نفس الوظيفة مع اختلافات بسيطة: ترجع echo قيمة من النوع void، بينما ترجع print عددًا صحيحًا قيمته 1. يمكن أن تأخذ echo عدة وسائط (لكن بدون أقواس)، بينما تأخذ print وسيطًا واحدًا. echo أسرع قليلًا من print. كل من echo وprint هي بنى لغوية، ليست دوالًا. هذا يعني أنها لا تحتاج إلى أقواس لتضم وسائطها لكن يمكن إضافة الأقواس من أجل التناسق الجمالي مع التوابع. تتوافر العديد من الأمثلة عن استخدام echo وprint في مكانٍ آخر. الدالة printf في لغة C والدوال التابعة لها متوافرة أيضًا، كما في المثال التالي: printf("%s\n", "Hello, World!"); يمكن ملاحظة خرج قيمة المتغير للحصول على نظرة شاملة عن خرج المتغيرات في PHP. خرج خادم الويب ليس شيفرة HTML قد نحتاج عند العمل مع خادم ويب في بعض الحالات تجاوز نوع المحتوى الافتراضي الذي يرسله خادم الويب، فقد نحتاج مثلًا إلى إرسال البيانات على شكل نص عادي أو بصيغة JSON أو بصيغة XML. يمكن للدالة ‏()header‏ إرسال ترويسة HTTP خام. يمكننا إضافة الحقل Content-Type إلى الترويسة لإعلام المتصفح بنوع المحتوى الذي نرسله. في الشيفرة التالية نسند القيمة text/plain إلى الحقل Content-Type: header("Content-Type: text/plain"); echo "Hello World"; سينتج هذا ملفًا نصيًا عاديًا بالمحتوى التالي: Hello World للحصول على محتوى ‏JSON‏ نستخدم نوع المحتوى application/json بدلًا من النوع السابق: header("Content-Type: application/json"); // P‏H‎P‎ إنشاء مصفوفة بيانات في $data = ["response" => "Hello World"]; // نصية صحيحة JSON سيحولها إلى صيغة json_encode echo json_encode($data); هذا سينتج ملفًا من النوع application/json وبداخله المحتوى التالي: {"response":"Hello World"} لاحظ أنّه يجب استدعاء الدالة ‏()header‏ قبل تنفيذ أية عملية خرج، وإلا سيرسل خادم الويب الترويسات للإجابة (response headers). لذا وبحسب الشيفرة التالية: // خطأ: لا يمكننا إرسال أية عملية خرج قبل إرسال الترويسات echo "Hello"; // يجب إرسال كل الترويسات قبل أيّ عملية خرج header("Content-Type: text/plain"); echo "World"; سينتج عن هذا التحذير التالي: Warning: Cannot modify header information - headers already sent by (output started at /dir/example.php:2) in /dir/example.php on line 3 عند استعمال الدالة ‏()header‏ فإنّ خرجها يجب أن يكون أول بايت يرسل من الخادم. لهذا السبب يُعد من المهم عدم وجود أسطر فارغة أو مسافات في بداية الملف قبل وسم البداية ‎‎<?php‎ الخاص بلغة PHP. وللسبب نفسه، يعدّ تجاهل وضع وسم الإغلاق <?‎‎ الخاص بلغة PHP في نهاية الملف عادةً جيدةً (انظرPSR-2‎‏) وذلك للملفات التي تحتوي فقط PHP وكتل الشيفرة المكتوبة بلغة PHP. يجب مشاهدة قسم الخرج في المخزن المؤقت لمعرفة كيفية "جلب" المحتوى الخاص بك ووضعه في متغيّر قبل عملية إظهاره لاحقًا، مثلًا، بعد ترويسات الإخراج. خادم PHP مدمج يأتي الإصدار PHP 5.4 وما بعده مع خادم تطوير مدمج يمكن استخدامه لتشغيل التطبيقات بدون الحاجة لتنصيب خادم لتزويد الـ HTTP مثل nginx أو Apache. هذا الخادم المدمج مصمم ليستعمل لأغراض التطوير والاختبار. يمكن أن يُشغَّل باستخدام الراية S–: php ‎–S <host/ip>:<port> مثال عملي أنشئ ملف index.php يحتوي على ما يلي: <?php echo "Hello World from built-in PHP server"; نفّذ الأمر php -S localhost:8080 باستخدام سطر الأوامر، بدون أن تضيف ‎http‎:‎\‎\‎. عندها يبدأ خادم الويب بالتنصت على المنفذ 8080 ويعدّ المجلد الموجود أنت فيه حاليًا المستند الجذر. افتح المتصفح وانتقل إلى http://localhost:8080. ستشاهد عندها صفحتك "Hello World". الإعدادات إن أردت تغيير الصفحة الجذر الافتراضية (أي المجلد الحالي)، استخدم الراية t–: php -S <host/ip>:<port> ‎–t <directory> مثلًا إذا كان لديك المجلد public‎/‎ في مشروعك يمكنك تنفيذ المشروع من هذا المجلد باستخدام الأمر: php –S localhost:8080 -t public/ السجلات في كل مرة يُقدم فيها طلب من خادم التطوير يُكتب مدخل سجل مثل الموجود أدناه إلى سطر الأوامر. [Mon Aug 15 18:20:19 2016] ::1:52455 [200]: / واجهة سطر أوامر PHP يمكن أيضًا تنفيذ شيفرة PHP مباشرةً عن طريق سطر الأوامر باستخدام CLI (واجهة سطر الأوامر). واجهة سطر الأوامر CLI بشكلٍ أساسي نفس الـ PHP من ناحية خوادم الويب، باستثناء بعض الاختلافات من حيث المدخلات والمخرجات القياسية. التشغيل توفر واجهة سطر الأوامر في PHP أربع طرائق لتشغيل شيفرة PHP: دخل قياسي (Standard input): نفذ الأمر php دون أي وسائط ولكن مع تمرير شيفرة PHP عبر أنبوب (pipe) له: echo '<?php echo "Hello world!";' | php اسم الملف كوسيط: نفذ الأمر php مع وضع اسم ملف PHP المصدري كأول وسيط: php hello_world.php الشيفرة كوسيط: استخدم الخيار r‎‎‎‎‎‎‎‎‎‎- في الأمر php، متبوعًا بالشيفرة لتنفيذها. وسم الفتح ‎<?php‎‎‎ غير مطلوب، إذ يعد كل ما هو في الوسيط شيفرة PHP: php -r 'echo "Hello world!";' الصدفة التفاعلية (interactive shell): استخدم الخيار ‎-a في الأمر php للحصول على الصدفة التفاعلية ثم اكتب (أو الصق) شيفرة PHP واضغط return: $ php -a Interactive mode enabled php > echo "Hello world!"; Hello world! الخرج كل الدوّال أو المتحكمات التي تعطي خرج HTML في خادم الويب PHP يمكن أن تستخدم لإعطاء خرج في مجرى الخرج القياسي stdout (واصف الملف 1)، وكل الأفعال التي تعطي خرجًا في سجلات الخطأ في خادم الويب PHP ستعطي خرجًا في مجرى الخطأ القياسي stderr (واصف الملف 2). الملف Example.php: <?php echo "Stdout 1\n"; trigger_error("Stderr 2\n"); print_r("Stdout 3\n"); fwrite(STDERR, "Stderr 4\n"); throw new RuntimeException("Stderr 5\n"); ?> Stdout 6 سطر أوامر الصدفة: $ php Example.php 2>stderr.log >stdout.log;\ > echo STDOUT; cat stdout.log; echo;\ > echo STDERR; cat stderr.log\ تكون المخرجات في مجرى الخرج القياسي STDOUT: Stdout 1 Stdout 3 وتكون المخرجات في مجرى الخطأ القياسي STDERR: Stderr 4 PHP Notice: Stderr 2 in /Example.php on line 3 PHP Fatal error: Uncaught RuntimeException: Stderr 5 in /Example.php:6 Stack trace: #0 {main} thrown in /Example.php on line 6 الدخل انظر واجهة سطر الأوامر. فصل التعليمات تُنهى كل تعليمة بفاصلة منقوطة، تمامًا مثل جميع اللغات التي تتبع نفس نمط اللغة C، ويُنهى السطر الأخير من كتلة شيفرة PHP بوسم الإغلاق في آخر سطر. إذا انتهى السطر الأخير من شيفرة PHP بفاصلة منقوطة ولم يتبعه أي سطر شيفرة، فإن وسم الإغلاق اختياري، مثلًا يمكننا ألا نضيف وسم إغلاق بعد ; echo "No error"‎ في المثال التالي: <?php echo "No error"; // لا حاجة لإضافة وسم إغلاق بما أنه لا يوجد شيفرة بعد هذه التعليمة لكن إذا وُجدت أي شيفرة بعد كتلة شيفرة الـPHP فإنّ وسم الإغلاق ليس اختياريًا. <?php echo "This will cause an error if you leave out the closing tag"; ?> <html> <body> </body> </html> يمكننا أيضًا أن نتجاهل الفاصلة المنقوطة للتعليمة الأخيرة في كتلة شيفرة PHP إذا احتوت على وسم الإغلاق: <?php echo "I hope this helps! :D"; echo "No error" ?> يوصى عمومًا باستخدام الفاصلة المنقوطة دائمًا، واستخدام وسم الإغلاق لكل كتلة شيفرة PHP باستثناء الكتلة الأخيرة إذا لم يتبعها أي شيفرة، لذا يمكن كتابة الشيفرة بالشكل التالي: <?php echo "!هنا نستخدم فاصلة منقوطة"; echo "Here as well!"; echo "Here as well!"; echo "Here we use a semicolon and a closing tag because more code follows"; ?> <p>Some HTML code goes here</p> <?php echo "Here we use a semicolon!"; echo "Here as well!"; echo "Here as well!"; echo "Here we use a semicolon and a closing tag because more code follows"; ?> <p>Some HTML code goes here</p> <?php echo "Here we use a semicolon!"; echo "Here as well!"; echo "Here as well!"; echo "Here we use a semicolon but leave out the closing tag"; وسوم PHP هناك ثلاث أنواع من الوسوم للإشارة إلى كتل PHP ضمن ملف. يبحث محلل (parser) لغة PHP عن وسوم البداية ووسوم الإغلاق (إن وجدت) لتحديد الشيفرة المراد ترجمتها. الوسوم القياسية هذه الوسوم هي الطريقة القياسية لتضمين شيفرة PHP في ملف. <?php echo "Hello World"; ?> PHP 5.x الإصدار 5.4 وما بعده وسوم الطباعة هذه الوسوم متوفرة في كل إصدارات الـPHP ومفعلة دائمًا بدءًا من الإصدار PHP 5.4، إذ في الإصدارات السابقة لا يمكن تفعيل وسوم الطباعة إلا بالتزامن مع الوسوم القصيرة. <?= "Hello World" ?> الوسوم القصيرة يمكنك تفعيل أو تعطيل هذه الوسوم مع الخيار shortopentag. <? echo "Hello World"; ?> الوسوم القصيرة: غير مسموحة في جميع معايير كتابة الشيفرة الأساسية للغة PHP. غير مرغوبة في التوثيق الرسمي. معطلة افتراضيًا في معظم الإصدارات. تتداخل مع تعليمات معالجة XML المضمنة. غير مقبولة لتسليم الشيفرة في معظم المشاريع المفتوحة المصدر. PHP 5.x الإصدار 5.6 وما بعده وسوم ASP (صفحات الخادم النشط) يمكن استخدام وسوم نمط لغة ASP (Active Server Pages)‎ عند تفعيل الخيار asp_tags. <% echo "Hello World"; %> هذه الوسوم غريبة ويجب عدم استخدامها وأُزيلت منذ الإصدار PHP 7.0. موازنة النوع يوجد نوعان للموازنة: الموازنة المتهاونة (loose comparison) باستخدام == والموازنة الصارمة (strict comparison) باستخدام ===، تضمن الموازنة العامة أنّ نوع وقيمة كِلا طرفي العامل (operator) متساويين. // موازنة متهاونة var_dump(1 == 1); // true var_dump(1 == "1"); // true var_dump(1 == true); // true var_dump(0 == false); // true // موازنة صارمة var_dump(1 === 1); // true var_dump(1 === "1"); // false var_dump(1 === true); // false var_dump(0 === false); // false // لا يمكن أن يساوي أي شيء NAN استثناء يجب ملاحظته var_dump(NAN == NAN); // false var_dump(NAN === NAN); // false يمكنك أيضًا استخدام الموازنة القوية (strong comparison) للتأكد من عدم تطابق النوع والقيمة باستخدام ‎!==‎‎. المثال النموذجي الذي يعدّ فيه العامل == غير كافٍ عندما تُرجع الدوال أنواعًا مختلفة، مثل الدالة strpos التي تُرجع false إذا لم تجد المحارف التي نبحث عنها في السلسلة وإلا تُرجع عددًا صحيحًا يمثّل موقعها في السلسلة. if(strpos('text', 'searchword') == false) // false تُرجع strpos تعمل الموازنة == كما هو متوقع لأنّ if(strpos('text bla', 'text') == false) // true ‏0 هي‏==false‏ونتيجة ‏‎ ‏سترجع الدالة 0 لأنّها وجدت المطلوب في الموقع 0‏ // وهذا ليس ما تتوقعه if(strpos('text','text') === false) // لذا تعمل هنا كما تتوقع false ‏0 هي‏===false‏ونتيجة ‏‎ ‏سترجع الدالة 0‏ النوع Boolean (القيم المنطقية) القيم المنطقية نوع له إحدى القيمتين true أو false، تعطي الشيفرة التالية القيمة true للمتغير ‎$foo والقيمة false للمتغير ‎$bar: $foo = true; $bar = false; بما أنّ هاتين القيمتين غير حساستين لحالة الأحرف يمكننا أن نكتب TRUE وFALSE كما يمكننا أن نكتب FaLsE، لكن استخدام الأحرف الصغيرة هو الصيغة الأشهر ويُنصح بها في معظم أدلة نمط الشيفرة مثل PSR-2. يمكن استخدام القيم المنطقية في تعليمات if كما في المثال التالي: if ($foo) { // if($foo == true) نفس نتيجة echo "true"; } بما أنّ PHP لغة متهاونة في تحديد النوع (weakly typed) فإذا اُستعمل المتغير ‎$foo في موضع يتوقع فيه استعمال قيمة منطقية فستتحول قيمة ذاك المتغير إلى قيمة منطقية إما true أو false. القيم التالية نتيجتها false: القيمة الصفرية: 0 (عدد صحيح)، 0.0 (عدد عشري)، '0' (سلسلة نصية). السلسلة النصية الفارغة ' ' أو المصفوفة الفارغة []. القيمة null (محتوى متغير لم تُسند له قيمة، أو إذا أُسندت لمتغيرٍ ما). أي قيم أخرى نتيجتها true. يمكنك استخدام الموازنة القوية === التي توازن القيمة والنوع لتجنب الموازنة المتهاونة. يمكنك إضافة (bool) أو (boolean) قبل النوع لتحويله إلى قيمة منطقية. var_dump((bool) "1"); // true أو يمكنك استدعاء الدالة boolval var_dump( boolval("1") ); // true تحويل القيمة المنطقية إلى سلسلة نصية (string) مع ملاحظة أنّ قيمة السلسلة الفارغة هي false: var_dump( (string) true ); // string(1) "1" var_dump( (string) false ); // string(0) "" تحويل القيمة المنطقية إلى عدد صحيح (integer): var_dump( (int) true ); // int(1) var_dump( (int) false ); // int(0) لاحظ أنّه يمكننا أن نقوم بالعملية المعاكسة: var_dump((bool) ""); // bool(false) var_dump((bool) 1); // bool(true) وأيضًا القيم غير الصفرية تُرجع true: var_dump((bool) -2); // bool(true) var_dump((bool) "foo"); // bool(true) var_dump((bool) 2.3e5); // bool(true) var_dump((bool) array(12)); // bool(true) var_dump((bool) array()); // bool(false) var_dump((bool) "false"); // bool(true) النوع Float (الأعداد العشرية) $float = 0.123; ملاحظة: يُرجع التابع gettype()‎ عند استخدامه لعدد عشري القيمة "double" وليس "float" وذلك لأسباب تاريخية. تسمح الأعداد العشرية بدقة خرج أكبر من الأعداد الصحيحة البسيطة، ويمكن استخدام الأعداد العشرية والصحيحة معًا بسبب التحويل بين أنواع المتغيرات: $sum = 3 + 0.14; echo $sum; // 3.14 لا تُظهر PHP العدد العشري على أنّه عدد عشري مثل اللغات الباقية، مثلًا: $var = 1; echo ((float) $var); // تُرجع 1 وليس 1.0 تحذير: دقة الفاصلة العشرية (Floating point precision) من دليل PHP: الأرقام العشرية ذات دقة محدودة (الدقة تعني عدد الأرقام بعد الفاصلة)، بالرغم من أنّ هذا يعتمد على النظام إلا أنّ PHP تعطي عادةً أقصى خطأ نسبي بسبب التقريب بترتيب 1.11e-16، قد تعطي العمليات الحسابية غير البسيطة أخطاءً أكبر ويجب الانتباه لانتشار الخطأ عند تفاقم العمليات المتعددة. بالإضافة إلى ذلك فإنّ الأرقام المنطقية التي يمكن تمثيلها بأعداد عشرية بالأساس 10 مثل 0.1 أو 0.7، ليس لها تمثيل بالأعداد العشرية بالأساس 2 (ثنائية) والتي تُستخدم داخليًا بغض النظر عن حجم الجزء العشري، وبالتالي لا يمكن تحويلها إلى نظائرها الثنائية الداخلية دون فقدان بسيط للدقة، مثلًا تُرجع عادةً التعليمة floor((0.1+0.7)*10)‎ القيمة 7 بدلًا من القيمة المتوقعة 8 لأنّ التمثيل الداخلي سيكون تقريبًا 7.9999999999999991118…. لذا لا تثق بنتائج الأعداد العشرية أبدًا حتى الرقم الأخير ولا توازن الأعداد العشرية مباشرةً في المساواة، ويمكنك استخدام دوال الحساب الدقيق التعسفي (arbitrary precision math) أو دوال مكتبة GMP، إذا كانت الدقة العالية مطلوبة. النوع Strings (السلاسل النصية) السلسلة النصية في PHP هي سلسلة من المحارف أحادية البايت (أي لا يوجد دعم لترميز يونيكود أصلي) ويمكن تعريفها بأربعة طرق: السلاسل ذات الاقتباس المفرد (Single Quoted) تعرض الأشياء تقريبًا بالكامل كما هي، دون ترجمة المتغيرات وسلاسل الهروب، الاستثناء من ذلك فقط عند عرض اقتباس حرفي، يمكنك عرض خط مائل عكسي ضمنه عن طريق الهروب بخط مائل عكسي آخر: $my_string = 'Nothing is parsed, except an escap\'d apostrophe or backslash. $foo\n'; var_dump($my_string); /* string(68) "Nothing is parsed, except an escap'd apostrophe or backslash. $foo\n" */ السلاسل ذات الاقتباس المزدوج (Double Quoted) ستُترجم في هذه السلاسل أسماء المتغيرات البسيطة وسلاسل الهروب على عكس سلاسل الاقتباس المفرد، يمكن استخدام القوسان المعقوصان (Curly braces) (كما في المثال الأخير) لعزل أسماء المتغير المعقدة. $variable1 = "Testing!"; $variable2 = [ "Testing?", [ "Failure", "Success" ] ]; $my_string = "Variables and escape characters are parsed:\n\n"; $my_string .= "$variable1\n\n$variable2[0]\n\n"; $my_string .= "There are limits: $variable2[1][0]"; $my_string .= "But we can get around them by wrapping the whole variable in braces: {$variable2[1][1]}"; var_dump($my_string); /* string(98) "Variables and escape characters are parsed: Testing! Testing? There are limits: Array[0]" But we can get around them by wrapping the whole variable in braces: Success */ صيغة Heredoc تُحلّل أسماء المتغيرات وسلاسل الهروب في السلاسل ذات الصيغة Heredoc بطريقة مشابهة لسلاسل الاقتباس المزدوج، ومع ذلك فإنّ الأقواس غير متاحة لأسماء المتغير المعقدة. تُحدّد بداية السلسلة بـ مُعرّف >>>، وتنتهي بمُعرّف والمعرّف هو أي اسم PHP صحيح، يجب أن يظهر معرّف النهاية على سطر لوحده، ولا يُنصح بوجود مسافة بيضاء قبل أو بعد المُعرّف على الرغم من أنّه يجب إنهاؤه بفاصلة منقوطة نفس أي سطر في PHP. $variable1 = "Including text blocks is easier"; $my_string = <<< EOF Everything is parsed in the same fashion as a double-quoted string, but there are advantages. $variable1; database queries and HTML output can benefit from this formatting. Once we hit a line containing nothing but the identifier, the string ends. EOF; var_dump($my_string); /* string(268) "Everything is parsed in the same fashion as a double-quoted string, but there are advantages. Including text blocks is easier; database queries and HTML output can benefit from this formatting. Once we hit a line containing nothing but the identifier, the string ends." */ صيغة Nowdoc تشبه سلاسل الصيغة nowdoc نسخة سلاسل الاقتباس المفرد من الصيغة heredoc بالرغم من أنّ معظم سلاسل الهروب الأساسية لا تُقيَّم، ويُحتوى معرّف بداية السلسلة باقتباس مفرد. PHP 5.x: الإصدار 5.3 وما بعده $my_string = <<< 'EOF' A similar syntax to heredoc but, similar to single quoted strings, nothing is parsed (not even escaped apostrophes \' and backslashes \\.) EOF; var_dump($my_string); /* string(116) "A similar syntax to heredoc but, similar to single quoted strings, nothing is parsed (not even escaped apostrophes \' and backslashes \\.)" */ قابلية الاستدعاء يمكن إطلاق صفة قابلية الاستدعاء على أي شيء يمكن أن يُستدعى رد نداء (callback)، الأشياء التالية يمكن أن نسميها رد نداء: الدوال مجهولة الاسم دوال PHP القياسية (وليس بنى اللغة) الأصناف الساكنة الأصناف غير الساكنة (باستخدام صيغة بديلة) الكائن المعرّف/ توابع الصنف الكائنات، مادام الكائن موجود عند الموقع 0 من المصفوفة مثال لمرجعية كائن كمصفوفة عناصر: $obj = new MyClass(); call_user_func([$obj, 'myCallbackMethod']); يمكن أن تدل ردود النداء على النوع القابل للاستدعاء بدءًا من PHP 5.4: $callable = function () { return 'value'; }; function call_something(callable $fn) { call_user_func($fn); } call_something($callable); الموارد المورد هو متغير خاص يحمل مرجعيةً إلى مورد خارجي مثل ملف أو مقبس (socket) أو مجرى (stream) أو صفحة أو اتصال. $file = fopen('/etc/passwd', 'r'); echo gettype($file); # resource echo $file; # Resource id #2 يوجد أنواع وأنواع فرعية مختلفة للموارد، يمكنك الحصول على نوع المورد باستخدام الدالة getresourcetype()‎: $file = fopen('/etc/passwd', 'r'); echo get_resource_type($file); # stream $sock = fsockopen('www.google.com', 80); echo get_resource_type($sock); # stream يمكنك إيجاد قائمة كاملة من أنواع الموارد المدمجة هنا. التحويل بين الأنواع تُقدّر PHP بشكلٍ عام نوع البيانات الذي تنوي استخدامه بشكلٍ صحيح من السياق الذي اُستخدمت به، لكن من المفيد أحيانًا أن تفرض نوع ما يدويًا. يمكنك القيام بذلك بأن تبدأ التصريح بإضافة اسم النوع المطلوب بين قوسين: $bool = true; var_dump($bool); // bool(true) $int = (int) true; var_dump($int); // int(1) $string = (string) true; var_dump($string); // string(1) "1" $string = (string) false; var_dump($string); // string(0) "" $float = (float) true; var_dump($float); // float(1) $array = ['x' => 'y']; var_dump((object) $array); // object(stdClass)#1 (1) { ["x"]=> string(1) "y" } $object = new stdClass(); $object->x = 'y'; var_dump((array) $object); // array(1) { ["x"]=> string(1) "y" } $string = "asdf"; var_dump((unset)$string); // NULL لكن يجب أن تكون حذرًا إذ لا تعمل كل عمليات التحويل بين البيانات كما تتوقع: // (PHP_INT_MAX=2147483647) لدينا 3 تعليمات في أنظمة 32 بت حيث // سيحوّل تلقائيًا إلى عدد عشري PHP_INT_MAX أي عدد صحيح قيمته أكبر من var_dump( 999888777666 ); // float(999888777666) // سيؤدي إلى الطفحان (int) التحويل القسري إلى var_dump((int) 999888777666 ); // int(-838602302) // PHP_INT_MAX لكن إذا حولنا القيمة على أنها سلسلة نصية ستُرجع لنا قيمة var_dump((int) "999888777666"); // int(2147483647) var_dump((bool) []); // bool(false) (empty array) var_dump((bool) [false]); // bool(true) (non-empty array) التعامل مع الأنواع PHP هي لغة متهاونة في تحديد النوع، لا تطلب التصريح عن نوع المتغير عند تعريفه لأنه نوعه يُحدَّد حسب السياق الذي سيستخدم هذا المتغير فيه: $a = "2"; // string $a = $a + 2; // integer (4) $a = $a + 0.5; // float (4.5) $a = 1 + "2 oranges"; // integer (3) Null إذا كانت القيمة null في php مسندة إلى متغير، فيعد ذاك المتغير عديم القيمة وهي تشبه نوعًا ما المؤشر إلى فراغ (null pointer) في لغة C أو القيمة NULL في SQL. إسناد القيمة null للمتغير: // إسناد مباشر $nullvar = null; // هذه الدالة لا ترجع شيئًا function doSomething() {} // $nullvar إلى null لذا أُسنِدت $nullvar = doSomething(); التحقق فيما إذا أُسندت القيمة null لمتغيرٍ ما: if (is_null($nullvar)){ /* null قيمة المتغير هي */ } if ($nullvar === null) { /* null قيمة المتغير هي */ } القيمة null مقابل المتغير غير المعرّف (undefined variable) إذا لم يُعرّف المتغير أولم تُسند قيمة له فعندها أي تحقق من عدم كونه null سيكون ناجحًا وسيُظهر لك الرسالة Notice: Undefined variable: nullvar:‎ $nullvar = null; unset($nullvar); if ($nullvar === null) { // صحيح لكن ستُطبع رسالة أيضًا } if (is_null($nullvar)) { // صحيح لكن ستُطبع رسالة أيضًا } لذا يجب التحقق من القيم غير المعرّفة بالدالة isset: if (!isset($nullvar)) { // أو غير معرّف null المتغير قيمته } الأعداد الصحيحة يمكن وصف الأعداد الصحيحة في PHP بالأساس 2 (ثنائي) أو بالأساس 8 (ثماني) أو بالأساس 10 (عشري) أو بالأساس 16 (ست عشري). $my_decimal = 42; $my_binary = 0b101010; $my_octal = 052; $my_hexadecimal = 0x2a; echo ($my_binary + $my_octal) / 2; //42 // الخرج دائمًا بالقيمة العشرية طول الأعداد الصحيحة إما 32 بت أو 64 بت ويعتمد ذلك على المنصة، يُعبّرالثابت PHP_INT_SIZE عن طول العدد الصحيح مقدّرًا بالبايت، ومتوفرة الثوابت PHP_INT_MAX وPHP_INT_MIN (منذ الإصدار PHP 7.0) أيضًا. printf("Integers are %d bits long" . PHP_EOL, PHP_INT_SIZE * 8); printf("They go up to %d" . PHP_EOL, PHP_INT_MAX); تُنشأ القيم الصحيحة تلقائيًا وفقًا للحاجة من الأعداد العشرية والقيم المنطقية والسلاسل النصية. ويمكن عند الحاجة إلى التحويل بين الأنواع استخدام (int) أو (integer): $my_numeric_string = "123"; var_dump($my_numeric_string); // string(3) "123" $my_integer = (int)$my_numeric_string; var_dump($my_integer); // int(123) سيُعالَج طفحان العدد الصحيح (Integer overflow) بالتحويل إلى float (عدد عشري): $too_big_integer = PHP_INT_MAX + 7; var_dump($too_big_integer); // float(9.2233720368548E+18) لا يوجد عامل تقسيم صحيح في PHP، لكن يمكن محاكاته باستخدام التحويل الضمني بين الأنواع الذي يقرّب دائمًا بالتخلص من الجزء العشري، أُضيفت دالة التقسيم الصحيح بدءًا PHP من الإصدار 7. $not_an_integer = 25 / 4; var_dump($not_an_integer); // float(6.25) var_dump((int) (25 / 4)); // int(6) var_dump(intdiv(25 / 4)); // PHP7 كما في // int(6) لاحظ أنّنا نحتاج الأقواس الزائدة حول ‎ (25 / 4)‎لأنّ أولوية التحويل بين الأنواع (int) أعلى من أولوية القسمة. التعليقات التعليقات المكونة من سطرٍ واحدٍ تبدأ التعليقات المكونة من سطرٍ واحد بـ // أو #، سيتجاهل مترجم PHP كل ما يأتي إلى يمين هاتين العلامتين. // هذا تعليق # هذا أيضًا تعليق echo "Hello World!"; // // هنا يوجد تعليق أيضًا بدءًا من العلامة التعليقات المكونة من عدة أسطر تُستخدم التعليقات المكونة من عدة أسطر للتعليق على كتل شيفرة كبيرة، تبدأ بـ ‎/*‎ وتنتهي بـ ‎*/‎‎: /* هذا تعليق متعدد الأسطر يمتد على عدة أسطر هذا السطر أيضًا من ضمن التعليق */ PSR إنّ PSR ‏(PHP Standards Recommendation) سلسلة من التوصيات التي جُمعت من قِبل FIG ‏(Framework Interop Group). يمكن أن تكون PSR في إحدى الحالات: مقبولة أو قيد المراجعة أو مسودة أو مهملة. PSR-4: المحمِّل التلقائي (Autoloader) PSR4 هي توصية مقبولة تحدد معيار أصناف التحميل التلقائي عبر أسماء الملفات، ويوصى بها كبديل للتوصية السابقة (والمهملة حاليًا) PSR-0. يجب أن يطابق اسم الصنف المؤهل بالكامل المتطلب التالي: \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName> يجب أن يحتوي على فضاء اسم المورد (vendor) العالي المستوى (مثل Alphabet). قد يحتوي على فضاء اسم فرعي واحد أو أكثر (مثل Google\AdWord). يجب أن يحتوي على اسم الصف النهائي (مثل KeywordPlanner). وبالتالي يكون اسم الصنف النهائي Alphabet\Google\AdWord\KeywordPlanner، ويجب أن يُترجم اسم الصنف المؤهل بالكامل إلى مسار ملف ذو معنى لذا يوجد Alphabet\Google\AdWord\KeywordPlanner في الملف ‎[path_to_source]/Alphabet/Google/AdWord/KeywordPlanner.php. بدءًا من الإصدار PHP 5.3.0 يمكننا تعريف دالة محمِّل تلقائي مخصصة لتحميل ملفات بالاعتماد على نمط المسار واسم الملف الذي تعرفه. // ‫عدّل شيفرة php لتضمين صنف بشكلٍ مشابه للتالي spl_autoload_register(function ($class) { include 'classes/' . $class . '.class.php';}); استبدل الموقع ('classes/') ولاحقة اسم الملف ('.class.php') بالقيم التي تناسب الهيكلية الخاصة بك. يدعم مدير حزمة المُنشئ PSR-4 مما يعني أنّه إذا اتبعت المعيار فإنّه يمكنك تحميل الأصناف في مشروعك بشكلٍ تلقائي باستخدام المحمّل التلقائي لمورد المُنشئ. // ‫عدّل ملف composer.json للتضمين { "autoload": { "psr-4": { "Alphabet\\": "[path_to_source]" } } } أعد توليد ملف المحمِّل التلقائي: $ composer dump-autoload يمكنك بعدها أن تكتب الشيفرة التالية: <?php require __DIR__ . '/vendor/autoload.php'; $KeywordPlanner = new Alphabet\Google\AdWord\KeywordPlanner(); PSR-1: معيار التشفير الأساسي PSR-1 توصية مقبولة وتحدد توصية المعيار الأساسية التي يجب أن تُكتب بها شيفرة PHP. تحدد اصطلاحات التسمية للأصناف والتوابع والثوابت. تجعل تبنّي توصيات PSR-0 وPSR-4 مطلبًا. تُشير إلى وسوم PHP التي يجب أن تُستخدم ‎<?php و‎‎<?=‎ وليس ‎<?‎. تحدد نمط ترميز الملف الذي يجب أن يُستخدم (UTF8). وتنص أيضًا على أنّ الملفات يجب إما أن تصرّح عن رموز جديدة (أصناف، دوال، ثوابت…) ولا تسبب تأثيرات جانبية أو تنفّذ منطق ما مع تأثيرات جانبية ولا تعرّف رموز لكن تفعل كليهما. أداة PHPDoc وصف متغير يمكن استخدام الكلمة المفتاحية ‎@var لوصف نوع واستخدام:? خاصيّة صنف متغير محلي أو عالمي ثابت صنف أو عالمي class Example { // ‫‎@var سلسلة نصية تبقى ثابتة const UNCHANGING = "Untouchable"; // ‫‎@var سلسلة نصية ما public $some_str; // ‫‎@var المتغير ‎$stuff مصفوفة? فيها مجموعة أشياء // ‫‎@var المتغير ‎$nonsense مصفوفة أيضًا private $stuff, $nonsense; … } يمكن أن يكون النوع إحدى أنواع PHP المضمنة أو اسم صنف معرَّف من قِبل المستخدم متضمنًا فضاءات الأسماء، ويجب أن يُضمَّن اسم المتغير لكن يمكن تجاوزه إذا طُبِّقت صيغة docblock على عنصر واحد فقط. إضافة بيانات وصفية إلى الدوال تساعد التوصيفات على مستوى الدالة بيئات التطوير المتكاملة (IDEs) في تحديد القيم المعادة أو الشيفرة التي يحتمل أن تكون خطيرة. /** * إضافة رقمين إلى بعضهما * ‎@param Int $a المعامل الأول في الإضافة * ‎@param Int $b المعامل الثاني في الإضافة * ‎@return Int */ function sum($a, $b) { return (int) $a + $b; } /** * ‫لا تنفّذني! سأرمي استثناء دائمًا * @throws Exception Always */ function dangerousCode() { throw new Exception('Ouch, that was dangerous!'); } /** * يجب إهمال الهياكل القديمة حتى تعرف الناس أنه لا يجب استخدامها * @deprecated */ function oldCode() { mysql_connect(/* ... */); } وصف المعاملات /** * * المعاملات * @param int $int * @param string $string * @param array $array * @param bool $bool */ function demo_param($int, $string, $array, $bool) { } /** * المعاملات - اختيارية / افتراضية * * @param int $int * @param string $string * @param array $array * @param bool $bool */ function demo_param_optional($int = 5, $string = 'foo', $array = [], $bool = false) { } /** * المعاملات - مصفوفات * * @param array $mixed * @param int[] $integers * @param string[] $strings * @param bool[] $bools * @param string[]|int[] $strings_or_integers */ function demo_param_arrays($mixed,$integers, $strings, $bools, $strings_or_integers) { } /** * المعاملات - مركبة * @param array $config * <pre> * $params = [ * 'hostname' => (string) DB hostname. Required. * 'database' => (string) DB name. Required. * 'username' => (string) DB username. Required. * ] * </pre> */ function demo_param_complex($config) { } المجموعات (collections) تقترح PSR-5 شكل تدوين معمّم للمجموعات. الصيغة المعمّمة Type[] Type<Type> Type<Type[, Type]...> Type<Type[|Type]...> قد تكون القيم في مجموعة إما مصفوفة أخرى أو حتى مجموعة أخرى. Type<Type<Type>> Type<Type<Type[, Type]...>> Type<Type<Type[|Type]...>> أمثلة <?php /** * @var ArrayObject<string> $name */ $name = new ArrayObject(['a', 'b']); /** * @var ArrayObject<int> $name */ $name = new ArrayObject([1, 2]); /** * @var ArrayObject<stdClass> $name */ $name = new ArrayObject([ new stdClass(), new stdClass() ]); /** * @var ArrayObject<string|int|stdClass|bool> $name */ $name = new ArrayObject([ 'a', true, 1, 'b', new stdClass(), 'c', 2 ]); /** * @var ArrayObject<ArrayObject<int>> $name */ $name = new ArrayObject([ new ArrayObject([1, 2]), new ArrayObject([1, 2]) ]); /** * @var ArrayObject<int, string> $name */ $name = new ArrayObject([ 1 => 'a', 2 => 'b' ]); /** * @var ArrayObject<string, int> $name */ $name = new ArrayObject([ 'a' => 1, 'b' => 2 ]); /** * @var ArrayObject<string, stdClass> $name */ $name = new ArrayObject([ 'a' => new stdClass(), 'b' => new stdClass() ]); إضافة بيانات وصفية إلى الملفات تُطبَّق البيانات الوصفية من مستوى الملف على كامل شيفرة الملف ويجب أن تُكتب في أعلاه: <?php /** * @author John Doe (jdoe@example.com) * @copyright MIT */ وراثة البيانات الوصفية من هياكل الآباء إذا ورث الصنف صنفًا آخر وكان يستخدم نفس البيانات الوصفية فيمكن أن نضيف له ‎@inheritDoc ليستخدم نفس التوثيق، إذا ورث عدة أصناف صنفًا أساسيًا فيجب تغيير الصنف الأب فقط حتى يتأثر كل الأصناف الأبناء. abstract class FooBase { /** * ‎@param Int $a المعامل الأول في الإضافة * ‎@param Int $b المعامل الثاني في الإضافة * ‎@return Int */ public function sum($a, $b) {} } class ConcreteFoo extends FooBase { /** * @inheritDoc */ public function sum($a, $b) { return $a + $b; } } خادم PHP المدمج العمود التفاصيل ‎-S ‫تخبر php بأننا نريد خادم ويب <hostname>:<port> اسم المضيف ورقم المنفذ الذي نريد استخدامه ‎-t المجلد العام <filename> سكربت التوجيه تشغيل الخادم المدمج php -S localhost:80 PHP 7.1.7 Development Server started at Fri Jul 14 15:11:05 2017 Listening on http://localhost:80 Document root is C:\projetos\repgeral Press Ctrl-C to quit. هذه أبسط طريقة لبدء خادم PHP يستجيب للطلبات المرسلة إلى المضيف المحلي (localhost) على المنفذ 80. يخبر ‎-S أننا نبدأ خادم ويب، أما localhost:80 يشير إلى المضيف الذي يجيب على الطلبات والمنفذ، يمكنك أن تستخدم تراكيب أخرى مثل: mymachine:80 - will listen on the address mymachine and port 80; 127.0.0.1:8080 - will listen on the address 127.0.0.1 and port 8080; خادم مدمج مع مجلد محدد وسكربت موجِّه php -S localhost:80 -t project/public router.php PHP 7.1.7 Development Server started at Fri Jul 14 15:22:25 2017 Listening on http://localhost:80 Document root is /home/project/public Press Ctrl-C to quit. ترجمة -وبتصرف- للفصول [-installing a PHP environment on Windows - Installing on Linux/Unix Environments- Getting started with PHP - Types - Comments - PHPDoc - PSR - PHP Built in server] من كتاب PHP Notes for Professionals book. اقرأ أيضًا المقال التالي: المتغيرات والثوابت في PHP
  23. يتضمن العمل مع أشكال SVG (الرسوميات المتجهية متغيرة الحجم) في سير عمل تصميم الويب المتجاوب مرحلة تصميم ومرحلة تطوير. يتعامل المصممون عادةً مع مرحلة التصميم وقد يعرفون أو لايعرفون كيفية كتابة الشيفرة. ونظرًا لطبيعة SVG كتنسيق صورة وتنسيق مستند معًا، فإنّ كل خطوة تتم في محرر الرسومات أثناء عملية إنشاء SVG تؤثر بشكلٍ مباشر على الشيفرة الناتجة وبالتالي على عمل المطور المسؤول عن تضمين SVG أو كتابته كنص برمجي أو تحريكه. عادةً، خلال عملي اليومي أنا المطور الذي يسلّم المصممون له أصول التصميم، وصور SVG هي جزء من هذه الأصول. احتاجت معظم الأصول التي تم تسليمها في مشاريعي السابقة إلى المزيد من العمل و/أو جولة ثانية من التحرير في محرر الرسومات قبل أن أتمكن من كتابتها كنصوص برمجية، لأنّ شيفرة SVG الناتجة لم تُحسَّن بما فيه الكفاية لنوع العمل - خاصةً الرسوم المتحركة - التي تم استئجاري للقيام بها. سبب ذلك هو أنّ العديد من المصممين الذين عملت معهم يعرفون القليل جدًا - إن كانوا يعرفون - عن شيفرة SVG. ينشؤون رسوميات متجهة وأصول واجهة المستخدم كل الوقت، لكن بالنسبة لهم فإنّ SVG ليس سوى تنسيق صورة ولا يعرفون الكثير عن الشيفرة المتولّدة عند تصدير أصولهم كمستندات SVG. هناك بعض الخطوات التي يمكن للمصممين اتخاذها أو تجنبها - مجموعة من "افعل ولا تفعل" - يمكن أن تساعد في جعل الشيفرة المتولدة أنظف. أودّ أن أشارك بعضها في هذه المقالة، إذا كنت تعرف أي شيء آخر يرجى مشاركته في التعليقات الموجودة في نهاية المقال. النصائح التي سنتحدث عنها قابلة للتطبيق في Adobe Illustrator -محرر الرسومات المفضل لي- وكذلك محررات الرسومات الأخرى. لكن بما أنني شخصيًا أستخدم AI فهذا ما سأركّز عليه خلال هذه المقالة. سنتحدث أيضًا عن خيارات تصدير SVG الحالية المتوفرة في AI وأيّ منها تختار ولماذا. لكن لاحظ أنّ هذه الخيارات ستتغير في المستقبل، وبعدها سيتم تحديث هذه المقالة لتعكس هذه التغييرات. تعتمد هذه المقالة على حديثي "SVG لمصممي ومطوري الويب" - حديث قمت به في CSSDevConf 2015 الشهر الماضي. لذا، لنبدأ. إذا كنت تستخدم برنامج Sketch لإنشاء SVG، هناك أيضًا بعض الأشياء التي يمكن أن تقوم بها لتصدير شيفرة أنظف. شارك Sean Kesterson بعض النصائح في هذه المقالة. 1- أنشئ الأشكال البسيطة باستخدام عناصر شكل بسيطة وليس عناصر <path> هناك سبب لوجود أشكال أساسية مختلفة في SVG لإنشاء أشكال أساسية بشكلٍ جيد. يمكن للشخص إنشاء أي شكل عمليًّا باستخدام عنصر <path>، أليس كذلك؟ عناصر الشكل البسيطة (<line>، <circle>، <rect>، <ellipse>، <polygon> و<polyline>) موجودة لعدة أسباب، وأحد هذه الأسباب هو أنّها سهلة القراءة وسهلة الصيانة وقابلية للتحرير باليد من بدائلها <path>. تأتي الأشكال الأساسية مع مجموعة من الخاصيات التي تسمح لك بالتحكم في ميزات الشكل، مثل الموضع (x، y، cx، cy) والأبعاد (width وheight)، بينما لا يكون لعناصر <path> مثل هذه الخاصيات. مثلًا، يعرض المقتطف التالي الاختلاف بين دائرة تم إنشاؤها وتصديرها كشكلٍ بسيط، مقابل دائرة تم إنشاؤها وتصديرها كمسار: <circle fill="#FFFFFF" stroke="#000" cx="28.1" cy="28.1" r="27.6"/> <!-- مقابل --> <path fill="#FFFFFF" stroke="#000" d="M55.7,28.1 c0,15.2-12.4,27.6-27.6,27.6 S0.5,43.3,0.5,28.1 S12.9,0.5,28.1,0.5 S55.7,12.9,55.7,28.1z"/> إذا أردت تحريك شكلك عن طريق تحريك موضع الدائرة أو تكبيرها، يمكنك القيام بذلك عن طريق تحريك موضع المركز عبر الإحداثيات x و y أي (cx وcy) ونصف قطر الدائرة (r). بينما إذا كنت تعمل مع دائرة متولدة كمسار، يجب عليك استخدام تحويلات CSS/SVG (التحريك وإعادة التحجيم) للقيام بذلك. ثمّ بفرض أردت تحريك ذلك المسار وتتطلب منك الحركة تطبيق المزيد من التحويلات، يمكن أن يصبح لديك ببساطة فوضى تحويلات. ميزة أخرى لاستخدام الأشكال البسيطة هي أنّه في معظم الحالات، الشيفرة المطلوبة لإنشاء شكل باستخدام عناصر شكل بسيطة هي أقلّ من المطلوبة لإنشاء نفس الشكل باستخدام عنصر <path> (شاهد المقتطف اعلاه للمقارنة)، لذا فإنّ استخدام أشكال بسيطة سيؤدي أيضًا إلى حجم ملف أصغر، وهذا أفضل دائمًا. 2 - حوّل النص إلى حدود خارجية..أو لا تحوّله لتحويل النص إلى حدود خارجية: اختر النص الذي تريد تحويله اختر النوع (Type) -> إنشاء حدود خارجية (Create Outlines) الإيجابيات النص المحوّل إلى حدود خارجية سيحافظ على نوع الخط المستخدم، بدون الحاجة إلى استخدام خط ويب لعرضه. هذا يعني أنك تحفظ القليل من طلبات HTTP الإضافية ولا تخاطر بعرض نصك بالخط الاحتياطي الذي لا يبدو جيدًا بشكلٍ كافٍ لاستبدال الخط الجميل الذي اخترته. يعدّ تحويل النص إلى حدود خارجية والحفاظ على نوع الخط المستخدم جيّدًا للحفاظ على هوية العلامة التجارية عندما تُعرّف بنوع خط مستخدم، مثلًا في شعار، أقوم دائمًا بتحويل نص الشعار إلى حدود خارجية وهذا جيد للحفاظ على الخط لنصوص برمجية معينة عندما تستخدم العناوين. السلبيات النص المحوّل إلى خطوط خارجية ليس نصًا حقيقيًا: إنّه مجموعة من المسارات التي تشكل الحدود الخارجية (الشكل) للنص. وبالتالي يصبح النص غير واقعي ولا يمكن الوصول إليه، وغير قابل للبحث وغير قابل للتحديد. إذا تمّ تحويل النص إلى حدود خارجية في عنوان نص برمجي أو حتى شعار، فإنّ استخدام نص alt (إذا تم تضمين الشعار كصورة) أو عناصر سهلة الوصول لـ SVG مثل (<title> و&>) هو فكرة جيدة لتوفير نص بديل لقارئات الشاشة. أوصي بشدّة بقراءة كل شيء عن جعل SVG سهل الوصول في هاتين المقالتين لـLéonie Watson: - نصائح لإنشاء SVG سهلة الوصول - استخدام ARIA لتحسين سهولة الوصول لـSVG يمكن أن يسبب تحويل النص إلى حدود خارجية زيادةً كبيرةً في حجم ملف SVG، حسب تعقيد الخط المستخدم. تُظهر الصورة أدناه الاختلاف في حجم (وسهولة القراءة) لـSVG مع النص المحوّل إلى حدود خارجية (يسار) والنص الذي يتم تصديره كـ SVG <text>‎ (يمين). لا يمكن التحكم بالمسارات أو تحريكها بسهولة مثلما يتم ذلك في عناصر <text> (المتضمنة عناصر <tspan>). إذ تحتوي الأخيرة على مجموعة من الخاصيات التي تمنحك المزيد من التحكم في رسومك المتحركة، بينما تبقى بيانات المسار محدودة بهذا الشأن. 3- تبسيط المسارات يعرَّف المسار بمجموعة من النقاط والتي بدورها يتم تحديد كلّ منها بزوج من الإحداثيات. كلّما قلّ عدد النقاط، قلّت بيانات المسار (خاصية d)، وبالتالي انخفض حجم ملف SVG الكلي. هذه الخطوة الجيدة يجب اتخاذها دائمًا لأنّ حجم الملف الأصغر أفضل للأداء. لتبسيط مسار: اختر المسار اذهب إلى كائن (Object) -> مسار (Path) -> تبسيط (Simplify) عدّل عدد النقاط. تأكّد من أن أنّك فحصت المعاينة حتى تتمكن من معرفة كيف يتغير المسار أثناء تغيّر عدد النقاط. عدّل العدد حتى تحصل على أقل عدد من النقاط أثناء المحافظة (أو التضحية) على أفضل مظهر مرئي للمسار الذي تحتاجه. يوجد فيديو تعليمي لشركة Adobe يشرح العملية، لذا إذا كنت أكثر انسجامًا مع مقاطع الفيديو، يمكنك الاطلاع عليه هنا. يمكنك أيضًا تبسيط المسارات باستخدام أداة Warp Tool. أنا لست مصممة ولكن أستخدم عادةً خوارزمية تبسيط Ai لتبسيط مساراتي، لذا إذا كنت مصممًا متمرّسًا، من المحتمل أنّك تعرف أكثر بكثير عن أداة Warp tool مني. يوجد مقال في مجلة Smashing حول هذه الأداة، يمكنك الاطلاع عليها إن أردت. 4- تجنب دمج المسارات إذا لم تكن بحاجة للتحكم في المسارات الفردية يميل العديد من المصممين إلى جمع المسارات أو دمجها كلما أمكن ذلك. لدمج المسارات: اختر الطرق التي تريد دمجها اذهب إلى نافذة (Pathfinder <- (Window اختر خيار الدمج (Merge) من بين قائمة الخيارات أسفل اللوحة (الأيقونة الثالثة من اليسار، المبينة في لقطة الشاشة أدناه). قد يكون هناك فوائدٌ لجمع المسارات، لكن تجنبها عندما تحتاج أنت أو المطور للتحكم و/أو تحريك المسارات بشكلٍ منفصل. تم تصميم بعض الرسوم المتحركة بحيث يتم تحريك عناصر متعددة بشكلٍ منفصلٍ، أو في بعض الأحيان قد تريد فقط تصميم المسارات باستخدام ألوان تعبئة مختلفة. إذا قمت بجمع المسارات، فلن يكون ذلك ممكنًا بعد الآن. تحتاج إلى التأكّد من معرفتك لما يحتاجه المطور (أو أنت إذا كنت ستتعامل مع مرحلة التطوير أيضًا) ولما يريد القيام به مع الأشكال التي تعمل عليها، واتخاذ قرار الدمج أو عدم الدمج وفقًا لذلك. هذا سيوفر لكما الكثير من الوقت والخلاف. 5- إنشاء مرشّحات باستخدم مرشّحات SVG، وليس تأثيرات Photoshop إذا كنت تستخدم المرشّحات في قسم تأثيرات Photoshop ضمن خيار تأثير، فسيقوم Illustrator بتصدير التأثيرات التي تنشؤها كصور نقطية. مثلًا، إذا قمت بإنشاء ظل إسقاط باستخدام تأثير الضبابية Blur في فوتوشوب، فإن الظلّ المسقط المتولّد سيكون صورة نقطية مضمنة داخل SVG إما مضمنة داخليًا أو خارجيًا، باستخدام عنصر <image>. أنت بالتأكيد لا تريد ذلك عند العمل مع SVG. تحتاج لتوليد مؤثراتك كشيفرة SVG إلى استخدام مرشّحات SVG المتاحة: اذهب إلى تأثير (Effect) -> مرشّحات (SVG Filters (SVG اختر واستخدم أحد المرشّحات المتاحة في اللائحة. 6- اجعل لوح الرسم (Artboard) مناسبًا لرسمتك هل قمت يومًا بتضمين SVG في صفحة، وإعطائه ارتفاعًا وعرضًا محددين ثمّ وجدت أنه كان معروضًا بحجم أصغر من الحجم الذي حددته؟ في معظم الحالات، يكون السبب في ذلك هو وجود مساحة بيضاء داخل منفذ العرض (viewport) الخاص بـ SVG. يتم عرض منفذ العرض بالحجم الذي تحدده في صفحة الأنماط (style sheet) الخاصة بك، لكن المساحة الإضافية داخلها - حول الرسم - تتسبب في "تقليص" صورتك، لأنّ هذه المساحة البيضاء تأخذ مساحةً داخل منفذ العرض. لتجنّب ذلك، تحتاج إلى التأكّد من أنَّ لوح الرسم الخاص بك كبير بما فيه الكفاية ليناسب الرسمة بداخله، ولكن ليس أكبر منها. أبعاد لوح الرسم هي أبعاد منفذ عرض SVG المصدَّر، وأي مساحة بيضاء في لوح الرسم سيتم إنشاؤها كمساحة بيضاء داخل منفذ العرض. ليتناسب لوح الرسم مع رسمتك: حدد كامل الرسمة (أنا أستخدم الاختصار cmd/ctrl + A) اذهب إلى كائن (Object) -> ألواح الرسم (Artboards) واختر الخيار تناسب مع حدود العمل الفني (Fit to Artwork Bounds option). 7- استخدم اصطلاحات تسمية، وتجميع وطبقات جيّدة أعلم أنّ هذا يبدو كأنّه غير عقلاني، لكن يجب التأكيد عليه لعدة أسباب: ستتم ترجمة معرّفات وأسماء الفئات التي تستخدمها في محرر الرسومات إلى معرّفات وأسماء الفئات في الشيفرة المتولّدة. كلّما كانت هذه الأسماء أكثر منطقيةً وأكثر وضوحًا في تصنيف العناصر الخاصة بكل منها، سيكون هناك تعارضًا أقل عندما يعمل المطوّر مع الشيفرة. أنا لا أقول الآن أنّه يجب عليك التفكير في الأسماء المثالية - أنا متأكّدة أنّه لكلّ منّا طرق مختلفة لتسمية الأشياء ويمكن أن تكون التسمية واحدة من أصعب المهام، لكن تسمية المجموعات بشكلٍ مناسب تقطع شوطًا طويلًا. مثلًا، إذا كنت ترسم سيارة، فإنّه من المناسب استخدام معرّف العجلة لتسمية الطبقة أو مجموعة تغليف الأشكال المكوّنة للعجلة. إذا كنت تقوم بتجميع كل العجلات في مجموعة واحدة، فقد تعطيها معرّف العجلات. تؤثّر الأسماء البسيطة في التعبير عن العناصر والمجموعات وتحفظ الكثير من الوقت، خاصةً إذا كان المطوّر سيحرر الشيفرة ويعالجها يدويًا. لا يقوم Illustrator بعمل أفضل في تسمية الأشياء، لذا فإنّ تحديد الأسماء يساعد في تقليل كمية المخلّفات التي ينتجها. وبالتأكيد، سيكون هناك بعض التحرير الإضافي المطلوب للتخلّص من الخطوط السفلية المزعجة التي يصرّ Ai على توليدها، لكن استخدام الأسماء الصحيحة يساعد في جعل هذه العملية أسهل قليلًا. كما ذُكر سابقًا، ستُظهر النسخة التالية من Illustrator تحسّنًا كبيرًا في طريقة توليد SVG، متضمنةً المعرّفات المتولّدة. استخدم الطبقات لتجميع العناصر ذات الصلة. تُترجم الطبقات إلى مجموعات في الشيفرة، لذا يجب تسميتها بشكلٍ جيّد أيضًا. أنشئ طبقات/مجموعات لتجميع العناصر ذات الصلة معًا، خاصةً تلك التي قد تكون متحركة بالكامل. إذا لم يتم ذلك بالفعل في مرحلة التصميم فمن الممكن قضاء الكثير من الوقت في إعادة ترتيب وتجميع العناصر يدويًا من قِبل المطوّر. تأكّد من التجميع بشكلٍ مناسب لتوفير الوقت. يُعدّ التحدّث مع المطور في مرحلة التصميم وتصميم كيفية ستُنفّذ الرسوم المتحركة معًا هما أكبر موفران للوقت. إذا كانت الصور التي تقوم بإنشائها ستُستخدم لإنشاء SVG sprite، فإنّ الأسماء التي تستخدمها يمكن أن تُستخدم وستُستخدم من قِبل معظم أدوات الأتمتة لتوليد ملفات جديدة. لذا فإنّ استخدام أسماء واضحة ومناسبة سيؤدي إلى أسماء ملفات أنظف. 8- اختر أفضل خيارات تصدير مناسبة للويب بدءًا من Illustrator CC 2015.2 الذي تمّ إصداره في تشرين الأول 2015، يوجد تدفق عمل جديد لتصدير SVG (ملف> تصدير> SVG) لتصدير ملفات SVG مُحسّنة للويب لتدفقات عمل تصميم الشاشة والويب لديك. يمكنك أيضًا أن تختار تصدير الأغراض الفردية أو كامل لوح الرسم. راجع هذه المقالة لمزيدٍ من التفاصيل. في وقت كتابة هذا المقال، يأتي Illustrator مع مجموعة من خيارات التصدير التي تسمح لك بتوليد شيفرة SVG أفضل بشكلٍ عام. لتصدير الـ SVG الخاص بك: اختر ملف (File) -> حفظ كـ (Save As) اختر SVG من القائمة المنسدلة اضغط حفظ (Save) بمجرد النقر على حفظ، سيظهر مربع حوار يحتوي على مجموعة من الخيارات التي يمكنك تخصيصها، والتي ستؤثّر على شيفرة SVG المتولّدة: الخيارات الموضّحة في الصورة أعلاه هي الخيارات المستحسنة لتوليد SVG للويب. يمكنك بالطبع اختيار تحويل النص إلى حدود خارجية إذا لم تكن ترغب باستخدام خط ويب، وكما ترى يوفر لك Illustrator خيارًا أيضًا للقيام بذلك عند التصدير. يحدد خيار موقع الصورة (Image Location) فيما إذا سيتم تضمين أي صور نقطية داخليًا في الـ SVG الخاص بك أو خارجيًا مع رابط يدلّ عليه في الـ SVG. ومجددًا، هذا يعتمد على ما تحتاجه. يمكن أن تزيد الصور المضمنة داخليًا في الـ SVG حجم الملف بشكلٍ كبيرٍ. آخر مرة أرسل مصممٌ لي SVG مع صورة مضمنة فيه، كان حجم الملف أكثر من 1 ميغابايت! بعد حذف تلك الصورة (وبسبب تأثيرات Photoshop المستخدمة المذكورة سابقًا)، انخفض حجم الملف إلى أكثر من 100 كيلو بايت! لذا، اختر بحكمة. يمنحك خيار خصائص CSS الحرية لاختيار كيف تريد إنشاء الأنماط داخل الـ SVG: استخدام خاصيات التقديم، الأنماط المضمنة داخليًا، أو داخل وسم <style>. هذا أيضًا مسألة تفضيل وتعتمد على الطريقة التي تنوي بها التعامل مع SVG بمجرد تضمينها. إذا لم تكن الشخص الذي سيفعل ذلك، فتأكد من التشاور مع المطور لتحديد الخيار الذي يناسب احتياجاته على أفضل وجه. كلّما قلّ عدد المنازل العشرية، قلّ حجم ملف SVG. يجب أن تكون منزلة عشرية واحدة كافية بشكلٍ عام، لذا سأتّبع ذلك. لاحظ أنّه إذا اخترت 3 أو 4 منازل عشرية، مثلًا، ثمّ استخدمت أداة تحسين لتحسين SVG وخفض هذا الرقم إلى 1، فقد ينتهي بك الأمر إلى SVG معطوب، لذا من الأفضل اختيار هذا الخيار في وقت مبكر. هناك المزيد في لائحة الخيارات التي قمت بتغطيتها. كتب Michaël Chaize من Adobe مقالةً ممتازةً حول لائحة التصدير توضّح ما يفعله كل خيار تمامًا. أوصي بشدّة بمراجعة مقالته: تصدير SVG للويب مع Illustrator CC الآن، في وقت كتابة هذا المقال، سيبقى Illustrator يولّد شيفرةً غير ضرورية مثل البيانات الوصفية للمحرر، والمجموعات الفارغة، من أمورٍ أخرى، لذا ستحتاج إلى تحسين الـ SVG بشكلٍ أكبر بعد تصديره، سواء كان ذلك باليد، أو باستخدام أداة تحسين SVG مستقلة. لكن قبل أن ننتقل إلى جزء التحسين، أريد أن أشير إلى أنّك قد ترغب أو لا ترغب في التحقق من خيارٍ إضافي أثناء حفظ SVG: خيار "استخدم ألواح الرسم" ، في لائحة الحفظ: يعدّ هذا الخيار مفيدًا عندما تعمل مع صور SVG متعددة (مثل الأيقونات) وتستخدم لوح رسم لكلّ أيقونة. سيؤدي تصدير ألواح رسم متعددة إلى توليد ملفات svg. متعددة، ملف لكل لوح رسم (واحد لكل أيقونة). سيتم تعطيل هذا الخيار بشكلٍ افتراضي إذا كنت تعمل مع لوح رسم واحد. يعتمد اختيار تصدير ملف SVG واحد أو عدة ملفات SVG على كيفية تضمين SVG. مثلًا، إذا كنت تريد إنشاء SVG sprite لنظام أيقونات SVG، فهناك عدة طرق يمكنك من خلالها إنشاء واستخدام الـ sprite، وكلّ منها تتطلب منهجًا مختلفًا: تتطلّب إحدى التقنيات فصل الأيقونات في البداية، بينما تتطلّب الأخرى أن تكون الأيقونات جزءًا من صورة واحدة. سأكتب منشورًا مفصّلًا حول SVG sprite وخيارات لوح الرسم، لكن حتى ذلك الحين، يمكنك أن تأخذ نظرة عامة حول التقنيات المختلفة لـ SVG sprite في المقالة التالية التي كتبتها لـ24Ways.org: نظرة عامة حول تقنيات إنشاء SVG sprite للتحسين أو لعدم التحسين يوصى عادةً بتحسين SVG بعد تصديره من محرر رسومات باستخدام أداة تحسين مستقلة. أداة التحسين الحالية الأكثر شيوعًا هي الأداة المعتمدة على NodeJS والتي تسمى SVGO. لكن قد لا يكون من الأفضل دائمًا تحسين SVG، خاصة إذا كنت تنوي تحريكه. إذا كنت تنوي كتابة SVG كنص برمجي و/أو تحريكه، فمن المحتمل أن تقوم بإعداد بنية وثيقة معينة - غلاف المجموعات، وأسماء المعرفات التي لا تستخدمها/تشير إليها داخل الـ SVG لكنك تنوي استخدامها في جافاسكربت أو غيرها. ستتغير البنية إذا قمت بتحسين الـ SVG باستخدام SVGO (أو أي أداة تحسين أخرى). عادةً ما تزيل أدوات التحسين أي مجموعات ومعرّفات غير مستخدمة، بالإضافة إلى أنّها تطبّق العديد من التغييرات على الـ SVG للتأكد من تحسينه بشكلٍ جيد. لقد قمت بتحسين ملف SVG مرةً بعد تطبيق رسم متحرك عليه باستخدام <animate>. كان ملف الـ SVG معطوبًا والحركة بداخله، لأنّه تم تغيير البنية بالكامل. وهذا شيء يجب تذكره قبل تحسين SVG. تجنّب التحسين مستخدمًا أداة تحسين، إذا قمت بتحرير و/أو توليد SVG يدويًا ببنية معينة تحتاجها، وحسّن يدويًا قدر الإمكان. يمكن إزالة بعض مخلفات التحرير في بداية ونهاية الـ SVG يدويًا بسهولة، والمخلفات الأخرى مثل البيانات الوصفية والفئات المتولّدة من قِبل برامج التحرير التي لا تحتوي على خيارات تحسين SVG - مثل Sketch - قد يكون من الصعب تحسينها يدويًا. عمومًا، أنا لا أستخدم Sketch لتوليد SVG معقدة. أستخدم Illustrator أو Inkscape؛ إذ يأتي الأخير مع لائحة تصدير افتراضية تعطيك الكثير من الخيارات لتحسين الـ SVG قبل تصديره (انظر الصورة أدناه). يولّد Inkscape أنظف شيفرة SVG في وقت كتابة هذا المقال. أي إذا اخترت خيار تحسين SVG، لكن ضبابية واجهة المستخدم على شاشة شبكة العين وكذلك اعتمادها على X11 على OS X يجعل من الصعب استخدامه، لذلك أنا متمسكة حاليًا بـ Illustrator. إذا كنت تحتاج/تريد تحسين الـ SVG الخاص بك فإنني أنصحك بأداة SVGO، إذ يأتي SVGO مع مجموعة من الإضافات التي يمكن دمجها مع أي نوع من تدفقات العمل تقريبًا. 9- تواصل. تواصل مبكرًا ربما كان أهم نصيحة يمكنني تقديمها هو التواصل والقيام بذلك مبكرًا في عملية التصميم. أفترض أنّك الآن - المصمم الذي ينشئ SVG - لست الشخص نفسه المسؤول عن تطوير SVG (كتابة نص برمجي، التحريك، التضمين، …إلخ). تتطلّب كل واحدة من النصائح السابقة تقريبًا معرفة مرحلة التطوير وما ينوي المطور القيام به مع الـ SVG - كيف ينوون تضمينه، وكتابته كنص برمجي، وتصميمه، وتحريكه. لذا إذا لم تكن أنت نفس الشخص الذي يصنع القرارات لكلا المرحلتين، وما لم تكن تريد تضييع الكثير من الوقت في تكرار وتحرير الـ SVG، تحتاج إلى التأكّد من معرفتك لما يحتاج لمطور أن يفعله مع الـ SVG وما المنهجية (أو المنهجيات) التي سيتبعها. إذا كنت تعمل في مشروع له موعد نهائي ضيق، فمن المحتمل أنك لا تستطيع أن تضيع وقتًا كبيرًا في إجراء تغييرات ومراجعات على أصول الصور، عندما يمكنك تجنب ذلك عن طريق التواصل مبكرًا. يمكن أن يكون المصممون والمطورون أفضل أصدقاء لبعضهم بعضًا. تتطلّب طبيعة SVG أن تكون مراحل التصميم والتطوير مفتوحة على بعضها بعضًا، وهذا بدوره يتطلب من المصمم (المصممين) والمطور (المطورين) التحدّث قبل بدء عملية التصميم وطوال العملية أيضًا . ترجمة -وبتصرف- للمقال Tips for Creating and Exporting Better SVGs for the Web لصاحبته Sara Soueidan
  24. كنت أعمل البارحة على إنشاء الشرائح والعروض التجريبية المرافقة لحديثي القادم عن الشيفرة في موقع web directions الأسبوع المقبل. أحد العروض التجريبية التي أنشئها هو دليل أساسي لمفهوم المفتاح البسيط الذي يُستخدم لتبديل مظهر واجهة المستخدم من مضيء إلى مظلم وبالعكس، أحببته واستلهمته من مبدّل المظهر في تطبيق Medium المبين أدناه. مُخصِّص مظهر تطبيق Medium هو لوحة منبثقة بسيطة تتضمن مفتاح بسيط للتبديل من وضع مضيء إلى مظلم وبالعكس. الاختلاف الوحيد الذي أريده هو أن يشير مفتاحي إلى حالة المظهر الفعالة حاليًا بشكلٍ واضح. لذا بدلًا من تفعيل وتعطيل المظهر المظلم فقط مثل مفتاح Medium، أريد أن يبدّل المستخدم بين خيارَي مضيء ومظلم بوضوح. لا يوجد سبب محدد لذلك سوى أنّه تفضيل شخصي. هناك طرق أخرى للقيام بذلك أيضًا، يبقى أمر كيفية تصميمه تفضيلًا شخصيًا، طالما أنّه يعمل وقابلًا للفهم بسهولة من قِبل المستخدمين. بدأت أفكر كالعادة في كيفية تمييز هذا العنصر البسيط، مع التأكّد من سهولة الوصول إليه مباشرةً من البداية. لذا بدأت القيام بواجبي وقراءة وتعلّم كل ما بوسعي بشأن هذا الموضوع. كان من المهم بالنسبة إليّ التأكد أنّ هذا العرض التجريبي سهل الوصول حتى لو كان مجرد دليل سريع للمفهوم لحديث. أولًا وقبل كل شيء، بما أنّ شيفرة العرض التجريبي ستكون متاحة للعامة، كان لدي مسؤولية أكبر للتأكّد من أنّها سهلة الوصول، لأنني لا أريد نشر أي شيفرة لا يمكن الوصول إليها، خاصةً إذا كان هناك فرصة ليستخدمها الناس في مكان آخر. وأردت أن تكون الشيفرة جيدةً لسببٍ آخر هو أنّني ربما أرغب في إعادة استخدامها لمكونات أخرى في ورشة عمل قادمة لي حول تطوير الواجهة الأمامية. البدء بالشيفرة كما ذكرت أعلاه، بدأت أفكر في كيفية ترميز هذا العنصر لضمان سهولة وصوله إلى قارئات الشاشة. عندها أدركت (وأحسست بالغباء نوعًا ما) أنّ الوظيفة والترميز يعتمدان على الطريقة التي أريد أن يكون سلوك التبديل بها وكيف أريده أن يظهر. كان الأمر واضحًا جدًا بالنسبة لي: سيسمح المفتاح للمستخدم بالاختيار بين المظهر المضيء والمظهر المظلم، وسيكون المظهر المضيء هو الافتراضي. وفي هذه اللحظة تبادر إلى ذهني أزرار الانتقاء (radio buttons): خياران يمكن تفعيل أحدهما فقط في وقت واحد، هذا يمكن أن يصنع حالة استخدام ممتازة لأزرار الانتقاء القديمة الجيدة. كنت أعلم أنّه يجب عليّ اختيار شيء ما مختلف في حال أردت أن تبدو واجهة المستخدم وتتصرف بشكلٍ مختلف. مثلًا، إذا أردت لواجهة المستخدم أن تعرض "تفعيل/تعطيل النمط المظلم"، عندها لن أحتاج إلى استخدام أزرار الانتقاء لأنّ لدي خيارًا واحدًا فقط للتعامل معه وهو التشغيل أو إيقاف التشغيل، سيكون ذلك حالة استخدام رائعة لمربع تأشير (checkbox) أو عنصر زر <button> تبديل قديم جيد. تحليلات التصميم والوظيفة مترابطان، مما يساعد على التفكير في هذين الأمرين في وقت واحد عند التصميم لسهولة الوصول (وفي الحقيقة تصميم أي شيء بشكلٍ عام). ابدأ دائمًا وأبدًا بالتفكير حول الترميز وسهولة الوصول عند إنشاء المكونات بغض النظر عن مدى صغرها وبساطتها. دراسة كنت بحاجة -كما هو الحال دائمًا- إلى دعم نظريتي وتطبيقي العملي بالبحث الجيد، لذا بدأت بالقراءة، مراجعي الأولى التي ألجأ إليها هي المكونات الشاملة ومشروع A11y لـ Heydon Pickering. كما اتّضح، لدى Heydon مقالة رائعة حول أزرار التبديل تعلّمت منها الكثير، ولدى صديقي Scott O’Hara زر تبديل ARIA مضمّن في قسم الأنماط من مشروع A11y. لذلك، بطبيعة الحال، فحصتُ شيفرة الزر وقرأتُ مقالة Heydon للتأكّد فيما إذا كنت على الطريق الصحيح. تجدر الإشارة قبل المتابعة إلى أنّ هذه ليست مقالة حول كيفية إنشاء مفاتيح تبديل سهلة الوصول. مقالة Heydon تقوم بعمل رائع يغطي ذلك. النقاط الرئيسية التي استخلصتها شخصيّا من البحث أعلاه هي: هناك أنواع مختلفة من المفاتيح التي تبدو وكأنّها تقوم بأشياء مماثلة ولكنها تختلف اختلافًا جوهريًا عندما يتعلق الأمر بالترميز وسهولة الوصول. ليس بالضرورة أن تكون متشابهة، لمجرد أنّها مصممة لتظهر بنفس الشكل. أحتاج إلى التفكير بالسلوك الذي ستسلكه واجهة المستخدم، الشكل والصوت عند التبديل. أولًا التصميم وتجربة المستخدم، ثمّ الشيفرة. يمكن استخدام مفتاح التبديل للتبديل بين خيارين منفصلين، أو يمكن استخدامه لتشغيل خيار واحد أو إيقاف تشغيله (أو مثل تفعيل/تعطيل خيار). هنا تبدأ اختلافات التطبيق بالظهور. إذا كنت تبدّل بين خيارين منفصلين، فإنّ استخدام أزرار الانتقاء القياسية يبدو منطقيًا. يتم استخدام أزرار الانتقاء عندما تريد من المستخدمين اختيار خيار واحد فقط من خيارين أو أكثر، لذا فإنّ هذه حالة استخدام مثالية لهم. لديهم أيضّا إمكانية وصول أساسية وسيتم وضع إشارة إلى جانب الخيار الذي تم تفعيله. تأكّد فقط أنّك لا تحدّ من إمكانية الوصول لأيٍّ منهما في HTML أو باستخدام CSS. إذا كان الهدف من التبديل هو تفعيل/تعطيل ميزة (أو تشغيلها/إيقافها)، هناك طرق أخرى لذلك: استخدام صندوق تأشير لتأشير/إلغاء تأشير (تفعيل/تعطيل) هذا الخيار. استخدام زر <button> يكون له حالتان: مضغوط وغير مضغوط. تتطلب هذه الطريقة استخدام ARIA والذي بدوره سيتطلب جافاسكربت لتعمل التقنيات المساعدة بشكل صحيح (يتم تحديث قيم خاصية ARIA عند الضغط باستخدام جافاسكربت). اقرأ مقال Heydon للمزيد من التفاصيل عن هذه الطريقة. تعني هذه الطريقة أن لديك خيارًا واحدًا فقط، وهذا يعني بدوره أنّ زر التبديل له تسمية واحدة مرتبطة فقط، وربما لن يبدو زر "التبديل المزدوج" بعد الآن، إلا إذا: حالات زر التبديل المزدوج (التي تشير إلى تشغيل/إيقاف) سيكون لها نصّا واضحًا يصف الحالة الحالية الفعالة، لذا ستبدو مثل هذا المثال من العرض التجريبي لـScott في مشروع A11y: بالنسبة لاستخدامي لمحوّل المظهر، فهو خيارٌ واضحٌ: بما أنّ المستخدم لديه خيارات (مظلم ومضيء)، أردت استخدام أزرار الانتقاء. لم أكن أريد أن أقول "تشغيل/إيقاف تشغيل الوضع المظلم" إنّما أردت أن أقول "تفعيل الوضع المضيء أو تفعيل الوضع المظلم". سيكون الحل هو HTML وCSS فقط، ولا يتطلب جافاسكربت، وستتم إتاحة إمكانية الوصول بشكلٍ افتراضي. تحليل دَع لديك فكرة واضحة حول كيفية تصميم المكون وماذا يُفترض أن يفعل أو لا يفعل، واجعل الترميز والتصميم يعتمدان على هذه الفكرة. فحص الشيفرة الآن، وبعد قراءة وفحص المصادر السابقة، قررت أن أرى كيف يقوم الناس بالتبديل. بعد كل ذلك، كنت أعلم أنني سأجد الكثير من الأمثلة لمفاتيح التبديل التي تبدو وتتصرف مثل هذا المثال الحي على codepen، من الرائع دومًا أن أرى كيف يفعل الآخرون ذلك - ربما أفتقد بعض التقنيات الرائعة التي يمكن أن أتعلمها للتصميم، أو ربما أفتقد بعض المعلومات المهمة عندما يتعلق الأمر بالترميز وقابلية الوصول. نظرًا لأنني كنت أعمل في codepen على نسختي الخاصة من هذا المفتاح (شاركت العرض التجريبي أدناه)، أظنُّ أنني فحصت مفتاح التبديل الخاص بـcodepen: المفتاح الذي تستعمله لاختيار فيما إذا إذا كنت تريد لتصميمك أن يكون عامًا أو خاصًا. تحليل سريع من المفيد والممتع أن تتعلم من عمل الآخرين بفحص شيفرتهم (سواء ذلك عن طريق أدوات المطور [devtools] أو على codepen). مفتاح الخصوصية في Codepen يعرض مفتاح التبديل في codepen بعض السلوكيات الغريبة التي لاحظتها سابقًا ولكن لم أشعر أبدًا بالفضول لأقوم بال"تنقيح". يُظهر الفيديو التالي هذا السلوك: Your browser does not support the video tag. تجدر الإشارة أولًا إلى أنّني كنت محتارة بشأن هذا المفتاح. دائمًا ما يعطي اللون الأحمر مقابل الأخضر حلقة ردود فعل غريبة. إذا جعلت التصميم خاصًا، سيتحول اللون للأخضر، وجعله عامًا سيجعل اللون أحمر. بالنسبة لي فإنَّ الأخضر يعني أكثر من ذلك "التصميم قابل للوصول من قِبل الأشخاص، إنّه متاح" واللون الأحمر أكثر من ذلك "هذا التصميم مغلق، لا يُسمح للأشخاص بالوصول". ولكن بالنسبة لـcodepen فإن هذه الألوان ترمز إلى أشياء أخرى. وبشكل أكثر تحديدًا يرمز الأحمر إلى "المنطقة العامة = المنطقة الخطرة" أو "احذر هذا التصميم متاح للعامة وهذا غير جيّد". (آسفة لأنّي درامية جدًا، ولكن هذا هادفًا). ثمّ هناك سلوك الأخطاء المبيّن في الفيديو أعلاه (إذ أنّ النقر على نفس التسمية يغيّر قيمة المفتاح إلى قيمة التسمية الأخرى). كنت فضولية مجددًا لمعرفة سبب ذلك، شغّلتُ أدوات المطور وفحصت الشيفرة. وجدت أنَّ مفتاح codepen هو صندوق تأشير واحد يبدو أنّه يحتوي تسميتين. لذلك عند الضغط على "عام" و"خاص" معًا عدة مرات يؤدي إلى تشغيل وإيقاف تشغيل المفتاح عدة مرات. بكلماتٍ أخرى، هذا هو السبب في أنَّ رد الفعل/ السلوك المرئي للمفتاح لا يُطابق هدف المفتاح أو ما يبدو أنّه يعمل. كتبت تغريدةً حول هذا الخطأ، وسببه المحتمل واقترحتُ حلًا بديلًا يُصلح الأمر. بدأت في الحصول على ردود وآراء من مطورين آخرين، مما أدّى إلى نقاش جيد ومفيد للغاية استمتعت به تمامًا (أحد الجوانب الإيجابية القليلة في تويتر). بالطبع، كان الإجماع العام هو أنّ المفتاح يحتاج إلى إصلاح. لكن كيفية إصلاحه تعتمد بشكلٍ كبير على ما يريد فريق codepen القيام به: إذا كان من المفترض أن يكون مفتاح التبديل هو زر تفعيل/تعطيل للخيار "خاص" عندها: هذا يعني أنّ نمط التبديل تشغيل/إيقاف المذكور في القسم السابق أعلاه سيكون الحل المثالي. هذا يعني أنّه سيتم تجاهل التسمية "عام" والاحتفاظ بالتسمية "خاص" فقط، مع إشارة واضحة إلى أنّ المفتاح المجاور لها يعمل على إيقاف الخيار أو تشغيله، وهذا يقودني إلى نقطة أخرى: يجب إمّا أن يتغير التمثيل البصري للمفتاح بالكامل أو يتم تعديله: إذا كان يجب أن يبقى زر المفتاح كما هو بصريًا (أي الحفاظ على سلوك المفتاح المزدوج "نقل هذا التبديل يسارًا ويمينًا")، أقترح إضافة تسميات "تشغيل/إيقاف" له بشكلٍ شبيه لما فعله Scott في عرضه التجريبي مشروع A11y، لأنّه وفقًا لإرشادات الوصول لمحتوى الويب (WCAG)، يجب ألا تستخدم اللون فقط لنقل المعلومات. تحتاج الألوان أيضًا أن يكون لديها تباين كافٍ إذا كنت تستخدمها. ومرةً أخرى، كنت أعيد النظر في ألوان الأحمر والأخضر لأنّها قد تربك شخصًا ما مثلما أربكتني لفترةٍ من الوقت. (اقترح عليّ أحدهم الرمادي والأخضر، لأنّ "إضافة اللون يمكن أن تدلّ بشكلٍ كبيرٍ على الحالة الفعّالة (بدون لون مقابل لون يواجه لونين منفصلين)"). يمكن أن تكون البنية الأساسية والترميز لهذه الحالة (تسمية واحدة فقط): صندوق تأشير، يمكن أن يكون مفعّلًا أو غير مفعّل. أودّ اقتراح استخدام تصاميم صندوق تأشير بسيطة (ربما رائعة) لهذا السلوك والتخلّي عن أسلوب التبديل المزدوج بالكامل. زر <button> مع خاصية aria-pressed (و aria-checked إذا احتجت)، إذ تشير حالة الضغط إلى أنّ التصميم خاص، وحالة عدم الضغط إلى أنّ التصميم غير خاص، أي أنّه عام. سيتغير تصميم الزر في هذه الحالة أيضًا، وقد يتطلّب سلوكه استخدام جافاسكربت لتعديل قيمة خواص ARIA عند الضغط. إذا كان من المفترض أن يعرض مفتاح التبديل خيارين بشكلٍ واضح ويمكّنهما: عام وخاص برأيي، عندها سيكون استخدام زوج من أزرار الانتقاء له معنىً كبير. يعلن زري الانتقاء، كلّ منهما بتسمية خاصة به، عن تقنيات جذابة كخيارين منفصلين يجب على المستخدم اختيار أحدهما، وسهلة الوصول تمامًا عبر لوحة المفاتيح، ولا تتطلب ARIA أو جافاسكربت لتعمل، واستخدم قليلًا من الـCSS لها لتخلق تصميم زر "التبديل المزدوج" إذا أردت (حقيقةً لا أعرف ماذا أسميه بشكلٍ آخر) وستكون قمت بكل العمل. قد يختلف قليلًا الترميز والتصميم لمثل هذا الحل بين المطورين، ولكن جوهر الشيفرة سيكون نفسه. وهذا سيقودني إلى التجربة الحية… مثال حيّ هذا تنفيذي لمفتاح تبديل بين خيارين. لقد قمت بإنشاءه ليس كحلّ لمفتاح codepen، لكن ببساطة كمفتاح لتبديل المظهر مضيء/مظلم الذي أستخدمه في حديثي: سيكون مفتاح التبديل في سياق مجموعة أكبر من عناصر النموذج، لهذا السبب ليس لدي أيّ مجموعة حقول (fieldset) أو وسيلة إيضاح (legend) في العرض التجريبي. وأعلم جيّدًا أنّك قد تعدّل الشيفرة لعدم الحاجة إلى عناصر span إضافية في الترميز وتستخدم عناصر زائفة بدلًا منها، ولكنني على أيّة حال قد اخترت هذه الطريقة، فقط لأنني أريدها. جرّب أن تلعب بالشيفرة وتزيل تعليقات بعض التصاميم (حاول إزالة عناصر span بشكلٍ خاص لتشاهد كيف يبدو مفتاحي بدونها) لتحلل الشيفرة وتمتلك فكرة أفضل عن خياراتي وما الذي كنت أحاول تحقيقه. أضفت أيضًا تصميمًا للتأكّد من أنّ مستخدمي لوحة المفاتيح يمكنهم رؤية مكان التركيز، إذ أنني غطيت أزرار الانتقاء الافتراضية بعناصر span، ولم يتم تسليط الضوء على التسميات بشكلٍ افتراضي. استخدمت :focus-visible فقط في البداية (مع polyfill) لكنه لم يعمل كما هو متوقع في متصفحي فايرفوكس وسفاري، لذا انتهيت بإضافة :focus مجددًا واستخدمته بدلًا من :focus-visible. وأعلم أيضًا أنّ تصميم التركيز ليس جميلًا جدًا، لكن هذا العرض التجريبي هو مجرد دليل للمفهوم لذا لا يحتاج أن يكون جميلًا بشكلٍ ملفت للنظر. إذا كنت ترغب في مشاهدة عرض تجريبي آخر يستخدم أيضًا أزرار انتقاء لكن ينفذّها بطريقةٍ مختلفةٍ، بدون عناصر spanواستخدام عناصر زائفة، راجع هذا التصميم على codepen لصديقي Scott O’hara: كلمات أخيرة دعني أبدأ بالقول أنني قد أكون مخطئة. أعلم أنّ مستخدمي codepen قد يريدون شيئًا آخر مختلف تمامًا، أو يفكرون في شيء ما مختلف تمامًا. لذلك لا تهدف هذه المقالة إلى إعادة تصميم زر التبديل في codepen، إنّما تقدم توثيقًا لبحثي وتدريبي على التفكير أثناء العمل على إنشاء زر التبديل الخاص بي لحديثي. سأحتاج أن أقوم بالمزيد من الأبحاث عندما يحين الوقت لمتابعة عملي على ورشة العمل الجديدة الخاصة بي، وقد تتغير الأمور، وأعلم أنني سأتعلّم وأعرف المزيد عندما أنشئ مفتاحي التالي. لكن، حتى ذلك الحين، أعلم أنني حصلت على منشور المدونة هذا للإشارة إلى بعض الأفكار والآراء التي دارت في ذهني عندما اتخذت الخطوة الأولى لذلك. أتمنى أن تجد شيئًا مفيدًا بقراءتك لهذا المقال. إذا حصل ذلك بالفعل، فشكرًا لقراءتك. شكرًا. ترجمة -وبتصرف- للمقال On Designing and Building Toggle Switches لصاحبته Sara Soueidan
  25. …إنّه أصعب مما ظننت اليوم هو أحد تلك الأيام التي بدأت مع بحث غوغل عن سؤال/مسألة أخرى تتعلق بسهولة الوصول (accessibility). أنا أعمل على مشروع جديد لعميلتي Provta وجزء من هذا المشروع هو بناء تلميح مساعدة بسيط وجميل يشرح للقارئ/المستخدم عن ماهيّة حاسبة فرامنغهام. يتم تشغيل التلميح بواسطة أيقونة مساعدة صغيرة مثل تلك التي تظهر في الزاوية اليمنى العليا من لقطة الشاشة هذه: كما هو الحال مع كل مشروع، فإنّ البدء بالتفكير ما هو عنصر HTML الذي سأستخدمه لترميز المكوّن هو أوّل شيء فعلته. لكن اتّضح أنّه لا يوجد عنصر HTML لترميز تلميح كهذا. وعندما لا يكون لدينا عناصر دلالية لترميز مكوناتنا، فإنّنا نواجه التحدي في التأكّد من أنّ التقنيات المساعدة (ATs)- التي عادةً ما تكون قادرة على فهم ونقل المعنى عن طريق الدلالات - قادرة على فهم ما هي عناصرنا بالفعل وماذا تفعل. هل تنقذنا خاصية ARIA؟ جعل العناصر سهلة القراءة من قِبل التقنيات المساعدة عندما لا يكون HTML كافيًا باستخدام خاصيّات ARIA. مثلًا، يمكنك الحصول على ذلك بإنشاء شريط تقدّم بدون استخدام عنصر <progess> من HTML غير المدعوم جيّدًا. يمكن جعل هذا الشريط يبدو ويتصرّف كأنّه شريطًا باستخدام CSS، لكن إذا كنت تستخدم عناصر div وspan لبنائه، فأنت بحاجة لإعطاء التقنيات المساعدة المزيد لصنع شيءٍ ما من تلك العناصر غير الدلالية. هذا هو المكان الذي تصبح فيه خاصيّات ARIA مثل role="progressbar"‎ ورفقائها المساعدين aria-valuenow، aria-valuemin وaria-valuemax (من بين أمور أخرى) مفيدةً. يمكنك تحديث القيمة داخل aria-valuenow باستخدام بعض الجافاسكربت كلّما تقدّم المستخدم في الخطوات قدّمتها له وهذا جيّد عمومًا. يمكنك قراءة المزيد حول هذا الموضوع على MDN إذا كنت مهتمًا أو غير معتاد على ذلك. عظيم! لدينا قيمة الخاصيّة ARIA لمساعدتنا في الإشارة إلى أن عنصرًا معينًا هو تلميح: tooltip. باستخدام role="tooltip"‎ فإنّ التقنيات المساعدة تعرف أنّ هذا العنصر هو بالفعل تلميح. لكن عادةً ما يتم تشغيل التلميحات بواسطة إجراء يتم تنفيذه على عنصر آخر. دعنا أولًا نعود إلى الأساسيات. حسب ويكيبيديا: التلميح أو infotip أو النص المُساعد هو عنصر واجهة مستخدم رسومي شائع، يُستخدم جنبًا إلى جنب مع مؤشر، والذي يكون عادةً مؤشر الفأرة. عندما يمرّر المستخدم مؤشر الفأرة على عنصر ما، بدون الضغط عليه، قد يظهر التلميح بشكل مربع صغير يتضمّن معلومات حول العنصر الذي تمّ تمرير المؤشر فوقه. لا تظهر التلميحات عادةً على أنظمة التشغيل في الأجهزة المحمولة، لأنّه لا يوجد مؤشر (على الرغم من أنه قد يتم عرض التلميحات عند استخدام الفأرة). المشكلة التي أواجهها هي هذا الجزء "لا تظهر التلميحات عادةً على أنظمة التشغيل في الأجهزة المحمولة، لأنّه لا يوجد مؤشر"، لأنّ مستخدمي الهواتف المحمولة لهم الحق أيضًا في الحصول على المعلومات على الصفحة مثل المستخدمين الذين لا يستخدمون الهواتف المحمولة. يجب أن تكون قادرًا على فهم نقاط فرامنغهام سواءً كنت تستخدم فأرة أو إصبعك، لذا فإنّ هذا التلميح يجب أن يكون سهل الوصول في جميع الأحوال، وهنا تبدأ المتعة. تشغيل فتح/إغلاق التلميح علمتُ أنني لا أستطيع الاعتماد على التمرير وحده (بالرغم من أن العميلة طلبت التمرير فقط) لأنّه إذا لم يكن لديك شاشة تعمل باللمس مع قلم رقمي فاخر يسمح بتشغيل التمرير، فلن تكون قادرًا على رؤية النص المساعد داخل التلميح، وهذا غير مقبول. بدأت في عرض التلميح عندما يتم التمرير والضغط على أيقونة المساعدة معًا. من الجدير بالذكر عند هذه النقطة أنّ التلميحات تعمل أحيانًا على شاشة اللمس والمؤشر بدون أن تضطر للقيام بأي عملٍ إضافي، مثل تلك التي تظهر كتسميات مساعدة في حقول الإدخال input، مثل هذا المثال لـ Heydon Pickering. في سيناريوهات مثل عندما يكون role="tooltip"‎ مع aria-describedby فإنّ هذا كافٍ لتعرف التقنيات المساعدة أنّ هذا الجزء من النص يحتاج إلى تلميح يصف محتوى أو وظيفة حقل الإدخال. من السهل جدًا وضع علامة على التلميح في هذه الحالة لأنّه واضح بالفعل بما فيه الكفاية. كل ما تبقى لك هو إظهار/إخفاء النص عندما يتم التركيز على حقل input، وهذا يمكن أن يتم باستخدام سطري CSS فقط. التجربة ممتازة بالنسبة لشاشات اللمس والفأرة، وسيجرّبها الجميع بنفس الطريقة. ومع ذلك فإنّ الأمور غير واضحة بشكلٍ جيّد عندما يكون لديك مثال كمثالي في المشروع الذي أعمل عليه. ويبدو أنني لست الشخص الوحيد الذي كان هنا وكان مرتبكًا تمامًا في كيفية القيام بهذا الأمر. إليك بعض الأفكار التي دارت بذهني عندما كنت أفكر في تنفيذ هذا: استخدام عنصر <button> لتشغيل فتح وإغلاق التلميح. سيكون التلميح مخفيًا بالحالة الافتراضية لذا فإنّ التقنيات المساعدة لن تقرأه بصوتٍ عالٍ أثناء تحرّك المستخدم إلى أسفل الصفحة. يقاطع التلميح تدفق المحتويات في حالة مشروعنا لذا يجب عرضه عند الطلب فقط. "عند الطلب" تعني عندما يمرر/يضغط/ينقر/يركّز للتشغيل سيُخفى التلميح في الحالة الافتراضية باستخدام display: none ويُعرض فقط عند الطلب. بما أنّ الدلالات و"كيف ستقرأ الآلات هذا" هي التي ستحدد كيف سأبدأ بكتابة شيفرة HTML، فكرت: إذا استخدمت <button> يجب أن أستخدم aria-controls للإشارة إلى عناصر التحكم للزر والتي ستُظهر/ستخفي بعض العناصر عندما تُضغط. الأمور جيّدة. عند الحديث عن الدلالات، يمكنني أيضًا استخدام رابط <a> يرتبط بقسم معين في الصفحة، وهو في حالتي العنصر الذي يحتوي على النص المساعد. عند استخدام رابط، سينتقل المستخدم إلى قسم الصفحة الذي يرتبط به هذا الرابط. أنا أكره القفز في الصفحات، أتجنّبه مثل الطاعون إلا إذا كان قرار القفز مقصودًا. (مثلًا، رابط "العودة إلى الأعلى" في كل صفحة طويلة). أحتاج جافاسكربت لتجنب القفز عند استخدام <a>، ولجعل <button> يفتح ويُغلق التلميح. أريد تجنّب جافاسكربت واستخدامه فقط عند الضرورة لسببٍ شاملٍ. ماذا لو كان المستخدم رجلًا كبيرًا ويستخدم جهازًا قديمًا تمّ تعطيل الجافاسكربت به؟ عادةً، أبحث عن آراء ثانية، عندما أحتار بين حلّين وعندما تبدأ الأفكار تدور في ذهني حول استخدام جافاسكربت وعدم استخدامها. مصدري الأول لرأيٍ ثانٍ هو غوغل. فكرت: "يجب أن يكون شخصًا ما قد بنى واحدًا من قبل وكذلك يجب أن يكون شخصًا غيره قد قام بذلك، لذا دعنا نرى كيف تعاملوا معه وحلّوا هذه المشكلة". عند تلك النقطة كنت مازلت أفكر "أريد القيام بذلك بدون استخدام جافاسكربت إذا كان ذلك ممكنًا"، لكن صدّق أو لا تصدّق أنا أحب جافاسكربت ومنفتحة تمامًا بشأن استخدامها إذا كان الحلّ المعتمد عليها أفضل من غيره من الحلول. قادني البحث في غوغل إلى سؤال كان بالضبط نفس سؤالي. وجدت الكثير من الأسئلة المتشابهة لكنهم كانوا جميعًا أشبه بمثال تلميح حقل الإدخال. وجدتُ مناقشةً قديمةً جدًا بدأتها Zoe Gillenwater التي كان عندها نفس السؤال الذي لدي الآن في عام 2011. أدرك أنّ بعض التفاصيل التقنية هناك ستكون أو قد تكون غير صالحة اليوم لكن المبادئ العامة تبقى صالحة. أنصح بشدّة بقراءة هذه المناقشة قبل متابعة قراءة هذا المقال لأنّ كل شيء آخر هنا يعتمد على بعض الأفكار التي حصلت عليها من تلك المناقشة. النقاط الرئيسية المذكورة في هذه المقالة: تجنّب استخدام العنصر <button> وتمسّك باستخدام العنصر <a> لأنّه يمكن استخدام الروابط لأي شيء تقريبًا بينما يجب ترك الأزرار للاستخدام في النماذج. يجدر بي القول أنّني لا أتفق مع هذه النقطة، إذا كنت تعرف سببًا جيّدًا لتجنّب استخدام <button> الرجاء إخباري. عند استخدام العنصر <a> يجب أن تأخذ بالحسبان/تتذكر ما يلي: إذا كنت تستخدم <a> وتمّ تضمين النص المساعد بداخله: ستبدو الشيفرة كالتالي: <a href="#" aria-describedby="#tip"> <!-- your icon here, img or svg --> <span id="tip"> Your hint text here </span> </a> سيكون النص قابلاً للقراءة مع التدفق (وهو غير مناسب في حالتي) لأن محتوى النص المساعد يقاطع تدفق المعلومات والبيانات المعروضة في المحتوى الرئيسي. يمكنك إخفاء وإظهار النص باستخدام CSS فقط مع display: none وdisplay: block على التوالي عندما يتم التمرير فوق الرابط. هذا جيد للمستخدمين المبصرين باستخدام الفأرة. لكن… إذا أخفيت النص باستخدام display: none داخل <a> وعرضته عند التركيز فإنّ التقنيات المساعِدة لن تكون قادرة على قراءة النص المعروض لأنّ محتويات الرابط تُعلن فقط عند focus الأولى ولن يتم إعادة الإعلان عنها عندما يُعرض النص داخل الرابط مع display: block. باختبار ذلك، لاحظتُ أنّه بما أنّ الرابط لا يرتبط فعليًا بأي مكان فإنّ المستخدم سينتقل إلى أعلى الصفحة عندما ينقر الرابط في شاشة تعمل باللمس. هذا أمر فظيع بشكلٍ خاص في حالتنا لأنّه لدينا صفحة طويلة والانتقال إلى الأعلى سيُربك المستخدمين. يمكن حلّ هذا باستخدام جافاسكربت preventDefault لمنع الحدث الإفتراضي عند النقر. (برأيي: أنا لا أحب حقيقة أنّ الرابط لا يرتبط بأيّ مكان. إنّه رابط ذاتي التضمين، برأيي، إذا كان هذا يصنع فرقًا فإنّه ينفي دور الرابط للبدء به. كنت أعلم أنني لا أريد استخدام هذا على الرغم من أنّني اختبرته بدافع الفضول.) إذا كنت تستخدم <a> وجعلته يرتبط بجزء نصي منفصل (ليس من فروع الرابط نفسه): ستكون الشيفرة كالتالي: <a href="#tip"><!-- icon here --></a><div id="tip"> <!-- tooltip text here --> </div> لا يزال الرابط يسبب انتقالًا في الصفحة ما لم يتم استخدام جافاسكربت. في شاشات اللمس، يؤدي النقر على الرابط إلى التركيز عليه وهذا بدوره يفتح التلميح، لكن تبقى صورة الرابط كما هي ولا توجد طريقة لإغلاق التلميح ما لم ينقر المستخدم أي مكان خارجه لإزالة التركيز عليه، وهذا غير بديهي لأنّ هذا سينطوي على الكثير من الجهد المعرفي والتخمينات والتساؤلات "كيف أغلق هذا" والإحباط، خاصةً أنّ التلميح يغطي المحتوى عندما يفتح. يأتي كل حلّ لا يستخدم جافاسكربت بجانب سيء للغاية يؤثّر سلبًا على تجربة المستخدم. جافاسكربت هي الطريقة التي تجعل هذا يعمل مع كل مستخدم بشكلٍ صحيح في جميع الأحوال. إنّها تحلّ كل واحدة من المشكلات المذكورة، باستثناء مشكلة "هذا الرابط لا يرتبط بأي مكان" والتي لا يمكن حلّها إلا بعدم استخدام رابط لا يشير إلى أيّ مكان. D: (قد لا يوافق البعض على أن الرابط الذي لا يرتبط بأيّ مكان ليس خاطئًا أو غير صالح عمليًا، لكن لا أظنّ ذلك شخصيًّا.)، انتهي بي الأمر باختيار حلّ يعمل باستخدام جافاسكربت ولديه آثار غير سيئة للغاية. يغطي هذا الأفكار والاعتبارات الرئيسية التي كانت لدي إلى حدٍّ كبيرٍ. خاتمة جافاسكربت أمر ضروري لبناء مكونات تفاعلية متاحة بالكامل. يمكنك بالتأكيد الابتعاد عن استخدامها في بعض الحالات، لكن الكثير من قابلية الوصول تتطلّب جافاسكربت. بعض أدوار وخاصيات ARIA ضرورية للغاية لجعل المكونات سهلة للوصول، والكثير منها لن تسلك السلوك الذي تحتاجه ما لم تجعلها تعمل مع جافاسكربت. بما أنّ عميلي هو شركة صحيّة، فمن الممكن أن يكون لدى المستخدمين شكل من أشكال الإعاقة أو غيره لذا يجب أن تكون الجافاسكربت معطّلة لديهم، لذا من الآمن التفكير بعدم استخدام جافاسكربت بدلًا من تعطيلها. إذا كنت مهتمًا بمعرفة المزيد حول ما تتطلّبه الخاصيّات، أنصح بشدّة بفحص مقاييس دور ARIA من WhatSock. فهي توفر نظرة عامة سهلة القراءة. أيضًا لدى Paul J Adam عرض تجريبي يظهر الطرق المختلفة لعرض تلميحات عند التمرير. لا تزال أمثلته تستخدم جافاسكربت لبناء مكونات أكثر قابلية للوصول بتشغيل خاصيات ARIA عندما تفتح/تغلق التلميحات. هذا العرض يستحق تفحّصه بالتأكيد. ترجمة -وبتصرف- للمقال Building a fully-accessible help tooltip لصاحبته Sara Soueidan
×
×
  • أضف...