عبد اللطيف ايمش

المساهمون
  • المساهمات

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

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

  • Days Won

    12

السُّمعة بالموقع

75 Excellent

9 متابعين

آخر الزُوّار

3,529 زيارة للملف الشّخصي
  1. هل وجدتَ موقعك الذي يعتمد على ووردبريس معطلًا وكل ما يمكنك قوله هو «لم أفعل شيئًا! صدقًا!» ومع ذلك سترى رسائل الخطأ التي تقول أنَّ ملفاتك ناقصة أو قاعدة بياناتك تالفة… لا تقلق في حال اختفت المنشورات والتصنيفات فجأةً، أو ظهرت رسائل خطأ مثل: “Warning: require_once(path/to/file.php) [function.require-once]: failed to open stream: No such file or directory in…” أو “Cannot establish database connection.” فسأشرح كيف تحدث مختلف أنواع رسائل الخطأ السابقة، وكيفية حل تلك المشاكل؛ وسأبيّن لك كيف أنَّه من غير المرجّح أن تكون أنت سبب المشكلة. حسنًا، ما هي الفكرة الرئيسية هنا؟ يصعب عادةً معرفة المُسبِّب الرئيسي لأيٍّ من الأخطاء السابقة لوجود عدد كبير (نسبيًا) من المسببات. هذه هي الأسباب الرئيسية التي تؤدي إلى تلف في قاعدة البيانات، أو فقدانها: لم توضَع معلومات الدخول بشكلٍ صحيحٍ في ملف wp-confing.php. حدثت مشكلة في نظام التشغيل. تعطلت إحدى القطع العتادية في الخادوم. حدثت علّة في خادوم MySQL أو مفسر PHP أو في القوالب والإضافات التي تستعملها. استهلاكك للموارد أصبح كبيرًا مما أدى إلى إيقاف محاولة الوصول إلى قاعدة البيانات (مثل PHP memory limit). ربما تعرضتَ إلى هجومٍ إلكتروني، وتم اختراق موقعك. هنالك قائمةٌ أطول للأسباب المحتملة لفقدان ملفاتك: أذونات الملفات غير مضبوطة ضبطًا صحيحًا. الجدار الناري (أو برمجيات الحماية الأخرى) لخادومك يمنع الوصول إلى بعض أجزاء موقعك. أدخلتَ رابط URL للموقع بشكلٍ خاطئ في صفحة «Settings > General» (الإعدادات > عام). الإضافات الخارجية حذفت أحد الملفات أو سببت مشكلةً في المسارات. ثبّتت شهادة SSL ونسيت استبدال روابط الصور. الروابط الدائمة غير مضبوطة ضبطًا صحيحًا. هنالك أخطاء في ملف ‎.htaccess. رُفعِتَ الصور في مجلدٍ خاطئ. تمت مقاطعة عملية رفع المحتوى. هنالك علّة في إضافة أو سكربت أو قالب تستعمله في موقعك. ربما حدثت مشكلة في الخادوم. ربما تعرضتَ إلى هجومٍ إلكتروني، وتم اختراق موقعك. بغض النظر عن المسبب الرئيسي، فهنالك بضع طرائق لإصلاح مشاكل الملفات وقاعدة البيانات. النسخ الاحتياطي والاستعادة منه أسهل طريقة لحل مشكلة تتعلق بقواعد البيانات أو فقدان الملفات هي استعادة نسخة احتياطية من موقعك؛ أما لو لم يكن لديك نسخةٌ احتياطيةٌ لتستعيدها، فهنالك حلولٌ أخرى يمكنك تجربتها. لكن قبل أن تجرِّب الحلول الأخرى، فأنصحك بأخذ نسخة احتياطية لكامل موقعك، حتى لو لم يكن يعمل، لأنَّه قد تسوء بعض الأمور ومن الأفضل أن تكون لديك نسخةٌ أساسيةٌ (حتى لو كانت معطلةً) عوضًا أن تكون خالي الوفاض. ألق نظرة على هذا المقال للمزيد من التفاصيل عن النسخ الاحتياطي: الحماية وإضافات النسخ الاحتياطي في ووردبريس وبعد أن تأخذ نسخةً احتياطيةً من موقعك لتستعملها في حال وقوع كارثة، فأنت جاهزٌ لتبدأ بإصلاح الموقع. إصلاح مشكلة في قاعدة البيانات هنالك عدِّة طرائق يمكنك فيها إصلاح قاعدة البيانات. يمكنك أن تجرب الطريقة الآتية التي تحل أغلبية المشاكل التي تواجه قواعد البيانات. يمكنك إضافة السطر الآتي إلى ملف wp-config.php والموجود في المجلد الجذر لموقعك: define( 'WP_ALLOW_REPAIR', true ); يمكنك إضافة السطر السابق في أي مكان في الملف لكن لا تضفه داخل دالة (على سبيل المثال). أنصحك بوضعه قبل السطر الآتي مباشرةً: /* That's all, stop editing! Happy blogging. */ لتفاصيلٍ إضافيةٍ حول طريقة تعديل ملف wp-config.php فراجع درس «كيف تستخدم FTP لنقل ملفات مدونة ووردبريس الخاص بك؟». بعد أن أضفتَ السطر السابق، فيمكنك زيادة صفحة إصلاح قاعدة البيانات من الرابط الآتي: http://your-site.com/wp-admin/maint/repair.php أو عليك زيارة https://your-site.com/wp-admin/maint/repair.php إذا كانت عندك شهادة SSL. لا تنسَ وضع اسم نطاق موقعك بدلًا من your-site.com. ابقِ في ذهنك أنَّه ليس من الضروري أن تسجِّل دخولك لرؤية هذه الصفحة، لذا بعد انتهاءك من إصلاح قاعدة البيانات، فاحرص على حذف السطر السابق، وإلا فسيستطيع أيُّ شخصٍ الوصولَ إلى تلك الصفحة. عندما تزور الصفحة السابقة، تستطيع أن تضغط على زر «Repair Database» لإصلاح قاعدة البيانات، أو يمكنك إصلاح قاعدة البيانات وتحسينها عبر الضغط على زر «Repair and Optimize Database». بعد إنهاء العملية السابقة، يجب أن يكون موقعك قد أُصلِحَ وأصبحَ جاهزًا للاستعمال. لكن إذا لم تفلح محاولتنا السابقة، فيمكننا إصلاح قاعدة البيانات عبر phpMyAdmin. بعد تسجيل الدخول إلى phpMyAdmin، فاضغط على اسم قاعدة البيانات الموجود في القائمة على يسار الشاشة، ثم بعد ظهور جداول قاعدة البيانات في الصفحة، فمرِّر إلى الأسفل ثم اضغط على مربع الاختيار المُعَنوَن Check All، ثم اختر «Repair table» من القائمة المنسدلة الموجودة بجوار مربع الاختيار. سيتم إصلاح جداول قاعدة البيانات تلقائيًا ويجب أن يعمل موقعك مجددًا. إصلاح الملفات التالفة أو المفقودة لكن ماذا لو كانت ملفاتك هي الناقصة أو التالفة؟ يمكنك إصلاحها أيضًا. إذا ثبّتتَ شهادة SSL في الآونة الأخيرة في موقعك، فقد يبدو أنَّ الصور أمست مفقودةً، ولحلّ هذه المشكلة عليك استبدال روابط الصور لكي تُضمِّن فيها السابقة https. لتفاصيل عن كيفية فعل ذلك، فراجع الدرس «استبدال روابط الصور على مواقع ووردبريس بعد تثبيت شهادة SSL». أما للغالبية العظمى من المشاكل الأخرى، فوضع نسخة جديدة من الملف المعطوب يجب أن يحل المشكلة. يمكنك استخدام FTP للوصول إلى موقعك واستبدال الملفات المعطوبة ووضع أخرى جديدة بدلًا منها، التي يمكنك أن تعثر عليها بتنزيل نسخة من برمجية ووردبريس من موقع WordPress.org. بعد فك ضغط ملف ZIP، يمكنك أن تنقل الملفات الجديدة إلى خادومك، لكن احرص على ترك المجلد ‎/wp-content/‎ دون تعديل، أو قد تعرِّض نفسك لخسارة جميع ملفات الوسائط والصور التي رفعتها إلى موقعك. يمكنك تطبيق ما سبق على الإضافات والقوالب، لكن الفرق الوحيد هو أنَّ عليك تنزيل نسخة حديثة من الإضافات أو القوالب بدلًا من ووردبريس نفسها. إذا فشل كل ما سبق… إذا جرّبتَ كل ما سبق، لكن لم يحالفك الحظ في تشغيل موقعك، فيمكنك أن تجرّب تفعيل نمط التنقيح في ووردبريس، لتفاصيل عن كيفية فعل ذلك، فارجع إلى درس «Debugging WordPress: How to Use WP_DEBUG». ربما تنظر أيضًا في سجل الأخطاء فلربما استطعت التعرف على المشكلة من هناك، ومن ثم ترسلها إلى شركة الاستضافة لتحليل الخطأ. ابحث عن ملفٍ باسم debug.log داخل مجلد ‎/wp-content/‎. إذا لم يحّل أيٌّ مما سبق مشكلتك، فحان الوقت لكي تراسل شركة الاستضافة، فقد تكون المشكلة من ضبط الخادوم، أو ربما أصابته مشكلة. والحل الوحيد لهذه المشكلة هو التواصل مع شركة الاستضافة وإعلامها بالأمر. حسنًا، هنالك حلٌّ أخير لا أحبذه: إذا لم تكن لديك نسخةٌ احتياطيةٌ وليست لديك مشكلة مع فقدان بيانات موقعك بالكامل، فيمكنك حذف الموقع وإعادة تثبيت ووردبريس. الخلاصة إذا طبّقتَ ما ذكرناه في هذا الدرس وحالفك الحظ فيجب أن يعمل موقعك عملًا سليمًا دون أن ترى أيّة رسائل خطأ تُشير إلى ملف ناقص أو قاعدة بيانات معطوبة. ولتجنب الصداع الناجم عن محاولة إصلاح الموقع (إذا صَدَفَ وتعطّل) فأنصحك بأخذ نسخ احتياطية دوريًّا. ترجمة -وبتصرّف- للمقال Repairing Corrupted, Broken or Missing Files and Databases in WordPress لصاحبته Jenni McKinnon.
  2. تمهيد عندما تُنشِئ حاوية Docker فسيُسنَد إليها مُعرِّف عالمي فريد (UUID) وهو ضروريٌ لتفادي التضاربات في الأسماء ولتسهيل أتمتة إنشاء الحاويات دون تدخل البشر. وهو مفيدٌ لكي يتعرف الحاسوب المضيف (والشبكة) على المضيف. لكن يصعب التفريق بين الحاويات بالنسبة للبشر، سواءً كنّا نقصد الاسم الطويل (64 محرف) أو المُعرِّف القصير (12 محرف) الذي يبدو كالآتي 285c9f0f9d3d. توفِّر Docker أسماءً مولّدةً تلقائيًا للحاويات تأتي من جمع كلمتين عشوائيتين بشرطة سفلية، مثلًا: evil_ptolemy، مما يُسهِّل التفريق بين الحاويات، لكن الأسماء العشوائية لا تعطينا أيّة فكرة عن وظيفة الحاوية مَثَلُهَا كمثل مُعرِّف UUID. سأخبرك بثلاث نصائح التي ستُسهِّل من التعرف على الحاويات. أولًا: سمِّ الحاوية عندما تُنشِئها وذلك بإضافة الخيار ‎--name=meaningful_name إلى الأمر docker run، وبهذا سيصبح اسم الحاوية ذا معنى عند استخدام الجلسات التفاعلية وفي ناتج بعض الأوامر مثل docker ps. لكن هنالك بعض المحدوديات، لأن أسماء الحاويات يجب أن تكون فريدةً، لهذا لا يمكن استخدام نفس الاسم لأكثر من حاوية. ضع ما يلي في سطر الأوامر أو في ملف Docker: docker run –name=meaningful_name على سبيل المثال، إذا أردنا إنشاء حاوية مبنيةً على صورة nginx فسنُنفِّذ الأمر: docker run --name nginx -d nginx وسيظهر الاسم في قائمة الحاويات التي تعمل حاليًا: docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 08f333ef7216 nginx "nginx -g 'daemon off" 15 seconds ago Up 14 seconds 80/tcp, 443/tcp nginx صحيحٌ أنَّ الاسم سيظهر في ناتج الأمر docker ps ويمكن استخدامه لإدارة الحاوية، لكنه لن يظهر في مِحَث (prompt) سطر الأوامر إذا فتحتَه، أو في السجلات. ولفعل ذلك عليك أن تُسنِد «اسم مضيف» (hostname) إلى الحاوية. ثانيًا: إسناد اسم مضيف إلى الحاوية القيمة المُمرَّرة إلى الخيار ‎--hostname ستُضبِط اسم المضيف داخل الحاوية في الملفين ‎/etc/hostname و ‎/etc/hosts. وبناءً على ذلك، سيظهر اسم المضيف في مِحَث سطر الأوامر، وسيلعب اسم المضيف دورًا في ضبط خدمة DNS للحاوية وقد يكون مفيدًا عند تعلم التعامل مع أكثر من حاوية. لكنه ليس ضروريًا للوصول إلى الحاوية من خارجها، إلا أنَّه سيظهر في ملفات السجل التابعة للحاوية، وعندما تُكتَب تلك الملفات إلى جهاز تخزين مستقل عن الحاسوب المضيف، فسيسهل التعرف على الحاوية بناءً على اسم المضيف الخاص بها. ضع ما يلي في سطر الأوامر أو في ملف Docker: docker run –hostname=value أو docker run -h value وصحيحٌ أنَّ ‎--name و ‎--hostname مفيدان للتعرف على الحاويات، لكن الأمر ليس متعلقًا بإطلاق اسم على الحاوية فحسب، وإنما ستستفيد منه (في السجلات مثلًا) إذا ضبطتَ الحاوية لكي تُحذَف تلقائيًا بعد انتهاءك منها. ثالثًا: حذف الحاويات تلقائيًا بعد انتهاء تنفيذها عندما تجرّب مع حاويات Docker، فقد تستفيد في بعض الأحيان من الحفاظ على الحاوية بعد انتهاء تنفيذها. يمكنك حفظ البيانات مثل ملفات السجلات وتتفحص آخر حالة للحاوية. لكن في بعض الأحيان لا ترغب في بقاء الحاوية بعد انتهاء التجربة معها، وفي هذه الحالة يمكنك استخدام الخيار ‎--rm لحذفها عند انتهاء تنفيذها. وهذا سيُسهِّل من «تنظيف» بيئة التجارب. لكن انتبه! إذا كنتَ تستخدم حجوم Docker ‏(Docker volumes) فالخيار ‎--rm سيؤدي إلى حذف أيّة حجوم مرتبطة بالحاوية. ضع ما يلي في سطر الأوامر أو في ملف Docker: docker run –rm ستستفيد من هذا الخيار كثيرًا عند إنشائك لصورة Docker وتحتاج إلى الوصول إلى حاوية للتجربة عليها، لكنك لا تريد أن تملأ كامل قرصك بحاويات لن تستعملها مرةً أخرى. الخلاصة هنالك ثلاثة خيارات للأمر docker run: الخيار ‎--name و ‎--hostname و ‎--rm التي تُسهِّل من تعاملك مع حاويات Docker عندما تحاول تعلم التعامل معها. يمكنك العثور على مزيدٍ من المعلومات حول الأمر docker run في درس «التعامل مع حاويات Docker». ترجمة -وبتصرّف- للمقال Naming Docker Containers: 3 Tips for Beginners لصاحبته Melissa Anderson
  3. قبل البدء بتوضيح كيفية استخدام Codeception مع شيفرات PHP، فيجب أن نغطي الأساسيات عبر شرحنا لماذا نحتاج إلى اختبار تطبيقاتنا. ربما يمكننا إكمال المشروع الذي نعمل عليه دون تضييع الوقت بكتابة الاختبارات، أليس ذلك؟ بلى، إذ لن تحتاج إلى كتابة اختبارات لكل شيء (كإنشاء صفحة رئيسية لموقعك). لن تحتاج غالبًا إلى كتابة اختبارات عندما يحتوي مشروعك على صفحات ثابتة مرتبطة بصفحة توجيه وحيدة. لكنك ستحتاج إلى إجراء اختبارات عندما: يستخدم فريقك أسلوب «التطوير الموجّه بالاختبارات» (TDD) أو «التطوير الموجّه بالسلوك» (BDD). تُجرى عدِّة عمليات إيداع في مستودع Git التابع لمشروعك يوميًا. أنت مطوِّرٌ محترف، وتعمل على مشروع جاد. يمكنك أن تعذر نفسك بقولك أنَّ هنالك فريق كامل مخصص للاختبارات، وهم مجموعةٌ من الأشخاص الذين يجرون اختبارات ويكتبونها عند الحاجة. لكن لك أن تتخيل كم سيستغرق حلّ العلّة من الوقت بعد أن تضيف وظائف جديدة إلى مشروعك. ما المشاكل التي تحلها الاختبارات؟ لنتعرّف أولًا على المشاكل التي يمكن حلها عبر الاختبارات. لن تتمكن من التخلص من جميع الأخطاء الظاهرة عند الاختبار، لكنك ستستطيع توصيف السلوك المتوقع في حالات الاختبار (test cases). انتبه إلى أنَّه قد توجد أخطاء ضمن حالات الاختبار التي تكتبها. لكن كن على يقين أنَّ شيفرتك ستتغير فيما بعد (إما بتصحيح العلل، أو إضافة ميزات جديدة، حتى تصبح شيفرتك خاليةً من الأخطاء الموصوفة في حالة الاختبار). يمكن -إضافةً إلى ما سبق- استعمال حالات الاختبار المكتوبة بشكلٍ جيد في التوثيق لأنك سترى من خلالها ما هو السلوك المتوقع للبرنامج في حالات معيّنة. خلاصة القول أنَّ كتابة الاختبارات هي استثمارٌ مهمٌ تستفيد منه في المستقبل. إذًا، ما هي الاختبارات التي يمكننا استعمالها في مشروعنا؟ اختبارات الوحدات (unit tests): هي اختباراتٌ منخفضة المستوى (low-level) التي تتحقق من سلامة أجزاء صغيرة من شيفرتك، والتي تكون عادةً الدوال الموجودة في الأصناف (classes) الموجودة في مشروعك والمعزولة عن بعضها. اختبارات التكامل (integration tests): تتحقق اختبارات التكامل من سلامة أداء جزءٍ من برنامجك والذي قد يحتوي على عدِّة أصناف أو دوال، إلا أنها تؤدي جميعًا غرضًا وحيدًا ألا وهو توفير ميزة معيّنة. يجب أن تتحقق هذه الاختبارات من كيفية تفاعل الأصناف مع بعضها. الاختبارات الوظيفية (function tests): اختبار استجابة التطبيق لمختلف الطلبيات، كالتغيرات الحاصلة في قاعدة البيانات ...إلخ. اختبارات القبول (acceptance tests): الغرض من هذه الاختبارات في أغلبية الحالات هو معرفة إن كان التطبيق يلبّي جميع متطلبات العميل. للتوضيح، لنحاول شرح هذه العملية بتمثيلها على شيءٍ ملموس مثل المباني. تتألف بعض أنواع المباني من لبنات صغيرة تُشكِّل جدرانًا، ويجب أن تتحقق اشتراطاتٌ معيّنةٌ فيها، إذ يجب أن تستطيع أن تقاوم الحِمل المُطبَّق عليها، ويجب أن تكون لها أبعاد وحجم معيّن وهلم جرًا… تلك الأمور تسمى «اختبارات الوحدات» (unit tests). أما الفكرة من اختبارات الاندماج هي معرفة متانة وقوة الارتباط بين اللّبنات، وكيف تُشكِّل مع بعضها عنصرًا من عناصر المبنى (مثل الجدران). أما الاختبارات الوظيفية فيمكن ربطها بالاختبارات التي تُجرى على جدارٍ من جدران المبنى، مثل اختبار جودة العزل الحراري، ومعرفة إن كان بالإمكان مرور أشعة الشمس من النوافذ الموجودة في هذا الجدار. وأخيرًا، تتضمن اختبارات القبول إجراء فحص لكامل المبنى: كفتح الباب، والدخول إلى الغرفة، وإغلاق الباب، وتشغيل الضوء، والصعود إلى الطابق الثاني والنظر إلى الحديقة عبر النافذة… تعرّف على Codeception التقسيم السابق مشروطٌ بعدِّة عوامل، ويصعب مقاومة النزعة إلى خلط عدِّة أنواع من الاختبارات. يستعمل الكثير من المبرمجين اختبارات الوحدات ويدّعون أنها كافية؛ وكنتُ منهم، إلا أنني وجدتُ أنَّ استخدام عدِّة أنظمة لمختلف أنواع الاختبارات هو أمرٌ مرهقٌ ويستهلك وقتًا طويلًا. لذا قررتُ منذ فترة أن أعثر على شيءٍ أكثر فائدةً من مكتبة PHPUnit؛ أردتُ أن يتطور استخدامي لاختبارات الشيفرات، إلا أنني لم أرغب بقراءة وتعلم الكثير من صفحات التوثيق وأن أبحث عن الحلول الالتفافية للمشاكل التي تواجهني. ومن ثم اكتشفت وجود Codeception. كنتُ بادئ الأمر متشككًا ومرتابًا منه، وهذه طبيعتنا عندما نرى شيئًا جديدًا (الحق يقال: هذا المشروع موجودٌ منذ خمس سنوات، لذا لا يمكننا اعتباره «جديدًا»)، لكن بعد تجربته لعدة أيام، خَلِصتُ إلى أنَّ Codeception هو نظامٌ مفيدٌ جدًا. قد تتساءل كيف تستطيع تثبيت Codeception؟ الأمر أبسط مما تتخيل: $ composer require "codeception/codeception" $ php vendor/bin/codecept bootstrap بعد التثبيت ستجد مجلدًا جديدًا باسم test في مشروعك، وستجد داخله مجلداتٍ فرعيةً باسم acceptance و functional و unit، يبدو أننا نستطيع البدء بكتابة اختباراتنا، إذًا ما هي الخطوة القادمة؟ لنحاول الآن إضافة اختبار قبول لمثالٍ بسيط: $ php vendor/bin/codecept generate:cept acceptance HelloWorld يمكننا الآن فتح ملف اختبار القبول ذي المسار tests/acceptance/HelloWorldCept.php ونضع فيه المحتوى الآتي: <?php $I = new AcceptanceTester($scenario); $I->wantTo('perform actions and see result'); المتغير الافتراضي المسمى ‎$I ليس مجرد حرف، وإنما هو ضمير متحدث يُشير إلى «الشخص» الذي يجري الاختبار: الذي يفتح صفحة تطبيقك أو أحد أصنافه ويفعل شيئًا ما ويريك النتيجة النهائية لأفعاله. إذ سترى ماذا فعل وما هو الخطأ الذي حدث. هذا هو سبب تسمية ذاك الكائن بالاسم ‎$I ولماذا تدعى الدوال الموجودة فيه wantTo()‎ و see()‎ و amOnPage()‎. لنفكر كشخصٍ يريد إجراء اختبارات للتحقق من عمل إحدى الصفحات. أوّل ما يخطر ببالنا هو فتح الصفحة والبحث عن عبارة معينة فيها، مما يعني أنَّ الصفحة متوافرة للزوار. يسهل كثيرًا فعل ذلك: <?php $I->amOnPage('/'); $I->see('Welcome'); ثم نستعمل الأمر الآتي لتشغيل اختبارات Codeception: $ php vendor/bin/codecept run سنلاحظ مباشرةً أنَّ شيئًا ما ليس صحيحًا، وسنرى من أوّل وهلة أنَّ رسالة الخطأ طويلة وغير واضحة، لكن عندما ننظر إليها عن قرب، فسنجد أنَّ معناها أصبح جليًا. لدينا اختبارٌ وحيدٌ وقد وَجَدَ خطأً: Acceptance Tests (1) Perform actions and see result (HelloWorldCept) Error ---------- 1) Failed to perform actions and see result in HelloWorldCept (tests/acceptance/HelloWorldCept .php) [GuzzleHttp\Exception\ConnectException] cURL error 6: Could not resolve host: localhost (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) خلاصة الرسالة السابقة هي أنَّ الموقع في localhost غير متوافر. انظر إلى أوّل سطر من ملف الاختبار: 1. $I->amOnPage("/") لنفتح ملف tests/acceptance.suite.yml ونعدّل قيمة url: http://localhost/‎ إلى رابطٍ آخر تتوافر عليه الصفحة. يتوافر على جهازي المحلي خادومٌ يحتوي على الصفحة التي نريد اختبارها url: https://local.codeception-article.com/‎. شغِّل الاختبار مرةً أخرى، ويجب أن تظهر عندك النتيجة الآتية: Acceptance Tests (1) --------------------------------------------------------------------------------------- Perform actions and result (HelloWorldCept) Ok تهانينا! لقد نجح اختبارنا. تتوافر العديد من الدوال للاختبار بجانب الدالة amOnPage()‎، لكنني عرضتُها لسهولة استعمالها. يمكن تقسيم دوال الاختبار الموجودة في Codeception إلى المجموعات الآتية: دوال التفاعل مع الصفحة: fillField()‎ و selectOption()‎ و submitForm()‎ و click()‎. دوال التحقق من وجود أشياء معيّنة في الصفحة: see()‎ و dontSee()‎ و seeElement()‎ و seeInCurrentUrl()‎ و seeCheckboxIsChecked()‎ و seeInField()‎ و seeLink()‎. يمكنك إضافة لاحقة (suffix) إلى الدوال السابقة عندما تحتاج استعمالها دون أن توقف الاختبار إن لم يُعثَر على العنصر الذي تختبر وجوده. دوال التعامل مع الكعكات (cookies): setCookie()‎ و grabCookie()‎ و seeCookie()‎. التعليقات وشرح حالة الاختبار: amGoingTo()‎ و wantTo()‎ و expect()‎، استخدم هذه الدوال لكي تشرح ما الذي يفعله الاختبار، مما يذكرك بالهدف منه. فلو أردنا مثلًا أن نختبر صفحةً لاستعادة كلمة المرور عبر البريد الإلكتروني، فسنكتب ملفًا شبيهًا بالملف الآتي: <?php $I = new AcceptanceTester($scenario); $I->wantTo('Test forgotten password functionality'); $I->amOnPage('/forgotten') $I->see('Enter email'); $I->fillField('email', 'incorrect@email.com'); $I->click('Continue'); $I->expect('Reset password link not sent for incorrect email'); $I->see('Email is incorrect, try again'); $I->amGoingTo('Fill correct email and get link'); $I->see('Enter email'); $I->fillField('email', 'correct@email.com'); $I->click('Continue'); $I->expect('Reset password link sent for correct email'); $I->see('Please check your email for next instructions'); يبدو أنَّ الملف السابق يكفي لاختبار الصفحة، لكن ماذا لو كانت هنالك بعض الأقسام في الصفحة التي تُحمَّل عبر Ajax؟ كيف نستطيع أن نختبر تلك الصفحة؟ أجيبك أنَّ Codeception يستعمل مكتبة PhpBrowser المبنية على Symfony BrowserKit و Guzzle. أي لا تقلق، فالأمر سهلٌ وبسيط، ولن تحتاج إلا إلى استعمال curl لحل هذه المشكلة. يمكنك أيضًا استخدام Selenium واختبار الصفحات عبر المتصفحات الحقيقية. وصحيحٌ أنَّ ذلك أبطأ، إلا أنك ستتمكن من اختبار سكربتات JavaScript أيضًا. لكن أولًا عليك تثبيت إضافة Selenium، وتعديل ملف acceptance.suite.yml وإعادة بناء الصنف AcceptanceTester، ويمكنك بعد ذلك استخدام الدوال wait()‎ و waitForElement()‎؛ وستتمكن أيضًا من الحفاظ على وقتك ومواردك باستخدام الدالتين saveSessionSnapshot()‎ و loadSessionSnapshot()‎ اللتين تسمحان لك بحفظ حالة الجلسة الراهنة، وإجراء اختبارات جديدة على جلسات محفوظة مسبقًا، وقد تستفيد من هذا في بعض الحالات مثل اختبار عملية الاستيثاق من المستخدم. الخلاصة هي أننا نستطيع اختبار العديد من الوظائف بسهولة وسرعة وكفاءة. الاختبار الوظيفي لننتقل الآن إلى إجراء اختبارات وظيفية على تطبيقنا: $ php vendor/bin/codecept generate:cept functional HelloWorld ومحتوى الملف: <?php $I = new FunctionalTester($scenario); $I->amOnPage('/'); $I->see('Welcome'); انتظر قليلًا، ماذا يحدث! لا يوجد خطأ في الشيفرة السابقة، حيث تُكتَب الاختبارات الوظيفية بنفس طريقة كتابة اختبارات التكامل، الفرق بينهما هو أنَّ الاختبارات الوظيفية تتفاعل مباشرةً مع تطبيقك، مما يعني أنَّك لن تحتاج إلى خادوم ويب لتشغيل اختبار وظيفي، وستستطيع اختبار أجزاء مختلفة من تطبيقك. وصحيحٌ أنَّه لا يتوافر دعمٌ لجميع إطارات العمل، لكن قائمة الإطارات المدعومة كبيرة (Symfony، و Silex، و Phalcon، و Yii، و Zend Framework، و Lumen، و Laravel) ويجب أن تكون كافيةً لأغلبية الحالات ولمعظم المطورين. رجاءً راجع توثيق Codeception للحصول على قائمة بجميع الدوال المتوافرة وكيفية تفعيلها في ملف functional.suite.yml. قبل أن ننتقل إلى اختبارات الوحدات، فدعني أنحرف قليلًا عن الموضوع وأحدثك قليلًا عن شيءٍ ربما لاحظتَه عند إنشاء الاختبارات: $ php vendor/bin/codecept generate: cept acceptance HelloWorld لاحظ الكلمة cept، واعلم أنَّ ما سبق ليس الطريقة الوحيدة لإنشاء الاختبارات، فهنالك أيضًا «اختبارات cest»، والفرق بينهما هو أنَّك تستطيع هيكلة عدِّة حالات وسيناروهات في نفس الصنف: $ php vendor/bin/codecept generate:cest acceptance HelloWorld <?php class HelloWorldCest { public function _before(AcceptanceTester $I) { $I->amOnPage('/forgotten') } public function _after(AcceptanceTester $I) { } // tests public function testEmailField(AcceptanceTester $I) { $I->see('Enter email'); } public function testIncorrectEmail(AcceptanceTester $I) { $I->fillField('email', 'incorrect@email.com'); $I->click('Continue'); $I->see('Email is incorrect, try again'); } public function testCorrectEmail(AcceptanceTester $I) { $I->fillField('email', 'correct@email.com'); $I->click('Continue'); $I->see('Please check your email for next instructions'); } } ستُشغَّل الدالتان ‎_before()‎ و ‎_after()‎ قبل وبعد كل اختبار. وستُمرَّر نسخةٌ من الصنف AcceptanceTester إلى كل اختبار، لذا يمكنك استخدامه بنفس الآلية المتبعة في اختبارات cest. قد يكون هذا النوع من الاختبارات مفيدًا في بعض الأحيان، لذا من المفيد تعلمه ومعرفة وجوده. اختبارات الوحدات أخيرًا: اختبارات الوحدات. بُني Codeception على PHPUnit، أي أنَّك تستطيع استخدام الاختبارات المكتوبة لمكتبة PHPUnit، وذلك باستخدام الأمر الآتي: $ php vendor/bin/codecept generate:phpunit unit HelloWorld أو يمكنك ببساطة أن «ترث» (inherit) الاختبارات في ‎\PHPUnit_Framework_TestCase. لكن إن أردتَ شيئًا إضافيًا، فيمكنك تجربة اختبارات الوحدات التي يوفرها Codeception: $ php vendor/bin/codecept generate:test unit HelloWorld <?php class HelloWorldTest extends \Codeception\TestCase\Test { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testUserSave() { $user = User::find(1); $user->setEmail('correct@email.com'); $user->save(); $user = User::find(1); $this->assertEquals('correct@email.com', $user->getEmail()); } } لا شيء يختلف عمّا عهدتَه من قبل، فالدالتان ‎_before()‎ و ‎_after()‎ هما بديلتا setUp()‎ و tearDown()‎، وستعملان قبل وبعد كل اختبار. الميزة الأساسية لهذا الاختبار هي القدرة على توسعة عملية الاختبار بتضمين وحدات (modules) التي يمكن تفعيلها في ملف unit.suite.yml: الوصول إلى memcache وقواعد البيانات لتتبع التغيرات (قواعد MySQL و SQLite و PostgreSQL و MongoDB مدعومة). اختبار تطبيقات REST/SOAP. Queues كل وحدة (module) لها ميزاتها، لذا من الأفضل أن تنظر إلى التوثيق وتجمع المعلومات الضرورية عنها قبل الانتقال إلى كتابة اختبارات تستعملها. يمكنك أيضًا استعمال حزمة Codeception/Specify (والتي يجب إضافتها إلى ملف composer.json) وتكتب وصفًا كالآتي: <?php class HelloWorldTest extends \Codeception\TestCase\Test { use \Codeception\Specify; private $user; protected function _before() { $this->user = User::find(1); } public function testUserEmailSave() { $this->specify("email can be stored", function() { $this->user->setEmail('correct@email.com'); $this->user->save(); $user = User::find(1); $this->assertEquals('correct@email.com', $user->getEmail()); }); } } شيفرة PHP الموجودة ضمن الدوال المغلقة (closure functions) السابقة معزولةٌ، لذا لن تؤثر التعديلات داخلها على بقية شيفراتك. ستساعد عبارات الشرح بجعل الغاية من الاختبار واضحةً وتسهيل التعرف على الاختبارات التي فشلت. إضافةً إلى ما سبق، يمكنك استعمال الحزمة Codeception\Verify لكتابة اختبارات بشيفرة شبيهة بأسلوب BDD: <?php public function testUserEmailSave() { verify($map->getEmail())->equals('correct@email.com'); } ويمكنك بالطبع استعمال stub: <?php public function testUserEmailSave() { $user = Stub::make('User', ['getEmail' => 'correct@email.com']); $this->assertEquals('correct@email.com', $user->getEmail()); } الخلاصة: يساعد Codeception على توفير الوقت والجهد ما الذي تتوقع الحصول عليه من Codeception؟ من هم الأشخاص المناسبون لاستعماله؟ برأيي الشخصي، Codeception مناسب لجميع أنواع الفرق: الكبيرة والصغيرة، والمبتدئين والمحترفين، والذين يستعملون أطر عمل والذين يكتبون برمجياتهم من الصفر، أي أنَّ Codeception صالحٌ لكل زمان ومكان (تقريبًا). Codeception هو إطار عمل ناضج وموثّق جيدًا ويمكن توسعته باستعمال إضافات. وهو حديثٌ، ومبنيٌ على المكتبة الشهيرة PHPUnit، مما يطمئن المطورين الذين لا يريدون تجربة الكثير من المكتبات قبل الاستقرار. أداءُ هذه المكتبة جيدٌ، وهذا يعني أنها سريعة ولا تحتاج وقتًا ولا جهدًا كثيرًا، ومن السهل تعلمها، والتوثيق الرائع يجعل عملية التعلم تمر دون مشاكل. من السهل جدًا تثبيت وضبط Codeception، مع أنه يحتوي على الكثير من الميزات المتقدمة. وصحيحٌ أنَّ أغلبية المستخدمين لا يحتاجون جميع (أو حتى أغلب) الميزات الموجودة فيه، إلا أنَّ ذلك يعتمد على ما تشاء فعله معه. يمكنك البدء بالأساسيات، ثم ستبدأ بالتعامل مع الميزات الإضافية عاجلًا أو آجلًا. ترجمة -وبتصرّف- للمقال Jumpstart Your PHP Testing with Codeception لصاحبه Vasily Koval
  4. تُسهِّل برمجية ووردبريس إنشاءَ صفحاتٍ ومنشوراتٍ محميةٍ بكلمة مرور، وهذه ميزةٌ رائعة إذا أردتَ تقييد الوصول إلى محتوى معيّن، لكن المشكلة الوحيدة هي أنَّ أيّة صور أومقاطع فيديو تَعرِضُها في تلك الصفحات ستبقى متاحةً للوصول من صفحات الوسائط المرفقة. يمكن أن يتحول الأمر إلى إشكالية رئيسية إذا كانت تحتوي ملفات الوسائط على معلومات مهمة التي تريد أن تبقى محميةً بكلمة مرور. أحدحلول هذه المشكلة هو إلغاء دعم صفحات الوسائط (attachment pages) في ووردبريس كليًّا، مما يعني أنَّ على زوار موقعك الوصول إلى المنشور (أوالصفحة) المحمي بكلمة مرور لرؤية المحتوى. سننظر في هذا الدرس إلى طريقتين لحل هذه المشكلة.فأولًا سنحلّ المشكلة بإنشاء «قالب ابن» (child theme) وكتابة بعض الشيفرات؛ ثم سنشرح كيف نستخدم إضافةً مجانيةً لفعل ذلك تلقائيًا. متى نخفي صفحات الوسائط؟ لنقل أنك كتبتَ درسًا تعليميًا رائعًا، وكان معروضًا في صفحةٍ وحيدةٍ، وبلغ طوله بضعة آلاف من الكلمات، إضافةً إلى مقطع فيديو أو أكثر، وعدد لا بأس به من الصور التوضيحية. سنفترض أنَّك لا تريد إنشاء متجر لبيع الدورات التعليمية وإنما توفِّر في بعض الأحيان محتوى مدفوع، لذا لن تكون مهتمًا بتثبيت نظام إدارة دورات تعليمية كامل مثلCoursePressPro. حيث اخترتَ حماية الصفحة بكلمة مرور التي ستُرسِلها بطريقةٍ مؤتمتة إلى البريد الإلكتروني للشخص الذي سيدفع لقاء الوصول إلى الدرس. سأفترض أنَّك وجدتَ حلًّا بسيطًا الذي يساعدك في ما سبق دون أن يتطلب جهدًا أو وقتًا طويلًا منك. لكن بعد عدِّة أسابيع أدركتَ وجود مشكلة، حيث شارك أحدهم صورةً من مقالتك على وسائل التواصل الاجتماعي مستعملًا رابط URL لصفحة الوسائط المرفقة، وتلك الصفحة يمكن الوصول إليها مباشرةً دون إدخال كلمة مرور. وما يزيد الطين بلّةً أنَّ القالب الذي تستعمله يُضيف روابط (للصورالتالية والسابقة) إلى صفحة المرفقات مما يسمح للزوار بالتنقل بسهولة بين جميع الصور التي أرفقتها بالمنشور المخفي! حسنًا، دعني أخبرك أنَّ هذه المشكلة سهلة الحل جدًا… إخفاء صفحات المرفقات باستخدام الشيفرات إذا توفر لديك عميل FTP ومحرر نصي، فيمكنك حلّ هذه المشكلة بسرعة عبر إنشاء «قالب ابن». كل ما عليك فعله هو إضافة سطرٌ واحدٌ إلى ملف image.php ويمكنك–اختياريًّا– إضافته إلى ملف video.php. إذا ألقيتَ نظرةً إلى البنية الهيكلية لقوالب ووردبريس فستلاحظ أنَّ أكثر ملف تخصيصًا يمكن إنشاؤه لصفحة المرفقات هو ملفٌ باسم MIME_type.php وهذا يعني أنَّه لو كانت لديك صورٌ ومقاطع فيديو في صفحةٍ محميةٍ بكلمة مرور، فستحتاج إلى إخفاء صفحة المرفقات لكلا النوعين، وستحتاج إلى تعديل ملفٍ مختلفٍ لكل نوع من الوسائط. لنبدأ بملف image.php،لكن قبل أن تبدأ بتعديل أيّة ملفات، فتأكد أنَّك تستعمل «قالب ابن». بعد إنشاء وتفعيل القالب الابن، أنشِئ ملفًا باسم image.php على حاسوبك، وافتحه باستخدام المحرر النصي المفضَّل لديك، وأضف إليه السطر البرمجي الآتي: <?php wp_redirect(get_permalink($post->post_parent)) ; ?> احفظ الملف وأغلقه، ثم ارفعه إلى مجلد الجذر للقالب الابن الذي أنشأتَه منذ قليل. عندما يحاول أحدهم الوصول إلى صفحة المرفقات للصور، فسيتم تحميل ملف image.php وسيؤدي إلى إعادة توجيه المتصفح إلى المنشور المرتبط بتلك الصورة، وهذا هو السلوك الذي نرغب به؛ فأيُّ شخصٍ يحاول الوصول إلى صفحة المرفقات الخاصة بالصور التابعة للمنشور المحمي بكلمة المرور سيُعاد توجيهه إلى الصفحة المحمية بكملة المرور، وعليه عندئذٍ إدخال كلمة المرور للوصول إلى تلك الصفحة. لإضافة حماية إلى صفحات المرفقات الخاصة بمقاطع الفيديو، أنشِئ ملفًا باسم video.php وأضف إليه نفس السطر السابق. هذاحلٌ بسيطٌ جدًا لإخفاء صفحات المرفقات باستخدام الشيفرات؛ لكن إذا كنتَ تُفضِّل إتمام الأمر دون استعمال محرر نصي وعميلFTP، فأود أن أبشِّرَك بوجود حلٍ آخر… إخفاء صفحات المرفقات باستخدام إضافة إذا أردتَ تفادي إنشاء قالب ابن وكتابة (أونسخ ولصق) السطر البرمجي السابق، فيمكنك استخدام إضافة لإعادة توجيه كل صفحات المرفقات. إضافة AttachmentPages Redirect هي إضافةٌ مجانيةٌ متاحةٌ على WordPress.org التي تؤدي الغرض الواضح من اسمها: إعادة توجيه جميع صفحات المرفقات إلى صفحة المنشور الأصلي الموجودةُ فيه (إعادة توجيه دائمة 301)،أو إلى الصفحة الرئيسية (إعادة توجيه مؤقتة 302) إذا لم تكن تلك الوسائط مرفقةً إلى أحد المنشورات. هنالك أكثر من 10000 موقع يستعمل هذه الإضافة، ولها تقييم 4.7 من 5 نجوم. للبدء باستخدام إضافة Attachment Pages Redirect اذهب إلى«Plugins > Add New» (الإضافات> أضف جديد) في لوحة تحكم ووردبريس وابحث عن العبارة «Attachment Pages Redirect»، ثم اضغط Install (تثبيت) ثم Activate (تفعيل). لا تحتاج إلى ضبط أيّ شيءٍ آخر. افتح متصفحك وحاول عرض صفحة أحد المرفقات،وستجد أنَّه قد تمت إعادة توجيهك إلى صفحة المنشور الأصلي أو إلى الصفحة الرئيسية للموقع. لا تظن أنَّ هنالك حماية كاملة لمرفقاتك لا تضمن الطريقتان اللتان شرحناهما في هذا الدرس أنَّ الزوار يجب أن يستعملوا كلمة مرور للوصول إلى الصور أو مقاطع الفيديو التي تُضيفها إلى منشوراتك المحمية؛ لكن دعني أوضِّح لك أنَّه لا توجد أيّة طريقة لحماية المرفقات حمايةً تامةً. فمثلًا، يستطيع المستخدمون الذين يملكون وصولًا إلى المرفقات (عبر إدخالهم لكلمة مرور صحيحة) أن ينسخوا محتواك بكل سهولة عبر: تفحص شيفرة الصفحة المصدرية والعثور على روابط URL مباشرة لملفات الوسائط أو عبر استخدام برمجيات لأخذ لقطات شاشة للصور أو تسجيل محتوى الفيديو عند تشغيله. هنالك خطواتٌ إضافية يمكنها التصدي لهذه المحاولات مثل إعادة التوجيه من طرف الخادوم (sever-level redirects) مما يمنع الوصول المباشر إلى ملفات الوسائط،وعبر استخدام JavaScript لمنع نسخ محتواك. لكن إذا كان المستخدم عاكفًا على نسخ مقالتك والوصول إلى ملفات الوسائط، فسيجد طريقةً للالتفاف على وسائل الوقاية التي استعملتها. في النهاية، أريد أن أُذكِّرك أنَّه لا توجد طريقةٌ لحماية ملفات الوسائط، ففي اللحظة التي تسمح بها لأيّ شخص بالوصول إليها،فلن تستطيع الاحتفاظ بالقدرة على التحكم بالمحتوى، لذا أبقِ هذا في بالك في كل مرة تُضيف فيها المحتوى في موقعك، حتى لو كنتَ «تحمي»المنشورات عبر كلمة مرور (يجب أن تكون مدركًا بعد قراءتك لما سبق أنَّه لا توجد حماية مطلقة للمحتوى). إذا لم تكن تريد رؤية الصور أو مقاطع الفيديو التي ترفعها إلى موقعك مسروقةً ومنشورةً في أحد المنتديات، فلا ترفعها إلى موقعك من الأساس. الخلاصة الميزة المُضمَّنة في ووردبريس لحماية المنشورات بكلمة مرور تُسهِّل من إنشاء محتوى خاص ومحمي، لكنها لا تحمي المرفقات والوسائط المُضافة إلى الصفحات والمنشورات التي لا تستطيع الوصول إليها إلا بكلمة مرور. الحل بسيطٌ جدًا. إذ يمكنك إعادة توجيه صفحات المرفقات إلى المنشور الأصلي الذي أُرفِقَت الوسائط به عبر سطرٍ برمجيٍ وحيد، وإذا لم تكن تريد أن تُضيع وقتك بكتابة ذاك السطر، فإضافة Attachment Pages Redirect ستساعدك في ذلك. ترجمة-وبتصرّف-للمقال [Howto Hide Media Attachment Pages in WordPress] لصاحبهJon Penland
  5. Docker هي أداةٌ لإنشاء الحاويات تُستخدَم لتوفير برمجيات مع نظام ملفات الذي يحتوي كل شيءٍ تحتاج له تلك البرمجيات لتعمل. استخدام حاويات Docker يضمن أنَّ البرمجيات ستعمل عملًا صحيحًا بغض النظر عن النظام الذي ستعمل داخله، لأنَّ بيئة التشغيل ستكون متماثلة في جميع الحالات. سنوفِّر في هذا الدرس لمحةً عن العلاقة بين صور Docker (أي Docker images) وحاويات Docker (أي Docker containers). ثم سنتعلم كيفية تشغيل وإيقاف وحذف الحاويات. لمحة عن حاويات Docker يمكننا أن نتخيل «صورة Docker» على أنها قالبٌ (template) يُستخدم لإنشاء حاويات Docker. تبدأ الصور عادةً بنظام ملفات رئيسي، وتُضاف التعديلات على نظام الملفات (والمعاملات المترافقة معها) على شكل طبقات متتالية. وعلى النقيض من توزيعات لينكس الاعتيادية، تحتوي صورة Docker على المكوِّنات الأساسية اللازمة لتشغيل التطبيق (ولا تحتوي على جميع الأدوات الموجودة في التوزيعات التقليدية). ولا تتغير صور Docker، وإنما تكوِّن نقطة انطلاق لإنشاء حاويات Docker. هذا المزيج بين الطبقات المتاحة للقراءة فقط والتي تعلوها طبقةٌ قابلةٌ للكتابة والقراءة يُشكِّل ما يُعرَف بنظام الملفات الاتحادي (union file system). فعند حدوث تغيير إلى ملفٍ موجودٍ في حاوية فسيُنسَخ الملف من الفضاء المتاح للقراءة فقط إلى الطبقة التي يُسمَح فيها بالقراءة والكتابة، ثم ستُطبَّق التغييرات، والنسخة الموجودة في الطبقة المتاحة للقراءة والكتابة تؤدي إلى «إخفاء» الملف الأصلي لكنها لا تحذفه. التغييرات التي تحدث في الطبقة المتاحة للقراءة والكتابة تتواجد في الحاويات المستقلة فقط، وعندما تُحذَف إحدى الحاويات فستضيع جميع التغييرات إلا إن اتخذنا إجراءات لحفظها. التعامل مع الحاويات في كل مرة تستعمل فيها الأمر docker run سيؤدي إلى إنشاء حاوية جديدة من الصورة التي حدَّدتَها. ما سبق قد يُسبِّب بعض الالتباس، لذا لنأخذ بعض الأمثلة للتوضيح. الخطوة الأولى: إنشاء حاويتَين الأمر docker run الآتي سيُنشِئ حاويةً جديدةً تستعمل الصورة ubuntu، ويمنحنا الخيار ‎-t وصولًا إلى الطرفية، والخيار ‎-i يسمح لنا بالتفاعل معها. سنعتمد على الأمر الافتراضي الموجود في ملف Docker لتوزيعة أوبنتو (وهو bash) لكي نحصل على وصولٍ للصدفة (shell): $ docker run -ti ubuntu سيتغير مِحَث سطر الأوامر (prompt) ليُشير إلى أننا نعمل داخل الحاوية بحساب الجذر، متبوعًا بمُعرِّفٍ ذي 12 محرفًا للحاوية، ويبدو المِحث كالآتي: root@11cc47339ee1:/# سنُجري تعديلًا على الحاوية عبر كتابة جملة نصيّة إلى ملفٍ داخل مجلد ‎/tmp، ثم سنستخدم الأمر cat للتأكد من أنَّ الملف قد تعدّل: echo "Example1" > /tmp/Example1.txt cat /tmp/Example1.txt الناتج: Example1 لنخرج الآن من الحاوية بالأمر exit. سيوقف تشغيل حاويات Docker عند انتهاء تنفيذ الأمر الذي بدأت به، لذا ستتوقف حاويتنا عن العمل عند خروجنا من الصدفة bash. ولو نفَّذنا الأمر docker ps الذي يعرض الحاويات التي تعمل حاليًا، فلن نشاهد حاويتنا مذكورةً بينها: docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES أما إذا استعملنا الخيار ‎-a الذي يعرض جميع الحاويات، سواءً كانت متوقفةً أم تعمل حاليًا، فستظهر عندئذٍ حاويتنا في القائمة: docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 11cc47339ee1 ubuntu "/bin/bash" 6 minutes ago Exited (127) 8 seconds ago small_sinoussi عندما أُنشِئَت الحاوية، أُعطيَتْ مُعرَّفًا واسمٍ مولِّدٍ تلقائيًا. وكان مُعرِّف الحاوية في حالتنا هو 11cc47339ee1 والاسم المولَّد تلقائيًا هو small_sinoussi. الخيار ps -a يُظهِر هذه القيمة بالإضافة إلى اسم الصورة التي بُنيَت الحاوية عليها (ubuntu)، ومتى أُنشِئَت الحاوية (‎6 minutes ago)، وما الأمر الذي بدأت الحاوية به (‎/bin/bash). والناتج يعرض أيضًا حالة الحاوية (Exited) ومتى دخلت الحاوية بهذه الحالة (‎8 seconds ago). إذا ما زالت الحاوية تعمل عند تنفيذ الأمر، فستظهر الحالة Up مع بيان مدّة تشغيلها. إذا أعدنا تنفيذ الأمر السابق، فستُنشَأ حاويةٌ جديدة: docker run -ti ubuntu سنعرف أنَّ هذه حاويةٌ جديدة لأنَّ مُعرِّف الحاوية الظاهر في المِحَث مختلف، وإذا حاولنا عرض الملف Example1 فلن نجده: root@6e4341887b69:/# cat /tmp/Example1 الناتج: cat: /tmp/Example1: No such file or directory قد تظن أنَّ الناتج السابق يعني أنَّ البيانات قد اختفت، لكن ذلك ليس صحيحًا. سنخرج الآن من الحاوية الثانية لنتأكد من أنَّ كلا الحاويتين ما زالتا موجودتين في نظام الملفات، ولم تُحذَف الحاوية الأولى التي أنشأنا فيها الملف. docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6e4341887b69 ubuntu "/bin/bash" About a minute ago Exited (1) 6 seconds ago kickass_borg 11cc47339ee1 ubuntu "/bin/bash" 13 minutes ago Exited (127) 6 minutes ago small_sinoussi الخطوة الثانية: إعادة تشغيل الحاوية الأولى لإعادة تشغيل حاوية موجودة مسبقًا، فسنستخدم الأمر start مع الخيار ‎-a و ‎-i، متبوعًا باسم الحاوية أو مُعرِّفها. احرص على أن تضع مُعرِّف الحاوية التي أنشأتها في نظامك في الأمر الآتي: docker start -ai 11cc47339ee1 سنجد أنفسنا داخل صدفة bash مرةً أخرى، وعندما نستخدم الأمر cat على الملف الذي أنشأناه مسبقًا فسنرى أنَّه ما يزال موجودًا: root@11cc47339ee1:/# cat /tmp/Example1.txt Example1 يمكننا الآن أن نخرج من الحاوية بالأمر exit. الناتج السابق يُظهِر أنَّ التعديلات التي أُجرِيَت داخل الحاوية ستبقى حتى لو أوقفناها ثم أعدنا تشغيلها مرةً أخرى. ولن يُحذَف محتواها إلا عند إزالة الحاوية. وهذا المثال يبيّن أنَّ التعديلات ستبقى محصورةً في الحاوية، ولن تؤثر على ما سواها، وعند بدء حاوية أخرى فستأخذ بنية نظام الملفات من الصورة الأصلية. الخطوة الثالثة: حذف كلا الحاويتين لقد أنشأنا في هذا الدرس حاويتين، وسنختمه بتوضيح كيف نحذفهما. يسمح لنا الأمر docker rm –الذي يعمل على الحاويات المتوقفة فقط– بحذف حاوية أو أكثر عبر تحديد اسمها أو مُعرِّفها كما يلي: docker rm 11cc47339ee1 kickass_borg 11cc47339ee1 kickass_borg لقد حُذِفَت الحاويتان مع جميع محتوياتها. الخلاصة لقد ألقينا نظرةً مفصّلةً على الأمر docker run ورأينا كيف يُنشِئ حاويةً جديدةً في كل مرّة يُنفَّذ فيها، ورأينا أيضًا كيفية الحصول على معلومات عن حاوية متوقفة وكيفية تشغيلها والوصول إليها. ترجمة -وبتصرّف- للمقال Working with Docker Containers لصاحبته Melissa Anderson
  6. تمهيد من المهم أن تراقب دومًا أداء الخادوم، ولمساعدتك في ذلك يوفِّر DigitalOcean مخططات Droplet Graphs التي تعرض أداء خادومك بشكلٍ بصري. سننظر في هذا الدرس إلى كيفية الاستفادة من المخططات المتوافرة افتراضيًا، إضافةً إلى المخططات التي سنحصل عليها بعد تثبيت عميل ‏DigitalOcean، الذي هو أداةٌ صغيرةٌ تجمع معلوماتٍ عن الذاكرة واستهلاك القرص وعن أكثر العمليات استعمالًا للمعالج وللذاكرة في النظام. المخططات الافتراضية لعرض مخططات أحد الخواديم، فاضغط على اسمه في صفحة Droplet: عند الضغط على اسم الخادوم، فستنتقل مباشرةً إلى صفحة Graphs. وإذا خرجتَ من صفحة Graphs فيمكنك العودة إليها بالضغط على رابط Graphs في شريط التنقل الظاهر على اليسار: يمكن عرض إحصائيات Droplet Graphs لآخر 6 ساعات، أو 24 ساعة، أو 7 أيام، أو 30 يوم. وتتوافر ثلاثة مخططات لأي خادوم: مخطط «Bandwidth public» (التراسل الشبكي الخارجي) يُظهِر استهلاك التراسل الشبكي للاتصالات الخارجية بواحدة ميغابت في الثانية. يظهر التراسل الشبكي للاتصالات الواردة باللون البنفسجي الداكن، أما التراسل الشبكي للاتصالات الصادرة فسيظهر باللون البنفسجي الفاتح. مخطط «CPU» (وحدة المعالجة المركزية) يُظهِر نسبةً مئويةً لمقدار استهلاك طاقة المعالجة، وتظهر عمليات المستخدم باللون الأزرق الفاتح، بينما تظهر عمليات النظام باللون الأزرق الداكن. مخطط «Disk I/O» (الكتابة والقراءة على القرص) يُظهِر عمليات القراءة والكتابة بواحدة ميغابايت في الثانية، وتُمثَّل عمليات القراءة باللون الأخضر الداكن، أما عمليات الكتابة فتظهر باللون الأخضر الفاتح. أما إذا فعّلتَ الشبكات الخاصة، فستستطيع الوصول إلى مخططٍ رابع لتتبع التراسل الشبكي الخاص. وكما في مخطط «Bandwidth public»، لن يظهر هذا المخطط إلا إذا كان هنالك تراسل شبكي فعلي، وعندئذٍ سيُعرَض مخططٌ باسم «Bandwidth private» التي يُظهِر استهلاك التراسل الشبكي الخاص بواحدة ميغابت في الثانية؛ يظهر التراسل الشبكي للاتصالات الواردة باللون البنفسجي الداكن، أما التراسل الشبكي للاتصالات الصادرة فسيظهر باللون البنفسجي الفاتح. عندما تضع الفأرة فوق أحد المخططات، فسيُعرَض خطٌ عموديٌ في جميع المخططات إضافةً إلى مربع صغير يحتوي على القيم المرتبطة بهذه اللحظة الزمنية. يمكنك أن تلحظ في الصورة الآتية أنَّ المربع سيعرض حالة النظام في الساعة 12:12 مساءً في تاريخ November 21, 2016: المخططات الإضافية التي تظهر بعد تثبيت العميل المخططات الافتراضية تُقاس وتُحسَب باستخدام أدوات خارجية، ولا تتطلب أي شيءٍ خاص في الخادوم نفسه. أما الإحصائيات الأخرى (كاستهلاك الذاكرة والقرص) تتطلب جمعًا للبيانات من داخل الخادوم. يوفِّر عميل DigitalOcean (الذي هو أداةٌ صغيرةٌ تعمل على الخادوم) المعلومات الإضافية حول استهلاك الذاكرة والقرص. يمكنك تعلم المزيد من المعلومات حول عميل DigitalOcean وكيفية تثبيته في درس «كيفية تثبيت واستخدام عميل DigitalOcean لإظهار المزيد من المخططات للخادوم». يمكن تثبيت العميل تلقائيًا أثناء إنشاء الخادوم عبر تفعيل الحقل «Monitoring» كما هو موضَّح في هذه الصورة: يمكن أيضًا تثبيت العميل يدويًا باتباع التعليمات الواردة في الصفحة الموجودُ رابطها أعلى المخططات: بعد تفعيل العميل، فستملك وصولًا إلى مخططاتٍ إضافيةٍ التي تتضمن: مخطط «Memory» الذي يُظهِر نسبة استهلاك الذاكرة الفيزيائية RAM. مخطط «Disk Usage» الذي يُظهِر نسبة المساحة المستخدمة في القرص الصلب المرتبط بالخادوم. مخطط «Top processes» يُظهِر أكثر العمليات التي تعمل على الخادوم استهلاكًا للموارد، والمرتّبة عبر استهلاك المعالج أو الذاكرة. وهذا المخطط لا يُظهِر أيّة بيانات من الماضي، وإنما سيُظهِر البيانات اللحظية فقط. المرور بالفأرة على أيّ مخطط سيؤدي إلى إظهار مربع يحتوي على تفاصيل استهلاك الموارد في اللحظة الحالية: أخيرًا، إذا كنتَ قد فعّلتَ الشبكة الخاصة واستخدمتها، فستُعرَض سبعة مخططات في الصفحة السابقة: الخلاصة رأينا ما هي المخططات المتوافرة لخواديم DigitalOcean. إذا دلّت تلك المخططات على مشاكل في الأداء، فيتوجّب عليك تتبّع الخلل ومُحاولة إصلاحه. ألق نظرة على دروسنا السّابقة لتجد فيها أمثلة عن تحسين أداء مُختلف جوانب الخادوم. ترجمة -وبتصرّف- للمقال How To Track Droplet Performance with DigitalOcean Droplet Graphs لصاحبته Melissa Anderson
  7. أداة البناء Grunt أو Gulp، مكتبة require.js، ‏browserify، الإصدار السادس من ES، المفسرات، أطر عمل React و Angular و Amber، التعابير المغلقة (closures)، سلسلة prototype. ارتفاع في ضغط الدم يؤدي إلى سكتة دماغية. حسنًا، تطوير الويب أمرٌ ممتع جدًا، لكن JavaScript مروعة! تجد نفسك منسجمًا تمامًا مع جميع جوانب تطوير الويب، لكن عندما يأتي الأمر إلى JavaScript فستشعر أنَّ جزءًا كبيرًا من المعلومات الأساسية ينقصك بينما يعرفه الآخرون، والذي سيؤدي إلى جعلهم يفهمون سكربتات JavaScript. نعم، الحقيقة هي أنَّك تفتقد بالفعل إلى بعض القطع؛ لكن هذا لا يعني أنَّ التوجه الحالي لتطوير الواجهات الأمامية ليس مجنونًا! اطمئن، فأنت لستَ بمفردك، لذا اسحب كرسيًا واجلس، وجهِّز نفسك لكتابة تطبيق JavaScript. أوّل خطوة هي ضبط بيئة التطوير المحلية، لذا اتخذ قرارك: هل ستستخدم Gulp، أم Grunt، لا! سأستعمل سكربتات NPM. هل أستعمل Webpack أم Browserify أم Require.js؟ هل أتخذ قرارًا مصيريًا بالانتقال إلى الإصدار السادس من ES؟ أليس ضروريًا أن أضع مرجعًا عن أمراض القلب بجواري؟ كيف سأنظِّم اختبار الشيفرات؟ هل من إطارِ عملٍ تنصحني به؟ أليس من الأفضل تشغيل الاختبارات من سطر الأوامر، لنستعمل إذًا PhantomJS؟ مع أي إطارٍ أذهب: Angular أم React؟ ربما Ember؟ ماذا عن Backbone؟ ربما قرأتَ بعض صفحات توثيق React ووجدتَ فيها أنَّ «Redux هو حاويةٌ ذاتُ حالةٍ قابلةٍ للتوقع لتطبيقات JavaScript» وبدت على وجهك أمارات الرضى، فمن المؤكد أنَّك ستحتاج إلى هذه الميزة العظيمة، بغض النظر عن أنَّك لم تفهم حرفًا من شرحها. السؤال الآن هو: لماذا أصبح تطوير تطبيقات JavaScript أمرًا يدفع إلى الجنون؟! دعني أساعدك لفهم سبب ذلك. لنبدأ بمثالٍ بسيطٍ ثم سنستعرض صورًا جميلةً توضِّح وجهة نظري. هذا تطبيق «Hello, World!‎» مكتوبٌ باستخدام React: // main.js var React = require('react'); var ReactDOM = require('react-dom'); ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') ); لم ننتهِ منه بعد: $ npm install --save react react-dom babelify babel-preset-react $ browserify -t [ babelify --presets [ react ] ] main.js -o bundle.js هنالك عدِّة خطوات ناقصة هنا، مثل تثبيت مكتبة browserify أو ما الذي عليك فعله لتشغيل الصفحة في المتصفح، إذ لا يبدو أنَّ ما سبق سيُنشِئ صفحة ويب قادرة على فعل أيّ شيء! بعد أن تنتهي من إنجاز ما سبق، فستجد ملفًا يدعى bundle.js يحتوي على تطبيق «Hello, World!‎» السابق المكتوب بمكتبة React والذي يضم حوالي 19374 سطرًا برمجيًا، وكل ما فعلتَه هو تثبيت browserify و babelify و react-dom، التي «تزن» آلاف الأسطر البرمجية. هذه صورة تعبيرية عن برنامج «Hello, World!‎» في React: حسنًا، هذا تطبيق «Hello, World!‎» باستخدام JavaScript دون مكتبات: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>Hello World</title> </head> <body> <div id="container"></div> <script> document.body.onload = function(){ var container = document.getElementById("container"); container.innerHTML = '<h1>"Hello, world!"</h1>'; } </script> </body> </html> هذا كل ما في الأمر! 18 سطر برمجي (يمكن اختصارها إلى أقل من ذلك)، التي تستطيع نسخها ولصقها في ملفٍ باسم index.html وتنقر نقرتين عليه مما يفتحه في متصفحك. يا للبساطة! إذا كنتَ تفكِّر في هذه اللحظة «أليس إطار React يفعل أكثر من ذاك المثال البسيط الذي كتبته، والذي لا يرقى أن يكون تطبيق JavaScript» فأنت مصيبٌ (تقريبًا)، وعلى بعد خطوة واحدة من فهمك لماذا كل هذا التعقيد. انظر إلى هذه الصورة: أغلبية تطبيقات JavaScript التي ستعمل عليها ستقع في مكانٍ ما في منتصف المنحني الجرسي (bell curve) السابق. وإذا كنتَ في منتصف المنحني السابق وبدأت تطبيقًا بالاعتماد على React فسوف ينتهي بك المطاف بهندسة تطبيقك زيادةً عن اللزوم من بدايته. وهذا هو سبب تعقيد تطوير تطبيقات JavaScript، لأنَّ غرض أغلبية الأدوات التي تظن أنَّك بحاجةٍ إليها هو حلّ المشاكل التي لن تتعرض إليها بتاتًا. أصبحت حالة تطوير تطبيقات JavaScript في الآونة الأخيرة معقدةً ومربكةً لأنَّ الجميع يبالغون في هندسة تطبيقاتهم دون أن يدركوا ذلك. إذًا، كيف يجب أن نبدأ بتطوير تطبيق JavaScript؟ هل علينا استخدام مكتبة شبيهة بمكتبة React أو Angular؟ هل يجب أن نستخدم مدير للحزم؟ ماذا يفترض علينا أن نفعل إذا لم نستخدمهما؟ هل كتابة الاختبارات ضرورية؟ هل علينا أصلًا توليد شيفرات HTML عبر JavaScript؟ هذه هي الأسئلة التي يجب أن تسألها لنفسك قبل أن تبدأ بمجموعة ضخمة من أدوات التطوير. عندما تبدأ بتطوير تطبيق JavaScript فمن المهم أن تختار نقطةً في المنحني الجرسي في المكان الذي تظن أنَّ من المرجح أن يصله تطبيقك في المستقبل من ناحية التعقيد. لن أكذب عليك، فعل ذلك ليس سهلًا ويحتاج خبرةً، لكن هنالك منطقةٌ كبيرة يمكنك أن تبدأ منها أغلبية تطبيقات JavaScript: استعمل مكتبة jQuery مع قوالب لصفحات الواجهة الأمامية مع أداة بناء بسيطة لجمع الملفات وتصغيرها (بفرض أنَّ إطار العمل الذي تستعمله لتطوير السند الخلفي [backend] لا يفعل ذلك تلقائيًا). إذا أردتَ أن تتعلم كيفية هيكلة تطبيق JavaScript بطريقةٍ صحيحة، فعليك أن تبدأ بفهم كيف ومتى ولماذا تستخدم إطار عمل أو حزمة npm أو إصدار ES6 أو متى تكتب اختبارات أو هل عليك جعل الاختبارات تعمل محليًا أو في متصفح، ثم سيأتي دور بقية الأسئلة وحلّ بقية المشاكل. إن أردتَ أن تملأ الفجوات الموجودة في معلوماتك حول تطوير JavaScript وأن تتجنّب الشعور بأنّك تبالغ في تصميم تطبيق JavaScript فحاول أن تتابع ما نطرحه هنا في قسم البرمجة في أكاديمية حسوب. ترجمة -وبتصرّف- للمقال Why JavaScript Development is Crazy لصاحبه Sean Fioritto
  8. تمهيد تَتَبَّعُ مخططات الخادوم (Droplet Graphs) استخدامَ موارد خادومك مع مرور الزمن. يمكن قياس بعض الأمور مثل التراسل الشبكي والقراءة والكتابة إلى القرص من أدواتٍ خارجية. لكن للحصول على معلوماتٍ إضافية فيجب تثبيت عميل DigitalOcean على الخادوم لتوفير إمكانية قياس استخدام الذاكرة والقرص الصلب والحصول على معلومات عن أكثر البرمجيات استهلاكًا للمعالج أو الذاكرة في النظام. سنتعرّف في هذا الدرس على عميل DigitalOcean وكيف يعمل، وسنشرح كيفية تثبيته للحصول على المعلومات السابقة. وسنريك أيضًا كيفية حذف العميل تمامًا في حال لم تعد تريد تلك الإحصائيات والبيانات. ما هو عميل DigitalOcean؟ عميل DigitalOcean هو أداةٌ مفتوحة المصدر مكتوبةٌ بلغة Go التي توفِّر قياسات إضافية لكي تُعرَض كمخططات تُظهِر أداء خادومك. دون تثبيت العميل، ستُعرَض لك عبر Droplet Graphs المعلومات الآتية: استهلاك التراسل الشبكي الداخلي والخارجي نشاط المعالج القراءة والكتابة إلى القرص أما مع تثبيت العميل، فسيُعرَض لك –إضافةً إلى ما سبق–: استهلاك الذاكرة استهلاك القرص الصلب أكثر العمليات استهلاكًا للمعالج وللذاكرة. يمكن تشغيل العميل على توزيعة أوبنتو 14.04 أو ما بعدها، وتوزيعة CentOS 6 وما بعدها، ودبيان 8. زر صفحة مستودع عميل DigitalOcean على GitHub لتنظر إلى شيفرته. ما هي المجلدات التي يستطيع العميل الوصول إليها تعمل خدمة العميل كمستخدمٍ دون امتيازات الذي يملك وصولًا إلى المجلدات الثلاثة الآتية: ‎/proc: المكان الذي يجمع فيه العميل معلوماتٍ عن حالة النظام ‎/var/opt: مكان كتابة العميل لمعلومات الاستيثاق (authentication information) الخاصة به ‎/opt/digitalocean: مكان تخزين الملفات الثنائية يبلِّغ العميل عن أسماء أكثر العمليات استهلاكًا لموارد النظام، لكنه لا يُرسِل أيّة متغيرات متعلقة بالبيئة، ولا أيّة وسائط مُمرَّرة إلى العمليات تفاديًا لكشف أيّة معلومات حساسة. كيف يُرسِل العميل البيانات المُقاسة من النظام؟ يستعمل عميل DigitalOcean المنفذين 80 و 443 لإرسال البيانات، ولن يحتاج إلى استقبل أيّة بيانات. يُستعمَل المنفذ 80 للتواصل مع خدمة DigitalOcean metadata للحصول على بيانات الاستيثاق، ثم سيستعمل العميل تلك البيانات للاستيثاق مع خدمة معالجة الإحصائيات وستُشفّر جميع البيانات. كيفية تفعيل العميل عليك تثبيت العميل على خادومك للحصول على معلومات إضافية عن خادومك في Droplet Graphs. يمكن فعل ذلك تلقائيًا أثناء عملية إنشاء الخادوم، أو يدويًا في أيّ وقت. تفعيل العميل أثناء عملية إنشاء الخادوم لتثبيت العميل أثناء إنشاء الخادوم، فاختر حقل Monitoring في قسم «additional options» في صفحة الإنشاء: سيُثبّت العميل تلقائيًا وسيُفعّل أثناء مرحلة إنشاء الخادوم. تثبيت العميل يدويًا يتوافر سكربت للتثبيت لتثبيت العميل يدويًا. سيُضيف السكربت مستودعًا إلى نظامك ويستعمل مدير الحزم لتثبيت العميل. وهذا سيُبسِّط إجراء عمليات إدارة الحزم مثل تحديث العميل أو حذفه. سجِّل دخولك إلى خادومك بالمستخدم root أو بأي مستخدم يملك امتيازات الجذر عبر الأمر sudo: ssh root@droplet_IP_address بعد أن تتصل بخادومك، يمكنك تنزيل وتنفيذ سكربت التثبيت مباشرةً لو شئت تثبيت العميل من فورك: curl -sSL https://agent.digitalocean.com/install.sh | sh قد يُطلَب منك إدخال كلمة مرورك إذا كنتَ تستعمل مستخدمًا يملك امتيازات الجذر عبر الأمر sudo. ملاحظة: لو أردتَ تفحص السكربت قبل تثبيت، فيمكنك كتابته إلى القرص أولًا: curl -sSL https://agent.digitalocean.com/install.sh -o /tmp/install.sh ثم تنظر في محتويات الملف بتنفيذ الأمر: less /tmp/install.sh بعد أن تتطلع على محتوى الملف وتتأكد منه، فيمكنك إكمال عملية التثبيت وتشغيل السكربت كما يلي: sh /tmp/install.sh يجب أن يكون العميل مثبتًا على نظامك الآن. كيفية عرض المخططات الجديدة زر قسم «Graphs» في صفحة تفاصيل الخادوم بعد تثبيت العميل وتفعيله: يجب أن تصبح المخططات الجديدة ظاهرةً لك بعد برهةٍ من تفعيل العميل: استخدم القائمة المنسدلة لتعديل المدة الزمنية الظاهرة في المخططات، وضع الفأرة فوق أحد المخططات لعرض معلومات تفصيلية. كيفية حذف العميل إذا لم تعد تريد استخدام العميل، فيمكنك حذف الحزمة باستخدام مدير الحزم الموجود في توزيعتك. لخواديم أوبنتو ودبيان، اكتب: sudo apt-get purge do-agent أما لخواديم CentOS: sudo yum remove do-agent سيتم إيقاف الخدمة وستُزال الحزمة من نظامك. وإذا أردتَ إعادة تثبيت العميل لاحقًا فيمكنك فعل ذلك عبر مدير الحزم. كيفية إزالة مستودع عميل DigitalOcean لحذف مستودع العميل من خادومك، فكل ما عليك فعله هو حذف ملف ضبط المستودع. لخواديم أوبنتو ودبيان، اكتب: sudo rm /etc/apt/sources.list.d/digitalocean-agent.list أما لخواديم CentOS: sudo rm /etc/yum.repo.d/digitalocean-agent.repo سيُحذَف ملف ضبط مستودع عميل DigitalOcean من خادومك. الخلاصة يزيد عميل DigitalOcean من البيانات المعروضة في Droplet Graphs ليوفر لك معلوماتٍ إضافيةٍ عن أداء خادومك واستعماله للموارد. ترجمة -وبتصرّف- للمقال How To Install and Use the DigitalOcean Agent for Additional Droplet Graphs لصاحبه Justin Ellingwood
  9. تمهيد تتوسع قواعد البيانات بسرعة مع مرور الزمن، وتكاد في بعض الأحيان أن تملأ المساحة التخزينية المتاحة في نظام الملفات كلها. وقد تتعرض أيضًا إلى مشاكل في الإدخال والإخراج نتيجةً لمحاولة عدِّة خدمات الكتابة على (أو القراءة من) نفس القسم معًا. هذا الدرس سيفيدك لو كنتَ تريد إضافة المزيد من المساحة التخزينية، أو استخدام خصائص جهاز التخزين لزيادة الأداء (ربما عبر استخدام RAID)، أو تتطلّع إلى استعمال ميزات أخرى للتخزين. سيعلِّمُك هذا الدرس طريقة تغيير مجلد تخزين بيانات MySQL. التعليمات المذكورة هنا تناسب الخواديم التي تُشغِّل نسخةً وحيدةً من MySQL، أمّا لو كانت عندك أكثر من نسخة، فسيساعدك درس «How To Move a MySQL Data Directory to a New Location on Ubuntu 16.04» في ذلك، لأنَّه يحتوي معلومات عن كيفية تغيير مكان التخزين عبر تعديل الضبط. المتطلبات المسبقة خادوم أوبنتو 16.04 (أو 14.04) مضبوطٌ كما في درس «الإعداد الابتدائي لخادوم أوبنتو 14.04»، بما في ذلك إعداد حساب مستخدم عادي لكنه يملك امتيازات الجذر (root) عبر الأداة sudo. خادوم MySQL. لو لم يكن عندك خادوم MySQL مضبوطٌ مسبقًا، فسيساعدك الدرس «تثبيت وإعداد نظامي إدارة قواعد البياناتMySQL وPostgreSQL على أوبنتو». نسخة احتياطية من قواعد بياناتك. ما لم تكن تتعامل مع نسخةٍ حديثة التثبيت من MySQL، فاحرص على أخذ نسخة احتياطية من بياناتك. سيساعدك درس «كيف تقوم بالنسخ الاحتياطي لقواعد بيانات MySQL على Ubuntu» على فعل ذلك. سننقل البيانات من جهاز تخزينٍ موصولٍ (mounted) في نقطة الوصل ‎/mnt/volume-nyc1-01.. سيعلّمك هذا الدرس طريقة نقل مجلد تخزين بيانات MySQL إلى مكانٍ جديد بغض النظر عن وسيط التخزين الذي تستخدمه (قرص صلب، أو مصفوفة RAID، أو تخزين شبكي). الخطوة الأولى: نقل مجلد بيانات MySQL لكي نضمن سلامة البيانات، علينا أولًا إيقاف خادوم MySQL: sudo systemctl stop mysql الأمر systemctl لا يُظهِر نتيجة تنفيذ أوامر إدارة الخدمات، لذا إذا أردتَ التحقق أنَّ الخادوم قد أُغلِق بنجاح، فنفِّذ الأمر الآتي: sudo systemctl status mysql انظر إلى آخر سطر من ناتج الأمر السابق الذي يجب أن يخبرك أنَّ الخادوم قد توقف عن العمل: . . . Jul 18 11:24:20 ubuntu-512mb-nyc1-01 systemd[1]: Stopped MySQL Community Server. نستطيع الآن –بعد إغلاق الخادوم– نقل مجلد قواعد البيانات إلى مكانٍ آخر: sudo mv /var/lib/mysql /mnt/volume-nyc1-01/mysql ثم سنُنشِئ وصلةً رمزيةً (symbolic link): sudo ln -s /mnt/volume-nyc1-01/mysql /var/lib/mysql يبدو أنَّنا نستطيع تشغيل خادوم MySQL بعد إنشاء الوصلة الرمزية، لكن هنالك أمرٌ إضافيٌ يجب ضبطه. الخطوة الثانية: ضبط قواعد الوصول في AppArmor بعد أن نقلتَ مجلد MySQL إلى مكانٍ آخر في نظام الملفات، فعليك أن تُعدِّل في ضبط AppArmor، وذلك بتعديل ملف alias التابع لبرمجية AppArmor: sudo nano /etc/apparmor.d/tunables/alias أضف الآن التعليمة الآتية في نهاية الملف: . . . alias /var/lib/mysql/ -> /mnt/volume-nyc1-01/mysql/, لكي تأخذ التعديلات مفعولها، فيجب إعادة تشغيل AppArmor: sudo systemctl restart apparmor ملاحظة: إذا تخطيت خطوة ضبط AppArmor وحاولت تشغيل mysql مباشرةً، فستظهر لك رسالة الخطأ الآتية: Job for mysql.service failed because the control process exited with error code. See "systemctl status mysql.service" and "journalctl -xe" for details. يمكن تلخيص الناتج الظاهر من الأمرَين systemctl و journalctl بما يلي: Jul 18 11:03:24 ubuntu-512mb-nyc1-01 systemd[1]: mysql.service: Main process exited, code=exited, status=1/FAILURE ولمّا كانت رسائل الخطأ لا تُظهِر ربطًا مباشرًا بين AppArmor ومجلد التخزين، فقد يصعب عليك أن تعرف لماذا يحدث هذا الخطأ. لكن إن نظرنا إلى ملف syslog فسنرى ما هي المشكلة: sudo tail /var/log/syslog الناتج: Nov 24 00:03:40 digitalocean kernel: [ 437.735748] audit: type=1400 audit(1479945820.037:20): apparmor="DENIED" operation="mknod" profile="/usr/sbin/mysqld" name="/mnt/volume-nyc1-01/mysql/mysql.lower-test" pid=4228 comm="mysqld" requested_mask="c" denied_mask="c" fsuid=112 ouid=112 يمكننا الآن أن نُشغِّل خدمة MySQL: sudo systemctl start mysql sudo systemctl status mysql بعد أن تُعيد تشغيل MySQL، فتحقق أنَّ بياناتك سليمة وأنَّ خادوم MySQL يعمل عملًا سليمًا دون مشاكل. الخلاصة نقلنا في هذا الدرس مجلد تخزين بيانات MySQL واستعملها وصلةً رمزيةً لكي نخبر MySQL ما هو مكان التخزين الجديد؛ ثم حدّثنا ضبط برمجية AppArmor (الموجودة في توزيعة أوبنتو وغيرها) لكي يتوافق مع ما عدّلناه. وصحيحٌ أننا استعملنا جهاز تخزين مستقل، إلا أنَّك تستطيع اتباع تعليمات هذا الدرس لإعادة تعريف مكان تخزين البيانات بغض النظر عن تقنية التخزين المستعملة. ترجمة -وبتصرّف- للمقال How to Change a MySQL Data Directory to a New Location Using a Symlink لصاحبته Melissa Anderson
  10. تمهيد كلما كانت صفحات الموقع أسرع تحميلًا كلما ازداد احتمال بقاء الزائر متصفحًا للموقع، وعندما تمتلئ صفحات مواقع الويب بالصور والمحتوى التفاعلي الظاهر عبر سكربتات تُحمَّل في الخلفية، فلن تكون عملية فتح «صفحة» ويب أمرًا هينًا، إذ تتضمن طلب ملفاتٍ عدِّة من خادوم الويب ملفًا ملفًا، وعملية تقليل تلك الطلبيات هي إحدى سُبُل تسريع موقعك. يمكن فعل ذلك بطرائق عدِّة، لكن إحدى أهم الخطوات التي يجب إجراؤها هي ضبط التخزين المؤقت في المتصفح (browser caching)؛ وهذا يعني إخبار المتصفح بإمكانية استخدام نسخ محلية من الملفات التي حُمِّلَت في إحدى المرات بدلًا من طلبها من الخادوم مرارًا وتكرارًا؛ ولفعل ذلك يجب إضافة ترويسات لرد HTTP ‏(HTTP response headers) تخبر المتصفح بما عليك فعله. هذا هو دور وحدة header (‏header module) في خادوم Nginx، التي يمكن استعمالها لإضافة الترويسات إلى رد HTTP، لكن دورها الأساسي يمكن في ضبط الترويسات المسؤولة عن التخزين المؤقت. سننظر في هذا الدرس إلى كيفية استخدام وحدة header لتطبيق الفكرة السابقة. المتطلبات المسبقة ستحتاج قبل إكمال قراءة هذا الدرس إلى ما يلي: خادوم أوبنتو 16.04 (أو 14.04) مضبوطٌ كما في درس «الإعداد الابتدائي لخادوم أوبنتو 14.04»، بما في ذلك إعداد حساب مستخدم عادي لكنه يملك امتيازات الجذر (root) عبر الأداة sudo. خدمة Nginx مثبّتة على خادومك باتباعك لهذا الدرس «تنصيب، إعداد واستخدام nginx كخادوم ويب». سنحتاج بالإضافة إلى وحدة header إلى وحدة map التابعة لخادوم Nginx؛ لمزيدٍ من المعلومات حول وحدة map، فاقرأ درس كيفية استخدام وحدة map على خادوم أوبنتو 16.04. الخطوة الأولى: إنشاء ملفات للتجربة سنُنشِئ في هذه الخطوة عدِّة ملفات في مجلد Nginx، إذ سنستخدم هذه الملفات لاحقًا للتحقق من سلوك خادوم Nginx الافتراضي لاختبار أنَّ التخزين المؤقت في المتصفح يعمل عملًا صحيحًا. لتقرير ما هو نوع الملفات المُخدَّمة عبر الشبكة، فلن يُحلِّل Nginx محتويات الملف لأن ذلك يستهلك وقتًا كثيرًا؛ وإنما سينظر إلى لاحقة (أو امتداد) الملف لتحديد ما هو نوع MIME التابع له، والذي سيُصرِّح عن الهدف أو الغاية من الملف. ونتيجةً لهذا السلوك، فلن يكون لمحتوى الملفات الاختبارية أيّة أهمية؛ إذ سنستطيع خداع خادوم Nginx بتسمية الملفات تسميةً صحيحةً، فقد يظن خادوم Nginx أنَّ ملفًا فارغًا يُمثِّل صورةً وآخر يُمثِّل سكربت JavaScript. لنُنشِئ ملفًا باسم test.html في مجلد Nginx الافتراضي عبر الأداة truncate (يمكنك استخدام أيّة أداة أو أمر آخر وليكن touch مثلًا). لاحقة الملف تُشير إلى أنَّه مستند HTML: sudo truncate -s 1k /var/www/html/test.html لنُنشِئ المزيد من الملفات الاختبارية بنفس الطريقة السابقة: صورة jpg وملف css وسكربت js: sudo truncate -s 1k /var/www/html/test.jpg sudo truncate -s 1k /var/www/html/test.css sudo truncate -s 1k /var/www/html/test.js الخطوة التالية هي التحقق من سلوك خادوم Nginx فيما يتعلق بإرسال ترويسات للتحكم بالتخزين المؤقت للمتصفح وذلك بتخديم الملفات التي أنشأناها أخيرًا مع الضبط الافتراضي له. الخطوة الثانية: التحقق من السلوك الافتراضي لخادوم Nginx يكون لجميع الملفات نفس سلوك التخزين المؤقت افتراضيًا. ولرؤية ذلك سنستخدم ملف HTML الذي أنشأناه في الخطوة الأولى، إلا أنَّك تستطيع إجراء هذا الاختبار على أيّ ملف من الملفات التي أنشأناها سابقًا. لنتحقق من أنَّ الملف test.html يُخدَّم ومعه المعلومات التي لها علاقة بالمدة الواجب تخزين المتصفح للملف فيها مؤقتًا. سيؤدي الأمر الآتي إلى طلب الملف من خادوم Nginx المحلي ويُظهِر ترويسات رد HTTP: curl -I http://localhost/test.html يجب أن ترى عدِّة ترويسات HTTP: HTTP/1.1 200 OK Server: nginx/1.10.0 (Ubuntu) Date: Sat, 10 Sep 2016 13:12:26 GMT Content-Type: text/html Content-Length: 1024 Last-Modified: Sat, 10 Sep 2016 13:11:33 GMT Connection: keep-alive ETag: "57d40685-400" Accept-Ranges: bytes يمكنك أن تلاحظ السطر قبل الأخير الذي يبدأ بالكلمة ETag الذي يتضمن مُعرِّفًا فريدًا للنسخة الحالية من الملف المطلوب. فإذا حاولت تنفيذ أمر curl السابق أكثر من مرة فستجد نفس قيمة ETag. أما عند استخدام متصفح ويب، فستُخزَّن قيمة ETag ثم تُرسَل إلى الخادوم عبر ترويسة If-None-Match عندما يريد المتصفح أن يطلب نفس الملف مرةً أخرى (عند تحديث الصفحة على سبيل المثال). يمكنك محاكاة ذلك في سطر الأوامر بالأمر الآتي. احرص على تغيير قيمة الترويسة If-None-Match في الأمر لتُطابِق قيمة ETag في ناتج الأمر السابق: curl -I -H 'If-None-Match: "57d40685-400"' http://localhost/test.html ستجد أنَّ الرد أصبح مختلفًا الآن: HTTP/1.1 304 Not Modified Server: nginx/1.10.0 (Ubuntu) Date: Sat, 10 Sep 2016 13:20:31 GMT Last-Modified: Sat, 10 Sep 2016 13:11:33 GMT Connection: keep-alive ETag: "57d40685-400" ردّ خادوم Nginx بحالة «‎304 Not Modified»، ولن يُرسِل الملف عبر الشبكة مجددًا، وإنما يخبر المتصفح أنَّه يستطيع إعادة استخدام الملف المُخزَّن محليًا والذي نزَّله سابقًا. ما سبق مفيدٌ لأنه يُقلِّل من التراسل الشبكي، لكنه ليس مثاليًا لتحقيق أداءٍ عالٍ نتيجةٍ للتخزين المؤقت. المشكلة مع ترويسة ETag أنَّ المتصفح سيرسل الطلبية إلى الخادوم دائمًا ويسأله إن كان بإمكانه استخدام الملف المُخزَّن مؤقتًا، وحتى لو ردّ الخادوم على المتصفح بالإيجاب بحالة 304 بدلًا من إعادة إرسال الملف مجددًا، إلا أنَّ إرسال الطلبية واستلام الرد سيستهلك وقتًا. سنستخدم في الخطوة التالية وحدة headers لإضافة معلومات للتحكم بالتخزين المؤقت. وهذا سيجعل المتصفح يُخزِّن الملفات محليًا دون أخذ إذن الخادوم أولًا. الخطوة الثالثة: ضبط ترويستَي Cache-Control و Expires إضافةً إلى ترويسة التحقق من تغيّر الملف (ETag)، هنالك ترويستان للتحكم بالتخزين المؤقت هما Cache-Control و Expires. ترويسة Cache-Control هي نسخةٌ أحدث، وفيها خياراتٌ أكثر مقارنةً بترويسة Expires وهي أفيد إن شئت التحكم بسلوك التخزين المؤقت تحكمًا دقيقًا. إذا ضُبِطَت تلك الترويسات، فهي تخبر المتصفح أنَّ الملف المطلوب يمكن أن يُخزَّن محليًا لفترةٍ زمنيةٍ معيّنة (تستطيع أن تجعل صلاحيته «للأبد»!) دون طلبه مرةً أخرى. أما إن لم تُضبَط تلك الترويسات، فسيطلب المتصفحُ الملفَ دومًا من الخادوم، متوقعًا أن يحصل على الحالة «‎200 OK» أو «‎304 Not Modified». يمكننا استخدام وحدة header لإرسال ترويسات HTTP آنفة الذكر. وحدة header هي وحدةٌ من أساس خادوم Nginx لذا لن تحتاج إلى تثبيت أيّ شيءٍ لاستخدامها. لإضافة وحدة header، افتح ملف ضبط Nginx في محرر nano أو أيّ محررٍ نصيٍ تشاء: sudo nano /etc/nginx/sites-available/default اعثر على القسم المُعَنوَنَ server، والذي يبدو كما يلي: . . . # Default server configuration # server { listen 80 default_server; listen [::]:80 default_server; . . . أضف القسمَين الآتيين: أولهما قبل قسم server لتعريف المدة الزمنية لتخزين مختلف أنواع الملفات مؤقتًا، وثانيهما داخل كتلة server الذي يضبط ترويسات التخزين المؤقت ضبطًا سليمًا: . . . # Default server configuration # # Expires map map $sent_http_content_type $expires { default off; text/html epoch; text/css max; application/javascript max; ~image/ max; } server { listen 80 default_server; listen [::]:80 default_server; expires $expires; . . . القسم الموجود قبل قسم server هو قسم map الذي يربط بين أنواع الملفات ومدة التخزين المؤقت لهذا النوع من الملفات. استعملنا عدِّة خيارات ضبط فيه: القيمة الافتراضية هي off، والتي لن تُضيف أيّة ترويسات للتحكم بالتخزين المؤقت، وهذا خيارٌ جيدٌ للمحتوى الذي لا يتطلب تخزينًا مؤقتًا. لنوع text/html سنضبط القيمة إلى epoch وهي قيمةٌ خاصةٌ التي تعني عدم التخزين المؤقت نهائيًا، مما يجبر المتصفح على السؤال إن كانت الصفحة مُحدَّثة. لنوع text/css و application/javascript -والتي هي ملفات الأنماط CSS وسكربتات JavaScript- سنضبط القيمة إلى max وهذا يعني أنَّ المتصفح سيُخزِّن هذه الملفات مؤقتًا أطول مدة يستطيعها، مما يُقلِّل عدد الطلبيات لوجود عدد كبير من هذه الملفات التي ترتبط بصفحة HTML. آخر خيار لنوع ‎~image/‎ وهو تعبيرٌ نمطيٌ (regular expression) الذي يُطابِق جميع الملفات ذات النوع الذي يحتوي على العبارة image/‎ فيه (مثل image/jpg و image/png). وكما في ملفات CSS و JS، تتواجد عادةً صورٌ كثير في مواقع الويب، ومن الجيد تخزينها محليًا، لذا ضبطنا القيمة إلى max أيضًا. التعليمة expires داخل قسم server (التي هي جزءٌ من وحدة headers) تضبط ترويسات التحكم بالتخزين المؤقت؛ حيث تستخدم القيمة المأخوذة من المتغير ‎$expires الذي يربط بين أنواع الملفات ومدة صلاحيتها. وبهذه الطريقة ستختلف الترويسات المُرسَلة اعتمادًا على نوع الملف. احفظ الملف واخرج من المُحرِّر النصي، وأعد تشغيل خادوم Nginx لتفعيل الضبط الجديد: sudo systemctl restart nginx سنتحقق في الخطوة التالية من أنَّ الضبط الجديد يعمل عملًا سليمًا. الخطوة الرابعة: اختبار التخزين المؤقت في المتصفح لنُنفِّذ نفس الأمر الذي طلبنا فيه ملف HTML في القسم السابق: curl -I http://localhost/test.html سيكون الرد مختلفًا هذه المرة، إذ يجب أن ترى ترويستين جديدتين: HTTP/1.1 200 OK Server: nginx/1.10.0 (Ubuntu) Date: Sat, 10 Sep 2016 13:48:53 GMT Content-Type: text/html Content-Length: 1024 Last-Modified: Sat, 10 Sep 2016 13:11:33 GMT Connection: keep-alive ETag: "57d40685-400" Expires: Thu, 01 Jan 1970 00:00:01 GMT Cache-Control: no-cache Accept-Ranges: bytes ترويسة Expires مرتبطة بتاريخٍ في الماضي وترويسة Cache-Control مضبوطةٌ إلى no-cache، مما يجعل المتصفح يسأل الخادوم إن توفرت نسخةٌ جديدةٌ من الملف (باستخدام ترويسة ETag كما سبق). ستجد ردًا مختلفًا عندما تُجرِّب على صورة: curl -I http://localhost/test.jpg ناتج الأمر السابق: HTTP/1.1 200 OK Server: nginx/1.10.0 (Ubuntu) Date: Sat, 10 Sep 2016 13:50:41 GMT Content-Type: image/jpeg Content-Length: 1024 Last-Modified: Sat, 10 Sep 2016 13:11:36 GMT Connection: keep-alive ETag: "57d40688-400" Expires: Thu, 31 Dec 2037 23:55:55 GMT Cache-Control: max-age=315360000 Accept-Ranges: bytes ارتبطت الترويسة Expires في هذه المرة بتاريخٍ في المستقبل البعيد، واحتوت ترويسة Cache-Control على max-age التي تخبر المتصفح كم ثانية يجب أن يُبقي على الملف. وهذا يُخبِر المتصفح أن يُخزِّن الصورة المُنزَّلة مؤقتًا لأطول مدة ممكنة، وبالتالي إذا ظهرت الصورة مرةً أخرى فستُستعمَل النسخة المحلية ولن تُرسَل الطلبية إلى الخادوم بتاتًا. يجب أن تكون النتيجة مشابهةً لملفَي test.js و test.css لأنهما ملفا JavaScript و CSS ويُضبَط لهما ترويسات التخزين المؤقت أيضًا، مَثَلُهُما كَمَثل الصور. إن ظهر عندك مثلما عرضنا في أمثلتنا، فاعلم أنَّ ترويسات التحكم بالتخزين المؤقت قد ضُبِطَت ضبطًا صحيحًا وسيستفيد موقعك من ناحية الأداء، وسيقل الحِمل على خادومك نتيجةً لتخزين المتصفح للملفات محليًا وعدم طلبها من الخادوم. عليك الآن أن تُخصِّص إعدادات التخزين المؤقت اعتمادًا على محتوى موقعك، لكن قد تجد أنَّ الضبط الذي وفرناه في هذا الدرس مناسبًا لتبدأ منه. الخلاصة يمكن أن تُستعمَل وحدة headers لإضافة أي نوع من الترويسات إلى رد HTTP، لكن من أفضل تطبيقات هذه الوحدة هو ضبط ترويسات التحكم بالتخزين المؤقت. إذ سيساعد ذلك في تحسين أداء مواقع الويب المُستضافة على خادومك، خصوصًا في الشبكات ذات زمن التأخير المرتفع (نسبيًا) مثل شبكات الهواتف المحمولة. يمكن أن يؤدي ذلك إلى تحسين ظهور موقعك في محركات البحث التي تأخذ عامل سرعة الموقع بالحسبان. حيث يُعتَبَر ضبط ترويسات التخزين المؤقت من أبزر نصائح أدوات اختبار سرعة الصفحات من Google (أقصد Google PageSpeed). لمزيدٍ من المعلومات التفصيلية عن وحدة headers، فارجع إلى توثيق Nginx الرسمي. ترجمة -وبتصرّف- للمقال How to Implement Browser Caching with Nginx’s header Module on Ubuntu 16.04 لصاحبه Mateusz Papiernik.
  11. من الطبيعي أن نرتكب نحن البشر أخطاءً، لكننا نصحح تلك الأخطاء بنفسنا، أي أننا نميل إلى التعلم من أخطائنا ولا نرتكب الخطأ نفسه مرتين؛ أغلبية الأخطاء التي أقع فيها عند تطوير مواقع ووردبريس تأتي من محاولتي لتقليل الوقت اللازم لإنشاء الميزات، لكن ينتهي المطاف بمَن يعتمد هذه الطريقة بألمٍ في الرأس وندمٍ على أخطائه؛ لكن التعلم من هفوات الآخرين أفضل من الوقوع بكثير من الأخطاء وتضييع الوقت. الخطأ الشائع الأول: عدم تفعيل ميزة التنقيح في ووردبريس لماذا علينا تفعيل ميزة التنقيح (debugging) عندما تعمل الشيفرة كما يجب؟ ميزة التنقيح هي ميزةٌ مدمجة في ووردبريس التي ستؤدي إلى إظهار الأخطاء والتحذيرات والملاحظات (عن الدوال المهملة [deprecated functions] …إلخ.). عندما تكون ميزة التنقيح معطلةً فقد لا تظهر التحذيرات أو الملاحظات المهمة، مما قد يؤدي إلى حدوث مشاكل مستقبلًا إن لم تتعامل مع تلك التحذيرات مباشرةً. ونريد أن تتوافق الشيفرات الجديدة مع بقية عناصر الموقع، لذا عند إضافتها إلى ووردبريس يجب فعل ذلك في بيئة تطويرية مُفعَّلةٌ فيها ميزة التنقيح (لكن احرص على تعطيل هذه الميزة قبل نشر الموقع وتحويله إلى بيئةٍ إنتاجيةٍ). عليك تعديل ملف wp-config.php الموجود في المجلد الرئيسي لووردبريس لتفعيل هذه الميزة، هذا جزءٌ من الملف: // Enable debugging define('WP_DEBUG', true); // Log all errors to a text file located at /wp-content/debug.log define('WP_DEBUG_LOG', true); // Don’t display error messages write them to the log file /wp-content/debug.log define('WP_DEBUG_DISPLAY', false); // Ensure all PHP errors are written to the log file and not displayed on screen @ini_set('display_errors', 0); لا تضم الشيفرة السابقة جميع خيارات الضبط التي يمكن تفعيلها، لكن الضبط السابق كافٍ عادةً لأغلبية الاحتياجات. الخطأ الشائع الثاني: إضافة السكربتات وأنماط CSS عبر wp_head أين الخطأ في إضافة السكربتات إلى قالب header؟ تتضمن ووردبريس مسبقًا عددًا كبيرًا من السكربتات المشهورة، ومع ذلك يأتي المطورون ويضيفون سكربتاتٍ جديدة باستخدام الخطّاف (hook) ذو الاسم wp_head. وهذا قد يؤدي إلى تحميل نفس السكربت عدِّة مرات لكن بإصدارات مختلفة. أتت عملية طلب تحميل السكربتات (enqueuing) لحل هذه الإشكالية، وهذه العملية تمثِّل الطريقة المثُلى لإضافة السكربتات وأنماط CSS إلى موقع ووردبريس. علينا استخدام هذه الطريقة لمنع التضاربات بين السكربتات التي تتطلّب الإضافات استخدامها، وللتعامل مع (ومن ثم تضمين) المكتبات المطلوبة من أحد السكربتات. يمكن استخدام هذه التقنية عبر الدالتين الموجودتين في ووردبريس wp_enqueue_script و wp_enqueue_style لطلب تحميل السكربتات وصفحات الأنماط (على التوالي وبالترتيب). الفرق الأساسي بين الدالتين السابقتين يمكن في أنَّ الدالة wp_enqueue_script تقبل معاملًا (parameter) إضافيًا الذي يسمح لنا بنقل مكان إضافة السكربت إلى أسفل الصفحة. wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) wp_enqueue_script( $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) wp_register_style( $handle, $src, $deps = array(), $ver = false, $media = 'all' ) wp_enqueue_style( $handle, $src = false, $deps = array(), $ver = false, $media = 'all' ) إذا لم يكن السكربت ضروريًا لعرض محتوى الصفحة، فيمكننا نقله إلى أسفلها لكي تُحمَّل محتويات الصفحة بسرعة؛ يجدر بالذكر أنَّه من المستحسن تسجيل (register) السكربت أولًا قبل طلب تحميله، لأنَّ ذلك يسمح للمطورين الآخرين بإلغاء تسجيل السكربت عبر المرجعية (أي ‎$handle) وذلك ضمن الإضافات التي يكتبونها، دون الحاجة إلى تعديل الشيفرات البرمجية لإضافتك. وعند تسجيل السكربت فيمكن ذكره ضمن مصفوفة الاعتماديات (أي ‎$deps) عبر مرجعيته إن وَجَبَ تحميله قبل تضمين أحد السكربتات الأخرى التي طُلِبَ تحميلها، فعندئذٍ سيُحمَّل السكربت تلقائيًا قبل تحميل السكربت الآخر. الخطأ الشائع الثالث: تفادي استخدام القوالب الأبناء وتعديل ملفات ووردبريس الأساسية أنشِئ قالب ابن (child theme) دومًا إذا أدرتَ تعديل أحد القوالب، فبعض المطورين يجرون تعديلات على ملفات القالب الأب ثم يكتشفون أنَّ جميع تعديلاتهم قد ذهبت أدراج الرياح ولن يستطيعوا استعادتها بعد تحديث القالب. لإنشاء قالب ابن عليك وضع ملف style.css في مجلدٍ فرعي تابعٍ للقالب الابن الخاص بك، وبه المحتويات الآتية: /* Theme Name: Twenty Sixteen Child Theme URI: http://example.com/twenty-fifteen-child/ Description: Twenty Fifteen Child Theme Author: John Doe Author URI: http://example.com Template: twentysixteen Version: 1.0.0 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Tags: light, dark, two-columns, right-sidebar, responsive-layout, accessibility-ready Text Domain: twenty-sixteen-child */ الملف السابق هو مثالٌ عن إنشاء قالب ابن يعتمد على أحد قوالب ووردبريس المُضمَّنة فيها وهو Twenty Sixteen. أهم سطر في الشيفرة السابقة هو السطر الذي يحتوي على كلمة Template والتي يجب أن يُطابِق محتواه اسمَ مجلد القالب الأب. ينطبق نفس المبدأ على ملفات ووردبريس الأساسية: لا تختر الطريق السهل وتُعدِّل الملفات الأساسية. وإنما ابذل جهدًا قليلًا وأعد تعريف الدوال القابلة للتعديل فقط (تسمى Pluggable functions) واستعمل مرشِّحات ووردبريس لمنع الكتابة فوق تعديلاتك بعد تحديث نسخة ووردبريس. تسمح لك إعادة تعريف الدوال باستخدام شيفرات خاصة بك بدلًا من الدوال الأساسية، لكن خفَّ استعمال هذه الطريقة وأصبحنا نستخدم المُرشِّحات (filters) بدلًا منها. حيث يؤدي استخدام المرشحات إلى نفس الناتج، وستُضاف التعليمات البرمجية في نهاية دوال ووردبريس للسماح بتعديل مخرجاتها. إحدى الأشياء التي عليك الانتباه إليها هي إعادة تعريف الدوال ضمن عبارة شرطية if ( !function_exists() ) لاحتمال تعديل أكثر من إضافة لنفس الدالة، فبدون تلك العبارة الشرطية فسيحدث خطأٌ من النوع fetal. الخطأ الشائع الرابع: ضبط بعض القيم ضبطًا ثابتًا نستسهل عادةً إضافة قيمٍ ثابتةٍ (مثل روابط URL) في مكانٍ ما في الشيفرات التي نكتبها، لكن الوقت الذي سننفقه لمعرفة سبب حدوث المشاكل ناتجة عن القيم الثابتة ولتصحيح تلك المشاكل سيكون أكبر. وإذا استعملنا الدالة المناسبة لتوليد المسار المطلوب ديناميكيًا، فسنُبسِّط عملية صيانة وتنقيح الشيفرة. فمثلًا لو نقلتَ موقعك من بيئةٍ تطويريةٍ إلى بيئةٍ إنتاجيةٍ لكنك استخدمتَ روابط URL ثابتة في الشيفرات؛ فعندئذٍ ستُفاجَأ أنَّ موقعك لا يعمل. وهذا هو السبب وراء وجوب استخدامنا للدوال –كتلك المذكورة أدناه– لتوليد مسارات وروابط URL توليدًا ديناميكيًا: // الحصول على رابط القالب الابن الحالي stylesheet_directory_uri(); // الحصول على رابط القالب الأب get_template_directory_uri(); // الحصول على رابط الموقع الحالي site_url(); مثالٌ آخر عن مساوئ ضبط القيم ضبطًا ثابتًا يظهر جليًا عند كتابة طلبيات SQL مخصصة. فنحن نغيّر عادةً «السابقة» (prefix) التي تأتي قبل أسماء الجداول في قاعدة بيانات ووردبريس من wp_‎ إلى شيءٍ آخر مختلف مثل wp743_‎، فلو وضعنا اسم الجدول كقيمة ثابتة فلن تُنفَّذ الطلبيات إذا نقلنا ووردبريس إلى قاعدة بيانات أخرى، لأنَّ السابقة قد تختلف حينها. ولمنع حدوث هذه الإشكالية فيمكننا استعمال خاصيات الصنف wpdb: global $wpdb; $user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->users" ); لاحظ أنَّنا لم نستعمل القيمة wp_users مكان اسم الجدول، وإنما تركنا الأمر بيد ووردبريس. سيساعدنا استعمال هذه الخاصيات لتوليد أسماء صحيحة للجداول. الخطأ الشائع الخامس: عدم منع فهرسة موقعك من محركات البحث لماذا قد أرغب منع فهرسة محركات البحث لموقعي؟ أليس ذلك أمرًا جيدًا؟ حسنًا، عندما تطوِّر موقع ويب فلن ترغب بالسماح لمحركات البحث بفهرسة الموقع إلى أن تنتهي من بنائه وتختار بنية «الروابط الدائمة» (permalinks) المناسبة. إضافةً إلى أنَّك إذا كنتَ تطوِّر الموقع على خادوم آخر فلن ترغب بفهرسة محركات البحث لصفحات الموقع كي لا تمسي صفحات الموقع الإنتاجي وكأنها منسوخة من هذا الموقع! وعندما تتواجد أكثر من نسخة لنفس المحتوى، فسيصعب على محركات البحث تحديد ما هو المصدر الملائم أكثر لمطابقة عبارة البحث، وستعاقب محركات البحث عادةً المواقع ذات المحتوى المتماثل مما يؤدي إلى تقليل تقييم صفحتك في نتائج البحث. وكما هو ظاهر في الصورة الآتية، هنالك خيارٌ في صفحة «Reading Settings» (إعدادات القراءة) المُعَنوَن «Discourage search engines from indexing this site» (منع محركات البحث من أرشفة هذا الموقع) لكن لا تغفل عن الملاحظة الظاهر أسفل ذاك الخيار التي تقول أنَّ الأمر متروكٌ لمحركات البحث لكي تستجيب لطلبك بعدم فهرسة الموقع. ابقِ ببالك أنَّ محركات البحث لا تستجيب عادةً لهذا الطلب، ولذا إذا أردتَ منعًا حقيقيًا لمحركات البحث من فهرسة الموقع، فعدِّل ملف ‎.htaccess وأضف السطر الآتي: Header set X-Robots-Tag "noindex, nofollow" الخطأ الشائع السادس: عدم التحقق من تفعيل إحدى الإضافات لماذا نتحقق أنَّ دالةً مرتبطةً بإحدى الإضافات موجودةٌ إذا كانت الإضافة مفعلةً دومًا؟ لكن ماذا لو كانت الإضافة مفعلةً بنسبة 99%، ماذا عن احتمال 1% أنَّ الإضافة معطلة؟ إذا حدث ذلك فمن المرجح أن يعرض موقعك رسائل خطأ، ولمنع ذلك يجب علينا أن نتأكَّد إن كانت الإضافة مفعلة قبل استدعاء إحدى دوالها. إذا كانت تلك الدالة مستدعاةً في «الواجهة الأمامية» (front-end)، فسنحتاج إلى تضمين ملف plugin.php لكي نستطيع استخدام الدالة is_plugin_active()‎: include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); if ( is_plugin_active( 'plugin-folder/plugin-main-file.php' ) ) { // تنفيذ الشيفرات هنا } هذه الطريقة ناجعة وعملية، لكن هنالك بعض الحالات التي يغيّر فيها المطوِّر اسم مجلد الإضافة الرئيسي، لذا من الأفضل التحقق من وجود أحد الأصناف المُعرَّفة في الإضافة: if( class_exists( ‘WooCommerce’ ) ) { // إضافة WooCommerce مفعلة } من غير المرجح أن يُعدِّل المطورون أسماء الأصناف، لذا أفضِّلُ استخدام الطريقة الثانية. الخطأ الشائع السابع: تحميل الكثير من الموارد لماذا يجب أن ننتقي ما هي الموارد التي يجب تحميلها لصفحات الموقع؟ لا يوجد سببٌ منطقيٌ لتحميل السكربتات أو صفحات أنماط CSS التابعة لإضافةٍ معينةٍ إن لم تكن تلك الإضافة مستعملةً في الصفحة التي انتقل إليها المستخدم. يمكننا تقليل وقت تحميل الصفحة إذا حمّلنا الموارد الضرورية فقط، وبالتالي سيُحسِّن ذلك من تجربة المستخدم. خذ على سبيل المثال موقعًا يستعمل WooCommerce، حيث نحتاج إلى تحميل الإضافة في صفحات التسوق فقط، وفي هذه الحالة يمكننا حذف أيّة موارد غير ضرورية في صفحات الموقع الأخرى لتحسين الأداء. يمكننا إضافة الشيفرة الآتية إلى ملف functions.php للقالب أو للإضافة: function load_woo_scripts_styles(){ if( function_exists( 'is_woocommerce' ) ){ if(! is_woocommerce() && ! is_cart() && ! is_checkout() ) { // Dequeue scripts. wp_dequeue_script('woocommerce'); wp_dequeue_script('wc-add-to-cart'); wp_dequeue_script('wc-cart-fragments'); // Dequeue styles. wp_dequeue_style('woocommerce-general'); wp_dequeue_style('woocommerce-layout'); wp_dequeue_style('woocommerce-smallscreen'); } } } add_action( 'wp_enqueue_scripts', 'load_woo_scripts_styles'); يمكن حذف السكربتات عبر الدالة wp_dequeue_script($handle) باستخدام المرجعية التي سُجِّلَت فيها. وبشكلٍ شبيه، تمنع الدالة wp_dequeue_style($handle) تحميل صفحات الأنماط. لكن إن رأيتَ أنَّ ما سبق معقدٌ جدًا ولا تستطيع تنفيذه لبقية الإضافات، فأنصحك بتثبيت إضافة Plugin Organizer التي توفر لك القدرة على اختيار الإضافات التي ستُحمَّل مواردها بناءً على معايير معيّنة، مثل نوع المنشور أو اسمه. من الأفضل أيضًا تعطيل أيّة إضافات للتخزين المؤقت مثل W3Cache والتي فعّلتَها مسبقًا لإظهار التعديلات التي أجريتها. الخطأ الشائع الثامن: الإبقاء على شريط الإدارة لماذا لا أترك شريط الإدارة الموجود أعلى الصفحات ظاهرًا لجميع المستخدمين؟ حسنًا، يمكنك السماح لمستخدميك بالوصول إلى الصفحات الإدارية، لكن ذاك الشريط لن يندمج بشكلٍ جميل مع القالب المختار. إذا أردتَ أن يبدو موقعك احترافيًا، فعليك تعطيل شريط الإدارة وتوفير صفحة مخصصة لإدارة الحساب: add_action('after_setup_theme', 'remove_admin_bar'); function remove_admin_bar() { if (!current_user_can('administrator') && !is_admin()) { show_admin_bar(false); } } ستؤدي الشيفرة السابقة عند إضافتها إلى ملف functions.php (التابع للقالب الذي تستعمله) إلى إظهار شريط الإدارة لمدراء الموقع فقط. يمكنك إضافة أيّة أنواع من المستخدمين إلى دالة current_user_can($capability) في الشيفرة السابقة لعدم إخفاء الشريط لهم. الخطأ الشائع التاسع: عدم استخدام المرشِّح GetText أستطيع استخدام CSS أو JavaScript لتغيير لافتة أحد الأزرار، ما المانع؟ حسنًا، أنت تضيف شيفرات عجيبة وتضيع وقتك سدى لتغيير تلك اللافتة، في حين يمكنك استعمال أحد أجمل المرشِّحات في ووردبريس المدعو gettext. وعند استخدامه مع textdomain التابع للإضافة (وهو مُعرِّف فريد الذي يضمن أن تستطيع ووردبريس التفريق بين جميع الترجمات المتوافرة)، فيمكننا حينها استعمال المرشح gettext لتعديل النص قبل توليد الصفحة. إذا بحثت في الشيفرة المصدرية عن الدالة load_plugin_textdomain($domain)فستجد اسم المجال (domain name) الذي ستحتاج له لتعديل النص. جميع الإضافات الاحترافية تضبط textdomain أثناء تهيئة الإضافة. أما إذا كان لديك نصٌ ما ضمن قالب وكنت تريد تغييره، فابحث عن load_theme_textdomain($domain). سنستعمل هنا إضافة WooCommerce مرةً أخرى كمثال، يمكننا تغيير النص الظاهر لترويسة «Related Products» بإضافة الشيفرة الآتية في ملف functions.php في قالبك: function translate_string( $translated_text, $untranslated_text, $domain ) { if ( $translated_text == 'Related Products') { $translated_text = __( 'Other Great Products', 'woocommerce' ); } return $translated_text; } add_filter( 'gettext', 'translate_string', 15, 3 ); سيُطبَّق المرشِّح السابق على النص المُترجَم عبر دوال ‎__()‎ و ‎_e()‎، لطالما كان مجال النص (textdomain) مضبوطٌ ضبطًا صحيحًا أثناء استخدام تلك الدوال: _e( 'Related Products', 'woocommerce' ); ابحث في إضافتك عن دوال ترجمة النص لمعرفة ما هي السلاسل النصية التي يمكنك تخصيصها. الخطأ الشائع العاشر: الإبقاء على بنية الروابط الدائمة الافتراضية تستخدم ووردبريس افتراضيًا بنية روابط دائمة (permalinks) تتضمن مُعرِّف (ID) المنشور لإظهار محتوى معيّن، لكن هذه البنية ليست جميلة ومستحيلة القراءة، وقد يحذف الزوار أجزاءً مهمةً من روابط URL أثناء نسخها. وأهم ما في الأمر أنَّ الروابط الدائمة الافتراضية ليست «صديقةً» لمحركات البحث. وتفعيل ما ندعوه «الروابط الدائمة الجميلة» سيؤدي إلى تضمين كلمات مفتاحية واسم المنشور في روابط URL مما يُحسِّن من تقييم محركات البحث لموقعنا. يمكن أن يكون تغيير بنية الروابط مهمةً شاقةً خصوصًا إذا كان موقعك يعمل منذ فترةٍ طويلةٍ ولديك مئات المنشورات التي تمت فهرستها من محركات البحث. لذا بعد أن تثبِّت ووردبريس فاحرص على تعديل بنية الروابط الدائمة ليستطيع الزائر معرفة محتوى الصفحة من رابطها بدلًا من عرض معرِّف المنشور فقط. عمومًا أستخدمُ اسم المنشور في الروابط الدائمة لأغلبية المواقع التي أبنيها، لكن يمكننا تخصيص الروابط الدائمة كيفما شئت، انظر إلى مقالة «الدليل الشامل للروابط الدائمة في ووردبريس». الخلاصة لم تضم هذه المقالة جميع الأخطاء التي يرتكبها مطورو ووردبريس (حتى الخبراء منهم)، الفكرة الأساسية التي عليك أن تفهمها من هذه المقالة هي أنَّك عليك ألّا تأخذ الطرق المختصرة (وهذه القاعدة تنطبق أيضًا على جميع منصات التطوير، ولا أقصد بها ووردبريس بعينها). الوقت الذي توفره باتباعك للأساليب البرمجية الضعيفة ستنفق أضعافه وأنت تحاول إصلاح المشاكل التي ستواجهك مستقبلًا. شاركنا بعض الأخطاء التي كنت تقع فيها وأخبرنا ما هي الدروس التي تعلمتها منها في التعليقات. ترجمة –وبتصرّف– للمقال The 10 Most Common Mistakes That WordPress Developers Make لصاحبه Andrew Schultz
  12. سنشرح في هذه الدرس كيفية تقليل عدد السجلات المُعادة من طلبية SELECT، وطريقة ترتيب الناتج بناءً على شرطٍ معيّن. إضافةً إلى ما سبق، سنتعلم كيفية تجميع السجلات وإجراء عمليات حسابية أساسية على الحقول الرقمية، وكل ما سبق سيساعدنا على إنشاء سكربت SQL الذي سنستخدمه لإنشاء تقارير مفيدة. إذا لم تكن لديك خلفية جيّدة حول قواعد بيانات MySQL فأنصحك بقراءة هذا الدّرس أوّلا المتطلبات المسبقة عليك اتباع الخطوات الآتية قبل المتابعة: نزِّل قاعدة بيانات employees، التي تحتوي على ستة جداول تتضمن حوالي 4 ملايين سجل إجماليًا: # wget https://launchpad.net/test-db/employees-db-1/1.0.6/+download/employees_db-full-1.0.6.tar.bz2 # tar xjf employees_db-full-1.0.6.tar.bz2 # cd employees_db ادخل إلى مِحَث MariaDB وأنشِئ قاعدة بيانات باسم employees: # mysql -u root -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 2 Server version: 10.1.14-MariaDB MariaDB Server Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> CREATE DATABASE employees; Query OK, 1 row affected (0.00 sec) استورد قاعدة البيانات إلى خادوم MairaDB كالآتي: MariaDB [(none)]> source employees.sql انتظر دقيقةً أو دقيقتين إلى أن ينتهي استيراد قاعدة البيانات (أبقِ في بالك أننا نتحدث هنا عن أربعة ملايين سجل!). تأكد من أنَّ استيراد قاعدة البيانات قد تمَّ بشكلٍ صحيح بعرض جداولها: MariaDB [employees]> USE employees; Database changed MariaDB [employees]> SHOW TABLES; +---------------------+ | Tables_in_employees | +---------------------+ | departments | | dept_emp | | dept_manager | | employees | | salaries | | titles | +---------------------+ 6 rows in set (0.02 sec) أنشِئ حسابًا مخصصًا لاستخدام قاعدة بيانات employees (اختر اسمًا وكلمة مرور): MariaDB [employees]> CREATE USER empadmin@localhost IDENTIFIED BY 'empadminpass'; Query OK, 0 rows affected (0.03 sec) MariaDB [employees]> GRANT ALL PRIVILEGES ON employees.* to empadmin@localhost; Query OK, 0 rows affected (0.02 sec) MariaDB [employees]> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec) MariaDB [employees]> exit Bye سجِّل دخولك الآن إلى مِحَث MariaDB بالمستخدم empadmin: # mysql -u empadmin -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 4 Server version: 10.1.14-MariaDB MariaDB Server Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> USE employees; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed احرص على تنفيذ جميع الخطوات الست السابقة قبل إكمالك لقراءة هذا الدرس. ترتيب وتقليل عدد السجلات المعروضة جدول الرواتب salaries يحتوي على جميع واردات كل موظف مع تاريخ البداية والنهاية. ربما تود أن تعرض رواتب الموظف emp_no=10001 خلال فترةٍ من الزمن. وهذا سيساعدك على الإجابة عن التساؤلات الآتية: هل حصل على زيادة في راتبه؟ إذا حدث ذلك، فمتى؟ نفِّذ الأمر الآتي لمعرفة ذلك: MariaDB [employees]> SELECT * FROM salaries WHERE emp_no=10001 ORDER BY from_date; +--------+--------+------------+------------+ | emp_no | salary | from_date | to_date | +--------+--------+------------+------------+ | 10001 | 60117 | 1986-06-26 | 1987-06-26 | | 10001 | 62102 | 1987-06-26 | 1988-06-25 | | 10001 | 66074 | 1988-06-25 | 1989-06-25 | | 10001 | 66596 | 1989-06-25 | 1990-06-25 | | 10001 | 66961 | 1990-06-25 | 1991-06-25 | | 10001 | 71046 | 1991-06-25 | 1992-06-24 | | 10001 | 74333 | 1992-06-24 | 1993-06-24 | | 10001 | 75286 | 1993-06-24 | 1994-06-24 | | 10001 | 75994 | 1994-06-24 | 1995-06-24 | | 10001 | 76884 | 1995-06-24 | 1996-06-23 | | 10001 | 80013 | 1996-06-23 | 1997-06-23 | | 10001 | 81025 | 1997-06-23 | 1998-06-23 | | 10001 | 81097 | 1998-06-23 | 1999-06-23 | | 10001 | 84917 | 1999-06-23 | 2000-06-22 | | 10001 | 85112 | 2000-06-22 | 2001-06-22 | | 10001 | 85097 | 2001-06-22 | 2002-06-22 | | 10001 | 88958 | 2002-06-22 | 9999-01-01 | +--------+--------+------------+------------+ 17 rows in set (0.03 sec) لكن ماذا لو أردنا معرفة آخر خمس زيادات؟ يمكننا أن نستخدم ORDER BY form_date DESC. تُشير الكلمة المحجوزة DESC إلى أنَّ الترتيب الذي نريد اتباعه هو الترتيب التنازلي. إضافةً إلى ما سبق، يسمح لنا التعبير LIMIT 5 بإعادة أوّل خمسة سجلات: MariaDB [employees]> SELECT * FROM salaries WHERE emp_no=10001 ORDER BY from_date DESC LIMIT 5; +--------+--------+------------+------------+ | emp_no | salary | from_date | to_date | +--------+--------+------------+------------+ | 10001 | 88958 | 2002-06-22 | 9999-01-01 | | 10001 | 85097 | 2001-06-22 | 2002-06-22 | | 10001 | 85112 | 2000-06-22 | 2001-06-22 | | 10001 | 84917 | 1999-06-23 | 2000-06-22 | | 10001 | 81097 | 1998-06-23 | 1999-06-23 | +--------+--------+------------+------------+ 5 rows in set (0.00 sec) يمكنك أيضًا استخدام ORDER BY على عدِّة حقول، فمثلًا ستُرتِّب الطلبية الآتية النتائج اعتمادًا على تاريخ ولادة الموظف تصاعديًا (وهو الترتيب الافتراضي) ثم عبر اسم الموظف الأخير بترتيبٍ هجائيٍ تنازلي: MariaDB [employees]> SELECT CONCAT(last_name, ', ', first_name) AS Name, gender AS Gender, hire_date AS "Hire date" FROM employees ORDER BY birth_date, last_name DESC LIMIT 10; +--------------------+--------+------------+ | Name | Gender | Hire date | +--------------------+--------+------------+ | Whitcomb, Kiyokazu | M | 1988-07-26 | | Schaad, Ronghao | M | 1988-07-10 | | Remmele, Supot | M | 1989-01-27 | | Pocchiola, Jouni | M | 1985-03-10 | | Kuzuoka, Eishiro | M | 1992-02-12 | | Decaestecker, Moni | M | 1986-10-06 | | Wiegley, Mircea | M | 1985-07-18 | | Vendrig, Sachar | M | 1985-11-04 | | Tsukuda, Cedric | F | 1993-12-12 | | Tischendorf, Percy | M | 1986-11-10 | +--------------------+--------+------------+ 10 rows in set (0.31 sec) يمكنك معرفة المزيد من المعلومات عن LIMIT في الدليل الرسمي. تجميع السجلات، واستخدام MAX و MIN و AVG و ROUND كما ذكرنا سابقًا، الجدول salaries يحتوي على رواتب الموظفين خلال فترات زمنية، وبجانب استخدام LIMIT، يمكننا استخدام الكلمتين المحجوزتين MAX و MIN لتحديد متى كان أعلى راتب وأدنى راتب لموظفين معينين: MariaDB [employees]> SELECT CONCAT(last_name, ', ', first_name) AS Name, MAX(B.salary) AS "Max. salary" FROM employees A JOIN salaries B ON A.emp_no = B.emp_no WHERE A.emp_no IN (10001, 10002, 10003) GROUP BY A.emp_no; +-----------------+-------------+ | Name | Max. salary | +-----------------+-------------+ | Facello, Georgi | 88958 | | Simmel, Bezalel | 72527 | | Bamford, Parto | 43699 | +-----------------+-------------+ 3 rows in set (0.02 sec) MariaDB [employees]> SELECT CONCAT(last_name, ', ', first_name) AS Name, MIN(B.salary) AS "Min. salary" FROM employees A JOIN salaries B ON A.emp_no = B.emp_no WHERE A.emp_no IN (10001, 10002, 10003) GROUP BY A.emp_no; +-----------------+-------------+ | Name | Min. salary | +-----------------+-------------+ | Facello, Georgi | 60117 | | Simmel, Bezalel | 65828 | | Bamford, Parto | 40006 | +-----------------+-------------+ 3 rows in set (0.00 sec) وبناءً على النتائج السابقة، هل تستطيع أن تعرف ماهو ناتج الطلبية الآتية: MariaDB [employees]> SELECT CONCAT(last_name, ', ', first_name) AS Name, ROUND(AVG(B.salary), 2) AS "Avg. salary" FROM employees A JOIN salaries B ON A.emp_no = B.emp_no WHERE A.emp_no IN (10001, 10002, 10003) GROUP BY A.emp_no; +-----------------+-------------+ | Name | Avg. salary | +-----------------+-------------+ | Facello, Georgi | 75388.94 | | Simmel, Bezalel | 68854.50 | | Bamford, Parto | 43030.29 | +-----------------+-------------+ 3 rows in set (0.01 sec) إذا توقعتَ أنَّ الناتج هو المتوسط الحسابي (عبر AVG) للراتب مقرّبًا إلى منزلتين عشريتين (عبر ROUND)، فأنت مصيب. إذا أردنا أن نعرف مجموع رواتب الموظفين وإعادة أوّل خمسة، فيمكننا حينها استعمال الطلبية الآتية: MariaDB [employees]> SELECT emp_no, SUM(salary) AS Salary FROM salaries GROUP BY emp_no ORDER BY Salary DESC LIMIT 5; +--------+---------+ | emp_no | Salary | +--------+---------+ | 109334 | 2553036 | | 43624 | 2492873 | | 66793 | 2383923 | | 237542 | 2381119 | | 47978 | 2374024 | +--------+---------+ 5 rows in set (2.22 sec) جمّعنا الرواتب في الطلبية السابقة عبر الموظف، ثم أجرينا عليها عملية SUM. تجميع المعلومات السابقة في سكربت لحسن الحظ، لا نحتاج إلى تشغيل طلبية تلو أخرى لإنشاء تقرير، وإنما نستطيع إنشاء سكربت فيه سلسلة من تعليمات SQL التي تُعيد جميع النتائج المطلوبة. بعد تنفيذ السكربت، فستظهر جميع النتائج دون تدخل منا، ولنسمِّ الملف باسم maxminavg.sql في مجلد العمل الحالي وفيه المحتويات الآتية: --Select database USE employees; --Calculate maximum salaries SELECT CONCAT(last_name, ', ', first_name) AS Name, MAX(B.salary) AS "Max. salary" FROM employees A JOIN salaries B ON A.emp_no = B.emp_no WHERE A.emp_no IN (10001, 10002, 10003) GROUP BY A.emp_no; --Calculate minimum salaries SELECT CONCAT(last_name, ', ', first_name) AS Name, MIN(B.salary) AS "Min. salary" FROM employees A JOIN salaries B ON A.emp_no = B.emp_no WHERE A.emp_no IN (10001, 10002, 10003) GROUP BY A.emp_no; --Calculate averages, round to 2 decimal places SELECT CONCAT(last_name, ', ', first_name) AS Name, ROUND(AVG(B.salary), 2) AS "Avg. salary" FROM employees A JOIN salaries B ON A.emp_no = B.emp_no WHERE A.emp_no IN (10001, 10002, 10003) GROUP BY A.emp_no; الأسطر التي تبدأ بشرطتين -- هي تعليقات وسيتم تجاهلها. وستُنفَّذ الطلبيات تلو بعضها. ويمكننا تنفيذ هذا السكربت إما من سطر أوامر لينكس: # mysql -u empadmin -p < maxminavg.sql Enter password: Name Max. salary Facello, Georgi 88958 Simmel, Bezalel 72527 Bamford, Parto 43699 Name Min. salary Facello, Georgi 60117 Simmel, Bezalel 65828 Bamford, Parto 40006 Name Avg. salary Facello, Georgi 75388.94 Simmel, Bezalel 68854.50 Bamford, Parto 43030.29 أو من مِحث MariaDB: # mysql -u empadmin -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 4 Server version: 10.1.14-MariaDB MariaDB Server Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> source maxminavg.sql Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed الخلاصة شرحنا في هذه المقالة كيفية استخدام عدِّة دوال في MariaDB لتحسين النتائج التي يُخرجها الاستعلام SELECT. وبعد أن تكتب الاستعلامات لأول مرة، فيمكنك بكل بساطة وضعها داخل سكربت وتنفيذها بسهولة مما سيُقلِّل من الخطأ البشري عند كتابتها. ترجمة -وبتصرّف- للمقال Learn How to Use Several Functions of MySQL and MariaDB – Part 2 لصاحبه Gabriel Cánepa.
  13. تمهيد السلاسل النصية في بايثون هي مجموعةٌ مُنشأةٌ من المحارف المنفصلة والتي تُمثِّل الأحرف والأرقام والفراغات والرموز. ولأنَّ السلسلة النصية هي «سلسلة»، فيمكن الوصول إليها كغيرها من أنواع البيانات المتسلسلة، عبر الفهارس والتقسيم. سيشرح لك هذا الدرس كيفية الوصول إلى السلاسل النصية بفهارسها، وتقسيمها إلى مجموعات من المحارف، وسيُغطي طرائق إحصاء المحارف وتحديد مواضعها. آلية فهرسة السلاسل النصية وكما في نوع البيانات list الذي فيه عناصر مرتبطة بأرقام، فإنَّ كل محرف في السلسلة النصية يرتبط بفهرس معيّن، بدءًا من الفهرس 0. فللسلسلة النصية Sammy Shark!‎ ستكون الفهارس والمحارف المرتبطة بها كالآتي: وكما لاحظت، سيرتبط المحرف S بالفهرس 0، وستنتهي السلسلة النصية بالفهرس 11 مع الرمز !. لاحظ أيضًا أنَّ الفراغ بين كلمتَي Sammy و Shark له فهرسٌ خاصٌ به، وفي مثالنا سيكون له الفهرس 5. علامة التعجب (!) لها فهرسٌ خاصٌ بها أيضًا، وأيّة رموز أو علامات ترقيم أخرى مثل *#$&.;? سترتبط بفهرسٍ مخصصٍ لها. وحقيقة أنَّ المحارف في بايثون لها فهرسٌ خاصٌ بها ستعني أنَّ بإمكاننا الوصول إلى السلاسل النصية وتعديلها كما نفعل مع أنواع البيانات المتسلسلة الأخرى. الوصول إلى المحارف عبر أرقام الفهارس الموجبة يمكننا الحصول على محرف من سلسلة نصية بالإشارة إليه عبر فهرسه. يمكننا فعل ذلك بوضع رقم الفهرس بين قوسين مربعين. سنُعرِّف في المثال الآتي سلسلةً نصيةً ونطبع المحرف المرتبط بالفهرس المذكور بين قوسين مربعين: ss = "Sammy Shark!" print(ss[4]) الناتج: y عندما نُشير إلى فهرسٍ معيّنٍ في سلسلةٍ نصيةٍ، فستُعيد بايثون المحرف الموجود في ذاك الموضع، ولمّا كان المحرف y موجودًا في الفهرس الرابع في السلسلة النصية ss = "Sammy Shark!"‎ فعندما طبعنا ss[4] فظهر الحرف y. الخلاصة: أرقام الفهارس ستسمح لنا بالوصول إلى محارف معيّنة ضمن سلسلة نصية. الوصول إلى المحارف عبر أرقام الفهارس السالبة إذا كانت لديك سلسلةٌ طويلةٌ وأردنا تحديد أحد محارفها لكن انطلاقًا من نهايتها، فعندئذٍ نستطيع استخدام الأرقام السالبة للفهارس، بدءًا من الفهرس ‎-1. لو أردنا أن نستخدم الفهارس السالبة مع السلسلة النصية Sammy Shark!‎، فستبدو كما يلي: يمكننا أن نطبع المحرف r في السلسلة السابقة في حال استخدمنا الفهارس السالبة بالإشارة إلى المحرف الموجود في الفهرس ‎-3 كما يلي: print(ss[-3]) الخلاصة: يمكننا الاستفادة من الفهارس السالبة لو أردنا الوصول إلى محرف في آخر سلسلة نصية طويلة. تقسيم السلاسل النصية يمكننا أن نحصل على مجال من المحارف من سلسلة نصية، فلنقل مثلًا أننا نريد أن نطبع الكلمة Shark فقط، يمكننا فعل ذلك بإنشائنا «لقسم» من السلسلة النصية، والذي هو سلسلةٌ من المحارف الموجودة ضمن السلسلة الأصلية. فالأقسام تسمح لنا بالوصول إلى عدِّة محارف دفعةً واحدة باستعمال مجال من أرقام الفهارس مفصولة فيما بينها بنقطتين رأسيتين [x:y]: print(ss[6:11]) الناتج: Shark عند إنشائنا لقسم مثل [6:11] فسيُمثِّل أوّل رقم مكان بدء القسم (متضمنًا المحرف الموجود عند ذاك الفهرس)، والرقم الثاني هو مكان نهاية القسم (دون تضمين ذاك المحرف)، وهذا هو السبب وراء استخدمنا لرقم فهرس يقع بعد نهاية القسم الذي نريد اقتطاعه في المثال السابق. نحن نُنشِئ «سلسلةً نصيةً فرعيةً» (substring) عندما نُقسِّم السلاسل النصية، والتي هي سلسلةٌ موجودةٌ ضمن سلسلةٍ أخرى. وعندما نستخدم التعبير ss[6:11] فنحن نستدعي السلسلة النصية Shark التي تتواجد ضمن السلسلة النصية Sammy Shark!‎. إذا أردت تضمين نهاية السلسلة (أو بدايتها) في القسم الذي ستُنشِئه، فيمكننا ألّا نضع أحد أرقام الفهارس في string[n:n]. فمثلًا، نستطيع أن نطبع أوّل كلمة من السلسلة ss –أي Sammy– بكتابة ما يلي: print(ss[:5]) فعلنا ذلك بحذف رقم الفهرس قبل النقطتين الرأسيتين، ووضعنا رقم فهرس النهاية فقط، الذي يُشير إلى مكان إيقاف اقتطاع السلسلة النصية الفرعية. لطباعة منتصف السلسلة النصية إلى آخرها، فسنضع فهرس البداية فقط قبل النقطتين الرأسيتين، كما يلي: print(ss[7:]) الناتج: hark! بكتابة فهرس البداية فقط قبل النقطتين الرأسيتين وترك تحديد الفهرس الثاني، فإنَّ السلسلة الفرعية ستبدأ من الفهرس الأول إلى نهاية السلسلة النصية كلها. يمكنك أيضًا استخدام الفهارس السالبة في تقسيم سلسلة نصية، فكما ذكرنا سابقًا، تبدأ أرقام الفهارس السلبية من الرقم ‎-1، ويستمر العد إلى أن نصل إلى بداية السلسلة النصية. وعند استخدام الفهارس السالبة فسنبدأ من الرقم الأصغر لأنه يقع أولًا في السلسلة. لنستخدم فهرسين ذوي رقمين سالبين لاقتطاع جزء من السلسلة النصية ss: print(ss[-4:-1]) الناتج: ark السلسلة النصية «ark» مأخوذة من السلسلة النصية «Sammy Shark!‎» لأنَّ الحرف «a» يقع في الموضع ‎-4 والمحرف k يقع قبل الفهرس ‎-1 مباشرةً. تحديد الخطوة عند تقسيم السلاسل النصية يمكن تمرير معامل ثالث عند تقسيم السلاسل النصية إضافةً إلى فهرسَي البداية والنهاية، وهو الخطوة، التي تُشير إلى عدد المحارف التي يجب تجاوزها بعد الحصول على المحرف من السلسلة النصية. لم نُحدِّد إلى الآن الخطوة في أمثلتنا، إلا أنَّ قيمته الافتراضية هي 1، لذا سنحصل على كل محرف يقع بين الفهرسين. لننظر مرةً أخرى إلى المثال السابق الذي يطبع السلسلة النصية الفرعية «Shark»: print(ss[6:11]) Shark سنحصل على نفس النتائج بتضمين معامل ثالث هو الخطوة وقيمته 1: print(ss[6:11:1]) Shark إذًا، إذا كانت الخطوة 1 فهذا يعني أنَّ بايثون ستُضمِّن جميع المحارف بين فهرسين، وإذا حذفتَ الخطوة فستعتبرها بايثون مساويةً للواحد. أما لو زدنا الخطوة، فسنرى أنَّ بعض المحارف ستهمل: print(ss[0:12:2]) SmySak تحديد الخطوة بقيمة 2 كما في ss[0:12:2] سيؤدي إلى تجاوز حرف بين كل حرفين، ألقِ نظرةً على المحارف المكتوبة بخطٍ عريضٍ: Sammy Shark! لاحظ أنَّ الفراغ الموجود في الفهرس 5 قد أُهمِل أيضًا عندما كانت الخطوة 2. إذا وضعنا قيمةً أكبر للخطوة، فسنحصل على سلسلةٍ نصيةٍ فرعيةٍ أصغر بكثير: print(ss[0:12:4]) Sya حذف الفهرسين وترك النقطتين الرأسيتين سيؤدي إلى إبقاء كامل السلسلة ضمن المجال، لكن إضافة معامل ثالث وهو الخطوة سيُحدِّد عدد المحارف التي سيتم تخطيها. إضافةً إلى ذلك، يمكنك تحديد رقم سالب كخطوة، مما يمكِّنك من كتابة السلسلة النصية بترتيبٍ معكوس إذا استعملتَ القيمة ‎-1: print(ss[::-1]) الناتج: !krahS ymmaS لنجرِّب ذلك مرةً أخرى لكن إذا كانت الخطوة ‎-2: print(ss[::-2]) الناتج: !rh ma في المثال السابق (ss[::-2])، سنتعامل مع كامل السلسلة النصية لعدم وجود أرقام لفهارس البداية والنهاية، وسيتم قلب اتجاه السلسلة النصية لاستخدامنا لخطوةٍ سالبة. بالإضافة إلى أنَّ الخطوة ‎-2 ستؤدي إلى تخطي حرف بين كل حرفين بترتيبٍ معكوس: !krahS[whitespace]ymmaS سيُطبَع الفراغ في المثال السابق. الخلاصة: تحديد المعامل الثالث عند تقسيم السلاسل النصية سيؤدي إلى تحديد الخطوة التي تُمثِّل عدد المحارف التي سيتم تخطيها عند الحصول على السلسلة الفرعية من السلسلة الأصلية. دوال الإحصاء بعد أن تعرفنا على آلية فهرسة المحارف في السلاسل النصية، حان الوقت لمعاينة بعض الدوال التي تُحصي السلاسل النصية أو تعيد أرقام الفهارس. يمكننا أن نستفيد من ذلك بتحديد عدد المحارف التي نريد استقبالها من مدخلات المستخدم، أو لمقارنة السلاسل النصية. ولدى السلاسل النصية –كغيرها من أنواع البيانات– عدِّة دوال تُستخدم للإحصاء. لننظر أولًا إلى الدالة len()‎ التي تُعيد طول أيّ نوع متسلسل من البيانات، بما في ذلك string و lists و tuples و dictionaries. لنطبع طول السلسلة النصية ss: print(len(ss)) 12 طول السلسلة النصية «Sammy Shark!‎» هو 12 محرفًا، بما في ذلك الفراغ وعلامة التعجب. بدلًا من استخدام متغير، فلنحاول مباشرةً تمرير سلسلة نصية إلى الدالة len()‎: print(len("Let's print the length of this string.")) 38 الدالة len()‎ تُحصي العدد الإجمالي من المحارف في سلسلة نصية. إذا أردنا إحصاء عدد مرات تكرار محرف أو مجموعة من المحارف في سلسلة نصية، فيمكننا استخدام الدالة str.count()‎، لنحاول إحصاء الحرف «a» في السلسلة النصية ss = "Sammy Shark!"‎: print(ss.count("a")) 2 يمكننا البحث عن محرفٍ آخر: print(ss.count("s")) 0 صحيحٌ أنَّ الحرف «S» قد ورد في السلسلة النصية، إلا أنَّه من الضروري أن تبقي بذهنك أنَّ بايثون حساسةٌ لحالة الأحرف، فلو أردنا البحث عن حروفٍ معيّنة بغض النظر عن حالتها، فعلينا حينها استخدام الدالة str.lower()‎ لتحويل حروف السلسلة النصية إلى حروفٍ صغيرة أولًا. يمكنك قراءة المزيد من المعلومات عن دالة str.lower()‎ في درس «مقدمة إلى دوال التعامل مع السلاسل النصية في بايثون 3» لنحاول استخدام الدالة str.count()‎ مع سلسلة من المحارف: likes = "Sammy likes to swim in the ocean, likes to spin up servers, and likes to smile." print(likes.count("likes")) الناتج هو 3، حيث تتواجد مجموعة المحارف «likes» ثلاث مرات في السلسلة النصية الأصلية. يمكننا أيضًا معرفة موقع الحرف أو مجموعة الحروف في السلسلة النصية، وذلك عبر الدالة str.find()‎، وسيُعاد موضع المحرف بناءً على رقم فهرسه. يمكننا أن نعرف متى يقع أوّل حرف «m» في السلسلة النصية ss كالآتي: print(ss.find("m")) 2 أوّل مرة يقع فيها الحرف «m» في الفهرس 2 من السلسلة «Sammy Shark!‎»، يمكنك أن تراجع بداية هذا الدرس لرؤية جدول يبيّن ارتباطات المحارف مع فهارسها في السلسلة السابقة. لنرى الآن مكان أوّل ظهور لمجموعة المحارف «likes» في السلسلة likes: print(likes.find("likes")) 6 أوّل مرة تظهر فيها السلسلة «likes» هي في الفهرس 6، أي مكان وجود الحرف l من likes. ماذا لو أردنا أن نعرف موضع ثاني تكرار للكلمة «likes»؟ نستطيع فعل ذلك بتمرير معاملٍ ثانٍ إلى الدالة str.find()‎ الذي سيجعلها تبدأ بحثها من ذاك الفهرس، فبدلًا من البحث من أوّل السلسلة النصية سنبحث انطلاقًا من الفهرس 9: print(likes.find("likes", 9)) 34 بدأ البحث في هذا المثال من الفهرس 9، وكانت أوّل مطابقة للسلسلة النصية «likes» عند الفهرس 34. إضافةً إلى ذلك، يمكننا تحديد نهاية إلى مجال البحث بتمرير معامل ثالث. وكما عند تقسيم السلاسل النصية، يمكننا استخدام أرقام الفهارس السالبة للعد عكسيًا: print(likes.find("likes", 40, -6)) 64 يبحث آخر مثال عن موضع السلسلة النصية «likes» بين الفهرس 40 و ‎-6، ولمّا كان المعامل الأخير هو رقم سالب، فسيبدأ العد من نهاية السلسلة الأصلية. دوال الإحصاء مثل len()‎ و str.count()‎ و str.find()‎ مفيدةٌ في تحديد طول السلسلة النصية وعدد حروفها وفهارس ورود محارف معيّنة فيها. الخلاصة ستمنحنا القدرة على تحديد محارف بفهارسها أو تقسيم سلسلة نصية أو البحث فيها مرونةً عاليةً عند التعامل مع السلاسل النصية. ولأنَّ السلاسل النصية هي نوعٌ من أنواع البيانات المتسلسلة (كنوع البيانات list)، فيمكننا الوصول إلى عناصرها عبر فهارس خاصة. ترجمة -وبتصرّف- للمقال How To Index and Slice Strings in Python 3 لصاحبته Lisa Tagliaferri.
  14. تمهيد لدى بايثون عدِّة دوال مبنية فيها للتعامل مع السلاسل النصية. تسمح هذه الدوال لنا بتعديل وإجراء عمليات على السلاسل النصية بسهولة. يمكنك أن تتخيل الدوال على أنها «أفعال» يمكننا تنفيذها على عناصر موجودة في الشيفرة. الدوال المبنية في اللغة هي الدوال المُعرَّفة داخل لغة بايثون وهي جاهزة مباشرةً للاستخدام. سنشرح في هذا الدرس مختلف الدوال التي نستطيع استخدامها للتعامل مع السلاسل النصية في بايثون 3. جعل السلاسل النصية بأحرف كبيرة أو صغيرة الدالتان str.upper()‎ و str.lower()‎ ستُعيدان السلسلة النصية بعد تحويل حالة جميع أحرفها الأصلية إلى الأحرف الكبيرة أو الصغيرة (على التوالي وبالترتيب). ولعدم قدرتنا على تعديل السلسلة النصية بعد إنشائها، فستُعاد سلسلةٌ نصيةٌ جديدةٌ. لن تُعدَّل أيّة محارف غير لاتينية في السلسلة النصية الأصلية وستبقى على حالها. لنحوِّل السلسلة النصية Sammy Shark إلى أحرفٍ كبيرة: ss = "Sammy Shark" print(ss.upper()) الناتج: SAMMY SHARK لنحوِّلها الآن إلى أحرفٍ صغيرة: print(ss.lower()) وسينتج: sammy shark ستُسهِّل الدالتان str.upper()‎ و str.lower()‎ عملية التحقق من مساواة سلسلتين نصيتين لبعضهما أو لمقارنتهما وذلك عبر توحيد حالة الأحرف. فلو كتب المستخدم اسمه بأحرفٍ صغيرةٍ فسنستطيع أن نتأكد إن كان مُسجَّلًا في قاعدة البيانات بمقارنته بعد تحويل حالة أحرفه. الدوال المنطقية تتوفر في بايثون عدِّة دوال التي ستتحقق من القيم المنطقية (Boolean). هذه الدوال مفيدةٌ عند إنشائنا للنماذج التي يجب على المستخدمين ملأها؛ فمثلًا، إذا سألنا المستخدم عن الرمز البريدي وأردنا أن نقبل السلاسل النصية التي تحتوي أرقامًا فقط، أو عندما نسأله عن اسمه فسنقبل سلسلةً نصيةً تحتوي حروفًا فقط. هنالك عددٌ من الدوال التي تُعيد قيمًا منطقيةً: str.isalnum()‎: تُعيد true إذا احتوت السلسلة النصية على أرقام وأحرف فقط (دون رموز). str.isalpha()‎: تُعيد true إذا احتوت السلسلة النصية على أحرف فقط (دون أرقام أو رموز). str.islower()‎: تُعيد true إذا كانت جميع أحرف السلسلة النصية صغيرة. str.isnumeric()‎: تُعيد true إذا احتوت السلسلة النصية على أرقام فقط. str.isspace()‎: تُعيد true إذا لم تحتوي السلسلة النصية إلا على الفراغات. str.istitle()‎: تُعيد true إذا كانت حالة أحرف السلسلة النصية كما لو أنها عنوان (أي أنَّ أوّل حرف من كل كلمة كبير، والبقية صغيرة). str.isupper()‎: تُعيد true إذا كانت جميع أحرف السلسلة النصية كبيرة. لننظر إلى بعضها عمليًا: number = "5" letters = "abcdef" print(number.isnumeric()) print(letters.isnumeric()) الناتج: True False استخدام الدالة str.isnumeric()‎ على السلسلة النصية 5 سيُعيد القيمة True، بينما استخدام نفس الدالة على السلسلة النصية abcdef سيُعيد False. وبشكلٍ مماثل، يمكننا معرفة إن كانت حالة الأحرف في سلسلةٍ نصيةٍ كما لو أنها عنوان، أو أنها كبيرة أو صغيرة. لنُنشِئ بدايةً بعض السلاسل النصية: movie = "2001: A SAMMY ODYSSEY" book = "A Thousand Splendid Sharks" poem = "sammy lived in a pretty how town" لنجرّب الدوال المنطقية لمعرفة الناتج (سنعرض كل دالتين وناتجهما تحتهما): print(movie.islower()) print(movie.isupper()) False True print(book.istitle()) print(book.isupper()) True False print(poem.istitle()) print(poem.islower()) False True ستساعدنا معرفة إن كانت أحرف السلسلة النصية بحالة صغيرة أو كبيرة أو كأنها عنوان في تصنيف البيانات تصنيفًا سليمًا، وتوفِّر لنا الفرصة لتوحيد طريقة تخزين البيانات بالتحقق من حالة أحرفها ثم تعديلها وفقًا لذلك. الدوال المنطقية التي تعمل على السلاسل النصية مفيدةٌ أيضًا عندما نريد التحقق إن حقَّقَت مدخلات المستخدم شروطًا معيّنة. الدوال join()‎ و split()‎ و replace()‎ توفِّر الدوال str.join()‎ و str.split()‎ و str.replace()‎ إمكانياتٍ إضافيةً لتعديل السلاسل النصية في بايثون. الدالة str.join()‎ تجمع سلسلتين نصيتين مع بعضهما، لكنها تفعل ذلك بتمرير إحداها إلى الأخرى. لنُنشِئ سلسلةً نصيةً: balloon = "Sammy has a balloon." لنستخدم الآن الدالة str.join()‎ لإضافة فراغات إلى تلك السلسلة النصية كالآتي: " ".join(balloon) إذا طبعنا الناتج: print(" ".join(balloon)) فسنجد أنَّ السلسلة النصية الجديدة هي السلسلة الأولى لكن بين كل حرفين فراغ: S a m m y h a s a b a l l o o n . يمكننا أيضًا استخدام الدالة str.join()‎ لإنشاء مقلوب سلسلة نصية: print("".join(reversed(balloon))) .noollab a sah ymmaS لم نرغب في إضافة أيّة سلسلة نصية إلى أخرى، لذا أبقينا على السلسلة النصية فارغةً دون محتوى داخلها. الدالة str.join()‎ مفيدةٌ أيضًا لجمع قائمة (list) من السلاسل النصية وإخراجها إلى سلسلةٍ وحيدة. لنُنشِئ سلسلة نصية يُفصَل بين كلماتها بفاصلة من القائمة الآتية: print(",".join(["sharks", "crustaceans", "plankton"])) sharks,crustaceans,plankton إذا أردتَ وضع فاصلة ثم فراغ بين القيم في المثال السابق، فيمكنك أن تُعيد كتابة التعبير البرمجي السابق لإضافة فراغ بعد الفاصلة كما يلي: ", ".join(["sharks", "crustaceans", "plankton"]). وكما نستطيع جمع السلاسل النصية مع بعضها بعضًا، نستطيع أيضًا تجزئتها، وذلك عبر الدالة str.split()‎: print(balloon.split()) ['Sammy', 'has', 'a', 'balloon.'] الدالة str.split()‎ ستُعيد قائمة (list) تحتوي سلاسل نصية كانت مفصولةً بالفراغات في السلسلة النصية الأصلية إذا لم يُمرَّر معاملٌ لتحديد محرف الفصل. يمكنك أيضًا استخدام الدالة str.split()‎ لحذف أجزاء معيّنة من السلسلة النصية الأصلية، فلنحاول مثلًا حذف الحرف a: print(balloon.split("a")) ['S', 'mmy h', 's ', ' b', 'lloon.'] حُذِفَ الحرف a من السلسلة النصية وأصبح الناتج مقسومًا عند كل ورود للحرف a مع الإبقاء على الفراغات. تأخذ الدالة str.replace()‎ سلسلةً نصيةً وتُعيد نسخةً محدَّثةً منها بعد إجراء بعض عمليات الاستبدال عليها. لنفترض أنَّ البالون الذي يملكه سامي قد ضاع، ولعدم امتلاك سامي للبالون في الوقت الراهن، فسنُبدِّل الكلمة "has" إلى "had": print(balloon.replace("has","had")) أوّل سلسلة نصية داخل أقواس الدالة replace()‎ هي السلسلة النصية التي نريد استبدالها، والسلسلة النصية الثانية هي السلسلة التي نريد وضعها بدلًا من الأولى. ناتج تنفيذ السطر السابق هو: Sammy had a balloon. استخدام دوال تعديل السلاسل النصية مثل str.join()‎ و str.split()‎ و str.replace سيمنحك تحكمًا كبيرًا بمعالجة السلاسل النصية في بايثون. الخلاصة لقد تعلمنا في هذا الدرس بعض الدوال المُضمَّنة في لغة بايثون لمعالجة النصوص والتعامل معها ومعرفة بعض خصائصها. ترجمة -وبتصرّف- للمقال An Introduction to String Methods in Python 3 لصاحبته Lisa Tagliaferri
  15. تُحدِّد خصائص مصفوفات RAID بطريقة الضبط والعلاقة بين الأقراص، وهذا يُسمى «مستوى RAID» ‏(RAID level). أكثر مستويات RAID شيوعًا هي: RAID 0 يدمج مستوى RAID 0 بين جهازين أو أكثر عبر توزيع البيانات عليها، وكما ذكرنا في الدرس السابق (مقدمة إلى اصطلاحات ومفاهيم RAID ) ، التوزيع (striping) هو الآلية التي تُقسِّم البيانات إلى أجزاء (chunks) ثم تكتب تلك الأجزاء بشكلٍ تناوبي على كل قرص في المصفوفة. الفائدة من اتباع هذه الطريقة هي أنَّ البيانات موزَّعة، ويمكن الاستفادة من كل قرص لإجراء عمليات القراءة والكتابة؛ وبالتالي سيكون الأداء النظري لمصفوفة RAID 0 هو حاصل ضرب أداء أحد الأقراص بالعدد الكلي لها (الأداء العملي والحقيقي سيكون أقل من ذلك). ميزة أخرى هي أنَّ المساحة القابلة للاستخدام من المصفوفة هي مجموع مساحات الأقراص المُشكِّلة لها. صحيحٌ أنَّ هذا المستوى يوفِّر أداءً رائعًا، إلا أنَّه يعاني من سلبيات ذات أهمية كبيرة. فلمّا كانت البيانات ستُقسَّم بين عدد من الأقراص في المصفوفة، فإن فشل أحد الأجهزة سيؤدي إلى توقف كامل المصفوفة عن العمل وستفقد جميع البيانات. وعلى النقيض من معظم مستويات RAID الأخرى، لا نستطيع إعادة بناء مصفوفات RAID 0، إذ لا توجد مجموعة من الأقراص التي تحتوي معلومات كافية عن محتوى المصفوفة للمساعدة في إعادة بنائها. إذا كنتَ ستستخدم مصفوفات RAID 0، فسيصبح أخذ نسخ احتياطية أمرًا شديد الأهمية، حيث ستفشل المصفوفة في حال فشل أحد الأقراص المكوِّنة لها. RAID 1 مستوى RAID 1 هو المستوى الذي يُنشِئ نسخةً انعكاسيةً بين جهازَين أو أكثر. أي أنَّ كل المعلومات التي تُكتَب على المصفوفة ستُكتَب بدورها مرتين في كلا القرصين المشكلَين للمجموعة. وهذا يعني أنَّ كل قرص سيحتوي على كامل البيانات الموجودة في المصفوفة، وهذا يوفِّر قدرةً تعويضيةً (redundancy) في حال فشل أحد الأقراص. ستتمكن من الوصول إلى بياناتك في مصفوفات RAID 1 في حال بقي قرصٌ وحيدٌ في المصفوفة سليمًا ويعمل دون مشاكل، ويمكن إعادة بناء المصفوفة باستبدال الأقراص التالفة، ومن ثم ستُستخدَم بقية الأقراص السليمة لنسخ البيانات إلى القرص الجديد. هنالك بعض المساوئ لهذا الضبط، فسرعة القراءة النظرية –كما في مستوى RAID 0– يمكن أن تُحسَب بضرب سرعة القراءة لأحد الأقراص بعددها. لكن سرعة الكتابة النظرية القصوى هي سرعة أبطأ قرص في المصفوفة، وذلك لأنَّ كل البيانات يجب أن تُكتَب على كل قرص في المصفوفة. إضافةً إلى ما سبق، المساحة التخزينية الكلية للمصفوفة ستكون مساويةً لمساحة أصغر قرص، لذا إذا كانت لدينا مصفوفة RAID 1 فيها قرصين لهما نفس القدرة التخزينية، فستكون القدرة التخزينية الإجمالية للمصفوفة مساوية لمساحة أحدهما. وإضافة أقراص أخرى سيزيد من عدد النسخ التعويضية للبيانات، ولن يزيد في المساحة المتوافرة. RAID 5 يملك RAID 5 بعض ميزات مستويي RAID السابقَين، لكن له أداءٌ مختلفٌ وسلبياتٌ مختلفة. ففي مستوى RAID 5، ستوزَّع البيانات على الأقراص بنفس الطريقة التي يتبعها المستوى RAID 0، لكن لكل جزء من البيانات التي تُكتَب على المصفوفة فستُكتَب معلومات parity على أحد الأقراص، والتي هي قيمةٌ محسوبةٌ رياضيًا التي تُستخدَم لتصحيح الأخطاء وإعادة بناء معلومات المصفوفة. سيتم تغيير القرص الذي سيستلم معلومات parity المحسوبة (بدلًا من البيانات الحقيقية) بعد كتابة كل جزء من البيانات. لهذا المستوى بعض المزيات المهمة. فمثل بقية المستويات التي توزِّع البيانات على أكثر من قرص، فإن أداء القراءة سيزداد نتيجةً للقدرة على القراءة من عدِّة أقراص معًا. ويمكن لمصفوفات RAID 5 أن تعمل حتى لو فشل أحد الأقراص في المصفوفة. حيث ستسمح معلومات parity بإكمال إعادة بناء البيانات إن حدث ذلك؛ وذلك لأنَّ معلومات parity موزعة (بعض مستويات RAID الأقل شيوعًا تستخدم قرصًا مخصصًا لمعلومات parity)، حيث يملك كل قرصٍ قدرًا متساويًا من معلومات parity. وصحيحٌ أنَّ المساحة التخزينية القابلة للاستخدام في مصفوفات RAID 1 مساوية لمساحة أحد الأقراص (وذلك لأنَّ جميع الأقراص فيها نسخ متماثلة من البيانات)، لكن في RAID 5، يمكن تحقيق القدرة التعويضية بالاستغناء عن المساحة التخزينية لقرصٍ وحيد، فمثلًا إذا كان لدينا أربعة أقراص 100G في مصفوفة RAID 5 فسينتج عنها مساحة تخزينية تساوي 300G (أما 100G الضائعة فستُستَخدم لتخزين معلومات parity). وكما في المستويات الأخرى، هنالك سلبياتٌ لمصفوفات RAID 5 التي يجب أخذها بعين الاعتبار. فقد تؤدي إلى تقليل أداء النظام نتيجةً لحساب معلومات parity ديناميكيًا طوال الوقت، وهذا قد يؤثر على الأداء عند كل عملية كتابة على المصفوفة. وإذا فشل أحد الأقراص في المصفوفة وأصبحت المصفوفة ذات حالة منخفضة، فسيؤدي ذلك أيضًا إلى بطئ شديد عند القراءة منها (لأنَّ البيانات الناقصة ستُحسَب من بقية الأقراص). بالإضافة إلى ذلك، عند محاولة إصلاح المصفوفة بعد استبدال القرص التالف، فيجب قراءة كل قرص بأكمله واستخدام المعالج لحساب البيانات الناقصة لإعادة بناء المصفوفة. وهذا قد يُجهِد بقية الأقراص، مما يؤدي أحيانًا إلى فشلٍ إضافيٍ في أحد الأقراص، مما يؤدي في النهاية إلى فقدان البيانات. RAID 6 يَستخدم مستوى RAID 6 بنيةً قريبةً من مستوى RAID 5، لكن مع مضاعفة كمية معلومات parity. هذا يعني أنَّ المصفوفة ستصمد حتى لو حدث عطب بقرصين. وهذه ميزة مهمة جدًا لزيادة احتمال حدوث عطب آخر أثناء عملية إعادة بناء المصفوفة عند حدوث خطأ. وفي هذا المستوى –كغيره من المستويات التي تستخدم التوزيع– سيكون أداء القراءة جيد إجمالًا. وجميع ميزات RAID 5 تنطبق أيضًا على RAID 6. أما مساوئ هذا المستوى، فهي استخدام مساحة تخزينية أكبر لمعلومات parity، وهذا يعني أنَّ المساحة الإجمالية للمصفوفة هي مجموع مساحات جميع الأقراص المكوِّنة لها منقوصًا منها مساحة قرصين. إضافةً إلى أنَّ العمليات الحسابية اللازمة لإنشاء معلومات parity لمستوى RAID 6 أكثر تعقيدًا من RAID 5، مما يعني أداءً أسوأ للكتابة مقارنةً مع RAID 5. يعاني مستوى RAID 6 من نفس المشاكل التي تحدث في RAID 5 عندما تصبح المصفوفة بحالة منخفضة، لكن وجود قرص إضافي للتعويض سيقل احتمال حدوث مشاكل إضافية تؤدي إلى حذف جميع البيانات عند عمليات إعادة البناء. RAID 10 يمكن تطبيق مستوى RAID 10 بعدِّة طرائق، والتي ستؤثِّر على خصائصه: مستويا 1 و 0 متشعبان تقليديًا، يُشير مستوى RAID 10 إلى مستوى متشعب، والذي يُنشَأ بضبط مصفوفتَي RAID 1 أو أكثر أولًا، ثم استخدام تلك المصفوفات كمكونات لإنشاء مصفوفة RAID 0 موزّعة. ويدعى هذا المستوى حاليًا أحيانًا باسم RAID 1+0 للإشارة إلى هذه العلاقة. وبسبب تصميم هذا المستوى، فإن العدد الأدنى للأقراص هو أربعة وذلك لإنشاء مصفوفة RAID 1+0 (أي مستوى RAID 0 يستعمل مصفوفتَي RAID 1 تتألف كلٌ منهما على قرصين). تملك مصفوفات RAID 1+0 أداءً عاليًا شبيهًا بمصفوفات RAID 0، لكن بدلًا من الاعتماد على أقراص مفردة لتوزيع البيانات، فسيتم استخدام مصفوفة منسوخة نسخًا انعكاسيًا (mirrored array)، مما يعني توفير قدرة تعويضية. يمكن أن يتحمل هذا النوع من الضبط أيّة أخطاء ومشاكل في الأقراص لطالما بقي قرصٌ وحيدٌ يعمل في كل مصفوفة RAID 1 مُشكِّلة للمصفوفة النهائية. يمكن أن يتحمل هذا المستوى عددًا مختلفًا من المشاكل اعتمادًا على مكان وقوعها. ولأنَّ مصفوفات RAID 1+0 توفِّر أداءً عاليًا وقدرةً تعويضيةً، فهي خيارٌ ممتازٌ إن لم تكن مقيدًا بعددٍ معيّنٍ من الأقراص. RAID 10 عبر mdadm لدى برمجية mdadm في لينكس نسخةٌ خاصةٌ بها من RAID 10، والتي تحمل نفس فوائد RAID 1+0 لكن تُغيّر في طريقة تنفيذها لكي تكون مرنةً أكثر وتوفِّر ميزاتٍ إضافيةٍ. وكما في مستوى RAID 1+0، مستوى RAID 10 في mdadm يسمح بتوزيع البيانات ونسخها أكثر من مرة. لكن الأقراص لن تُرتَّب كمجموعتَين منسوختين نسخًا انعكاسيًا، وإنما سيقرِّر مدير النظام عدد النسخ التي ستُكتَب على المصفوفة. ستُجزَّأ البيانات وتُكتَب على المصفوفة بأكثر من نسخة، مع الحرص على أن تُكتَب كل نسخة من البيانات على قرصٍ فيزيائيٍ منفصل. والنتيجة النهائية هو وجود نفس العدد من النسخ، لكن طريقة بناء المصفوفة تختلف (أي لن يكون فيها «تشعب»). هذا الضبط لمستوى RAID 10 له ميزات تجعله متفوقًا عن RAID 1+0، فلأنه لا يعتمد على تشعّب المصفوفات فذلك يعني أننا نستطيع استخدام رقم فردي من الأقراص ويمكن أيضًا تقليل عدد الأقراص الأدنى (ليصبح 3 فقط). يمكن أيضًا ضبط عدد النسخ. وستصبح الإدارة أبسط لأنك ستحتاج إلى إدارة مصفوفة وحيدة، ويمكنك استخدام أقراص بديلة (كقطع غيار) لكامل المصفوفة، بدلًا من إمكانية استخدامها لمصفوفة متشعبة وحيدة. الخلاصة أكثر مستوى من مستويات RAID يناسب خادومك يعتمد تمامًا على حالات استخدامك له وعلى هدفك من المصفوفة. التكلفة والقيود التي يضعها العتاد سيؤثران أيضًا على عملية تقرير مستوى RAID المناسب. ترجمة -وبتصرّف- للمقال An Introduction to RAID Terminology and Concepts لصاحبه Justin Ellingwood