المحتوى عن 'ملاحظات للعاملين بلغة php'.



مزيد من الخيارات

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المُحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
  • جافاسكريبت
    • Node.js
    • React
    • AngularJS
    • Vue.js
    • jQuery
    • Cordova
  • HTML
    • HTML5
  • CSS
  • SQL
  • لغة C#‎
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • Sass
    • إطار عمل Bootstrap
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • برمجة أندرويد
  • لغة Swift
  • لغة R
  • لغة TypeScript
  • ‎.NET
    • ASP.NET
  • الذكاء الاصطناعي
  • صناعة الألعاب
    • Unity3D
    • منصة Xamarin
  • سير العمل
    • Git
  • سهولة الوصول
  • مقالات برمجة عامة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • خوادم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)
  • مقالات DevOps عامة

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • التسويق بالرسائل النصية القصيرة
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • مقالات عمل حر عامة

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
  • أندرويد
  • iOS
  • macOS
  • ويندوز
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
  • أساسيات استعمال الحاسوب
  • مقالات عامة

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

  • الأقسام
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة البرمجة
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات
    • أسئلة الشهادات المتخصصة

التصنيفات

  • ريادة الأعمال
  • العمل الحر
  • التسويق والمبيعات
  • البرمجة
  • التصميم
  • DevOps

تمّ العثور على 28 نتائج

  1. قراءة بيانات خام مُرسَلة عبر طلب POST تُنظَّم عادةً البيانات المُرسلة عبر طلب POST في أزواج مفتاح/قيمة مع نوع MIME ‏application/x-www-formurlencoded، ومع ذلك تتطلب العديد من التطبيقات مثل خدمات الويب (web services) بيانات خام بتنسيق XML أو JSON غالبًا لتُرسَل، يمكن قراءة هذه البيانات بإحدى طريقتين. الأولى هي عبر مجرى يوفر الوصول إلى هيكل الطلب الخام php://input. لدينا الشيفرة التالية في الإصدار PHP < 5.6: $rawdata = file_get_contents("php://input"); // ‫بفرض أننا نحصل على JSON $decoded = json_decode($rawdata); الثانية عبر المتغير ‎$HTTP_RAW_POST_DATA الذي هو متغير عام يتضمن بيانات خام مُرسَلة عبر طلب POST وهو مُتاح فقط إذا كان الموجِّه always_populate_raw_post_data في ملف php.ini مفعّلًا. $rawdata = $HTTP_RAW_POST_DATA; // ‫إذا كنا نريد الحصول على XML $decoded = simplexml_load_string($rawdata); أُهمل هذا المتغير منذ الإصدار PHP 5.6 وأُزيل في الإصدار PHP 7.0. لاحظ أنّ هذه التوابع غير متاحة عندما يُضبَط نوع المحتوى إلى القيمة multipart/form-data التي تُستَخدم لرفع الملفات. قراءة بيانات مُرسَلة عبر طلب POST تُخزَّن البيانات المُرسَلة عبر طلب POST في المتغير ذو النطاق العام العالي ‎$_POST على شكل مصفوفة ترابطية. لاحظ أنّ الوصول إلى عنصر مصفوفة غير موجود يولّد ملاحظة لذا يجب التأكد دومًا من وجود العنصر باستخدام الدوال isset()‎ أو empty()‎ أو عامل تجميع null، مثال: $from = isset($_POST["name"]) ? $_POST["name"] : "NO NAME"; $message = isset($_POST["message"]) ? $_POST["message"] : "NO MESSAGE"; echo "Message from $from: $message"; // ‫الإصدار PHP ≥ 7.0 $from = $_POST["name"] ?? "NO NAME"; $message = $_POST["message"] ?? "NO MESSAGE"; echo "Message from $from: $message"; قراءة بيانات مُرسَلة عبر طلب GET تُخزَّن البيانات المُرسَلة عبر طلب GET في المتغير ذو النطاق العام العالي ‎$_GET على شكل مصفوفة ترابطية. لاحظ أنّ الوصول إلى عنصر مصفوفة غير موجود يولّد ملاحظة لذا يجب التأكد دومًا من وجود العنصر باستخدام الدوال isset()‎ أو empty()‎ أو عامل تجميع null، مثال: مثال (بفرض لدينا الرابط ‎/topics.php?author=alice&topic=php‎): $author = isset($_GET["author"]) ? $_GET["author"] : "NO AUTHOR"; $topic = isset($_GET["topic"]) ? $_GET["topic"] : "NO TOPIC"; echo "Showing posts from $author about $topic"; // ‫الإصدار PHP ≥ 7.0 $author = $_GET["author"] ?? "NO AUTHOR"; $topic = $_GET["topic"] ?? "NO TOPIC"; echo "Showing posts from $author about $topic"; معالجة أخطاء رفع ملف قد يتضمن العنصر ‎$_FILES["FILE_NAME"]['error']‎ (حيث "FILE_NAME" قيمة الخاصيّة name للعنصر input الموجود في النموذج الخاص بك) إحدى القيم التالية: 1- UPLOAD_ERR_OK: لا يوجد خطأ، رُفِع الملف بنجاح. 2- UPLOAD_ERR_INI_SIZE: تجاوز الملف المرفوع قيمة upload_max_filesize في الملف php.ini. 3- UPLOAD_ERR_PARTIAL: تجاوز الملف المرفوع القيمة MAX_FILE_SIZE المحددة في نموذج HTML. 4- UPLOAD_ERR_NO_FILE: لم يُحمَّل أي ملف. 5- UPLOAD_ERR_NO_TMP_DIR: فقدان مجلد مؤقت (من الإصدار PHP 5.0.3). 6- UPLOAD_ERR_CANT_WRITE: فشل كتابة الملف على القرص (من الإصدار PHP 5.1.0). 7- UPLOAD_ERR_EXTENSION: إضافة PHP أوقفت رفع الملف (من الإصدار PHP 5.2.0). الطريقة الأساسية لفحص الأخطاء: <?php // ‫"FILE_NAME" قيمة الخاصيّة `name` للعنصر `input` الموجود في النموذج الخاص بك‫ $fileError = $_FILES["FILE_NAME"]["error"]; switch($fileError) { case UPLOAD_ERR_INI_SIZE: // ‫تجاوز الحجم الأعظمي المحدد في php.ini break; case UPLOAD_ERR_PARTIAL: // ‫تجاوز الحجم الأعظمي المحدد في نموذج html break; case UPLOAD_ERR_NO_FILE: // لم يُحمَّل أي ملف break; case UPLOAD_ERR_NO_TMP_DIR: // لا يوجد مجلد مؤقت للكتابة فيه break; case UPLOAD_ERR_CANT_WRITE: // خطأ في الكتابة إلى القرص break; default: // لا يوجد أخطاء! break; } تمرير مصفوفات عبر طلب POST يُرسَل عادةً عنصر نموذج HTML إلى PHP كقيمة مفردة، مثال: <pre> <?php print_r($_POST);?> </pre> <form method="post"> <input type="hidden" name="foo" value="bar"/> <button type="submit">Submit</button> </form> الخرج: Array ( [foo] => bar ) لكن قد تحتاج أحيانًا إلى تمرير مصفوفة قيم، يمكنك القيام بذلك بإضافة مايشبه اللاحقة في PHP إلى الخاصيّة name لعناصر HTML: <pre> <?php print_r($_POST);?> </pre> <form method="post"> <input type="hidden" name="foo[]" value="bar"/> <input type="hidden" name="foo[]" value="baz"/> <button type="submit">Submit</button> </form> الخرج: Array ( [foo] => Array ( [0] => bar [1] => baz ) ) يمكنك أيضًا تحديد فهارس المصفوفة إما أرقام أو سلاسل نصية: <pre> <?php print_r($_POST);?> </pre> <form method="post"> <input type="hidden" name="foo[42]" value="bar"/> <input type="hidden" name="foo[foo]" value="baz"/> <button type="submit">Submit</button> </form> الخرج: Array ( [foo] => Array ( [42] => bar [foo] => baz ) ) يمكن استخدام هذه التقنية لتجنب حلقات ما بعد المعالجة عبر مصفوفة ‎$_POST مما يجعل شيفرتك أنظف وأدق. رفع ملفات باستخدام HTTP PUT توفر PHP الدعم للطريقة HTTP PUT المستخدمة من قِبَل بعض العملاء لتخزين الملفات على الخادم، تعدّ طلبات PUT أبسط من رفع ملف باستخدام طلبات POST وتبدو بالشكل: PUT /path/filename.html HTTP/1.1 يمكنك كتابة شيفرة PHP التالية: <?php // ‫تأتي بيانات PUT من مجرى الدخل القياسي $putdata = fopen("php://input", "r"); // فتح ملف للكتابة $fp = fopen("putfile.ext", "w"); // قراءة 1 كيلوبايت في كل مرة وكتابتها في الملف while ($data = fread($putdata, 1024)) fwrite($fp, $data); /* إغلاق المجاري */ fclose($fp); fclose($putdata); ?> ترجمة -وبتصرف- للفصل [Reading Request Data] من كتاب PHP Notes for Professionals book
  2. تحليل رابط نستخدم الدالة parse_url()‎ لتقسيم رابط (url) إلى مكوناته الفردية: $url = 'http://www.example.com/page?foo=1&bar=baz#anchor'; $parts = parse_url($url); تصبح محتويات المتغير ‎$parts بعد تنفيذ الشيفرة السابقة: Array ( [scheme] => http [host] => www.example.com [path] => /page [query] => foo=1&bar=baz [fragment] => anchor ) يمكنك أن ترجع أيضًا مكون واحد من الرابط بشكلٍ انتقائي، فمثلًا لترجع سلسلة الاستعلام (query string) فقط: $url = 'http://www.example.com/page?foo=1&bar=baz#anchor'; $queryString = parse_url($url, PHP_URL_QUERY); يعدّ أيّ من الثوابت التالية مقبولًا: PHP_URL_SCHEME ‏PHP_URL_HOST PHP_URL_PORT ‏PHP_URL_USER ‏PHP_URL_PASS ‏PHP_URL_PATH ‏PHP_URL_QUERY PHP_URL_FRAGMENT يمكنك استخدام الدالة parse_str()‎ لتحليل سلسلة الاستعلام إلى أزواج مفتاح/قيمة: $params = []; parse_str($queryString, $params); تصبح محتويات المتغير ‎$params بعد تنفيذ الشيفرة السابقة: Array ( [foo] => 1 [bar] => baz ) بناء سلسلة استعلام للرابط من مصفوفة تنشأ الدالة http_build_query()‎ سلسلة استعلام من مصفوفة أو كائن، يمكن أن تُضاف هذه السلسلة إلى رابط ما لإنشاء طلب من النوع GET أو تُستخدم في طلب POST مع مكتبة cURL مثلًا: $parameters = array( 'parameter1' => 'foo', 'parameter2' => 'bar', ); $queryString = http_build_query($parameters); تصبح محتويات المتغير ‎$queryString بعد تنفيذ الشيفرة السابقة: parameter1=foo&parameter2=bar تعمل الدالة http_build_query()‎ مع المصفوفات متعددة الأبعاد أيضًا: $parameters = array( "parameter3" => array( "sub1" => "foo", "sub2" => "bar", ), "parameter4" => "baz", ); $queryString = http_build_query($parameters); تصبح محتويات المتغير ‎$queryString بعد تنفيذ الشيفرة السابقة: parameter3%5Bsub1%5D=foo&parameter3%5Bsub2%5D=bar&parameter4=baz والذي هو النسخة المرمزة للرابط: parameter3[sub1]=foo&parameter3[sub2]=bar&parameter4=baz إعادة التوجيه إلى رابط آخر يمكنك استخدام الدالة header()‎ لتوجيه المتصفح ليعيد التوجيه إلى رابط مختلف: $url = 'https://example.org/foo/bar'; // فحص الترويسات - لايمكنك إرسال ترويسات إذا أُرسلت بالفعل if (!headers_sent()) { header('Location: ' . $url); // الحماية من تنفيذ الشيفرة بعد إعادة توجيه الطلب exit; } else { throw new Exception('Cannot redirect, headers already sent'); } يمكنك أيضًا إعادة التوجيه إلى رابط نسبي (هذا ليس جزء من توصيف HTTP الرسمي، لكنه يعمل مع جميع المتصفحات): $url = 'foo/bar'; if (!headers_sent()) { header('Location: ' . $url); exit; } else { throw new Exception('Cannot redirect, headers already sent'); } إذا كانت الترويسات قد أُرسلت، يمكنك إرسال وسم HTML ‏meta refresh. يعتمد وسم ‏meta refresh على معالجة HTML بشكلٍ صحيح عند العميل لكن قد لايتم ذلك وفقًا للمطلوب، بشكلٍ عام يعمل بشكل صحيح فقط مع متصفحات الويب، ويجب أن تنتبه أيضًا أنه إذا كانت الترويسات قد أُرسلت بالفعل فقد تحصل على خطأ وهذا يشغّل استثناءً. يمكنك أيضًا أن تطبع رابطًا يضغط عليه المستخدم للعملاء الذين يتجاهلون وسم ‏meta refresh: $url = 'https://example.org/foo/bar'; if (!headers_sent()) { header('Location: ' . $url); } else { // ‫الحماية من رؤية المتصفح للرابط على أنّه HTML $saveUrl = htmlspecialchars($url); // ‫إخبار المتصفح أن يعيد توجيه الصفحة إلى ‎$saveUrl بعد 0 ثانية print '<meta http-equiv="refresh" content="0; url=' . $saveUrl . '">'; // إظهار الرابط للمستخدم print '<p>Please continue to <a href="' . $saveUrl . '">' . $saveUrl . '</a></p>'; } exit; كيفية تحليل رابط عند كتابتك شيفرة بلغة PHP قد تحتاج إلى تقسيم رابط إلى عدة أجزاء، ويوجد عدة طرق لذلك وفقًا لاحتياجاتك، سنشرح هذه الطرق في الفقرة التالية لتختار منها ما يناسبك. استخدام parse_url()‎ تحلل الدالة parse_url()‎ رابطًا وتعيد مصفوفة ترابطية (associative array) تتضمن مكونات الرابط المختلفة. $url = parse_url('http://example.com/project/controller/action/param1/param2'); /* Array ( [scheme] => http [host] => example.com [path] => /project/controller/action/param1/param2 ) */ يمكنك استخدام الدالة explode إذا كنت تحتاج أن يكون path مقسّمًا: $url = parse_url('http://example.com/project/controller/action/param1/param2'); $url['sections'] = explode('/', $url['path']); /* Array ( [scheme] => http [host] => example.com [path] => /project/controller/action/param1/param2 [sections] => Array ( [0] => [1] => project [2] => controller [3] => action [4] => param1 [5] => param2 ) ) */ يمكنك استخدام الدالة end()‎ إذا كنت تحتاج الجزء الأخير من sections: $last = end($url['sections']); إذا كان الرابط يحتوي على متغيرات GET يمكنك استعادتها أيضًا: $url = parse_url('http://example.com?var1=value1&var2=value2'); /* Array ( [scheme] => http [host] => example.com [query] => var1=value1&var2=value2 ) */ إذا أردت تقسيم متغيرات الاستعلام يمكنك استخدام parse_str()‎ كما يلي: $url = parse_url('http://example.com?var1=value1&var2=value2'); parse_str($url['query'], $parts); /* Array ( [var1] => value1 [var2] => value2 ) */ استخدام explode()‎ تعيد الدالة explode مصفوفة من السلاسل النصية، كل منها سلسلة جزئية من سلسلة نصية مُشكَّلة من تقسيمها على الحدود التي يشكّلها حد السلسلة النصية. $url = "http://example.com/project/controller/action/param1/param2"; $parts = explode('/', $url); /* Array ( [0] => http: [1] => [2] => example.com [3] => project [4] => controller [5] => action [6] => param1 [7] => param2 ) */ يمكنك استعادة الجزء الأخير من الرابط كالتالي: $last = end($parts); // param2 يمكنك أيضًا التنقل في المصفوفة باستخدام sizeof()‎ مع عامل رياضي كما في الشيفرة التالية: echo $parts[sizeof($parts)-2]; // param1 استخدام basename()‎ تُعطى الدالة basename()‎ سلسلة نصية تتضمن مسارًا إلى ملف أو مجلد وترجع مكوّن الاسم الزائد أي ترجع الجزء الأخير من الرابط فقط. $url = "http://example.com/project/controller/action/param1/param2"; $parts = basename($url); // param2 إذا كان الرابط يحتوي على المزيد من الأشياء وكل ما تحتاجه هو اسم المجلد الذي يتضمن الملف الذي تستخدمه عندها يمكنك استخدام dirname()‎: $url = "http://example.com/project/controller/action/param1/param2/index.php"; $parts = basename(dirname($url)); // param2 ترجمة -وبتصرف- للفصول [URLs - How to break down an URL] من كتاب PHP Notes for Professionals book
  3. الكلمة المفتاحية Yield تشبه التعليمة yield تعليمة return باستثناء أنّه بدلًا من إيقاف تنفيذ الدالة وإرجاع شيء ما، فإنّها تُرجع كائن Generator وتوقف تنفيذ دالة المولِّد، إليك مثال عن دالة مجال مكتوبة كمولِّد: function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { // ‫لاحظ أنّ المتغير ‎$i مسبوق بالكلمة المفتاحية yield. yield $i; } } يمكنك ملاحظة أنّ هذه الدالة ترجع كائنًا من النوع مولِّد Generator من خلال فحص الخرج باستخدام الدالة var_dump: var_dump(gen_one_to_three()) /* class Generator (0) { } */ القيم المعادة (Yielding) يمكن تكرار الكائن Generator مثل المصفوفة: foreach (gen_one_to_three() as $value) { echo "$value\n"; } خرج المثال السابق: 1 2 3 القيم المعادة مع مفاتيح بالإضافة إلى إعادة قيم يمكنك إعادة أزواج قيمة/مفتاح: function gen_one_to_three() { $keys = ["first", "second", "third"]; for ($i = 1; $i <= 3; $i++) { // ‫لاحظ أنّ المتغير ‎$i‎ هو قيمة معادة yield $keys[$i - 1] => $i; } } foreach (gen_one_to_three() as $key => $value) { echo "$key: $value\n"; } خرج المثال السابق: first: 1 second: 2 third: 3 قراءة ملف كبير باستخدام مولد إحدى حالات الاستخدام الشائعة للمولِّدات هي قراءة ملف من القرص والمرور على كامل محتوياته، إليك في الشيفرة التالية صنفًا يسمح لك بالتكرار عبر ملف CSV، يمكن التنبؤ باستخدام الذاكرة لهذا السكربت ولن يتفاوت هذا الاستخدام بتغير حجم ملف CSV. <?php class CsvReader { protected $file; public function __construct($filePath) { $this->file = fopen($filePath, 'r'); } public function rows() { while (!feof($this->file)) { $row = fgetcsv($this->file, 4096); yield $row; } return; } } $csv = new CsvReader('/path/to/huge/csv/file.csv'); foreach ($csv->rows() as $row) { // ‫القيام بشيء ما مع صف CSV } لماذا نستخدم المولِّد؟ تعدّ المولِّدات مفيدةً عندما نحتاج إلى توليد مجموعة كبيرة للمرور عليها لاحقًا، وهي أبسط بديل لإنشاء صنف ينفّذ مكرِّرًا والذي غالبًا ما يكون مبالغ به. بفرض لدينا الدالة التالية: function randomNumbers(int $length) { $array = []; for ($i = 0; $i < $length; $i++) { $array[] = mt_rand(1, 10); } return $array; } تولِّد الدالة السابقة مصفوفة تُملأ بأرقام عشوائية، قد نستخدمها بالشكل randomNumbers(10)‎ مثلًا مما يعطينا مصفوفة من 10 أرقام عشوائية، لكن ماذا لو أردنا توليد مليون رقمًا عشوائيًا؟ يمكننا القيام بذلك بكتابة randomNumbers(1000000)‎ لكن هذا يكلّف ذاكرة، إذ أنّ مليون عدد صحيح مخزّن في مصفوفة يحتاج إلى حوالي 33 ميجابايت من الذاكرة. $startMemory = memory_get_usage(); $randomNumbers = randomNumbers(1000000); echo memory_get_usage() - $startMemory, ' bytes'; هذا بسبب توليد مليون رقم عشوائي وإرجاعه مرةً واحدةً، بدلًا من توليد وإرجاع رقم في كل مرة، تعدّ المولِّدات طريقةً سهلةً لحل هذه المشكلة. استخدام دالة send()‎ لتمرير القيم إلى مولد تُشفَّر المولِّدات بسرعة وتكون في الكثير من الحالات بديلًا خفيفًا عن تنفيذات المكرِّر الثقيلة، ومع التنفيذ السريع يصبح لدينا نقصًا قليلًا في التحكم عندما يجب أن يتوقف المولِّد عن التوليد أو إذا كان يجب أن يولِّد شيئًا آخر. لكن يمكن تحقيق ذلك باستخدام دالة send()‎ مما يتيح لدالة الطلب إرسال المعاملات إلى المولِّد بعد كل حلقة. بفرض أنّه نريد الوصول إلى كمية بيانات كبيرة باستخدام المولِّد في الشيفرة التالية: function generateDataFromServerDemo() { // في هذا المثال نُرسل التغذية الراجعة في كل تكرار للحلقة بدلًا من إرسال بيانات من الخادم $indexCurrentRun = 0; $timeout = false; while (!$timeout) { $timeout = yield $indexCurrentRun; //(1) $indexCurrentRun++; } yield 'X of bytes are missing. </br>'; } // بدء استخدام المولِّد $generatorDataFromServer = generateDataFromServerDemo (); foreach($generatorDataFromServer as $numberOfRuns) { if ($numberOfRuns < 10) { echo $numberOfRuns . "</br>"; } else { // إرسال البيانات إلى المولِّد $generatorDataFromServer->send(true); // الوصول إلى العنصر الأخير (التلميح إلى عدد البايتات المفقودة) echo $generatorDataFromServer->current(); } } في الموضع (1) تُمرَّر القيم إلى المستدعي، وفي المرة التالية يُستدعى المولِّد الذي يبدأ عند هذه التعليمة، إذا استخدمنا الدالة send()‎ فإنّ المتغير ‎$timeout سيأخذ هذه القيمة. خرج الشيفرة السابقة: 0 1 2 3 4 5 6 7 8 9 X bytes are missing المغلف (closure) الاستخدام الأساسي للمغلِّف يكافئ المغلِّف الدالة المجهولة (anonymous function) أي الدالة التي ليس لها اسم، حتى لو كان ذلك غير صحيح تقنيًا فإنّ سلوك المغلِّف نفس سلوك الدالة مع ميزات إضافية قليلة. مثال: <?php $myClosure = function() { echo 'Hello world!'; }; $myClosure(); // "Hello world!" تذكر أنّ ‎$myClosure هو نسخة من مغلِّف (closure) لذا يجب أن تنتبه لما يمكنك القيام به (اطلع على الرابط). الحالة التقليدية التي تحتاج فيها استخدام المغلِّف هي عندما تريد إعطاء شيء قابل للاستدعاء إلى دالة ما، مثل الدالة usort، في المثال التالي تُرتَّب المصفوفة حسب عدد الأبناء لكل أب: <?php $data = [ [ 'name' => 'John', 'nbrOfSiblings' => 2, ], [ 'name' => 'Stan', 'nbrOfSiblings' => 1, ], [ 'name' => 'Tom', 'nbrOfSiblings' => 3, ] ]; usort($data, function($e1, $e2) { if ($e1['nbrOfSiblings'] == $e2['nbrOfSiblings']) { return 0; } return $e1['nbrOfSiblings'] < $e2['nbrOfSiblings'] ? -1 : 1; }); var_dump($data); // ‫سيظهر في الخرج Stan ثم John ثم Tom استخدام متغيرات خارجية من الممكن استخدام متغير خارجي داخل المُغلِّف باستخدام الكلمة المفتاحية الخاصة use، مثال: <?php $quantity = 1; $calculator = function($number) use($quantity) { return $number + $quantity; }; var_dump($calculator(2)); // "3" ويمكنك أيضًا إنشاء دوال مغلِّفة ديناميكية، فمثلًا يمكنك إنشاء دالة تُرجع عملية حسابية معينة بالاعتماد على كمية تريد إضافتها: <?php function createCalculator($quantity) { return function($number) use($quantity) { return $number + $quantity; }; } $calculator1 = createCalculator(1); $calculator2 = createCalculator(2); var_dump($calculator1(2)); // "3" var_dump($calculator2(2)); // "4" الربط الأساسي لمغلِّف كما لاحظت سابقًا فإنّ المغلِّف هو نسخة من الصنف Closure ويمكن استدعاء توابع مختلفة عليه، مثل التابع bindTo الذي يُعطى مغلِّف ويرجع مغلِّفًا جديدًا مرتبطًا بكائن مُعطى، مثال: <?php $myClosure = function() { echo $this->property; }; class MyClass { public $property; public function __construct($propertyValue) { $this->property = $propertyValue; } } $myInstance = new MyClass('Hello world!'); $myBoundClosure = $myClosure->bindTo($myInstance); $myBoundClosure(); // "Hello world!" ربط المغلف والنطاق بفرض لدينا المثال التالي: <?php $myClosure = function() { echo $this->property; }; class MyClass { public $property; public function __construct($propertyValue) { $this->property = $propertyValue; } } $myInstance = new MyClass('Hello world!'); $myBoundClosure = $myClosure->bindTo($myInstance); $myBoundClosure(); // "Hello world!" إذا حاولت تغيير مرئية الخاصيّة إلى محميّة (protected) أو خاصة (private)، ستحصل على خطأ فادح يشير إلى أنّه لا يمكنك الوصول إلى هذه الخاصيّة، وحتى لو كان المغلِّف مرتبطًا بالكائن فإنّ النطاق الذي يُنفَّذ فيه المغلِّف ليس بحاجة ليكون لديه إمكانية الوصول ولهذا السبب نستخدم المعامل الثاني للتابع bindTo. الطريقة الوحيدة لتكون الخاصيّة الخاصة (private) قابلة للوصول هي الوصول إليها من النطاق الذي يسمح بذلك، أي نطاق الصنف. في مثال الشيفرة السابق لم يُحدَّد النطاق مما يعني أنّ المغلِّف يُنفَّذ في نفس النطاق الذي أُنشئَ فيه، لنغيّر هذا: <?php $myClosure = function() { echo $this->property; }; class MyClass { private $property; public function __construct($propertyValue) { $this->property = $propertyValue; } } $myInstance = new MyClass('Hello world!'); $myBoundClosure = $myClosure->bindTo($myInstance, MyClass::class); $myBoundClosure(); // "Hello world!" إذا لم يُستخدم المعامل الثاني فإنّ المُغلِّف يُنفَّذ في نفس السياق الذي أُنشئ فيه، فمثلًا إذا أُنشئ المغلِّف داخل تابع صنف والذي يُنفَّذ في سياق كائن سيكون له نفس نطاق هذا التابع: <?php class MyClass { private $property; public function __construct($propertyValue) { $this->property = $propertyValue; } public function getDisplayer() { return function() { echo $this->property; }; } } $myInstance = new MyClass('Hello world!'); $displayer = $myInstance->getDisplayer(); $displayer(); // "Hello world!" ربط مغلِّف لاستدعاء واحد بدءًا من الإصدار PHP7 أصبح من الممكن ربط مغلِّف باستدعاء واحد فقط بفضل التابع call، مثال: <?php class MyClass { private $property; public function __construct($propertyValue) { $this->property = $propertyValue; } } $myClosure = function() { echo $this->property; }; $myInstance = new MyClass('Hello world!'); $myClosure->call($myInstance); // "Hello world!" لا داعي للقلق بشأن النطاق على عكس التابع bindTo، إذ أنّ النطاق المستخدم لهذا الاستدعاء هو نفس المستخدم عند الوصول إلى أو تنفيذ خاصية ‎$myInstance. استخدام المغلِّفات لتنفيذ نمط المراقِب بشكل عام المراقب (observer) هو صنف بتابع محدد يُستدعى عندما يحدث إجراء على الكائن المُراقَب، في بعض الحالات قد تكون المغلِّفات كافية لتنفيذ نمط تصميم المراقب. إليك مثال تفصيلي لمثل هذا التنفيذ، لنصرّح أولًا عن صنف الهدف منه هو إخبار المراقبين عند تغيّر خاصيّاتهم. <?php class ObservedStuff implements SplSubject { protected $property; protected $observers = []; public function attach(SplObserver $observer) { $this->observers[] = $observer; return $this; } public function detach(SplObserver $observer) { if (false !== $key = array_search($observer, $this->observers, true)) { unset($this->observers[$key]); } } public function notify() { foreach ($this->observers as $observer) { $observer->update($this); } } public function getProperty() { return $this->property; } public function setProperty($property) { $this->property = $property; $this->notify(); } } ثم نصرّح عن الصنف الذي سيمثّل المراقبين المختلفين: <?php class NamedObserver implements SplObserver { protected $name; protected $closure; public function __construct(Closure $closure, $name) { $this->closure = $closure->bindTo($this, $this); $this->name = $name; } public function update(SplSubject $subject) { $closure = $this->closure; $closure($subject); } } لنبدأ بالاختبار الآن: <?php $o = new ObservedStuff; $observer1 = function(SplSubject $subject) { echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n"; }; $observer2 = function(SplSubject $subject) { echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n"; }; $o->attach(new NamedObserver($observer1, 'Observer1')) ->attach(new NamedObserver($observer2, 'Observer2')); $o->setProperty('Hello world!'); /* Observer1 has been notified! New property value: Hello world! Observer2 has been notified! New property value: Hello world! */ لاحظ أنّ هذا المثال يعمل لأنّ المراقبين يتشاركون نفس الطبيعة (كلاهما "named observers"). ترجمة -وبتصرف- للفصول [Generators - Closure] من كتاب PHP Notes for Professionals book
  4. ترشِّح هذه الإضافة البيانات إما عن طريق تدقيقها أو تعقيمها (sanitizing)، ويعدّ هذا مفيدًا خاصةً عندما يتضمن مصدر البيانات بيانات غير معروفة (أو غريبة) مثل الدخل الذي يوفره مستخدم ما، قد تأتي هذه البيانات من نموذج HTML مثلًا. المعامل تفاصيل variable القيمة التي نريد ترشيحها، لاحظ أنّ القيم العددية تُحوَّل إلى سلاسل نصية داخليًا قبل أي ترشيح filter ‫ معرِّف المرشّح الذي نريد تطبيقه، تذكر صفحة أنواع المرشِّحات في توثيق PHP جميع المرشِّحات المتاحة، إذا تجاوزت هذا المعامل ستُستخدم افتراضيًا القيمة `FILTER_DEFAULT` والتي تكافئ القيمة `FILTER_UNSAFE_RAW`، مما يؤدي إلى عدم إجراء ترشيح بشكلٍ افتراضي options ‫مصفوفة ترابطية من الخيارات أو عملية ثنائية OR على الرايات، إذا قَبِل المرشّح هذه الخيارات يمكن توفير الرايات في الحقل "flags" من المصفوفة. يجب تمرير نوع قابل للاستدعاء من أجل مرشِّح رد النداء، ويجب أن يقبل رد النداء وسيط واحد (القيمة التي نريد ترشيحها) ويعيد القيمة بعد ترشيحها/تعقيمها 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; } التحقق من القيم المنطقية var_dump(filter_var(true, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // true var_dump(filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false var_dump(filter_var(1, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // true var_dump(filter_var(0, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false var_dump(filter_var('1', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // true var_dump(filter_var('0', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false var_dump(filter_var('', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false var_dump(filter_var(' ', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false var_dump(filter_var('true', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // true var_dump(filter_var('false', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false var_dump(filter_var([], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // NULL var_dump(filter_var(null, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // false التحقق من القيم العشرية يتحقق من القيمة على أنّها قيمة عشرية ويحوّلها إلى قيمة عشرية عند النجاح. var_dump(filter_var(1, FILTER_VALIDATE_FLOAT)); var_dump(filter_var(1.0, FILTER_VALIDATE_FLOAT)); var_dump(filter_var(1.0000, FILTER_VALIDATE_FLOAT)); var_dump(filter_var(1.00001, FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1.0', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1.0000', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1.00001', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1,000', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1,000.0', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1,000.0000', FILTER_VALIDATE_FLOAT)); var_dump(filter_var('1,000.00001', FILTER_VALIDATE_FLOAT)); var_dump(filter_var(1, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var(1.0, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var(1.0000, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var(1.00001, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.0', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.0000', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.00001', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000.0', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000.0000', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000.00001', FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); الخرج: float(1) float(1) float(1) float(1.00001) float(1) float(1) float(1) float(1.00001) bool(false) bool(false) bool(false) bool(false) float(1) float(1) float(1) float(1.00001) float(1) float(1) float(1) float(1.00001) float(1000) float(1000) float(1000) float(1000.00001) التحقق من العنوان الفيزيائي (MAC Address) يتحقق من قيمة أنّها عنوان فيزيائي صحيح. var_dump(filter_var('FA-F9-DD-B2-5E-0D', FILTER_VALIDATE_MAC)); var_dump(filter_var('DC-BB-17-9A-CE-81', FILTER_VALIDATE_MAC)); var_dump(filter_var('96-D5-9E-67-40-AB', FILTER_VALIDATE_MAC)); var_dump(filter_var('96-D5-9E-67-40', FILTER_VALIDATE_MAC)); var_dump(filter_var('', FILTER_VALIDATE_MAC)); الخرج: string(17) "FA-F9-DD-B2-5E-0D" string(17) "DC-BB-17-9A-CE-81" string(17) "96-D5-9E-67-40-AB" bool(false) bool(false) تعقيم عناوين البريد الإلكتروني يحذف جميع المحارف باستثناء الأحرف والأرقام والمحارف ‎!#$%&'*+-=?^_{|}~@.[]‎`. var_dump(filter_var('john@example.com', FILTER_SANITIZE_EMAIL)); var_dump(filter_var("!#$%&'*+-=?^_`{|}~.[]@example.com", FILTER_SANITIZE_EMAIL)); var_dump(filter_var('john/@example.com', FILTER_SANITIZE_EMAIL)); var_dump(filter_var('john\@example.com', FILTER_SANITIZE_EMAIL)); var_dump(filter_var('joh n@example.com', FILTER_SANITIZE_EMAIL)); الخرج: string(16) "john@example.com" string(33) "!#$%&'*+-=?^_`{|}~.[]@example.com" string(16) "john@example.com" string(16) "john@example.com" string(16) "john@example.com" تعقيم الأعداد الصحيحة يحذف جميع المحارف باستثناء الأرقام وإشارة الناقص والزائد. var_dump(filter_var(1, FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var(-1, FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var(+1, FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var(1.00, FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var(+1.00, FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var(-1.00, FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('1', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('-1', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('+1', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('1.00', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('+1.00', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('-1.00', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('1 unicorn', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('-1 unicorn', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var('+1 unicorn', FILTER_SANITIZE_NUMBER_INT)); var_dump(filter_var("!#$%&'*+-=?^_`{|}~@.[]0123456789abcdefghijklmnopqrstuvwxyz", FILTER_SANITIZE_NUMBER_INT)); الخرج: string(1) "1" string(2) "-1" string(1) "1" string(1) "1" string(1) "1" string(2) "-1" string(1) "1" string(2) "-1" string(2) "+1" string(3) "100" string(4) "+100" string(4) "-100" string(1) "1" string(2) "-1" string(2) "+1" string(12) "+-0123456789" تعقيم الروابط يحذف جميع المحارف باستثناء الأحرف والأرقام والمحارف ‎$-_.+!*'(),{}|\^~[]<>#%";/?:@&=‎`. var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=y', FILTER_SANITIZE_URL)); var_dump(filter_var("http://www.example.com/path/to/dir/index.php?test=y!#$%&'*+-=?^_`{|}~.[]", FILTER_SANITIZE_URL)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=a b c', FILTER_SANITIZE_URL)); الخرج: string(51) "http://www.example.com/path/to/dir/index.php?test=y" string(72) "http://www.example.com/path/to/dir/index.php?test=y!#$%&'*+-=?^_`{|}~.[]" string(53) "http://www.example.com/path/to/dir/index.php?test=abc" التحقق من عناوين البريد الإلكتروني تعيد الدالة filter_var()‎ عند استخدامها للتحقق من عنوان بريد إلكتروني بيانات مرشّحةً، والتي يجب أن تكون عنوان بريد إلكتروني في هذه الحالة أو false إذا لم يكن عنوان البريد إلكتروني صحيحًا: var_dump(filter_var('john@example.com', FILTER_VALIDATE_EMAIL)); var_dump(filter_var('notValidEmail', FILTER_VALIDATE_EMAIL)); الخرج: string(16) "john@example.com" bool(false) لا تتحقق هذه الدالة من المحارف غير اللاتينية، يمكن التحقق من اسم المجال المدول (Internationalized) من خلال نموذج ‎xn-- الخاص به. لاحظ أنّه لا يمكن معرفة فيما إذا كان عنوان البريد الإلكتروني صحيحًا قبل إرسال بريد إلكتروني إليه، قد ترغب بالمزيد من عمليات التحقق مثل التحقق من سجل MX، لكن هذا ليس ضروريًا. لا تنسَ إزالة الحسابات غير المستخدمة عند إرسال بريد تأكيد إلكتروني. التحقق من القيم الصحيحة تعيد الدالة filter_var()‎ عند استخدامها للتحقق من قيمة يجب أن تكون عددًا صحيحًا بيانات مرشّحةً والتي يجب أن تكون عددًا صحيحًا في هذه الحالة أو false إذا لم تكن القيمة عددًا صحيحًا. ولا تنسَ أنّ الأعداد العشرية ليست أعدادًا صحيحة. var_dump(filter_var('10', FILTER_VALIDATE_INT)); var_dump(filter_var('a10', FILTER_VALIDATE_INT)); var_dump(filter_var('10a', FILTER_VALIDATE_INT)); var_dump(filter_var(' ', FILTER_VALIDATE_INT)); var_dump(filter_var('10.00', FILTER_VALIDATE_INT)); var_dump(filter_var('10,000', FILTER_VALIDATE_INT)); var_dump(filter_var('-5', FILTER_VALIDATE_INT)); var_dump(filter_var('+7', FILTER_VALIDATE_INT)); الخرج: int(10) bool(false) bool(false) bool(false) bool(false) bool(false) int(-5) int(7) يمكنك استخدام تعبير نمطي إذا كنت تتوقع أرقامًا فقط: if(is_string($_GET['entry']) && preg_match('#^[0-9]+$#', $_GET['entry'])) // ‫هذا رقم صحيح (موجب) else // الرقم المدخل غير صحيح لا تحتاج للقيام بمثل هذا التحقق إذا كنت تحوّل هذه القيمة إلى عدد صحيح لذا يمكنك استخدام filter_var()‎. التحقق من وجود عدد صحيح ضمن مجال نحتاج إلى تضمين الحد الأقصى والحد الأدنى للمجال للتحقق من وجود عدد صحيح ضمن هذا المجال. $options = array( 'options' => array( 'min_range' => 5, 'max_range' => 10, ) ); var_dump(filter_var('5', FILTER_VALIDATE_INT, $options)); var_dump(filter_var('10', FILTER_VALIDATE_INT, $options)); var_dump(filter_var('8', FILTER_VALIDATE_INT, $options)); var_dump(filter_var('4', FILTER_VALIDATE_INT, $options)); var_dump(filter_var('11', FILTER_VALIDATE_INT, $options)); var_dump(filter_var('-6', FILTER_VALIDATE_INT, $options)); الخرج: int(5) int(10) int(8) bool(false) bool(false) bool(false) التحقق من رابط تعيد الدالة filter_var()‎ عند استخدامها لترشيح رابط بيانات مرشّحةً والتي يجب أن تكون رابطًا في هذه الحالة أو false إذا لم يكن الرابط صحيحًا: مثال الرابط example.com: var_dump(filter_var('example.com', FILTER_VALIDATE_URL)); var_dump(filter_var('example.com', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)); var_dump(filter_var('example.com', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)); var_dump(filter_var('example.com', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)); var_dump(filter_var('example.com', FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)); الخرج: bool(false) bool(false) bool(false) bool(false) bool(false) الرابط http://example.com: var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL)); var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)); var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)); var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)); var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)); الخرج: string(18) "http://example.com" string(18) "http://example.com" string(18) "http://example.com" bool(false) bool(false) الرابط http://www.example.com: var_dump(filter_var('http://www.example.com', FILTER_VALIDATE_URL)); var_dump(filter_var('http://www.example.com', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)); var_dump(filter_var('http://www.example.com', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)); var_dump(filter_var('http://www.example.com', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)); var_dump(filter_var('http://www.example.com', FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)); الخرج: string(22) "http://www.example.com" string(22) "http://www.example.com" string(22) "http://www.example.com" bool(false) bool(false) الرابط http://www.example.com/path/to/dir/‎: var_dump(filter_var('http://www.example.com/path/to/dir/', FILTER_VALIDATE_URL)); var_dump(filter_var('http://www.example.com/path/to/dir/', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/', FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)); الخرج: string(35) "http://www.example.com/path/to/dir/" string(35) "http://www.example.com/path/to/dir/" string(35) "http://www.example.com/path/to/dir/" string(35) "http://www.example.com/path/to/dir/" bool(false) الرابط http://www.example.com/path/to/dir/index.php: var_dump(filter_var('http://www.example.com/path/to/dir/index.php', FILTER_VALIDATE_URL)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php', FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)); الخرج: string(44) "http://www.example.com/path/to/dir/index.php" string(44) "http://www.example.com/path/to/dir/index.php" string(44) "http://www.example.com/path/to/dir/index.php" string(44) "http://www.example.com/path/to/dir/index.php" bool(false) الرابط http://www.example.com/path/to/dir/index.php?test=y: var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=y', FILTER_VALIDATE_URL)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=y', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=y', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=y', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)); var_dump(filter_var('http://www.example.com/path/to/dir/index.php?test=y', FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)); الخرج: string(51) "http://www.example.com/path/to/dir/index.php?test=y" string(51) "http://www.example.com/path/to/dir/index.php?test=y" string(51) "http://www.example.com/path/to/dir/index.php?test=y" string(51) "http://www.example.com/path/to/dir/index.php?test=y" string(51) "http://www.example.com/path/to/dir/index.php?test=y" تحذير: يجب التحقق من أنّ البروتوكول يحميك من هجوم البرمجة عبر المواقع (XSS). var_dump(filter_var('javascript://comment%0Aalert(1)', FILTER_VALIDATE_URL)); // string(31) "javascript://comment%0Aalert(1)" تعقيم الأعداد العشرية يحذف جميع المحارف باستثناء الأرقام و‎+- والمحارف ‎.,eE اختياريًا. var_dump(filter_var(1, FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var(1.0, FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var(1.0000, FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var(1.00001, FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1.0', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1.0000', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1.00001', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1,000', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1,000.0', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1,000.0000', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1,000.00001', FILTER_SANITIZE_NUMBER_FLOAT)); var_dump(filter_var('1.8281e-009', FILTER_SANITIZE_NUMBER_FLOAT)); الخرج: string(1) "1" string(1) "1" string(1) "1" string(6) "100001" string(1) "1" string(2) "10" string(5) "10000" string(6) "100001" string(4) "1000" string(5) "10000" string(8) "10000000" string(9) "100000001" string(9) "18281-009" باستخدام خيار FILTER_FLAG_ALLOW_THOUSAND: var_dump(filter_var(1, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var(1.0, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var(1.0000, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var(1.00001, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.0', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.0000', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.00001', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000.0', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000.0000', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1,000.00001', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); var_dump(filter_var('1.8281e-009', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_THOUSAND)); يكون الخرج: string(1) "1" string(1) "1" string(6) "100001" string(1) "1" string(2) "10" string(5) "10000" string(6) "100001" string(5) "1,000" string(6) "1,0000" string(9) "1,0000000" string(10) "1,00000001" string(9) "18281-009" باستخدام الخيار FILTER_FLAG_ALLOW_SCIENTIFIC: var_dump(filter_var(1, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var(1.0, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var(1.0000, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var(1.00001, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1.0', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1.0000', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1.00001', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1,000', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1,000.0', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1,000.0000', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1,000.00001', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); var_dump(filter_var('1.8281e-009', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_SCIENTIFIC)); يكون الخرج: string(1) "1" string(1) "1" string(1) "1" string(6) "100001" string(1) "1" string(2) "10" string(5) "10000" string(6) "100001" string(4) "1000" string(5) "10000" string(8) "10000000" string(9) "100000001" string(10) "18281e-009" التحقق من عنوان IP يفحص قيمة فيما إذا كانت عنوان IP صحيح: var_dump(filter_var('185.158.24.24', FILTER_VALIDATE_IP)); var_dump(filter_var('2001:0db8:0a0b:12f0:0000:0000:0000:0001', FILTER_VALIDATE_IP)); var_dump(filter_var('192.168.0.1', FILTER_VALIDATE_IP)); var_dump(filter_var('127.0.0.1', FILTER_VALIDATE_IP)); الخرج: string(13) "185.158.24.24" string(39) "2001:0db8:0a0b:12f0:0000:0000:0000:0001" string(11) "192.168.0.1" string(9) "127.0.0.1" التحقق من عنوان IP فيما إذا كان عنوان IPv4 صحيح: var_dump(filter_var('185.158.24.24', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); var_dump(filter_var('2001:0db8:0a0b:12f0:0000:0000:0000:0001', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); var_dump(filter_var('192.168.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); var_dump(filter_var('127.0.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); الخرج: string(13) "185.158.24.24" bool(false) string(11) "192.168.0.1" string(9) "127.0.0.1" التحقق من عنوان IP فيما إذا كان عنوان IPv6 صحيح: var_dump(filter_var('185.158.24.24', FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)); var_dump(filter_var('2001:0db8:0a0b:12f0:0000:0000:0000:0001', FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)); var_dump(filter_var('192.168.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)); var_dump(filter_var('127.0.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)); الخرج: bool(false) string(39) "2001:0db8:0a0b:12f0:0000:0000:0000:0001" bool(false) bool(false) التحقق من كون عنوان IP غير موجود ضمن مجال خاص: var_dump(filter_var('185.158.24.24', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)); var_dump(filter_var('2001:0db8:0a0b:12f0:0000:0000:0000:0001', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)); var_dump(filter_var('192.168.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)); var_dump(filter_var('127.0.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)); الخرج: string(13) "185.158.24.24" string(39) "2001:0db8:0a0b:12f0:0000:0000:0000:0001" bool(false) string(9) "127.0.0.1" التحقق من كون عنوان IP غير موجود ضمن مجال محجوز: var_dump(filter_var('185.158.24.24', FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE)); var_dump(filter_var('2001:0db8:0a0b:12f0:0000:0000:0000:0001', FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE)); var_dump(filter_var('192.168.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE)); var_dump(filter_var('127.0.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE)); الخرج: string(13) "185.158.24.24" bool(false) string(11) "192.168.0.1" bool(false) التعقيم بالمرشحات يمكننا استخدام المرشِّحات لتعقيم متغير ما وفقًا لاحتياجاتنا، مثال: $string = "<p>Example</p>"; $newstring = filter_var($string, FILTER_SANITIZE_STRING); var_dump($newstring); // string(7) "Example" ستحذف الشيفرة السابقة وسوم html من المتغير ‎$string. ترجمة -وبتصرف- للفصل [Filters & Filter Functions] من كتاب PHP Notes for Professionals book
  5. دوال الملاءمة (Convenience functions) الخرج والدخل الخام المباشر توفر الدالتان file_get_contents و file_put_contents قابلية القراءة/الكتابة إلى/من ملف من/إلى سلسلة نصية في PHP في استدعاء واحد. يمكن أن تستخدم الدالة file_put_contents أيضًا مع راية القناع البتي FILE_APPEND للإضافة إلى الملف بدلًا من الكتابة فوقه، ويمكن استخدامها مع القناع البتي LOCK_EX للحصول على قفل حصري للملف أثناء إجراء الكتابة. يمكن دمج رايات القناع البتي باستخدام عامل العملية الثنائية OR |. $path = "file.txt"; // ‫قراءة محتويات الملف file.txt إلى ‎$contents $contents = file_get_contents($path); // ‫إذا غيّرنا شيئًا ما مثلًا `CRLF` إلى `LF` $contents = str_replace("\r\n", "\n", $contents); // ‫نكتب هذا التغيير في file.txt مستبدلين المحتويات الأصلية file_put_contents($path, $contents); تفيد الراية FILE_APPEND في الإضافة إلى ملفات السجلات، بينما تساعد الراية LOCK_EX في منع حالة سباق عدة عمليات للكتابة في الملف، فمثلًا لكتابة حالة الجلسة الحالية في ملف سجل: file_put_contents("logins.log", "{$_SESSION["username"]} logged in", FILE_APPEND | LOCK_EX); الخرج والدخل بصيغة CSV fgetcsv($file, $length, $separator) تحلل الدالة fgetcsv سطرًا من الملف المفتوح وتبحث عن حقول csv، ثم تعيد هذه الحقول في مصفوفة في حالة النجاح وإلا تعيد FALSE. تقرأ بشكلٍ افتراضي سطرًا واحدًا فقط من ملف csv. $file = fopen("contacts.csv","r"); print_r(fgetcsv($file)); print_r(fgetcsv($file,5," ")); fclose($file); محتويات ملف contacts.csv: Kai Jim, Refsnes, Stavanger, Norway Hege, Refsnes, Stavanger, Norway الخرج: Array ( [0] => Kai Jim [1] => Refsnes [2] => Stavanger [3] => Norway ) Array ( [0] => Hege, ) قراءة ملف إلى مجرى الخرج القياسي مباشرةً تنسخ الدالة readfile ملفًا إلى المخزن المؤقت للخرج، ولا تؤدي إلى ظهور أيّة مشاكل في الذاكرة حتى عند إرسال ملفات كبيرة من تلقاء نفسها. $file = 'monkey.gif'; if (file_exists($file)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($file).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($file)); readfile($file); exit; } القراءة بدءًا من مكان مؤشر في ملف نستخدم الدالة fpassthru بدلًا من ذلك للبحث عن نقطة في الملف للبدء بالنسخ إلى مجرى الخرج القياسي، في المثال التالي يُنسخ آخر 1024 بايت إلى الخرج القياسي: $fh = fopen("file.txt", "rb"); fseek($fh, -1024, SEEK_END); fpassthru($fh); قراءة ملف إلى مصفوفة تعيد الدالة file أسطر الملف الممرَّر في مصفوفة، يقابل كل عنصر من المصفوفة سطرًا من الملف مع استمرار إضافة السطر الجديد. print_r(file("test.txt")); محتويات الملف test.txt: Welcome to File handling This is to test file handling الخرج: Array ( [0] => Welcome to File handling [1] => This is to test file handling ) حذف ملفات ومجلدات حذف ملفات تحذف الدالة unlink ملفًا واحدًا وتعيد القيمة TRUE عند نجاح العملية وFALSE عند فشلها. $filename = '/path/to/file.txt'; if (file_exists($filename)) { $success = unlink($filename); if (!$success) { throw new Exception("Cannot delete $filename"); } } حذف مجلدات مع حذف عودي (recursive deletion) يمكن حذف المجلدات باستخدام الدالة rmdir إلا أنّ هذه الدالة تحذف المجلدات الفارغة فقط، ولحذف مجلد يحتوي ملفات يجب حذف هذه الملفات أولًا، إذا احتوى المجلد على مجلدات فرعية نحتاج إلى استخدام العودية. يفحص المثال التالي الملفات في مجلد ويحذف ما بداخله من ملفات ومجلدات بشكلٍ تعاودي ويعيد عدد الملفات (وليس المجلدات) المحذوفة. function recurse_delete_dir(string $dir) : int { $count = 0; // ‫التأكد من أنّ ‎$dir ينتهي بخط مائل لنتمكن من ربطه مع أسماء الملفات مباشرةً $dir = rtrim($dir, "/\\") . "/"; // ‫استخدام dir()‎᠎ للحصول على قائمة الملفات $list = dir($dir); // ‫تخزين اسم الملف التالي في ‎$file، إذا كانت قيمة ‎$file هي false أي لا توجد ملفات بعد عندها تُنهى الحلقة while(($file = $list->read()) !== false) { if($file === "." || $file === "..") continue; if(is_file($dir . $file)) { unlink($dir . $file); $count++; } elseif(is_dir($dir . $file)) { $count += recurse_delete_dir($dir . $file); } } // يمكننا الآن حذف المجلد rmdir($dir); return $count; } الحصول على معلومات ملف التحقق من كون المسار ملف أو مجلد تتحقق الدالة is_dir إن كان الوسيط الممرر إليها مجلدًا أم لا، فيما تتحقق الدالة is_file إن كان الوسيط الممرر إليها ملفًا أم لا، ويمكن استخدام الدالة file_exists للتحقق من وجود ملف أو مجلد محدد. $dir = "/this/is/a/directory"; $file = "/this/is/a/file.txt"; echo is_dir($dir) ? "$dir is a directory" : "$dir is not a directory", PHP_EOL, is_file($dir) ? "$dir is a file" : "$dir is not a file", PHP_EOL, file_exists($dir) ? "$dir exists" : "$dir doesn't exist", PHP_EOL, is_dir($file) ? "$file is a directory" : "$file is not a directory", PHP_EOL, is_file($file) ? "$file is a file" : "$file is not a file", PHP_EOL, file_exists($file) ? "$file exists" : "$file doesn't exist", PHP_EOL; الخرج: /this/is/a/directory is a directory /this/is/a/directory is not a file /this/is/a/directory exists /this/is/a/file.txt is not a directory /this/is/a/file.txt is a file /this/is/a/file.txt exists التحقق من نوع ملف نستخدم الدالة filetype لمعرفة نوع ملف محدد والذي قد يكون إمَّا أنبوبة مسماة FIFO أو ملف محرفي خاص char أو مجلد dir أو ملف كتلي خاص block أو وصلة رمزية link أو ملف عادي file أو مقبس socket أو نوع غير معروف unknown. تمرير اسم الملف إلى الدالة filetype مباشرةً: echo filetype("~"); // dir لاحظ أنّه إذا لم يكن الملف موجودًا فإنّ الدالة تعيد false وتشغّل E_WARNING. التحقق من قابلية القراءة والكتابة نمرر اسم الملف للدالتين is_writable وis_readable للتحقق من كون الملف قابلًا للكتابة أو القراءة. تعيد الدالتان القيمة false إذا لم يكن الملف موجودًا. التحقق من وقت الوصول إلى ملف وتعديله تعيد الدالتان filemtime وfileatime الختم الزمني لآخر تعديل أو وصول للملف، ويكون الختم الزمني المعاد بصيغة Unix. echo "File was last modified on " . date("Y-m-d", filemtime("file.txt")); echo "File was last accessed on " . date("Y-m-d", fileatime("file.txt")); الحصول على أجزاء المسار مع fileinfo $fileToAnalyze = ('/var/www/image.png'); $filePathParts = pathinfo($fileToAnalyze); echo '<pre>'; print_r($filePathParts); echo '</pre>'; خرج المثال السابق: Array ( [dirname] => /var/www [basename] => image.png [extension] => png [filename] => image ) يمكن استخدام هذا الخرج كالتالي: $filePathParts['dirname'] $filePathParts['basename'] $filePathParts['extension'] $filePathParts['filename'] المعامل تفاصيل ‎$path المسار الكامل للملف الذي نريد تحليله ‎$option ‫أحد الخيارات التالية: PATHINFO_DIRNAME أو PATHINFO_BASENAME أو PATHINFO_EXTENSION أو PATHINFO_FILENAME 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; } تُعاد مصفوفة ترابطية (associative array) إذا لم يُمرَّر المعامل الثاني وإلا تُعاد سلسلة نصية. لا تتحقق من وجود الملف تُحلَّل السلسلة النصية ببساطة إلى أجزاء ولا يُجرى أي تحقق على الملف (مثل التحقق من نوع الترويسة وغير ذلك) الامتداد هو الامتداد الأخير للمتغير ‎$path، سيكون مسار الملف image.jpg.png هو ‎.‎png حتى لو كان هو ملف ‎.‎jpg تقنيًا، وإذا كان الملف بدون امتداد لن يُعاد عنصر الامتداد في المصفوفة. خرج ودخل ملف معتمد على المجرى فتح مجرى تفتح الدالة fopen ملفًا وتخصص له مقبضًا يشبه مقبض الباب يمكن أن تستخدمه دوالًا أخرى للقراءة والكتابة والبحث وغيرها، هذه القيمة من النوع مورد ولا يمكن تمريرها إلى النياسب الأخرى المستمرة في وظيفتها. $f = fopen("errors.log", "a"); ستحاول الشيفرة السابقة فتح الملف errors.log للكتابة. المعامل الثاني هو نمط مجرى الملف: النمط الوصف r يفتح الملف للقراءة فقط بدءًا من أول الملف r+‎ يفتح الملف للقراءة والكتابة بدءًا من أول الملف w يفتح الملف للكتابة فقط بدءًا من أول الملف، إذا كان الملف موجودًا سيفرغه وإذا لم يكن موجودًا سيحاول أن ينشئه w+‎ يفتح الملف للقراءة والكتابه بدءًا من أول الملف، إذا كان الملف موجودًا سيفرغه وإذا لم يكن موجودًا سيحاول أن ينشئه a يفتح الملف للكتابة فقط بدءًا من نهاية الملف، إذا لم يكن الملف موجودًا سيحاول أن ينشئه a+‎ يفتح الملف للقراءة والكتابة بدءًا من نهاية الملف، إذا لم يكن الملف موجودًا سيحاول أن ينشئه x ‫ينشئ ويفتح ملف للكتابة فقط، وإذا كان الملف موجودًا ستفشل الدالة fopen ‏ x+‎ ‫ينشئ ويفتح ملف للكتابة فقط، وإذا كان الملف موجودًا ستفشل الدالة fopen ‏ c يفتح الملف للكتابة فقط، إذا لم يكن موجودًا سيحاول أن ينشئه، يبدأ الكتابة من أول الملف لكنه لا يفرغ الملف قبل الكتابة c+‎ يفتح الملف للقراءة والكتابة، إذا لم يكن موجودًا سيحاول أن ينشئه، يبدأ الكتابة من أول الملف لكنه لا يفرغ الملف قبل الكتابة إنّ إضافة المحرف t بعد النمط (مثل a+b، ‏wt وغير ذلك) في نظام التشغيل ويندوز ستترجم نهايات الأسطر "‎\n" إلى "‎\r\n" عند العمل مع الملف، نضيف المحرف b بعد النمط إذا لم يكن هذا مقصودًا خاصةً إذا كان الملف ثنائيًا. يجب أن يغلق تطبيق PHP أي مجرى بعد الانتهاء من استخدامه باستخدام الدالة fclose وذلك منعًا لظهور الخطأ Too many open files، يعدّ هذا مهمًا خاصةً في برامج واجهة سطر الأوامر (CLI) بما أنّ المجاري تُغلق عند إيقاف التشغيل فقط، وهذا يعني أن ذلك ليس ضروريًا جدًا في خوادم الويب (لكن يجب استخدامه أيضًا كممارسة لمنع تسرب المورد) إذا لم تتوقع تنفيذ العملية لوقتٍ طويل ولن تفتح عدة مجاري. القراءة نستخدم الدالة fread لقراءة عدد مُعطى من البايتات بدءًا من مؤشر الملف أو حتى الوصول إلى نهاية الملف (EOF). قراءة أسطر نستخدم الدالة fgets لقراءة ملف وتستمر بالقراءة حتى تصل إلى نهاية السطر (EOL) أو حتى تنتهي قراءة العدد المُعطى. ستحرِّك كل من الدالتين fread وfgets مؤشر الملف أثناء القراءة. قراءة كل البايتات المتبقية تضع الدالة stream_get_contents كل البايتات المتبقية في المجرى في سلسلة نصية وتعيدها. تعديل موضع مؤشر ملف يكون مؤشر الملف عند فتح المجرى افتراضيًا في بداية الملف (أو في نهايته إذا اُستعمل النمط)، نستخدم الدالة fseek لنحرّك مؤشر الملف إلى موضع جديد، نسبةً لإحدى القيم الثلاثة: SEEK_SET: هذه هي القيمة الافتراضية، تكون إزاحة موقع مؤشر الملف نسبةً لبدايته. SEEK_CUR: إزاحة موقع مؤشر الملف نسبةً للموقع الحالي. SEEK_END: إزاحة موقع مؤشر الملف نسبةً إلى نهاية الملف، ومن الشائع تمرير إزاحة سالبة لهذه القيمة، ستحرّك موقع مؤشر الملف عددًا محددًا من البايتات قبل نهاية الملف. تعد الدالة rewind اختصارًا ملائمًا للشيفرة fseek($fh, 0, SEEK_SET)‎. يُظهر استخدام الدالة ftell الموقع المطلق لمؤشر الملف. يتخطى السكربت التالي أول 10 بايتات، ويقرأ 10 بايتات التالية ثم يتخطى 10 بايتات ويقرأ 10 بايتات ثم يقرأ 10 بايتات الأخيرة من الملف file.txt: $fh = fopen("file.txt", "rb"); // ‫البدء من الإزاحة 10 fseek($fh, 10); // قراءة 10 بايتات echo fread($fh, 10); // تخطي 10 بايتات fseek($fh, 10, SEEK_CUR); // قراءة 10 بايتات echo fread($fh, 10); // تخطي 10 بايتات قبل نهاية الملف fseek($fh, -10, SEEK_END); // قراءة 10 بايتات echo fread($fh, 10); fclose($fh); الكتابة نستخدم الدالة fwrite لكتابة سلسلة في ملف بدءًا من موقع المؤشر الحالي. fwrite($fh, "Some text here\n"); نقل ونسخ ملفات ومجلدات نسخ ملفات تنسخ الدالة copy الملف المصدر المحدد في الوسيط الأول إلى الهدف المحدد في الوسيط الثاني، يجب أن يكون الهدف في مجلد مُنشأ بالفعل. if (copy('test.txt', 'dest.txt')) { echo 'File has been copied successfully'; } else { echo 'Failed to copy file to destination given.' } نسخ مجلدات مع عودية (recursion) يشبه نسخ الملفات حذفها إلى درجة كبيرة، باستثناء أننا نستخدم الدالة copy بدلًا من unlink للملفات وmkdir بدلًا من rmdir للمجلدات في بداية بدلًا من كونها في نهاية الدالة. function recurse_delete_dir(string $src, string $dest) : int { $count = 0; // ‫التأكد من أنّ كل من ‎$src و‎$dest ينتهي بخط مائل حتى يمكننا دمجه مع أسماء الملفات $src = rtrim($dest, "/\\") . "/"; $dest = rtrim($dest, "/\\") . "/"; // ‫استخدام dir()‎ للحصول على قائمة الملفات $list = dir($src); // ‫إنشاء المجلد ‎$dest إذا لم يكن موجودًا @mkdir($dest); // ‫تخزين اسم الملف التالي في ‎$file، إذا أصبحت قيمة ‎$file هي false تُنهى الحلقة while(($file = $list->read()) !== false) { if($file === "." || $file === "..") continue; if(is_file($src . $file)) { copy($src . $file, $dest . $file); $count++; } elseif(is_dir($src . $file)) { $count += recurse_copy_dir($src . $file, $dest . $file); } } return $count; } إعادة التسمية/النقل عملية إعادة التسمية/النقل أبسط بكثير، إذ يمكن نقل أو إعادة تسمية كامل المجلد في استدعاء واحد للدالة rename، أمثلة: rename("~/file.txt", "~/file.html"); rename("~/dir", "~/old_dir"); rename("~/dir/file.txt", "~/dir2/file.txt"); تقليل استخدام الذاكرة عند التعامل مع ملفات كبيرة يمكننا استخدام إحدى الدالتين file أو file_get_contents إذا احتجنا إلى تحليل ملف كبير مثل ملف CSV يتضمن ملايين الأسطر وحجمه أكبر من 10 ميجابايت وينتهي الأمر بالحصول على الخطأ: Allowed memory size of XXXXX bytes exhausted بفرض أنّ المصدر التالي top-1m.csv يحتوي على مليون سطر وحجمه حوالي 22 ميجابايت. var_dump(memory_get_usage(true)); $arr = file('top-1m.csv'); var_dump(memory_get_usage(true)); الخرج: int(262144) int(210501632) بما أنّ المفسّر يحتاج إلى حمل الأسطر في مصفوفة ‎$arr لذا فإنّه يستهلك 200 ميجابايت من ذاكرة الوصول العشوائي (RAM)، لاحظ أنّه لا يمكننا فعل أي شيء مع محتويات المصفوفة. بفرض أنّه لدينا الشيفرة التالية: var_dump(memory_get_usage(true)); $index = 1; if (($handle = fopen("top-1m.csv", "r")) !== FALSE) { while (($row = fgetcsv($handle, 1000, ",")) !== FALSE) { file_put_contents('top-1m-reversed.csv',$index . ',' . strrev($row[1]) . PHP_EOL,FILE_APPEND); $index++; } fclose($handle); } var_dump(memory_get_usage(true)); خرج الشيفرة السابقة: int(262144) int(262144) لا نستخدم بايتًا واحدًا إضافيًا من الذاكرة لكن نحلل كامل ملف CSV ونحفظه إلى ملف آخر مع عكس قيمة العمود الثاني، وذلك لأنّ الدالة fgetcsv تقرأ سطر واحد فقط ويُكتب فوق ‎$row عند كل تكرار. المجاري (streams) اسم المعامل الوصف Stream Resource ‫ يتألف مزود البيانات من الصيغة `‎://‎` تسجيل مغلِّف مجرى يوفر مغلِّف المجرى مقبضًا لمخطط محدد أو أكثر، يُظهر المثال التالي مُغلِّف مجرى بسيط يرسل طلبات PATCH HTTP عند إغلاق المجرى. // ‫تسجيل الصنف FooWrapper مغلِّفًا لمجرى روابط ‎foo://‎ stream_wrapper_register("foo", FooWrapper::class, STREAM_IS_URL) or die("Duplicate stream wrapper registered"); class FooWrapper { // ‫‫سيُعدَّل هذا من PHP لإظهار السياق الممرَّر في الاستدعاء الحالي public $context; // يستخدم هذا داخليًا في هذا المثال لتخزين الرابط private $url; // ‫عند استدعاء fopen()‎ مع بروتوكول لهذا المغلِّف، يمكن تنفيذ هذا التابع لتخزين بيانات مثل المضيف. public function stream_open(string $path, string $mode, int $options, string &$openedPath) :bool { $url = parse_url($path); if($url === false) return false; $this->url = $url["host"] . "/" . $url["path"]; return true; } // ‫معالجة استدعاءات الدالة fwrite()‎ على هذا المجرى public function stream_write(string $data) : int { $this->buffer .= $data; return strlen($data); } // ‫معالجة استدعاءات الدالة fclose()‎ على هذا المجرى public function stream_close() { $curl = curl_init("http://" . $this->url); curl_setopt($curl, CURLOPT_POSTFIELDS, $this->buffer); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_exec($curl); curl_close($curl); $this->buffer = ""; } // ‫مُعالج استثناء قيمة تراجعية في حال القيام بعملية غير مدعومة // هذا ليس ضروريًا‎ public function __call($name, $args) { throw new \RuntimeException("This wrapper does not support $name"); } // ‫تُستدعى الشيفرة التالية عند استدعاء unlink("foo://something-else")‎ public function unlink(string $path) { $url = parse_url($path); $curl = curl_init("http://" . $url["host"] . "/" . $url["path"]); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_exec($curl); curl_close($curl); } } يُظهر هذا المثال بعض الأمثلة على ما قد يحتويه مُغلِّف مجرى عام، ليست هذه كل التوابع المتوفرة، يمكنك أن تجد هنا كل التوابع المتوفرة والتي يمكن تنفيذها. ترميز UTF-8 الدخل يجب التحقق من كل سلسلة نصية مُستقبَلة أنّها مكتوبة بترميز UTF-8 صحيح قبل محاولة تخزينها في أي مكان، تقوم دالة mbcheckencoding()‎ بهذا العمل لكن يجب أن تستخدمها بشكلٍ منتظم، لا توجد طريقة لحل هذه المشكلة إذ يمكن للعملاء الضارّين إرسال البيانات بأي تشفير يريدونه. $string = $_REQUEST['user_comment']; if (!mb_check_encoding($string, 'UTF-8')) { // ‫ترميز السلسلة النصية ليس UTF-8 لذا أعد تشفيرها $actualEncoding = mb_detect_encoding($string); $string = mb_convert_encoding($string, 'UTF-8', $actualEncoding); } يمكنك تجاهل النقطة الأخيرة إذا كنت تستخدم HTML5، إذ أنّ الطريقة الموثوقة الوحيدة لتكون كل البيانات المرسلة إليك عبر المتصفحات بالترميز UTF-8 هي إضافة السمة accept-charset لكل وسوم <form> كالتالي: <form action="somepage.php" accept-charset="UTF-8"> الخرج إذا كان تطبيقك ينقل نصًا إلى الأنظمة الأخرى فأنت تحتاج إلى التأكد من ترميز المحارف، يمكنك في PHP استخدام خيار default_charset في php.ini أو التصريح عن نوع المحتوى يدويًا Content-Type MIME في الترويسة وهذه الطريقة الأفضل عند استهداف المتصفحات الحديثة. header('Content-Type: text/html; charset=utf-8'); يمكنك ضبط الترميز في ملف HTML باستخدام البيانات الوصفية إذا كنت غير قادرٍ على ضبط ترويسة الرد. // HTML5 <meta charset="utf-8"> // ‫الإصدارات الأقدم من HTML‫ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> تخزين البيانات والوصول إليها تتحدث هذه الفقرة بشكلٍ أساسي عن الترميز UTF-8 ودوافع استخدامه مع قاعدة البيانات. تخزين البيانات في قاعدة بيانات MySQL: عندما تحدد مجموعة المحارف (character set) ‏utf8mb4 لكل الجداول والأعمدة النصية في قاعدة البيانات فأنت تجعل MySQL تخزّن وتستعيد القيم المرمّزة محليًا بالترميز UTF-8. تستخدم MySQL ضمنيًا الترميز utf8mb4 إذا حُدِّد الترتيب utf8mb4_*‎ (دون أي مجموعة محارف صريحة). لا تدعم النسخ القديمة من MySQL (الإصدار ما قبل ‏‎5.5.3‏) الترميز utf8mb4 لذا ستضطر إلى استخدام الترميز utf8 الذي يدعم مجموعة فرعية من محارف يونيكود (Unicode). الوصول إلى البيانات في قاعدة بيانات MySQL: في شيفرة التطبيق (PHP مثلًا)، مهما كانت الطريقة التي تستخدمها للوصول إلى قاعدة البيانات فأنت تحتاج إلى ضبط ترميز محارف الاتصال لتكون utf8mb4، بهذه الطريقة لا تحوّل MySQL من ترميز UTF-8 الأصلي عند تسليم البيانات إلى تطبيقك وبالعكس. توفر بعض برامج التشغيل آلية خاصة بها لضبط ترميز محارف الاتصال والتي تحدّث حالتها الداخلية وتُخبر MySQL بالترميز الذي سيُستخدم في الاتصال، وهذه هي الطريقة المفضّلة عادةً. مثلًا (نطبق نفس الدوافع التي في الأعلى فيما يتعلق باستخدام ‎utf8mb4/utf8‎): يمكنك تحديد ترميز المحارف باستخدام DSN إذا كنت تستخدم طبقة التجريد PDO ᠎مع إصدار PHP ≥ 5.3.6: $handle = new PDO('mysql:charset=utf8mb4'); يمكنك استدعاء الدالة set_charset()‎ إذا كنت تستخدم mysqli: $conn = mysqli_connect('localhost', 'my_user', 'my_password', 'my_db'); // نمط كائني التوجه $conn->set_charset('utf8mb4'); // نمط إجرائي mysqli_set_charset($conn, 'utf8mb4'); يمكنك استدعاء الدالة mysql_set_charset إذا كنت تستخدم MySQL البسيطة وإصدار PHP ≥ 5.2.3. $conn = mysql_connect('localhost', 'my_user', 'my_password'); // نمط كائني التوجه $conn->set_charset('utf8mb4'); // نمط إجرائي mysql_set_charset($conn, 'utf8mb4'); إذا لم يوفر نظام تشغيل قاعدة البيانات آلية خاصة به لضبط ترميز محارف الاتصال، قد تضطر إلى إصدار استعلام لإخبار MySQL عن الطريقة التي يتوقع بها تطبيقك تشفير البيانات عند الاتصال بالشكل: SET NAMES 'utf8mb4'‎. دعم يونيكود في PHP تحويل محارف يونيكود إلى تنسيق "‎\uxxxx" باستخدام PHP يمكنك استخدام الشيفرة التالية للترميز وفك الترميز: if (!function_exists('codepoint_encode')) { function codepoint_encode($str) { return substr(json_encode($str), 1, -1); } } if (!function_exists('codepoint_decode')) { function codepoint_decode($str) { return json_decode(sprintf('"%s"', $str)); } } طريقة الاستخدام echo "\\nUse JSON encoding / decoding\\n"; var_dump(codepoint_encode("我好")); var_dump(codepoint_decode('\\u6211\\u597d')); الخرج: Use JSON encoding / decoding string(12) "\\u6211\\u597d" string(6) "我好" تحويل محارف يونيكود إلى قيمها الرقمية و/أو كيانات HTML باستخدام PHP يمكنك استخدام الشيفرة التالية للترميز وفك الترميز: if (!function_exists('mb_internal_encoding')) { function mb_internal_encoding($encoding = NULL) { return ($from_encoding === NULL) ? iconv_get_encoding() : iconv_set_encoding($encoding); } } if (!function_exists('mb_convert_encoding')) { function mb_convert_encoding($str, $to_encoding, $from_encoding = NULL) { return iconv(($from_encoding === NULL) ? mb_internal_encoding() : $from_encoding,$to_encoding, $str); } } if (!function_exists('mb_chr')) { function mb_chr($ord, $encoding = 'UTF-8') { if ($encoding === 'UCS-4BE') { return pack("N", $ord); } else { return mb_convert_encoding(mb_chr($ord, 'UCS-4BE'), $encoding, 'UCS-4BE'); } } } if (!function_exists('mb_ord')) { function mb_ord($char, $encoding = 'UTF-8') { if ($encoding === 'UCS-4BE') { list(, $ord) = (strlen($char) === 4) ? @unpack('N', $char) : @unpack('n', $char); return $ord; } else { return mb_ord(mb_convert_encoding($char, 'UCS-4BE', $encoding), 'UCS-4BE'); } } } if (!function_exists('mb_htmlentities')) { function mb_htmlentities($string, $hex = true, $encoding = 'UTF-8') { return preg_replace_callback('/[\x{80}-\x{10FFFF}]/u', function ($match) use ($hex) { return sprintf($hex ? '&#x%X;' : '&#%d;', mb_ord($match[0])); }, $string); } } if (!function_exists('mb_html_entity_decode')) { function mb_html_entity_decode($string, $flags = null, $encoding = 'UTF-8') { return html_entity_decode($string, ($flags === NULL) ? ENT_COMPAT | ENT_HTML401 : $flags,$encoding); } } طريقة الاستخدام echo "Get string from numeric DEC value\n"; var_dump(mb_chr(50319, 'UCS-4BE')); var_dump(mb_chr(271)); echo "\nGet string from numeric HEX value\n"; var_dump(mb_chr(0xC48F, 'UCS-4BE')); var_dump(mb_chr(0x010F)); echo "\nGet numeric value of character as DEC string\n"; var_dump(mb_ord('ď', 'UCS-4BE')); var_dump(mb_ord('ď')); echo "\nGet numeric value of character as HEX string\n"; var_dump(dechex(mb_ord('ď', 'UCS-4BE'))); var_dump(dechex(mb_ord('ď'))); echo "\nEncode / decode to DEC based HTML entities\n"; var_dump(mb_htmlentities('tchüß', false)); var_dump(mb_html_entity_decode('tchüß')); echo "\nEncode / decode to HEX based HTML entities\n"; var_dump(mb_htmlentities('tchüß')); var_dump(mb_html_entity_decode('tchüß')); الخرج: Get string from numeric DEC value string(4) "ď" string(2) "ď" Get string from numeric HEX value string(4) "ď" string(2) "ď" Get numeric value of character as DEC int int(50319) int(271) Get numeric value of character as HEX string string(4) "c48f" string(3) "10f" Encode / decode to DEC based HTML entities string(15) "tch&#252;&#223;" string(7) "tchüß" Encode / decode to HEX based HTML entities string(15) "tch&#xFC;&#xDF;" string(7) "tchüß" استخدام الإضافة Intl لدعم يونيكود تُربط دوال السلسلة النصية الأصلية إلى دوال البايت المفرد ولا تعمل بشكلٍ جيد مع ترميز يونيكود (unicode)، توفر الإضافات iconv وmbstring بعض الدعم لترميز يونيكود بينما توفر الإضافة Intl الدعم الكامل. تعدّ الإضافة Intl مغلّفة للمعيار المعتمد لمكتبة ICU (‏International Components for unicode)، يمكنك الاطلاع على الموقع لمعرفة المزيد من المعلومات التفصيلية غير الموجودة هنا. اطّلع على تنفيذ بديل للإضافة Intl من بيئة العمل Symfony إذا لم تتمكن من تثبيت الإضافة. توفر ICU تدويلًا كاملًا وترميز يونيكود هو جزء صغير منه، يمكنك التحويل بسهولة: // إزالة البايتات السيئة للحماية من الهجمات \UConverter::transcode($sString, 'UTF-8', 'UTF-8'); لكن يجب ألا تتخلى عن الإضافة iconv: \iconv('UTF-8', 'ASCII//TRANSLIT', "Cliënt"); // "Client" ترجمة -وبتصرف- للفصول [File handling - Streams - UTF-8 - Unicode Support in PHP] من كتاب PHP Notes for Professionals book
  6. ‎call()‎__ و‎callStatic()‎__ يُستدعى التابعين ‎__call()‎ و‎__callStatic()‎ عندما تريد استدعاء تابع كائن غير موجود في سياق تابع أو سياق ساكن. في الشيفرة التالية يُستدعى التابع ‎__call()‎ عندما يحاول شخص ما استدعاء تابع في سياق كائن غير موجود مثل ‎$foo->method($arg, $arg1);‎، سيحتوي الوسيط الأول على اسم التابع وهو method في مثالنا وسيحتوي الوسيط الثاني على قيم ‎$arg و‎$arg1 كمصفوفة. ويُستدعى التابع ‎__callStatic()‎ من محتوى ساكن عند استدعاء تابع ساكن غير موجود، مثلًا Foo::buildSomethingCool($arg);‎، سيحتوي الوسيط الأول على اسم التابع وهو buildSomethingCool في مثالنا ويحتوي الوسيط الثاني على قيمة ‎$arg في مصفوفة. لاحظ أنّ بصمة هذا التابع مختلفة (يتطلب الكلمة المفتاحية static)، هذا التابع لم يكن موجودًا قبل الإصدار PHP 5.3. class Foo { public function __call($method, $arguments) { // (1) $snakeName = CaseHelper::camelToSnake($method); // الحصول على البادئة $subMethod = substr($snakeName, 0, 3); // إسقاط اسم التابع $propertyName = substr($snakeName, 4); switch ($subMethod) { case "get": return $this->data[$propertyName]; case "set": $this->data[$propertyName] = $arguments[0]; break; case "has": return isset($this->data[$propertyName]); default: throw new BadMethodCallException("Undefined method $method"); } } public static function __callStatic($method, $arguments) { // يمكن استخدام هذا التابع عندما تحتاج شيء ما مثل مصنع عام أو شيء ما آخر print_r(func_get_args()); } } في الموضع (1) نفعل شيء ما مع المعلومات مثل زيادة التحميل أو شيء عام، فمثلًا لنفرض أننا نكتب صنفًا عامًا يحمل بعض البيانات ويسمح للمستخدم بأن يجلبها ويضبطها باستخدام التوابع الجالب والضابط، وكان لدينا الصنف CaseHelper يساعد على تحويل نمط سنام الجمل (camelCase) إلى نمط الثعبان (snake_case). هذا التابع مبسّط لذا لا يتحقق من صلاحية الاسم أو عدمها. مثال: $instance = new Foo(); $instance->setSomeState("foo"); var_dump($instance->hasSomeState()); // bool(true) var_dump($instance->getSomeState()); // string "foo" Foo::exampleStaticCall("test"); /* Array ( [0] => exampleCallStatic [1] => test ) */ ‎get()‎__ و‎__set()‎ و‎__isset()‎ و‎__unset()‎ كلما حاولت استعادة حقل معين من صنف كما في الشيفرة: $animal = new Animal(); $height = $animal->height; تستدعي PHP التابع السحري ‎__get($name)‎ ويكون ‎$name هو "height" في حالتنا. كتابة حقل ما من الصنف كالتالي: $animal->height = 10; ستستدعي التابع السحري ‎__set($name, $value),‎ ويكون ‎$name هو "height" و‎$value هو 10. لدينا في PHP أيضًا تابعين مدمجين هما ‎isset()‎ الذي يتحقق من وجود متغير، وunset‎()‎ الذي يدمر متغير. إنّ الشيفرة التالية: isset($animal->height); ستستدعي الدالة ‎__isset($name)‎ على ذلك الكائن، كما أنّ إزالة تعيين متغير كما في الشيفرة: unset($animal->height); ستستدعي الدالة ‎__unset($name)‎ على ذلك الكائن. تستعيد PHP الحقل كما هو مخزن في الصنف عندما لاتعرّف هذه التوابع في صنفك، لكن يمكنك تجاوز هذه التوابع لإنشاء أصناف يمكنها أن تحمل بيانات مثل المصفوفة لكنها قابلة للاستخدام ككائن: class Example { private $data = []; public function __set($name, $value) { $this->data[$name] = $value; } public function __get($name) { if (!array_key_exists($name, $this->data)) { return null; } return $this->data[$name]; } public function __isset($name) { return isset($this->data[$name]); } public function __unset($name) { unset($this->data[$name]); } } $example = new Example(); // وقيمتها 15 $data في المصفوفة 'a' تخزين $example->a = 15; // $data من المصفوفة 'a' استعادة مفتاح المصفوفة echo $example->a; // 15 // null محاولة استعادة مفتاح غير موجود في المصفوفة تُعيد echo $example->b; // لا يطبع شيء if (isset($example->a)) { unset($example->a)); } الدالة empty()‎‍ والتوابع السحرية لاحظ أنّ استدعاء الدالة empty()‎ على خاصيّة صنف ستستدعي الدالة ‎__isset($name)‎ حسب ما ورد في توثيق PHP: ‏empty()‎ هي المكافئ المختصر للشيفرة: !isset($var) || $var == false ‎__construct()‎ و‎__destruct()‎ ‎__construct()‎ هو التابع السحري الأشهر في PHP لأنّه يُستخدم لضبط صنف عند تهيئته، أما التابع العكسي له ‎__destruct()‎ فإنّه يُستدعى عندما لا يوجد مراجع متبقية للكائن الذي أنشأته أو عندما تفرض حذفه وعندها ستقوم مجموعة المهملات بتنظيف الكائن عن طريق استدعاء الهادم أولًا ثمّ حذفه من الذاكرة: class Shape { public function __construct() { echo "Shape created!\n"; } } class Rectangle extends Shape { public $width; public $height; public function __construct($width, $height) { parent::__construct(); $this->width = $width; $this->height = $height; echo "Created {$this->width}x{$this->height} Rectangle\n"; } public function __destruct() { echo "Destroying {$this->width}x{$this->height} Rectangle\n"; } } function createRectangle() { // (1) $rectangle = new Rectangle(20, 50); // (2) } createRectangle(); // (3) // (4) unset(new Rectangle(20, 50)); في الموضع (1) ستستدعي تهيئة الكائن الباني مع الوسطاء المحددين. في الموضع (2) سيُطبع 'Shape Created' ثم 'Created 20x50 Rectangle'. في الموضع (3) سيُطبع 'Destroying 20x50 Rectangle' لأنّ الكائن ‎$rectangle هو كائن محلي بالنسبة للدالة createRectangle لذا عندما ينتهي نطاق الدالة سيُدمَّر الكائن ويُستدعى هادمه. في الموضع (4) سيُستدعى هادم الكائن عند استخدام الدالة unset. ‎__toString()‎ يُستدعى التابع ‎__toString()‎ عندما يُعامل الكائن على أنّه سلسلة نصية، ويعيد سلسلة نصية تمثّل الصنف: class User { public $first_name; public $last_name; public $age; public function __toString() { return "{$this->first_name} {$this->last_name} ($this->age)"; } } $user = new User(); $user->first_name = "Chuck"; $user->last_name = "Norris"; $user->age = 76; // في سياق سلسلة $user كلما اُستخدم الكائن‏ __toString() سيُستدعى التابع‏‏ echo $user; // Chuck Norris (76) // Selected user: Chuck Norris (76) :أصبحت قيمة السلسلة النصية $selected_user_string = sprintf("Selected user: %s", $user); // __toString() التحويل إلى سلسلة نصية يستدعي أيضًا $user_as_string = (string) $user; ‎__clone()‎ يُستدعَى التابع ‎__clone باستخدام الكلمة المفتاحية clone، ويستخدم لمعالجة حالة كائن عند النسخ بعد أن يكون الكائن قد نُسخَ فعلًا. class CloneableUser { public $name; public $lastName; // "Copy " سيُستدعى هذا التابع بعامل النسخ ويضيف قبل خاصيّات‏‏‏ الاسم والاسم الأخير الكلمة‏ public function __clone() { $this->name = "Copy " . $this->name; $this->lastName = "Copy " . $this->lastName; } } مثال: $user1 = new CloneableUser(); $user1->name = "John"; $user1->lastName = "Doe"; // __clone تنفيذ التابع السحري $user2 = clone $user1; echo $user2->name; // Copy John echo $user2->lastName; // Copy Doe ‎__invoke()‎ يُستدعى هذا التابع السحري عندما يحاول المستخدم استدعاء كائن كدالة، قد تتضمن حالات الاستخدام الممكنة بعض الطرق مثل البرمجة الوظيفية أو بعض ردود النداء. سيُستدعى التابع في الشيفرة التالية إذا نُفِّذ الكائن كدالة: ‎$invokable();‎ وستمرر الوسائط كما في استدعاء التابع العادي: class Invokable { public function __invoke($arg, $arg, ...) { print_r(func_get_args()); } } مثال: $invokable = new Invokable(); $invokable([1, 2, 3]); /* Array ( [0] => 1 [1] => 2 [2] => 3 ) */ ‎⁠__sleep()‎ و‎__wakeup()‎ يرتبط التابعان ‎__sleep و‎__wakeup بعملية السَلسَلة، تتحقق الدالة serialize إذا كان للصنف تابع ‎__sleep‎، إذا كان موجودًا سيُنفَّذ قبل أي سَلسَلة ويُفترض أن يعيد مصفوفة من أسماء كل المتغيرات للكائن الذي يجب أن يُسلسَل. وسيُنفَّذ التابع ‎__wakeup بدوره من قبل الدالة unserialize إذا وُجد في الصنف، ويهدف إلى إعادة إنشاء الموارد والأشياء الأخرى التي نحتاج تهيئتها بعد إلغاء التسلسل. في الشيفرة التالية سيُنفَّذ التابع السحري ‎__sleep من قِبل الدالة serialize ولاحظ أنّ ‎$dbConnection مُستبعد، وسيُنفَّذ التابع السحري ‎__wakeup من قِبل الدالة unserialize، لنفرض مثلًا أنّ ‎$this->c الذي لم يُسَلسَل هو نوع من الاتصال بقاعدة البيانات سيُعيد الاتصال عندما نستخدم تابع الاستيقاظ. class Sleepy { public $tableName; public $tableFields; public $dbConnection; public function __sleep() { // فقط $this->tableNameو $this->tableFields سيُسَلسَل return ['tableName', 'tableFields']; } public function __wakeup() { // الاتصال بقاعدة البيانات الافتراضية وتخزين المُعالج/المُغلِّف فيها // $this->dbConnection $this->dbConnection = DB::connect(); } } ‎__debugInfo()‎ يُستدعى هذا التابع من قِبل الدالة var_dump()‎ عند تفريغ كائن للحصول على الخاصيّات التي يجب عرضها، إذا لم يُعرَّف التابع على الكائن ستُعرَض كل الخاصيّات العامة والمحمية والخاصة. class DeepThought { public function __debugInfo() { return [42]; } } الإصدار PHP ≤ 5.6 var_dump(new DeepThought()); خرج الشيفرة السابقة: class DeepThought#1 (0) { } الإصدار PHP ≥ 5.6 var_dump(new DeepThought()); خرج الشيفرة السابقة: class DeepThought#1 (1) { public ${0} => int(42) } ترجمة -وبتصرف- للفصل [Magic Methods] من كتاب PHP Notes for Professionals book
  7. الملحِّن أو 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
  8. ما هي السمة (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
  9. المعامل تفاصيل ‎$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
  10. الانعكاس كشف ميزة الأصناف أو الكائنات يمكن إجراء كشف ميزة الأصناف جزئيًا مع الدالتين 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
  11. الدالة تفاصيل 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
  12. التصريح عن فضاء اسم يُصرَّح عن فضاء الاسم كالتالي: للتصريح عن فضاء الاسم 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
  13. الجلسات خيارات 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
  14. الدالة 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
  15. تستخدم الأصناف والكائنات لجعل الشيفرة أكثر فعالية وأقل تكرارًا عن طريق تجميع الوظائف المتشابهة. يستخدم الصنف لتعريف الوظائف وبنى المعطيات المستخدمة لإنشاء كائنات، ثم تُنشأ الكائنات باستخدام البنية المعرّفة مسبقًا. ثوابت الصنف توفر ثوابت الصنف (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
  16. الدالة 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
  17. استيفاء السلسلة النصية يمكنك استخدام الاستيفاء لتستوفي (تُدرج) متغير في سلسلة نصية (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
  18. قائمة الوسطاء ذات الطول المتغير النسخة 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
  19. التعليمات الشرطية 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
  20. تطبيق دالة على كل عنصر من عناصر المصفوفة نستخدم الدالة 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
  21. الصنف 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
  22. المصفوفة (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
  23. الإسناد بالمرجعية (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
  24. العامل هو عنصر يأخذ قيمة واحدة أو أكثر (أو تعابير في المصطلحات البرمجية) ويُنتج قيمة جديدة (لذا تصبح البنية بحد ذاتها تعبيرًا)، ويمكن تجميع العوامل وفقًا لعدد القيم التي تأخذها. عامل تجميع 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
  25. من المفيد لبناء برنامج 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