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

Ola Abbas

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

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

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

كل منشورات العضو Ola Abbas

  1. أصبحنا الآن جاهزين لإضافة الشيفرة البرمجية التي تعرض أول صفحة كاملة، وهي الصفحة الرئيسية لموقع المكتبة المحلية LocalLibrary. ستعرض الصفحة الرئيسية عددًا من سجلاتنا لكل نوع نموذج وتوفر روابط التنقّل الجانبية إلى صفحاتنا الأخرى. سنكتسب في هذا المقال خبرةً عمليةً في كتابة روابط Maps وعروض Views عناوين URL الأساسية للحصول على السجلات من قاعدة البيانات واستخدام القوالب. المتطلبات الأساسية: الاطلاع على مقال مدخل إلى إطار عمل الويب جانغو، وأكمل موضوعات المقالات السابقة (بما في ذلك مقال موقع مدير جانغو). الهدف: تعلم كيفية إنشاء روابط وعروض بسيطة لعناوين URL (إذ لا توجد بيانات مُشفَّرة في عنوان URL) والحصول على البيانات من النماذج وإنشاء القوالب. عرّفنا نماذجنا وأنشأنا بعض سجلات المكتبة الأولية للعمل معها، لذا حان الوقت الآن لكتابة الشيفرة البرمجية التي تقدم هذه المعلومات للمستخدمين. أول شيء يجب علينا تطبيقه هو تحديد المعلومات التي نريد عرضها في صفحاتنا وتعريف عناوين URL المراد استخدامها لإعادة تلك الموارد، ثم سننشئ رابط Mapper عنوان URL والعروض والقوالب لعرض الصفحات. يوضح الشكل الآتي تدفق البيانات الرئيسي والمكونات المطلوبة عند معالجة طلبات واستجابات HTTP. بما أننا قدّمنا النموذج مسبقًا، فإن المكونات الرئيسية التي سننشئها هي: روابط عناوين URL لتوجيه عناوين URL المدعومة (وأي معلومات مُشفَّرة في عناوين URL) إلى دوال العرض المناسبة. دوال العرض للحصول على البيانات المطلوبة من النماذج وإنشاء صفحات HTML التي تعرض البيانات وإعادة الصفحات إلى المستخدم لعرضها في المتصفح. القوالب المراد استخدامها عند عرض البيانات في العروض. لدينا خمس صفحات لعرضها، وهي معلومات كثيرة جدًا لتوثيقها في مقال واحد، لذا سيركز هذا المقال على كيفية تقديم الصفحة الرئيسية وسنغطي الصفحات الأخرى في مقال لاحق، إذ يجب أن يمنحك ذلك فهمًا جيدًا شاملًا لكيفية عمل روابط عناوين URL والعروض والنماذج عمليًا. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تعريف مورد عناوين URLs بما أن هذه النسخة من موقع المكتبة المحلية LocalLibrary للقراءة فقط للمستخدمين النهائيين، فيجب توفير صفحة هبوط للموقع، أي صفحة رئيسية وصفحات تعرض عروض القائمة والعروض التفصيلية للكتب والمؤلفين. عناوين URLs التي سنحتاجها لصفحاتنا، هي: catalog/‎: الصفحة الرئيسية (الفهرس index). catalog/books/‎: قائمة بجميع الكتب. catalog/authors/‎: قائمة بجميع المؤلفين. catalog/book/<id>‎: العرض التفصيلي لكتاب معين مع حقل المفتاح الرئيسي <id> (الافتراضي)، فمثلًا سيكون عنوان URL للكتاب الثالث المُضاف إلى القائمة هو ‎/catalog/book/3. catalog/author/<id>‎: العرض التفصيلي للمؤلف المحدد مع حقل المفتاح الرئيسي <id>، فمثلًا سيكون عنوان URL الخاص بالمؤلف الحادي عشر المُضاف إلى القائمة هو ‎/catalog/author/11. ستعرض عناوين URLs الثلاثة الأولى صفحة الفهرس وقائمة الكتب وقائمة المؤلفين. لا تشفّر عناوين URLs هذه أيّ معلومات إضافية، وستبقى الاستعلامات التي تجلب البيانات من قاعدة البيانات هي نفسها دائمًا، ولكن ستعتمد النتائج التي تعيدها الاستعلامات على محتويات قاعدة البيانات. بينما سيعرض عنوانا URL الأخيرين معلومات مُفصَّلة حول كتاب أو مؤلف معين. تشفّر عناوين URLs هذه هوية العنصر المُراد عرضه (يمثله <id>). سيستخرج رابط عنوان URL المعلومات المُشفَّرة ويمررها إلى العرض، وسيحدد العرض ديناميكيًا المعلومات التي سنحصل عليها من قاعدة البيانات، وسنستخدم مجموعة واحدة من ربط عنوان URL والعرض والقالب للتعامل مع جميع الكتب (أو المؤلفين) من خلال تشفير المعلومات في عنوان URL. ملاحظة: يمكنك باستخدام جانغو بناء عناوين URLs كما تشاء، إذ يمكنك تشفير المعلومات في متن عنوان URL كما هو موضح سابقًا، أو تضمين معاملات GET في عنوان URL مثل ‎/book/?id=6. يجب أن تبقى عناوين URLs نظيفة ومنطقية وقابلة للقراءة على النحو الذي توصي به منظمة W3C بغض النظر عن الطريقة التي تستخدمها. يوصي توثيق جانغو بتشفير المعلومات في متن عنوان URL لتحقيق تصميم أفضل لعنوان URL. سنوضح في باقي هذا المقال كيفية إنشاء صفحة الفهرس أو الصفحة الرئيسية. إنشاء صفحة الفهرس الصفحة الأولى التي سننشئها هي صفحة الفهرس (catalog/‎)، إذ ستتضمن صفحة الفهرس بعض شيفرة HTML الثابتة إلى جانب "عدادات" ناتجة عن السجلات المختلفة في قاعدة البيانات. يمكن إنجاز ذلك من خلال إنشاء ربطٍ لعناوين URLs وعرض وقالب. ملاحظة: يستحق الأمر مزيدًا من الاهتمام في هذا القسم، إذ تنطبق معظم هذه المعلومات على الصفحات الأخرى التي سننشئها. ربط Mapping عناوين URLs حدّثنا الملف locallibrary/urls.py عندما أنشأنا موقع الويب الهيكلي للتأكد من أنه كلما اُستلِم عنوان URL له البادئة catalog/‎، فسيعالج الوحدة catalog.urls من النوع URLConf السلسلة الفرعية المتبقية. يتضمن جزء الشيفرة البرمجية التالي من الملف locallibrary/urls.py الوحدة catalog.urls: urlpatterns += [ path('catalog/', include('catalog.urls')), ] ملاحظة: إذا قابل جانغو دالة الاستيراد django.urls.include()‎، فسيقسم سلسلة عنوان URL عند المحرف النهائي المحدد ويرسل السلسلة الفرعية المتبقية إلى وحدة URLconf المُضمَّنة لمزيدٍ من المعالجة. أنشأنا أيضًا ملفًا بديلًا لوحدة URLConf بالاسم ‎/catalog/urls.py، لذا أضِف الأسطر التالية إلى هذا الملف: urlpatterns = [ path('', views.index, name='index'), ] تعرّف الدالة path()‎ ما يلي: نمط عنوان URL وهو سلسلة نصية فارغة: '' (سنناقش أنماط URL بالتفصيل عند العمل على العروض Views الأخرى). دالة عرض ستُستدعَى عند اكتشاف نمط URL: هي الدالة views.index التي تكون في الملف views.py بالاسم index()‎. تحدد الدالة path()‎ أيضًا المعامل name، وهو معرّف فريد لهذا الربط لعنوان URL. يمكنك استخدام الاسم لعكس الرابط Mapper، أي لإنشاء عنوان URL ديناميكيًا الذي يؤشّر إلى المورد الذي صُمِّم الرابط للتعامل معه، فمثلًا يمكننا استخدام معامل الاسم للربط بصفحتنا الرئيسية من أيّ صفحة أخرى من خلال إضافة الارتباط التالي في القالب: <a href="{% url 'index' %}">Home</a>. ملاحظة: يمكننا كتابة الارتباط بصورة ثابتة كما هو الحال في <a href="/catalog/">Home</a>، ولكن إذا غيّرنا نمط صفحتنا الرئيسية إلى ‎/catalog/index مثلًا، فلن تُربَط القوالب بصورة صحيحة، لذا يُعَد استخدام ربط عنوان URL المعكوس أفضل. العرض القائم على الدوال يُعَد العرض View دالةً تعالج طلب HTTP، وتجلب البيانات المطلوبة من قاعدة البيانات، وتعرض البيانات في صفحة HTML باستخدام قالب HTML، ثم تعيد صفحة HTML المُنشَأة في استجابة HTTP لعرض الصفحة للمستخدم. يتّبع عرض الفهرس هذا النموذج، إذ يجلب معلومات حول عدد سجلات Book و BookInstance و Author سجلات BookInstance المتوفرة في قاعدة البيانات، وتمرر هذه المعلومات إلى قالب لعرضها. افتح الملف catalog/views.py ولاحظ أن الملف يستورد دالة render()‎ المُختصَرة لإنشاء ملف HTML باستخدام قالب وبيانات: from django.shortcuts import render # أنشئ عروضك هنا الصق الأسطر التالية في نهاية الملف: from .models import Book, Author, BookInstance, Genre def index(request): """View function for home page of site.""" # توليد عدّادات من بعض الكائنات الرئيسية num_books = Book.objects.all().count() num_instances = BookInstance.objects.all().count() # ‫الكتب المتوفرة (status = 'a'‎) num_instances_available = BookInstance.objects.filter(status__exact='a').count() # تُضمَّن‫ 'all()‎' افتراضيًا num_authors = Author.objects.count() context = { 'num_books': num_books, 'num_instances': num_instances, 'num_instances_available': num_instances_available, 'num_authors': num_authors, } # ‫عرض قالب HTML وهو index.html مع البيانات الموجودة في متغير السياق return render(request, 'index.html', context=context) يستورد السطر الأول أصناف النموذج التي سنستخدمها للوصول إلى البيانات في جميع عروضنا، إذ يجلب الجزء الأول من دالة العرض عدد السجلات باستخدام السمة objects.all()‎ في أصناف النموذج، ويحصل على قائمة بكائنات BookInstance التي لها القيمة "a" (متاح Available) في حقل الحالة status. يمكنك العثور على مزيد من المعلومات حول كيفية الوصول إلى بيانات النموذج في قسم البحث عن السجلات من مقال استخدام النماذج. نستدعي الدالة render()‎ في نهاية دالة العرض لإنشاء صفحة HTML وإعادة الصفحة بوصفها استجابة. تغلّف هذه الدالة المُختصَرة عددًا من الدوال الأخرى لتبسيط حالة الاستخدام الشائعة، وتقبل الدالة render()‎ المعاملات التالية: كائن الطلب request الأصلي وهو HttpRequest. قالب HTML مع عناصر بديلة للبيانات. متغير context وهو قاموس بايثون الذي يحتوي على البيانات المطلوب إدخالها في العناصر البديلة. سنتحدث أكثر عن القوالب ومتغير context في القسم التالي، ولنبدأ الآن في إنشاء قالبنا لنتمكّن من عرض شيء ما للمستخدم. القالب Template القالب هو ملف نصي يعرّف بنية الملف أو تخطيطه، مثل صفحة HTML، ويستخدم عناصر بديلة لتمثيل المحتوى الفعلي. سيبحث تطبيق جانغو الذي أنشأه تطبيق البدء Startapp (مثل التطبيق الهيكلي) عن قوالب في مجلد فرعي له الاسم 'templates' لتطبيقاتك، فمثلًا ستتوقّع الدالة render()‎ في عرض الفهرس الذي أضفناه للتو العثور على الملف index.html في المجلد /catalog/templates/ وستصدِر خطأً إذا لم يكن الملف موجودًا. يمكنك التحقق من ذلك من خلال حفظ التغييرات السابقة والوصول إلى العنوان "127.0.0.1:8000" في متصفحك، حيث ستظهر رسالة خطأ بديهية إلى حدٍ ما هي "TemplateDoesNotExist at /catalog/‎" وتفاصيل أخرى. ملاحظة: سيبحث جانغو عن قوالب في عدد من الأماكن بناءً على ملف إعدادات مشروعك، وسيبحث في التطبيقات المُثبَّتة افتراضيًا. يمكنك معرفة المزيد حول كيفية عثور جانغو على القوالب وتنسيقات القوالب التي يدعمها في قسم القوالب من توثيق جانغو. توسيع القوالب يحتاج قالب الفهرس إلى توصيف معياري في لغة HTML لقسم الرأس والمتن مع أقسام التنقل للربط بالصفحات الأخرى من الموقع (التي لم ننشئها بعد) والأقسام التي تعرض نصًا تقديميًا وبيانات الكتاب. سيكون الكثير من شيفرة HTML وبنية التنقل هي نفسها في صفحات موقعنا. يمكنك استخدام لغة قوالب جانغو للتصريح عن قالب أساسي ثم توسيعه ليحل محل الأجزاء المختلفة لكل صفحة محددة بدلًا من تكرار الشيفرة المتداولة في كل صفحة. يُعَد جزء الشيفرة البرمجية الآتية نموذجًا للقالب الأساسي من الملف base_generic.html، وسننشئ قالبًا لموقع المكتبة المحلية LocalLibrary قريبًا. يشتمل النموذج الآتي على شيفرة HTML مشتركة لأقسام العنوان والشريط الجانبي والمحتويات الرئيسية المميزة بوسوم القالب المُسمَّاة block و endblock. يمكنك ترك الكتل Blocks فارغة أو تضمين محتوًى افتراضي لاستخدامه عند عرض الصفحات المشتقة من القالب. ملاحظة: وسوم القالب Tags هي دوال يمكنك استخدامها في قالب لتكرارها على القوائم وإجراء عمليات شرطية بناءً على قيمة متغير وغير ذلك. تسمح صيغة القوالب بالإضافة إلى وسوم القالب بالإشارة إلى المتغيرات المُمرَّرة إلى القالب من العرض واستخدام مرشّحات القالب لتنسيق المتغيرات مثل تحويل سلسلة نصية إلى أحرف صغيرة. <!DOCTYPE html> <html lang="en"> <head> {% block title %}<title>Local Library</title>{% endblock %} </head> <body> {% block sidebar %}<!-- أدخِل نص التنقل الافتراضي لكل صفحة -->{% endblock %} {% block content %}<!-- نص المحتوى الافتراضي، ويكون فارغ عادة -->{% endblock %} </body> </html> نحدّد أولًا القالب الأساسي باستخدام وسم القالب extends عند تعريف قالب لعرض معين (اطّلع على نموذج الشيفرة البرمجية الآتي)، ثم نصرّح عن أقسام القالب التي نريد استبدالها -إن وجدت- باستخدام أقسام block أو endblock كما في القالب الأساسي. يوضح جزء الشيفرة البرمجية الآتي كيفية استخدام وسم القالب extends وتعديل الكتلة content. ستتضمن شيفرة HTML الشيفرة البرمجية والبنية المُعرَّفة في القالب الأساسي بما في ذلك المحتوى الافتراضي الذي عرّفته في الكتلة title، ولكن توضَع الكتلة content الجديدة مكان الكتلة الافتراضية. {% extends "base_generic.html" %} {% block content %} <h1>Local Library Home</h1> <p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p> {% endblock %} القالب الأساسي لموقع المكتبة المحلية LocalLibrary سنستخدم جزء الشيفرة الآتي بمثابة قالب أساسي لموقع المكتبة المحلية LocalLibrary، إذ يحتوي هذا الجزء على بعض شيفرة HTML ويعرّف كتل title و sidebar و content. لدينا عنوان افتراضي وشريط جانبي افتراضي يحتوي روابطًا لقوائم جميع الكتب والمؤلفين، وكلاهما مُضمَّن في كتل يمكن تغييرها بسهولة لاحقًا. ملاحظة: سنشرح أيضًا وسمي قالب إضافيين هما: url و load static في الأقسام اللاحقة. أنشئ ملفًا جديدًا هو base_generic.html في المجلد /locallibrary/catalog/templates/ والصق الشيفرة البرمجية التالية في هذا الملف: <!DOCTYPE html> <html lang="en"> <head> {% block title %} <title>Local Library</title> {% endblock %} <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" /> ‏<-- ضِف شيفرة‫ CSS إضافية في ملف ثابت --!> {% load static %} <link rel="stylesheet" href="{% static 'css/styles.css' %}" /> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-sm-2"> {% block sidebar %} <ul class="sidebar-nav"> <li><a href="{% url 'index' %}">Home</a></li> <li><a href="">All books</a></li> <li><a href="">All authors</a></li> </ul> {% endblock %} </div> <div class="col-sm-10 ">{% block content %}{% endblock %}</div> </div> </div> </body> </html> يتضمن القالب شيفرة CSS من إطار عمل بوتستراب Bootstrap لتحسين تخطيط وعرض صفحة HTML؛ إذ يُعَد استخدام بوتستراب -أو إطار عمل ويب آخر من طرف العميل- طريقةً سريعةً لإنشاء صفحة جذابة تُعرَض بصورة جيدة على أحجام الشاشات المختلفة. يشير القالب الأساسي إلى ملف CSS محلي (styles.css) يوفر تنسيقًا إضافيًا. أنشئ ملف styles.css في المجلد /locallibrary/catalog/static/css/ والصق الشيفرة البرمجية التالية في الملف: .sidebar-nav { margin-top: 20px; padding: 0; list-style: none; } قالب الفهرس أنشئ ملف HTML جديد بالاسم index.html في المجلد /locallibrary/catalog/templates/ والصق الشيفرة البرمجية التالية فيه، إذ توسّع هذه الشيفرة البرمجية القالب الأساسي في السطر الأول، ثم تستبدل كتلة content الافتراضية في هذا القالب: {% extends "base_generic.html" %} {% block content %} <h1>Local Library Home</h1> <p> Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>! </p> <h2>Dynamic content</h2> <p>The library has the following record counts:</p> <ul> <li><strong>Books:</strong> {{ num_books }}</li> <li><strong>Copies:</strong> {{ num_instances }}</li> <li><strong>Copies available:</strong> {{ num_instances_available }}</li> <li><strong>Authors:</strong> {{ num_authors }}</li> </ul> {% endblock %} نصرّح في قسم المحتوى الديناميكي Dynamic content عن العناصر البديلة، أي متغيرات القالب للمعلومات الواردة من العرض الذي نريد تضمينها، إذ تكون المتغيرات محاطة بأقواس معقوصة مزدوجة Handlebars. ملاحظة: يمكنك التعرف بسهولة على متغيرات القالب ووسوم القالب (الدوال)، إذ تكون المتغيرات محاطةً بأقواس معقوصة مزدوجة {{ num_books }}، وتكون الوسوم محاطة بأقواس معقوصة مفردة مع إشارات النسبة المئوية {% extends "base_generic.html" %}. الشيء المهم الذي يجب ملاحظته هنا هو تسمية المتغيرات بالمفاتيح Keys التي نمررها في قاموس context ضمن الدالة render()‎ الخاصة بالعرض (اطلع على النموذج التالي)، وسنستبدل المتغيرات بالقيم المرتبطة بها عند عرض القالب: context = { 'num_books': num_books, 'num_instances': num_instances, 'num_instances_available': num_instances_available, 'num_authors': num_authors, } return render(request, 'index.html', context=context) الإشارة إلى الملفات الثابتة في القوالب يُحتمَل أن يستخدم مشروعك مواردًا ثابتة مثل ملفات جافاسكربت و CSS والصور، ويمكن أن يكون موقع هذه الملفات غير معروف (أو يمكن أن يتغير)، لذا يتيح لك جانغو تحديد الموقع في قوالبك المتعلقة بالإعداد العام STATIC_URL. يضبط موقع الويب الهيكلي الافتراضي الإعداد STATIC_URL على القيمة /static/، ولكن يمكنك اختيار استضافتها على شبكة توصيل المحتوى أو أيّ مكان آخر. استدعِ أولًا ضمن القالب وسم القالب load مع تحديد "static" لإضافة مكتبة القوالب كما هو موضح في نموذج الشيفرة البرمجية التالية، ويمكنك بعد ذلك استخدام وسم القالب static وتحديد عنوان URL النسبي للملف المطلوب: ‏<-- إضافة شيفرة‫ CSS إضافية في ملف ثابت --!> {% load static %} <link rel="stylesheet" href="{% static 'css/styles.css' %}" /> يمكنك إضافة صورة إلى الصفحة بطريقة مماثلة كما يلي: {% load static %} <img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="UML diagram" style="width:555px;height:540px;" /> ملاحظة: تحدد النماذج السابقة مكان وجود الملفات، ولكن جانغو لا يخدّمها افتراضيًا، لذا ضبطنا خادم الويب الخاص بالتطوير لتخديم الملفات من خلال تعديل رابط عنوان URL العام (‎/locallibrary/locallibrary/urls.py) عندما أنشأنا موقع الويب الهيكلي، ولكننا ما زلنا بحاجة إلى تفعيل تخديم الملفات في بيئة الإنتاج، إذ سنوضح ذلك لاحقًا. اطّلع على إدارة الملفات الثابتة في توثيق جانغو لمزيد من المعلومات. الارتباط بعناوين URLs قدّم القالب الأساسي السابق وسم القالب url. <li><a href="{% url 'index' %}">Home</a></li> يقبل هذا الوسم اسم الدالة path()‎ المُستدعاة في الملف urls.py وقيم أيّ وسائط يتلقّاها العرض من هذه الدالة، ويعيد عنوان URL الذي يمكنك استخدامه للارتباط بالمورد. ضبط مكان العثور على القوالب يُحدَّد الموقع الذي يبحث فيه جانغو عن القوالب في الكائن TEMPLATES ضمن الملف settings.py، إذ يبدو هذا الملف افتراضيًا (كما أُنشِئ لهذه السلسلة من المقالات) كما يلي: TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] يُعَد إعداد ‎'APP_DIRS': True الأكثر أهمية من بين القوالب المُسمَّاة، لأنه يخبر جانغو بالبحث عن قوالب في مجلد فرعي لكل تطبيق في المشروع، مما يسهّل تجميع القوالب مع التطبيق المرتبط بها لسهولة إعادة الاستخدام. يمكننا أيضًا تحديد مواقع محددة لجانغو للبحث عن المجلدات باستخدام ‎'DIRS': []‎ لكنه ليس مطلوبًا حاليًا. ملاحظة: يمكنك معرفة المزيد حول كيفية عثور جانغو على القوالب وتنسيقات القوالب التي يدعمها في قسم القوالب ضمن توثيق جانغو. كيف تبدو صفحة موقعنا الرئيسية؟ أنشأنا حتى الآن جميع الموارد المطلوبة لعرض صفحة الفهرس. شغّل الخادم باستخدام الأمر python3 manage.py runserver وافتح العنوان "http://127.0.0.1:8000/‎" في متصفحك. إذا جرى ضبط كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي: ملاحظة: لن تعمل ارتباطات جميع الكتب All books وجميع المؤلفين All authors حاليًا، لأن المسارات والعروض والقوالب الخاصة بهذه الصفحات لم تُعرَّف بعد، فقد أدخلنا فقط عناصرًا بديلة لتلك الارتباطات في القالب base_generic.html. تحدى نفسك إليك بعض المهام لاختبار مدى إلمامك باستعلامات النماذج والعروض والقوالب. أولًا، يتضمن القالب الأساسي لموقع المكتبة المحلية LocalLibrary الكتلة title. عدّل هذه الكتلة في قالب الفهرس وأنشئ عنوانًا جديدًا للصفحة. ملاحظة: يوضح القسم "توسيع القوالب" في هذا المقال كيفية إنشاء كتل وتوسيعها في قالب آخر. ثانيًا، عدّل العرض لتوليد أعداد من أنواع الكتب genres والكتب books التي تحتوي على كلمة معينة (غير حساسة لحالة الأحرف)، ومرّر النتائج إلى context. يمكنك تحقيق ذلك بطريقة مماثلة لإنشاء واستخدام المتغيرات num_books وnum_instances_available، ثم حدّث قالب الفهرس لتضمين هذه المتغيرات. الخلاصة لقد أنشأنا الصفحة الرئيسية لموقعنا، وهي صفحة HTML تعرض عددًا من السجلات من قاعدة البيانات وارتباطات لصفحات أخرى لم تُنشَأ بعد. تعلّمنا المعلومات الأساسية حول روابط Mappers عناوين URLs والعروض، والاستعلام في قاعدة البيانات باستخدام النماذج، وتمرير المعلومات إلى القالب من العرض، وإنشاء القوالب وتوسيعها. سننشئ في المقال التالي الصفحات الأربع المتبقية من موقعنا بناءً على هذه المعرفة. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 5: Creating our home page. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية المقال السابق: تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin العروض والقوالب الجزء الأول والجزء الثاني البدء مع إطار العمل جانغو لإنشاء تطبيق ويب بناء تطبيق مهام باستخدام جانغو Django وريآكت React مرسل Dispatcher عناوين URL (توثيق جانغو) دوال العرض (توثيق جانغو) القوالب (توثيق جانغو) إدارة الملفات الثابتة (توثيق جانغو) دوال جانغو المُختصَرة (توثيق جانغو)
  2. أنشأنا نماذجًا لموقع المكتبة المحلية LocalLibrary، وسنستخدم الآن موقع مدير جانغو Django Admin لإضافة بعض بيانات الكتب الحقيقية. سنوضّح أولًا كيفية تسجيل النماذج في موقع المدير، ثم سنوضّح كيفية تسجيل الدخول وإنشاء بعض البيانات، وسنعرض في نهاية المقال بعض الطرق التي يمكنك من خلالها تحسين عرض موقع المدير. المتطلبات الأساسية: أكمل أولًا مقال استخدام النماذج Models. الهدف: فهم فوائد وقيود موقع مدير جانغو واستخدامه لإنشاء بعض السجلات للنماذج. يمكن لتطبيق مدير جانغو استخدام نماذجك لإنشاء منطقة موقع تلقائيًا يمكنك استخدامها لإنشاء السجلات وعرضها وتحديثها وحذفها، مما يوفر لك الكثير من الوقت أثناء عملية التطوير، ويسهّل اختبار نماذجك ومعرفة ما إذا كان لديك البيانات الصحيحة. يمكن أن يكون تطبيق المدير مفيدًا لإدارة البيانات في عملية الإنتاج اعتمادًا على نوع موقع الويب، إذ يوصي مشروع جانغو به لإدارة البيانات الداخلية فقط، أي للاستخدام من طرف المديرين أو الأشخاص الداخليين في مؤسستك فقط، إذ لا يُعَد النهج المتمحور حول النماذج بالضرورة أفضل واجهة ممكنة لجميع المستخدمين، ويكشف الكثير من التفاصيل غير الضرورية عن النماذج. أُجرِي كل الإعداد المطلوب لتضمين تطبيق المدير في موقع الويب تلقائيًا عندما أنشأتَ المشروع الهيكلي (اطلع على توثيق جانغو للحصول على معلومات حول الاعتماديات Dependencies الفعلية المطلوبة)، فكل ما يجب عليك فعله لإضافة نماذجك إلى تطبيق المدير هو تسجيلها فقط. سنقدم في نهاية هذا المقال عرضًا موجزًا لكيفية إعداد منطقة المدير لعرض بيانات نموذجنا بصورة أفضل. سنتعرّف بعد تسجيل النماذج على كيفية إنشاء مستخدم مميز Superuser جديد وتسجيل الدخول إلى الموقع وإنشاء بعض الكتب والمؤلفين ونسخ الكتب وأنواعها، إذ سيكون ذلك مفيدًا لاختبار العروض والقوالب التي سننشئها في المقالات اللاحقة. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تسجيل النماذج أولًا، افتح الملف admin.py في التطبيق catalog ضمن ‎/locallibrary/catalog/admin.py، إذ يبدو هذا الملف حاليًا كما يلي: from django.contrib import admin # سجّل نماذجك هنا لاحظ أنه يستورد django.contrib.admin. سجّل النماذج من خلال نسخ النص التالي في نهاية الملف، إذ تستورد هذه الشيفرة البرمجية النماذج وتستدعي admin.site.register لتسجيل كلٍّ منها: from .models import Author, Genre, Book, BookInstance admin.site.register(Book) admin.site.register(Author) admin.site.register(Genre) admin.site.register(BookInstance) ملاحظة: إذا قبلت التحدي لإنشاء نموذج يمثل اللغة الطبيعية للكتاب في المقال السابق، فاستورد ذلك النموذج وسجّله. تُعَد هذه الطريقة أبسط طريقة لتسجيل نموذج واحد أو عدة نماذج في الموقع، إذ يُعَد موقع المدير قابلًا للتخصيص بدرجة كبيرة، وسنتحدث أكثر عن الطرق الأخرى لتسجيل نماذجك لاحقًا. إنشاء مستخدم مسؤول Superuser تحتاج حساب مستخدم مع تفعيل الحالة Staff (أي أن المستخدم من الموظفين) لتسجيل الدخول إلى موقع المدير، ويجب أن يكون لدى هذا المستخدم أذونات لإدارة جميع الكائنات لعرض السجلات وإنشائها. يمكنك إنشاء حساب مستخدم مميز يتمتع بوصول كامل إلى الموقع وجميع الأذونات المطلوبة باستخدام الملف manage.py. استدعِ الأمر التالي في مجلد الملف manage.py لإنشاء المستخدم المميز، إذ سيُطلَب منك إدخال اسم مستخدم وعنوان بريد إلكتروني وكلمة مرور قوية: python3 manage.py createsuperuser سيُضاف مستخدم مميز جديد إلى قاعدة البيانات بعد اكتمال الأمر السابق. أعِد تشغيل خادم التطوير لتتمكن من اختبار تسجيل الدخول كما يلي: python3 manage.py runserver تسجيل الدخول واستخدام الموقع يمكنك تسجيل الدخول إلى الموقع من خلال فتح عنوان URL للمدير "‎/admin"، مثل "http://127.0.0.1:8000/admin" وإدخال بيانات اعتماد المستخدم المميز وكلمة المرور الجديدة، إذ سيُعاد توجيهك إلى صفحة تسجيل الدخول ثم ستعود إلى عنوان URL للمدير "‎/admin" بعد إدخال تفاصيلك. يعرض هذا الجزء من الموقع جميع نماذجنا مُجمَّعةً حسب التطبيق المُثبَّت. يمكنك النقر على اسم النموذج للانتقال إلى شاشة تعرض قائمةً بجميع السجلات المرتبطة به، ثم النقر على هذه السجلات لتعديلها. يمكنك النقر مباشرةً على رابط الإضافة Add بجانب كل نموذج لإنشاء سجل من هذا النوع. انقر على رابط الإضافة Add الموجود على يمين النموذج Books لإنشاء كتاب جديد، إذ سيظهر مربع حوار يشبه إلى حد كبير الشكل الآتي. لاحظ كيف تتطابق عناوين كل حقل ونوع عنصر الواجهة المُستخدَم ونص التعليمات help_text -إن وجد- مع القيم التي حدّدتها في النموذج. أدخِل قيم الحقول، ويمكنك إنشاء مؤلفين Authors أو أنواع Genres جديدة بالضغط على الزر + بجانب الحقول أو تحديد قيم الموجودة من القوائم إذا أنشأتها مسبقًا. يمكنك عند الانتهاء الضغط على زر حفظ SAVE أو حفظ وإضافة آخر Save and add another أو حفظ ومتابعة التعديل Save and continue editing لحفظ السجل. ملاحظة: أضف بعض الكتب والمؤلفين والأنواع (مثل النوع الخيالي Fantasy) إلى تطبيقك، وتأكد من أن كل مؤلف ونوع يشتملان على عدة كتب، مما سيجعل قائمتك وتفاصيل عروضك أفضل عندما نقدّمها لاحقًا في المقالات القادمة. انقر على رابط الصفحة الرئيسية Home link في الأعلى للرجوع إلى صفحة المدير الرئيسية عند الانتهاء من إضافة الكتب، ثم انقر على رابط الكتب Books لعرض قائمة الكتب الحالية، أو انقر على أحد الروابط الأخرى لمشاهدة قوائم النماذج الأخرى. يمكن أن تبدو القائمة الآن مشابهة للقطة الشاشة التالية بعد أن أضفت بعض الكتب، إذ سيُعرَض عنوان كل كتاب، وهي القيمة المُعادة في التابع ‎__str__()‎ الخاص بالنموذج Book الذي حدّدناه في المقال السابق. يمكنك حذف الكتب من هذه القائمة من خلال تحديد مربع الاختيار بجانب الكتاب الذي لا تريده وتحديد إجراء الحذف delete…‎ من القائمة المنسدلة Action، ثم الضغط على الزر Go. يمكنك إضافة كتب جديدة بالضغط على زر إضافة كتاب ADD BOOK. يمكنك تعديل كتاب من خلال اختيار اسمه في الارتباط، فصفحة تعديل الكتاب الموضَّحة في الشكل الآتي مطابقة تقريبًا لصفحة الإضافة، ولكن الاختلافات الرئيسية بينهما هي عنوان الصفحة "Change book" وإضافة الأزرار حذف Delete والسجل HISTORY وعرض على الموقع VIEW ON SITE، إذ يظهر هذا الزر الأخير لأننا عرّفنا التابع get_absolute_url()‎ في نموذجنا. ملاحظة: يؤدي النقر فوق الزر VIEW ON SITE إلى ظهور استثناء NoReverseMatch بسبب محاولة التابع get_absolute_url()‎ لعكس ()reverse رابط عنوان URL المُسمى ('book-detail') غير المُعرّف بعد. سنعرّف ربط العنوان URL والعرض المرتبط به في المقال السابع من هذه السلسلة. انتقل الآن مرةً أخرى إلى الصفحة الرئيسية Home باستخدام رابط Home في مسار التنقل ثم اعرض قوائم المؤلف Author والنوع Genre، إذ يجب أن يكون لديك عدد منها مُنشَأ مسبقًا عند إضافة الكتب الجديدة، ولكن لا تتردد في إضافة المزيد منها. لن يكون لديك أيّ نسخ كتب Book Instances، لأنها لم تُنشَأ من الكتب Books، بالرغم من أنه يمكنك إنشاء كتاب Book من BookInstance، فهذه هي طبيعة الحقل من النوع ForeignKey. انتقل مرةً أخرى إلى الصفحة الرئيسية واضغط على زر الإضافة Add المرتبط بعرض شاشة إضافة نسخة كتاب Add book instance التالية. لاحظ المعرّف الكبير والفريد بصورة عامة، والذي يمكن استخدامه لتحديد نسخة من كتاب في المكتبة بصورة منفصلة. أنشئ عددًا من هذه السجلات لكل كتاب من كتبك، واضبط الحالة على أنها متوفرة Available لبعض السجلات على الأقل وأنها مُعارة On Loan لسجلات أخرى. إذا كانت الحالة غير متوفرة، فاضبط أيضًا تاريخ استرجاع الكتاب Due back مستقبلًا. لقد تعلمت كيفية إعداد واستخدام موقع المدير، وأنشأت سجلات للنماذج Book و BookInstance و Genre و Author التي سنتمكن من استخدامها بمجرد إنشاء العروض والقوالب. الضبط المتقدم يطبّق جانغو عملًا جيدًا جدًا في إنشاء موقع مدير أولي باستخدام معلومات النماذج المسجلة، بحيث: يحتوي كل نموذج على قائمة من السجلات الفردية التي تحدّدها السلسلة النصية الناتجة باستخدام التابع ‎__str__()‎ الخاص بالنموذج وترتبط باستمارات أو عروض تفصيلية للتعديل، إذ يحتوي هذا العرض view افتراضيًا على قائمة إجراءات في الأعلى يمكنك استخدامها لإجراء مجموعة عمليات حذف على السجلات. تحتوي استمارات السجلات التفصيلية الخاصة بالنموذج لتعديل السجلات وإضافتها على جميع الحقول الموجودة في النموذج والموضوعة عموديًا في ترتيب التصريح عنها. يمكنك تخصيص الواجهة لتسهيل استخدامها، فبعض الأشياء التي يمكنك تطبيقها هي: عروض القائمة List Views: أضف الحقول أو المعلومات الإضافية المعروضة لكل سجل. أضف مرشّحات لتحديد السجلات المُدرجَة بناءً على التاريخ أو بعض قيم التحديد الأخرى، مثل حالة إعارة الكتاب. أضف خيارات إضافية إلى قائمة الإجراءات في عروض القائمة واختر مكان عرض هذه القائمة في الاستمارة. العروض التفصيلية Detail Views: اختر الحقول المراد عرضها (أو استبعادها) مع ترتيبها وتجميعها وما إذا كانت قابلة للتعديل وعنصر الواجهة المُستخدَم والاتجاه وغير ذلك. أضف الحقول المرتبطة بسجلٍ ما للسماح بالتعديل المُضمَّن مثل إضافة القدرة على إضافة وتعديل سجلات الكتاب أثناء إنشاء سجل المؤلف الخاص بها. سنلقي في هذا القسم نظرةً على بعض التغييرات التي من شأنها تحسين واجهة موقع مكتبتنا المحلية LocalLibrary، مثل إضافة المزيد من المعلومات إلى قوائم النموذج Book و Author، وتحسين تخطيط عروض التعديل الخاصة بها. لن نغير عرض النموذجين Language و Genre، إذ يحتوي كلٌ منهما على حقل واحد فقط، لذلك لا توجد فائدة حقيقية من ذلك. يمكنك العثور على مرجع كامل لجميع خيارات تخصيص موقع المدير في توثيق جانغو، ويمكنك على الاطلاع على مقال تنشيط واجهة مدير جانغو والاتصال بها لمعلومات أكثر. تسجيل الصنف ModelAdmin يمكنك تغيير كيفية عرض النموذج في واجهة المدير من خلال تعريف الصنف ModelAdmin (الذي يصف التخطيط) وتسجيله مع النموذج. لنبدأ بالنموذج Author. افتح الملف admin.py في التطبيق catalog ضمن ‎/locallibrary/catalog/admin.py. ضع تعليقًا على التسجيل الأصلي (ابدأه بالرمز #) للنموذج Author كما يلي: # admin.site.register(Author) أضف صنف AuthorAdmin جديد وسجّله كما يلي: # تعريف صنف المدير class AuthorAdmin(admin.ModelAdmin): pass # تسجيل صنف المدير في النموذج المرتبط به admin.site.register(Author, AuthorAdmin) سنضيف الآن أصناف ModelAdmin للنموذجين Book و BookInstance. يجب التعليق على التسجيلات الأصلية مرةً أخرى كما يلي: # admin.site.register(Book) # admin.site.register(BookInstance) يمكننا الآن إنشاء وتسجيل النماذج الجديدة من خلال استخدام المزخرف ‎@register لتسجيل النماذج، والذي يفعل الشيء نفسه تمامًا الذي تفعله صيغة admin.site.register()‎: # ‫تسجيل أصناف المدير للنموذج Book باستخدام المزخرف @admin.register(Book) class BookAdmin(admin.ModelAdmin): pass # BookInstance تسجيل أصناف المدير للنموذج باستخدام المزخرف @admin.register(BookInstance) class BookInstanceAdmin(admin.ModelAdmin): pass جميع أصناف المدير فارغة حاليًا (لاحظ pass)، لذلك لن يتغير سلوك المدير، ويمكننا توسيعها لتحديد سلوك المدير الخاص بالنموذج. ضبط عروض القائمة تسرد المكتبة المحلية LocalLibrary حاليًا جميع المؤلفين الذين يستخدمون اسم الكائن الذي ينشئه التابع ‎__str__()‎ الخاص بالنموذج، وهذا جيد عندما يكون لديك عدد قليل من المؤلفين، ولكن يمكن أن ينتهي بك الأمر بالحصول على نسخ مكررة بمجرد أن يكون لديك العديد من المؤلفين. يمكنك التمييز بينها أو إظهار مزيد من المعلومات حول كل مؤلف من خلال استخدام السمة list_display لإضافة حقول إضافية إلى العرض. ضع الشيفرة البرمجية الآتية بدلًا من الصنف AuthorAdmin. يُصرَّح عن أسماء الحقول المعروضة في القائمة ضمن صف tuple بالترتيب المطلوب كما يلي (هذه هي الأسماء نفسها المُحدَّدة في نموذجك الأصلي): class AuthorAdmin(admin.ModelAdmin): list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') انتقل إلى قائمة المؤلفين في موقع الويب، إذ يجب الآن عرض الحقول السابقة على النحو التالي: سنعرض أيضًا حقول المؤلف author والنوع genre للنموذج Book، فالحقل author هو حقل من النوع ForeignKey للعلاقة (واحد إلى متعدد)، وبالتالي ستمثله قيمة التابع ‎__str__()‎ للسجل المرتبط به. ضع ما يلي بدلًا من الصنف BookAdmin: class BookAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'display_genre') لسوء الحظ، لا يمكننا تحديد الحقل genre مباشرةً في السمة list_display، لأنه حقل من النوع ManyToManyField، إذ يمنع جانغو هذا النوع من الحقول لأنه سيكون هناك تكلفة كبيرة للوصول إلى قاعدة البيانات عند تطبيقه. لذا سنعرّف الدالة display_genre للحصول على المعلومات بوصفها سلسلة نصية، وهي الدالة التي استدعيناها سابقًا وسنعرّفها لاحقًا. ملاحظة: يمكن ألّا يكون الحصول على الحقل genre فكرةً جيدة هنا، بسبب تكلفة عملية قاعدة البيانات، ولكن يمكن أن تكون استدعاء الدوال في نماذجك مفيدة جدًا لأسباب أخرى مثل إضافة ارتباط الحذف Delete بجانب كل عنصر في القائمة. أضف الشيفرة البرمجية الآتية إلى النموذج Book في الملف models.py، إذ يؤدي هذا التابع إلى إنشاء سلسلة نصية من القيم الثلاث الأولى للحقل genre (إن وجدت) وإنشاء وصف قصير short_description يمكن استخدامه في موقع المدير لهذا التابع: def display_genre(self): """Create a string for the Genre. This is required to display genre in Admin.""" return ', '.join(genre.name for genre in self.genre.all()[:3]) display_genre.short_description = 'Genre' افتح موقع الويب بعد حفظ النموذج والمدير المُحدَّث وانتقل إلى صفحة قائمة الكتب، إذ يجب أن تشاهد قائمة كتب مشابهة للقائمة التالية: النموذج Genre (والنموذج Language إذا عرّفته) لهما حقل واحد، لذلك لا جدوى من إنشاء نموذج إضافي لهما لعرض حقول إضافية. ملاحظة: يجب تحديث قائمة النموذج BookInstance لعرض الحالة وتاريخ الإعادة المتوقع على الأقل، إذ أضفنا ذلك في نهاية هذا المقال. إضافة مرشحات القائمة يمكن أن يكون من المفيد أن تكون قادرًا على ترشيح العناصر المعروضة بعد حصولك على الكثير من العناصر في القائمة، إذ يمكن تحقيق ذلك من خلال سرد الحقول في السمة list_filter. ضع جزء الشيفرة البرمجية التالية بدلًا من الصنف BookInstanceAdmin الحالي: class BookInstanceAdmin(admin.ModelAdmin): list_filter = ('status', 'due_back') سيتضمن عرض القائمة الآن مربع ترشيح على اليمين. لاحظ كيف يمكنك اختيار التواريخ والحالة لترشيح القيم: تنظيم تخطيط العرض التفصيلي تضع العروض التفصيلية افتراضيًا كل الحقول عموديًا بترتيب التصريح عنها في النموذج، ولكن يمكنك تغيير ترتيب التصريح وما هي الحقول المعروضة (أو المستبعدة) وما إذا كانت الأقسام تُستخدَم لتنظيم المعلومات وما إذا كانت الحقول معروضة أفقيًا أو عموديًا وما هي عناصر واجهة التعديل المُستخدَمة في استمارات المدير. ملاحظة: تُعَد نماذج موقع المكتبة المحلية LocalLibrary بسيطةً نسبيًا، لذا لا توجد حاجة كبيرة لتغيير التخطيط، ولكن سنجري بعض التغييرات على أية حال لنوضّح لك كيفية تطبيق ذلك. التحكم في الحقول المعروضة وتنسيقها حدّث الصنف AuthorAdmin لإضافة السطر fields كما يلي: class AuthorAdmin(admin.ModelAdmin): list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')] تسرد السمة fields فقط الحقول التي ستُعرَض في الاستمارة وفق ترتيبها، إذ تُعرَض الحقول عموديًا افتراضيًا، ولكن ستُعرَض أفقيًا إذا جمّعتها ضمن صف كما هو موضح في حقول "date" السابقة. انتقل إلى عرض المؤلف التفصيلي في موقعك، إذ يجب أن يظهر الآن كما يلي: ملاحظة: يمكنك أيضًا استخدام السمة exclude للتصريح عن قائمة السمات التي ستُستبعَد من الاستمارة، إذ ستُعرَض جميع السمات الأخرى في النموذج. تقسيم العرض التفصيلي يمكنك إضافة أقسام Sections لتجميع معلومات النموذج ذات الصلة في الاستمارة التفصيلية باستخدام السمة fieldsets. لدينا في النموذج BookInstance معلومات تتعلق بما هو الكتاب (name و imprint و id) ومتى سيكون متاحًا (status و due_back)، إذ يمكننا إضافة هذه المعلومات إلى الصنف BookInstanceAdmin باستخدام الخاصية fieldsets كما يلي: @admin.register(BookInstance) class BookInstanceAdmin(admin.ModelAdmin): list_filter = ('status', 'due_back') fieldsets = ( (None, { 'fields': ('book', 'imprint', 'id') }), ('Availability', { 'fields': ('status', 'due_back') }), ) لكل قسم عنوانه الخاص، أو القيمة None إذا كنت لا تريد عنوانًا وصفٌ من الحقول المرتبطة به في القاموس، إذ يكون هذا التنسيق معقدًا لشرحه، ولكنه سهل الفهم إذا نظرت مباشرةً إلى جزء الشيفرة البرمجية السابقة. انتقل الآن إلى عرض نسخة الكتاب في موقعك، إذ يجب أن تظهر الاستمارة كما يلي: تعديل السجلات المضمن يكون في بعض الأحيان من المنطقي أن تكون قادرًا على إضافة سجلات في الوقت نفسه، فمن المنطقي مثلًا أن يكون لديك معلومات الكتاب ومعلومات نسخ محددة حصلت عليها في صفحة التفاصيل نفسها. يمكنك تطبيق ذلك من خلال التصريح عن السمة inlines من النوع TabularInline (تخطيط أفقي) أو StackedInline (تخطيط عمودي تمامًا مثل تخطيط النموذج الافتراضي). يمكنك إضافة معلومات نسخ الكتاب BookInstance مضمنة في تفاصيل الكتاب Book من خلال تحديد السمة inlines في الصنف BookAdmin: class BooksInstanceInline(admin.TabularInline): model = BookInstance @admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'display_genre') inlines = [BooksInstanceInline] انتقل الآن إلى عرض النموذج Book في موقعك، وسترى الآن في الأسفل نسخ الكتاب المتعلقة به أسفل حقول نوع الكتاب مباشرةً: كل ما فعلناه في هذه الحالة هو التصريح عن الصنف TabularInline المُضمَّن الذي يضيف فقط جميع الحقول من النموذج المُضمَّن inlined. يمكنك تحديد جميع أنواع المعلومات الإضافية للتخطيط بما في ذلك الحقول المراد عرضها وترتيبها وما إذا كانت للقراءة فقط أم لا وغير ذلك (اطلع على TabularInline لمزيد من المعلومات). ملاحظة: هناك بعض القيود في هذه الوظيفة، إذ لدينا في لقطة الشاشة السابقة ثلاث نسخ حالية من الكتاب متبوعة بثلاثة عناصر بديلة لنسخ كتب جديدة، والتي تبدو متشابهة جدًا. يُفضَّل عدم وجود نسخ كتب احتياطية افتراضيًا وإضافتها فقط باستخدام ارتباط إضافة نسخة كتاب أخرى Add another Book instance، أو أن تكون قادرًا فقط على سرد نماذج BookInstance بوصفها ارتباطات غير قابلة للقراءة من هنا. يمكن تحقيق الخيار الأول من خلال ضبط السمة extra على القيمة "0" في النموذج BooksInstanceInline (جرّبها بنفسك). تحدى نفسك لقد تعلمنا الكثير في هذا القسم، لذا حان الوقت الآن لتجربة بعض الأمور وهي: أضف لعرض قائمة BookInstance شيفرة برمجية لعرض الكتاب والحالة وتاريخ الاسترجاع والمعرّف (بدلًا من النص الافتراضي للتابع ‎__str__()‎). أضف قائمة مُضمَّنة لعناصر الكتاب Book إلى عرض Author التفصيلي باستخدام الأسلوب نفسه الذي استخدمناه مع Book و BookInstance. الخلاصة لقد تعلمتَ الآن كيفية إعداد موقع المدير في شكله الأبسط والمحسّن وكيفية إنشاء مستخدم مميز وكيفية التنقل في موقع المدير وعرض السجلات وحذفها وتحديثها، وأنشأتَ مجموعة من الكتب ونسخ الكتب والأنواع والمؤلفين التي سنكون قادرين على سردها وعرضها بمجرد إنشاء العرض والقوالب الخاصة بنا. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 4: Django admin site. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية المقال السابق: تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تنشيط واجهة مدير جانغو والاتصال بها. العروض والقوالب في Django حزم بايثون الثمانية التي تسهل تعاملك مع Django كتابة أول تطبيق جانغو - الجزء الثاني: مقدمة إلى مدير جانغو (توثيق جانغو) موقع مدير جانغو (توثيق جانغو)
  3. يوضح هذا المقال كيفية تعريف النماذج Models لموقع المكتبة المحلية LocalLibrary، ويشرح ما هو النموذج وكيفية التصريح عنه وبعض أنواع الحقول الرئيسية، ويعرض بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج. المتطلبات الأساسية: الاطلاع على مقال إنشاء موقع ويب هيكلي لمكتبة محلية. الهدف: أن تكون قادرًا على تصميم وإنشاء نماذجك واختيار الحقول بصورة مناسبة. يمكن لتطبيقات جانغو Django الوصول إلى البيانات وإدارتها من خلال كائنات لغة بايثون Python المُشار إليها بالنماذج Models، إذ تعرِّف النماذج بنية البيانات المخزنة بما في ذلك أنواع الحقول وربما حجمها الأقصى وقيمها الافتراضية وخيارات قائمة الاختيار ونص التعليمات للتوثيق ونص التسمية للاستمارات Forms وغير ذلك. يُعَد تعريف النموذج مستقلًا عن قاعدة البيانات الأساسية، إذ يمكنك اختيار قاعدة بيانات واحدة من عدة قواعد بيانات بوصفها جزءًا من إعدادات مشروعك. لن تحتاج أبدًا إلى التواصل مع قاعدة البيانات التي تريد استخدامها مباشرةً بمجرد اختيارها، فما عليك سوى كتابة بنية نموذجك وشيفرة برمجية أخرى، وسيتولى إطار عمل جانغو كل الأعمال الأخرى للتواصل مع قاعدة البيانات نيابةً عنك. يوضح هذا المقال كيفية تعريف النماذج والوصول إليها في مثال موقع المكتبة المحلية LocalLibrary. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تصميم نماذج المكتبة المحلية LocalLibrary يُفضَّل أخذ بضع دقائق للتفكير في البيانات التي يجب تخزينها والعلاقات بين الكائنات قبل الانتقال والبدء في كتابة شيفرة النماذج البرمجية. نعلم أننا نحتاج إلى تخزين معلومات حول الكتب (العنوان والملخص والمؤلف واللغة المكتوبة والفئة ورقم ISBN)، وأنه يمكن أن يكون لدينا نسخ متعددة متاحة (مع معرّف فريد عام وحالة التوفر وغير ذلك). يمكن أن نحتاج إلى تخزين مزيد من المعلومات حول المؤلف أكثر من مجرد اسمه، ويمكن أن يكون هناك عدة مؤلفين لهم الاسم نفسه أو أسماء متشابهة. نريد أن نكون قادرين على فرز المعلومات بناءً على عنوان الكتاب والمؤلف واللغة المكتوبة والفئة، إذ يُفضَّل أن يكون لديك نماذج منفصلة لكل كائن (مجموعة من المعلومات المتعلقة ببعضها) عند تصميم نماذجك، والأشياء الواضحة في هذه الحالة هي الكتب ونسخ الكتب والمؤلفون. قد ترغب في استخدام النماذج لتمثيل خيارات قائمة الاختيار، مثل قائمة اختيارات منسدلة بدلًا من الشيفرة الثابتة للخيارات في موقع الويب نفسه، إذ يُوصَى بذلك عندما لا تكون جميع الخيارات معروفة مسبقًا أو أنها تتغير باستمرار. يشمل المرشحون الواضحون للنماذج في هذه الحالة نوع الكتاب، مثل الخيال العلمي والشعر واللغة العربية والإنجليزية والفرنسية وغير ذلك. يجب التفكير في العلاقات بمجرد أن نقرر ما هي النماذج والحقول، إذ يسمح جانغو بتعريف العلاقات التي تكون من نوع واحد لواحد "OneToOneField" وواحد إلى متعدد "ForeignKey" ومتعدد إلى متعدد "ManyToManyField". يوضح مخطط الارتباط التالي باستخدام لغة UML النماذج التي سنعرّفها في هذه الحالة (على شكل مربعات): أنشأنا نماذجًا للكتاب (التفاصيل العامة للكتاب)، ونسخة الكتاب (حالة النسخ الحقيقية المُحدَّدة للكتاب المتاح في النظام)، والمؤلف، وقررنا أن يكون لدينا نموذج للنوع، بحيث يمكن إنشاء أو تحديد القيم من خلال واجهة المدير، وقررنا عدم وجود نموذج لحالة نسخة الكتاب BookInstance:status، إذ كتبنا شيفرة ثابتة للقيم (LOAN_STATUS) لأننا لا نتوقع تغييرها. يمكنك رؤية اسم النموذج وأسماء الحقول وأنواعها والتوابع وأنواع قيمها المُعادة ضمن كل مربع من المربعات. يوضح المخطط العلاقات بين النماذج بما في ذلك درجة تعدّدها Multiplicities، وهي الأعداد الموجودة على المخطط والتي توضح أعداد (الحد الأقصى والحد الأدنى) كل نموذج والتي يمكن أن تكون موجودةً في العلاقة، فمثلًا يوضّح الخط المتصل بين المربعات أن الكتاب Book والنوع Genre مرتبطان، وتوضح الأعداد القريبة من نموذج النوع أن الكتاب يجب أن يحتوي على نوع واحد أو أكثر (بقدر ما تريد)، بينما توضح الأعداد الموجودة على الطرف الآخر من الخط بجوار نموذج الكتاب أن النوع يمكن ألّا يكون له أيّ نوع مرتبط بالكتاب أو يمكن أن يكون له العديد من الأنواع المرتبطة بالكتب. ملاحظة: يوفر القسم التالي تمهيدًا يشرح كيفية تعريف النماذج واستخدامها، وضع في بالك أثناء قراءته كيفية بناء كل نموذج من النماذج الموضحة في المخطط السابق. مدخل إلى النماذج يقدم هذا القسم نظرةً عامة موجزة عن كيفية تعريف النموذج وبعضًا من أهم الحقول والوسطاء. تعريف النموذج تُعرَّف النماذج عادةً في الملف models.py الخاص بالتطبيق، وتُقدَّم بوصفها صنفًا فرعيًا من django.db.models.Model، ويمكن أن تشمل الحقول والتوابع والبيانات الوصفية. يُظهِر جزء الشيفرة البرمجية التالي نموذجًا قياسيًا بالاسم MyModelName: from django.db import models from django.urls import reverse class MyModelName(models.Model): """A typical class defining a model, derived from the Model class.""" # حقول my_field_name = models.CharField(max_length=20, help_text='Enter field documentation') # … # بيانات وصفية class Meta: ordering = ['-my_field_name'] # توابع def get_absolute_url(self): """Returns the URL to access a particular instance of MyModelName.""" return reverse('model-detail-view', args=[str(self.id)]) def __str__(self): """String for representing the MyModelName object (in Admin site etc.).""" return self.my_field_name سنستكشف في الأقسام التالية كل ميزة في النموذج بالتفصيل. الحقول Fileds يمكن أن يحتوي النموذج على عدد عشوائي من الحقول ومن أيّ نوع، إذ يمثل كل حقل عمودًا من البيانات التي نريد تخزينها في أحد جداول قاعدة بياناتنا، ويتألف كل سجل أو صف في قاعدة البيانات من قيمة واحدة لكل قيمة حقل. لنلقِ نظرةً على المثال التالي: my_field_name = models.CharField(max_length=20, help_text='Enter field documentation') يحتوي هذا المثال على حقل واحد يسمى my_field_name من النوع models.CharField، مما يعني أن هذا الحقل سيحتوي على سلاسل نصية من المحارف الأبْجَعَددية alphanumeric. تُسنَد أنواع الحقول باستخدام أصناف Classes معينة تحدد نوع السجل المُستخدَم لتخزين البيانات في قاعدة البيانات مع معايير التحقق لاستخدامها عند استلام القيم من استمارة HTML (أي ما يشكّل قيمة صالحة). يمكن أن تأخذ أنواع الحقول وسطاء تحدّد كيفية تخزين الحقل أو كيفية استخدامه، إذ سنعطي الحقل في حالتنا وسيطين، هما: max_length=20، الذي يشير إلى أن أقصى طول لقيمةٍ ما في هذا الحقل وهو 20 محرفًا. help_text='Enter field documentation'‎، وهو نص مفيد يمكن عرضه في استمارة لمساعدة المستخدمين على فهم كيفية استخدام الحقل. يُستخدَم اسم الحقل للإشارة إليه في الاستعلامات والقوالب، وتحتوي الحقول على تسمية Label يحدّدها الوسيط verbose_name (بقيمة افتراضية هي None). إذا لم يُضبَط الوسيط verbose_name، فستُنشَأ التسمية من اسم الحقل من خلال استبدال أيّ شرطات سفلية بمسافة وجعل الحرف الأول حرفًا كبيرًا، فمثلًا سيكون للحقل my_field_name تسمية افتراضية هي "My field name" عند استخدامه في الاستمارات. يؤثر ترتيب التصريح عن الحقول على ترتيبها الافتراضي إذا عُرِض نموذج ضمن استمارة (في موقع المدير مثلًا) بالرغم من أن هذا الترتيب يمكن تجاهله. يمكن استخدام الوسطاء الشائعة التالية عند التصريح عن أنواع الحقول: help_text: يوفر هذا الوسيط تسميةً نصيةً لاستمارات HTML (في موقع المدير مثلًا) كما وضّحنا سابقًا. verbose_name: اسم يمكن أن يقرأه الإنسان للحقل المُستخدَم في تسميات الحقل، وإذا لم يُحدَّد، فسيستنتج جانغو الاسم المُطوَّل Verbose Name الافتراضي من اسم الحقل. default: القيمة الافتراضية للحقل، ويمكن أن يكون هذا الوسيط قيمةً أو كائنًا يمكن استدعاؤه، إذ سيُستدعَى الكائن في هذه الحالة في كل مرة يُنشَأ فيها سجل جديد. null: إذا كانت قيمة هذا الوسيط True، فسيخزّن جانغو القيم الفارغة على أنها NULL في قاعدة البيانات للحقول التي يكون ذلك مناسبًا لها، وسيخزّن الحقل من النوع CharField سلسلة نصية فارغة بدلًا من ذلك. القيمة الافتراضية لهذا الوسيط هي False. blank: إذا كانت قيمة هذا الوسيط True، فسيُسمَح للحقل بأن يكون فارغًا في استماراتك. القيمة الافتراضية لهذا الوسيط هي False، أي سيجبرك التحقق من صحة استمارة جانغو على إدخال قيمة. يُستخدَم هذا الحقل عادةً مع القيمة null=True، لأنك إذا أردتَ السماح بقيم فارغة، فأنت تحتاج أيضًا أن تكون قاعدة البيانات قادرة على تمثيلها بصورة مناسبة. choices: مجموعة من الاختيارات للحقل، بحيث إذا توفّرت هذه الاختيارات، فستكون أداة الاستمارة المقابلة الافتراضية هي مربع تحديد لهذه الاختيارات بدلًا من حقل النص المعياري. primary_key: إذا كانت قيمة هذا الوسيط True، فسيُضبط الحقل الحالي بوصفه مفتاحًا رئيسيًا للنموذج؛ والمفتاح الرئيسي هو عمود خاص في قاعدة البيانات مخصَّص لتعريف جميع سجلات الجدول المختلفة بطريقة فريدة. إذا لم يُحدَّد أيّ حقل بوصفه مفتاحًا رئيسيًا، فسيضيف جانغو تلقائيًا حقلًا لهذا الغرض. يمكن تحديد نوع حقول المفاتيح الرئيسية المُنشَأة تلقائيًا لكل تطبيق في AppConfig.default_auto_field أو بصورة عامة في إعداد DEFAULT_AUTO_FIELD. ملاحظة: تضبط التطبيقات المُنشَأة باستخدام manage.py نوعَ المفتاح الرئيسي على النوع BigAutoField. يمكنك رؤية ما يلي في الملف catalog/apps.py الخاص بموقع المكتبة المحلية: class CatalogConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' هناك العديد من الخيارات الأخرى، إذ يمكنك الاطلاع القائمة الكاملة لخيارات الحقول في توثيق جانغو. توضح القائمة التالية بعض أنواع الحقول الأكثر استخدامًا: يُستخدَم النوع CharField لتعريف سلاسل نصية ذات طول ثابت قصير إلى متوسط الحجم. يجب عليك تحديد الطول الأقصى max_length للبيانات المراد تخزينها. يُستخدَم النوع TextField للسلاسل النصية الكبيرة ذات الطول العشوائي. يمكنك تحديد الطول الأقصى max_length للحقل، ولكنه لا يُستخدَم إلا عند عرض الحقل في استمارات (ليس إجباريًا على مستوى قاعدة البيانات). النوع IntegerField هو حقل لتخزين القيم الصحيحة (عدد صحيح)، وللتحقق من صحة القيم المدخَلة بوصفها أعدادًا صحيحة في الاستمارات. يُستخدَم النوعان DateField و DateTimeField لتخزين أو تمثيل التواريخ ومعلومات التاريخ/الوقت، مثل كائنات بايثون datetime.date و datetime.datetime على التوالي. يمكن أن تصرِّح هذه الحقول إضافةً لما سبق عن المعاملات (الحصرية فيما بينها) auto_now=True (لضبط الحقل على التاريخ الحالي في كل مرة يُحفَظ فيها النموذج) و auto_now_add (لضبط التاريخ عند إنشاء النموذج لأول مرة فقط) و default (لضبط تاريخ افتراضي يمكن للمستخدم تجاهله). يُستخدَم النوع EmailField لتخزين عناوين البريد الإلكتروني والتحقق من صحتها. يُستخدَم النوعان FileField و ImageField لتحميل الملفات والصور على التوالي، إذ يضيف الحقل ImageField تحققًا إضافيًا من أن الملف المُحمَّل هو صورة. يمتلك هذان النوعان من الحقول معاملات لتحديد كيفية ومكان تخزين الملفات التي جرى تحميلها. النوع AutoField هو نوع خاص من الحقل IntegerField الذي يزداد تلقائيًا. يُضاف مفتاح رئيسي من هذا النوع تلقائيًا إلى نموذجك إذا لم تحدده صراحةً. يُستخدَم النوع ForeignKey لتحديد علاقة واحد إلى متعدد بنموذج قاعدة بيانات آخر، فمثلًا للسيارة مُصنِّع واحد، ولكن يمكن للمصنِّع صنع العديد من السيارات. الجانب "واحد" من العلاقة هو النموذج الذي يحتوي على المفتاح، وتشير النماذج التي تحتوي على مفتاح خارجي Foreign Key إلى ذلك المفتاح، إذ تكون هذه النماذج في الجانب "متعدد" من هذه العلاقة. يُستخدَم النوع ManyToManyField لتحديد علاقة متعدد إلى متعدد، فمثلًا يمكن أن يكون للكتاب عدة أنواع ويمكن أن يحتوي كل نوع على عدة كتب. سنستخدم هذا النوع في تطبيق المكتبة بطريقة مشابهة جدًا للنوع ForeignKeys، ولكن يمكن استخدامه بطرق أكثر تعقيدًا لوصف العلاقات بين المجموعات. يحتوي هذا النوع على المعامل on_delete لتحديد ما يحدث عند حذف السجل المرتبط به مثل قيمة models.SET_NULL التي تضبط القيمة على NULL. هناك العديد من الأنواع الأخرى من الحقول بما في ذلك حقول أنواع مختلفة من الأعداد (الأعداد الصحيحة الكبيرة والأعداد الصحيحة الصغيرة والأعداد العشرية) والقيم المنطقية وعناوين URL والعناوين الفرعية Slugs والمعرّفات الفريدة وغيرها من المعلومات المتعلقة بالوقت (المدة والوقت وغير ذلك)، ويمكنك الاطلاع على القائمة الكاملة في توثيق جانغو. البيانات الوصفية Metadata يمكنك التصريح عن البيانات الوصفية على مستوى النموذج من خلال التصريح عن الصنف class Meta كما يلي: class Meta: ordering = ['-my_field_name'] تتمثل إحدى الميزات الأكثر فائدة لهذه البيانات الوصفية في التحكم في الترتيب الافتراضي للسجلات المُعادة عند الاستعلام عن نوع النموذج، ويمكنك تطبيق ذلك من خلال تحديد ترتيب المطابقة في قائمة أسماء الحقول مع السمة ordering كما وضّحنا سابقًا. يعتمد الترتيب على نوع الحقل، إذ تُفرَز الحقول المحرفية أبجديًا، بينما تُفرَز حقول التاريخ بترتيب زمني، كما يمكنك أن تسبق اسم الحقل بإشارة ناقص (-) لعكس ترتيب الفرز. إذا اخترنا مثلًا فرز الكتب افتراضيًا كما يلي: ordering = ['title', '-pubdate'] فستُفرَز الكتب أبجديًا حسب العنوان من A إلى Z، ثم حسب تاريخ النشر ضمن كل عنوان من الأحدث إلى الأقدم. هناك سمة شائعة أخرى هي verbose_name، وهي اسم مطوَّل للصنف بصيغة المفرد والجمع: verbose_name = 'BetterName' تسمح لك السمات المفيدة الأخرى بإنشاء وتطبيق أذونات وصول جديدة للنموذج (تُطبَّق الأذونات الافتراضية تلقائيًا)، أو السماح بالترتيب بناءً على حقل آخر، أو التصريح عن أن الصنف "مجرد Abstract"، أي صنف أساسي لا يمكنك إنشاء سجلات له، وستُشتَق بدلًا من ذلك لإنشاء نماذج أخرى. تتحكم العديد من خيارات البيانات الوصفية الأخرى في قاعدة البيانات التي يجب استخدامها مع النموذج وكيفية تخزين البيانات، وهي مفيدة حقًا إذا كنت بحاجة إلى ربط نموذج مع قاعدة بيانات موجودة مسبقًا. يمكنك الاطلاع على القائمة الكاملة لخيارات بيانات النموذج الوصفية في توثيق جانغو. التوابع Methods يمكن أن يحتوي النموذج أيضًا على توابع، إذ يجب عليك في كل نموذج على الأقل تعريف تابع لصنف بايثون المعياري ‎__str__()‎ لإعادة سلسلة نصية يمكن يقرأها الإنسان لكل كائن، إذ تُستخدَم هذه السلسلة النصية لتمثيل السجلات الفردية في موقع المدير وفي أيّ مكان آخر تحتاج فيه للإشارة إلى نسخة من النموذج. يعيد هذا التابع غالبًا حقل العنوان أو الاسم من النموذج. def __str__(self): return self.field_name هناك تابع آخر شائع لتضمينه في نماذج جانغو وهو التابع get_absolute_url()‎ الذي يعيد عنوان URL لعرض سجلات النماذج على موقع الويب. إذا حدّدتَ هذا التابع، فسيضيف جانغو تلقائيًا زر "عرض على الموقع View on Site" إلى شاشات تعديل سجل النموذج في موقع المدير. يوضّح ما يلي نمطًا معياريًا للتابع get_absolute_url()‎: def get_absolute_url(self): """Returns the URL to access a particular instance of the model.""" return reverse('model-detail-view', args=[str(self.id)]) ملاحظة: يجب إنشاء رابط Mapper عنوان URL لتمرير الاستجابة والمعرّف إلى "عرض النموذج التفصيلي" (الذي سينفّذ العمل المطلوب لعرض السجل) بافتراض أنك ستستخدم عناوين URL، مثل "‎/myapplication/mymodelname/2" لعرض سجلات نموذجك، إذ يمثل "2" معرّف id سجل معين. الدالة reverse()‎ السابقة قادرة على عكس رابط عنوان URL (المسماة في الحالة المذكورة سابقًا باسم نموذج-تفاصيل-عرض 'model-detail-View') لإنشاء عنوان URL بالتنسيق الصحيح، ويجب عليك كتابة ربط عنوان URL والعرض والقالب. يمكنك تعريف أيّ توابع أخرى تريدها واستدعاؤها من شيفرتك البرمجية أو قوالبك بشرط ألّا تأخذ أيّ معاملات. إدارة النموذج يمكنك استخدام أصناف نموذجك بعد تعريفها لإنشاء السجلات أو تحديثها أو حذفها ولتشغيل الاستعلامات للحصول على جميع السجلات أو مجموعات فرعية معينة من السجلات. سنوضّح لك كيفية تطبيق ذلك لاحقًا عندما نعرّف عروضنا، ولكن إليك ملخصًا موجزًا. إنشاء وتعديل السجلات يمكنك إنشاء سجل من خلال تعريف نسخة من النموذج ثم استدعاء الدالة save()‎. # ‫أنشِئ سجلًا جديدًا باستخدام باني النموذج record = MyModelName(my_field_name="Instance #1") # احفظ الكائن في قاعدة البيانات record.save() ملاحظة: إذا لم تصرّح عن أي حقل primary_key، فسيُمنَح السجل الجديد حقلًا من هذا النوع تلقائيًا مع معرّف id اسم الحقل، إذ يمكنك الاستعلام عن هذا الحقل بعد حفظ السجل السابق، وسيكون له القيمة 1. يمكنك الوصول إلى الحقول في هذا السجل الجديد باستخدام الصيغة النقطية وتغيير القيم، ويجب عليك استدعاء الدالة save()‎ لتخزين القيم المُعدَّلة في قاعدة البيانات. # الوصول إلى قيم حقل النموذج باستخدام سمات بايثون print(record.id) # يجب أن تعيد القيمة 1 للسجل الأول print(record.my_field_name) # ‫يجب أن تطبع 'Instance #1' # ‫غيّر السجل من خلال تعديل الحقول ثم استدعِ save()‎ record.my_field_name = "New Instance Name" record.save() البحث عن السجلات يمكنك البحث عن السجلات التي تطابق معايير معينة باستخدام سمة النموذج objects، والتي يوفّرها الصنف الأساسي. ملاحظة: يمكن أن يكون شرح كيفية البحث عن السجلات باستخدام أسماء الحقول والنماذج المجردة مربكًا قليلًا، إذ سنشير في المناقشة الآتية إلى النموذج Book باستخدام الحقلين title و genre، بحيث يكون النوع genre نموذجًا له حقل name واحد. يمكننا الحصول على جميع سجلات النموذج بوصفها كائن QuerySet باستخدام الدالة objects.all()‎، إذ يُعَد QuerySet كائنًا تكراريًا، مما يعني أنه يحتوي على عدد من الكائنات الممكن تكرارها. all_books = Book.objects.all() تسمح لنا الدالة filter()‎ من جانغو بترشيح الكائن QuerySet المُعاد لمطابقة نص محدّد، أو حقل عددي مع معايير معينة، فمثلًا يمكننا تطبيق ما يلي لترشيح الكتب التي تحتوي على الكلمة "wild" في عنوانها ثم عَدّها: wild_books = Book.objects.filter(title__contains='wild') number_wild_books = wild_books.count() تُعرَّف الحقول المراد مطابقتها ونوع التطابق في اسم معامل الترشيح باستخدام التنسيق field_name__match_type، ويمكنك ملاحظة الشرطة السفلية المزدوجة بين title و contains. رشّحنا title باستخدام مطابقة حساسة لحالة الأحرف، وهناك العديد من أنواع التطابقات الأخرى الممكن إجراؤها، مثل icontains (غير حساسة لحالة الأحرف) و iexact (مطابقة تامة غير حساسة لحالة الأحرف) و exact (مطابقة تامة حساسة لحالة الأحرف) و in و gt (أكبر من) و startswith وغير ذلك. يمكنك الاطلاع على القائمة الكاملة في توثيق جانغو. ستحتاج في بعض الحالات إلى ترشيح حقل يحدّد علاقة واحد إلى متعدد مع نموذج آخر، مثل ForeignKey، إذ يمكنك في هذه الحالة "فهرسة Index" الحقول ضمن النموذج المتعلق بها باستخدام شرطات سفلية مزدوجة إضافية، لذلك سيتعين عليك فهرسة الاسم name من خلال الحقل genre لترشيح كتب ذات نمط نوع معين كما يلي: # Will match on: Fiction, Science fiction, non-fiction etc. books_containing_genre = Book.objects.filter(genre__name__icontains='fiction') ملاحظة: يمكنك استخدام الشرطين السفليتين __ للتنقل عبر العديد من مستويات العلاقات ForeignKey/ManyToManyField كما تريد، فمثلًا يمكن أن يكون للكتاب Book الذي له أنواع مختلفة ومُعرَّف باستخدام علاقة "cover" معامل اسمٍ هو type__cover__name__exact='hard'‎. هناك الكثير من الأشياء الممكن فعلها باستخدام الاستعلامات، مثل عمليات البحث العكسية من النماذج ذات الصلة وتسلسل المرشّحات وإعادة مجموعة أصغر من القيم وغير ذلك. اطلع على إجراء الاستعلامات في توثيق جانغو لمزيد من المعلومات. تعريف نماذج المكتبة المحلية LocalLibrary سنبدأ في هذا القسم بتعريف نماذج المكتبة، لذا افتح الملف "models.py" ضمن المجلد "/locallibrary/catalog/". تستورد الشيفرة البرمجية المتداولة الموجودة في أعلى الصفحة الوحدة models التي تحتوي على صنف النموذج الأساسي models.Model الذي سترثه نماذجنا. from django.db import models # أنشِئ نماذجك هنا النموذج Genre انسخ شيفرة النموذج Genre البرمجية التالية والصقه في نهاية الملف "models.py" الخاص بك. يُستخدَم هذا النموذج لتخزين المعلومات حول فئة الكتاب مثل كونه خياليًا أو غير خيالي، أو عاطفيًا أو تاريخًا وغير ذلك. أنشأنا النوع genre بوصفه نموذجًا وليس نصًا حرًا أو قائمة اختيار بحيث يمكن إدارة القيم الممكنة عبر قاعدة البيانات بدلًا من أن تكون شيفرة ثابتة. class Genre(models.Model): """Model representing a book genre.""" name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)') def __str__(self): """String for representing the Model object.""" return self.name يحتوي النموذج على حقل واحد من النوع CharField هو name، بحيث يُستخدَم لوصف نوع الكتاب وهو مُحدَّد ليحتوي على 200 محرف ولديه بعض نصوص التعليمات help_text. صرّحنا في نهاية النموذج عن التابع ‎__str__()‎ الذي يعيد اسم النوع الذي يحدّده سجل معين. لم يُحدَّد أيّ اسم مطوَّل لذلك سيُسمَّى الحقل بالاسم Name في الاستمارات. النموذج Book انسخ النموذج Book التالي والصقه في نهاية ملفك، إذ يمثل هذا النموذج جميع المعلومات حول الكتاب المتاح بالمعنى العام، وليس مثيلًا أو نسخةً ماديةً معينة متاحة للإعارة. يستخدم النموذجُ النوعَ CharField لتمثيل الحقلين title و isbn الخاصَين بالكتاب، ولاحظ كيف يضبط المعامل الأول غير المُسمَّى للحقل isbn التسميةَ بصورة صريحة على القيمة "ISBN" وإلّا فستُضبَط افتراضيًا على القيمة "Isbn". ضبطنا المعامل unique على القيمة true لضمان حصول جميع الكتب على رقم ISBN فريد، إذ يجعل المعامل unique قيمة الحقل فريدةً بصورة عامة في الجدول. يستخدم النموذج النوع TextField للحقل summary، لأن هذا النص يمكن أن يكون طويلًا جدًا. # يُستخدَم لإنشاء عناوين‫ URL من خلال عكس أنماط عنوان URL from django.urls import reverse class Book(models.Model): """Model representing a book (but not a specific copy of a book).""" title = models.CharField(max_length=200) # اُستخدِم المفتاح الخارجي لأنه لا يمكن أن يكون للكتاب سوى مؤلف واحد، ولكن يمكن أن يكون للمؤلفين عدة كتب # المؤلف هو سلسلة نصية وليس كائنًا بسبب عدم النصريح عنه بعد في الملف author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True) summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book') isbn = models.CharField('ISBN', max_length=13, unique=True, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>') # استُخدم حقل‫ ManyToManyField لأن النوع genre يمكن أن يحتوي على العديد من الكتب، ويمكن أن تغطي الكتب العديد من الأنواع.‫ # ‫عُرِّف الصنف Genre، وبالتالي يمكننا تحديد الكائن السابق. genre = models.ManyToManyField(Genre, help_text='Select a genre for this book') def __str__(self): """String for representing the Model object.""" return self.title def get_absolute_url(self): """Returns the URL to access a detail record for this book.""" return reverse('book-detail', args=[str(self.id)]) يكون الحقل genre من النوع ManyToManyField، بحيث يمكن أن يكون للكتاب أنواع متعددة ويمكن أن يحتوي النوع على العديد من الكتب. صُرِّح عن المؤلف على أنه من النوع ForeignKey، لذلك سيكون لكل كتاب مؤلف واحد فقط، ولكن يمكن أن يكون للمؤلف العديد من الكتب. يمكن أن يكون للكتاب مؤلفِين متعددين عمليًا، ولكن هذا غير ممكن في هذا التطبيق. يُصرَّح عن صنف النموذج ذي الصلة في كلا هذين النوعين من الحقول بوصفه أول معامل غير مسمًى باستخدام صنف النموذج أو سلسلة نصية تحتوي على اسم النموذج المرتبط بها، إذ يجب استخدام اسم النموذج بوصفه سلسلة نصية إذا لم يُعرَّف الصنف المرتبط به في هذا الملف قبل الإشارة إليه. المعاملات الأخرى ذات الأهمية في حقل المؤلف author هي null=True الذي يسمح لقاعدة البيانات بتخزين قيمة Null عند عدم تحديد أي مؤلف، و on_delete=models.SET_NULL الذي سيحدد قيمة حقل مؤلف الكتاب إلى Null عند حذف سجل المؤلف المرتبط به. تحذير: يكون المعامل on_delete=models.CASCADE افتراضيًا، مما يعني أنه إذا حُذِف المؤلف، فسيُحذَف هذا الكتاب أيضًا. استخدمنا SET_NULL هنا، ولكن يمكننا أيضًا استخدام PROTECT أو RESTRICT لمنع حذف المؤلف أثناء استخدام أيّ كتاب له. يعرّف النموذج أيضًا التابعَ ‎__str__()‎ باستخدام حقل عنوان الكتاب title لتمثيل سجل Book، إذ يعيد التابع الأخير get_absolute_url()‎ عنوان URL يمكن استخدامه للوصول إلى سجل تفصيلي لهذا النموذج، إذ يجب لتحقيق ذلك تعريف ربط لعنوان URL له الاسم book-detail وتعريف عرض وقالب مرتبط به. النموذج BookInstance انسخ النموذج BookInstance الآتي بعد النماذج الأخرى، إذ يمثل هذا النموذج نسخةً محددةً من كتاب يمكن أن يستعيره شخص ما، ويتضمن معلومات حول ما إذا كانت النسخة متاحة، أو التاريخ المتوقع لاسترجاعها وتفاصيل الطبعة، أو الإصدار ومعرّف فريد للكتاب في المكتبة. ستصبح بعض هذه الحقول والتوابع مألوفةً الآن، إذ يستخدم النموذج BookInstance ما يلي: حقلًا من النوع ForeignKey لتحديد النموذج Book المرتبط به (يمكن أن يكون لكل كتاب نسخ متعددة، ولكن يمكن أن يكون للنسخة كتاب Book واحد فقط). يحدِّد المفتاح on_delete=models.RESTRICT لضمان عدم إمكانية حذف النموذج Book عندما يشير إليه النموذج BookInstance. حقلًا من النوع CharField لتمثيل الطبعة (إصدار محدد) للكتاب. import uuid # مطلوب لنسخ الكتاب الفريدة class BookInstance(models.Model): """Model representing a specific copy of a book (i.e. that can be borrowed from the library).""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library') book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True) imprint = models.CharField(max_length=200) due_back = models.DateField(null=True, blank=True) LOAN_STATUS = ( ('m', 'Maintenance'), ('o', 'On loan'), ('a', 'Available'), ('r', 'Reserved'), ) status = models.CharField( max_length=1, choices=LOAN_STATUS, blank=True, default='m', help_text='Book availability', ) class Meta: ordering = ['due_back'] def __str__(self): """String for representing the Model object.""" return f'{self.id} ({self.book.title})' نصرّح إضافةً إلى ذلك عن بعض الأنواع الجديدة من الحقول وهي: يُستخدَم النوع UUIDField للحقل id لضبطه بوصفه مفتاحًا رئيسيًا primary_key لهذا النموذج. يخصِّص هذا النوع من الحقول قيمةً فريدةً عامة لكل نسخة (قيمة لكل كتاب تجده في المكتبة). يُستخدَم النوع DateField للتاريخ due_back، أي التاريخ المتوقع ليصبح فيه الكتاب متاحًا بعد استعارته أو صيانته، إذ يمكن أن تكون القيمة blank أو null (مطلوبة عندما يكون الكتاب متاحًا). تستخدم بيانات النموذج الوصفية (Class Meta) هذا الحقل لترتيب السجلات عند إعادتها ضمن استعلام. الحقل status من النوع CharField الذي يعرّف قائمة الاختيار أو الخيارات. نعرّف كما ترى صفًا يحتوي على صفوف من أزواج مفتاح-قيمة ونمرّرها إلى وسيط الاختيارات. تُعَد القيمة في زوج مفتاح/قيمة قيمة عرض display value يمكن للمستخدم تحديدها، بينما تكون المفاتيح هي القيم المحفوظة عند تحديد الخيار. ضبطنا قيمة افتراضية هي "m" (للصيانة Maintenance) عند إنشاء الكتب في البداية إذ تكون غير متوفرة قبل تخزينها على الرفوف. يمثل التابع ‎__str__()‎ كائن BookInstance باستخدام مجموعة من المعرّف الفريد وعنوان الكتاب Book المرتبط به. ملاحظة: إليك بعض المعلومات عن لغة بايثون: يمكنك بدءًا من الإصدار 3.6 للغة بايثون استخدام صيغة توليد السلاسل النصية (المعروفة أيضًا باسم f-strings) بالشكل التالي: f'{self.id} ({self.book.title})'‎ استخدمنا سابقًا صيغة السلاسل النصية المُنسَّقة Formatted String التي تُعَد طريقةً صالحةً لتنسيق السلاسل النصية في لغة بايثون، مثل ‎'{0} ({1})'.format(self.id,self.book.title)‎. النموذج Author انسخ نموذج Author التالي بعد الشيفرة البرمجية الموجودة في الملف models.py: class Author(models.Model): """Model representing an author.""" first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) date_of_death = models.DateField('Died', null=True, blank=True) class Meta: ordering = ['last_name', 'first_name'] def get_absolute_url(self): """Returns the URL to access a particular author instance.""" return reverse('author-detail', args=[str(self.id)]) def __str__(self): """String for representing the Model object.""" return f'{self.last_name}, {self.first_name}' يجب أن تكون جميع الحقول والتوابع مألوفةً الآن، إذ يعرِّف هذا النموذج مؤلفًا يحمل اسمًا أول واسم عائلة وتاريخ ميلاد ووفاة (كلاهما اختياري)، ويحدّد أنّ التابع ‎__str__()‎ افتراضيًا يعيد الاسم بالترتيب: اسم العائلة last name ثم الاسم الأول firstname. يعكس التابع get_absolute_url()‎ ربط عنوان URL لتفاصيل المؤلف author-detail للحصول على عنوان URL لعرض مؤلف واحد. إعادة تشغيل عمليات تهجير قاعدة البيانات أنشأتَ حتى الآن جميع نماذجك، لذا أعِد تشغيل عمليات تهجير قاعدة البيانات لإضافتها إلى قاعدة بياناتك كما يلي: python3 manage.py makemigrations python3 manage.py migrate النموذج Language- تحدي تخيل أن أحد المتبرعين المحليين يتبرع بعدد من الكتب الجديدة المكتوبة بلغة أخرى (الفارسية مثلًا)، إذ يكمن التحدي في معرفة أفضل طريقة لتمثيلها في موقع مكتبتنا ثم إضافتها إلى النماذج. إليك بعض الأشياء التي يجب مراعاتها: هل يجب ربط اللغة بالنموذج Book أو BookInstance أو أيّ كائن آخر؟ هل يجب تمثيل اللغات المختلفة باستخدام نموذج، أم حقل نص حر، أم قائمة اختيار ثابتة؟ أضف الحقل بعد أن تقرر ما تريده، ويمكنك أن ترى ما قررناه على GitHub. لا تنسَ أنه يجب إعادة تشغيل عمليات تهجير قاعدة البيانات مرة أخرى لإضافة التغييرات بعد إجراء تغيير على نموذجك. python3 manage.py makemigrations python3 manage.py migrate الخلاصة تعلمنا في هذا المقال كيفية تعريف النماذج، ثم استخدمنا هذه المعلومات لتصميم وتقديم النماذج المناسبة لموقع المكتبة المحلية LocalLibrary. سنحوّل الآن اهتمامنا عن إنشاء الموقع لفترة وجيزة لنتعرّف على موقع إدارة جانغو الذي سيسمح لنا بإضافة بعض البيانات إلى المكتبة، والتي يمكننا عرضها بعد ذلك باستخدام العروض والقوالب التي لم ننشئها بعد. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 3: Using models. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin المقال السابق: تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية كتابة شيفرات بايثون: صيغ شائعة الاستخدام على نحو خاطئ إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات النماذج Models والاستعلام عن البيانات في جانغو كتابة أول تطبيق جانغو - الجزء 2 (توثيق جانغو) إجراء الاستعلامات (توثيق جانغو) مرجع واجهة برمجة تطبيقات QuerySet (توثيق جانغو)
  4. يشرح هذا المقال ما ستتعلمه لبناء موقع ويب باستخدام إطار عمل جانغو Django، ويوفر نظرةً عامةً على مثال موقع المكتبة المحلية الذي سنعمل عليه ونطوّره في المقالات اللاحقة، وسنوضّح كيفية إنشاء مشروع موقع ويب هيكلي skeleton يمثّل الأساس الذي يمكنك ملؤه لاحقًا بالإعدادات والمسارات والنماذج Models والعروض Views والقوالب Templates الخاصة بالموقع. المتطلبات الأساسية: قراءة مقال مدخل إلى إطار عمل جانغو، كما يجب إعداد بيئة تطوير جانغو. الهدف: مقدمة إلى التطبيق العملي الذي سنبنيه في المقالات اللاحقة، وفهم الموضوعات التي سنتناولها، والقدرة على استخدام أدوات جانغو لبدء مشروعات مواقع الويب الجديدة الخاصة بك. سنطور موقع ويب يمكن استخدامه لإدارة دليلٍ لمكتبة محلية، وسنتعلم في هذه السلسلة من المقالات ما يلي: استخدم أدوات جانغو لإنشاء موقع وتطبيق هيكلي. بدء وإيقاف خادم التطوير. إنشاء نماذج لتمثيل بيانات تطبيقك. استخدام موقع مدير جانغو لتعبئة بيانات موقعك. إنشاء عروض لاسترجاع بيانات محددة استجابةً لطلبات مختلفة، وإنشاء قوالب لعرض البيانات بتنسيق HTML في المتصفح. إنشاء روابط Mappers لربط أنماط عناوين URL المختلفة مع عروض محددة. إضافة تصريح المستخدم والجلسات للتحكم في سلوك الموقع والوصول إليه. العمل مع الاستمارات Forms. كتابة شيفرة اختبار تطبيقك البرمجية. استخدام أمان جانغو بفعالية. نشر تطبيقك في بيئة الإنتاج. تعلّمت مسبقًا عن بعض هذه الموضوعات، وتعرّفت إلى بعضها الآخر بإيجاز، ولكن يجب أن تعرف ما يكفي لتطوير تطبيقات جانغو بنفسك في نهاية هذه السلسلة من المقالات. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو موقع المكتبة المحلية LocalLibrary LocalLibrary هو اسم موقع الويب الذي سننشئه ونطوّره في المقالات اللاحقة، والغرض الأساسي من هذا الموقع هو توفير دليل Catalog عبر الإنترنت لمكتبة محلية صغيرة، إذ يمكن للمستخدمين تصفح الكتب المتاحة وإدارة حساباتهم. اختير هذا المثال بعناية لأنه يمكن تغيير حجمه لإظهار الكثير أو القليل من التفاصيل التي نحتاجها، ويمكن استخدامه لإظهار أي ميزة من ميزات جانغو تقريبًا، ويسمح بتوفير مسار إرشادي عبر أهم الوظائف في إطار عمل جانغو كما يلي: سنحدد في المقالات الأولى مكتبة بسيطة للتصفح فقط، إذ يمكن لأعضاء المكتبة استخدامها لمعرفة الكتب المتاحة، مما يتيح استكشاف العمليات المشتركة لكل مواقع الويب تقريبًا، وهي قراءة المحتوى وعرضه من قاعدة بيانات. يتوسّع مثال المكتبة في المقالات اللاحقة لإظهار ميزات جانغو الأكثر تقدمًا، فمثلًا يمكننا توسيع المكتبة للسماح للمستخدمين بحجز الكتب لتوضيح كيفية استخدام الاستمارات ودعم استيثاق المستخدم. سُمِّي هذا المثال بمكتبة محلية لسببٍ ما بالرغم من أنه قابل جدًا للتوسّع. هدفنا من هذا المثال هو إظهار الحد الأدنى من المعلومات التي ستساعدك على بدء استخدام جانغو وتشغيله بسرعة، لذا سنخزّن معلومات عن الكتب ونسخها والمؤلفين والمعلومات الأساسية الأخرى، ولكن لن نخزّن معلومات حول العناصر الأخرى التي يمكن أن تخزّنها المكتبة، ولن نوفّر البنية الأساسية اللازمة لدعم مواقع مكتبات متعددة، أو ميزات مكتبة كبيرة أخرى. سنوفر في هذه السلسلة من المقالات مقتطفات من الشيفرة البرمجية المناسبة لك لنسخها ولصقها في كل مرحلة، وستكون هناك شيفرة برمجية أخرى نأمل أن توسّعها بنفسك مع بعض الإرشادات. إذا واجهتك مشكلة، فيمكنك العثور على كامل النسخة المُطوَّرة من موقع الويب على غيت هب GitHub. حان الوقت لبدء إنشاء مشروع هيكلي بعد أن تعرّفت على موقع المكتبة المحلية وما ستتعلمه. إنشاء موقع ويب هيكلي سنوضح الآن كيفية إنشاء موقع ويب هيكلي يمكنك ملؤه لاحقًا بإعدادات ومسارات ونماذج وعروض وقوالب (سنناقشها في مقالات لاحقة) خاصة بالموقع. اتبع الخطوات التالية للبدء: استخدم أداة django-admin لإنشاء مجلد المشروع وقوالب الملفات الأساسية والملف manage.py الذي يعمل بوصفه سكربتًا لإدارة المشروع. استخدم manage.py لإنشاء تطبيق واحد أو أكثر. سجّل التطبيقات الجديدة لتضمينها في المشروع. صِل رابط Mapper عنوان url / المسار path لكل تطبيق. ملاحظة: يمكن أن يتكون موقع الويب من قسم واحد أو أكثر، مثل الموقع الرئيسي والمدونة والويكي wiki ومنطقة التنزيل download area وغيرها. يشجعك إطار عمل جانغو على تطوير هذه المكونات بوصفها تطبيقات منفصلة، بحيث يمكن إعادة استخدامها لاحقًا في مشاريع مختلفة إذا رغبت في ذلك. نسمّي موقع ويب المكتبة المحلية ومجلدات المشروع بالاسم "locallibrary" ويتضمن تطبيقًا واحدًا يسمى "catalog"، لذا ستكون بنية مجلد المستوى الأعلى كما يلي: locallibrary/ # مجلد موقع الويب manage.py # ‫سكربت لتشغيل أدوات جانغو لهذا المشروع (أُنشِئ باستخدام django-admin) locallibrary/ # ‫مجلد الموقع أو المشروع (أُنشِئ باستخدام django-admin) catalog/ # ‫مجلد التطبيق (أُنشِئ باستخدام manage.py) تناقش الأقسام التالية خطوات العملية بالتفصيل، وتوضح كيفية اختبار تعديلاتك، وسنناقش في نهاية هذا المقال إعدادًا آخر على مستوى الموقع يمكن تطبيقه حاليًا. إنشاء المشروع يمكنك إنشاء المشروع باتباع الخطوات التالية: أولًا، افتح صدفة الأوامر Command Shell أو نافذة طرفية Terminal، وتأكد من أنك في بيئتك الافتراضية. ثانيًا، انتقل إلى المكان الذي تريد تخزين تطبيقات جانغو فيه (اجعله في مكان يسهل العثور عليه مثل مجلد المستندات)، وأنشئ مجلدًا لموقع الويب الجديد (django_projects في هذه الحالة)، ثم انتقل إلى المجلد الذي أنشأته كما يلي: mkdir django_projects cd django_projects ثالثًا، أنشئ المشروع الجديد باستخدام الأمر django-admin startproject، ثم انتقل إلى مجلد المشروع كما يلي: django-admin startproject locallibrary cd locallibrary تنشئ الأداة django-admin بنية مجلد أو ملف على النحو التالي: locallibrary/ manage.py locallibrary/ __init__.py settings.py urls.py wsgi.py asgi.py يجب أن يبدو مجلد العمل الحالي كما يلي: ../django_projects/locallibrary/ يُعَد مجلد المشروع الفرعي locallibrary نقطة الدخول إلى موقع الويب، إذ يحتوي على الملفات التالية: "‎init.py"، وهو ملف فارغ يوجّه لغة بايثون للتعامل مع هذا المجلد بوصفه حزمة بايثون. يحتوي الملف "settings.py" على جميع إعدادات موقع الويب بما في ذلك تسجيل أيّ تطبيقات ننشئها، وموقع ملفاتنا الساكنة، وتفاصيل إعداد قاعدة البيانات وغير ذلك. يحدد الملف "urls.py" الروابط الخاصة بالموقع لعنوان URL مع العرض URL-to-View، ويمكن أن يحتوي هذا الملف على كامل شيفرة ربط عناوين URL، ولكن من الشائع تفويض بعض الروابط إلى تطبيقات معينة كما سترى لاحقًا. يُستخدَم الملف "wsgi.py" لمساعدة تطبيق جانغو على التواصل مع خادم الويب، إذ يمكنك التعامل مع هذا الملف على أنه نموذج أولي Boilerplate. يُعَد الملف "asgi.py" معيارًا لتطبيقات وخوادم الويب غير المتزامنة الخاصة بلغة بايثون للتواصل مع بعضها بعضًا. واجهة بوابة الخادم غير المتزامنة Asynchronous Server Gateway Interface -أو اختصارًا ASGI- هي الواجهة التالية غير المتزامنة لواجهة بوابة خادم الويب Web Server Gateway Interface -أو اختصارًا WSGI، وتوفر معيارًا لكل من تطبيقات بايثون غير المتزامنة والمتزامنة، بينما قدمت واجهة WSGI معيارًا للتطبيقات المتزامنة فقط. ASGI متوافقة مع الإصدارات السابقة من واجهة WSGI وتدعم العديد من الخوادم وأطر عمل التطبيقات. يُستخدَم سكربت manage.py لإنشاء التطبيقات والعمل مع قواعد البيانات وبدء خادم تطوير الويب. إنشاء تطبيق الدليل catalog شغّل الأمر التالي لإنشاء تطبيق دليل المكتبة catalog الذي سيكون ضمن مشروع locallibrary، وتأكّد من تشغيل هذا الأمر من المجلد نفسه مثل manage.py الخاص بمشروعك: # في نظام لينكس وماك python3 manage.py startapp catalog # في نظام ويندوز py manage.py startapp catalog ملاحظة: تستخدم هذه السلسلة من المقالات صياغة نظامي لينكس وماك أو إس macOS، فإذا كنت تعمل على نظام ويندوز، فيجب عليك استخدام py (أو py -3) بدلًا من python3 عندما ترى أمرًا يبدأ بها. تنشئ الأداة مجلدًا جديدًا وتعبّئه بملفات لأجزاء مختلفة من التطبيق كما هو موضح في المثال الآتي. تُسمَّى معظم الملفات وفقًا للغرض منها، فمثلًا يجب تخزين العروض في الملف views.py والنماذج في الملف models.py والاختبارات في الملف tests.py وإعداد موقع المدير في الملف admin.py وتسجيل التطبيق في الملف apps.py، وتحتوي الملفات على الحد الأدنى من الشيفرة البرمجية المعيارية للعمل مع الكائنات المرتبطة بها. يجب أن يبدو مجلد المشروع المُحدَّث كما يلي: locallibrary/ manage.py locallibrary/ catalog/ admin.py apps.py models.py tests.py views.py __init__.py migrations/ لدينا أيضًا ما يلي: المجلد "migrations" الذي يُستخدَم لتخزين عمليات التهجير Migrations، وهي الملفات التي تتيح لك تحديث قاعدة البيانات تلقائيًا عند تعديل نماذجك. الملف الفارغ "‎init.py" الذي أُنشِئ ليتعرّف جانغو أو بايثون على المجلد بوصفه حزمة بايثون ويسمح باستخدام كائناته في أجزاء أخرى من المشروع. ملاحظة: توجد العروض والنماذج في قائمة الملفات السابقة، ولكن لا يوجد مكان يمكنك من خلاله وضع روابط عناوين URL والقوالب والملفات الساكنة، إذ سنوضح كيفية إنشائها لاحقًا، فهي ليست مطلوبة في جميع مواقع الويب ولكنها مطلوبة في مثالنا. تسجيل التطبيق catalog يجب الآن بعد إنشاء التطبيق تسجيلُه في المشروع بحيث يُضمَّن عند تشغيل أيّ أدوات، مثل إضافة نماذج إلى قاعدة البيانات، إذ تُسجَّل التطبيقات من خلال إضافتها إلى القائمة INSTALLED_APPS في إعدادات المشروع. افتح ملف إعدادات المشروع (django_projects/locallibrary/locallibrary/settings.py) وابحث عن تعريف القائمة INSTALLED_APPS، ثم أضِف سطرًا جديدًا في نهاية القائمة كما يلي: INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # أضِف تطبيقنا الجديد 'catalog.apps.CatalogConfig', #‫أُنشئ هذا الكائن في /catalog/apps.py ] يحدد السطر الجديد كائن إعداد التطبيق (CatalogConfig) الذي أُنشِئ في "‎/locallibrary/catalog/apps.py" عندما أنشأتَ التطبيق. ملاحظة: ستلاحظ أن هناك الكثير من قوائم INSTALLED_APPS الأخرى وقوائم MIDDLEWARE أسفل ملف الإعدادات، مما يتيح دعم موقع مدير جانغو والوظائف التي يستخدمها بما في ذلك الجلسات والاستيثاق وغيرها. تحديد قاعدة البيانات يجب الآن تحديد قاعدة البيانات التي ستُستخدَم للمشروع. يُفضَّل استخدام قاعدة البيانات نفسها للتطوير والإنتاج إن أمكن ذلك، لتجنب اختلافات السلوك البسيطة. يمكنك التعرف على الخيارات المختلفة في قواعد البيانات في توثيق جانغو. سنستخدم قاعدة بياناتSQLite في مثالنا، لأننا لا نتوقع أن نطلب الكثير من الوصول المتزامن إلى قاعدة بيانات توضيحية Demonstration Database التي لا يتطلب إعدادها أيّ عمل إضافي. يمكنك رؤية كيفية إعداد قاعدة البيانات هذه في الملف settings.py: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } لا نحتاج إلى إجراء أيّ إعداد إضافي لأننا نستخدم قاعدة بيانات SQLite. إعدادات المشروع الأخرى يُستخدَم الملف settings.py لضبط عددٍ من الإعدادات الأخرى، ولكن نريد حاليًا فقط تغيير المنطقة الزمنية TIME_ZONE التي يجب أن تكون مساوية لسلسلة نصية من القائمة المعيارية للمناطق الزمنية في قاعدة بيانات TZ، إذ يحتوي العمود TZ في الجدول على القيم التي تريدها. عدّل قيمة المنطقة الزمنية TIME_ZONE إلى إحدى هذه السلاسل النصية المناسبة لمنطقتك الزمنية كما يلي: TIME_ZONE = 'Europe/London' هناك إعدادان آخران لن تغيّرهما الآن، ولكن يجب أن تكون على دراية بهما، وهما: SECRET_KEY: هو مفتاح سري يُستخدَم بوصفه جزءًا من استراتيجية أمان موقع جانغو. إذا لم تحمي هذا الرمز في مرحلة التطوير، فيجب استخدام رمز مختلف (ربما يُقرَأ من متغير بيئة أو ملف) عند وضعه في مرحلة الإنتاج. DEBUG الذي يتيح عرض سجلات تنقيح الأخطاء Debugging عند حدوث الخطأ بدلًا من استجابات رمز حالة HTTP، ويجب ضبطه على القيمة False في مرحلة الإنتاج عندما تكون معلومات تنقيح الأخطاء مفيدة للمهاجمين، ولكن يمكننا حاليًا الاحتفاظ به مضبوطًا على القيمة True. وصل رابط عنوان URL يُنشَأ موقع الويب باستخدام ملف رابط عنوان URL (هو urls.py) في مجلد المشروع، إذ يمكنك استخدام هذا الملف لإدارة جميع روابط عناوين URL الخاصة بك، ولكن من المعتاد تأجيل هذه الروابط للتطبيق المرتبط به. افتح الملف locallibrary/locallibrary/urls.py ولاحظ النص الإرشادي الذي يشرح بعض طرق استخدام رابط عنوان URL. """locallibrary URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] تُدار روابط عنوان URL من خلال المتغير urlpatterns، وهو قائمة بايثون لدوال path()‎، بحيث إما تربط هذه الدالة نمط عنوان URL بعرض معين يُعرَض عند مطابقة النمط، أو تربطه بقائمة أخرى من شيفرة اختبار نمط عنوان URL؛ إذ يصبح النمط في الحالة الثانية "عنوان URL الأساسي" للأنماط المُعرَّفة في الوحدة الهدف. تعرِّف قائمة urlpatterns في البداية دالةً واحدة تربط جميع عناوين URL باستخدام النمط "admin/‎" مع الوحدة admin.site.urls التي تحتوي على تعريفات ربط عناوين URL الخاصة بتطبيق المدير. ملاحظة: الوجهة Route في الدالة path()‎ هي سلسلة نصية تعرِّف نمط عنوان URL لمطابقته، إذ يمكن أن تحتوي هذه السلسلة النصية على متغير مُسمَّى بين قوسي زاوية، مثل 'catalog/<id>/‎'. سيطابق هذا النمط عنوان URL مثل العنوان catalog/any_chars/‎ ويمرِّر any_chars إلى العرض بوصفه سلسلة نصية مع معاملٍ بالاسم id. سنناقش توابع المسار Path Methods وأنماط الوجهة Route Patterns لاحقًا. يمكن إضافة عنصر قائمة جديد إلى القائمة urlpatterns من خلال إضافة الأسطر الآتية إلى أسفل الملف، إذ يتضمن هذا العنصر الجديد الدالة path()‎ التي توجّه الطلبات مع النمط catalog/‎ إلى الوحدة catalog.urls (الملف الذي يحتوي على عنوان URL النسبي catalog/urls.py). # ‎استخدم‫ التابع include()‎ لإضافة مسارات من تطبيق Catalog from django.urls import include urlpatterns += [ path('catalog/', include('catalog.urls')), ] ملاحظة: لاحظ أننا ضمّنا سطر الاستيراد from django.urls import include مع الشيفرة البرمجية التي يستخدمها، لذلك يمكن بسهولة رؤية ما أضفناه، ولكن من الشائع تضمين جميع سطور الاستيراد في أعلى ملف بايثون. لنعيد الآن توجيه عنوان URL الجذر لموقعنا (أي 127.0.0.1:8000) إلى العنوان ‎‏‪‫‎.127.0.0.1:8000‪/catalog/ هذا هو التطبيق الوحيد الذي سنستخدمه في هذا المشروع، ويمكن تحقيق ذلك من خلال استخدام دالة عرض خاصة هي RedirectView، التي تأخذ عنوان URL النسبي الجديد لإعادة التوجيه إلى /catalog/ بوصفه وسيطها الأول عند مطابقة نمط عنوان URL المُحدَّد في الدالة path()‎ (عنوان URL الجذر في حالتنا). ضِف الأسطر التالية في نهاية الملف: #ضِف روابط عنوان‫ URL لإعادة توجيه عنوان URL الأساسي إلى تطبيقنا from django.views.generic import RedirectView urlpatterns += [ path('', RedirectView.as_view(url='catalog/', permanent=True)), ] اترك المعامل الأول لدالة المسار path()‎ فارغًا للإشارة إلى '/'. إذا كتبتَ المعامل الأول بالشكل '/'، فسيعطيك جانغو التحذير التالي عند بدء تشغيل خادم التطوير: System check identified some issues: WARNINGS: ?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'. لا يخدّم جانغو ملفات ساكنة مثل ملفات CSS وجافا سكريبت والصور افتراضيًا، ولكن يمكن أن يكون مفيدًا لخادم الويب للتطوير لفعل ذلك أثناء إنشاء موقعك. يمكنك تفعيل تخديم الملفات الساكنة أثناء التطوير من خلال إضافة الأسطر الآتية وذلك بمثابة إضافةٍ أخيرة إلى رابط عنوان URL. أضف الكتلة النهائية التالية إلى أسفل الملف: # ‫استخدم الدالة static()‎ لإضافة رابط عنوان URL لتخديم الملفات الساكتة أثناء التطوير (فقط) from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ملاحظة: هناك عدد من الطرق لتوسيع قائمة urlpatterns؛ إذ ألحقنا سابقًا عنصر قائمة جديد باستخدام المعامل =+ للفصل بوضوح بين الشيفرة البرمجية القديمة والجديدة، لكن كان بإمكاننا بدلًا من ذلك تضمين ربط النمط الجديد في تعريف القائمة الأصلية كما يلي: urlpatterns = [ path('admin/', admin.site.urls), path('catalog/', include('catalog.urls')), path('', RedirectView.as_view(url='catalog/')), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) أخيرًا، أنشِئ ملفًا ضمن المجلد catalog يُسمَّى urls.py، وضِف النص التالي لتعريف قائمة urlpatterns المستوردة (الفارغة)، وهذا هو المكان الذي سنضيف فيه أنماطنا عند بناء التطبيق. from django.urls import path from . import views urlpatterns = [ ] اختبار إطار عمل الموقع لدينا الآن مشروع هيكلي كامل. لا يفعل موقع الويب أيّ شيء حتى الآن، ولكن الأمر يستحق تشغيله للتأكد من أن أيّ تغييرات أجريناها لم تعطّل شيئًا ما. يجب علينا أولًا قبل ذلك تشغيل تهجير قاعدة البيانات Database Migration، مما يؤدي إلى تحديث قاعدة بياناتنا (لتضمين أيّ نماذج في تطبيقاتنا المُثبَّتة) ويزيل بعض تحذيرات البناء. تشغيل تهجيرات قاعدة البيانات يستخدم جانغو رابط كائنات علائقي Object-Relational-Mapper -أو ORM اختصارًا- لربط تعريفات النموذج في شيفرة جانغو البرمجية مع بنية البيانات التي تستخدمها قاعدة البيانات الأساسية. بما أننا نغيّر تعريفات نماذجنا، فسيتتبّع جانغو التغييرات ويمكنه إنشاء سكربتات تهجير قاعدة البيانات (في /locallibrary/catalog/migrations/) لتهجير بنية البيانات الأساسية تلقائيًا في قاعدة البيانات لمطابقة النموذج. أضاف جانغو تلقائيًا عددًا من النماذج عندما أنشأنا موقع الويب ليستخدمها قسم المدير في الموقع (الذي سنلقي نظرةً عليه لاحقًا). شغّل الأوامر التالية لتعريف الجداول لتلك النماذج في قاعدة البيانات، وتأكد من أنك في المجلد الذي يحتوي على الملف manage.py: python3 manage.py makemigrations python3 manage.py migrate تحذير: يجب تشغيل هذه الأوامر في كل مرة تتغير فيها نماذجك بطريقة تؤثر على بنية البيانات التي يجب تخزينها بما في ذلك إضافة وإزالة النماذج الكاملة والحقول الفردية. ينشئ الأمر makemigrations -لكنه لا يُطبِّق- تهجيرات لجميع التطبيقات المثبتة في مشروعك، إذ يمكنك تحديد اسم التطبيق لتشغيل تهجير لمشروع واحد فقط، مما يمنحك فرصةً للتحقق من الشيفرة البرمجية لهذه التهجيرات قبل تطبيقها. إذا كنت خبيرًا باستخدام جانغو، فيمكنك اختيار تعديلها بعض الشيء. يطبّق الأمر migrate التهجيرات على قاعدة بياناتك، ويتتبّع جانغو التهجيرات المُضافة إلى قاعدة البيانات الحالية. ملاحظة: اطّلع على عمليات التهجير Migrations في توثيق جانغو للحصول على معلومات إضافية حول أوامر التهجير الأقل استخدامًا. تشغيل الموقع يمكنك أثناء التطوير تخديم موقع الويب أولًا باستخدام خادم الويب الخاص بالتطوير، ثم عرضه في متصفح الويب المحلي. ملاحظة: لا يُعَد خادم الويب الخاص بالتطوير قويًا أو ذا أداء كافٍ للاستخدام في الإنتاج، ولكنه طريقة سهلة لتنشيط موقع جانغو وتشغيله أثناء التطوير لمنحه اختبارًا سريعًا مناسبًا، إذ سيخدّم هذا الخادم افتراضيًا الموقعَ لحاسوبك المحلي (http://127.0.0.1:8000/‎)، ولكن يمكنك تحديد حواسيب أخرى على شبكتك لتخديمها. اطلع على django-admin و manage.py: runserver في توثيق جانغو لمزيد من المعلومات. شغّل خادم الويب الخاص بالتطوير من خلال استدعاء الأمر runserver (في مجلد الملف manage.py نفسه): $ python3 manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). March 01, 2022 - 04:08:45 Django version 4.0.2, using settings 'locallibrary.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. يمكنك عرض الموقع بمجرد تشغيل الخادم بالانتقال إلى http://127.0.0.1:8000/‎ في متصفح الويب المحلي. يُفترَض أن ترى صفحة تظهِر خطأ في الموقع وتبدو كما يلي: لا تقلق فهذه الصفحة متوقعة لأنه ليس لدينا أي صفحات أو عناوين URLs معرّفة في الوحدة catalog.urls التي أُعيد توجيهنا إليها عندما حصلنا على عنوان URL لجذر الموقع. ملاحظة: يوضح هذا المثال ميزة جانغو الرائعة، وهي تسجيل تنقيح الأخطاء الآلي، إذ يعرض جانغو شاشة خطأ تحتوي على معلومات مفيدة أو أيّ خطأ ناتج عن الشيفرة البرمجية عندما يتعذر العثور على صفحة، إذ يمكننا في هذه الحالة أن نرى أن عنوان URL المتوفر لا يتطابق مع أيٍّ من أنماط عناوين URL الخاصة بنا كما هو موضَّح. يُوقَف التسجيل في مرحلة الإنتاج (وهو عندما نعرض الموقع على الويب مباشرة)، إذ ستُخدَّم في هذه الحالة صفحة ذات معلومات أقل ولكنها أسهل في الاستخدام. نعلم في هذه المرحلة أن تطبيق جانغو يعمل. ملاحظة: يجب إعادة تشغيل عمليات التهجير وإعادة اختبار الموقع كلما أجريت تغييرات مهمة، ولا يستغرق الأمر وقتًا طويلًا. تحدى نفسك يحتوي المجلد "catalog/‎" على ملفات للعروض والنماذج وأجزاء أخرى من التطبيق. افتح هذه الملفات وافحص النموذج المعياري. أُضيف ربط عناوين URL لموقع المدير في الملف urls.py الخاص بالمشروع كما رأيت سابقًا. انتقل إلى منطقة المدير في متصفحك وشاهد ما يحدث (يمكنك استنتاج عنوان URL الصحيح من الربط). الخلاصة أنشأت الآن مشروعًا هيكليًا كاملًا لموقع الويب، والذي يمكنك الاستمرار في ملؤه بعناوين URL والنماذج والعروض والقوالب. اكتمل هيكل موقع المكتبة المحلية وجرى تشغيله، وحان الوقت الآن لبدء كتابة الشيفرة البرمجية التي تجعل هذا الموقع يفعل ما يفترض به أن يفعل. ترجمة -وبتصرُّف- للمقالين Django Tutorial: The Local Library website و Django Tutorial Part 2: Creating a skeleton website. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models المقال السابق: إعداد بيئة تطوير تطبيقات جانغو Django مدخل إلى إطار عمل الويب جانغو من طرف الخادم تثبيت إطار العمل جانغو على أوبنتو البدء مع إطار العمل جانغو لإنشاء تطبيق ويب كتابة أول تطبيق جانغو - الجزء 1 (توثيق جانغو). التطبيقات (توثيق جانغو) الذي يحتوي على معلومات حول إعداد التطبيقات.
  5. عرفت حتى الآن الغرض من إطار عمل جانغو Django، وسنوضّح الآن كيفية إعداد واختبار بيئة تطوير جانغوعلى أنظمة التشغيل ويندوز ولينكس (أوبنتو) وماك أو إس macOS، إذ يجب أن يعطيك هذا المقال ما تحتاجه لتكون قادرًا على البدء في تطوير تطبيقات جانغو بغض النظر عن نظام التشغيل الذي تستخدمه. المتطلبات الأساسية: معرفة أساسية باستخدام سطر الأوامر أو الطرفية Terminal وكيفية تثبيت الحزم البرمجية على نظام تشغيل حاسوب التطوير خاصتك. الهدف: تشغيل بيئة تطوير جانغو على حاسوبك. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو نظرة عامة إلى بيئة تطوير جانغو يسهّل جانغو Django عملية إعداد حاسوبك للبدء في تطوير تطبيقات الويب. سيشرح هذا القسم ما ستحصل عليه في بيئة التطوير، ويقدّم نظرة عامة على بعض خيارات الضبط والإعداد، وسيوضح الجزء المتبقي من المقال الطريقة الموصَى بها لتثبيت بيئة تطوير جانغو على أنظمة تشغيل أوبنتو وماك أو إس وويندوز وكيفية اختبارها. ما هي بيئة تطوير جانغو؟ بيئة التطوير هي تثبيت جانغو على حاسوبك المحلي الذي يمكنك استخدامه لتطوير واختبار تطبيقات جانغو قبل نشرها في بيئة الإنتاج. الأدوات الرئيسية التي يوفرها جانغو بنفسه هي مجموعة من سكربتات بايثون لإنشاء مشاريع جانغو والعمل معها وخادم ويب بسيط للتطوير يمكنك استخدامه لاختبار تطبيقات جانغو المحلية (أي على حاسوبك وليس على خادم ويب خارجي) على متصفح الويب في حاسوبك. هناك أدوات طرفية أخرى تشكل جزءًا من بيئة التطوير، ولكن لن نتحدث عنها، ويتضمن ذلك أشياءً مثل محرر النصوص، أو بيئة تطوير شاملة IDE لتحرير الشيفرة البرمجية، وأداة إدارة التحكم بالمصادر مثل غيت Git لإدارة النسخ المختلفة من شيفرتك البرمجية بأمان. سنفترض أن محرر نصوص مُثبَّت مسبقًا. خيارات إعداد جانغو يُعَد جانغو مرنًا جدًا من حيث كيفية ومكان تثبيته وإعداده، إذ يمكن أن يكون جانغو: مثبتًا على أنظمة تشغيل مختلفة. مثبتًا من المصدر من نظام إدارة الحزم Python Package Index -أو PyPi اختصارًا- ومن تطبيق مدير الحزم للحاسوب المضيف في كثير من الحالات. مُعَدًا لاستخدام واحدة من عدة قواعد بيانات، والتي يمكن أن تحتاج إلى التثبيت والإعداد بصورة منفصلة. يُشغَّل في بيئة نظام بايثون الرئيسي، أو ضمن بيئات بايثون الافتراضية المنفصلة. يتطلب كل خيار من هذه الخيارات إعدادًا وضبطًا مختلفًا قليلًا، إذ ستوضح الأقسام الفرعية التالية بعض اختياراتك، وسنوضّح لاحقًا كيفية إعداد جانغو على عدد من أنظمة التشغيل، وسنعتمد هذا الإعداد في بقية هذه السلسلة من المقالات. ملاحظة: خيارات التثبيت الممكنة الأخرى موضَّحة في توثيق جانغو الرسمي، وسنقدّم روابطًا إلى التوثيق المناسب لاحقًا. أنظمة التشغيل المدعومة يمكن تشغيل تطبيقات جانغو على أيّ جهاز تقريبًا يمكنه تشغيل لغة بايثون 3 مثل أنظمة ويندوز وماك أو إس ولينكس/ يونيكس وسولاريس Solaris. يجب أن يتمتع أي حاسوب تقريبًا بالأداء اللازم لتشغيل جانغو أثناء عملية التطوير. سنقدم في هذا المقال إرشادات لأنظمة ويندوز وماك أو إس ولينكس/ يونيكس. نسخة بايثون التي يجب استخدامها يمكنك استخدام أيّ نسخة من بايثون يدعمها إصدار جانغو المستهدف. النسخ المسموح بها بالنسبة لإصدار جانغو 4.0.2 هي إصدار بايثون 3.8 إلى 3.10 (اطلع على نسخة بايثون التي يمكن استخدامها مع جانغو). يوصي مشروع جانغو (ويدعم رسميًا) باستخدام أحدث إصدار مدعوم من بايثون. مكان تنزيل جانغو هناك ثلاثة أماكن لتنزيل جانغو، هي: مستودع حزم بايثون Python Package Repository -أو اختصارًا PyPi- باستخدام أداة Pip، وهي أفضل طريقة للحصول على أحدث نسخة مستقرة من جانغو. استخدم نسخةً من مدير حزم حاسوبك، إذ تقدم توزيعات جانغو المُجمَّعة مع أنظمة التشغيل آلية تثبيت مألوفة. يمكن أن تكون النسخة المُضمَّنة قديمة جدًا، ولا يمكن تثبيتها إلا في بيئة بايثون الخاصة بالنظام (وهذا ما لا تريده). التثبيت من المصدر، إذ يمكنك الحصول على أحدث نسخة من جانغو وتثبيتها من المصدر، وهذا غير موصَى به للمبتدئين ولكنه ضروري عندما تكون مستعدًا لبدء المساهمة في جانغو. يوضح هذا المقال كيفية تثبيت جانغو من مستودع حزم PyPi للحصول على أحدث نسخة مستقرة. قاعدة البيانات المستخدمة يدعم جانغو رسميًا قواعد بيانات PostgreSQL و MariaDB و MySQL و Oracle و SQLite، وهناك مكتبات مجتمعية توفر مستويات مختلفة من الدعم لقواعد بيانات SQL و NoSQL الشائعة الأخرى. نوصيك باختيار قاعدة البيانات نفسها لكل من الإنتاج والتطوير، إذ لا تزال هناك مشاكل محتملة يُفضَّل تجنبها بالرغم من أن جانغو يجرّد العديد من الاختلافات في قاعدة البيانات باستخدام رابط الكائنات العلائقي Object-Relational Mapper -أو ORM اختصارًا. سنستخدم في هذا المقال قاعدة بيانات SQLite التي تخزن بياناتها في ملف؛ إذ صُمِّمت قاعدة بيانات SQLite للاستخدام بوصفها قاعدة بيانات خفيفة الوزن ولا يمكنها دعم مستوى عالٍ من التزامن، ولكنها تُعَد اختيارًا ممتازًا للتطبيقات التي تكون للقراءة فقط. ملاحظة: أُعِّد جانغو لاستخدام قاعدة بيانات SQLite افتراضيًا عند بدء مشروع موقع الويب باستخدام الأدوات المعيارية (django-admin)، وتُعَد خيارًا رائعًا عند البدء لأنها لا تتطلب أي إعداد أو ضبط إضافي. التثبيت على مستوى النظام أم في بيئة بايثون الافتراضية تحصل بتثبيت بايثون 3 على بيئة عامة واحدة تشترك فيها جميع شيفرات بايثون البرمجية، ويمكنك تثبيت أيّ حزمة من حزم بايثون التي تريدها في هذه البيئة، ولكن يمكنك تثبيت نسخة معينة واحدة فقط من كل حزمة في الوقت نفسه. ملاحظة: يُحتمَل أن تتعارض تطبيقات بايثون المثبتة في البيئة العامة مع بعضها بعضًا، أي إذا كانت معتمدة على نسخ مختلفة من الحزمة نفسها. إذا ثبَّتَ جانغو في البيئة الافتراضية أو العامة، فستتمكّن فقط من استهداف نسخة واحدة من جانغو على الحاسوب، ويمكن أن يشكّل ذلك مشكلة إذا أردتَ إنشاء مواقع ويب جديدة (باستخدام أحدث نسخة من جانغو) مع الاستمرار في الحفاظ على مواقع الويب التي تعتمد على النسخ القديمة، لذا يشغّل مطورو بايثون/جانغو ذوو الخبرة تطبيقات بايثون في بيئات بايثون الافتراضية المستقلة، مما يتيح العديد من بيئات جانغو المختلفة على حاسوب واحد. يوصي فريق مطوري جانغو باستخدام بيئات بايثون الافتراضية. تفترض هذه السلسلة من المقالات أنك ثبَّتَ جانغو في بيئة افتراضية، وسنوضح فيما يلي كيفية ذلك. تثبيت لغة بايثون 3 يجب تثبيت بايثون على نظام التشغيل لاستخدام جانغو، وإذا كنت تستخدم بايثون 3، فستحتاج أداة الخاصة بمستودع حزم بايثون Python Package Index وهي pip3 التي تُستخدَم لإدارة (تثبيت وتحديث وحذف) حزم أو مكتبات بايثون التي تستخدمها تطبيقات جانغو وتطبيقات بايثون الأخرى. يشرح هذا القسم بإيجاز كيفية التحقق من نسخ بايثون الموجودة مسبقًا ويوضح طريقة تثبيت نسخ جديدة حسب الحاجة لنظام التشغيل أوبنتو لينكس 20.04 ونظام ماك أو إس ونظام ويندوز 10. ملاحظة: يمكن أن تتمكن من تثبيت بايثون وأداة pip من مدير الحزم الخاص بنظام التشغيل، أو عبر آليات أخرى اعتمادًا على منصتك التي تستخدمها، إذ يمكنك تنزيل ملفات التثبيت المطلوبة من موقع بايثون وتثبيتها باستخدام الطريقة المناسبة الخاصة بالمنصة. تثبيت لغة بايثون على نظام التشغيل لينكس أوبنتو يتضمن نظام أوبنتو لينكس LTS‏ 20.04 بايثون 3.8.10 افتراضيًا، ويمكنك التأكّد من ذلك من خلال تشغيل الأمر التالي في طرفية باش Bash: python3 -V Python 3.8.10 ليست الأداة pip3 الخاصة بمستودع حزم بايثون التي ستحتاجها لتثبيت حزم بايثون 3 (بما في ذلك جانغو) متوفرة افتراضيًا، إذ يمكنك تثبيت pip3 في طرفية باش باستخدام الأمر التالي: sudo apt install python3-pip ملاحظة: تُعَد بايثون 3.8 أقدم نسخة يدعمها جانغو 4.0. يوصي إطار عمل جانغو بالتحديث إلى أحدث نسخة، ولكنك لا تحتاج إلى استخدام أحدث نسخة في هذه السلسلة من المقالات. إذا أردت تحديث بايثون، فيمكنك البحث عن التعليمات على الإنترنت. تثبيت لغة بايثون على نظام ماك أو إس لا تتضمن النسخة إل كابيتان El Capitan من نظام ماك أو إس والنسخ الأحدث الأخرى لغةَ بايثون 3، ويمكنك التأكّد من ذلك من خلال تشغيل الأوامر التالية في طرفية zsh أو طرفية باش: $ python3 -V python3: command not found يمكنك بسهولة تثبيت بايثون 3 (إضافةً إلى أداة pip3) من موقع بايثون كما يلي: نزّل برنامج التثبيت المطلوب: انتقل إلى موقع بايثون الرسمي. نزّل أحدث نسخة مدعومة تعمل مع جانغو 4.0.2 (وهي نسخة بايثون 3.10.2 في وقت كتابة هذا المقال). حدّد موقع الملف باستخدام مدير الملفات فايندر Finder، وانقر نقرًا مزدوجًا على ملف الحزمة، ثم اتبع خطوات التثبيت. يمكنك الآن تأكيد التثبيت الناجح من خلال التحقق من نسخة بايثون 3 كما يلي: python3 -V Python 3.10.2 يمكنك التحقق من تثبيت أداة pip3 من خلال سرد الحزم المتاحة: pip3 list تثبيت لغة بايثون على نظام ويندوز 10 أو 11 لا يتضمن نظام ويندوز لغة بايثون افتراضيًا، ولكن يمكنك تثبيتها بسهولة (إضافةً إلى أداة pip3) من موقع بايثون كما يلي: نزّل برنامج التثبيت المطلوب بالطريقة التالية: انتقل إلى موقع بايثون. نزّل أحدث نسخة مدعومة تعمل مع جانغو 4.0.2 (وهي نسخة بايثون 3.10.2 في وقت كتابة هذا المقال). ثبّت بايثون بالنقر المزدوج على الملف الذي نزّلته واتبّع خطوات التثبيت. تأكد من تحديد المربع المسمَّى "إضافة بايثون إلى المسار Add Python to PATH". يمكنك بعد ذلك التحقق من تثبيت بايثون 3 من خلال إدخال النص التالي في موجّه الأوامر: py -3 -V Python 3.10.2 يضمّن مثبِّت نظام ويندوز الأداة pip3 (مدير حزمة بايثون) افتراضيًا، ويمكنك سرد الحزم المُثبَّتة كما يلي: pip3 list ملاحظة: يجب على المثبِّت إعداد كل ما تحتاجه حتى يعمل الأمر السابق. إذا تلقيتَ رسالةً مفادها أنه لا يمكن العثور على بايثون، فيمكن أن تكون قد نسيت إضافتها إلى مسار نظامك، ويمكنك تطبيق ذلك من خلال تشغيل المثبت مرةً أخرى وتحديد الخيار "تعديل Modify" وتحديد المربع المسمى "إضافة بايثون إلى متغيرات البيئة Add Python to Environment Variables" في الصفحة الثانية. استخدام جانغو ضمن بيئة بايثون الافتراضية المكتبات التي سنستخدمها لإنشاء بيئاتنا الافتراضية هي virtualenvwrapper (لينكس وماك أو إس) وvirtualenvwrapper-win (ويندوز)، إذ تستخدم هاتان المكتبتان أداة virtualenv. تنشئ الأدوات المغلِّفة واجهة متناسقة لإدارة الواجهات على جميع المنصات. تثبيت برمجيات البيئة الافتراضية سنتعرّف الآن على كيفية إعداد البيئة الافتراضية في كل من أنظمة تشغيل أوبنتو وماك وويندوز. إعداد بيئة أوبنتو الافتراضية يمكنك بعد تثبيت بايثون وأداة pip تثبيت المكتبة virtualenvwrapper التي تتضمن المكتبة virtualenv. يمكنك الاطلاع على دليل التثبيت الرسمي أو اتباع الإرشادات التالية. ثبّت الأداة أو المكتبة باستخدام الأداة pip3 كما يلي: sudo pip3 install virtualenvwrapper أضف بعد ذلك الأسطر التالية إلى نهاية ملف بدء تشغيل الصدفة Shell، وهو اسم ملف مخفي "‎.bashrc" في مجلدك الرئيسي. تحدد الأسطر التالية الموقع الذي توجد فيه البيئات الافتراضية وموقع مجلدات مشروع التطوير وموقع السكربت المُثبَّت مع هذه الحزمة: export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 export VIRTUALENVWRAPPER_VIRTUALENV_ARGS=' -p /usr/bin/python3 ' export PROJECT_HOME=$HOME/Devel source /usr/local/bin/virtualenvwrapper.sh ملاحظة: تشير المتغيرات VIRTUALENVWRAPPER_PYTHON و VIRTUALENVWRAPPER_VIRTUALENV_ARGS إلى موقع التثبيت العادي لبايثون 3، ويشير المصدر ‎/usr/local/bin/virtualenvwrapper.sh إلى الموقع الاعتيادي للسكربت virtualenvwrapper.sh. إذا لم تعمل مكتبة virtualenv عند اختبارها، فالشيء الوحيد الذي يجب التحقق منه هو أن بايثون والسكربت موجودان في الموقع المتوقع، ثم غيّر ملف بدء التشغيل بطريقة مناسبة. يمكنك العثور على المواقع الصحيحة لنظامك باستخدام الأوامر which virtualenvwrapper.sh و which python3. أعِد بعد ذلك تحميل ملف بدء التشغيل من خلال تشغيل الأمر التالي في الطرفية: source ~/.bashrc يجب الآن أن ترى مجموعة من السكربتات التي تُشغَّل كما يلي: virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/premkproject virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postmkproject # … virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/preactivate virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postactivate virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/get_env_details يمكنك الآن إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv. إعداد البيئة الافتراضية لنظام ماك أو إس يطابق إعداد المكتبة virtualenvwrapper على نظام ماك أو إس تقريبًا إعدادها على نظام أوبنتو، ويمكنك اتباع التعليمات إما من دليل التثبيت الرسمي، أو من الخطوات التي سنوضحها فيما يلي. ثبّت المكتبة Virtualenvwrapper (وتجميع المكتبة virtualenv) باستخدام الأداة pip كما يلي: sudo pip3 install virtualenvwrapper أضف بعد ذلك الأسطر الآتية إلى نهاية ملف بدء تشغيل الصدفة، وهي السطور في نظام أوبنتو نفسها. إذا كنت تستخدم صدفة zsh، فسيكون ملف بدء التشغيل ملفًا مخفيًا باسم "‎.zshrc" في مجلدك الرئيسي، وإذا كنت تستخدم صدفة باش Bash، فسيكون ملفًا مخفيًا باسم "‎.bash_profile"، وقد تحتاج إلى إنشاء الملف إن لم يكن موجودًا بعد. export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 export PROJECT_HOME=$HOME/Devel source /usr/local/bin/virtualenvwrapper.sh ملاحظة: يشير المتغير VIRTUALENVWRAPPER_PYTHON إلى موقع التثبيت العادي لبايثون 3، ويشير المصدر ‎/usr/local/bin/virtualenvwrapper.sh إلى الموقع الاعتيادي للسكربت virtualenvwrapper.sh. إذا لم تعمل المكتبة virtualenv عند اختبارها، فالشيء الوحيد الذي يجب التحقق منه هو أن بايثون والسكربت موجودان في الموقع المتوقع، ثم غيّر ملف بدء التشغيل بطريقة مناسبة. انتهى اختبار أحد التثبيتات على نظام ماك أو إس مثلًا بالأسطر التالية التي تُعَد ضرورية في ملف بدء التشغيل: export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/Library/Frameworks/Python.framework/Versions/3.7/bin/python3 export PROJECT_HOME=$HOME/Devel source /Library/Frameworks/Python.framework/Versions/3.7/bin/virtualenvwrapper.sh يمكنك العثور على المواقع الصحيحة لنظامك باستخدام الأوامر which virtualenvwrapper.sh و which python3. أعِد بعد ذلك تحميل ملف بدء التشغيل من خلال إجراء الاستدعاء التالي في الطرفية: source ~/.bash_profile يمكن أن ترى الآن مجموعة من السكربتات التي تكون قيد التشغيل (السكربتات الخاصة بتثبيت نظام أوبنتو نفسها)، ويجب أن تكون الآن قادرًا على إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv. ملاحظة: إذا لم تتمكن من العثور على ملف بدء التشغيل لتعديله في أداة البحث، فيمكنك فتحه في الطرفية باستخدام الأمر nano. تبدو الأوامر كما يلي بافتراض أنك تستخدم باش Bash: cd ~ # الانتقال إلى المجلد الرئيسي ls -la #قائمة محتويات المجلد. ‫يجب أن ترى ‎.bash_profile nano .bash_profile # ‫افتح الملف في محرر نصوص نانو nano ضمن الطرفية # انتقل إلى نهاية الملف، وانسخ الأسطر السابقة # استخدم‫ Ctrl + X للخروج من نانو، واختر Y لحفظ الملف. إعداد بيئة ويندوز الافتراضية يُعَد تثبيت مكتبة virtualenvwrapper-win أبسط من إعداد مكتبة virtualenvwrapper، لأنك لا تحتاج إلى إعداد مكان تخزين الأداة لمعلومات البيئة الافتراضية، فهناك قيمة افتراضية، وكل ما عليك فعله هو تشغيل الأمر التالي في موجّه الأوامر: pip3 install virtualenvwrapper-win يمكنك الآن إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv. إنشاء بيئة افتراضية يكون العمل مع البيئات الافتراضية متشابهًا جدًا على جميع المنصات بعد تثبيت المكتبة virtualenvwrapper أو virtualenvwrapper-win. يمكنك الآن إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv، إذ سترى أثناء تشغيل هذا الأمر البيئة التي يجري إعدادها (يكون ما تراه خاصًا بالمنصة). ستكون البيئة الافتراضية الجديدة نشطةً عندما يكتمل الأمر، ويمكنك رؤية ذلك لأن بداية موجّه الأوامر ستكون اسم البيئة بين قوسين، إذ سنعرض ذلك لنظام أوبنتو، ولكن السطر الأخير مشابه لنظام ويندوز وماك أو إس. $ mkvirtualenv my_django_environment Running virtualenv with interpreter /usr/bin/python3 # … virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/t_env7/bin/get_env_details (my_django_environment) ubuntu@ubuntu:~$ أصبحت الآن ضمن البيئة الافتراضية، ويمكنك تثبيت جانغو والبدء في التطوير. ملاحظة: يُرجَى من الآن فصاعدًا في هذا المقال (وهذه السلسلة من المقالات) افتراض أن أيّ أوامر مُشغَّلة ضمن بيئة بايثون الافتراضية تشبه الأمر الذي ضبطناه سابقًا. استخدام البيئة الافتراضية لا يوجد سوى عدد قليل من الأوامر المفيدة الأخرى التي يجب أن تعرفها، إذ يوجد المزيد في توثيق الأدوات، ولكن الأوامر التالية هي التي سنستخدمها بانتظام: deactivate: الخروج من بيئة بايثون الافتراضية الحالية. workon: سرد البيئات الافتراضية المتاحة. workon name_of_environment: تنشيط بيئة بايثون الافتراضية المحددة. rmvirtualenv name_of_environment: إزالة البيئة المحددة. تثبيت جانغو يمكنك استخدام الأداة pip3 لتثبيت جانغو بعد إنشاء بيئة افتراضية واستدعاء الأمر workon للدخول إليها كما يلي: pip3 install django~=4.0 يمكنك اختبار تثبيت جانغو من خلال تشغيل الأمر التالي، وهو مجرد اختبار لإمكانية أن تعثر بايثون على وحدة جانغو: # Linux/macOS python3 -m django --version 4.0.2 # Windows py -3 -m django --version 4.0.2 ملاحظة: إن لم يُظهِر أمر ويندوز السابق وجود وحدة جانغو، فجرّب الأمر التالي: py -m django --version تُشغَّل سكربتات بايثون 3 في ويندوز من خلال أن نضع py -3 في بداية الأمر، بالرغم من أن ذلك يمكن أن يختلف اعتمادًا على التثبيت المحدد، وحاول حذف المعدِّل ‎-3 إذا واجهت أي مشاكل مع الأوامر. بينما يكون الأمر هو python3 في لينكس وماك. ملاحظة: تستخدم هذه السلسلة من المقالات أمر لينكس python3 لاستدعاء بايثون 3، فإذا كنت تعمل على نظام ويندوز، فاستبدل هذه البادئة بالبادئة py -3. أدوات بايثون الأخرى يمكن أن يثبّت مطورو بايثون ذوو الخبرة أدوات إضافية مثل منقحات الصياغة linters التي تساعد في اكتشاف الأخطاء الشائعة في الشيفرة البرمجية. لاحظ أنه يجب عليك استخدام منقّح صياغة جانغو مثل pylint-django. يمكن أن تبلّغ منقحات صياغة بايثون شائعة الاستخدام مثل pylint بصورة غير صحيحة عن أخطاء في الملفات المعيارية التي جرى إنشاؤها لجانغو. اختبار تثبيتك لجانغو Django يعمل الاختبار السابق بطريقة جيدة، ولكنه ليس ممتعًا كثيرًا، فالاختبار الأكثر إثارة للاهتمام هو إنشاء مشروع هيكلي ورؤيته يعمل، ويمكن ذلك من خلال الانتقال أولًا في موجه الأوامر أو الطرفية إلى المكان الذي تريد تخزين تطبيقات جانغو فيه. أنشِئ مجلدًا لموقع اختبارك وانتقل إليه كما يلي: mkdir django_test cd django_test يمكنك بعد ذلك إنشاء موقع هيكلي جديد بالاسم "mytestsite" باستخدام الأداة django-admin كما هو موضح. يمكنك -بعد إنشاء الموقع- الانتقال إلى المجلد حيث ستجد السكربت الرئيسي لإدارة المشاريع والذي يُسمَّى manage.py. django-admin startproject mytestsite cd mytestsite يمكننا تشغيل خادم الويب الخاص بالتطوير من داخل هذا المجلد باستخدام manage.py والأمر runserver كما يلي: $ python3 manage.py runserver Watching for file changes with StatReloader Performing system checks… System check identified no issues (0 silenced). You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. March 01, 2022 - 01:19:16 Django version 4.0.2, using settings 'mytestsite.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ملاحظة: يعرض الأمرُ السابق الأمرَ الخاص بنظام لينكس أو ماك. يمكنك تجاهل التحذيرات بشأن عمليات التهجير غير المُطبَّقة "‎18 unapplied migration(s)‎" حاليًا. يمكنك بمجرد تشغيل الخادم عرض الموقع بالانتقال إلى عنوان URL التالي على متصفح الويب المحلي الخاص بك: http://127.0.0.1:8000/‎، ويجب أن تشاهد موقعًا يشبه ما يلي: الخلاصة لديك الآن بيئة تطوير جانغو التي تعمل على حاسوبك. رأيتَ في قسم الاختبار بإيجاز كيف يمكننا إنشاء موقع جانغو جديد باستخدام الأمر django-admin startproject وتشغيله في متصفحك باستخدام خادم تطوير الويب (python3 manage.py runserver). سنتوسّع في هذه العملية ونبني تطبيق ويب بسيط وكامل في المقال التالي. اطّلع أيضًا على المقالات التالية: تثبيت إطار العمل جانغو على أوبنتو. دليل التثبيت السريع (توثيق جانغو). كيفية تثبيت جانغو - الدليل الكامل (توثيق جانغو): يغطي كيفية إزالة جانغو أيضًا. كيفية تثبيت جانغو على نظام ويندوز (توثيق جانغو). ترجمة -وبتصرُّف- للمقال Setting up a Django development environment. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية المقال السابق: مدخل إلى إطار عمل الويب جانغو من طرف الخادم إعداد تطبيق جانغو جاهز للنشر مع قاعدة بيانات Postgres وخادم Nginx و Gunicorn البدء مع إطار العمل جانغو لإنشاء تطبيق ويب حزم بايثون الثمانية التي تسهل تعاملك مع Django
  6. يُعد جانغو Django إطار عمل ويب شائع جدًا من طرف الخادم ويمتلك ميزات كاملة وهو مكتوب بلغة البرمجة بايثون. سنوضح في هذه السلسلة من المقالات سبب كون جانغو أحد أكثر إطارات عمل خادم الويب شيوعًا وكيفية إعداد بيئة التطوير والبدء في استخدامه لإنشاء تطبيقات الويب. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو المتطلبات الأساسية لست بحاجة إلى معرفة إطار عمل جانغو Django قبل البدء، ولكنك تحتاج إلى فهم ماهية برمجة الويب من طرف الخادم وأطر الويب من خلال الاطلاع على سلسلة مقالات الخطوات الأولى لبرمجة موقع الويب من طرف الخادم. يُوصَى بمعرفة مفاهيم البرمجة ولغة بايثون، ولكنها ليست ضرورية لفهم المفاهيم الأساسية. ملاحظة: تُعَد لغة بايثون إحدى أسهل لغات البرمجة ليقرأها ويفهمها المبتدئون، ولكن إذا أردت فهم هذه السلسلة من المقالات بصورة أفضل، فيمكنك الاطلاع على توثيق لغة بايثون في موسوعة حسوب، وقراءة كتاب البرمجة بلغة بايثون من أكاديمية حسوب. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو من طرف الخادم: سنجيب في مقالنا الأول على سؤال "ما هو إطار عمل جانغو؟" وسنقدّم نظرةً عامة على ما يجعله إطار عمل مميز، وسنحدّد الميزات الرئيسية بما في ذلك بعض الوظائف المتقدمة التي لن يكون لدينا الوقت لتغطيتها بالتفصيل. سنوضّح أيضًا بعض أساسيات تطبيق جانغو لإعطائك فكرة عمّا يمكنه تطبيقه قبل إعداده والبدء به. إعداد بيئة تطوير جانغو: سنوضّح بعد معرفة الغرض من إطار عمل جانغو كيفية إعداد واختبار بيئة تطويره على أنظمة تشغيل ويندوز ولينكس (أوبونتو Ubuntu) وماك أو إس MacOS. يجب أن يعطيك هذا المقال ما تحتاج إليه لتكون قادرًا على البدء في تطوير تطبيقات جانغو بغض النظر عن نظام التشغيل الشائع الذي تستخدمه. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية: يشرح هذا المقال ما ستتعلمه، ويوفر نظرةً عامة على موقع "المكتبة المحلية Local Library"، وهو مثال لموقع الويب الذي سنعمل من خلاله ونطوّره في مقالات لاحقة. يوضح هذا المقال كيفية إنشاء مشروع موقع الويب الهيكلي، والذي يمكنك بعد ذلك ملؤه بالإعدادات الخاصة بالموقع وعناوين URL والنماذج والعروض والقوالب. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الثاني: استخدام النماذج Models: يوضح هذا المقال كيفية تحديد النماذج Models لموقع ويب المكتبة المحلية؛ إذ تمثل النماذج بنى البيانات التي نريد تخزين بيانات التطبيق فيها، وتسمح لجانغو بتخزين البيانات في قاعدة بياناتنا وتعديلها لاحقًا. يشرح هذا المقال أيضًا مفهوم النموذج وكيفية التصريح عنه وبعض أنواع الحقول الرئيسية، ويعرض بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الثالث: موقع مدير جانغو: أنشأنا نماذج موقع ويب المكتبة المحلية، وسنستخدم الآن موقع مدير جانغو Django Admin لإضافة بعض بيانات الكتب الحقيقية، إذ سنوضح أولًا كيفية تسجيل النماذج في موقع المدير، ثم سنوضح كيفية تسجيل الدخول وإنشاء بعض البيانات، وسنعرض بعض الطرق التي يمكنك من خلالها تحسين عرض موقع المدير. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الرابع: إنشاء الصفحة الرئيسية: نحن الآن جاهزون لإضافة الشيفرة البرمجية لعرض صفحتنا الأولى الكاملة، وهي الصفحة الرئيسية للمكتبة المحلية التي تعرض عددًا من السجلات لكل نوعٍ من النماذج وتوفر روابط تنقّل جانبية إلى صفحاتنا الأخرى، وسنكتسب خبرةً عملية في كتابة روابط عناوين URL والعروض الأساسية والحصول على السجلات من قاعدة البيانات واستخدام القوالب. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الخامس: عروض القائمة المعممة والعروض التفصيلية: سنوسّع في هذا المقال موقع المكتبة المحلية من خلال إضافة قائمة وصفحات تفصيلية للكتب والمؤلفين، إذ سنتعرف على العروض المعمَّمة Generic Views المستندة إلى الأصناف، وسنوضّح كيف يمكنها تقليل مقدار الشيفرة البرمجية التي يجب عليك كتابتها لحالات الاستخدام الشائعة، وسننتقل إلى معالجة عناوين URL بمزيد من التفصيل مع توضيح كيفية إجراء مطابقة الأنماط الأساسية. تطبيق عملي لتعلم إطار عمل جانغو - الجزء السادس: إطار عمل الجلسة: سنوسّع في هذا المقال موقع المكتبة المحلية من خلال إضافة عدّاد زيارات مستند إلى جلسات الصفحة الرئيسية، إذ يُعَد ذلك مثالًا بسيطًا نسبيًا، ولكنه يوضّح كيفية استخدام إطار عمل الجلسة لتوفير سلوك مستمر للمستخدمين المجهولين على مواقعك الخاصة. تطبيق عملي لتعلم إطار عمل جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم: سنشرح في هذا المقال كيفية السماح للمستخدمين بتسجيل الدخول إلى موقعك بحساباتهم الخاصة وكيفية التحكم بما يمكنهم فعله ومشاهدته بناءً على ما إذا قد سجّلوا الدخول أم لا وبناءً على أذوناتهم، إذ سنوسّع موقع ويب المكتبة المحلية من خلال إضافة صفحات تسجيل الدخول والخروج وصفحات خاصة بالمستخدمين والموظفين لعرض الكتب المستعارة. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الثامن: التعامل مع الاستمارات: سنشرح في هذا المقال كيفية التعامل مع استمارات HTML في إطار عمل جانغو وخاصة استخدام أسهل طريقة لكتابة الاستمارات Forms لإنشاء نسخ من النماذج Model وتحديثها وحذفها، إذ سنوسّع موقع الويب للمكتبة المحلية بحيث يمكن لأمناء المكتبة تجديد الكتب وإنشاء المؤلفين وتحديثهم وحذفهم باستخدام الاستمارات بدلًا من استخدام تطبيق المدير. تطبيق عملي لتعلم إطار عمل جانغو - الجزء التاسع: اختبار تطبيق جانغو: يصبح الاختبار اليدوي لمواقع الويب أصعب مع زيادة حجمها، بسبب وجود المزيد لاختباره، إضافةً إلى زيادة تعقيد التفاعلات بين المكونات، إذ يمكن أن يتطلب تغيير بسيط في منطقة واحدة العديدَ من الاختبارات الإضافية للتحقق من تأثيره على مناطق أخرى. تتمثل إحدى طرق التخفيف من هذه المشاكل في كتابة الاختبارات الآلية التي يمكن تشغيلها بسهولة وموثوقية في كل مرة تُجري فيها تغييرًا. يوضّح هذا المقال كيفية جعل اختبار الوحدة Unit Testing لموقع الويب آليًا باستخدام إطار اختبار جانغو. تطبيق عملي لتعلم إطار عمل جانغو - الجزء العاشر: نشر تطبيق جانغو في بيئة الإنتاج: أنشأتَ واختبرتَ موقعًا رائعًا للمكتبة المحلية، ويجب الآن تثبيته على خادم ويب عام حتى يصل إليه موظفو المكتبة وأعضاؤها عبر الإنترنت. يقدّم هذا المقال نظرةً عامةً حول كيفية البحث عن مضيف لنشر موقع الويب وما يجب فعله لتجهيز موقعك للإنتاج. أمان تطبيق جانغو: تُعَد حماية بيانات المستخدم جزءًا أساسيًا من تصميم أيّ موقع ويب، إذ شرحنا سابقًا بعض الهجمات الأمنية الأكثر شيوعًا في مقال أمان مواقع الويب، وسيقدّم هذا المقال شرحًا عمليًا لكيفية تعامل أدوات الحماية المُضمَّنة في إطار عمل جانغو مع هذه الهجمات. يمكنك اختبار فهمك لكيفية إنشاء موقع ويب باستخدام جانغو كما هو موضح في هذه السلسلة من المقالات من خلال إنشاء مدونتك الخاصة. لنبدأ بمقالنا الأول من هذه السلسلة من خلال التعرف على إطار عمل جانغو. مقدمة إلى إطار العمل جانغو سنجيب في مقالنا الأول على سؤال "ما هو جانغو؟" وسنقدّم نظرةً عامةً على ما يجعله إطار عمل ويب مميز، إذ سنحدد الميزات الرئيسية بما في ذلك بعض الوظائف المتقدمة التي لن يكون لدينا الوقت الكافي لتغطيتها بالتفصيل، وسنعرض بعض أساسيات بناء تطبيق جانغو بالرغم من أنه لن يكون لديك بيئة تطوير لاختبارها بعد. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، وفهم عام لبرمجة مواقع الويب من طرف الخادم وخاصةً تفاعلات الخادم مع العميل في مواقع الويب. الهدف: التعرف على إطار عمل جانغو والوظائف التي يوفرها وأساسيات بناء تطبيق جانغو. ما هو جانغو؟ يُعَد جانغو إطار عمل ويب عالي المستوى يستخدم لغة بايثون ويتيح التطوير السريع لمواقع الويب الآمنة والقابلة للصيانة. صمّم مطورون ذوو خبرة إطار عمل جانغو الذي يخلصك من الكثير من متاعب تطوير الويب، بحيث يمكنك التركيز على كتابة تطبيقك دون الحاجة إلى إعادة اختراع العجلة، وهو إطار عمل مجاني ومفتوح المصدر وله مجتمع مزدهر ونشط وتوثيق رائع والعديد من خيارات الدعم المجاني والمدفوع. يساعدك جانغو في كتابة برمجيات تتمتع بالمواصفات التالية: مكتملة: يتبع جانغو فلسفة أنه يتضمن كل شيء "Batteries Included" ويوفر تقريبًا كل ما يرغب المطوّرون في تطبيقه. بما أن كل ما تحتاجه هو جزء من منتج واحد، فإن كل شيء يعمل مع بعضه بعضًا بسلاسة ويتبع مبادئ تصميم متناسقة ويحتوي على توثيق شامل ومُحدَّث. متعددة الاستعمالات: يمكن استخدام جانغو -وقد اُستخدِم فعليًا- لبناء أيّ نوع من مواقع الويب تقريبًا بدءًا من أنظمة إدارة المحتوى ومواقع الويكي مرورًا بشبكات التواصل الاجتماعي والمواقع الإخبارية. يمكن لجانغو العمل مع أيّ إطار عمل من طرف العميل، ويمكنه تقديم المحتوى بأي تنسيق تقريبًا، مثل HTML، و RSS Feeds، و JSON، و XML. يوفّر جانغو داخليًا خيارات لأي وظيفة تقريبًا ترغب فيها مثل دعم العديد من أنواع قواعد البيانات الشائعة ومحركات القوالب وغيرها، ولكن يمكن توسيعه لاستخدام مكونات أخرى إذا لزم الأمر. آمنة: يساعد جانغو المطورين على تجنب العديد من الأخطاء الأمنية الشائعة من خلال توفير إطار عمل مُصمَّم لتطبيق الأشياء الصحيحة بهدف حماية الموقع آليًا، فمثلًا يوفر جانغو طريقةً آمنةً لإدارة حسابات المستخدمين وكلمات المرور وتجنب الأخطاء الشائعة مثل وضع معلومات الجلسة في ملفات تعريف الارتباط Cookies، حيث تكون معرضةً للخطر، وذلك من خلال جعل ملفات تعريف الارتباط cookies تحتوي على مفتاح فقط وتخزين البيانات الفعلية في قاعدة البيانات، أو تخزين كلمات المرور مباشرةً بدلًا من تعمية Hash كلمة المرور التي هي قيمة ذات طول ثابت تُنشَأ من خلال إرسال كلمة المرور عبر دالة تعمية مُشفَّرة Cryptographic Hash Function. يمكن لجانغو التحقق مما إذا كانت كلمة المرور المدخَلة صحيحة من خلال تشغيلها عبر دالة التعمية وموازنة الخرج مع قيمة التعمية المخزنة، ولكن حتى إذا اُخترِقت قيمة التعمية المخزنة، فمن الصعب على المهاجم معرفة كلمة المرور الأصلية نظرًا لطبيعة الدالة أحادية الاتجاه. يتيح جانغو الحماية ضد العديد من الثغرات افتراضيًا بما في ذلك حقن استعلامات SQL وهجمات السكربتات العابرة للمواقع وتزوير الطلبات عبر المواقع والاختطاف بالنقر clickjacking (اطلع على مقال أمان مواقع الويب لمزيد من التفاصيل حول هذه الهجمات). قابلة للتوسّع: يستخدم جانغو معمارية "لا شيء مشترك shared-nothing" القائمة على المكونات، إذ يكون كل جزء من المعمارية مستقلًا عن الأجزاء الأخرى، وبالتالي يمكن استبداله أو تغييره حسب الحاجة. يعني وجود فصل واضح بين الأجزاء المختلفة أنه يمكن توسيع حركة الزوار المتزايدة من خلال إضافة عتاد على أي مستوى من خوادم التخزين المؤقت، أو خوادم قواعد البيانات، أو خوادم التطبيقات. وقد نجحت بعض المواقع الأكثر ازدحامًا في توسيع جانغو لتلبية مطالبهم مثل إنستغرام وديسكاس Disqus. قابلة للصيانة: كُتِبت شيفرة جانغو البرمجية باستخدام مبادئ وأنماط التصميم التي تشجع على إنشاء شيفرة برمجية قابلة للصيانة وإعادة الاستخدام؛ إذ يستخدم جانغو مبدأ لا تكرر نفسك Don't Repeat Yourself -أو DRY اختصارًا، لذلك لا يوجد تكرار غير ضروري، مما يقلل من كمية الشيفرة البرمجية. كما يدعم جانغو تجميع الوظائف المرتبطة ببعضها ضمن تطبيقات قابلة لإعادة الاستخدام، وتجميع الشيفرة البرمجية المرتبطة ببعضها ضمن وحدات على مستوى أدنى مثل نمط نموذج-عرض-مُتحكِّم Model View Controller -أو MVC اختصارًا. قابلة للنقل: كُتِب إطار عمل جانغو باستخدام لغة بايثون التي تعمل على العديد من المنصات، وهذا يعني أنك لست مرتبطًا بأي منصة خوادم معينة، ويمكنك تشغيل تطبيقاتك على العديد من إصدارات لينكس وويندوز وماك أو إس. كما يدعم العديد من مزودي استضافة الويب إطار عمل جانغو بصورة جيدة، ويوفّر مزودو استضافة الويب في أغلب الأحيان بنيةً أساسيةً محدّدة وتوثيقًا لاستضافة مواقع جانغو. تاريخ إطار عمل جانغو طوّر فريقُ ويب كان مسؤولًا عن إنشاء مواقع الصحف وصيانتها إطارَ عمل جانغو في البداية بين عامي 2003 و2005، ثم بدأ الفريق -بعد إنشاء عدد من المواقع- في تحليل وإعادة استخدام الكثير من أنماط التصميم والشيفرة البرمجية المشتركة. تطورت هذه الشيفرة البرمجية المشتركة إلى إطار عمل عام لتطوير الويب، والذي أصبح مشروع جانغو مفتوح المصدر في الشهر 7 من عام 2005. استمر إطار عمل جانغو في النمو والتحسن من الإصدار الأول 1.0 في الشهر التاسع من عام 2008 وحتى الإصدار 4.0 الصادر مؤخرًا في عام 2022. أضاف كل إصدار وظائفًا جديدة وإصلاحات للأخطاء بدءًا من دعم الأنواع الجديدة من قواعد البيانات ومحركات القوالب والتخزين المؤقت وإضافة دوال وأصناف العرض المعمَّمة التي تقلل من مقدار الشيفرة البرمجية التي يجب أن يكتبها المطورون لعددٍ من المهام البرمجية. ملاحظة: تحقَّق من ملاحظات الإصدارات على موقع جانغو لترى ما الذي تغير في الإصدارات الأخيرة ومقدار العمل المبذول في تحسين جانغو. يُعَد جانغو الآن مشروعًا تعاونيًا مزدهرًا ومفتوح المصدر، ولديه عدة آلاف من المستخدمين والمساهمين، وقد تطوّر إلى إطار عمل متعدد الاستخدامات قادر على تطوير أيّ نوع من مواقع الويب، بالرغم من أنه لا يزال يحتوي على بعض الميزات التي تعكس أصله. ما مدى شعبية جانغو؟ لا يوجد أي مقياس نهائي ومتوفر حاليًا لشعبية أطر العمل من طرف الخادم، بالرغم من أنه يمكنك تقدير الشعبية باستخدام آليات، مثل حساب عدد مشاريع غيت هَب GitHub وأسئلة موقع StackOverflow لكل منصة، لذا يُفضَّل أن تسأل ما إذا كان جانغو يتمتع بشعبية كافية لتجنب مشاكل المنصات التي لا تحظى بشعبية، وما إذا كان مستمرًا في التطور ويمكنك الحصول على المساعدة إذا احتجت إليها، وإذا كان هناك فرصة لك للحصول على عمل مدفوع الأجر إذا تعلمت جانغو. يمكن القول أن جانغو إطار عمل شائع الاستخدام استنادًا إلى عدد المواقع البارزة التي تستخدمه وعدد الأشخاص المساهمين في قاعدة الشيفرة البرمجية وعدد الأشخاص الذين يقدمون الدعم المجاني والمدفوع. تشمل المواقع البارزة التي تستخدم جانغو: ديسكاس Disqus وإنستغرام و Knight Foundation و MacArthur Foundation وموزيلا Mozilla وناشيونال جيوغرافيك National Geographic و Open Knowledge Foundation وينترست Pinterest و Open Stack (المصدر: موقع جانغو الرسمي). هل جانغو إطار عمل متشبث برأيه؟ تشير أطر عمل الويب إلى نفسها في أغلب الأحيان على أنها "قائمة على رأيها أو متشبثة برأيها Opinionated" أو أنها "غير قائمة على رأيها Unopinionated"، فأطر العمل القائمة على رأيها هي أطر العمل التي لديها آراء حول الطريقة الصحيحة للتعامل مع أيّ مهمة معينة، وتدعم التطور السريع أو حل المشاكل في مجال معين، إذ تكون الطريقة الصحيحة لفعل أي شيء عادةً مفهومة ومُوثَّقة جيدًا، لكن يمكن أن تكون أقل مرونة في حل المشاكل خارج مجالها الرئيسي، وتميل إلى تقديم خيارات أقل للمكونات والأساليب التي يمكن استخدامها. يكون لأطر العمل غير القائمة على رأيها قيود أقل بكثير على أفضل طريقة لربط المكونات مع بعضها بعضًا لتحقيق هدفٍ ما أو حتى لتحديد المكونات التي يجب استخدامها، وتسهّل على المطورين استخدام أنسب الأدوات لإكمال مهمة معينة وإن كان ذلك على حساب الجهد الذي تحتاجه للعثور على تلك المكونات بنفسك. يُعَد إطار عمل جانغو قائمًا على رأيه إلى حدٍ ما، وبالتالي يقدم أفضل ما في هذين النوعين، إذ يوفر مجموعةً من المكونات للتعامل مع معظم مهام تطوير الويب وطريقة أو طريقتين مفضلتين لاستخدامهما، ولكن تعني معمارية جانغو المنفصلة أنه يمكنك الانتقاء والاختيار من بين عدد من الخيارات المختلفة، أو إضافة دعم لخيارات جديدة تمامًا إذا رغبت في ذلك. كيف تبدو شيفرة جانغو البرمجية؟ ينتظر تطبيق الويب طلبات HTTP من متصفح الويب (أو عميل آخر) في موقع ويب تقليدي مُوجَّه بالبيانات، إذ يعمل التطبيق عند تلقي طلب على تحديد ما هو مطلوب بناءً على عنوان URL وربما على المعلومات الموجودة في بيانات طلب POST أو بيانات طلب GET، ثم يمكنه بعد ذلك اعتمادًا على ما هو مطلوب قراءة المعلومات، أو كتابتها من قاعدة بيانات، أو أداء مهام أخرى مطلوبة لتلبية الطلب، ثم سيعيد التطبيق استجابةً إلى متصفح الويب، وينشئ غالبًا صفحة HTML ديناميكيًا ليعرضها المتصفح من خلال إدخال البيانات المُسترجَعة ضمن عناصر بديلة في قالب HTML. تجمّع تطبيقاتُ ويب جانغو الشيفرةَ البرمجية التي تعالج كل خطوة من الخطوات السابقة ضمن ملفات منفصلة كما يلي: عناوين URL: يمكن معالجة الطلبات من كل عنوان URL باستخدام دالة واحدة، ولكن تُعَد كتابة دالة عرض منفصلة للتعامل مع كل مورد أمرًا قابلًا للصيانة بصورة أفضل، إذ يُستخدَم رابط Mapper عنوان URL لإعادة توجيه طلبات HTTP إلى العرض المناسب بناءً على عنوان URL الخاص بالطلب، ويمكن لرابط عنوان URL مطابقة أنماط معينة من السلاسل النصية أو الأرقام التي تظهر في عنوان URL وتمريرها إلى دالة عرض بوصفها بيانات. العرض View: العرض هو دالة معالج الطلب والتي تتلقى طلبات HTTP وتعيد استجابات HTTP. تصل العروض إلى البيانات اللازمة لتلبية الطلبات باستخدام النماذج وتكلّف القوالب Templates بتنسيق الاستجابة. النموذج Model: النماذج هي كائنات بايثون تعرّف بنية بيانات التطبيق وتوفّر آليات لإدارة (إضافة وتعديل وحذف) السجلات والاستعلام عنها في قاعدة البيانات. القالب Template: القالب هو ملف نصي يعرّف بنية أو تخطيط ملف، مثل صفحة HTML، مع استخدام العناصر البديلة لتمثيل المحتوى الفعلي. يمكن للعرض أن ينشئ صفحة HTML ديناميكيًا باستخدام قالب HTML وتعبئتها ببيانات من نموذج، ويمكن استخدام القالب لتعريف بنية أيّ نوع من الملفات، فليس بالضرورة أن يكون ملف HTML. ملاحظة: يشير جانغو إلى هذا التنظيم بأنه معمارية نموذج-عرض-قالب Model View Template -أو MVT اختصارًا، إذ تمتلك هذه المعمارية العديد من أوجه التشابه مع معمارية نموذج-عرض-مُتحكِّم MVC الأكثر شيوعًا. ستمنحك الأقسام الآتية فكرةً عمّا تبدو عليه هذه الأجزاء الرئيسية من تطبيق جانغو، وسنعرض مزيدًا من التفاصيل لاحقًا بعد إعداد بيئة التطوير. إرسال الطلب إلى العرض الصحيح: الملف urls.py يُخزَّن رابط عنوان URL في ملف يسمى urls.py، إذ يعرّف الرابط urlpatterns في المثال الآتي قائمةً من الروابط بين الوجهات Routes (وهي أنماط Patterns عنوان URL مُحدَّدة) ودوال العرض المقابلة لها. إذا اُستلِم طلب HTTP يحتوي على عنوان URL يطابق نمطًا محددًا، فستُستدعَى دالة العرض المرتبطة به ويُمرَّر الطلب. urlpatterns = [ path('admin/', admin.site.urls), path('book/<int:id>/', views.book_detail, name='book_detail'), path('catalog/', include('catalog.urls')), re_path(r'^([0-9]+)/$', views.best), ] كائن urlpatterns هو قائمة من دوال path()‎ و/ أو re_path()‎. تُعرَّف قوائم بايثون باستخدام أقواس مربعة، إذ تُفصَل العناصر بفواصل ويمكن أن تحتوي على فاصلة لاحقة اختيارية مثل [item1, item2, item3,‎]. الوسيط الأول لكلا التابعين هو الوجهة أو النمط الذي ستجري مطابقته. يستخدم التابع path()‎ أقواس زاوية لتحديد أجزاء من عنوان URL التي ستُلتقَط وتُمرَّر إلى دالة العرض بوصفها وسائطًا مُسمَّاة، بينما تستخدم الدالة re_path()‎ أسلوبًا مرنًا لمطابقة الأنماط يُعرَف بالتعبير النمطي Regular Expression الذي سنتحدث عنه لاحقًا. الوسيط الثاني هو دالة أخرى تُستدعَى عند مطابقة النمط، إذ تشير الصيغة views.book_detail إلى أن الدالة تسمى book_detail()‎ ويمكن العثور عليها في الوحدة views، أي ضمن الملف views.py. معالجة الطلب: الملف views.py تُعَد العروض قلب تطبيق الويب، فهي تتلقى طلبات HTTP من عملاء الويب وتعيد استجابات HTTP، وتنظّم الموارد الأخرى لإطار العمل للوصول إلى قواعد البيانات وعرض القوالب وغير ذلك. وتُخزَّن العروض عادةً في ملف يسمى views.py. يوضّح المثال الآتي أصغر دالة عرض index()‎ التي كان من الممكن أن يستدعيها رابط عنوان URL في القسم السابق؛ إذ تتلقى هذه الدالة مثل جميع دوال العرض كائن HttpRequest بوصفه معاملًا request وتعيد كائن HttpResponse، إذ لا نفعل أيّ شيء مع الطلب في هذه الحالة، وتعيد استجابتنا سلسلةً نصيةً ثابتة Hard-coded، وسنعرض لاحقًا طلبًا يفعل شيئًا آخر أكثر إثارة للاهتمام. # اسم الملف‫: views.py (دوال عرض جانغو) from django.http import HttpResponse def index(request): # الحصول على‫ HttpRequest - معامل الطلب # إجراء العمليات باستخدام المعلومات الواردة في الطلب‫. # إعادة‫ HttpResponse return HttpResponse('Hello from Django!') ملاحظة: إليك بعض المعلومات عن لغة بايثون: وحدات بايثون هي مكتبات من الدوال مُخزَّنة في ملفات منفصلة، والتي يمكن أن نرغب في استخدامها في شيفرتنا البرمجية. نستورد في مثالنا كائن HttpResponse من الوحدة django.http حتى نتمكّن من استخدامه في عرضنا from django.http import HttpResponse، وهناك طرق أخرى لاستيراد بعض الكائنات أو جميعها من وحدةٍ ما. يُصرَّح عن الدوال باستخدام الكلمة المفتاحية def كما هو موضح سابقًا، مع وجود معاملات مُسمَّاة بين قوسين بعد اسم الدالة وينتهي السطر بأكمله بنقطتين. لاحظ وضع مسافة بادئة لجميع الأسطر التالية، إذ تُعَد المسافة البادئة مهمة، لأنها تحدد أن سطور الشيفرة البرمجية موجودة ضمن تلك الكتلة المحددة. تُعَد المسافة البادئة الإلزامية ميزةً رئيسية في لغة بايثون، وهي أحد أسباب سهولة قراءة شيفرة بايثون البرمجية. تعريف نماذج البيانات: الملف models.py تدير تطبيقات ويب جانغو البيانات وتستعلم عنها من خلال كائنات بايثون التي يُشَار إليها باسم النماذج، التي تحدّد بنية البيانات المُخزَّنة بما في ذلك أنواع الحقول وحجمها الأقصى وقيمها الافتراضية وخيارات قائمة الاختيار ونص تعليمات التوثيق ونص التسمية في الاستمارات وغير ذلك. يُعَد تعريف النموذج مستقلًا عن قاعدة البيانات الأساسية، إذ يمكنك اختيار قاعدة بيانات من عدة قواعد بيانات بوصفها جزءًا من إعدادات مشروعك. لن تحتاج إلى التواصل مع قاعدة البيانات التي تريد استخدامها مباشرةً بمجرد اختيارها، فما عليك سوى كتابة بنية النموذج والشيفرة البرمجية الأخرى، وسيتولى جانغو جميع الأعمال الأخرى للتواصل مع قاعدة البيانات. يُظهِر مقتطف الشيفرة البرمجية الآتي نموذج جانغو بسيط جدًا للكائن Team، فالصنف Team مُشتَق من صنف جانغو models.Model، ويعرّف اسم الفريق ومستوى الفريق بوصفها حقولًا محرفية ويحدد الحد الأقصى لعدد المحارف التي ستُخزَّن لكل سجل. يمكن أن يكون مستوى الفريق team_level قيمةً من عدة قيم، لذلك نحدّده بوصفه حقلًا اختياريًا ونوفّر ربطًا بين الخيارات المراد عرضها والبيانات المراد تخزينها إلى جانب القيمة الافتراضية. # اسم الملف: models.py from django.db import models class Team(models.Model): team_name = models.CharField(max_length=40) TEAM_LEVELS = ( ('U09', 'Under 09s'), ('U10', 'Under 10s'), ('U11', 'Under 11s'), # … # قائمة مستويات الفريق الأخرى ) team_level = models.CharField(max_length=3, choices=TEAM_LEVELS, default='U11') ملاحظة: إليك بعض المعلومات عن لغة بايثون: تدعم لغة بايثون البرمجة كائنية التوجه التي تُعَد نمطًا من البرمجة، إذ ننظم شيفرتنا البرمجية ضمن كائنات، والتي تتضمن بيانات ودوال متعلقة بها للعمل على تلك البيانات. يمكن للكائنات أن ترث، أو توسّع، أو تُشتَق من كائنات أخرى، مما يسمح بمشاركة السلوك المشترك بين الكائنات المتعلقة ببعضها. نستخدم في لغة بايثون الكلمة المفتاحية class لتعريف المخطط الأولي للكائن. يمكننا إنشاء نسخ متعددة محددة لنوع الكائن بناءً على النموذج في الصنف؛ فلدينا في مثالنا الصنف Team مُشتَق من الصنف Model، وهذا يعني أنه نموذج وسيحتوي على جميع توابعه، ولكن يمكننا منحه ميزات خاصة به أيضًا. نحدد في نموذجنا الحقول التي ستحتاجها قاعدة البيانات لتخزين بياناتنا من خلال منحها أسماءً محددة. يستخدم جانغو هذه التعريفات -بما في ذلك أسماء الحقول- لإنشاء قاعدة البيانات الأساسية. الاستعلام عن البيانات: الملف views.py يوفر نموذج جانغو واجهة برمجة تطبيقات استعلام بسيطة للبحث في قاعدة البيانات المرتبطة به، ويمكن أن تتطابق هذه الواجهة مع عدد من الحقول في وقت واحد باستخدام معايير مختلفة، مثل التطابق التام exact وغير الحساس لحالة الأحرف case-insensitive وأكبر من وغير ذلك، ويمكن أن يدعم العبارات المعقدة، فمثلًا يمكنك تحديد بحث عن فرق U11 التي يبدأ اسم فريقها بالحرفين "Fr" أو ينتهي بالحرفين "al". يُظهِر مقتطف الشيفرة البرمجية التالية دالة العرض (معالج المورد) لعرض جميع فرق U09، إذ يوضح السطر التالي: list_teams = Team.objects.filter(team_level__exact="U09")‎ كيف يمكننا استخدام واجهة API للاستعلام عن النموذج لترشيح جميع السجلات، إذ يحتوي الحقل team_level على النص "U09" حرفيًا (لاحظ كيف تُمرَّر هذه المعايير إلى الدالة filter()‎ بوصفها وسيطًا مع فصل اسم الحقل ونوع المطابقة بشرطة سفلية مزدوجة: team_level__exact). ## اسم الملف: views.py from django.shortcuts import render from .models import Team def index(request): list_teams = Team.objects.filter(team_level__exact="U09") context = {'youngest_teams': list_teams} return render(request, '/best/index.html', context) تستخدم الدالةُ السابقة الدالةَ render()‎ لإنشاء الكائن HttpResponse الذي يُرسَل إلى المتصفح، إذ تُعَد هذه الدالة اختصارًا Shortcut ينشئ ملف HTML من خلال الجمع بين قالب HTML محدَّد وبعض البيانات لإدخالها في القالب (تتوفر هذه البيانات في المتغير المُسمَّى "context"). سنوضّح في القسم التالي كيفية إدخال البيانات في القالب لإنشاء ملف HTML. عرض البيانات: قوالب HTML تسمح لك أنظمة القوالب بتحديد بنية مستند الخرج باستخدام عناصر بديلة للبيانات التي ستُملَأ عند إنشاء الصفحة، إذ تُستخدَم القوالب غالبًا لإنشاء مستندات HTML، ولكن يمكنها إنشاء أنواع أخرى من المستندات أيضًا. يدعم جانغو كلًا من نظام القوالب الأصيل Native ومكتبة بايثون الشهيرة الأخرى التي تسمى Jinja2، ويمكن جعله يدعم أنظمة أخرى إن لزم الأمر. يوضح مقتطف الشيفرة البرمجية التالية الشكل الذي يمكن أن يبدو عليه قالب HTML الذي تستدعيه الدالة render()‎ في القسم السابق. كُتِب هذا القالب على أساس الافتراض بأنه يمكنه الوصول إلى متغير قائمة يُسمَّى youngest_teams عند عرضه (يوجد هذا المتغير ضمن المتغير context في الدالة render()‎). يوجد ضمن بنية HTML تعبيرٌ يتحقق أولًا من وجود المتغير youngest_teams، ثم يكرره في حلقة for، إذ يعرض القالب في كل تكرار قيمة team_name لكل فريق في عنصر <li>. ## اسم الملف: best/templates/best/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Home page</title> </head> <body> {% if youngest_teams %} <ul> {% for team in youngest_teams %} <li>{{ team.team_name }}</li> {% endfor %} </ul> {% else %} <p>No teams are available.</p> {% endif %} </body> </html> ميزات جانغو الأخرى أظهرت الأقسام السابقة الميزات الرئيسية التي ستستخدمها في كل تطبيق ويب تقريبًا وهي: ربط عناوين URL والعروض والنماذج والقوالب. تشمل بعض الأشياء الأخرى التي يقدمها إطار عمل جانغو الميزاتِ التالية: الاستمارات Forms: تُستخدَم استمارات HTML لتجميع بيانات المستخدم لمعالجتها على الخادم. يبسّط جانغو إنشاء الاستمارات والتحقق من صحتها ومعالجتها. استيثاق Authentication المستخدم وأذوناته: يتضمن جانغو نظامًا قويًا لاستيثاق المستخدم وأذوناته، إذ أُنشئ هذا النظام مع وضع الأمان في الحسبان. التخزين المؤقت Caching: يتطلب إنشاء المحتوى ديناميكيًا عمليات حسابية أكثر وأبطأ من تقديم محتوى ساكن، لذا يوفر جانغو تخزينًا مؤقتًا مرنًا بحيث يمكنك تخزين الصفحة المعروضة بالكامل أو جزء منها حتى لا يُعاد عرضها إلّا عند الضرورة. موقع المدير Administration site: يُضمَّن موقع مدير جانغو افتراضيًا عند إنشاء تطبيق باستخدام الهيكل الأساسي، مما يسهّل توفير صفحة مدير لمديري الموقع لإنشاء أي نماذج بيانات في موقعك وتعديلها وعرضها. سَلسَلة البيانات Serializing data: يسهّل جانغو من سَلسَلة بياناتك وتقديمها بتنسيق XML أو JSON، ويمكن أن يكون ذلك مفيدًا عند إنشاء خدمة ويب (موقع ويب يقدّم البيانات فقط لتستهلكها تطبيقات أو مواقع أخرى ولا يعرض أيّ شيء بنفسه)، أو عند إنشاء موقع ويب تعالج فيه الشيفرة البرمجية من طرف العميل كل عملية عرض أو تصيير البيانات. الخلاصة أكملنا الخطوة الأولى في رحلة تعلم جانغو، ويجب أن تفهم الآن الفوائد الرئيسية لتطبيق جانغو وتاريخه والشكل الذي يمكن أن يبدو عليه كل جزء من الأجزاء الرئيسية لتطبيق جانغو، ويجب أن تكون قد تعلمت بعض الأشياء حول لغة برمجة بايثون بما في ذلك صيغة القوائم والدوال والأصناف. لقد رأيتَ بعض شيفرات جانغو الحقيقية، ولكن تحتاج -بخلاف الشيفرة البرمجية من طرف العميل- إلى إعداد بيئة تطوير لتشغيلها، وهذه خطوتنا التالية. ترجمة -وبتصرُّف- للمقالين Django Web Framework (Python) و Django introduction. اقرأ أيضًا المقال التالي: إعداد بيئة تطوير تطبيقات جانغو المقال السابق: تعرف على أمان مواقع الويب أطر عمل الويب من طرف الخادم المرجع الشامل إلى تعلم لغة بايثون برمجة عملاء ويب باستخدام بايثون
  7. يتطلب أمان مواقع الويب اليقظة في جميع جوانب تصميم الموقع واستخدامه. لن يجعلك هذا المقال التمهيدي خبيرًا في أمان مواقع الويب، ولكنه سيساعدك على فهم مصدر الهجمات وما يمكنك فعله لتقوية تطبيق الويب ضد الهجمات الأكثر شيوعًا. المتطلبات الأساسية: المهارات الحاسوبية الأساسية. الهدف: فهم الهجمات الأكثر شيوعًا التي تهدّد أمان تطبيقات الويب وما يمكنك فعله لتقليل مخاطر اختراق موقعك. ما هو أمان موقع الويب؟ تُعَد شبكة الإنترنت مكانًا خطيرًا، إذ نسمع بانتظام عن عدم توفّر مواقع الويب بسبب هجمات حجب الخدمة أو عرض معلومات مُعدَّلة وضارة غالبًا على صفحاتها الرئيسية، وكانت قد سُرِّبت الملايين من كلمات المرور وعناوين البريد الإلكتروني وتفاصيل بطاقة الائتمان إلى النطاق العام في حالات أخرى عالية المستوى، مما عرّض مستخدمي مواقع الويب للإحراج الشخصي والمخاطر المالية، فالغرض من أمان موقع الويب هو منع هذه الهجمات أو أيّ نوع آخر منها. التعريف الرسمي لأمان موقع الويب هو فعل أو ممارسة تهدف إلى حماية مواقع الويب من الوصول، أو الاستخدام، أو التعديل، أو التدمير، أو التعطيل غير المُصرَّح به. يتطلب أمان موقع الويب الفعّال جهدًا في التصميم على كامل موقع الويب بما في ذلك تطبيق الويب وإعداد خادم الويب وسياساتك لإنشاء كلمات المرور وتجديدها والشيفرة البرمجية من طرف العميل. يمكن أن يبدو ذلك صعبًا، إلا أن الخبر السار هو أنه إذا استخدمتَ إطار عمل ويب من طرف الخادم، فسيفعِّل افتراضيًا آليات دفاع قوية ومدروسة جيدًا ضد عدد من الهجمات الأكثر شيوعًا، ويمكن تخفيف الهجمات الأخرى عبر إعداد خادم الويب من خلال تفعيل بروتوكول HTTPS مثلًا. أخيرًا، هناك أدوات فحص للثغرات الأمنية عامة يمكنها مساعدتك في معرفة ما إذا كنت مرتكبًا لأي أخطاء واضحة. دورة علوم الحاسوب دورة تدريبية متكاملة تضعك على بوابة الاحتراف في تعلم أساسيات البرمجة وعلوم الحاسوب اشترك الآن سيمنحك هذا المقال مزيدًا من التفاصيل حول بعض الهجمات الشائعة وبعض الخطوات البسيطة الممكن اتخاذها لحماية موقعك. ملاحظة: يُعَد هذا المقال موضوعًا تمهيديًا، وهو مُصمَّم لمساعدتك على البدء في التفكير في أمان موقع الويب، ولكنه ليس شاملًا. هجمات أمان مواقع الويب سنوضّح عددًا من هجمات مواقع الويب الأكثر شيوعًا وكيفية التخفيف منها. لاحظ كيف تكون الهجمات أنجح عندما يثق تطبيق الويب بالبيانات القادمة من المتصفح، أو عندما لا يكون متشككًا بها بالقدر الكافي. هجمات السكربتات العابرة للمواقع Cross-Site Scripting أو XSS هجمات السكربتات العابرة للمواقع هو مصطلح يُستخدَم لوصف فئة من الهجمات التي تسمح للمهاجمين بحقن سكربتات من طرف العميل من خلال موقع الويب في متصفحات المستخدمين الآخرين. تأتي الشيفرة البرمجية المحقونة إلى المتصفح من الموقع، لذلك تُعَد هذه الشيفرة البرمجية موثوقة ويمكنها تطبيق أمور، مثل إرسال ملف تعريف ارتباط تصريح الموقع الخاص بالمستخدم إلى المهاجم، وبالتالي يمكنه تسجيل الدخول إلى الموقع كما لو أنه المستخدم ويفعل أي شيء يمكن للمستخدم فعله مثل الوصول إلى تفاصيل بطاقته الائتمانية، أو الاطلاع على تفاصيل جهات الاتصال، أو تغيير كلمات المرور. ملاحظة: كانت ثغرات XSS سابقًا أكثر شيوعًا من أي نوع آخر من الهجمات الأمنية. تنقسم ثغرات XSS إلى ثغرات منعكسة Reflected وأخرى دائمة Persistent بناءً على كيفية إعادة الموقع للسكربتات البرمجية المحقونة في المتصفح كما يلي: تحدث ثغرة XSS المنعكسة عند إعادة محتوى المستخدم المُمرَّر إلى الخادم مباشرةً مع عدم تعديله لعرضه في المتصفح، إذ ستُشغّل أيّ سكربتات برمجية في محتوى المستخدم الأصلي عند تحميل الصفحة الجديدة. ضع في بالك مثلًا وظيفة البحث في الموقع إذ تُرمَّز مصطلحات البحث بوصفها معاملات في عنوان URL، وتُعرَض هذه المصطلحات مع النتائج. يمكن للمهاجم إنشاء رابط بحث يحتوي على سكربت ضار بوصفه معاملًا مثل الرابط الآتي ويرسله بالبريد الإلكتروني إلى مستخدم آخر. إذا نقر المستخدم المستهدف على هذا "الرابط المهم"، فسيُنفَّذ السكربت عند عرض نتائج البحث، مما يمنح المهاجم جميع المعلومات التي يحتاجها للدخول إلى الموقع بصفته المستخدم المستهدف، ويُحتمَل أن ينفّذ عمليات شراء بصفته المستخدم، أو يشارك معلومات جهات اتصاله: http://developer.mozilla.org?q=beer<script%20src="http://example.com/tricky.js"></script>‎ تحدث ثغرة XSS الدائمة عند تخزين السكربت الضار على موقع الويب ثم يعاد عرضه لاحقًا دون تعديل لينفّذه المستخدمون الآخرون دون علمهم. يمكن أن تخزّن مثلًا لوحة المناقشة التي تقبل تعليقات تحتوي على شيفرة HTML غير معدلة سكربتًا ضارًا من المهاجم، ويُنفَّذ السكربت عند عرض التعليقات ويمكن أن يرسل إلى المهاجم المعلومات المطلوبة للوصول إلى حساب المستخدم. يُعَد هذا النوع من الهجمات شائعًا وقويًا جدًا، لأن المهاجم يمكن ألّا يكون له أي ارتباطٍ مباشر مع الضحايا. تُعَد البيانات الواردة من طلبات من النوع "POST" أو "GET" المصدر الأكثر شيوعًا لثغرات XSS، ولكن يُحتمَل أن تكون أيّ بيانات من المتصفح عرضةً للخطر، مثل بيانات ملفات تعريف الارتباط التي يعرضها المتصفح، أو ملفات المستخدم المُحمَّلة والمعروضة. أفضل دفاع ضد ثغرات XSS هو إزالة أو تعطيل أي شيفرة HTML يمكن أن تحتوي على تعليمات لتشغيل الشيفرة البرمجية، ويتضمن ذلك في لغة HTML عناصرًا، مثل العناصر <script> و <object> و <embed> و <link>. تُعرَف عملية تعديل بيانات المستخدم بحيث لا يمكن استخدامها لتشغيل السكربتات أو التأثير بطريقة أخرى على تنفيذ شيفرة الخادم البرمجية باسم تعقيم الإدخال Input Sanitization، إذ تطبّق العديد من أطر عمل الويب تلقائيًا عملية تعقيم مدخلات المستخدم من نماذج HTML افتراضيًا. حقن استعلامات SQL تمكّن الثغرات الأمنية الخاصة بحقن استعلامات SQL المستخدمين الضارين من تنفيذ شيفرة SQL عشوائية على قاعدة بيانات، مما يسمح بالوصول إلى البيانات أو تعديلها أو حذفها بغض النظر عن أذونات المستخدم. يمكن أن يؤدي هجوم الحقن الناجح إلى انتحال الهويات، أو إنشاء هويات جديدة لديها حقوق إدارة، أو الوصول إلى جميع البيانات الموجودة على الخادم، أو تخريب، أو تعديل البيانات لجعلها غير قابلة للاستخدام. تتضمن أنواع حقن استعلامات SQL حقن استعلامات SQL المستندة إلى الأخطاء وحقن استعلامات SQL المستندة إلى الأخطاء المنطقية وحقن استعلامات SQL المستندة إلى الوقت. تظهر هذه الثغرة الأمنية إذا كان من الممكن أن يغيّر مُدخل المستخدم المُمرَّر إلى تعليمة SQL الأساسية معنى التعليمة، فالغرض من الشيفرة التالية مثلًا هو سرد كافة المستخدمين الذين يملكون اسمًا معينًا userName يوفّره نموذج HTML: statement = "SELECT * FROM users WHERE name = '" + userName + "';" إذا حدّد المستخدم اسمًا حقيقيًا، فستعمل التعليمة السابقة كما هو متوقع، ولكن يمكن لمستخدم ضار تغيير سلوك تعليمة SQL هذه بالكامل إلى التعليمة الجديدة في المثال الآتي من خلال تحديد التعليمة a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't للحقل userName. SELECT * FROM users WHERE name = 'a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't'; تنشِئ التعليمة المُعدَّلة تعليمة SQL صالحة تحذف جدول المستخدمين users وتحدّد جميع البيانات من جدول معلومات المستخدم userinfo الذي يكشف عن معلومات كل مستخدم. تعمل هذه التعليمة لأن الجزء الأول من النص المحقون (a';‎) يكمل التعليمة الأصلية. يمكنك تجنب هذا النوع من الهجوم من خلال التأكد من أن بيانات المستخدم المُمرَّرة إلى استعلام SQL لا يمكنها تغيير طبيعته، ويمكن تحقيق ذلك باستخدام محرف الهروب Escape Character للهروب من جميع المحارف في مدخلات المستخدم التي لها معنًى خاص في لغة SQL. ملاحظة: تعالج تعليمة SQL المحرف (') بوصفه بداية ونهاية سلسلة محرفية، وإذا وضعت شرطة مائلة عكسية أمام هذا المحرف (')، فسنهرّب الرمز ونطلب من لغة SQL التعامل معه بوصفه محرفًا، أي مجرد جزء من السلسلة النصية. سنهرّب في التعليمة التالية المحرف (')، إذ ستفسّر لغة SQL الآن الاسم name على أنه سلسلة نصية كاملة بالخط العريض bold (حسنًا إنه اسم غريب جدًا، ولكنه ليس ضارًا): SELECT * FROM users WHERE name = 'a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t'; تهتم أطر عمل الويب بمحرف الهروب نيابةً عنك، إذ يضمن إطار عمل جانغو Django مثلًا هروب أيّ بيانات مستخدم مُمرَّرة إلى مجموعات الاستعلام (استعلامات النموذج). ملاحظة: يمكنك الاطلاع على مقال كيفية تأمين الخوادم السحابية ضد هجمات حقن SQL لمعلومات أكثر. تزوير الطلبات عبر المواقع Cross-Site Request Forgery أو CSRF تسمح هجمات CSRF لمستخدمٍ ضار بتنفيذ إجراءات باستخدام ثبوتيات مستخدم آخر دون عِلم هذا المستخدم أو موافقته. لنفترض مثلًا أن سامي مستخدم ضار يعرف أن موقعًا معينًا يسمح للمستخدمين الذين سجّلوا الدخول بإرسال أموال إلى حسابٍ محدد باستخدام طلب HTTP من النوع "POST"، بحيث يتضمن هذا الطلب اسم الحساب ومبلغًا من المال. ينشئ سامي نموذجًا يتضمن التفاصيل المصرفية الخاصة به ومبلغًا من المال بوصفها حقولًا مخفية، ويرسله بالبريد الإلكتروني إلى مستخدمي الموقع الآخرين مع زر الإرسال Submit المخفي بوصفه رابطًا إلى موقع "الثراء السريع get rich quick". إذا نقر المستخدم على زر الإرسال، فسيُرسَل طلب HTTP من النوع "POST" إلى الخادم الذي يحتوي على تفاصيل المعاملة وأيّ ملفات تعريف ارتباط من طرف العميل يرتبط من خلالها المتصفح بالموقع، إذ تُعَد إضافة ملفات تعريف الارتباط المرتبطة بالموقع إلى الطلبات سلوك المتصفح العادي. سيتحقق الخادم من ملفات تعريف الارتباط، ويستخدمها لتحديد ما إذا كان المستخدم قد سجّل الدخول ولديه إذن لإجراء المعاملة أم لا. النتيجة هي أن أيّ مستخدم ينقر على زر الإرسال أثناء تسجيل الدخول إلى موقع التعاملات المالية سيجري المعاملة، وسيصبح سامي ثريًا. ملاحظة: الحيلة هنا هي أن سامي لا يحتاج إلى الوصول إلى ملفات تعريف الارتباط الخاصة بالمستخدم أو الوصول إلى ثبوتياته، إذ يخزّن متصفح المستخدم هذه المعلومات ويضمّنها تلقائيًا في جميع الطلبات إلى الخادم المرتبط به. تتمثل إحدى طرق منع هذا النوع من الهجوم في أن يطلب الخادم أن تتضمن طلبات "POST" سرًا خاصًا بالمستخدم يولّده الموقع، إذ سيوفّر الخادم هذا السر عند إرسال نموذج الويب المُستخدَم لإجراء التحويلات. يمنع هذا الأسلوب سامي من إنشاء نموذجه الخاص، لأنه سيتعين عليه معرفة السر الذي يوفره الخادم للمستخدم، وحتى إذا اكتشف السر وأنشأ نموذجًا لمستخدم معين، فلن يكون قادرًا على استخدام النموذج نفسه لمهاجمة جميع المستخدمين. تتضمن أطر عمل الويب آليات منع هجمات CSRF في أغلب الأحيان. هجمات أخرى تشمل الهجمات أو الثغرات الشائعة الأخرى ما يلي: هجمات الاختطاف بالنقر Clickjacking: يختطف مستخدم ضار في هذا الهجوم نقراتٍ مُوجَّهة إلى موقع مرئي من المستوى الأعلى ويوجّهها إلى صفحة مخفية. يمكن استخدام هذه التقنية مثلًا لعرض موقع مصرفي شرعي ولكنه يلتقط ثبوتيات تسجيل الدخول ضمن عنصر <iframe> غير مرئي يتحكم فيه المهاجم. يمكن استخدام هجمات الاختطاف بالنقر لجعل المستخدم ينقر على زر في موقع مرئي، ولكنه بذلك ينقر على زر مختلف تمامًا من غير عِلمه. يمكن تجنب ذلك من خلال أن يمنع موقعك نفسه من أن يُدمَج في عنصر <iframe> في موقع آخر عبر ضبط ترويسات HTTP المناسبة. هجمات حجب الخدمة Denial of Service -أو اختصارًا DoS: تتحقق هجمات DoS من خلال إغراق الموقع المستهدف بطلبات مزيفة بحيث يتعطل الوصول إلى الموقع للمستخدمين الشرعيين. يمكن أن تكون هذه الطلبات كثيرةً، أو يستهلك كل طلب منها كميات كبيرة من الموارد، مثل عمليات القراءة البطيئة، أو تحميل ملفات كبيرة. تعمل دفاعات DoS من خلال تحديد وحظر حركة المرور السيئة مع السماح بمرور الرسائل المشروعة، وتوجد هذه الدفاعات عادةً قبل خادم الويب أو فيه، فهي ليست جزءًا من تطبيق الويب نفسه. هجوم اجتياز شجرة الملفات Directory Traversal (الملف ومعلوماته): يحاول مستخدم ضار في هذا الهجوم الوصول إلى أجزاء من نظام ملفات خادم الويب التي لا ينبغي أن يتمكن من الوصول إليها. تحدث هذه الثغرة عندما يكون المستخدم قادرًا على تمرير أسماء الملفات التي تتضمن محارف التنقل في نظام الملفات، مثل /../..، والحل هو تعقيم مدخلات المستخدم قبل استخدامها. تضمين الملفات File Inclusion: يكون المستخدم في هذا الهجوم قادرًا على تحديد ملف عشوائي لعرض أو تنفيذ البيانات المُمرَّرة إلى الخادم. يمكن عند التحميل تنفيذ هذا الملف على خادم الويب أو من طرف العميل، مما يؤدي إلى هجوم XSS، والحل هو تعقيم مدخلات المستخدم قبل استخدامها. حقن الأوامر Command Injection: تسمح هجمات حقن الأوامر للمستخدم الضار بتنفيذ أوامر نظام عشوائية على نظام التشغيل المضيف. الحل هو تعقيم مدخلات المستخدم قبل استخدامها في استدعاءات النظام. اطّلع أيضًا على الهجمات الأمنية Security Attacks في الشبكات الحاسوبية. بعض النصائح الأساسية تنجح تقريبًا جميع عمليات استغلال الأمان الموضَّحة سابقًا عندما يثق تطبيق الويب في البيانات الواردة من المتصفح، لذا يجب عليك تعقيم جميع البيانات التي ينشئها المستخدم قبل عرضها في المتصفح، أو استخدامها في استعلامات SQL، أو تمريرها إلى نظام التشغيل، أو استدعاء نظام الملفات مهما كان ما تفعله لتحسين أمان موقعك الويب. تحذير: الدرس الوحيد الأكثر أهمية الذي يمكنك تعلّمه حول أمان موقع الويب هو عدم الوثوق أبدًا في البيانات الواردة من المتصفح مثل البيانات الموجودة في معاملات عنوان URL لطلبات "GET" وطلبات "POST" وترويسات وملفات تعريف ارتباط HTTP والملفات التي يرفعها المستخدم. تحقق دائمًا من جميع البيانات الواردة وعقّمها، وافترض الأسوأ دائمًا. إليك عددًا من الخطوات الأخرى التي يمكنك اتخاذها: استخدام إدارة أكثر فاعلية لكلمات المرور: شجّع على استخدام كلمات المرور القوية، واستخدم الاستيثاق الثنائي two-factor authentication في موقعك، بحيث يجب على المستخدم إدخال رمز استيثاق آخر بالإضافة إلى كلمة المرور، إذ يمكن تسليم رمز الاستيثاق باستخدام العتاد الحقيقي الذي يمتلكه المستخدم فقط مثل رمز في رسالة SMS مرسَلة إلى هاتف المستخدم. إعداد خادمك الويب لاستخدام بروتوكولات HTTPS وأمن HTTP للنقل الصارم HTTP Strict Transport Security -أو اختصارًا HSTS: يشفّر بروتوكول HTTPS البيانات المرسَلة بين العميل والخادم، مما يضمن أن تكون ثبوتيات تسجيل الدخول وملفات تعريف الارتباط وبيانات طلبات "POST" ومعلومات الترويسة غير متاحة بسهولة للمهاجمين. يجب تتبّع الهجمات الأكثر شيوعًا (قائمة OWASP الحالية) ومعالجة الثغرات الأكثر شيوعًا أولًا. استخدام أدوات فحص الثغرات لإجراء اختبار أمان آلي على موقعك. يمكن أن يجد موقع الويب الناجح الخاص بك لاحقًا أخطاءً من خلال تقديم مكافآت على اكتشاف الأخطاء كما يفعل موقع موزيلا. تخزين وعرض البيانات التي تحتاجها فقط، فمثلًا إذا كان يجب على المستخدمين تخزين معلومات حساسة مثل تفاصيل البطاقة الائتمانية، فاعرض فقط أرقامًا كافية من رقم البطاقة التي يمكن للمستخدم تحديدها ولا يكفي أن ينسخها المهاجم ويستخدمها على موقع آخر، والنمط الأكثر شيوعًا عندها هو عرض آخر 4 أرقام فقط من رقم البطاقة الائتمانية. يمكن أن تساعد أطر عمل الويب في التخفيف من العديد من الثغرات الأكثر شيوعًا. الخلاصة أوضحنا في هذا المقال مفهوم أمان الويب وبعض الهجمات الأكثر شيوعًا التي يجب أن يحاول موقع الويب الحماية منها، ويجب أن تفهم أن تطبيق الويب لا يمكنه الوثوق بأيّ بيانات من متصفح الويب، لذا يجب تعقيم جميع بيانات المستخدم قبل عرضها أو استخدامها في استعلامات SQL واستدعاءات نظام الملفات. وصلنا مع هذا المقال إلى نهاية هذه السلسلة من المقالات والتي تغطي خطواتك الأولى في برمجة مواقع الويب من طرف الخادم، وبذلك تعلّمتَ المفاهيم الأساسية، وأصبحتَ جاهزًا لتحديد إطار عمل الويب وبدء البرمجة. ترجمة -وبتصرُّف- للمقال Website security. اقرأ أيضًا المقال السابق: أطر عمل الويب من طرف الخادم تأمين متصفحات الويب في العالم الرقمي ممارسات الأمن والحماية في تطبيقات PHP رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج
  8. وضّحنا في المقال السابق كيف يبدو الاتصال بين عملاء وخوادم الويب، وطبيعة طلبات واستجابات HTTP، وما يحتاج تطبيق الويب من طرف الخادم لتنفيذه بهدف الاستجابة للطلبات القادمة من متصفح الويب. حان الوقت الآن لاستكشاف كيف يمكن لأطر عمل الويب تبسيط هذه المهام، وإعطائك فكرةً عن كيفية اختيار إطار عمل لتطبيقك الأول من طرف الخادم. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، والفهم الأساسي لكيفية معالجة الشيفرة البرمجية من طرف الخادم لطلبات HTTP والاستجابة لها (اطّلع على تفاعلات الخادم مع العميل في موقع ويب ديناميكي). الهدف: فهم كيف يمكن لأطر عمل الويب تبسيط تطوير أو صيانة الشيفرة البرمجية من طرف الخادم وجعل القارئ يفكر في اختيار إطار عمل لعملية التطوير الخاصة به. سنوضّح في الأقسام التالية بعض النقاط باستخدام أجزاء من الشيفرة البرمجية المأخوذة من أطر عمل ويب حقيقية. لا تقلق إن لم تفهم كل شيء الآن، إذ سنعمل معك لاحقًا باستخدام شيفرة برمجية عند التعرّف على أطر عمل الويب من طرف الخادم. مقدمة عامة تُعَد أطر عمل الويب من طرف الخادم -المعروفة أيضًا باسم "أطر عمل تطبيقات الويب"- أطر عمل برمجية تسهّل كتابة تطبيقات الويب وصيانتها وتوسيعها، وتوفّر الأدوات والمكتبات التي تبسّط المهام الشائعة لتطوير الويب، مثل توجيه عناوين URL إلى المعالجات المناسبة والتفاعل مع قواعد البيانات ودعم الجلسات والتصريح للمستخدمين وتنسيق الخرج، مثل ملفات HTML و JSON و XML، وتحسين الأمان في مواجهة هجمات الويب. يوفّر القسم التالي مزيدًا من التفاصيل حول طريقة أطر عمل الويب لتسهيل تطوير تطبيقات الويب، ثم سنشرح بعض المعايير التي يمكنك استخدامها لاختيار إطار عمل ويب، وسنعرض بعض الخيارات المتاحة أمامك. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن فوائد أطر عمل الويب توفّر أطر عمل الويب أدوات ومكتبات لتبسيط عمليات تطوير الويب الشائعة. لا يُعَد استخدام إطار عمل ويب من طرف الخادم أمرًا ضروريًا، ولكن يُنصح بشدة بذلك، لأنه يسهّل الأمور عليك كثيرًا. سنناقش في هذا القسم بعض الوظائف التي توفرها أطر عمل الويب في أغلب الأحيان، إذ لن توفر جميع أطر العمل بالضرورة جميع هذه الميزات. العمل المباشر مع طلبات واستجابات HTTP تتواصل خوادم الويب والمتصفحات باستخدام بروتوكول HTTP، إذ تنتظر الخوادم طلبات HTTP من المتصفح ثم تعيد المعلومات في استجابات HTTP. تسمح لك أطر عمل الويب بكتابة صيغة مبسّطة من شأنها أن تنشِئ شيفرةً برمجيةً من طرف الخادم للعمل مع هذه الطلبات والاستجابات، وهذا يعني أنه سيكون لديك وظيفة أسهل، وستتفاعل مع شيفرة برمجية أسهل وأعلى مستوًى بدلًا من أساسيات الشبكات ذات المستوى الأدنى. يوضح المثال الآتي كيفية عمل ذلك في إطار عمل جانغو Django باستخدام لغة بايثون Python، إذ تتلقى كل دالة عرض (أو معالج طلب) الكائن HttpRequest الذي يحتوي على معلومات الطلب، وتكون هذه الدالة مطلوبة لإعادة الكائن HttpResponse مع الخرج المُنسَّق (سلسلة نصية في هذه الحالة). # دالة عرض جانغو from django.http import HttpResponse def index(request): # الحصول على كائن‫ HttpRequest (طلب) # إجراء العمليات باستخدام المعلومات الواردة في الطلب. # إعادة الكائن‫ HttpResponse return HttpResponse('Output string to return') توجيه الطلبات إلى المعالج المناسب ستوفِّر معظم المواقع عددًا من الموارد المختلفة التي يمكن الوصول إليها من خلال عناوين URL مميزة. يمكن أن يكون الحفاظ على التعامل مع جميع هذه الموارد باستخدام دالة واحدة أمرًا صعبًا، لذلك توفر أطر عمل الويب آليات بسيطة لربط أنماط عنوان URL مع دوال معالجة محدَّدة. لهذا النهج فوائد من حيث الصيانة، لأنه يمكنك تغيير عنوان URL المُستخدَم لتقديم ميزة معينة دون الحاجة إلى تغيير الشيفرة البرمجية الأساسية. تستخدم أطر العمل المختلفة آليات مختلفة للربط Mapping، فمثلًا يضيف إطار عمل الويب فلاسك Flask باستخدام لغة بايثون مسارات لعرض الدوال باستخدام مزخرِف Decorator. @app.route("/") def hello(): return "Hello World!" بينما يتوقع إطار عمل جانغو من المطورين تحديد قائمة بروابط عناوين URL بين نمط URL ودالة عرض. urlpatterns = [ url(r'^$', views.index), # مثال : /best/myteamname/5/ url(r'^best/(?P<team_name>\w.+?)/(?P<team_number>[0-9]+)/$', views.best), ] تسهيل الوصول إلى البيانات في الطلب يمكن تشفير البيانات في طلب HTTP بعدة طرق، إذ يمكن أن يشفِّر طلب HTTP من النوع "GET" البيانات المطلوبة في معاملات عنوان URL أو ضمن بنية عنوان URL للحصول على ملفات أو بيانات من الخادم؛ بينما سيتضمن طلب HTTP من النوع "POST" معلومات التحديث بوصفها "بيانات POST" ضمن متن الطلب لتحديث موردٍ على الخادم. كما يمكن أن يتضمن طلب HTTP معلومات حول الجلسة أو المستخدم الحالي في ملف تعريف الارتباط Cookie من طرف العميل. توفر أطر عمل الويب آليات مناسبة للغة البرمجة للوصول إلى هذه المعلومات، فمثلًا يحتوي كائن HttpRequest الذي يمرره إطار عمل جانغو إلى كل دالة عرض على توابع وخاصيات للوصول إلى عنوان URL الهدف ونوع الطلب، مثل طلب HTTP من النوع "GET"، ومعاملات "GET" أو "POST" وملف تعريف الارتباط وبيانات الجلسة وغير ذلك. كما يمكن لإطار عمل جانغو تمرير المعلومات المُشفَّرة في بنية عنوان URL من خلال تحديد "الأنماط الملتقَطة" في رابط Mapper عنوان URL (ألقِ نظرةً على الجزء الأخير من الشيفرة البرمجية في القسم السابق). تجريد وتبسيط الوصول إلى قاعدة البيانات تستخدم مواقع الويب قواعد البيانات لتخزين معلومات عن المستخدمين ومعلومات لمشاركتها معهم. توفّر أطر عمل الويب في أغلب الأحيان طبقة قاعدة بيانات تجرّد عمليات القراءة والكتابة والاستعلام والحذف لقاعدة البيانات، إذ يُشار إلى طبقة التجريد هذه باسم رابط الكائنات بالعلاقات Object-Relational Mapper -أو اختصارًا ORM. توجد فائدتان لاستخدام رابط ORM، هما: يمكنك استبدال قاعدة البيانات الأساسية دون الحاجة بالضرورة إلى تغيير الشيفرة البرمجية التي تستخدمها، مما يتيح للمطورين تحسين خصائص قواعد البيانات المختلفة بناءً على استخدامها. يمكن تطبيق التحقق validation الأساسي من صحة البيانات ضمن إطار العمل، مما يجعل من الأسهل والأكثر أمانًا التحقق من تخزين البيانات في النوع الصحيح لحقل قاعدة البيانات وبالتنسيق الصحيح (مثل عنوان البريد الإلكتروني) وكذلك التحقق من أنها غير ضارة بأيّ شكل من الأشكال، إذ يمكن للمخترقين استخدام أنماط معينة من الشيفرة البرمجية ليفعلوا أمورًا سيئة مثل حذف سجلات قاعدة البيانات. يوفر إطار عمل الويب جانغو رابط ORM مثلًا، ويشير إلى الكائن المُستخدَم لتعريف بنية السجل بوصفه نموذجًا Model، بحيث يحدّد هذا النموذج أنواع الحقول المراد تخزينها والتي يمكن أن توفر تحققًا على مستوى الحقل لصحة المعلومات الممكن تخزينها، فمثلًا سيسمح حقل البريد الإلكتروني بعناوين البريد الإلكتروني الصالحة فقط، كما يمكن أن تحدّد تعريفات الحقول حجمها الأقصى وقيمها الافتراضية وخيارات قائمة التحديد ونص التعليمات للوثائق ونص التسمية للاستمارات وإلخ. لا يذكر النموذج أيّ معلومات حول قاعدة البيانات الأساسية لأنه يُعَد ضبطًا لإعدادٍ يمكن تغييره بصورة منفصلة عن شيفرتنا البرمجية. يُظهر مقتطف الشيفرة البرمجية الأول التالي نموذج جانغو بسيط جدًا للكائن Team الذي يخزن اسم الفريق ومستوى الفريق بوصفهما حقولًا محرفية ويحدّد الحد الأقصى لعدد المحارف التي ستُخزَّن لكل سجل. يُعَد الحقل team_level حقل اختيار، لذلك سنقدّم ربطًا بين الاختيارات المُراد عرضها والبيانات المراد تخزينها إلى جانب القيمة الافتراضية. #best/models.py from django.db import models class Team(models.Model): team_name = models.CharField(max_length=40) TEAM_LEVELS = ( ('U09', 'Under 09s'), ('U10', 'Under 10s'), ('U11', 'Under 11s'), # قائمة فرقنا الأخرى ) team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11') يوفّر نموذج جانغو واجهة برمجة تطبيقات API بسيطة للاستعلام بهدف البحث في قاعدة البيانات، إذ يمكن أن تتطابق هذه الواجهة مع عدد من الحقول في وقت واحد باستخدام معايير مختلفة، مثل التطابق التام، وعدم الحساسية لحالة الأحرف، وأكبر من وإلخ، ويمكن أيضًا دعم العبارات المعقدة، فمثلًا يمكنك تحديد بحثٍ عن فِرق U11 التي اسمها يبدأ بالحرفين "Fr"، أو ينتهي بالحرفين "al". يوضّح مقتطف الشيفرة البرمجية الثاني دالة عرض (معالج موارد) لعرض جميع فِرق U09، إذ نحدّد في هذه الحالة أننا نريد ترشيح جميع السجلات عندما يحتوي الحقل team_level على النص "U09" تمامًا. لاحظ فيما يلي كيفية تمرير هذه المعايير إلى الدالة filter()‎ بوصفها وسيطًا مع اسم الحقل ونوع التطابق ومفصولٌ بينها شرطة سفلية team_level__exact. #best/views.py from django.shortcuts import render from .models import Team def youngest(request): list_teams = Team.objects.filter(team_level__exact="U09") context = {'youngest_teams': list_teams} return render(request, 'best/index.html', context) تصيير البيانات توفر أطر عمل الويب أنظمة إنشاء قوالب تتيح لك تحديد بنية مستند الخرج باستخدام العناصر البديلة للبيانات التي ستُضاف عند إنشاء صفحة. تُستخدم القوالب في أغلب الأحيان لإنشاء مستند HTML، ولكن يمكنها إنشاء أنواع أخرى من المستندات. توفر أطر عمل الويب آلية لتسهيل إنشاء تنسيقات أخرى من البيانات المخزنة بما في ذلك تنسيقات JSON و XML، فمثلًا يتيح لك نظام قوالب جانغو تحديد المتغيرات باستخدام صيغة "تعابير موضوعة بين قوسين معقوصين Double-handlebars"، مثل {{ variable_name }}، والتي ستُستبدَل بالقيم المُمرَّرة من دالة العرض عند عرض الصفحة. كما يوفّر نظام القوالب دعمًا للتعابير ذات الصيغة {% expression %} التي تسمح للقوالب بإجراء عمليات بسيطة، مثل تكرار قيم القائمة المُمرَّرة في القالب. ملاحظة: تستخدم العديد من أنظمة إنشاء القوالب الأخرى البنية نفسها مثل Jinja2 بلغة بايثون و Handlebars بلغة جافا سكريبت و Moustache بلغة جافا سكريبت وغيرها. يوضّح مقتطف الشيفرة البرمجية التالي كيف يعمل ذلك، فاستمرارًا لمثال "فريق الناشئين" من القسم السابق، يُمرَّر قالب HTML إلى متغير قائمة يسمى youngest_teams باستخدام العرض view. لدينا ضمن بنية ملف HTML تعبير يتحقق أولًا من وجود المتغير youngest_teams، ثم يكرّره في حلقة for. يعرض القالب في كل تكرار قيمة اسم الفريق team_name الخاص بالفريق في عنصر قائمة. #best/templates/best/index.html <!DOCTYPE html> <html lang="en"> <body> {% if youngest_teams %} <ul> {% for team in youngest_teams %} <li>{{ team.team_name }}</li> {% endfor %} </ul> {% else %} <p>No teams are available.</p> {% endif %} </body> </html> كيفية اختيار إطار عمل الويب توجد العديد من أطر عمل الويب لكل لغة برمجة تقريبًا ترغب في استخدامها، وسنوضّح عددًا من أطر العمل الأكثر شيوعًا في القسم التالي، ولكن يمكن أن يصبح تحديد إطار العمل الذي يوفر أفضل نقطة بداية لتطبيقك الويب الجديد أمرًا صعبًا مع وجود العديد من الخيارات. إليك بعض العوامل التي يمكن أن تؤثر على قرارك: حجم الجهد المبذول للتعلم: يعتمد الجهد المبذول لتعلم إطار عمل الويب على مدى معرفتك بلغة البرمجة الأساسية وتناسق واجهة برمجة التطبيقات الخاصة به وجودة توثيقه وحجم ونشاط مجتمعه. إذا لم تكن لديك خبرةٌ في البرمجة إطلاقًا، ففكر في إطار عمل جانغو الذي يُعَد من أسهل أطر العمل للتعلم بناءً على المعايير التي ذكرناها سابقًا؛ بينما إذا كنت جزءًا من فريق تطوير لديه خبرةٌ فعلية كبيرة في إطار عمل ويب معين أو لغة برمجة معينة، فلا بُد أنك ستتمسّك بها طبعًا. الإنتاجية: هي مقياس لمدى السرعة التي يمكنك بها إنشاء ميزات جديدة بمجرد أن تكون على دراية بإطار العمل، وتتضمن كلًا من الجهد المبذول لكتابة الشيفرة البرمجية وصيانتها، إذ لا يمكنك كتابة ميزات جديدة عندما تكون الميزات القديمة معطلة. تتشابه العديد من العوامل التي تؤثر على الإنتاجية مع عوامل حجم الجهد المبذول للتعلم، مثل التوثيق والمجتمع والخبرة في البرمجة وغيرها من العوامل التي نوضّحها فيما يلي: هدف أو أصل إطار العمل: اُنشِأت بعض أطر عمل الويب في البداية لحل أنواع معينة من المشاكل والعمل بصورة أفضل على إنشاء تطبيقات الويب ذات القيود المماثلة؛ فمثلًا أُنشِئ إطار عمل جانغو لدعم تطوير موقع ويب لصحيفة، لذا يُعَد جيدًا للمدونات والمواقع الأخرى التي تتضمن عمليات نشر؛ بينما يُعَد إطار عمل فلاسك أخف بكثير وهو رائع لإنشاء تطبيقات الويب التي تعمل على الأجهزة المُضمَّنة. إطار العمل القائم على رأيه Opinionated وغير القائم على رأيه Unopinionated: إطار العمل القائم على رأيه هو إطار العمل الذي توجد فيه طرق أفضل موصَى بها لحل مشكلة معينة، وتميل إلى أن تكون أكثر إنتاجية عندما تحاول حل المشاكل الشائعة، لأنها تقودك في الاتجاه الصحيح، ولكنها تكون أقل مرونة أحيانًا. إطار العمل الذي يتضمن كل شيء Batteries included وإطار العمل الذي تضطر للبحث فيه عن كل شيء بنفسك get it yourself: تتضمن بعض أطر عمل الويب أدوات أو مكتبات تعالج كل مشكلة يمكن لمطوريها التفكير فيها افتراضيًا، مثل إطار عمل جانغو، بينما تتوقع الأطر الأخف وزنًا أن يحدد ويختار مطورو الويب حلًا للمشاكل من مكتبات منفصلة مثل إطار عمل فلاسك خفيف الوزن. يكون البدء في أطر العمل التي تتضمن كل شيء أسهل لأن لديك كل ما تحتاجه فعليًا، ويُحتمَل أن تكون متكاملة وذات توثيق جيد، لكن إذا احتوى إطار العمل الأصغر على كل ما تحتاجه، فيمكن تشغيله في بيئات أكثر تقييدًا وسيكون لديه مجموعةً أصغر وأسهل من الأشياء لتعلّمها. إطار العمل الذي يشجع ممارسات التطوير الجيدة وإطار العمل الذي لا يشجع ذلك، إذ ينتج مثلًا عن إطار العمل -الذي يشجّع بنية نموذج-عرض-مُتحكِّم Model-View-Controller لفصل الشيفرة البرمجية إلى دوال منطقية- شيفرةٌ برمجية أكثر قابلية للصيانة من الشيفرة البرمجية التي ليس لديها أيّ توقعات بشأن المطورين، كما يمكن أن يكون لتصميم إطار العمل تأثير كبير على مدى سهولة اختبار وإعادة استخدام الشيفرة البرمجية. أداء لغة البرمجة أو إطار العمل: لا تُعَد السرعة عادةً العامل الأكبر في الاختيار لأن أوقات التشغيل البطيئة نسبيًا مثل لغة بايثون تكون أكثر من "جيدة" للمواقع متوسطة الحجم التي تعمل على عتاد متوسط. يمكن تعويض فوائد هذه السرعة المحسوسة للغة أخرى مثل لغة C++‎ أو جافا سكريبت من خلال تكاليف التعلم والصيانة. دعم التخزين المؤقت Caching: يمكن أن يصبح موقعك الويب أنجح، وبالتالي ستجد أنه لم يعد قادرًا على التعامل مع عدد الطلبات التي يتلقاها عندما يصل المستخدمون إليه، وبالتالي يمكن أن تفكر في إضافة دعم للتخزين المؤقت الذي يمثّل تحسينًا عندما تخزّن كل استجابة الويب أو جزءًا منها بحيث لا يلزم إعادة حسابها في الطلبات اللاحقة. تُعَد استعادة استجابة مُخزَّنة مؤقتًا أسرع بكثير من حساب الاستجابة. يمكن تقديم التخزين المؤقت في شيفرتك البرمجية أو الخادم (اطّلع على الوسيط العكسي Reverse Proxy)، وسيكون لأطر عمل الويب مستويات مختلفة من الدعم لتحديد المحتوى الممكن تخزينه مؤقتًا. قابلية التوسع: إذا أصبح موقعك ناجحًا جدًا، فستُستنفَد فوائد التخزين المؤقت ويمكن أن تصل إلى حدود التوسع الرأسي Vertical Scaling أي تشغيل تطبيق الويب على عتاد أقوى، وبالتالي ستحتاج إلى التوسع أفقيًا (مشاركة الحِمل من خلال توزيع موقعك على عدد من خوادم الويب وقواعد البيانات)، أو التوسع جغرافيًا، إذ يقيم بعض عملائك بعيدًا عن خادمك. يمكن أن يُحدِث إطار عمل الويب الذي تختاره فرقًا كبيرًا في مدى سهولة توسيع نطاق موقعك. أمان الويب: توفر بعض أطر عمل الويب دعمًا أفضل للتعامل مع هجمات الويب الشائعة. يعقّم Sanitize إطار عمل جانغو مثلًا جميع مدخلات المستخدم من قوالب HTML، وبالتالي لا يمكن تشغيل شيفرة جافا سكريبت التي أدخلها المستخدم. توفر أطر العمل الأخرى حمايةً مماثلة، ولكن لا تُفعَّل دائمًا افتراضيًا. هناك عدة عوامل محتملة أخرى بما في ذلك منح التراخيص Licensing سواءً كان إطار العمل قيد التطوير النشط أم لا وغير ذلك. إذا كنت مبتدئًا في البرمجة، فيُحتمَل أن تختار إطار عملك بناءً على أساس سهولة التعلم. يُعَد التوثيق أو البرامج التعليمية عالية الجودة والمجتمع النشط الذي يساعد المستخدمين الجدد أكثر مواردك قيمةً، إضافةً إلى سهولة استخدام اللغة نفسها. اخترنا إطار عمل جانغو بلغة بايثون و Express باستخدام Node/JavaScript لكتابة أمثلتنا، ويرجع ذلك أساسًا إلى أنها سهلة التعلم ولديها دعمٌ جيد. ملاحظة: يمكننا زيارة مواقع الويب الرسمية لإطار عمل جانغو بلغة بايثون و Express باستخدام Node/JavaScript للتحقق من توثيقها ومجتمعها. انتقل إلى المواقع الرسمية. انقر على روابط قائمة التوثيق التي تحتوي أشياءً بأسماء مثل "Documentation و Guide و API Reference و Getting Started" وغير ذلك. هل يمكنك رؤية موضوعات توضّح كيفية إعداد توجيه عناوين URL والقوالب وقواعد البيانات والنماذج؟ هل التوثيق واضح؟ انتقل إلى القوائم البريدية الخاصة بكل موقع (يمكن الوصول إليها من روابط المجتمع). كم عدد الأسئلة المنشورة في الأيام القليلة الماضية؟ كم عدد الردود؟ هل لديها مجتمع نشط؟ أطر عمل الويب الجيدة لننتقل الآن ونناقش بعض أطر عمل الويب من طرف الخادم، إذ تمثل أطر العمل من طرف الخادم الآتية بعضًا من أكثر أطر العمل المتاحة شيوعًا، فلديها كل ما تحتاجه لتكون منتِجًا، فهي مفتوحة المصدر وقيد التطوير النشط ولديها مجتمعات نشطة تنشِئ توثيقًا وتساعد المستخدمين في لوحات المناقشة، وتُستخدَم في عدد كبير من مواقع الويب رفيعة المستوى. هناك العديد من أطر العمل الرائعة الأخرى من طرف الخادم التي يمكنك اكتشافها بنفسك. إطار عمل جانغو Django باستخدام لغة بايثون يُعَد جانغو إطار عمل ويب بلغة بايثون عالي المستوى ويشجّع التطوير السريع والتصميم النظيف والعملي، وقد أنشأه مطورون ذوو خبرة، وهو يعتني بالكثير من متاعب تطوير الويب، بحيث يمكنك التركيز على كتابة تطبيقك دون الحاجة إلى إعادة اختراع العجلة، وهو مجاني ومفتوح المصدر. يتبع جانغو فلسفة "أنه يتضمن كل شيء Batteries Included" التي تنص على أن إطار عمل جانغو يتضمن جميع الأجزاء الممكنة والمطلوبة للاستخدام الكامل، ويوفر تقريبًا كل شيء يمكن أن يرغب معظم المطورين في تطبيقه. بما أنه يتضمّن كل شيء، سيعمل كل شيء مع بعضه بعضًا. يتبع إطار عمل جانغو أيضًا مبادئ تصميم متناسقة، ويحتوي على توثيق شامل وحديث، وهو سريع وآمن وقابل للتوسّع. تُعَد شيفرة جانغو البرمجية سهلة القراءة والصيانة لأنه يعتمد على لغة بايثون. تشمل المواقع الشهيرة التي تستخدم إطار عمل جانغو (من صفحته الرئيسية): ديسكاس Disqus وإنستغرام Instagram و Knight Foundation و MacArthur Foundation وموزيلا Mozilla وناشونال جيوغرافيك National Geographic و Open Knowledge Foundation وبنترست Pinterest وأوبن ستاك Open Stack. إطار عمل فلاسك باستخدام لغة بايثون يُعَد فلاسك إطار عمل مصغّر للغة بايثون، وهو إطار عمل بسيط، إلّا أنه يمكنه إنشاء مواقع ويب جادة ومميزة. يحتوي فلاسك على خادم تطوير ومنقح أخطاء، ويتضمن دعمًا لإنشاء قوالب Jinja2 وملفات تعريف الارتباط الآمنة واختبار الوحدات وإرسال طلبات RESTful، ولديه توثيق جيد ومجتمع نشط. أصبح فلاسك شائعًا جدًا خاصة للمطورين الذين يحتاجون إلى تقديم خدمات الويب على أنظمة صغيرة محدودة الموارد مثل تشغيل خادم ويب على راسبيري باي Raspberry Pi وأجهزة التحكم بدون طيار وغير ذلك. إطار عمل Express باستخدام Node.js/JavaScript يُعَد Express إطار عمل ويب سريع وغير قائم على رأيه ومرن وبسيط لبيئة Node.js التي تمثل بيئةً دون متصفح لتشغيل شيفرة جافا سكريبت، ويوفر مجموعةً قويةً من الميزات لتطبيقات الويب والهاتف المحمول ويوفر طرقًا لاستخدام HTTP وبرامجًا وسيطة مفيدة. إطار عمل Express شائع الاستخدام، ويرجع ذلك إلى أنه يسهل عملية التهجير migration على مبرمجي الويب باستخدام لغة جافا سكريبت من طرف العميل إلى التطوير من طرف الخادم، ولأنه فعّال في استخدام الموارد، إذ تستخدم بيئة Node الأساسية مهامًا متعددة خفيفة الوزن ضمن خيط Thread بدلًا من إنتاج عمليات منفصلة لكل طلب ويب جديد. نظرًا لبساطة إطار عمل Express، فإنه لا يدمج كل مكوِّن ترغب في استخدامه، فهو يوفّر مثلًا الوصول إلى قاعدة البيانات والدعم للمستخدمين والجلسات من خلال مكتبات مستقلة. هناك العديد من المكونات المستقلة الممتازة، ولكن يمكن أن يكون في بعض الأحيان من الصعب تحديد أيها الأفضل لغرض معين. تعتمد العديد من أطر العمل الشائعة من طرف الخادم والشاملة (التي تتضمن أطر عمل من طرف الخادم والعميل) على إطار عمل Express بما في ذلك أطر عمل Feathers و ItemsAPI و KeystoneJS و Kraken و LoopBack و MEAN و Sails، كما تستخدم الكثير من الشركات المرموقة إطار عمل Express بما في ذلك شركات أوبر Uber و Accenture و IBM وغيرها. اطّلع على قائمة هذه الشركات الكاملة. إطار عمل دينو Deno باستخدام لغة جافا سكريبت دينو هو إطار عمل بسيط وحديث وآمن في وقت التنفيذ خاص بلغتي جافا سكريبت و TypeScript، وأُنشِئ على محرك Chrome V8 ولغة رست Rust. إطار عمل دينو مدعوم من طرف مكتبة Tokio التي توفّر وقت تنفيذ غير متزامن قائم على لغة رست وتتيح لإطار عمل دينو خدمة صفحات الويب بصورة أسرع، وتدعم WebAssembly داخليًا، والذي يتيح تصريف Compilation الشيفرة الثنائية لاستخدامها من طرف العميل. يهدف دينو إلى سد بعض الثغرات في بيئة Node.js من خلال توفير آلية تحافظ على أمان أفضل. تشمل ميزات إطار عمل دينو ما يلي: الأمان افتراضيًا، إذ تقيّد وحدات دينو أذونات الوصول إلى الملفات، أو الشبكة، أو البيئة ما لم يُسمَح بذلك صراحةً. يدعم لغة TypeScript. آلية انتظار من الدرجة الأولى. أداة اختبار ومنسّق شيفرة برمجية مُدمَجَين (deno fmt). التوافق مع المتصفحات (جافا سكريبت): يجب أن تعمل برامج دينو المكتوبة كاملًا بلغة جافا سكريبت باستثناء فضاء الأسماء Deno، أو ميزاتها التي جرى اختبارها، مباشرةً في أيّ متصفح حديث. تجميع السكريبت في ملف جافا سكريبت واحد. يوفّر إطار عمل دينو طريقةً سهلةً لكنها قويةً لاستخدام لغة جافا سكريبت لكل من البرمجة من طرف العميل وطرف الخادم. إطار عمل روبي أون ريلز Ruby on Rails باستخدام لغة روبي Ruby ريلز -أو كما يشار إليه عادةً باسم روبي أون ريلز- هو إطار عمل ويب مكتوب للغة البرمجة روبي. يتبع ريلز فلسفة تصميم مشابهة جدًا لفلسفة إطار عمل جانغو من خلال توفير آليات معيارية لتوجيه عناوين URL والوصول إلى البيانات من قاعدة بيانات وإنشاء شيفرة HTML من القوالب وتنسيق البيانات بتنسيق JSON أو XML. يشجّع إطار عمل ريلز على استخدام أنماط التصميم، مثل نمط DRY "لا تكرر نفسك Don't Repeat Yourself" -أي كتابة الشيفرة البرمجية مرة واحدة فقط إذا كان ذلك ممكنًا- ونمط MVC وهو نموذج-عرض-متحكِّم Model-View-Controller وغير ذلك. هناك طبعًا عدة اختلافات بسبب قرارات التصميم وطبيعة لغات البرمجة. اُستخدِم إطار عمل ريلز للمواقع البارزة مثل Basecamp وغيت هب GitHub و Shopify و Airbnb و Twitch وساوند كلاود SoundCloud وهولو Hulu و Zendesk و Square و Highrise. إطار عمل لارافيل Laravel باستخدام لغة PHP لارافيل هو إطار عمل لتطبيق ويب يستخدم صياغةً معبرةً وأنيقة، فهو يحاول التخلص من مصاعب التطوير من خلال تسهيل المهام الشائعة المُستخدَمة في غالبية مشاريع الويب مثل: محرّك توجيه بسيط وسريع. حاوية حقن اعتماديات قوية. نهايات خلفية متعددة لتخزين الجلسة والذاكرة المؤقتة. قاعدة بيانات ORM معبّرة وبديهية. عمليات تهجير حيادية لمخطط قاعدة البيانات. معالجة خلفية قوية. بث الحدث في الوقت الفعلي. يُعَد إطار عمل لارافيل شاملًا، وهو قوي ويوفر الأدوات اللازمة للتطبيقات الكبيرة والقوية. إطار عمل ASP.NET ASP.NET هو إطار عمل ويب مفتوح المصدر طوّرته مايكروسوفت لبناء تطبيقات وخدمات الويب الحديثة. يمكنك باستخدام إطار عمل ASP.NET إنشاء مواقع ويب تعتمد على لغات HTML و CSS وجافا سكريبت بسرعة وتوسيع نطاقها ليستخدمها ملايين المستخدمين وإضافة إمكانات أكثر تعقيدًا بسهولة مثل واجهات Web API، أو النماذج عبر البيانات، أو الاتصالات في الوقت الفعلي. أحد العوامل المميزة لإطار عمل ASP.NET هو أنه مبني على وقت التنفيذ المشترك للغات Common Language Runtime -أو CLR اختصارًا، مما يسمح للمبرمجين بكتابة شيفرة ASP.NET باستخدام أي لغة "‎.NET" مدعومة مثل C#‎ وفيجوال بيسك Visual Basic وغيرها. يستفيد إطار عمل ASP.NET -مثل العديد من منتجات مايكروسوفت- من أدوات ممتازة ومجانية غالبًا ومجتمع مطورين نشط وتوثيق جيد، وتستخدم العديد من الشركات إطار عمل ASP.NET مثل مايكروسوفت و Xbox.com و Stack Overflow وغيرها الكثير. إطار عمل Mojolicious باستخدام لغة بيرل Perl Mojolicious هو إطار عمل ويب من الجيل التالي للغة برمجة بيرل Perl. تعلّم الكثير من الناس لغة بيرل بسبب مكتبتها الرائعة التي تُدعَى CGI في الأيام الأولى للويب، وكان الأمر بسيطًا بما يكفي للبدء دون معرفة الكثير عن اللغة ومناسبًا بما يكفي للاستمرار، إذ يطبّق إطار عمل Mojolicious هذه الفكرة باستخدام تقنيات حد النزيف Bleeding Edge، وهي نوع من التقنيات التي تصدر للجميع بالرغم من عدم اكتمال اختبارها ويمكن أن تكون غير موثوقة. بعض الميزات التي يوفرها إطار عمل Mojolicious هي: إطار عمل ويب في الوقت الفعلي لتنمية نماذج الملف الواحد الأولية بسهولة إلى تطبيقات ويب MVC جيدة التنظيم. وجهات RESTful، وإضافات، وأوامر، وقوالب Perl-ish، ومفاوضات المحتوى، وإدارة الجلسات وكذلك التحقق من صحة النموذج، واختبار إطار العمل، وخادم الملفات الساكن، واكتشاف CGI/PSGI، ودعم الترميز الموحّد Unicode من الدرجة الأولى. تقديم بروتوكول HTTP الشامل وبروتوكول WebSocket للعميل/الخادم مع دعم بروتوكولات IPv6 و TLS و SNI و IDNA، ووسيط HTTP/SOCKS5، ومقبس Socket نطاق يونيكس UNIX، وتقنية Comet (الاستطلاع المفتوح Long Polling)، واستمرار النشاط Keep-alive، وتجمّع الاتصالات Connection Pooling، والمهلة الزمنية، وملف تعريف الارتباط Cookie، وتعدد الأجزاء، وطريقة الضغط Gzip. محللات ومولّدات ملفات JSON و HTML و XML مع دعم محدّدات CSS. واجهة برمجة تطبيقات Pure-Perl النقية والقابلة للنقل وكائنية التوجّه دون أيّ سحر خفي. شيفرة برمجية حديثة تعتمد على سنوات من الخبرة، وهي مجانية ومفتوحة المصدر. إطار عمل Spring Boot باستخدام لغة جافا يُعَد إطار عمل Spring Boot أحد المشاريع التي قدمتها منصة Spring، وهو نقطة انطلاق جيدة لتطوير الويب من طرف الخادم باستخدام لغة جافا. ليس إطار عمل Spring Boot إطار العمل الوحيد الذي يعتمد على لغة جافا، ولكن من السهل استخدامه لإنشاء تطبيقات قائمة بذاتها في مرحلة الإنتاج وتعتمد على منصة Spring، إذ يمكنك تشغيل هذه التطبيقات مباشرةً. كما يُعَد بمثابة وجهة نظر راسخة مستندة إلى منصة Spring ومكتبات الجهات الخارجية، ولكنه يسمح بالبدء بأقل قدر من الاضطراب والإعداد. يمكن استخدام إطار عمل Spring Boot للمشاكل الصغيرة، ولكن تكمن قوته في بناء تطبيقات واسعة النطاق تستخدم نهج السحابة، ويمكن تشغيل تطبيقات متعددة بالتوازي مع بعضها بعضًا، إذ يوفّر بعضها تفاعل المستخدم ويطبّق البعض الآخر أعمال الواجهة الخلفية مثل الوصول إلى قواعد البيانات أو الخدمات الأخرى. تساعد موازنات الأحمال على ضمان وجود قدرة تعويضية Redundancy وموثوقية أو السماح بمعالجة محددة جغرافيًا لطلبات المستخدم بهدف ضمان الاستجابة. الخلاصة أظهر هذا المقال أن أطر عمل الويب يمكن أن تسهّل تطوير الشيفرة البرمجية من طرف الخادم وصيانتها، وقدّم نظرةً عامةً عالية المستوى على عدد من أطر العمل الشائعة، وناقش معايير اختيار إطار عمل تطبيق الويب. يجب أن يكون لديك الآن على الأقل فكرة عن كيفية اختيار إطار عمل الويب لتطوير تطبيقك من طرف الخادم. إذا لم يكن الأمر كذلك، فلا داعٍ للقلق، إذ سنقدم لك لاحقًا دروسًا تفصيلية حول إطاري عمل جانغو و Express لمنحك بعض الخبرة العملية مع إطار عمل ويب. سنغير الاتجاه قليلًا في المقال التالي وسنتعمّق أكثر في مجال أمان الويب. ترجمة -وبتصرُّف- للمقال Server-side web frameworks. اقرأ أيضًا المقال السابق: نظرة على تفاعلات الخادم مع العميل في موقع ويب ديناميكي تعرف على مفهوم إطار العمل Framework وأهميته في البرمجة استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا
  9. تعرّفنا في المقال السابق من هذه السلسلة تعلم تطوير الويب على الغرض والفوائد المُحتمَلة من البرمجة من طرف الخادم، وسنوضّح الآن تفصيليًا ما يحدث عندما يتلقى الخادم طلبًا ديناميكيًا من المتصفح. تتعامل معظم الشيفرات البرمجية من طرف خادم موقع الويب مع الطلبات والاستجابات بطرق مماثلة، فسيساعدك ذلك على فهم ما عليك تطبيقه عند كتابة شيفرتك البرمجية. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، وفهم أساسي لخادم الويب. الهدف: فهم تفاعلات الخادم مع العميل في موقع ويب ديناميكي وخاصةً العمليات التي يجب أن تجريها الشيفرة البرمجية من طرف الخادم. لن نضع شيفرة برمجية حقيقية في هذه المناقشة لأننا لم نختر بعد إطار عمل ويب لاستخدامه في كتابة شيفرتنا البرمجية، ولكن لا تزال هذه المناقشة وثيقة الصلة بالموضوع، لأنه يجب تقديم السلوك الموصوف من خلال شيفرة برمجية من طرف الخادم بغض النظر عن لغة البرمجة أو إطار عمل الويب الذي تحدده. مفهوم خوادم الويب وبروتوكول HTTP تتواصل متصفحات الويب مع خوادم الويب باستخدام بروتوكول نقل النص التشعبي HyperText Transfer Protocol -أو اختصارًا HTTP. إذا نقرتَ على رابط في صفحة ويب أو أرسلتَ نموذجًا أو أجريتَ بحثًا، سيرسل المتصفح طلب HTTP إلى الخادم، ويشمل هذا الطلب ما يلي: عنوان URL الذي يعرِّف الخادم والمورد الهدف مثل ملف HTML، أو نقطة بيانات معينة على الخادم، أو أداة للتشغيل. طريقة تحدد الإجراء المطلوب للحصول على ملف، أو حفظ، أو تحديث بعض البيانات مثلًا. الطرق أو الأفعال المختلفة والإجراءات المرتبطة بها هي ما يلي: GET: الحصول على مورد معين مثل ملف HTML يحتوي على معلومات حول منتج أو قائمة منتجات. POST: إنشاء مورد جديد مثل إضافة مقال جديد إلى مواقع ويكي Wiki، أو إضافة جهة اتصال جديدة إلى قاعدة بيانات. HEAD: الحصول على معلومات البيانات الوصفية حول مورد معين دون الحصول على المتن كما تفعل الطريقة GET. يمكنك مثلًا استخدام طلب الطريقة HEAD لمعرفة آخر تحديث لأحد الموارد، ثم استخدام طلب الطريقة GET (الأكثر تكلفة) لتنزيل المورد عند تغييره فقط. PUT: تحديث مورد موجود مسبقًا أو إنشاء مورد جديد إن لم يكن موجودًا مسبقًا. DELETE: حذف المورد المحدد. TRACE و OPTIONS و CONNECT و PATCH: تُستخدَم هذه الأفعال لمهام أقل شيوعًا وتقدمًا، لذلك لن نشرحها هنا. يمكن ترميز المعلومات الإضافية مع الطلب مثل بيانات نموذج HTML، إذ يمكن ترميز هذه المعلومات على النحو التالي: معاملات URL: تطلب الطريقة GET تشفير البيانات في عنوان URL المُرسَل إلى الخادم من خلال إضافة أزواج الاسم/القيمة في نهايته مثل العنوان http://example.com?name=Fred&age=11. لديك دائمًا علامة استفهام (?) تفصل بقية عنوان URL عن معاملات URL، وإشارة مساواة (=) تفصل كل اسم عن القيمة المرتبطة به، وعلامة العطف (&) التي تفصل بين الأزواج. تُعَد معاملات URL غير آمنة بطبيعتها، إذ يمكن للمستخدمين تغييرها ثم إعادة إرسالها، ولذلك لا تُستخدَم معاملات عنوان URL أو طلبات GET مع الطلبات التي تحدّث البيانات على الخادم. بيانات POST: تضيف طلبات POST مواردًا جديدة، وتُشفَّر بياناتها ضمن متن الطلب. ملفات تعريف الارتباط Cookies من طرف العميل: تحتوي ملفات تعريف الارتباط على بيانات جلسة العميل بما في ذلك المفاتيح التي يمكن للخادم استخدامها لتحديد حالة تسجيل الدخول والأذونات أو الوصول إلى الموارد. تنتظر خوادم الويب رسائل طلب العميل وتعالجها عند وصولها، وترد على متصفح الويب برسالة استجابة HTTP، إذ تحتوي الاستجابة على رمز حالة استجابة HTTP الذي يشير إلى نجاح الطلب من عدمه مثل "200‎ OK" للنجاح و "‎404 Not Found" إذا تعذر العثور على المورد و "‎403 Forbidden" إذا كان المستخدم غير مصرَّح له برؤية الموارد وغير ذلك. يمكن أن يحتوي متن الاستجابة الناجحة لطلب "GET" على المورد المطلوب. يعرض متصفح الويب صفحة HTML المُعادة، ويمكن أن يكتشف المتصفح روابطًا إلى موارد أخرى بمثابة جزء من المعالجة، إذ تشير صفحة HTML عادةً إلى صفحات جافا سكريبت و CSS مثلًا، وسيرسِل طلبات HTTP منفصلة لتنزيل هذه الملفات. تستخدم كل من مواقع الويب الساكنة والديناميكية التي سنناقشها لاحقًا بروتوكول أو أنماط الاتصال نفسها تمامًا. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن مثال عن طلب/استجابة GET يمكنك تقديم طلب GET بسيط من خلال النقر على رابط أو البحث في موقع (مثل الصفحة الرئيسية لمحرّك البحث)، فمثلًا سيبدو طلب HTTP المُرسَل عند إجراء بحث على موقع MDN للمصطلح "client-server overview" إلى حد كبير مثل النص الذي سنوضّحه لاحقًا (لن يكون متطابقًا لاعتماد أجزاء من الرسالة على متصفحك أو إعداداتك). ملاحظة: يُحدَّد تنسيق رسائل HTTP في معيار الويب RFC9110. لست بحاجة إلى معرفة هذا المستوى من التفاصيل، ولكنك تعرف الآن على الأقل من أين أتى كل ذلك. الطلب request يحتوي كل سطر من الطلب على معلومات عنه. يُطلَق على الجزء الأول اسم الترويسة header، ويحتوي على معلومات مفيدة حول الطلب بالطريقة نفسها التي تحتوي بها ترويسة HTML على معلومات مفيدة حول مستند HTML، ولكن ليس المحتوى الفعلي نفسه الموجود في المتن. GET /en-US/search?q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev HTTP/1.1 Host: developer.mozilla.org Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Referer: https://developer.mozilla.org/en-US/ Accept-Encoding: gzip, deflate, sdch, br Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7 Accept-Language: en-US,en;q=0.8,es;q=0.6 Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true يحتوي السطران الأول والثاني على معظم المعلومات التي تحدثنا عنها سابقًا وهي: نوع الطلب (GET). عنوان URL للمورد الهدف (‎/en-US/search). معاملات عنوان URL: q=client%2Bserver%2Boverview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev موقع الويب الهدف أو المضيف (developer.mozilla.org). تتضمن نهاية السطر الأول سلسلة نصية قصيرة تحدد إصدار البروتوكول المُحدَّد (HTTP/1.1). يحتوي السطر الأخير على معلومات حول ملفات تعريف الارتباط من طرف العميل، إذ يمكنك أن ترى في هذه الحالة أن ملف تعريف الارتباط يتضمن معرّفًا لإدارة الجلسات: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; …‎ تحتوي الأسطر المتبقية على معلومات حول المتصفح المُستخدَم ونوع الاستجابات التي يمكنه التعامل معها، إذ يمكنك أن ترى هنا مثلًا ما يلي: المتصفح (User-Agent) هو موزيلا فايرفوكس (Mozilla/5.0). يمكن للمتصفح قبول معلومات Gzip المضغوطة (Accept-Encoding: gzip). يمكنه قبول مجموعة محدَّدة من المحارف (Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7) واللغات (Accept-Language: en-US,en;q=0.8,es;q=0.6). يشير السطر Referer إلى عنوان صفحة الويب التي تحتوي على رابط هذا المورد (مثل أصل الطلب https://developer.mozilla.org/en-US/‎). يمكن أن تحتوي طلبات HTTP على متن، ولكنه فارغ في هذه الحالة. الاستجابة سنعرض لاحقًا الجزء الأول من الاستجابة لهذا الطلب، إذ تحتوي الترويسة على معلومات كما يلي: يتضمن السطر الأول رمز الاستجابة "200‎ OK‎" الذي يمثل نجاح الطلب. يمكننا أن نرى أن الاستجابة بتنسيق "text/html" (نوع المحتوى Content-Type). يمكننا أن نرى استخدام مجموعة محارف UTF-8 بالشكل: Content-Type: text/html; charset=utf-8. تخبرنا الترويسة عن حجمها (Content-Length: 41823). نرى في نهاية الرسالة محتوى المتن الذي يحتوي على شيفرة HTML الفعلية التي يعيدها الطلب. HTTP/1.1 200 OK Server: Apache X-Backend-Server: developer1.webapp.scl3.mozilla.com Vary: Accept, Cookie, Accept-Encoding Content-Type: text/html; charset=utf-8 Date: Wed, 07 Sep 2016 00:11:31 GMT Keep-Alive: timeout=5, max=999 Connection: Keep-Alive X-Frame-Options: DENY Allow: GET X-Cache-Info: caching Content-Length: 41823 <!DOCTYPE html> <html lang="en-US" dir="ltr" class="redesign no-js" data-ffo-opensanslight=false data-ffo-opensans=false > <head prefix="og: http://ogp.me/ns#"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script> … يتضمن الجزء المتبقي من ترويسة الاستجابة معلومات حول الاستجابة (مثل وقت إنشائها) والخادم وكيف يتوقع أن يتعامل المتصفح مع الصفحة (مثل السطر X-Frame-Options: DENY الذي يخبر المتصفح بعدم السماح لتلك الصفحة بتضمينها ضمن العنصر <iframe> في موقع آخر). مثال عن طلب/استجابة POST يمكن إجراء الطريقة "POST" الخاصة ببروتوكول HTTP عند إرسال نموذج يحتوي على معلومات لحفظها على الخادم. الطلب يوضّح النص التالي طلب HTTP، الذي يُجرَى عندما يرسل المستخدم تفاصيل جديدة للملف الشخصي على هذا الموقع. يماثل تنسيق الطلب تقريبًا طلب "GET" الموضح سابقًا، بالرغم من أن السطر الأول يعرِّف هذا الطلب على أنه طلب "POST". POST /en-US/profiles/hamishwillee/edit HTTP/1.1 Host: developer.mozilla.org Connection: keep-alive Content-Length: 432 Pragma: no-cache Cache-Control: no-cache Origin: https://developer.mozilla.org Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.8,es;q=0.6 Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url= لكن الاختلاف الرئيسي بينهما هو عدم احتواء عنوان URL على أية معاملات، وتُرمَّز المعلومات من النموذج في متن الطلب، فمثلًا يُضبَط الاسم الكامل للمستخدم الجديد باستخدام: ‎&user-fullname=Hamish+Willee الاستجابة response يوضّح النص التالي استجابة الطلب، إذ يخبر رمز الحالة "302‎ Found" المتصفح بنجاح الإرسال، وأنه يجب عليه إصدار طلب HTTP ثانٍ لتحميل الصفحة المُحدَّدة في الحقل Location. تكون معلومات الاستجابة مشابهة للمعلومات الخاصة بالاستجابة على طلب "GET". HTTP/1.1 302 FOUND Server: Apache X-Backend-Server: developer3.webapp.scl3.mozilla.com Vary: Cookie Vary: Accept-Encoding Content-Type: text/html; charset=utf-8 Date: Wed, 07 Sep 2016 00:38:13 GMT Location: https://developer.mozilla.org/en-US/profiles/hamishwillee Keep-Alive: timeout=5, max=1000 Connection: Keep-Alive X-Frame-Options: DENY X-Cache-Info: not cacheable; request wasn't a GET or HEAD Content-Length: 0 ملاحظة: اُلتقِطت استجابات وطلبات HTTP الموضَّحة في هذه الأمثلة باستخدام تطبيق Fiddler، ولكن يمكنك الحصول على معلومات مماثلة باستخدام أدوات التعرّف على متصفحات الويب Web Sniffers (مثل الأداة Websniffer)، أو أدوات تحليل الحزم مثل Wireshark التي يمكنك تجربتها بنفسك. استخدم أيًا من هذه الأدوات، ثم تنقّل عبر الموقع وعدّل معلومات الملف الشخصي لمشاهدة الطلبات والاستجابات المختلفة. تحتوي معظم المتصفحات الحديثة على أدوات مراقبة طلبات الشبكة مثل أداة مراقبة الشبكة Network Monitor في فايرفوكس. المواقع الساكنة الموقع الساكن Static Site هو الموقع الذي يعيد المحتوى الثابت نفسه من الخادم كلما طُلِب مورد معين. لذلك، إذا كانت لديك صفحةٌ عن منتجٍ ما في الوجهة "‎/static/myproduct1.html" مثلًا، ستُعاد هذه الصفحة نفسها إلى جميع المستخدمين. إذا أضفت منتجًا مشابهًا آخر إلى موقعك، فيجب إضافة صفحة أخرى (مثل الصفحة "myproduct2.html") وهكذا. يمكن لذلك أن يفقد فعاليته عندما يكون لديك آلاف صفحات المنتجات، إذ ستكِّرر الكثير من الشيفرة البرمجية عبر كل صفحة (قالب الصفحة الأساسي وبنيتها وغير ذلك)، وإذا أردت تغيير أي شيء يتعلق ببنية الصفحة مثل إضافة قسم جديد للمنتجات ذات الصلة، فيجب تغيير كل صفحة على حدة. ملاحظة: تُعَد المواقع الساكنة ممتازة عندما يكون لديك عددٌ قليلٌ من الصفحات وتريد إرسال المحتوى نفسه إلى جميع المستخدمين، ولكن يمكن أن يكون لها تكلفة صيانة كبيرة عندما يصبح عدد الصفحات أكبر. لنلخص كيفية عمل ذلك من خلال الاطلاع مرةً أخرى على مخطط معمارية الموقع الساكن الذي تحدثنا عنه في المقال السابق. إذا أراد المستخدم الانتقال إلى صفحةٍ ما، فسيرسل المتصفح طلب HTTP من النوع "GET" الذي يحدّد عنوان URL لصفحة HTML. يسترجع الخادم المستند المطلوب من نظام ملفاته ويعيد استجابة HTTP تحتوي على المستند ورمز حالة استجابة HTTP الذي يشير إلى النجاح "200‎ OK". يمكن أن يعيد الخادم رمز حالة مختلف مثل "‎404 Not Found" إذا لم يكن الملف موجودًا على الخادم أو الرمز "301‎ Moved Permanently" إذا كان الملف موجودًا ولكن أُعيد توجيهه إلى موقع مختلف. يحتاج خادم الموقع الساكن فقط إلى معالجة طلبات GET، لأن الخادم لا يخزّن أيّ بيانات قابلة للتعديل، ولا يغيّر استجاباته بناءً على بيانات طلب HTTP (مثل معاملات URL أو ملفات تعريف الارتباط)، لكن يُعَد فهم كيفية عمل المواقع الساكنة مفيدًا عند تعلم البرمجة من طرف الخادم، لأن المواقع الديناميكية تتعامل مع طلبات الملفات الساكنة (ملفات CSS وجافا سكريبت والصور الساكنة وغير ذلك) بالطريقة نفسها تمامًا. المواقع الديناميكية الموقع الديناميكي Dynamic Site هو الموقع الذي يمكنه إنشاء المحتوى وإعادته بناءً على عنوان URL والبيانات المحدَّدة للطلب بدلًا من إعادة الملف الثابت نفسه لعنوان URL معين. يخزّن الخادم في مثال موقع المنتجات بيانات المنتج في قاعدة بيانات بدلًا من ملفات HTML الفردية، ويحدّد معرّف المنتج ويجلب البيانات من قاعدة البيانات، ثم يبني صفحة HTML للاستجابة من خلال إدراج البيانات في قالب HTML عند تلقي طلب HTTP من النوع "GET" لمنتجٍ ما. للموقع الديناميكي مزايا كبيرة يتفوق بها على الموقع الساكن، وهي: يسمح استخدام قاعدة البيانات بتخزين معلومات المنتج بكفاءة بطريقة قابلة للتوسّع والتعديل والبحث بسهولة. يسهّل استخدام قوالب HTML تغيير بنية ملف HTML، لأنه يُطبَّق في مكان واحد فقط وفي قالب واحد، وليس عبر آلاف الصفحات الساكنة المُحتمَلة. تحليل الطلب الديناميكي يوضّح هذا القسم دورة طلب واستجابة HTTP الديناميكية بناءً على ما تحدثنا عنه في المقال السابق بمزيد من التفاصيل. سنستخدم سياق موقع ويب مدير فريق رياضي، إذ يمكن للمدرب تحديد اسم فريقه وحجم الفريق في نموذج HTML والحصول على أفضل تشكيلة مقترحة للمباراة القادمة. يوضح الشكل التالي العناصر الرئيسية لموقع مدرب الفريق مع تسميات مرقَّمة لتسلسل العمليات عندما يصل المدرب إلى قائمة "أفضل فريق". أجزاء الموقع التي تجعله ديناميكيًا هي تطبيق الويب Web Application (وهو الطريقة التي سنشير بها إلى الشيفرة البرمجية من طرف الخادم الذي يعالج طلبات HTTP ويعيد استجابات HTTP)، وقاعدة البيانات التي تحتوي على معلومات حول اللاعبين والفرق والمدربين وعلاقاتهم، وقوالب HTML. يكون تسلسل العمليات كما يلي بعد أن يرسل المدرب النموذج مع اسم الفريق وعدد اللاعبين: ينشئ متصفح الويب طلب HTTP من النوع "GET" للخادم باستخدام عنوان URL الأساسي للمورد (‎/best) ويرمّز الفريق ورقم اللاعب إما مثل معاملات URL (مثل ‎/best?team=my_team_name&show=11) أو مثل جزء من عنوان URL للنمط (مثل /best/my_team_name/11/). يُستخدَم طلب "GET" لأن الطلب يجلب البيانات فقط ولا يعدّلها. يكتشف خادم الويب أن الطلب ديناميكي ويوجّهه إلى تطبيق الويب للمعالجة. يحدّد خادم الويب كيفية التعامل مع عناوين URL المختلفة بناءً على قواعد مطابقة الأنماط المُعرّفة في ضبطه. يحدد تطبيق الويب أن القصد من الطلب هو الحصول على "قائمة أفضل فريق" استنادًا إلى عنوان URL (وهو /best/) ويكتشف اسم الفريق المطلوب وعدد اللاعبين من عنوان URL. يحصل تطبيق الويب بعد ذلك على المعلومات المطلوبة من قاعدة البيانات باستخدام معاملات داخلية إضافية لتحديد اللاعبين الأفضل، ويمكن أن يحصل على هوية المدرب الذي سجّل الدخول من ملف تعريف الارتباط من طرف العميل. ينشئ تطبيق الويب صفحة HTML ديناميكيًا من خلال وضع البيانات (من قاعدة البيانات) في عناصر بديلة ضمن قالب HTML. يعيد تطبيق الويب ملف HTML المُنشَأ إلى متصفح الويب (عبر خادم الويب) مع رمز حالة HTTP التي تمثل النجاح وهي 200. إذا كان هناك أي شيء يمنع إعادة ملف HTML، فسيعيد تطبيق الويب رمزًا آخر مثل الرمز "404" للإشارة إلى أن الفريق غير موجود. يبدأ متصفح الويب بعد ذلك بمعالجة ملف HTML المُعاد، وإرسال طلبات منفصلة للحصول على أيّ ملفات CSS أو جافا سكريبت أخرى يشير إليها (راجع الخطوة 7). يحمّل خادم الويب الملفات الساكنة من نظام الملفات ويعيدها إلى المتصفح مباشرةً. تعتمد المعالجة الصحيحة للملفات على قواعد الإعداد ومطابقة نمط عنوان URL. تُعالَج عملية تحديث سجل في قاعدة البيانات بصورة مشابهة باستثناء أنه يجب ترميز طلب HTTP من المتصفح بوصفه طلب "POST" مثل أيّ تحديث لقاعدة البيانات. تنفيذ أعمال أخرى تتمثل مهمة تطبيق الويب في استقبال طلبات HTTP وإعادة استجابات HTTP. يكون التفاعل مع قاعدة بيانات للحصول على المعلومات أو تحديثها مهامًا شائعة جدًا، ولكن يمكن أن تطبّق الشيفرة البرمجية أشياءً أخرى في الوقت نفسه أو يمكن ألّا تتفاعل مع قاعدة بيانات إطلاقًا. من الأمثلة الجيدة على المهمة الإضافية التي يمكن أن يؤدّيها تطبيق الويب إرسال بريد إلكتروني إلى المستخدمين لتأكيد تسجيلهم في الموقع، كما يمكن أن يطبّق الموقع عمليات التسجيل، أو عمليات أخرى. إعادة شيء آخر غير ملف HTML لا يتعين على شيفرة موقع الويب البرمجية من طرف الخادم إعادة أجزاء أو ملفات HTML في الاستجابة، بل يمكنها بدلًا من ذلك إنشاء وإعادة أنواع أخرى من الملفات ديناميكيًا (نصوص وملفات PDF و CSV وغيرها) أو حتى بيانات (JSON و XML وغير ذلك). كانت فكرة إعادة البيانات إلى متصفح الويب ليتمكّن من تحديث محتواه AJAX ديناميكيًا موجودةٌ منذ فترة طويلة، ولكن أصبحت "التطبيقات أحادية الصفحة Single-page Apps" شائعةً في الآونة الأخيرة، إذ يُكتَب كامل موقع الويب باستخدام ملف HTML واحد يُحدَّث ديناميكيًا عند الحاجة. تدفع مواقع الويب المُنشَأة باستخدام هذا الأسلوب من التطبيقات الكثير من التكلفة الحوسبية من الخادم إلى متصفح الويب، ويمكن أن تؤدي إلى ظهور مواقع الويب التي يبدو أنها تتصرف كثيرًا مثل التطبيقات الأصيلة native (سريعة الاستجابة وما إلى ذلك). تبسيط أطر عمل الويب لبرمجة الويب من طرف الخادم تسهّل أطر عمل الويب من طرف الخادم كثيرًا كتابة الشيفرة البرمجية للتعامل مع العمليات التي وضّحناها سابقًا، وتتمثل إحدى أهم العمليات التي تطبّقها أطر عمل الويب في توفير آليات بسيطة لربط عناوين URL لمصادر أو صفحات مختلفة مع دوال معالجة محددة، مما يسهّل الاحتفاظ بالشيفرة البرمجية المرتبطة بكل نوعٍ من الموارد بصورة منفصلة، كما توجد فوائد من حيث الصيانة، إذ يمكنك تغيير عنوان URL المستخدَم لتقديم ميزة معينة في مكانٍ واحد دون الحاجة إلى تغيير دالة المعالجة. افترض مثلًا الشيفرة البرمجية التالية باستخدام إطار عمل جانغو Django (باستخدام لغة بايثون Python)، الذي يربط نمطي عنوان URL مع دالتي عرض. يضمن النمط الأول تمرير طلب HTTP مع عنوان URL للمورد ‎/best إلى الدالة index()‎ في الوحدة views. بينما سيُمرَّر الطلب الذي يحتوي على النمط ‎/best/junior إلى دالة العرض junior()‎. # الملف: best/urls.py # from django.conf.urls import url from . import views urlpatterns = [ # مثال: /best/ url(r'^$', views.index), # مثال: /best/junior/ url(r'^junior/$', views.junior), ] ملاحظة: يمكن أن تبدو المعاملات الأولى في دوال url()‎ غريبةً بعض الشيء (مثل المعامل r'^junior/$'‎) لأنها تستخدم تقنية مطابقة النمط التي تسمى التعابير النمطية Regular Expressions -أو RegEx أو RE اختصارًا. لست بحاجة إلى معرفة كيفية عمل التعابير النمطية حاليًا، ولكن يجب أن تعرف أنها تسمح بمطابقة الأنماط في عنوان URL (عوضًا عن القيم الثابتة) وتُستخدَم بوصفها معاملات في دوال العرض. يمكن أن يقول التعبير النمطي RegEx البسيط: "التطابق مع حرف كبير واحد متبوع بما بين 4 إلى 7 أحرف صغيرة" مثلًا. يسهّل إطار عمل الويب على دالة العرض إحضار المعلومات من قاعدة البيانات. تُعرّف بنية بياناتنا في النماذج وهي أصناف بايثون تعرّف الحقول المراد تخزينها في قاعدة البيانات الأساسية. إذا كان لدينا نموذج يسمى "Team" مع الحقل "team_type"، فيمكننا استخدام صيغة استعلام بسيطة لاستعادة جميع الفرق التي لديها نوع معين. يحصل المثال التالي على قائمة بجميع الفرق التي يكون لديها الحقل team_type له القيمة "junior" تمامًا (حساسة لحالة الأحرف). لاحظ تنسيق اسم الحقل team_type متبوع بشرطة سفلية مزدوجة ثم نوع التطابق المراد استخدامه وهو exact في هذه الحالة. هناك العديد من أنواع التطابقات الأخرى ويمكننا ترتيبها ضمن سلسلة تعاقبية والتحكم في ترتيبها وعدد النتائج المُعادة. #best/views.py from django.shortcuts import render from .models import Team def junior(request): list_teams = Team.objects.filter(team_type__exact="junior") context = {'list': list_teams} return render(request, 'best/index.html', context) تستدعي الدالة junior()‎ الدالة render()‎ بعد أن تحصل على قائمة الفرق الناشئة، وتمرّر كائن HttpRequest الأصلي وقالب HTML وكائن السياق الذي يعرّف المعلومات التي ستُضمَّن في القالب. تُعَد الدالة render()‎ دالةً ملائمةً تنشئ ملف HTML باستخدام سياق وقالب HTML وتعيده ضمن كائن HttpResponse. يمكن أن تساعدك أطر عمل الويب في كثيرٍ من المهام الأخرى، وسنناقش الكثير من الفوائد وبعض خيارات أطر عمل الويب الشائعة في المقال القادم. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن الخلاصة يجب أن يكون لديك الآن فهمٌ جيدٌ للعمليات التي يجب أن تطبّقها الشيفرة البرمجية من طرف الخادم، ومعرفة بعض الطرق التي يمكن من خلالها لإطار عمل الويب من طرف الخادم تسهيل ذلك. سنساعدك في المقال التالي في اختيار أفضل إطار عمل ويب لموقعك الأول. ترجمة -وبتصرُّف- للمقال Client-Server Overview. اقرأ أيضًا المقال السابق: مدخل إلى برمجة مواقع الويب من طرف الخادم دليل إعداد خادم ويب محلي خطوة بخطوة إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP
  10. سنجيب في هذه السلسلة من المقالات سلسلة تعلم تطوير الويب على بعض الأسئلة الأساسية حول برمجة موقع الويب من طرف الخادم، مثل ماهية البرمجة من طرف الخادم واختلافها عن البرمجة من طرف العميل وسبب فائدتها الكبيرة، كما سنقدم نظرةً عامةً على بعض أكثر أطر عمل الويب من طرف الخادم شيوعًا مع إرشادات حول كيفية اختيار إطار العمل الأنسب لإنشاء مشروعك الأول، وسنقدم مقدمةً تمهيديةً عالية المستوى حول أمان خادم الويب. المتطلبات الأساسية لا تحتاج إلى معرفة برمجة مواقع الويب من طرف الخادم أو أيّ نوع آخر من البرمجة قبل البدء، ولكن يجب أن تفهم شيئًا ما عن طريقة عمل مواقع الويب وخوادم الويب، لذلك نوصي بقراءة المقالات التالية: مدخل إلى خادم الويب. تثبيت البرمجيات الأساسية للانطلاق في تطوير الويب. رفع ملفات موقع الويب إلى خادم على الإنترنت. ستكون جاهزًا للعمل من خلال الفهم الأساسي الذي تكتسبه من هذه المقالات. تتألف هذه السلسلة الفرعية من المقالات التالية: مدخل إلى برمجة مواقع الويب من طرف الخادم: يتناول المقال الأول برمجة موقع الويب من طرف الخادم عالية المستوى، وتجيب على أسئلة، مثل ماهية البرمجة من طرف الخادم واختلافها عن البرمجة من طرف العميل وسبب فائدتها الكبيرة. ستفهم بعد قراءة هذا المقال القدرات الإضافية المتاحة لمواقع الويب من خلال كتابة الشيفرة البرمجية من طرف الخادم. نظرة عامة على تفاعلات الخادم مع العميل في موقع ويب ديناميكي: سنختبر ما يحدث عندما يتلقى الخادم طلبًا ديناميكيًا من متصفح بعد معرفة الغرض والفوائد المحتملة من البرمجة من طرف الخادم. بما أن معظم الشيفرة البرمجية من طرف الخادم لمواقع الويب تتعامل مع الطلبات والاستجابات بطريقة مماثلة، سيساعدك ذلك على فهم ما عليك فعله عند كتابة شيفرتك البرمجية. أطر عمل الويب من طرف الخادم: أوضح المقال السابق ما يجب على تطبيق الويب من طرف الخادم فعله للاستجابة لطلبات متصفح الويب، ويشرح هذا المقال كيف يمكن لأطر عمل الويب تبسيط هذه المهام، ويساعدك على اختيار إطار العمل المناسب لأول تطبيق ويب من طرف الخادم. تعرف على أمان مواقع الويب: يتطلب أمان الموقع الحذر في جميع جوانب بناء الموقع وتشغيله. يساعدك هذا المقال التمهيدي في فهم الخطوات الأولى المهمة الممكن اتخاذها لحماية تطبيق الويب من الهجمات الأكثر شيوعًا. ملاحظة: يتناول هذا المقال أطر العمل من طرف الخادم، وكيفية استخدامها لإنشاء مواقع الويب. إذا كنت تبحث عن معلومات حول أطر عمل جافا سكريبت JavaScript من طرف العميل، فاطلع على مقال فهم أدوات تطوير الويب من طرف العميل. لا تحتوي هذه السلسلة من المقالات على أي تقييم لأننا لم نعرض لك أي شيفرة برمجية بعد، إذ يجب أن يكون لديك في هذه المرحلة فهمٌ عام للوظائف التي يمكنك تقديمها من خلال البرمجة من طرف الخادم مع اتخاذ قرار بشأن إطار عمل الويب من طرف الخادم الذي ستستخدمه لإنشاء أول تطبيق من طرف الخادم. لنبدأ بمقالنا الأول من هذه السلسلة من خلال التعرُّف على مفهوم برمجة مواقع الويب من طرف الخادم. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن تمهيد إلى برمجة مواقع الويب من طرف الخادم server-side المتطلبات الأساسية: المهارات الحاسوبية الأساسية، وفهم أساسي لخادم الويب. الهدف: التعرف على مفهوم برمجة مواقع الويب من طرف الخادم، وما يمكن أن تفعله، وكيف تختلف عن البرمجة من طرف العميل. تستخدم معظمُ مواقع الويب واسعة النطاق الشيفرةَ البرمجية من طرف الخادم لعرض بيانات مختلفة ديناميكيًا عند الحاجة، وتُسحَب من قاعدة البيانات المخزنة على الخادم وتُرسَل إلى العميل لعرضها باستخدام شيفرة برمجية مثل شيفرة لغتي HTML وجافا سكريبت. من أهم فوائد الشيفرة البرمجية من طرف الخادم أنها تسمح لك بتخصيص محتوى موقع الويب لكل مستخدم، إذ يمكن للمواقع الديناميكية إبراز المحتوى الأكثر صلة بناءً على تفضيلات المستخدم وعاداته، ويمكن أن تسهّل الشيفرة البرمجية من طرف الخادم استخدامَ المواقع من خلال تخزين التفضيلات والمعلومات الشخصية، مثل إعادة استخدام تفاصيل بطاقة الائتمان المخزنة لتبسيط المدفوعات اللاحقة، كما يمكنها السماح بالتفاعل مع مستخدمي الموقع وإرسال الإشعارات والتحديثات عبر البريد الإلكتروني، أو عبر قنوات أخرى، مما يتيح مشاركةً أعمق بكثير مع المستخدمين. ملاحظة: يُوصَى بشدة بالتعرف على تطوير مواقع الويب من طرف الخادم في العالم الحديث لتطوير الويب. ما هي برمجة مواقع الويب من طرف الخادم؟ تتواصل متصفحات الويب مع خوادم الويب باستخدام بروتوكول نقل النص التشعبي HyperText Transfer Protocol -أو اختصارًا HTTP، إذ يُرسَل طلب HTTP من متصفحك إلى الخادم الهدف عند النقر على رابط على صفحة ويب، أو إرسال نموذج، أو إجراء بحث. يتضمن الطلب عنوان URL يحدد المورد المتأثر، وطريقةً تحدد الفعل المطلوب، مثل الحصول على المورد، أو حذفه، أو نشره، ويمكن أن يتضمن الطلب أيضًا معلومات إضافية مشفرة في معاملات URL (أزواج حقل-قيمة field-value المرسَلة عبر سلسلة الاستعلام query string)، مثل بيانات POST (البيانات المرسَلة باستخدام طريقة POST لبروتوكول HTTP) أو في ملفات تعريف الارتباط Cookies المرتبطة بها. تنتظر خوادم الويب رسائل طلب العميل وتعالجها عند وصولها وترد على متصفح الويب برسالة استجابة HTTP، إذ تحتوي الاستجابة على سطر حالة يشير إلى نجاح الطلب من عدمه مثل السطر "HTTP/1.1 200 OK"، الذي يشير إلى النجاح. يمكن أن يحتوي متن الاستجابة على الطلب الناجح للمورد المطلوب (مثل صفحة HTML جديدة أو صورة)، والذي يمكن عرضه بعد ذلك باستخدام متصفح الويب. المواقع الساكنة static sites يوضّح الشكل الآتي معمارية خادم الويب الأساسية لموقع ساكن Static Site يعيد المحتوى الثابت نفسه من الخادم كلما طلب موردًا معينًا؛ فإذا أراد المستخدم الانتقال إلى صفحة ما، فسيرسل المتصفح طلب HTTP من النوع "GET" يحدد عنوان URL الخاص به. يسترجع الخادمُ المستندَ المطلوب من نظام ملفاته ويعيد استجابة HTTP التي تحتوي على المستند وحالة النجاح (عادةً ‎200 OK). إذا تعذّر استرجاع الملف لسبب ما، ستُعاد حالة خطأ (اطلع على كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعة). المواقع الديناميكية dynamic sites موقع الويب الديناميكي هو الموقع الذي يُنشَأ فيه بعضٌ من محتوى الاستجابة ديناميكيًا عند الحاجة فقط، إذ تُنشَأ صفحات HTML من خلال إدخال بيانات من قاعدة بيانات إلى عناصر بديلة في قوالب HTML، وتُعَد هذه الطريقة أكثر فاعلية من استخدام مواقع الويب الساكنة لتخزين كميات كبيرة من المحتوى. يمكن للموقع الديناميكي إعادة بيانات مختلفة لعنوان URL بناءً على المعلومات التي يقدّمها المستخدم أو التفضيلات المخزنة ويمكنه إجراء عمليات أخرى بوصفها جزءًا من إعادة الاستجابة مثل إرسال الإشعارات. يجب تشغيل معظم الشيفرة البرمجية لدعم موقع ويب ديناميكي على الخادم، ويُعرَف إنشاء هذه الشيفرة البرمجية باسم "البرمجة من طرف الخادم" أو "كتابة سكريبتات الواجهة الخلفية" أحيانًا. يوضّح الشكل الآتي معمارية بسيطة لموقع ويب ديناميكي، إذ ترسل المتصفحات طلبات HTTP إلى الخادم الذي يعالجها ويعيد استجابات HTTP المناسبة. يجري التعامل مع طلبات الموارد الساكنة باستخدام طريقة التعامل مع المواقع الساكنة نفسها، فالموارد الساكنة هي ملفات لا تتغير مثل ملفات CSS وجافا سكريبت والصور وملفات PDF المُنشَأة مسبقًا وإلخ. تُمرّر طلبات الموارد الديناميكية (الخطوة رقم 2) إلى الشيفرة البرمجية من طرف الخادم (الموضحة في الشكل السابق بوصفها تطبيق ويب)، بينما يفسّر الخادم الطلب الديناميكي، ويقرأ المعلومات المطلوبة من قاعدة البيانات (الخطوة رقم 3)، ويجمع البيانات المسترجعة مع قوالب HTML (الخطوة رقم 4)‎، ويرسل استجابة تحتوي على ملف HTML المُنشَأ (الخطوتين 5 و 6). هل تتشابه البرمجة من طرف الخادم مع البرمجة من طرف العميل؟ لنوجّه انتباهنا الآن إلى الشيفرة البرمجية الموجودة في البرمجة من طرف الخادم وطرف العميل، فهما مختلفتان كثيرًا للأسباب التالية: لديهما أغراض واهتمامات مختلفة. لا تستخدمان لغات البرمجة نفسها باستثناء لغة جافا سكريبت التي يمكن استخدامها من طرف الخادم ومن طرف العميل. تعملان ضمن بيئات أنظمة تشغيل مختلفة. تُعرَف الشيفرة البرمجية التي تعمل في المتصفح باسم الشيفرة البرمجية من طرف العميل وتهتم بتحسين مظهر وسلوك صفحة الويب المعروضة، ويتضمن ذلك تحديد مكونات واجهة المستخدم وتنسيقها وإنشاء التخطيطات والتنقل والتحقق من صحة النموذج وما إلى ذلك؛ بينما تتضمن برمجة مواقع الويب من طرف الخادم اختيار المحتوى المُعاد إلى المتصفح استجابةً للطلبات، إذ تعالج الشيفرة البرمجية من طرف الخادم مهامًا، مثل التحقق من صحة البيانات والطلبات المُرسَلة واستخدام قواعد البيانات لتخزين البيانات واسترجاعها وإرسال البيانات الصحيحة إلى العميل كما هو مطلوب. تُكتَب الشيفرة البرمجية من طرف العميل باستخدام لغات HTML وCSS وجافا سكريبت، وتعمل ضمن متصفح ويب ولديها وصول ضئيل أو معدوم إلى نظام التشغيل الأساسي بما في ذلك الوصول المحدود إلى نظام الملفات. لا يستطيع مطورو الويب التحكم في المتصفح الذي يمكن أن يستخدمه كل مستخدم لعرض موقع الويب، إذ توفر المتصفحات مستويات غير متناسقة من التوافق مع ميزات الشيفرة البرمجية من طرف العميل، ويُعَد التعامل مع الاختلافات في دعم المتصفحات بسلاسة جزءًا من التحدي المتمثل في البرمجة من طرف العميل. يمكن كتابة الشيفرة البرمجية من طرف الخادم باستخدام لغات برمجة متعددة، إذ تشمل أمثلة لغات الويب الشائعة من طرف الخادم لغات PHP وبايثون Python وروبي Ruby وC#‎ وجافا سكريبت (NodeJS)، وتتمتع الشيفرة البرمجية من طرف الخادم بالوصول الكامل إلى نظام تشغيل الخادم ويمكن للمطور اختيار لغة البرمجة (والإصدار المحدد) التي يرغبون في استخدامها. يكتب المطورون شيفرتهم البرمجية باستخدام أطر عمل الويب التي هي مجموعات من الدوال والكائنات والقواعد وبنيات الشيفرات البرمجية الأخرى المُصمَّمة لحل المشاكل الشائعة وتسريع التطوير وتبسيط الأنواع المختلفة من المهام التي تواجه مجالًا معينًا. تستخدم الشيفرة البرمجية لكل من العميل والخادم أطر عمل، ولكن المجالات مختلفة جدًا، وبالتالي تكون أطر العمل مختلفة أيضًا، إذ تعمل أطر عمل الويب من طرف العميل على تبسيط مهام التخطيط والعرض، بينما توفر أطر عمل الويب من طرف الخادم الكثير من وظائف خادم الويب الشائعة التي يمكن أن تضطر إلى تقديمها بنفسك، مثل دعم الجلسات ودعم المستخدمين والاستيثاق Authentication والوصول السهل إلى قاعدة البيانات ومكتبات القوالب وما إلى ذلك. ملاحظة: تُستخدَم أطر العمل من طرف العميل للمساعدة لتسريع تطوير الشيفرة البرمجية من طرف العميل، لكن يمكنك اختيار كتابة شيفرتك البرمجية يدويًا، والتي من الممكن أن تكون أسرع وأكثر فاعلية إذا أردتَ واجهة مستخدم موقع ويب صغيرة وبسيطة؛ بينما لن تفكر أبدًا في كتابة مكوِّن من طرف الخادم لتطبيق ويب بدون إطار عمل، فمن الصعب تقديم ميزة أساسية مثل خادم HTTP من الصفر باستخدام لغة بايثون، ولكن توفر أطر عمل الويب باستخدام لغة بايثون مثل إطار عمل جانغو Django ميزات مهمة إلى جانب أدوات أخرى مفيدة. فوائد واستخدامات البرمجة من طرف الخادم تُعَد البرمجة من طرف الخادم مفيدةً جدًا لأنها تتيح تقديم معلومات مُصمَّمة خصيصًا لكل مستخدم بكفاءة وبالتالي إنشاء تجربة مستخدم أفضل بكثير. تستخدم شركات مثل أمازون Amazon البرمجة من طرف الخادم لإنشاء نتائج البحث عن المنتجات، وتقديم اقتراحات المنتجات المستهدفة بناءً على تفضيلات العميل وعادات الشراء السابقة وتبسيط عمليات الشراء وما إلى ذلك؛ بينما تستخدم البنوك البرمجة من طرف الخادم لتخزين معلومات الحسابات والسماح للمستخدمين المُصرَّح لهم فقط بمشاهدة المعاملات وإجرائها؛ وتستخدم خدمات أخرى، مثل فيسبوك Facebook وتويتر Twitter وإنستغرام Instagram وويكيبيديا Wikipedia البرمجة من طرف الخادم لإظهار المحتوى ومشاركته والتحكم في الوصول إليه. سنذكر الآن بعض الاستخدامات والفوائد الشائعة للبرمجة من طرف الخادم، إذ ستلاحظ بعض التداخل بينها. تخزين المعلومات وتسليمها بفعالية هناك الكثير من المنتجات المتوفرة على أمازون والمنشورات المكتوبة على فيسبوك، وبالتالي سيكون إنشاء صفحة ساكنة منفصلة لكل منتج أو منشور غير عملي. تسمح البرمجة من طرف الخادم بتخزين المعلومات في قاعدة بيانات وبناء ملفات HTML وأنواع أخرى من الملفات (مثل ملفات PDF والصور وغيرها) وإعادتها ديناميكيًا. يمكن إعادة بيانات (JSON و XML وغيرها) لعرضها باستخدام أطر عمل الويب المناسبة من طرف العميل، وهذا يقلل من عبء المعالجة على الخادم وكمية البيانات التي يجب إرسالها. لا يقتصر عمل الخادم على إرسال المعلومات من قواعد البيانات، إذ يمكن أن يعيد نتيجة الأدوات البرمجية أو البيانات من خدمات الاتصالات، ويمكن استهداف المحتوى حسب نوع جهاز العميل الذي يستقبله. توجد المعلومات في قاعدة بيانات، لذا يمكن مشاركتها وتحديثها بسهولة أكبر مع أنظمة الأعمال الأخرى، فمثلًا يمكن أن يحدّث المتجر قاعدة بيانات مُخزنة عند بيع المنتجات إما عبر الإنترنت أو ضمن متجر. يمكنك معرفة فائدة الشيفرة البرمجية من طرف الخادم لتخزين المعلومات الفعال وتسليمها من خلال المثال التالي: اذهب إلى موقع أمازون أو أي موقع تجارة إلكترونية آخر. ابحث عن عدد من الكلمات الرئيسية ولاحظ عدم تغيّر بنية الصفحة بالرغم من تغيّر النتائج. افتح منتجين أو ثلاثة منتجات مختلفة، ولاحظ كيف أن لديها بنيةً وتخطيطًا مشتركًا مع سحب محتوى المنتجات المختلفة من قاعدة البيانات. يمكنك أن ترى فعليًا ملايين القيم المُعادة بالنسبة لمصطلح بحث شائع مثل "السمك"، إذ يسمح استخدام قاعدة البيانات بتخزينها ومشاركتها بفاعلية، كما يسمح بالتحكم في عرض المعلومات في مكان واحد فقط. تجربة مستخدم مخصصة يمكن للخوادم تخزين واستخدام المعلومات المتعلقة بالعملاء لتوفير تجربة مستخدم ملائمة ومُخصَّصة، فمثلًا تخزّن العديد من المواقع بطاقات الائتمان دون إدخال التفاصيل مرةً أخرى. يمكن لمواقع مثل خرائط جوجل استخدام المواقع المحفوظة أو الحالية لتوفير معلومات التوجيه والبحث، أو سجل السفر لإظهار الأنشطة التجارية المحلية في نتائج البحث. يمكن استخدام تحليل أعمق لعادات المستخدم لتوقّع اهتماماتهم وتخصيص الاستجابات والإشعارات، مثل تقديم قائمة بالمواقع التي زارها سابقًا أو التي يمكن أن يرغب في إلقاء نظرة عليها على الخريطة. تحفظ خرائط جوجل سجل البحث والزيارة، وتميّز المواقع التي تتكرر زيارتها، أو التي يتكرر البحث عنها أكثر من المواقع الأخرى. تُحسَّن نتائج بحث جوجل بناءً على عمليات البحث السابقة كما في المثال التالي: اذهب إلى بحث جوجل. ابحث عن "كرة القدم". حاول الآن كتابة كلمة "كرة" في مربع البحث ولاحظ توقعات البحث التي جرى إكمالها تلقائيًا. هل تعتقد أن هذه صدفة؟ حسنًا، إنها ليست كذلك. الوصول المتحكم فيه إلى المحتوى تسمح البرمجة من طرف الخادم للمواقع بتقييد الوصول إلى المستخدمين المُصرَّح لهم وتقديم المعلومات التي يُسمَح للمستخدم برؤيتها فقط. تشمل الأمثلة الواقعية مواقع الشبكات الاجتماعية التي تسمح للمستخدمين بتحديد مَن يمكنه رؤية المحتوى الذي ينشرونه على الموقع ويظهر على صفحتهم الرئيسية. ملاحظة: هناك أمثلة حقيقية أخرى، إذ يمكن التحكم في الوصول إلى المحتوى، مثل ما يمكنك رؤيته إذا ذهبت إلى موقع الإنترنت الخاص بالمصرف الذي تتعامل معه. سجّل الدخول إلى حسابك ولاحظ ما هي المعلومات الإضافية التي يمكنك رؤيتها وتعديلها، وما هي المعلومات التي يمكنك رؤيتها والتي يمكن للمصرف تغييرها فقط. تخزين معلومات الجلسة أو الحالة تسمح البرمجة من طرف الخادم للمطورين بالاستفادة من الجلسات sessions، والتي هي آلية تسمح للخادم بتخزين معلومات عن المستخدم الحالي للموقع وإرسال استجابات مختلفة بناءً على تلك المعلومات. يسمح ذلك لموقع ما مثلًا بمعرفة أن مستخدمًا قد سجل الدخول مسبقًا وعرض روابطًا إلى رسائل بريده الإلكتروني، أو رتب السجل، أو حفظ حالة لعبة بسيطة بحيث يمكن للمستخدم الانتقال إلى الموقع مرةً أخرى والمتابعة من حيث تركه. ملاحظة: زُر موقع صحيفة يحتوي على نموذج اشتراك وافتح مجموعة من النوافذ مثل موقع The Age. استمر في زيارة الموقع على مدار بضع ساعات أو أيام، ثم سيُعاد توجيهك إلى صفحات تشرح كيفية الاشتراك، ولن تتمكن من الوصول إلى المقالات. تمثل هذه المعلومات مثالًا عن معلومات الجلسة المخزنة في ملفات تعريف الارتباط. الإشعارات والاتصالات يمكن للخوادم إرسال إشعارات عامة أو خاصة بالمستخدم من خلال موقع الويب نفسه، أو عبر البريد الإلكتروني، أو الرسائل القصيرة، أو الرسائل الفورية، أو محادثات الفيديو، أو خدمات الاتصالات الأخرى، ومن الأمثلة على ذلك ما يلي: يرسل فيسبوك وتويتر رسائل بريد إلكتروني ورسائل قصيرة لإعلامك بالاتصالات الجديدة. يرسل أمازون بانتظام رسائل بريد إلكتروني خاصة بالمنتجات بحيث ثقترح منتجات مشابهة لتلك التي اشتريتها أو شاهدتها مسبقًا والتي يمكن أن تكون مهتمًا بها. يمكن أن يرسل خادم الويب رسائل تحذير إلى مسؤولي الموقع لتنبيههم بانخفاض الذاكرة على الخادم أو نشاط مستخدم مريب. ملاحظة: أكثر أنواع الإشعارات شيوعًا هو "تأكيد التسجيل". اختر أيّ موقع كبير تهتم به مثل جوجل وأمازون وإنستغرام وأنشئ حسابًا جديدًا باستخدام عنوان بريدك الإلكتروني، وستتلقى بعدها بريدًا إلكترونيًا يؤكد تسجيلك أو يطلب تأكيدًا لتفعيل حسابك. تحليل البيانات يمكن أن يجمع موقع الويب الكثير من البيانات حول المستخدمين مثل ما الذي يبحثون عنه وماذا يشترون وما يوصون به ومدة بقائهم في كل صفحة. يمكن استخدام البرمجة من طرف الخادم لتحسين الاستجابات بناءً على تحليل هذه البيانات، فمثلًا تعلن كلًا من أمازون وجوجل عن منتجات بناءً على عمليات البحث السابقة وعمليات الشراء. ملاحظة: إذا كنت من مستخدمي فيسبوك، فانتقل إلى الصفحة الرئيسية وانظر إلى المنشورات. لاحظ كيف أن بعض المنشورات ليست مرتبةً عدديًا، إذ تكون في أغلب الأحيان المشاركات التي تحتوي على عدد أكبر من الإعجابات أعلى في القائمة من المشاركات الأحدث. ألقِ نظرةً على نوع الإعلانات المعروضة، إذ يمكن أن تشاهد إعلانات عن الأشياء التي اطّلعتَ عليها على مواقع أخرى. يمكن أن تكون خوارزمية فيسبوك لإظهار المحتوى والإعلانات غامضةً بعض الشيء، ولكنها تعتمد على إعجاباتك وعادات المشاهدة. الخلاصة تعلمت الآن أن الشيفرة البرمجية من طرف الخادم تعمل على خادم ويب وأن دورها الرئيسي هو التحكم في المعلومات المُرسَلة إلى المستخدم، بينما تتعامل الشيفرة البرمجية من طرف العميل مع بنية تلك البيانات وعرضها للمستخدم، كما يجب أن تفهم أن الشيفرة البرمجية من طرف الخادم مفيدةٌ، لأنها تتيح إنشاء مواقع الويب التي تقدم معلومات مُخصَّصة لكل مستخدم بكفاءة ولديها فكرةٌ جيدةٌ عن بعض الأشياء التي يمكن أن تكون قادرًا على تطبيقها عندما تكون مبرمجًا من طرف الخادم. أخيرًا، يجب أن تفهم أنه يمكن كتابة الشيفرة البرمجية من طرف الخادم باستخدام عدد من لغات البرمجة وأنه يجب عليك استخدام إطار عمل ويب لتسهيل تلك العملية. سنساعدك في المقال التالي على اختيار أفضل إطار عمل ويب لموقعك الأول، وسنأخذك في جولة حول التفاعلات الرئيسية بين العميل والخادم بمزيدٍ من التفاصيل. ترجمة -وبتصرُّف- للمقالين Server-side website programming first steps و Introduction to the server side. اقرأ أيضًا المقال السابق: إعداد البيئة للاختبارات الآلية في مشاريع الويب للتوافق مع المتصفحات المدخل الشامل لتعلم تطوير الويب وبرمجة المواقع أساسيات إنشاء موقع ويب باستخدام تعليمات HTML رفع ملفات موقع الويب إلى خادم على الإنترنت
  11. سنتعلم في هذا المقال كيفية تثبيت بيئة الاختبارات الآلية وإجراء اختباراتك باستخدام نظام Selenium/WebDriver ومكتبة اختبار مثل selenium-webdriver في Node، وسنتعرّف على كيفية دمج بيئة اختبارك المحلية مع الأدوات التجارية مثل الأدوات التي ناقشناها في المقال السابق. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML و CSS وجافاسكربت JavaScript، وأن يكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات المختلفة والاختبارات الآلية. الهدف: معرفة كيفية إعداد بيئة اختبار Selenium محليًا وتشغيل الاختبارات باستخدامه، وكيفية دمجه مع أدوات مثل LambdaTest و Sauce Labs و BrowserStack. نظام الاختبار Selenium يُعَدّ نظام Selenium أشهر أداة لاختبار المتصفحات آليًا، وهناك طرق متعددة لاستخدام Selenium، ولكن أفضل طريقة لاستخدامه هي عبر WebDriver التي تُعَد واجهة برمجة تطبيقات API مبنية على نظام Selenium وتجري استدعاءات للمتصفح لجعله آليًا من خلال تنفيذ إجراءات مثل "افتح صفحة الويب" و"انقل هذا العنصر في الصفحة" و"انقر على هذا الرابط" و"تأكد مما إذا كان هذا الرابط يفتح عنوان URL" وغير ذلك، كما يُعَدّ ذلك مثاليًا لإجراء الاختبارات الآلية. تعتمد كيفية تثبيت واجهة WebDriver واستخدامها على البيئة البرمجية التي تريد استخدامها لكتابة الاختبارات وتشغيلها، كما تحتوي معظم البيئات الشائعة على حزمة أو إطار عمل يثبّت واجهة WebDriver والارتباطات المطلوبة للتواصل معها باستخدام هذه اللغة مثل لغات Java و C#‎ و Ruby و Python و JavaScript (Node)‎ وغيرها. تتطلب المتصفحات المختلفة مشغّلات Drivers مختلفة للسماح لواجهة WebDriver بالاتصال والتحكم بها، وراجع المنصات التي يدعمها Selenium للحصول على مزيد من المعلومات حول مكان الحصول على مشغّلات المتصفحات. سنغطّي كتابة وتشغيل اختبارات Selenium باستخدام Node.js التي تُعَدّ سريعةً وسهلة الاستخدام وبيئةً مألوفةً أكثر لمطورِي الواجهة الأمامية. إعداد Selenium في Node أعِدّ أولًا مشروع npm جديد كما ناقشنا في قسم إعداد Node و npm في المقال السابق، وسمِّ هذا المشروع باسم مختلف مثل selenium-test. يجب بعد ذلك تثبيت إطار عمل للسماح بالتعامل مع Selenium ضمن Node، كما سنختار مكتبة selenium-webdriver الرسمية الخاصة بنظام Selenium، فتوثيقها محدثٌ إلى حد ما وتُطبَّق صيانتها جيدًا، وتوجد خيارات أخرى جيدة مثل webdriver.io و nightwatch.js، كما يمكنك تثبيت selenium-webdriver من خلال تشغيل الأمر التالي مع التأكد من أنك ضمن مجلد مشروعك: npm install selenium-webdriver ملاحظة: لا يزال اتباع الخطوات السابقة أمرًا جيدًا حتى إذا ثَبَّتَّ selenium-webdriver ونزّلتَ مشغّلات المتصفح مسبقًا، إذ يجب عليك التأكد من تحديث كل شيء. يجب بعد ذلك تنزيل المشغّلات ذات الصلة لتتمكّن WebDriver من التحكم في المتصفحات التي تريد اختبارها. تُعَد بعض المتصفحات خاصة بنظام التشغيل، لذلك سنلتزم باستخدام فايرفوكس Firefox وكروم Chrome المتوفرَين على جميع أنظمة التشغيل الرئيسية. نزّل الإصدار الأحدث من المشغّلات GeckoDriver (لمتصفح Firefox) و ChromeDriver. فك ضغطها في مكان يسهل الانتقال إليه مثل جذر مجلد مستخدمك الرئيسي. أضِف موقع مشغّل chromedriver و geckodriver إلى المتغير PATH في نظامك، ويجب أن يكون هذا الموقع مسارًا مطلقًا من جذر قرصك الصلب إلى المجلد الذي يحتوي على المشغّلات، فإذا استخدمت جهاز macOS وكان اسم مستخدِمك هو sami ووضعت المشغّلات في جذر المجلد الرئيسي مثلًا، فسيكون المسار ‎/Users/sami. ملاحظة: يجب أن يكون المسار الذي تضيفه إلى المتغير PATH هو المسار إلى المجلد الذي يحتوي على المشغّلات، وليس المسارات المؤدية إلى المشغّلات نفسها، فهذا خطأ شائع. يمكنك ضبط المتغير PATH على نظام macOS ومعظم أنظمة لينكس كما يلي: إذا لم تستخدِم صدفة bash مثل أنظمة macOS التي يكون فيها الإعداد الافتراضي هو صدفة zsh وليس bash، فبدّل إلى صدفة bash كما يلي: exec bash افتح ملف ‎.bash_profile أو ‎.bashrc، فإذا لم تتمكن من رؤية الملفات المخفية، فستحتاج لعرضها. الصق ما يلي في أسفل الملف (عدّل المسار كما هو على جهازك): #Add WebDriver browser drivers to PATH export PATH=$PATH:/Users/sami احفظ هذا الملف وأغلقه، ثم أعِد تشغيل موجّه الأوامر أو الطرفية لإعادة تطبيق إعداد Bash. تحقق من وجود مساراتك الجديدة في المتغير PATH من خلال إدخال ما يلي في طرفيتك: echo $PATH يجب أن تراه مطبوعًا في الطرفية. لنجرب اختبارًا سريعًا للتأكد من أن كل شيء يعمل. أولًا، أنشئ ملفًا جديدًا ضمن مجلد مشروعك بالاسم google_test.js. ثانيًا، ضع فيه المحتويات التالية ثم احفظه: const webdriver = require('selenium-webdriver'), By = webdriver.By, until = webdriver.until; const driver = new webdriver.Builder() .forBrowser('firefox') .build(); driver.get('http://www.google.com'); driver.findElement(By.name('q')).sendKeys('webdriver'); driver.sleep(1000).then(function() { driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); }); driver.findElement(By.name('btnK')).click(); driver.sleep(2000).then(function() { driver.getTitle().then(function(title) { if(title === 'webdriver - Google Search') { console.log('Test passed'); } else { console.log('Test failed'); } driver.quit(); }); }); ثالثًا، تأكد من أنك ضمن مجلد مشروعك في الطرفية ثم أدخِل الأمر التالي: node google_test يجب أن ترى متصفح Firefox يفتح تلقائيًا، ويجب تحميل جوجل تلقائيًا في تبويب المتصفح، وإدخال النص "webdriver" في مربع البحث، وسيُنقَر على زر البحث، ثم ستنتظر بعدها WebDriver لمدة ثانيتين، ثم يجب الوصول إلى عنوان المستند، وإذا كان النص "webdriver - Google Search"، فستُعاد رسالة تعبِّر عن اجتياز الاختبار، ثم ستغلِق WebDriver المتصفحَ Firefox وتتوقف. اختبار متصفحات متعددة في وقت واحد لا يوجد شيء يمنعك من تشغيل الاختبار على متصفحات متعددة في وقت واحد، إذًا لنجرّب ذلك. أولًا، أنشئ ملفًا جديدًا آخر ضمن مجلد مشروعك بالاسم googletestmultiple.js، ولا تتردد في تغيير المراجع إلى بعض المتصفحات الأخرى التي أضفناها أو إزالتها وغير ذلك اعتمادًا على المتصفحات المتاحة للاختبار على نظام تشغيلك، ولكن يجب التأكد من إعداد مشغّلات المتصفحات الصحيحة على نظامك، فإذا أردت معرفة السلسلة النصية المراد استخدامها ضمن التابع ‎.forBrowser()‎ للمتصفحات الأخرى، فراجع صفحة قائمة المتصفحات. ثانيًا، ضع المحتويات التالية في هذا الملف ثم احفظه: const webdriver = require('selenium-webdriver'), By = webdriver.By, until = webdriver.until; let driver_fx = new webdriver.Builder() .forBrowser('firefox') .build(); let driver_chr = new webdriver.Builder() .forBrowser('chrome') .build(); searchTest(driver_fx); searchTest(driver_chr); function searchTest(driver) { driver.get('http://www.google.com'); driver.findElement(By.name('q')).sendKeys('webdriver'); driver.sleep(1000).then(() => { driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); }); driver.findElement(By.name('btnK')).click(); driver.sleep(2000).then(() => { driver.getTitle().then((title) => { if(title === 'webdriver - Google Search') { console.log('Test passed'); } else { console.log('Test failed'); } driver.quit(); }); }); } تأكد من أنك ضمن مجلد مشروعك في الطرفية ثم أدخِل الأمر التالي: node google_test_multiple إذا استخدمت جهاز ماك Mac وقررت اختبار المتصفح سفاري Safari، فيمكن أن تتلقى رسالة خطأ مثل "Could not create a session: You must enable the 'Allow Remote Automation' option in Safari's Develop menu to control Safari via WebDriver"، فإذا حصلت على هذا الخطأ، فاتبع التعليمات المُعطاة وحاول مرةً أخرى. أجرينا هنا الاختبار مثل الاختبار السابق باستثناء أننا غلّفناه هذه المرة ضمن الدالة searchTest()‎، وأنشأنا نسخًا من متصفحات جديدة ثم مررنا كلًا منها إلى الدالة لإجراء الاختبار على المتصفحات الثلاثة. لنلقِ نظرةً الآن على أساسيات صياغة واجهة WebDriver بمزيد من التفاصيل. أساسيات صياغة WebDriver لنلقِ نظرةً على بعض الميزات الرئيسية لصياغة Webdriver، ويمكنك الحصول على مزيد من التفاصيل من خلال الرجوع إلى مرجع واجهة برمجة تطبيقات جافاسكربت selenium-webdriver وتوثيق Selenium الرئيسي الذي يحتوي على أمثلة متعددة مكتوبة بلغات مختلفة للتعلم منها. بدء اختبار جديد يجب تضمين الوحدة selenium-webdriver كما يلي لبدء اختبار جديد: const webdriver = require('selenium-webdriver'); const By = webdriver.By; const until = webdriver.until; يجب بعد ذلك إنشاء نسخة جديدة من المشغّل باستخدام الباني new webdriver.Builder()‎، ولكن يجب ربط التابع forBrowser()‎ بها لتحديد المتصفح الذي تريد اختباره باستخدام هذا الباني، والتابع build()‎ لبنائه. let driver = new webdriver.Builder() .forBrowser('firefox') .build(); لاحظ أنه يمكن ضبط خيارات إعدادات محددة للمتصفحات المراد اختبارها، إذ يمكنك مثلًا ضبط إصدار معيّن من المتصفح ونظام التشغيل للاختبار ضمن التابع forBrowser()‎ كما يلي: let driver = new webdriver.Builder() .forBrowser('firefox', '46', 'MAC') .build(); كما يمكنك ضبط هذه الخيارات باستخدام متغير البيئة كما يلي على سبيل المثال: SELENIUM_BROWSER=firefox:46:MAC لننشئ اختبارًا جديدًا من خلال إنشاء ملف جديد بالاسم quick_test.js ضمن مجلد مشروعك لاختبار Selenium وأضِف الشيفرة البرمجية التالية إليه: const webdriver = require('selenium-webdriver'); const By = webdriver.By; const until = webdriver.until; const driver = new webdriver.Builder() .forBrowser('firefox') .build(); الحصول على الصفحة الذي تريد اختبارها يمكنك تحميل الصفحة التي تريد اختبارها من خلال استخدام التابع get()‎ مع نسخة المشغّل التي أنشأتها مسبقًا كما يلي: driver.get('http://www.google.com'); يمكنك استخدام أيّ عنوان URL للإشارة إلى موردك، بما في ذلك العنوان file://‎ لاختبار مستند محلي كما يلي: driver.get('file:///Users/chrismills/git/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html'); أو كما يلي: driver.get('http://localhost:8888/fake-div-buttons.html'); لكن يُفضَّل استخدام موقع خادم بعيد بحيث تكون الشيفرة البرمجية أمرن، فإذا استخدَمت خادمًا بعيدًا لإجراء اختباراتك، فستُعطَّل شيفرتك إذا حاولت استخدام المسارات المحلية. أضِف السطر التالي في نهاية الملف quick_test.js: driver.get('https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html'); التفاعل مع الصفحة لدينا مستند للاختبار، لذا يجب التفاعل معه بطريقة ما تتضمن تحديد عنصر معيّن أولًا لاختباره، ويمكنك تحديد عناصر واجهة المستخدِم بعدة طرق في WebDriver بما في ذلك استخدام المعرّف ID والصنف Class واسم العنصر وغير ذلك، كما يمكن تحقيق تحديد العنصر الفعلي باستخدام التابع findElement()‎ الذي يقبل تابعَ تحديد بوصفه معاملًا له، إذ يمكن مثلًا تحديد عنصر باستخدام المعرّف كما يلي: const element = driver.findElement(By.id('myElementId')); إحدى أكثر الطرق المفيدة للعثور على عنصر باستخدام لغة CSS هي استخدام التابع By.css الذي يسمح بتحديد عنصر باستخدام محدَّد CSS. أدخِل ما يلي في الجزء السفلي من شيفرة الملف quick_test.js: const button = driver.findElement(By.css('button:nth-of-type(1)')); اختبار العنصر إذا أردت إدخال نص ضمن الزر، فيمكننا استخدام ما يلي ضمن الملف quick_test.js: button.getText().then((text) => { console.log(`Button text is '${text}'`); }); تأكد من أنك ضمن مجلد المشروع وشغّل الاختبار كما يلي: node quick_test.js يجب أن ترى تسمية نص الزر في الطرفية. احذف إدخال الشيفرة السابقة ثم أضِف السطر التالي عوضًا عنه: button.click(); شغّل اختبارك مرةً أخرى، إذ سيُنقَر على الزر وستظهر نافذة التنبيه alert()‎ المنبثقة، وسنعلَم بذلك أنّ الزر يعمل على الأقل. كما يمكنك التفاعل مع النافذة المنبثقة من خلال إضافة ما يلي إلى أسفل الشيفرة البرمجية واختبارها مرةً أخرى: const alert = driver.switchTo().alert(); alert.getText().then((text) => { console.log(`Alert text is '${text}'`); }); alert.accept(); لنحاول بعد ذلك إدخال نص في أحد عناصر النموذج، لذا أضِف الشيفرة التالية وشغّل اختبارك مرةً أخرى: const input = driver.findElement(By.id('name')); input.sendKeys('Filling in my form'); يمكنك إرسال ضغطات على المفاتيح التي لا يمكن تمثيلها بمحارف عادية باستخدام خاصيات الكائن webdriver.Key، وقد استخدمنا سابقًا البناء التالي مثلًا للخروج من حقل إدخال النموذج قبل الإرسال: driver.sleep(1000).then(() => { driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); }); انتظار اكتمال شيء ما تريد في بعض الأحيان جعل واجهة WebDriver تنتظر حتى يكتمل شيء ما قبل المتابعة، فإذا حمّلت صفحةً جديدةً مثلًا، فيجب عليك الانتظار حتى ينتهي تحميل نموذج DOM الخاص بالصفحة قبل محاولة التفاعل مع أيّ من عناصره، وإلّا فيُحتمَل أن يفشل الاختبار. ضمّن الكتلة التالية في اختبار الملف google_test.js مثلًا: driver.sleep(2000).then(() => { driver.getTitle().then((title) => { if(title === 'webdriver - Google Search') { console.log('Test passed'); } else { console.log('Test failed'); } }); }); يقبل التابع sleep()‎ قيمةً تحدد وقت الانتظار بالميلي ثانية ويعيد وعدًا يُؤكَّد في نهاية ذلك الوقت وتُنفَّذ الشيفرة البرمجية الموجودة ضمن التابع then()‎، إذ نحصل في هذه الحالة على عنوان الصفحة الحالية باستخدام التابع getTitle()‎ ثم نعيد رسالة نجاح أو فشل اعتمادًا على قيمة هذا العنوان. كما يمكننا إضافة التابع sleep()‎ إلى اختبار الملف quick_test.js، لذا غلّف آخر سطر من الشيفرة البرمجية ضمن كتلة كما يلي: driver.sleep(2000).then(() => { input.sendKeys('Filling in my form'); input.getAttribute("value").then((value) => { if(value !== '') { console.log('Form input editable'); } }); }); ستنتظر واجهة WebDriver الآن لمدة ثانيتين قبل ملء حقل النموذج ثم نختبر مما فيما إذا كانت قيمته مملوءةً -أي ليست فارغةً- باستخدام التابع getAttribute()‎ لاسترداد قيمة السمة value وطباعة رسالة في الطرفية إذا لم تكن فارغةً. ملاحظة: هناك تابع بالاسم wait()‎ يختبر بطريقة تكرارية شرطًا لفترة زمنية معينة ثم ينفّذ الشيفرة البرمجية، إذ يستخدِم هذا التابع المكتبة until التي تحدِّد الشروط الشائعة للاستخدام مع التابع wait()‎. إغلاق المشغلات بعد الانتهاء من استخدامها يجب عليك إغلاق أيّ نسخ من المشغّلات التي فتحتها بعد الانتهاء من إجراء الاختبار للتأكد من عدم وجود الكثير من نسخ المتصفحات المفتوحة على جهازك من خلال استخدام التابع quit()‎. استدعِ هذا التابع على نسخة مشغّلك عند الانتهاء منه من خلال إضافة السطر التالي إلى الجزء السفلي من اختبار الملف quick_test.js: driver.quit(); يجب أن ترى الآن عند تشغيله تنفيذَ الاختبار وإغلاق نسخة المتصفح مرةً أخرى بعد اكتمال الاختبار، ويُعَدّ ذلك مفيدًا لتقليل الفوضى في حاسوبك بسبب وجود الكثير من نسخ المتصفح خاصةً إذا كان لديك الكثير منها مما يتسبب في إبطاء الحاسوب. أفضل ممارسات في عملية الاختبار يجب أن تتأكد من أن اختباراتك تطبّق ما يلي: أولًا، تستخدِم استراتيجيات جيدة لتحديد المواقع: تأكّد عند التفاعل مع المستند من استخدام محدّدات المواقع وكائنات الصفحة التي لا يُحتمَل أن تتغير، فإذا كان لديك عنصر قابل للاختبار تريد إجراء اختبار عليه، فتأكد من أنه يملك معرّفًا مستقرًا أو موضعًا على الصفحة يمكن تحديده باستخدام محدّد CSS الذي لن يتغير مع تكرار الموقع التالي، إذ يجب أن تجعل اختباراتك مستقرةً قدر الإمكان، أي أنها لن تنهار عندما يتغير شيء ما. ثانيًا، تكتب اختبارات ذرية: يجب أن يختبر كل اختبار شيئًا واحدًا فقط، مما يسهل تعقّب ملف الاختبار الذي يختبر معيارًا معينًا، ويُعَدّ اختبار الملف google_test.js الذي ناقشناه سابقًا مثالًا جيدًا لأنه يختبر شيئًا واحدًا فقط، إذ يختبر ما إذا كان عنوان صفحة نتائج البحث مضبوطًا بصورة صحيحة، كما يمكننا إعطاء هذا الملف اسمًا أفضل حتى يسهل معرفة ما يفعله إذا أضفنا اختبارات جوجل أخرى مثل الاسم results_page_title_set_correctly.js. ثالثًا، تكتب اختبارات مستقلة: يجب أن يعمل كل اختبار بمفرده دون الاعتماد على اختبارات أخرى للعمل. كما يجب أن نذكر نتائج أو تقرير الاختبار، إذ أصدرنا تقريرًا بالنتائج في الأمثلة السابقة باستخدام تعليمات console.log()‎ البسيطة، ولكن يحدث كل ذلك في شيفرة جافاسكربت، لذا يمكنك استخدام أيّ نظام اختبار تريده لتشغيل الاختبار وإعداد التقارير سواءً كان Mocha أو Chai أو أيّ أداة أخرى. أولًا، أنشئ مثلًا نسخةً محليةً من المثال mocha_test.js ضمن مجلد مشروعك، ثم ضع هذا الملف ضمن مجلد فرعي بالاسم test، إذ يستخدِم هذا المثال سلسلةً طويلةً من الوعود لتنفيذ جميع الخطوات المطلوبة في اختبارنا، حيث يجب تأكيد التوابع المستندة إلى الوعود التي تستخدمها WebDriver حتى تعمل بطريقة صحيحة. ثانيًا، ثبّت أداة الاختبار Mocha عبر تشغيل الأمر التالي ضمن مجلد مشروعك: npm install --save-dev mocha يمكنك الآن تشغيل الاختبار وأيّ اختبارات أخرى تضعها ضمن مجلد test باستخدام الأمر التالي: npx mocha --no-timeouts يجب عليك تضمين الراية ‎--no-timeouts للتأكد من عدم فشل اختباراتك بسبب مهلة Mocha التي هي 3 ثوان. إجراء الاختبارات عن بعد اتضح أنّ إجراء الاختبارات على الخوادم البعيدة ليس أصعب من تشغيلها محليًا، إذ يجب فقط إنشاء نسخة من مشغّلك مع تحديد عدد من الميزات الإضافية بما في ذلك إمكانات المتصفح الذي تريد تطبيق الاختبار عليه وعنوان الخادم وبيانات ثبوتيات المستخدِم التي تحتاجها -إذا وجِدت- للوصول إليه. LambdaTest يُعَدّ إجراء اختبارات Selenium عن بُعد على نظام LambdaTest أمرًا بسيطًا، ويجب أن تتّبع الشيفرة البرمجية التي تحتاجها النمطَ الآتي. إذًا لنكتب مثالًا يوضّح إجراء اختبارات Selenium عن بُعد على نظام LambdaTest: أولًا، أنشئ ملفًا جديدًا بالاسم lambdatest-google_test.js ضمن مجلد المشروع. ثانيًا، ضع فيه المحتويات التالية: const webdriver = require('selenium-webdriver'); const By = webdriver.By; const until = webdriver.until; // username: يمكن العثور على اسم المستخدِم في لوحة تحكم الأتمتة const USERNAME = '{username}'; // AccessKey: يمكن توليد مفتاح الوصول من لوحة تحكم الأتمتة أو قسم الملف الشخصي const KEY = '{accessKey}'; // gridUrl: يمكن العثور عليه في لوحة تحكم الأتمتة const GRID_HOST = 'hub.lambdatest.com/wd/hub'; function searchTextOnGoogle() { // إعداد إمكانات الإدخال const capabilities = { platform: 'windows 10', browserName: 'chrome', version: '67.0', resolution: '1280x800', network: true, visual: true, console: true, video: true, name: 'Test 1', // اسم الاختبار build: 'NodeJS build' // اسم البناء }; // URL: https://{username}:{accessToken}@hub.lambdatest.com/wd/hub const gridUrl = `https://${USERNAME}:${KEY}@${GRID_HOST}`; // إعداد وبناء كائن مشغّل‫ selenium const driver = new webdriver.Builder() .usingServer(gridUrl) .withCapabilities(capabilities) .build(); // ‫انتقل إلى عنوان url، وابحث عن نص واحصل على عنوان الصفحة driver.get('https://www.google.com/ncr').then(function() { driver.findElement(webdriver.By.name('q')).sendKeys('LambdaTest\n').then(function() { driver.getTitle().then((title) => { setTimeout(() => { console.log(title); driver.quit(); }, 5000); }); }); }); } searchTextOnGoogle(); ثالثًا، انتقل إلى لوحة تحكم أتمتة LambdaTest لجلب اسم مستخدِم LambdaTest ومفتاح الوصول من خلال النقر على رمز المفتاح في أعلى اليمين، حيث سترى اسم المستخدِم Username ومفاتيح الوصول Access Keys، واستبدل العناصر البديلة {username} و {accessKey} في شيفرتك باسم المستخدِم وقيم مفتاح الوصول الفعلية وتأكّد من الحفاظ عليها آمنةً. رابعًا، شغّل الأمر التالي في طرفيتك لتنفيذ اختبارك: node lambdatest_google_test سيُرسَل الاختبار إلى LambdaTest وسيظهر خرج الاختبار على طرفية LambdaTest، فإذا أردتَ استخراج هذه النتائج لإصدار التقارير من منصة LambdaTest، فيمكنك ذلك باستخدام واجهة برمجة تطبيقات LambdaTest. إذا ذهبتَ الآن إلى لوحة تحكم أتمتة LambdaTest، فسترى اختبارك موجودًا هناك حيث ستتمكن من مشاهدة مقاطع الفيديو ولقطات الشاشة وغيرها من البيانات. يمكنك استرداد سجلات الشبكة والأوامر والاستثناءات و Selenium لكل اختبار في عملية بناء اختبارك، وستجد تسجيل فيديو لتنفيذ سكربت Selenium. ملاحظة: سيوفر زر "المساعدة HELP" الموجود في لوحة تحكم أتمتة LambdaTest كميةً وافرةً من المعلومات لمساعدتك في بدء تشغيل اختبار LambdaTest الآلي. ملاحظة: إذا لم ترغب في كتابة كائنات الإمكانات لاختباراتك يدويًا، فيمكنك إنشاؤها باستخدام منشئ إمكانات Selenium. ملء تفاصيل الاختبار على LambdaTest برمجيا يسهّل تمييز حالة الاختبارات الآلية المتعددة على أنها نجحت أو فشلت الأمورَ كثيرًا عند تنفيذها. أولًا، استخدِم الأمر التالي لتمييز الحالة على أنها ناجحة في LambdaTest: driver.executeScript("lambda-status=passed"); ثانيًا، استخدِم الأمر التالي لتمييز الحالة على أنها فاشلة في LambdaTest: driver.executeScript("lambda-status=failed"); BrowserStack يُعَدّ إجراء اختبارات Selenium للتشغيل عن بُعد على BrowserStack أمرًا سهلًا، إذ يجب أن تتبع الشيفرة البرمجية التي تحتاجها النمطَ الآتي. لنكتب إذًا مثالًا يوضّح إجراء اختبارات Selenium للتشغيل عن بُعد على BrowserStack: أولًا، أنشئ ملفًا جديدًا بالاسم bstack_google_test.js ضمن مجلد المشروع. ثانيًا، ضع فيه المحتويات التالية: const webdriver = require('selenium-webdriver'); const By = webdriver.By; const until = webdriver.until; // إمكانات الإدخال let capabilities = { 'browserName' : 'Firefox', 'browser_version' : '56.0 beta', 'os' : 'OS X', 'os_version' : 'Sierra', 'resolution' : '1280x1024', 'browserstack.user' : 'YOUR-USER-NAME', 'browserstack.key' : 'YOUR-ACCESS-KEY', 'browserstack.debug' : 'true', 'build' : 'First build' }; const driver = new webdriver.Builder() .usingServer('http://hub-cloud.browserstack.com/wd/hub') .withCapabilities(capabilities) .build(); driver.get('http://www.google.com'); driver.findElement(By.name('q')).sendKeys('webdriver'); driver.sleep(1000).then(() => { driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); }); driver.findElement(By.name('btnK')).click(); driver.sleep(2000).then(() => { driver.getTitle().then((title) => { if(title === 'webdriver - Google Search') { console.log('Test passed'); } else { console.log('Test failed'); } }); }); driver.quit(); ثالثًا، انتقل إلى لوحة تحكم أتمتة BrowserStack للحصول اسم مستخدِم ومفتاح الوصول، واستبدل العناصر البديلة YOUR-USER-NAME و YOUR-ACCESS-KEY في شيفرتك باسم المستخدِم وقيم مفتاح الوصول الفعلية وتأكّد من الحفاظ عليها آمنة. رابعًا، شغّل اختبارك باستخدام الأمر التالي: node bstack_google_test سيُرسَل الاختبار إلى BrowserStack وستُعاد نتيجة الاختبار إلى طرفيتك، ويدل ذلك على أهمية تضمين آلية لإعطاء تقارير بالنتائج. أخيرًا، إذا عدت الآن إلى صفحة لوحة تحكم أتمتة BrowserStack، فسترى اختبارك موجودًا هناك: إذا نقرتَ على رابط اختبارك، فستنتقل إلى شاشة جديدة حيث ستتمكن من مشاهدة تسجيل فيديو للاختبار وسجلات تفصيلية متعددة للمعلومات المتعلقة به. ملاحظة: يحتوي خيار قائمة الموارد Resources في لوحة تحكم أتمتة Browserstack على مجموعة كبيرة من المعلومات المفيدة حول استخدامه لإجراء الاختبارات الآلية. ملاحظة: إذا لم ترغب في كتابة كائنات الإمكانات لاختباراتك يدويًا، فيمكنك إنشاؤها باستخدام منشئ إمكانات Selenium. ملء تفاصيل اختبار BrowserStack برمجيا يمكنك استخدام واجهة BrowserStack REST API وبعض الإمكانات الأخرى للتعليق على اختبارك بمزيد من التفاصيل مثل إذا نجح الاختبار وسبب نجاحه وما هو المشروع الذي يكون الاختبار جزءًا منه وغير ذلك، إذ لا يعرف BrowserStack هذه التفاصيل افتراضيًا. لنعدّل bstack_google_test.js لإظهار كيفية عمل هذه الميزات: يجب أولًا استيراد وحدة طلب Node لنتمكّن من استخدامها لإرسال الطلبات إلى واجهة REST API، لذا أضِف السطر التالي في بداية شيفرتك البرمجية: const request = require("request"); سنعدّل الآن الكائن capabilities لتضمين اسم المشروع من خلال إضافة السطر التالي قبل قوس الإغلاق المعقوص، ولاتنسى إضافة فاصلة في نهاية السطر السابق، كما يمكنك تغيير أسماء البناء والمشروع لتنظيم الاختبارات في نوافذ مختلفة من لوحة تحكم أتمتة BrowserStack كما يلي: 'project' : 'Google test 2' يجب بعد ذلك الوصول إلى المعرّف sessionId للجلسة الحالية لنعرف مكان إرسال الطلب، حيث يُضمَّن المعرّف في عنوان URL الخاص بالطلب كما سنرى لاحقًا. ضمّن الأسطر التالية بعد الكتلة التي تنشئ الكائن driver (أي let driver ...‎? let sessionId; driver.session_.then((sessionData) =>{ sessionId = sessionData.id_; }); أخيرًا، عدّل الكتلة driver.sleep(2000) ...‎ بالقرب من أسفل شيفرتك لإضافة استدعاءات واجهة REST API، ولا تنسى استبدال العناصر البديلة YOUR-USER-NAME و YOUR-ACCESS-KEY في شيفرتك البرمجية باسم المستخدِم وقيم مفتاح الوصول الفعلية: driver.sleep(2000).then(() => { driver.getTitle().then((title) => { if(title === 'webdriver - Google Search') { console.log('Test passed'); request({ uri: `https://YOUR-USER-NAME:YOUR-ACCESS-KEY@www.browserstack.com/automate/sessions/${sessionId}.json`, method:"PUT", form:{"status":"passed","reason":"Google results showed correct title"}} ); } else { console.log('Test failed'); request({ uri: `https://YOUR-USER-NAME:YOUR-ACCESS-KEY@www.browserstack.com/automate/sessions/${sessionId}.json`, method:"PUT", form:{"status":"failed","reason":"Google results showed wrong title"}} ); } }); }); سنرسل استدعاء واجهة API إلى BrowserStack بعد اكتمال الاختبار لتحديثه بحالة النجاح أو الفشل وبسبب النتيجة. إذا عُدتَ الآن إلى صفحة لوحة تحكم أتمتة BrowserStack، فيجب أن ترى جلسة اختبارك متاحةً كما كانت سابقًا ولكن مع البيانات المحدَّثة المرفقة بها: Sauce Labs يُعَدّ إجراء اختبارات Selenium للتشغيل عن بُعد في Sauce Labs أمرًا بسيطًا، ويشبه إلى حد كبير اختبار BrowserStack مع بعض الاختلافات الصياغية، كما يجب أن تتّبع الشيفرة البرمجية التي تحتاجها النمط الآتي. لنكتب إذًا مثالًا يوضّح إجراء اختبارات Selenium للتشغيل عن بُعد على Sauce Labs: أولًا، أنشئ ملفًا جديدًا بالاسم sauce_google_test.js ضمن مجلد مشروعك. ثانيًا، ضع فيه المحتويات التالية: const webdriver = require('selenium-webdriver'); const By = webdriver.By; const until = webdriver.until; const username = "YOUR-USER-NAME"; const accessKey = "YOUR-ACCESS-KEY"; const driver = new webdriver.Builder() .withCapabilities({ 'browserName': 'chrome', 'platform': 'Windows XP', 'version': '43.0', 'username': username, 'accessKey': accessKey }) .usingServer(`https://${username}:${accessKey}@ondemand.saucelabs.com:443/wd/hub`) .build(); driver.get('http://www.google.com'); driver.findElement(By.name('q')).sendKeys('webdriver'); driver.sleep(1000).then(() => { driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); }); driver.findElement(By.name('btnK')).click(); driver.sleep(2000).then(() => { driver.getTitle().then((title) => { if(title === 'webdriver - Google Search') { console.log('Test passed'); } else { console.log('Test failed'); } }); }); driver.quit(); ثالثًا، انتقل إلى إعدادات مستخدِم Sauce Labs للحصول اسم المستخدِم ومفتاح الوصول، واستبدل العناصر البديلة YOUR-USER-NAME و YOUR-ACCESS-KEY في شيفرتك باسم المستخدِم وقيم مفتاح الوصول الفعلية وتأكّد من الحفاظ عليها آمنة. رابعًا، شغّل اختبارك باستخدام الأمر التالي: node sauce_google_test سيُرسَل الاختبار إلى Sauce Labs وستُعاد نتيجة الاختبار إلى طرفيتك، إذ يدل ذلك على أهمية تضمين آلية لإعطاء تقارير بالنتائج. أخيرًا، إذا انتقلت الآن إلى صفحة لوحة تحكم أتمتة Sauce Labs، فسترى اختبارك موجودًا هناك حيث ستتمكّن من مشاهدة مقاطع الفيديو ولقطات الشاشة وغيرها من البيانات. ملاحظة: تُعَدّ أداة إعداد منصة Sauce Labs أداةً مفيدةً لإنشاء كائنات الإمكانات لتقديمها إلى نسخ المشغّلات بناءً على المتصفح/نظام التشغيل الذي تريد الاختبار عليه. ملء تفاصيل اختبار Sauce Labs برمجيا يمكنك استخدام Sauce Labs API للتعليق على اختبارك بمزيد من التفاصيل مثل نجاح الاختبار واسم الاختبار وغير ذلك، إذ لا يعرف Sauce Labs هذه التفاصيل افتراضيًا. يمكن تطبيق ذلك كما يلي: أولًا، ثبّت المغلّف Node Sauce Labs باستخدام الأمر التالي (إذا لم تفعل ذلك مسبقًا في هذا المشروع): npm install saucelabs --save-dev ثانيًا، اطلب saucelabs في أعلى الملف sauce_google_test.js بعد التصريح عن المتغيرات: const SauceLabs = require('saucelabs'); ثالثًا، أنشئ نسخةً جديدةً من SauceLabs من خلال إضافة ما يلي: let saucelabs = new SauceLabs({ username : "YOUR-USER-NAME", password : "YOUR-ACCESS-KEY" }); لا تنسى استبدال العناصر البديلة YOUR-USER-NAME و YOUR-ACCESS-KEY في شيفرتك باسم المستخدِم وقيم مفتاح الوصول الفعلية، ولاحظ أنّ حزمة saucelabs npm تستخدِم كلمة المرور password وليس مفتاح الوصول accessKey، وبما أنك تستخدِم هاتين القيمتين، فيمكن أن ترغب في إنشاء متغيرين مساعدَين لتخزينهما فيهما. رابعًا، أضِف الكتلة التالية بعد كتلة تعريف المتغير driver بعد سطر build()‎ مباشرةً، حيث تحصل هذه الكتلة على معرّف جلسة sessionID المشغّل الصحيح الذي نحتاجه لكتابة البيانات في الوظيفة، حيث يمكنك رؤية هذا المعرّف قيد التشغيل في كتلة الشيفرة البرمجية التالية: driver.getSession().then((sessionid) => { driver.sessionID = sessionid.id_; }); أخيرًا، استبدل الكتلة driver.sleep(2000) ...‎ الموجودة في أسفل الشيفرة البرمجية بما يلي: driver.sleep(2000).then(() => {.then(function() { driver.getTitle().then((title) => { let testPassed = false; if(title === 'webdriver - Google Search') { console.log('Test passed'); let testPassed = true; } else { console.log('Test failed'); } saucelabs.updateJob(driver.sessionID, { name: 'Google search results page title test', passed: testPassed }); }); }); ضبطنا هنا المتغير testPassed على القيمة true أو false اعتمادًا على نجاح الاختبار أو فشله، ثم استخدمنا التابع saucelabs.updateJob()‎ لتحديث التفاصيل. إذا عُدتَ الآن إلى صفحة لوحة تحكم أتمتة Sauce Labs، فسترى أن وظيفتك الجديدة تحتوي الآن على البيانات المُحدَّثة المرفقة بها: خادمك البعيد إذا لم ترغب في استخدام خدمة مثل Sauce Labs أو BrowserStack، فيمكنك دائمًا إعداد خادمك للاختبار عن بُعد كما يلي: أولًا، يتطلب خادم Selenium البعيد تشغيل شيفرة جافا، لذا يجب عليك تنزيل أحدث إصدار من JDK لمنصتك من صفحة تنزيلات Java SE، ثم ثبّته بعد تنزيله. ثانيًا، نزّل خادم Selenium المستقل الأحدث الذي يعمل بوصفه وكيلًا proxy بين السكربت ومشغّلات المتصفح، ثم اختر أحدث رقم إصدار مستقر -أي ليس إصدارًا تجريبيًا beta- واختر من القائمة ملفًا يبدأ بعبارة "selenium-server-standalone"، ثم ضعه في مكان يمكنك الوصول إليه بسهولة بعد تنزيله مثل مجلدك الرئيسي، كما يجب إضافة الموقع إلى المتغير PATH إذا لم تفعل ذلك بعد. ثالثًا، شغّل الخادم المستقل من خلال إدخال ما يلي في طرفية حاسوب خادمك: java -jar selenium-server-standalone-3.0.0.jar عدّل اسم الملف ‎.jar بحيث يتطابق تمامًا مع ملفك. رابعًا، سيعمل الخادم على http://localhost:4444/wd/hub، لذا انتقل إلى هناك الآن لترى ما ستحصل عليه. لدينا الآن الخادم قيد التشغيل، فلننشئ اختبارًا تجريبيًا يعمل على خادم Selenium البعيد: أنشئ نسخةً من الملف google_test.js بالاسم google_test_remote.js وضعه في مجلد مشروعك. عدّل كتلة الشيفرة البرمجية الثانية التي تبدأ بالسطر let driver = ...‎ كما يلي: let driver = new webdriver.Builder() .forBrowser('firefox') .usingServer('http://localhost:4444/wd/hub') .build(); شغّل اختبارك وسترى أنه يعمل بالشكل المتوقع، ولكنه سيعمل هذه المرة على الخادم المستقل: node google_test_remote.js أجرينا الاختبار محليًا، ولكن يمكنك إعداده على أيّ خادم تقريبًا مع مشغّلات المتصفح ذات الصلة، ثم اربطه مع سكربتاتك باستخدام عنوان URL الذي تختاره للوصول إليه. دمج Selenium مع أدوات CI يمكن دمج Selenium والأدوات المتعلقة به مثل LambdaTest و Sauce Labs مع أدوات التكامل المستمر Continuous Integration أو CI اختصارًا، إذ يُعَدّ ذلك مفيدًا لأنه يعني أنه يمكنك إجراء اختباراتك عبر أداة CI وإجراء تغييرات جديدة في مستودع شيفرتك البرمجية فقط إذا نجحت الاختبارات. نقترح عليك البدء باستخدام أداة Travis CI التي تُعَدّ أسهل أداة CI للبدء بها ولديها تكامل جيد مع أدوات الويب مثل GitHub و Node. ملاحظة: إذا أردتَ إجراء اختبار مستمر باستخدام اختبار آلي بدون شيفرة برمجية، فيمكنك استخدام أداة Endtest أو TestingBot. يجب أن يمنحك هذا المقال نظرةً كافيةً لكتابة وتشغيل الاختبارات الآلية لتبدأ في كتابة اختباراتك الآلية الخاصة. ترجمة -وبتصرُّف- للمقال Setting up your own test automation environment. اقرأ أيضًا المقال السابق: مدخل إلى اختبارات مشاريع الويب الآلية للتوافق مع المتصفحات مدخل إلى اختبار مشاريع الويب للتوافق مع المتصفحات استراتيجيات اختبارات مشاريع الويب للتوافق مع المتصفحات معالجة المشاكل الشائعة للتوافق مع المتصفحات في شيفرة جافاسكربت
  12. يمكن أن يصبح إجراء الاختبارات يدويًا على العديد من المتصفحات والأجهزة عدة مرات في اليوم أمرًا مملًا ويمكن أن يستغرق وقتًا طويلًا، لذا يجب أن تكون على دراية بأدوات الاختبار الآلية Automation، وسنلقي في هذا المقال نظرةً على الأدوات المتاحة وكيفية استخدام مشغّلات المهام وكيفية استخدام أساسيات تطبيقات اختبار المتصفح التجارية الآلية مثل LambdaTest و Sauce Labs و BrowserStack و TestingBot. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML و CSS وجافاسكربت JavaScript، وأن يكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات. الهدف: فهم ما يتطلبه الاختبار الآلي، وكيفية الاستفادة من بعض المنتجات التجارية لتسهيل الأمور. تسهيل الأمور باستخدام الاختبارات الآلية سنتطرق في هذا المقال إلى الكثير من الطرق التفصيلية المختلفة التي يمكنك من خلالها اختبار مواقع وتطبيقات الويب، وشرح نوع النطاق الذي يجب أن تتضمنه جهود الاختبار للتوافق مع المتصفحات فيما يتعلق بالمتصفحات التي يجب اختبارها وسهولة الوصول وأمور أخرى. يمكن أن يكون اختبار جميع الأشياء التي اطلعنا عليها في المقالات السابقة يدويًا أمرًا صعبًا للغاية، ولكن لحسن الحظ هناك أدوات تساعدنا على التخلص من ذلك تلقائيًا، فهناك طريقتان رئيسيتان يمكننا من خلالهما أتمتة هذه الاختبارات وهما: استخدِم مشغّلات المهام Task Runner مثل Grunt أو Gulp أو سكربتات npm لتشغيل الاختبارات وتنظيف الشيفرة البرمجية أثناء عملية البناء، فهذه طريقة رائعة لإجراء مهام مثل تنقيح صياغة Linting الشيفرة البرمجية وتصغيرها وإضافة بادئات CSS أو تحويل Transpiling ميزات جافاسكربت المُنشَاة للوصول إلى أقصى حد من التوافق مع المتصفحات وما إلى ذلك. استخدِم نظام أتمتة للمتصفح مثل Selenium لإجراء اختبارات محددة على المتصفحات المُثبَّتة وإعادة النتائج لإعطائك تنبيهات بأخطاء المتصفحات عند ظهورها. تعتمد تطبيقات الاختبار التجارية للتوافق مع المتصفحات مثل LambdaTest و Sauce Labs و BrowserStack و TestingBot على نظام Selenium، ولكنها تتيح لك الوصول إلى إعدادها عن بُعد باستخدام واجهة بسيطة، مما يوفر لك متاعب إعداد نظام الاختبار. سنتطّرق إلى كيفية إعداد نظام الاختبار القائم على نظام Selenium في المقال التالي، ولكن سنوضح في هذا المقال كيفية إعداد مشغّل المهام واستخدام الوظائف الأساسية للأنظمة التجارية المذكورة سابقًا. ملاحظة: ليست الفئتان السابقتان متعارضتَين، إذ يمكن إعداد مشغل المهام للوصول إلى خدمة مثل Sauce Labs أو LambdaTest عبر واجهة برمجة التطبيقات وإجراء اختبارات التوافق مع المتصفحات وإعادة النتائج. استخدام مشغل المهام لجعل أدوات الاختبار آلية يمكنك تسريع المهام الشائعة بصورة كبيرة مثل تنقيح صياغة الشيفرة البرمجية وتصغيرها باستخدام مشغّل المهام لتشغيل كل ما تحتاجه تشغيلًا تلقائيًا في مرحلة معينة من عملية البناء، إذ يمكن أن يحدث ذلك مثلًا في كل مرة تحفظ فيها ملفًا أو في أيّ وقت آخر، كما سننظر في كيفية جعل تشغيل المهام آلية باستخدام Node و Gulp، وهو خيار سهل الاستخدام للمبتدئين. إعداد Node و npm تعتمد معظم الأدوات هذه الأيام على Node.js، لذا يجب تثبيته من موقع Node.js كما يلي: نزّل برنامج التثبيت على نظامك، فإذا كان لديك Node و npm مثبتين مسبقًا، فانتقل إلى الخطوة رقم 4. ثبّته مثل أيّ برنامج آخر، ولاحظ أنّ Node يأتي مع مدير الحزم npm، مما يسمح لك بسهولة بتثبيت الحزم ومشاركة حزمك مع الآخرين وتشغيل السكربتات المفيدة في مشاريعك. اختبر تثبيت Node بعد اكتمال التثبيت من خلال كتابة الأوامر التالية في الطرفية التي تعيد الإصدارات المُثبَّتة من Node و npm: node -v npm -v إذا ثبّتَ Node و npm مسبقًا، فيجب تحديثهما إلى الإصدارات الأحدث، إذ يمكن تحديث Node من خلال تنزيل حزمة برنامج تثبيت محدَّثة وتثبيتها من الموقع الرسمي، ويمكن تحديث npm من خلال استخدام الأمر التالي في طرفيتك: npm install npm@latest -g يمكنك بدء استخدام الحزم المستندة إلى Node/npm في مشروعاتك من خلال إعداد مجلدات مشروعك بوصفها مشاريع npm، ويُعَدّ ذلك أمرًا سهلًا، فلننشئ أولًا مجلد اختبار مثلًا ولنعدّل ما نريده دون خوف. أنشئ أولًا مجلدًا جديدًا في مكان مناسب باستخدام واجهة مستخدِم مدير الملفات أو على سطر الأوامر من خلال الانتقال إلى الموقع الذي تريده وتشغيل الأمر التالي: mkdir node-test يمكنك جعل هذا المجلد مشروع npm من خلال الانتقال إلى مجلد الاختبار وتهيئته كما يلي: cd node-test npm init سيطرح عليك الأمر الثاني العديد من الأسئلة لمعرفة المعلومات المطلوبة لإعداد المشروع، ويمكنك تحديد الإعدادات الافتراضية فقط حاليًا، كما ستُسأَل بعد طرح جميع الأسئلة عمّا إذا كانت المعلومات التي أدخلتها صحيحةً، لذا اكتب yes واضغط على مفتاح Enter أو Return وسينشئ npm الملف package.json في مجلدك. يُعَدّ هذا الملف ملف إعداد للمشروع ويمكنك تخصيصه لاحقًا، ولكن سيبدو كما يلي حاليًا: { "name": "node-test", "version": "1.0.0", "description": "Test for npm projects", "main": "index.js", "scripts": { "test": "test" }, "author": "Chris Mills", "license": "MIT" } إعداد أداة Gulp للاختبارات الآلية لنلقِ نظرةً على إعداد أداة Gulp واستخدامها لجعل بعض أدوات الاختبار آليةً. أنشئ أولًا مشروع اختبار npm كما تحدّثنا سابقًا، إذ ستحتاج بعد ذلك لبعض النماذج لمحتوى HTML و CSS وجافاسكربت لاختبار نظامك، لذلك أنشئ نسخةً من الملفات index.html و main.js و style.css في مجلد فرعي باسم src ضمن مجلد مشروعك، كما يمكنك تجربة محتوى الاختبار الذي تريده، ولكن ضع في الحسبان أنّ هذه الأدوات لن تعمل على ملفات JS و CSS الداخلية، فأنت بحاجة إلى ملفات خارجية. ثبّت الأداة Gulp تثبيتًا عامًا بحيث تكون متاحةً في جميع المشاريع باستخدام الأمر التالي: npm install --global gulp-cli شغّل بعد ذلك الأمر التالي ضمن جذر مجلد مشروع npm لإعداد أداة Gulp بوصفها اعتماديةً Dependency لمشروعك: npm install --save-dev gulp أنشئ الآن ملفًا جديدًا ضمن مجلد مشروعك بالاسم gulpfile.js، وهو الملف الذي سيشغّل جميع مهامنا، وضَع ما يلي ضمن هذا الملف: const gulp = require('gulp'); exports.default = function(cb) { console.log('Gulp running'); cb(); }; يتطلب ذلك وحدة gulp التي ثبّتناها مسبقًا، ثم يصدّر مهمةً افتراضيةً لا تفعل شيئًا سوى طباعة رسالة إلى الطرفية، وهذا مفيد لإعلامنا بأنّ أداة Gulp تعمل. تُصدَّر كل مهمة من مهام gulp بالتنسيق الأساسي نفسه exports.taskName = taskFunction، وتأخذ كل دالة معامِلًا واحدًا هو دالة رد نداء callback لتشغيلها عند اكتمال المهمة. يمكنك تشغيل مهمة gulp الافتراضية باستخدام الأمر التالي: gulp إضافة بعض المهام الحقيقية إلى أداة Gulp يمكنك إضافة بعض المهام الحقيقية إلى أداة Gulp من خلال التفكير فيما نريد تطبيقه، إذ سنوضّح فيما يلي مجموعةً من الدوال الأساسية لتشغيلها في مشروعنا: html-tidy وcss-lint و js-hint للإبلاغ عن أو إصلاح أخطاء شيفرة HTML أو CSS أو JS الشائعة (اطّلع على gulp-htmltidy و gulp-csslint و gulp-jshint). Autoprefixer لفحص شيفرة CSS وإضافة بادئات البائع عند الحاجة فقط (اطلع على gulp-autoprefixer). babel لتحويل أيّ ميزات صيغة جافاسكربت جديدة إلى صيغة تقليدية تعمل في المتصفحات القديمة (اطلع على gulp-babel). راجع الروابط السابقة للحصول على إرشادات كاملة حول حزم gulp المختلفة التي نستخدمها. يجب أولًا تثبيت الإضافة عبر مدير حزم npm لاستخدامها، ثم طلب أيّ اعتماديات في الجزء العلوي من الملف gulpfile.js ثم إضافة الاختبار -أو الاختبارات- إلى الجزء السفلي منه وتصدير اسم مهمتك لتكون متاحةً باستخدام أوامر gulp. html-tidy ثبّتها باستخدام الأمر التالي: npm install --save-dev gulp-htmltidy ملاحظة: تضيف ‎--save-dev الحزمة بوصفها اعتماديةً لمشروعك، فإذا نظرت في الملف package.json الخاص بمشروعك، فسترى مدخلةً لها في الخاصية devDependencies. أضِف بعد ذلك الاعتمادية التالية إلى الملف gulpfile.js: const htmltidy = require('gulp-htmltidy'); أضِف الاختبار التالي إلى الجزء السفلي من الملف gulpfile.js: function html(cb) { return gulp.src('src/index.html') .pipe(htmltidy()) .pipe(gulp.dest('build')); cb(); } صدّر مهمة html باستخدام ما يلي: exports.html = html; عدّل بعد ذلك تعليمة التصدير الافتراضية إلى ما يلي: exports.default = html; حصلنا هنا على ملف التطوير index.html باستخدام التابع gulp.src()‎ الذي يتيح الحصول على ملف مصدر لتطبيق شيء ما عليه، ثم استخدمنا بعد ذلك الدالة pipe()‎ لتمرير هذا الملف المصدر إلى أمر آخر لتطبيق شيء آخر عليه، حيث يمكننا ربط سلسلة من دوال pipe()‎ عليه بالقدر الذي نريده. شغّلنا أولًا الدالة htmltidy()‎ على الملف المصدر التي تمر على الملف وتصلح الأخطاء فيه، وتكتب الدالة pipe()‎ الثانية ملف HTML الناتج في المجلد build. لا بدّ أنك لاحظت أننا وضعنا عنصر <p> فارغ في نسخة الدخل من الملف، حيث أزالته الدالة htmltidy عند إنشاء ملف الخرج. Autoprefixer و css-lint ثبّتهما أولًا باستخدام الأمرين التاليين: npm install --save-dev gulp-autoprefixer npm install --save-dev gulp-csslint ثانيًا، أضف الاعتماديات التالية إلى الملف gulpfile.js: const autoprefixer = require('gulp-autoprefixer'); const csslint = require('gulp-csslint'); ثالثًا، أضِف الاختبار التالي إلى الجزء السفلي من الملف gulpfile.js: function css(cb) { return gulp.src('src/style.css') .pipe(csslint()) .pipe(csslint.formatter('compact')) .pipe(autoprefixer({ cascade: false })) .pipe(gulp.dest('build')); cb(); } رابعًا، أضِف الخاصية التالية إلى الملف package.json: "browserslist": [ "last 5 versions" ] خامسًا، أضِف السطر التالي بعد التعريفات الثابتة const: const { series } = require('gulp'); سادسًا، صدِّر مهمة css باستخدام ما يلي: exports.css = css; أخيرًا، عدّل المهمة الافتراضية إلى ما يلي: exports.default = series(html, css); حصلنا هنا على الملف style.css وشغّلنا الدالة csslint عليه والتي تعطي قائمةً بأخطاء شيفرة CSS في الطرفية، ثم شغلنا الدالة autoprefixer عليه لإضافة أيّ بادئات مطلوبة لجعل ميزات CSS الحديثة تعمل في المتصفحات القديمة، ويكون الخرج في نهاية سلسلة دوال pipe()‎ ملفَ CSS المعدَّل مع البادئات ضمن المجلد build. لاحظ أنّ ذلك لن يعمل إذا لم تعثر الدالة csslint على أخطاء، لذا جرّب إزالة قوس معقوص من ملف CSS وأعِد تشغيل gulp لمعرفة الخرج الذي تحصل عليه. js-hint و babel أولًا، ثبّتهما باستخدام الأوامر التالية: npm install --save-dev gulp-babel @babel/preset-env npm install --save-dev @babel/core npm install jshint gulp-jshint --save-dev ثانيًا، أضِف الاعتماديات التالية إلى الملف gulpfile.js: const babel = require('gulp-babel'); const jshint = require('gulp-jshint'); ثالثًا، أضِف الاختبار التالي إلى الجزء السفلي من الملف gulpfile.js: function js(cb) { return gulp.src('src/main.js') .pipe(jshint()) .pipe(jshint.reporter('default')) .pipe(babel({ presets: ['@babel/env'] })) .pipe(gulp.dest('build')); cb(); } رابعًا، صدّر مهمة js باستخدام ما يلي: exports.js = js; أخيرًا، عدّل المهمة الافتراضية إلى ما يلي: exports.default = series(html, css, js); حصلنا هنا على الملف main.js وشغّلنا الدالة jshint عليه وأخرجنا النتائج في الطرفية باستخدام jshint.reporter، ثم مرّرنا الملف إلى الدالة babel التي تحوله إلى صيغة التنسيق القديم وتخرج النتيجة إلى المجلد build، وقد تضمّنت شيفرتنا الأصلية على دالة سهمية Arrow Function عدّلتها دالة babel إلى دالة ذات تنسيق قديم. مزيد من الأفكار يمكنك تشغيل الأمر gulp ضمن مجلد مشروعك بمجرد إعداد كل ما سبق، ويجب أن تحصل على الخرج التالي: يمكنك بعد ذلك تجربة خرج الملفات من خلال مهامك الآلية بالنظر إليها ضمن المجلد build وتحميل الملف build/index.html في متصفح الويب، فإذا حصلت على أخطاء، فتحقق من أنك قد أضفت جميع الاعتماديات والاختبارات المذكورة سابقًا، وحاول تعليق أقسام شيفرة HTML/CSS/JavaScript ثم أعِد تشغيل gulp لمعرفة ما إذا كان بإمكانك معرفة المشكلة. تأتي Gulp مع الدالة watch()‎ والتي يمكنك استخدامها لمراقبة ملفاتك وإجراء الاختبارات كلما حفظتَ ملفًا، لذا أضِف ما يلي إلى الجزء السفلي من الملف gulpfile.js مثلًا: function watch() { gulp.watch('src/*.html', html) gulp.watch('src/*.css', css) gulp.watch('src/*.js', js) } exports.watch = watch; أدخِل الآن الأمر gulp watch في طرفيتك، وستراقب أداة Gulp مجلدك، وتشغّل المهام المناسبة كلما حفظتَ تعديلًا في ملف HTML أو CSS أو جافاسكربت. ملاحظة: المحرف * هو محرف بدل Wildcard، فالقصد هنا هو تشغيل هذه المهام عند حفظ أيّ ملفات من هذه الأنواع، كما يمكنك استخدام محارف البدل في مهامك الرئيسية مثل gulp.src('src/*.css')‎ التي تحصل على جميع ملفات CSS ثم تشغّل مهام pipe عليها. اطّلع على مقال دليلك الشامل إلى أداة البناء Gulp في أكاديمية حسوب لمزيد من التفاصيل. مشغلات المهام الأخرى هناك العديد من مشغّلات المهام الأخرى المتاحة، فإننا لا نحاول القول أنّ الأداة Gulp هي أفضل حل موجود، ولكنها جيدة إلى حد ما للمبتدئين، كما يمكنك تجربة استخدام حلول أخرى مثل: تعمل الأداة Grunt بطريقة مشابهة جدًا للأداة Gulp باستثناء أنها تعتمد على المهام المحددة في ملف الإعداد بدلًا من استخدام شيفرة مكتوبة بلغة جافاسكربت. يمكنك تشغيل المهام مباشرةً باستخدام سكربتات npm الموجودة ضمن الملف package.json دون الحاجة إلى تثبيت أيّ نظام إضافي لتشغيل المهام، إذ تعمل سكربتات npm على أساس أنّ أشياء مثل إضافات Gulp تغلّف أدوات سطر الأوامر، لذلك إذا كان بإمكانك معرفة كيفية تشغيل الأدوات باستخدام سطر الأوامر، فيمكنك تشغيلها باستخدام سكربتات npm، إذ يكون التعامل معها أصعب قليلًا، ولكنها جيدة للأشخاص ذوي المهارات العالية في استخدام سطر الأوامر. استخدام خدمات الاختبارات التجارية لتسريع اختبار المتصفحات لنلقِ نظرةً الآن على الخدمات الخارجية التجارية لاختبار المتصفحات وما فائدتها. الفكرة الأساسية لمثل هذه التطبيقات هي أنّ الشركة التي تدير كلًا منها لديها مجموعة خوادم ضخمة يمكنها إجراء العديد من الاختبارات المختلفة، إذ ستقدّم عند استخدام هذه الخدمة عنوان URL للصفحة التي تريد اختبارها مع بعض المعلومات مثل المتصفحات التي تريد اختبارها فيها، ثم يضبط التطبيق آلة افتراضية VM جديدة مع نظام التشغيل والمتصفح الذي حددته، ويعيد الاختبار النتائج على شكل لقطات شاشة ومقاطع فيديو وملفات تسجيل ونصوص وما إلى ذلك. يمكنك بعد ذلك رفع مستوى العتاد باستخدام واجهة برمجة تطبيقات API للوصول إلى الوظائف برمجيًا، مما يعني أنه يمكن دمج هذه التطبيقات مع مشغّلات المهام مثل بيئات Selenium المحلية وغيرها لإنشاء اختبارات آلية. ملاحظة: هناك أنظمة تجارية أخرى متاحة لاختبار المتصفحات ولكن سنركّز في هذا المقال على LambdaTest و Sauce Labs و BrowserStack، كما ليست هذه الأنظمة بالضرورة أفضل الأدوات المتاحة، لكنها أدوات جيدة ومناسبة للمبتدئين. LambdaTest ابدأ بالتسجيل في نظام LambdaTest مجانًا. سجّل الدخول، إذ يجب أن يحدث ذلك تلقائيًا بعد التحقق من عنوان بريدك الإلكتروني. ملاحظة: يقدّم نظام LambdaTest حسابًا مجانيًا يمكنك من خلاله الوصول إلى منصته مدى الحياة على عكس مزودي خدمة اختبار التوافق مع المتصفحات عبر السحابة الأخرى، والفرق الوحيد بين خطته المدفوعة والمجانية هو مقدار الاستهلاك.، إذ يوفر نظام LambdaTest ستِّين دقيقة شهريًا للاختبار المجاني الآلي عبر شبكة Selenium Grid. الأساسيات: الاختبارات اليدوية سيوجّهك نظام LambdaTest بعد تسجيل الدخول إلى لوحة التحكم التي ستزودك بالتفاصيل المتعلقة بعدد الدقائق التي استهلكتها وعدد الجلسات المتزامنة الجارية والعدد الإجمالي للاختبارات حتى الآن والمزيد. أولًا، يجب اختيار التبويب "اختبار الوقت الحقيقي Real Time Testing" من قائمة التنقل اليسرى لبدء الاختبار اليدوي. ستُوجَّه عند النقر على التبويب "اختبار الوقت الحقيقي Real Time Testing" إلى شاشة حيث يمكنك اختيار إعداد المتصفح وإصداره ونظام التشغيل ودقة الشاشة التي تريد اختبار موقع الويب بها. إذا نقرتَ على زر "البدء Start"، فستظهر شاشة تحميل توفر لك آلة افتراضية VM بناءً على إعداداتك، ثم يمكنك إجراء اختبار حي وتفاعلي للتوافق مع المتصفحات على موقع ويب. إذا لاحظت وجود مشكلة في واجهة المستخدِم، فيمكنك مشاركتها مع زملائك من خلال التقاط لقطة شاشة لآلتك الافتراضية باستخدام زر لقطة الشاشة Screenshot، ويمكنك تسجيل مقطع فيديو لجلسة الاختبار من خلال الضغط على زر التسجيل في جلسة الاختبار. حدّد لقطة الشاشة قبل رفعها إلى زملائك باستخدام محرّر الصور المبني مسبقًا. استخدِم زر "تحديده على أساس خطأ Mark as Bug" لرفع الأخطاء إلى العديد من الأدوات الخارجية مثل Jira و Asana و Trello وغيرها، وبذلك يمكنك تسجيل خطأ مباشرةً من جلسة الاختبار على نظام LambdaTest إلى برنامج إدارة مشروعك. ملاحظة: تُوضَع جميع مقاطع الفيديو والصور المُلتقَطة ضمن جلسة الاختبار في المعرض وسجلات الاختبار ومتعقّب المشاكل ضمن LambdaTest. Sauce Labs لنبدأ بتجربة نظام Sauce Labs. أنشئ حسابًا تجريبيًا على Sauce Labs. سجّل الدخول، إذ يجب أن يحدث ذلك تلقائيًا بعد التحقق من عنوان بريدك الإلكتروني. الأساسيات: الاختبارات اليدوية تحتوي لوحة تحكم Sauce Labs على الكثير من الخيارات المتاحة، وتأكّد حاليًا من أنك ضمن تبويب "الاختبارات اليدوية Manual Tests". انقر على "بدء جلسة يدوية جديدة Start a new manual session". اكتب في الشاشة التالية عنوان URL للصفحة التي تريد اختبارها (استخدم العنوان https://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box-fixed.html مثلًا)، ثم اختر متصفحًا ونظام تشغيل تريد اختبارهما معًا باستخدام الأزرار والقوائم المختلفة، فهناك الكثير من الخيارات. إذا نقرت على "بدء الجلسة Start Session"، فستظهر شاشة التحميل التي تشغّل آلةً افتراضيةً تعمل عليها مجموعة المتصفح ونظام تشغيل التي اخترتها. يمكنك بعد انتهاء التحميل البدء في اختبار موقع الويب الذي يعمل في المتصفح المختار عن بُعد. يمكنك رؤية تخطيط الموقع كما سيبدو في المتصفح الذي تختبره، وتحريك الفأرة والنقر فوق الأزرار وما إلى ذلك، إذ تتيح لك القائمة العلوية ما يلي: إيقاف الجلسة. إعطاء شخص آخر عنوان URL ليتمكن من مراقبة الاختبار عن بُعد. نسخ نص أو ملاحظات إلى حافظة بعيدة. أخذ لقطة شاشة. الاختبار في وضع ملء الشاشة. ستعود بعد إيقاف الجلسة إلى تبويب الاختبارات اليدوية، حيث سترى مدخلةً entry لكل جلسة من الجلسات اليدوية السابقة التي بدأتها، وسيؤدي النقر على إحدى هذه المدخلات إلى إظهار المزيد من بيانات الجلسة، حيث يمكنك تنزيل لقطات الشاشة التي التقطتها سابقًا ومشاهدة مقطع فيديو للجلسة وعرض سجلات البيانات وغير ذلك. ملاحظة: يُعَدّ ذلك مفيدًا جدًا، وهو أكثر ملاءمةً من الاضطرار إلى إعداد كل هذه المحاكيات والآلات الافتراضية بنفسك. الخيارات المتقدمة: واجهة Sauce Labs API يحتوي نظام Sauce Labs على واجهة برمجة تطبيقات مريحة تتيح لك استرداد تفاصيل حسابك والاختبارات الحالية برمجيًا وإضافة تعليقات توضيحية إلى الاختبارات بمزيد من التفاصيل مثل حالة النجاح أو الفشل التي لا يمكن تسجيلها باستخدام الاختبار اليدوي وحده، فيمكن أن ترغب مثلًا في تشغيل أحد اختبارات Selenium عن بُعد باستخدام نظام Sauce Labs لاختبار مجموعة معينة من المتصفحات وأنظمة التشغيل، ثم تمرير نتائج الاختبار مرةً أخرى إلى نظام Sauce Labs. كما يحتوي على العديد من العملاء المتاحين للسماح لك بإجراء استدعاءات لواجهة API باستخدام بيئتك المفضلة سواءً كانت PHP أو Java أو Node.js أو غيرها. لنلقِ نظرةً سريعة على كيفية وصولنا إلى واجهة برمجة التطبيقات باستخدام Node.js و node-saucelabs. أعِدّ أولًا مشروع npm جديد لاختبار ذلك كما ذكرنا سابقًا، واستخدم اسم مجلد مختلف مثل sauce-test. ثانيًا، ثبّت مغلّف Node Sauce Labs باستخدام الأمر التالي: npm install saucelabs ثالثًا، أنشئ ملفًا جديدًا ضمن جذر مشروعك بالاسم call_sauce.js وضع فيه المحتويات التالية: const SauceLabs = require('saucelabs'); let myAccount = new SauceLabs({ username: "your-sauce-username", password: "your-sauce-api-key" }); myAccount.getAccountDetails((err, res) => { console.log(res); myAccount.getServiceStatus((err, res) => { // ‫حالة خدمات Sauce Labs console.log(res); myAccount.getJobs((err, jobs) => { // احصل على قائمة بجميع وظائفك for (const job of jobs) { myAccount.showJob(job.id, (err, res) => { let str = `${res.id}: Status: ${res.status}`; if (res.error) { str += `\x1b[31m Error: ${res.error}\x1b[0m`; } console.log(str); }); } }); }); }); رابعًأ، املأ اسم مستخدِم Sauce Labs ومفتاح API في الأماكن المشار إليها، كما يمكن استرجاعها من صفحة إعدادات المستخدِم الخاصة بك. أخيرًا، تأكد من حفظ كل شيء وشغّل ملفك كما يلي: node call_sauce الخيارات المتقدمة: الاختبارات الآلية سنغطّي تشغيل اختبارات Sauce Lab الآلية في المقال التالي. BrowserStack لنبدأ بالإصدار التجريبي من نظام BrowserStack. أنشئ حسابًا تجريبيًا من BrowserStack. سجّل الدخول، إذ يجب أن يحدث ذلك تلقائيًا بعد التحقق من عنوان بريدك الإلكتروني. يجب أن تكون في صفحة الاختبار المباشرة عند تسجيل الدخول لأول مرة، فإذا لم يكن الأمر كذلك، فانقر على الرابط Live في قائمة التنقل العلوية. إذا أردت استخدام Firefox أو Chrome، فيجب تثبيت إضافة متصفح في مربع حوار بعنوان "تفعيل الاختبار المحلي Enable Local Testing"، ثم انقر على زر "تثبيت Install" للمتابعة. ستظل قادرًا على استخدام بعض الميزات في المتصفحات الأخرى باستخدام Flash عمومًا، ولكن يمكن ألّا تحصل على التجربة الكاملة. الأساسيات: الاختبارات اليدوية تتيح لك لوحة تحكم BrowserStack Live اختيار الجهاز والمتصفح الذي ترغب في اختباره، حيث تكون المنصات في العمود الأيسر والأجهزة على اليمين، فإذا حرّكت الفأرة على كل جهاز أو نقرت عليه، فستحصل على مجموعة مختارة من المتصفحات المتوفرة على هذا الجهاز. سيؤدي النقر على إحدى أيقونات المتصفح إلى تحميل اختيارك للمنصة/الجهاز/المتصفح، لذا اختر أحد الخيارات الآن وجرّبه. ملاحظة: تشير أيقونة الجهاز الزرقاء بجوار بعض خيارات الجهاز المحمول إلى أنك ستختبر على جهاز حقيقي، وستُشغَّل الخيارات بدون هذه الأيقونة على المحاكي. ستجد أنه يمكنك إدخال عناوين URL في شريط العناوين واستخدام عناصر التحكم الأخرى كما تتوقع على جهاز حقيقي، كما يمكنك تنفيذ أشياء مثل النسخ واللصق من الجهاز إلى حافظتك أو التمرير لأعلى ولأسفل عن طريق السحب بالفأرة أو استخدام الحركات المناسبة على لوحات اللمس الخاصة بالأجهزة الداعمة مثل MacBook مثل التصغير/التكبير والضغط بإصبعين للتمرير، ولاحظ أنه ليست كل الميزات متوفرة على جميع الأجهزة. كما سترى قائمةً تسمح لك بالتحكم في الجلسة. الميزات هنا هي كما يلي: "التبديل Switch": التغيير إلى مجموعة منصة/جهاز/متصفح أخرى. الاتجاه (تشبه أيقونة إعادة التحميل): تبديل الاتجاه بين الوضع الرأسي والأفقي. ملاءمة الشاشة (تبدو مثل أيقونة ملء الشاشة): ملء مناطق الاختبار بمقدار حجم الجهاز. التقاط خطأ (تبدو مثل الكاميرا): تأخذ لقطة شاشة ثم تسمح لك بالتعليق عليها وحفظها. متعقّب المشاكل (يبدو مثل مجموعة بطاقات): عرض الأخطاء/لقطات الشاشة التي جرى التقاطها مسبقًا. الإعدادات (أيقونة الترس): تسمح لك بتعديل إعدادات الجلسة العامة. المساعدة (علامة الاستفهام): للوصول إلى وظائف المساعدة أو الدعم. أدوات التطوير Devtools: تسمح لك باستخدام أدوات التطوير الخاصة بمتصفحك لتصحيح أو معالجة الصفحة المعروضة في متصفح الاختبار مباشرةً، ويعمل ذلك حاليًا فقط عند اختبار متصفح Safari على أجهزة iOS. معلومات الجهاز Device info: تعرض معلومات حول جهاز الاختبار. الميزات Features: تعرض الميزات التي يدعمها الإعداد الحالي مثل النسخ إلى الحافظة ودعم الإيماءات وما إلى ذلك. التوقف Stop: ينهي الجلسة. ملاحظة: يُعَدّ ذلك مفيدًا جدًا، وهو أكثر ملاءمةً من الاضطرار إلى إعداد كل هذه المحاكيات والآلات الافتراضية بنفسك. الميزات الأساسية الأخرى إذا عُدتَ إلى صفحة BrowserStack الرئيسية، فستجد بعض الميزات الأساسية المفيدة الأخرى ضمن خيار قائمة "المزيد More" مثل: التجاوب مع الشاشات Responsive: أدخِل عنوان URL واضغط على إنشاء Generate، وسيحمّل BrowserStack عنوان URL على أجهزة متعددة ذات أحجام إطار عرض مختلفة، كما يمكنك في كل جهاز ضبط الإعدادات أكثر مثل حجم الشاشة للحصول على فكرة جيدة عن كيفية عمل تخطيط موقعك باستخدام عوامل الأشكال المختلفة. لقطات الشاشة Screenshots: أدخِل عنوان URL واختر المتصفحات/الأجهزة/المنصات التي تريدها، ثم اضغط على إنشاء لقطات شاشة Generate Screenshots، ثم سيأخذ BrowserStack لقطات شاشة لموقعك في جميع تلك المتصفحات ويجعلها متاحةً لك لعرضها وتنزيلها. الخيارات المتقدمة: واجهة BrowserStack API يحتوي نظام BrowserStack على واجهة برمجة تطبيقات مريحة تتيح لك استرداد تفاصيل خطة حسابك وجلساتك وعمليات البناء برمجيًا، كما يحتوي على العديد من العملاء المتاحين للسماح لك بإجراء استدعاءات لواجهة API باستخدام بيئتك المفضلة سواءً كانت PHP أو Java أو Node.js أو غيرها. لنلقِ نظرةً سريعةً على كيفية وصولنا إلى واجهة برمجة التطبيقات باستخدام Node.js. أعِدّ أولًا مشروع npm جديدًا للاختبار كما ذكرنا سابقًا، واستخدم اسم مجلد مختلف مثل bstack-test. ثانيًا، أنشئ ملفًا جديدًا ضمن جذر مشروعك بالاسم call_bstack.js وضَع فيه المحتويات التالية: const request = require("request"); let bsUser = "BROWSERSTACK_USERNAME"; let bsKey = "BROWSERSTACK_ACCESS_KEY"; let baseUrl = `https://${bsUser}:${bsKey}@www.browserstack.com/automate/`; function getPlanDetails(){ request({ uri: `${baseUrl}plan.json` }, (err, res, body) => { console.log(JSON.parse(body)); }); /* Response: { automate_plan: <string>, parallel_sessions_running: <int>, team_parallel_sessions_max_allowed: <int>, parallel_sessions_max_allowed: <int>, queued_sessions: <int>, queued_sessions_max_allowed: <int> } */ } getPlanDetails(); ثالثًا، املأ اسم مستخدِم BrowserStack ومفتاح API في الأماكن المشار إليها، ويمكن استرجاعها من [لوحة تحكم BrowserStack](https://www.browserstack.com/automate الآلية). رابعًا، تأكد من حفظ كل شيء وشغّل ملفك كما يلي: node call_bstack سنقدِّم فيما يلي بعض الدوال الأخرى الجاهزة المفيدة عند العمل مع واجهة برمجة تطبيقات BrowserStack: function getBuilds(){ request({ uri: `${baseUrl}builds.json` }, (err, res, body) => { /* Response: [ { automation_build: { name: <string>, duration: <int>, status: <string>, hashed_id: <string> } }, { automation_build: { name: <string>, duration: <int>, status: <string>, hashed_id: <string> } }, ... ] */ }; function getSessionsInBuild(build){ const buildId = build.automation_build.hashed_id; request({ uri: `${baseUrl}builds/${buildId}/sessions.json` }, (err, res, body) => { console.log(JSON.parse(body)); }); /* Response: [ { automation_session: { name: <string>, duration: <int>, os: <string>, os_version: <string>, browser_version: <string>, browser: <string>, device: <string>, status: <string>, hashed_id: <string>, reason: <string>, build_name: <string>, project_name: <string>, logs: <string>, browser_url: <string>, public_url: <string>, video_url: <string>, browser_console_logs_url: <string>, har_logs_url: <string> } }, { automation_session: { name: <string>, duration: <int>, os: <string>, os_version: <string>, browser_version: <string>, browser: <string>, device: <string>, status: <string>, hashed_id: <string>, reason: <string>, build_name: <string>, project_name: <string>, logs: <string>, browser_url: <string>, public_url: <string>, video_url: <string>, browser_console_logs_url: <string>, har_logs_url: <string> } }, ... ] */ } function getSessionDetails(session){ const sessionId = session.automation_session.hashed_id; request({uri: `${baseUrl}sessions/${sessionId}.json`}, (err, res, body) => { console.log(JSON.parse(body)); }); /* Response: { automation_session: { name: <string>, duration: <int>, os: <string>, os_version: <string>, browser_version: <string>, browser: <string>, device: <string>, status: <string>, hashed_id: <string>, reason: <string>, build_name: <string>, project_name: <string>, logs: <string>, browser_url: <string>, public_url: <string>, video_url: <string>, browser_console_logs_url: <string>, har_logs_url: <string> } } */ } الخيارات المتقدمة: الاختبارات الآلية سنغطّي تشغيل اختبارات BrowserStack الآلية في المقال التالي. TestingBot لنبدأ باختبار نظام TestingBot. أنشئ حسابًا تجريبيًا في TestingBot. سجّل الدخول، إذ يجب أن يحدث ذلك تلقائيًا بعد التحقق من عنوان بريدك الإلكتروني. الأساسيات: الاختبارات اليدوية تسرد لوحة تحكم TestingBot الخيارات المختلفة التي يمكنك الاختيار من بينها، وتأكَّد من أنك حاليًا في التبويب Live Web Testing. أدخِل عنوان URL للصفحة التي تريد اختبارها. اختر مجموعة المتصفح/نظام التشغيل التي تريد اختبارها من خلال تحديدها من الشبكة. إذا نقرت على "بدء المتصفح Start Browser"، فستظهر شاشة تحميل تشغّل آلة افتراضية تعمل عليها المجموعة التي اخترتها. يمكنك بعد انتهاء التحميل البدء في اختبار موقع الويب الذي يعمل في المتصفح المختار عن بُعد. يمكنك رؤية تخطيط الموقع كما سيبدو في المتصفح الذي تختبره، وتحريك الفأرة والنقر على الأزرار وما إلى ذلك، كما تتيح لك القائمة الجانبية ما يلي: إيقاف الجلسة. تغيير دقة الشاشة. نسخ نصوص أو ملاحظات إلى حافظة بعيدة. أخذ لقطات شاشة وتحريرها وتنزيلها. الاختبار في وضع ملء الشاشة. ستعود إلى الصفحة Live Web Testing بعد إيقاف الجلسة، حيث سترى مدخلةً لكل جلسة من الجلسات اليدوية السابقة التي بدأتها، كما سيؤدي النقر على إحدى هذه المدخلات إلى إظهار المزيد من بيانات الجلسة، إذ يمكنك تنزيل لقطات الشاشة التي التقطتها ومشاهدة مقطع فيديو للاختبار وعرض سجلات الجلسة. الخيارات المتقدمة: واجهة TestingBot API يحتوي نظام TestingBot على واجهة برمجة تطبيقات مريحة تتيح لك استرداد تفاصيل حسابك والاختبارات الحالية برمجيًا، وإضافة تعليقات توضيحية إلى الاختبارات مع مزيد من التفاصيل مثل حالة النجاح أو الفشل التي لا يمكن تسجيلها باستخدام الاختبار اليدوي وحده، كما يحتوي TestingBot على العديد من عملاء واجهة برمجة التطبيقات الذين يمكنك استخدامهم للتفاعل معها بما في ذلك عملاء NodeJS و Python و Ruby و Java و PHP. سنوضّح فيما يلي مثالًا عن كيفية التفاعل مع واجهة TestingBot API مع اختبار عميل NodeJS وهو testingbot-api: أعِدّ أولًا مشروع npm جديدًا للاختبار كما تحدثنا سابقًا، واستخدِم اسم مجلد مختلف مثل tb-test. ثانيًا، ثبّت مغلّف Node TestingBot باستخدام الأمر التالي: npm install testingbot-api ثالثًا، أنشئ ملفًا جديدًا ضمن جذر مشروعك بالاسم tb.js وضَع فيه المحتويات التالية: const TestingBot = require('testingbot-api'); let tb = new TestingBot({ api_key: "your-tb-key", api_secret: "your-tb-secret" }); tb.getTests(function (err, tests) { console.log(tests); }); رابعًا، املأ مفتاح TestingBot Key و Secret في الأماكن المشار إليها، حيث يمكنك العثور عليها في لوحة تحكم TestingBot. أخيرًا، تأكد من حفظ كل شيء وشغّل الملف كما يلي: node tb.js الخيارات المتقدمة: الاختبارات الآلية سنغطّي تشغيل اختبارات TestingBot الآلية في المقال التالي. الخلاصة يمكنك الآن البدء في رؤية فوائد استخدام أدوات الاختبار الآلية لمساعدتك في عملية اختبار مواقع الويب، وسنتطرق في المقال التالي إلى إعداد نظام الاختبارات الآلية المحلي باستخدام Selenium وكيفية دمجه مع خدمات مثل Sauce Labs و BrowserStack و TestingBot. ترجمة -وبتصرُّف- للمقال Introduction to automated testing. اقرأ أيضًا بعض المشكلات الشائعة في اختبار قابلية الاستخدام وكيفية تجنبها للحصول على مراجعات صادقة قابلية الاستخدام وأهميتها في تجربة المستخدم كيفية اكتشاف دعم المتصفحات للميزات أثناء اختبار مشاريع الويب مدخل إلى اختبار مشاريع الويب للتوافق مع المتصفحات
  13. يتضمن اكتشاف الميزات Feature Detection معرفة ما إذا كان المتصفح يدعم كتلةً معينةً من الشيفرة البرمجية ويشغّل شيفرةً مختلفةً اعتمادًا على ذلك، بحيث يمكن للمتصفح دائمًا توفير تجربة عمل ناجحة بدلًا من التعطل أو ظهور الأخطاء في بعض المتصفحات، ويوضح هذا المقال بالتفصيل كيفية كتابة شيفرة اكتشاف المتصفحات للميزات البسيطة وكيفية استخدام مكتبة لتسريع التطبيق والميزات الأصيلة Native لاكتشاف الميزات مثل الميزة ‎@supports. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML و CSS وجافاسكربت JavaScript، وأن تكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات. الهدف: فهم مفهوم اكتشاف الميزات والقدرة على تطبيق الحلول المناسبة في شيفرة CSS وجافاسكربت. مفهوم اكتشاف الميزات تكمن الفكرة وراء اكتشاف الميزات Feature Detection في أنه يمكنك إجراء اختبار لتحديد ما إذا كانت الميزة مدعومةً في المتصفح الحالي ثم تشغيل الشيفرة البرمجية شرطيًا لتوفير تجربة مقبولة في كل من المتصفحات التي تدعم الميزة والمتصفحات التي لا تدعمها، فإذا لم تطبّق ذلك، فلن تعرض المتصفحات التي لا تدعم الميزات التي تستخدمها في شيفرتك البرمجية الخاصة مواقعَك بصورة صحيحة وستفشل، مما يخلق تجربة مستخدِم سيئة. لنطّلع على المثال الذي تطرقنا إليه في مقال معالجة المشاكل الشائعة للتوافق مع المتصفحات المختلفة في شيفرة جافاسكربت، إذ كان لدينا واجهة Geolocation API التي تعرض بيانات الموقع المتاحة للجهاز الذي يعمل عليه متصفح الويب، ولهذه الواجهة نقطة دخول رئيسية لاستخدامها وهي الخاصية geolocation التي يوفرها الكائن العام Navigator، لذا يمكنك اكتشاف ما إذا كان المتصفح يدعم تحديد الموقع الجغرافي أم لا باستخدام الشيفرة البرمجية التالية مثلًا: if ("geolocation" in navigator) { navigator.geolocation.getCurrentPosition(function(position) { // اعرض الموقع على الخريطة باستخدام‫ واجهة Google Maps API مثلًا }); } else { // اجعل المستخدِم يختار من بين الخرائط الساكنة بدلًا من ذلك } لاكتشاف ميزة واحدة، تكفي تعليمة if ولكن يُفضَّل استخدام مكتبة اكتشاف ميزات مُنشَأة مسبقًا من أجل التطبيقات المعقّدة بدلًا من كتابة مكتبتك طوال الوقت، إذ تُعَدّ مكتبة مودرنيزر Modernizr المعيار القياسي لاختبارات اكتشاف الميزات وسنتطرق إليها لاحقًا. لا تخلط بين اكتشاف الميزات والتعرف على المتصفح Browser Sniffing -أي اكتشاف المتصفح المُحدَّد الذي يصل إلى الموقع- الذي يُعَدّ ممارسةً سيئةً يجب عدم استخدامها، وراجع فقرة استخدام شيفرة التعرف على المتصفح السيئة من مقال معالجة المشاكل الشائعة للتوافق مع المتصفحات المختلفة في شيفرة جافاسكربت للتعرف على مزيد من التفاصيل. كتابة اختباراتك الخاصة لاكتشاف الميزات سنتعرّف في هذا القسم على تطبيق اختباراتك الخاصة لاكتشاف الميزات في كل من شيفرة CSS وجافاسكربت. شيفرة CSS يمكنك كتابة اختبارات لميزات CSS من خلال اختبار وجود خاصيات element.style.property -مثل الخاصية paragraph.style.transform- في شيفرة جافاسكربت. يمكن أن يكون المثال التقليدي لذلك هو اختبار دعم قيمة subgrid ضمن التخطيط الشبكي Grid في المتصفح، إذ يمكننا استعمال القيمة subgrid التخطيط الشبكي الفرعي مع grid-template-columns و grid-template-rows في المتصفحات التي تدعم ضمن التخطيط الشبكي أما في المتصفحات التي لا تدعهما فنستخدم التخطيط الشبكي العادي ولكن لن تكون كما نريد مع subgrid. بناء على المثال السابق، يمكن أن نضيف ملف تنسيق stylesheet ضمن الترويسة head في ملف HTML الأول فيه تنفيذ كل subgrid وذلك إن كانت مدعومة في المتصفح والآخر يستعمل التخطيط الشبكي العادي إن لم تكن مدعومة وذلك لتعويض نقص الدعم الحاصل وتجنب اختلال التنسيق، بالشكل التالي مثلًا: <link href="basic-styling.css" rel="stylesheet" /> <link class="conditional" href="grid-layout.css" rel="stylesheet" /> يضيف هنا الملف basic-styling.css كل تنسيقات الصفحة المشتركة والمتوافقة لكل المتصفحات، وبالإضافة له يوجد ملفين CSS آخرين الذين أشرنا إليهما قبل قليل، الأول grid-layout.css والثاني subgrid-layout.css وسنضمن ملفًا منهما بناءً على دعم subgrid في المتصفح وسنستعمل لغة جافاسكربت JavaScript لاختبار دعمها ثم نُحدِّث قيمة href الذي يشير إليه العنصر <link> الثاني في الشيفرة السابقة أي ذي الصنف class="conditional"‎ بناء على دعم المتصفح. نضيف وسم <script></script> إلى ملف HTML في الجزء السفلي من العنصر body قبل وسم الإغلاق <‎/body> مباشرة يحوي الشيفرة التالية: const conditional = document.querySelector(".conditional"); if (CSS.supports("grid-template-columns", "subgrid")) { conditional.setAttribute("href", "subgrid-layout.css.css"); } اختبرنا في الشيفرة السابقة إن كانت الخاصية grid-template-columns تدعم القيمة subgrid باستعمال التابع CSS.support()‎. ‎@supports استخدَمتْ لغة CSS في الآونة الأخيرة آليةً لاكتشاف ميزاتها الأصيلة وهي ‎@supports باستخدام القاعدة @ التي تعمل بطريقة مشابهة لاستعلامات الوسائط Media Queries باستثناء أنها تطبّق شيفرة CSS بطريقة انتقائية بناءً على ما إذا كانت ميزة CSS مدعومة، بدلًا من تطبيق شيفرة CSS بطريقة انتقائية اعتمادًا على ميزة وسائط مثل الدقة أو عرض الشاشة أو نسبة العرض إلى الارتفاع aspect ratio. يمكننا إعادة كتابة مثالنا السابق باستخدام ‎@supports كما يلي: @supports (grid-template-columns: subgrid) { main { display: grid; grid-template-columns: repeat(9, 1fr); grid-template-rows: repeat(4, minmax(100px, auto)); } .item { display: grid; grid-column: 2 / 7; grid-row: 2 / 4; grid-template-columns: subgrid; grid-template-rows: repeat(3, 80px); } .subitem { grid-column: 3 / 6; grid-row: 1 / 3; } } تطبّق كتلة قاعدة @ تنسيق CSS فيها فقط إذا دعم المتصفح الحالي التصريح grid-template-columns: subgrid;‎. ويجب تضمين تصريح كامل (وليس مجرد اسم خاصية فقط) وعدم تضمين فاصلة منقوطة في النهاية لكي يعمل كل شرط. يتوفر لدى ‎@supports العبارات المنطقية OR و NOT و AND، فمثلًا ستطبق الكتلة التالية تنسيق التخطيط الشبكي grid العادي إن لم يكن المتصفح يدعم subgrid: @supports not (grid-template-columns: subgrid) { /* ضع القواعد هنا */ } يمكن أن يبدو ذلك ملائمًا أكثر من المثال السابق، إذ يمكننا تطبيق اكتشاف الميزات في CSS دون الحاجة إلى شيفرة جافاسكربت، ويمكننا التعامل مع جميع العبارات المنطقية في ملف CSS واحد مع تقليل طلبات HTTP، لذا هذه هي الطريقة المفضلة في اكتشاف الميزات دون اللجوء إلى جافاسكربت. شيفرة جافاسكربت رأينا مثالًا لاختبار اكتشاف ميزات جافاسكربت في وقت سابق، كما يمكن إجراء هذه الاختبارات عبر أحد الأنماط الشائعة القليلة. ضع في بالك أن بعض الميزات معروفة بعدم قابليتها للاكتشاف، ارجع إلى صفحة Undetectables بدءًا من عام 2016. إليك بعضًا من الأنماط الشائعة للميزات القابلة للاكتشاف: عضو في كائن تحقق مما إذا كان هناك تابع أو خاصية معينة (تكون عادةً نقطة دخولٍ إلى استخدام واجهة برمجة تطبيقات API أو ميزة أخرى تريد اكتشافها) موجودةً في الكائن الأب Object. رأينا في مثال سابق استخدامًا لهذا النمط عند اكتشاف دعم واجهة Geolocation وذلك بفحص وجود العضو geolocation في الكائن navigator: if ("geolocation" in navigator) { // navigator.geolocation الوصول إلى الواجهة } خاصية في عنصر أنشئ عنصرًا في الذاكرة باستخدام التابع Document.createElement()‎ ثم تحقق من وجود خاصية فيه، ويوضح هذا المثال طريقةً لاكتشاف دعم الواجهة Canvas API: function supports_canvas() { return !!document.createElement("canvas").getContext; } if (supports_canvas()) { // ‫ إنشاء عناصر canvas واستخدامها } ملاحظة: استخدمنا !! في المثال السابق وذلك لتحويل أي نوع بيانات إلى قيمة منطقية true أو false. تابع في عنصر يعيد قيمة أنشئ عنصرًا في الذاكرة باستخدام Document.createElement()‎ ثم تحقق من وجود تابع فيه، فإذا كان موجودًا، فتحقق من القيمة التي يعيدها. اطلع على اختبار اكتشاف تنسيقات الفيديو في HTML5 خاصية في عنصر تحتفظ بقيمة أنشئ عنصرًا في الذاكرة باستخدام التابع Document.createElement()‎ ثم اضبط خاصيةً على قيمة معينة، ثم تحقق من الاحتفاظ بالقيمة. ملاحظة: تُعَدّ قيمة NOT المُضاعفَة في المثال السابق (!!) طريقةً لإجبار القيمة المُعادة لتصبح قيمة منطقية مناسبة بدلًا من القيمة true أو false التي يمكن أن تؤدي إلى تحريف النتائج. اطّلع على مقال اكتشاف دعم المتصفحات لميزات HTML5 للعثور على مزيد من المعلومات، وضَع في الحسبان أنه توجد بعض الميزات التي لا يمكن اكتشافها بالرغم من كل ذلك. الميزة matchMedia تتيح ميزة جافاسكربت Window.matchMedia تشغيل اختبارات استعلام الوسائط ضمن شيفرة جافاسكربت، وتبدو كما يلي: if (window.matchMedia("(max-width: 480px)").matches) { // شغّل شيفرة جافاسكربت هنا } يستخدِم تطبيق Snapshot مثلًا هذه الميزة لتطبيق مكتبة جافاسكربت Brick بطريقة انتقائية ويستخدِمها للتعامل مع تخطيط واجهة المستخدم لتخطيط الشاشات الصغيرة فقط (480 بكسل أو أقل)، كما نستخدِم أولًا السمة media لتطبيق مكتبة Brick CSS على الصفحة إذا كان عرض الصفحة 480 بكسل أو أقل فقط: <link href="dist/brick.css" rel="stylesheet" media="all and (max-width: 480px)" /> نستخدِم بعد ذلك matchMedia()‎ في جافاسكربت عدة مرات لتشغيل دوال التنقل في مكتبة Brick مع تخطيط الشاشة الصغيرة فقط، ويمكن رؤية كل شيء دفعةً واحدةً في تخطيطات الشاشة الأوسع، لذلك لا نحتاج للتنقل بين العروض المختلفة. if (window.matchMedia("(max-width: 480px)").matches) { deck.shuffleTo(1); } استخدام مكتبة Modernizr لتطبيق اكتشاف الميزات يمكن تنفيذ اختبارات اكتشاف الميزات باستخدام تقنيات مثل التقنيات التي أوضحناها سابقًا، ويمكنك استخدام مكتبة مخصَّصة لاكتشاف الميزات لأنها تسهّل الأمور كثيرًا. تُعَد مكتبة Modernizr أساس جميع مكتبات اكتشاف الميزات، ويمكنها اكتشاف كل ما تحتاجه في أي وقت، ولنلقِ نظرةً على كيفية استخدامها. يمكنك استخدام بناء التطوير الذي يتضمن كل اختبار ممكن لاكتشاف الميزات عندما تجرّب مكتبة Modernizr، لذا نزّل بناء التطوير كما يلي: انقر على رابط بناء التطوير. انقر على زر "البناء Build" الوردي الكبير على الصفحة التي تظهر. انقر على رابط "التنزيل Download" الأعلى في مربع الحوار الذي يظهر. احفظه في مكان ما مثل المجلد الذي تنشئ أمثلتك الأخرى فيه. إذا أردت استخدام مكتبة Modernizr في عملية الإنتاج، فيمكنك الانتقال إلى صفحة التنزيل التي زرتها سابقًا ثم النقر على أزرار علامة الزائد للاطلاع على الميزات التي تريد اكتشافها فقط، وستنزّل بعد ذلك عند النقر على زر "البناء Build" بناءً مخصَّصًا يحتوي فقط على تلك الميزات التي جرى اكتشافها، مما يجعل حجم الملف أصغر بكثير. شيفرة CSS لنلقِ نظرةً على كيفية عمل مكتبة Modernizr من حيث التطبيق الانتقائي لشيفرة CSS. أنشئ أولًا نسخةً من الملفين supports-feature-detect.html و supports-styling.css ثم احفظهما بالاسم modernizr-css.html و modernizr-css.css. ثانيًا، عدّل العنصر <link> في شيفرة HTML بحيث يشير إلى ملف CSS الصحيح، ويجب تعديل العنصر <title> إلى شيء أكثر ملاءمةً: <link href="modernizr-css.css" rel="stylesheet"> ثالثًا، أضِف العنصر <script> قبل عنصر <link> لتطبيق مكتبة Modernizr على الصفحة، إذ يجب تطبيق ذلك على الصفحة قبل أيّ شيفرة CSS (أو جافاسكربت) تستخدِمها. <script src="modernizr-custom.js"></script> رابعًا، عدّل وسم الفتح <html> بحيث يبدو كما يلي: <html lang="en-us" class="no-js"> … </html> جرّب تحميل صفحتك، وستحصل على فكرة عن كيفية عمل مكتبة Modernizr مع ميزات CSS، فإذا ألقيت نظرةً على فاحص DOM الخاص بأدوات المطور في متصفحك، فسترى أنّ مكتبة Modernizr قد حدّثت قيمة صنف class العنصر <html> كما يلي: <html class="js no-htmlimports no-proximity sizes no-flash transferables applicationcache blobconstructor blob-constructor no-contextmenu (and loads of more values)"> … </html> يحتوي هذا العنصر الآن على عدد كبير من الأصناف التي تشير إلى حالة دعم ميزات التقنيات المختلفة، فإذا لم يدعم المتصفح الميزة التخطيط الشبكي grid مثلًا على الإطلاق، فسيُعطَى العنصر <html> اسم الصنف no-cssgrid، كما هنالك عدة أصناف أخرى متعلقة مثلًا بالتخطيط grid مثل cssgridlegacy أو no-cssgridlegacy بناء على دعم المتصفح للإصدار القديم legacy من التخطيط الشبكي. ملاحظة: يمكنك الاطلاع على قائمة الأصناف الكاملة وما تعنيه أسماؤها وما تقوم به بالرجوع إلى صفحة Features detected من Modernizr. لسوء الحظ لا تفحص مكتبة Modernizr دعم بعض ميزات CSS الجديدة مثل subgrid التي رأيتها للتو وغيرها ولو كانت تفعل ذلك، لكنا عدلنا مثال ‎@support السابق بالشكل التالي: main { display: grid; grid-template-columns: repeat(9, 1fr); grid-template-rows: repeat(4, minmax(100px, auto)); } .item { display: grid; grid-column: 2 / 7; grid-row: 2 / 4; grid-template-rows: repeat(3, 80px); } ‫/* ‫خاصيات المتصفحات مع خاصية subgrid الحديثة */ .csssubgrid .item { grid-template-columns: subgrid; } .csssubgrid .subitem { grid-column: 3 / 6; grid-row: 1 / 3; } ‫/* ‫الإجراءات الاحتياطية للمتصفحات التي لا تدعم خاصية subgrid الحديثة */ .no-csssubgrid .subitem { display: flex; flex: 33%; } يمكنك استهداف المتصفحات التي تدعم أو التي لا تدعم ميزة معينة باستخدام محددات أحفاد معينة بسبب وضع جميع أسماء الأصناف في العنصر <html>. لذلك طبّقنا هنا المجموعة العليا من القواعد فقط على المتصفحات التي تدعم subgrid ومجموعة القواعد السفلية فقط على المتصفحات التي لا تدعمها. ملاحظة: ضع في الحسبان أنّ جميع اختبارات ميزات HTML وجافاسكربت الخاصة بمكتبة Modernizr تُذكَر في أسماء الأصناف، لذلك يمكنك تطبيق شيفرة CSS بطريقة انتقائية بناءً على دعم المتصفح لميزات HTML أو جافاسكربت إذا لزم الأمر. شيفرة جافاسكربت مكتبة Modernizr مُهيَّأة جيدًا لتطبيق اكتشاف ميزات جافاسكربت من خلال توفير كائن Modernizr العام للصفحة التي تُطبَّق عليها، ويحتوي هذا الكائن على نتائج اكتشاف الميزات بوصفها خاصيات true أو false. حمّل المثال modernizr-css.html في متصفحك، ثم حاول الانتقال إلى طرفية جافاسكربت واكتب Modernizr.‎ متبوعةً ببعض أسماء هذه الأصناف كما يلي: Modernizr.flexbox Modernizr.xhr2 Modernizr.fetch ستعيد الطرفية قيم true أو false للإشارة إلى ما إذا كان متصفحك يدعم هذه الميزات أم لا. لنلقِ نظرةً على مثال لتوضيح كيفية استخدامك لتلك الخاصيات: أنشئ أولًا نسخةً محليةً من الملف modernizr-js.html. ثانيًا، اربط مكتبة Modernizr بشيفرة HTML باستخدام العنصر <script> كما فعلنا سابقًا ثم ضع هذا العنصر قبل العنصر <script> الموجود مسبقًا الذي يربط واجهة Google Maps API بالصفحة. املأ بعد ذلك نص العنصر البديل YOUR-API-KEY في عنصر <script> الثاني بمفتاح Google Maps API صالح، ويمكنك الحصول على مفتاح من خلال تسجيل الدخول إلى حساب Google، والانتقال إلى صفحة الحصول على مفتاح أو المصادقة ثم انقر على الزر الأزرق "الحصول على مفتاح Get a Key" واتبع التعليمات. أخيرًا، أضِف عنصر <script> آخر في الجزء السفلي من العنصر <body> وقبل وسم الإغلاق <‎/body> مباشرةً ثم ضع السكربت التالي ضمن الوسوم: if (Modernizr.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { let latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude); let myOptions = { zoom: 8, center: latlng, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDefaultUI: true } let map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); }); } else { const para = document.createElement('p'); para.textContent = 'Argh, no geolocation!'; document.body.appendChild(para); } جرب مثالك واستخدِم اختبار Modernizr.geolocation للتحقق مما إذا كان المتصفح الحالي يدعم تحديد الموقع الجغرافي، فإذا كان الأمر كذلك، فسنشغّل الشيفرة البرمجية التي تحدد موقع جهازك الحالي وترسمه على خريطة جوجل. الخلاصة لقد غطينا في هذا المقال اكتشاف دعم المتصفحات للميزات وعرضنا المفاهيم الرئيسية ووضحنا كيفية تطبيق اختبارات اكتشاف ميزاتك واستخدام مكتبة Modernizr لتطبيق الاختبارات بسهولة أكبر، وسنوضح في المقال التالي الاختبارات الآلية. ترجمة -وبتصرُّف- للمقال Implementing feature detection. اقرأ أيضًا مدخل إلى اختبار مشاريع الويب للتوافق مع المتصفحات استراتيجيات اختبارات مشاريع الويب للتوافق مع المتصفحات اكتشاف دعم المتصفحات لميزات HTML5
  14. لقد غطينا في المقال السابق معالجة مشاكل سهولة الوصول Accessibility الشائعة للتوافق مع المتصفحات أهم أفكار سهولة الوصول الخاصة بتقنيات الويب المختلفة بما في ذلك بعض تقنيات الاختبار مثل التنقل باستخدام لوحة المفاتيح وأدوات فحص تباين الألوان، وسنلقي نظرةً الآن على الأدوات الأخرى التي يمكنك الاستفادة منها عند إجراء اختبار سهولة الوصول. أدوات التدقيق Auditing Tools هناك عدد من أدوات التدقيق المتاحة التي يمكنك استخدامها في صفحات الويب، حيث ستلقي هذه الأدوات نظرةً على الصفحات وتعيد قائمةً بمشاكل سهولة الوصول الموجودة فيها، ومن هذه الأدوات ما يلي: Wave: تُعَدّ أداةً جيدةً لاختبار سهولة الوصول عبر الإنترنت، حيث تأخذ عنوان ويب وتعيد عرضًا توضيحيًا مفيدًا لتلك الصفحة مع إبراز مشاكل سهولة الوصول. Tenon: تُعَدّ أداةً جيدةً أخرى عبر الإنترنت تراجِع الشيفرة البرمجية من عنوان URL المُقدَّم وتعرض نتائجًا عن أخطاء سهولة الوصول بما في ذلك المقاييس والأخطاء المحددة باستخدام معايير WCAG التي تؤثر عليها والإصلاحات المقترحة، ولكنها تتطلب تسجيلًا تجريبيًا مجانيًا لعرض النتائج. tota11y: تُعَدّ أداةً سهولة وصول من Khan Academy تتخذ شكل مكتبة جافاسكربت التي ترفقها مع صفحتك لتوفير عدد من أدوات سهولة الوصول. لنلقِ نظرةً على مثال باستخدام أداة Wave: انتقل إلى صفحة Wave الرئيسية. أدخِل عنوان URL الخاص بالمثال bad-semantics.html في مربع إدخال النص بالقرب من أعلى الصفحة، ثم اضغط على مفتاح Enter أو انقر/اضغط على السهم في أقصى يمين مربع الإدخال. يجب أن يستجيب الموقع مع إعطاء وصف بمشاكل سهولة الوصول، وانقر على الرموز المعروضة لمعرفة المزيد من المعلومات حول كلٍّ من المشاكل التي حددها تقييم أداة Wave. ملاحظة: لا تُعَدّ هذه الأدوات جيدةً بما يكفي لحل جميع مشاكل سهولة الوصول، لذا ستحتاج إلى مزيج من المعرفة والخبرة واختبار المستخدِمين وغير ذلك للحصول على صورة كاملة. أدوات الاختبارات الآلية تذهب أداة Deque's aXe إلى أبعد قليلًا من أدوات التدقيق التي ذكرناها سابقًا، إذ تفحص الصفحات وتعيد أخطاء سهولة الوصول، كما يُحتمَل أن يكون شكلها الأكثر فائدةً هو إضافات المتصفح التالية: aXe للمتصفح Chrome. aXe للمتصفح Firefox. تضيف هذه الإضافات تبويب سهولة الوصول "Accessibility" إلى أدوات المطور في المتصفح، وقد ثبّتنا إصدار Firefox مثلًا، ثم استخدمناه لتدقيق المثال bad-table.html، وحصلنا على النتائج التالية: كما يمكن تثبيت الأداة aXe باستخدام npm ويمكن دمجها مع مشغّلات المهام مثل Grunt و Gulp وأطر العمل الآلية مثل Selenium و Cucumber وأطر اختبار الوحدات مثل Jasmine. قارئات الشاشة يجب بالتأكيد إجراء الاختبار باستخدام قارئات الشاشة لتعتاد على كيفية استخدام الأشخاص الكفيفين للويب، ويتوفر عدد من قارئات الشاشة مثل: بعض المنتجات التجارية المدفوعة مثل JAWS و Window Eyes (لنظام Windows). بعض المنتجات المجانية مثل NVDA (لنظام Windows) و ChromeVox (للمتصفح Chrome ونظامَي Windows و Mac OS X) و Orca (لنظام لينكس Linux). بعض المنتجات المدمَجة مع نظام التشغيل مثل VoiceOver (على نظام Mac OS X و iOS) و ChromeVox (على أجهزة Chromebook) و TalkBack (على نظام Android). تُعَدّ قارئات الشاشة عمومًا تطبيقات منفصلةً تعمل على نظام التشغيل المضيف ولا يمكنها قراءة صفحات الويب فحسب، وإنما يمكنها قراءة النصوص في التطبيقات الأخرى، ويمكن أن يكون بعضها إضافةً لمتصفح مثل ChromeVox، وتتصرف قارئات الشاشة بطرق مختلفة قليلًا ولديها عناصر تحكم مختلفة، لذا يجب الرجوع إلى الوثائق الخاصة بقارئ الشاشة الذي اخترته للحصول على جميع التفاصيل، ولكن يمكن القول بأن التفاصيل الأساسية لطريقة عملها واحدة. لنطبّق بعض الاختبارات باستخدام بعض قارئات الشاشة المختلفة لإعطائك فكرةً عامةً عن كيفية عملها وكيفية الاختبار باستخدامها. إضافة: توفر منظمة WebAIM معلومات دورية مفيدة حول التصميم الأمثل لقارئات الشاشة وتحقيق التوافقية معها بالإضافة إلى معلومات موسعة حول عن استخداماتها وما الحلول التي تتلاءم معها وبعض الإحصائيات المفيدة عنها. قارئ الشاشة VoiceOver يأتي قارئ الشاشة VoiceOver -أو VO اختصارًا- مجانًا مع جهاز Mac أو iPhone أو iPad، لذا فهو مفيد للاختبار على الحاسوب أو الهاتف المحمول إذا أردت استخدام منتجات Apple، إذ سنختبره على نظام Mac OS X على جهاز MacBook Pro. يمكنك تشغيل هذا البرنامج من خلال الضغط على الاختصار Cmd + F5، فإذا لم تستخدِم VO سابقًا، فستظهر شاشة ترحيب حيث يمكنك اختيار بدء تشغيل VO أو عدم تشغيله، وتشغيل برنامج تعليمي مفيد إلى حد ما لمعرفة كيفية استخدامه، ويمكنك إيقاف تشغيله من خلال الضغط على الاختصار Cmd + F5 مرةً أخرى. ستبدو الشاشة نفسها عند تشغيل VO، ولكنك سترى مربعًا أسودًا في الجزء السفلي الأيسر من الشاشة يحتوي على معلومات حول ما هو مُحدَّد حاليًا، كما سيُميَّز التحديد الحالي بحدود سوداء، ويُعرَف هذا التمييز بمؤشر VO. ستستفيد كثيرًا من معدِّل VO لاستخدام قارئ الشاشة VO، إذ يُعَدّ المعدِّل Modifier مفتاحًا أو مجموعة مفاتيح يجب الضغط عليها بالإضافة إلى اختصارات VO من لوحة المفاتيح الفعلية لجعلها تعمل، كما يُعَدّ استخدام المعدل بهذه الطريقة أمرًا شائعًا مع قارئات الشاشة من أجل ألّا تتعارض أوامرها مع الأوامر الأخرى، ويمكن أن يكون المعدِّل في حالة VO المفتاح CapsLock أو Ctrl + Option. يحتوي قارئ الشاشة VO على العديد من أوامر لوحة المفاتيح، ولكننا لن ندرجها جميعًا هنا، حيث ستجد أوامر أو اختصارات لوحة المفاتيح الأساسية الخاصة بقارئ الشاشة VO والتي ستحتاجها لاختبار صفحة الويب في الجدول التالي، إذ يعني الاختصار "VO" معدِّل VoiceOver في اختصارات لوحة المفاتيح: اختصار لوحة المفاتيح الوصف المعدِّل VO + مفاتيح الأسهم تحريك مؤشر قارئ الشاشة VO للأعلى ولليمين وللأسفل ولليسار. المعدِّل VO + مفتاح المسافة تحديد أو تنشيط العناصر المميزة باستخدام مؤشر VO، ويتضمن ذلك العناصر المحددة في الدوّار Rotor (انظر أدناه). المعدِّل VO + مفتاح Shift + السهم للأسفل الانتقال إلى مجموعة من العناصر (مثل جدول HTML أو نموذج، …إلخ)، ويمكنك التنقل وتحديد العناصر ضمن تلك المجموعة باستخدام الأوامر المذكورة أعلاه. المعدِّل VO + مفتاح Shift + السهم للأعلى الخروج من المجموعة. المعدِّل VO + مفتاح C قراءة عنوان العمود الحالي (عندما تكون ضمن الجدول). المعدِّل VO + مفتاح R قراءة عنوان الصف الحالي (عندما تكون ضمن الجدول). المعدِّل VO + مفتاح C + مفتاح C (مفتاحا C على التوالي) قراءة العمود الحالي بأكمله بما في ذلك عنوانه (عندما تكون ضمن الجدول). المعدِّل VO + مفتاح R + مفتاح R (مفتاحا R على التوالي) قراءة الصف الحالي بأكمله بما في ذلك العناوين التي تقابل كل خلية (عندما تكون ضمن الجدول). المعدِّل VO + السهم لليسار أو المعدّل VO + السهم لليمين التنقل بين الخيارات عندما تكون ضمن بعض الخيارات الأفقية مثل منتقي التاريخ أو الوقت. المعدِّل VO + السهم للأعلى أو المعدّل VO + السهم للأسفل تغيير الخيار الحالي عندما تكون ضمن بعض الخيارات الأفقية مثل منتقي التاريخ أو الوقت. المعدِّل VO + المفتاح U استخدم الدوّار Rotor الذي يعرض قوائم العناوين والروابط وعناصر التحكم في النموذج وغير ذلك لسهولة التنقل. المعدِّل VO + السهم لليسار أو المعدّل VO + السهم لليمين التنقل بين القوائم المختلفة المتوفرة في الدوّار Rotor (عندما تكون ضمن الدوّار Rotor). المعدّل VO + السهم للأعلى أو المعدّل VO + السهم للأسفل التنقل بين العناصر المختلفة في قائمة الدوّار Rotor الحالية (عندما تكون ضمن الدوّار Rotor). المفتاح Esc الخروج من الدوّار Rotor (عندما تكون ضمن الدوّار Rotor). المفتاح Ctrl إيقاف أو استئناف الكلام (عندما يتحدث قارئ الشاشة VO). المعدّل VO + المفتاح Z إعادة تشغيل الجزء الأخير من الكلام. المعدّل VO + المفتاح D الانتقال إلى لوحة التفضيلات في جهاز Mac لتتمكن من تحديد التطبيقات التي تعمل ضمنها. يمكن أن تكون هذه الأوامر كثيرةً، لكنها ليست سيئةً عندما تعتاد عليها، إذ يعطيك قارئ الشاشة VO بانتظام تذكيرات بالأوامر التي يجب استخدامها في أماكن معينة. قارئ الشاشة NVDA يعمل قارئ الشاشة NVDA في نظام ويندوز فقط ويجب تثبيته كما يلي: نزّله من موقع nvaccess، حيث يمكنك اختيار التبرع أو تنزيله مجانًا، ويجب أن تضع عنوان بريدك الإلكتروني قبل أن تتمكّن من تنزيله. ثبّته بعد تنزيله من خلال النقر نقرًا مزدوجًا على برنامج التثبيت واقبل الترخيص واتبع التعليمات. يمكنك بدء تشغيل NVDA من خلال النقر نقرًا مزدوجًا على ملف أو اختصار البرنامج أو استخدام اختصار لوحة المفاتيح Ctrl + Alt + N. سترى مربع حوار ترحيب NVDA عند بدء تشغيله، ويمكنك هنا الاختيار من بين عدة خيارات، ثم الضغط على زر "موافق OK" للبدء بالعمل. سيصبح NVDA نشطًا الآن على حاسوبك. ستستفيد كثيرًا من معدِّل NVDA لاستخدام NVDA، وهو مفتاح يجب الضغط عليه بالإضافة إلى اختصارات NVDA من لوحة المفاتيح الفعلية لتشغيله، كما يُعَدّ استخدام مثل هذا المعدِّل أمرًا شائعًا مع قارئات الشاشة بحيث لا تتعارض أوامرها مع الأوامر الأخرى، كما يمكن أن يكون المُعدِّل في حالة NVDA إما المفتاح Insert (الحالة الافتراضية) أو المفتاح CapsLock (يمكن اختياره من خلال تحديد مربع الاختيار الأول في مربع حوار ترحيب NVDA قبل الضغط على زر موافق). ملاحظة: يُعَدّ NVDA أدق من VoiceOver من حيث كيفية تمييز مكان التحديد وما يفعله، فإذا تنقّلت عبر العناوين والقوائم وما إلى ذلك، فستُميَّز العناصر التي حدّدتها بإطار دقيق، ولكن ليس ذلك هو الحال دائمًا، فإذا ضعت تمامًا، فيمكنك الضغط على الاختصار Ctrl + F5 لتحديث الصفحة الحالية والبدء من الأعلى مرةً أخرى. يحتوي قارئ الشاشة NVDA على العديد من أوامر لوحة المفاتيح التي لن نذكرها جميعًا هنا، وإنما سنذكر فقط الأوامر الأساسية التي ستحتاجها لاختبار صفحة الويب في الجدول التالي، حيث يعني الاختصار "NVDA" معدِّل NVDA في اختصارات لوحة المفاتيح: اختصار لوحة المفاتيح الوصف المعدِّل NVDA + المفتاح Q إيقاف تشغيل قارئ الشاشة NVDA بعد تشغيله. المعدِّل NVDA + السهم للأعلى قراءة السطر الحالي. المعدِّل NVDA + السهم للأسفل بدء القراءة في الموضع الحالي. السهم للأعلى والسهم للأسفل، أو مفتاح Shift + مفتاح Tab ومفتاح Tab الانتقال إلى العنصر السابق/التالي في الصفحة وقراءته. السهم لليسار والسهم لليمين الانتقال إلى المحرف السابق/التالي في العنصر الحالي وقراءته. مفتاح Shift + مفتاح H ومفتاح H الانتقال إلى العنوان السابق/التالي وقراءته. مفتاح Shift + مفتاح K ومفتاح K الانتقال إلى الرابط السابق/التالي وقراءته. مفتاح Shift + مفتاح D ومفتاح D الانتقال إلى علامة المستند السابق/التالي مثل <nav> وقراءته. مفتاح Shift + المفاتيح 1–6 والمفاتيح 1–6 الانتقال إلى العنوان السابق/التالي (ذات المستويات 1-6) وقراءته. مفتاح Shift + مفتاح F ومفتاح F الانتقال إلى حقل الإدخال في النموذج السابق/التالي والتركيز عليه. مفتاح Shift + مفتاح T ومفتاح T الانتقال إلى جدول البيانات السابق/التالي والتركيز عليه. مفتاح Shift + مفتاح B ومفتاح B الانتقال إلى الزر السابق/التالي وقراءة تسميته أو عنوانه. مفتاح Shift + مفتاح L ومفتاح L الانتقال إلى القائمة السابقة/التالية وقراءة عنصر القائمة الأول. مفتاح Shift + مفتاح I ومفتاح I الانتقال إلى عنصر القائمة السابق/التالي وقراءته. مفتاح Enter أو Return تنشيط العنصر (عند تحديد رابط/زر أو عنصر آخر قابل للتنشيط). المعدِّل NVDA + مفتاح المسافة Space الدخول في النموذج بحيث يمكن تحديد العناصر الفردية أو الخروج منه إذا كنت موجودًا فيه فعليًا (عند تحديد النموذج). مفتاح Shift + مفتاح Tab ومفتاح Tab التنقل بين حقول إدخال النموذج (عندما تكون ضمن النموذج). السهم للأعلى والسهم للأسفل تغيير قيم حقول إدخال النموذج في حالة مثل مربعات التحديد مثلًا (عندما تكون ضمن النموذج). مفتاح المسافة تحديد القيمة المختارة (عندما تكون ضمن النموذج). المفتاح Ctrl + المفتاح Alt + مفاتيح الأسهم التنقل بين خلايا الجدول (عند تحديد الجدول). اختبار قارئ الشاشة اعتدت الآن على استخدام قارئات الشاشة، لذا يمكنك استخدامه لإجراء بعض اختبارات سهولة الوصول السريعة للحصول على فكرة عن كيفية تعامل قارئات الشاشة مع ميزات صفحات الويب الجيدة والسيئة: اطّلع على المثال good-semantics.html ولاحظ كيف يعثر قارئ الشاشة على العناوين وكيف تكون متاحةً للاستخدام للتنقل، و اطّلع على المثال bad-semantics.html، ولاحظ كيف لا يحصل قارئ الشاشة على أيّ من هذه المعلومات، وتخيل كم سيكون هذا مزعجًا عند محاولة التنقل في صفحة نصية طويلة. اطّلع على المثال good-links.html، ولاحظ كيف تبدو منطقيةً عند عرضها، وليس ذلك هو الحال مع المثال bad-links.html فجميع عباراته مجرد عبارات "انقر هنا". اطّلع على المثال good-form.html ولاحظ كيف أنّ حقول إدخال النموذج موصوفة باستخدام تسمياتها لأننا استخدمنا عناصر <label> بصورة صحيحة، في حين ستحصل في المثال bad-form.html على تسمية غير مفيدة. اطّلع على المثال punk-bands-complete.html وشاهد كيف يمكن لقارئ الشاشة ربط أعمدة وصفوف المحتوى وقراءتها معًا لأننا حددنا العناوين بصورة صحيحة، في حين لا يمكن ربط الخلايا على الإطلاق في المثال bad-table.html، ولاحظ أنّ قارئ الشاشة NVDA يبدو أنه يتصرف بغرابة بعض الشيء عندما يكون لديك جدول واحد فقط في الصفحة، لذا يمكنك تجربة صفحة اختبار جدول WebAIM بدلًا من ذلك. ألقِ نظرةً على مثال WAI-ARIA Live Regions الذي رأيناه سابقًا، ولاحظ كيف سيستمر قارئ الشاشة في قراءة القسم المُحدَّث باستمرار عند تحديثه. اختبار المستخدم لا يمكنك الاعتماد على الأدوات الآلية وحدها لتحديد مشاكل سهولة الوصول على موقعك، إذ يُفضَّل تضمين بعض مجموعات مستخدمي سهولة الوصول إذا كان ذلك ممكنًا عند وضع خطة الاختبار، لذا حاول إشراك بعض مستخدِمِي قارئ الشاشة وبعض مستخدِمِي لوحة المفاتيح فقط وبعض المستخدِمين الصُم ومجموعات أخرى بما يناسب متطلباتك. قائمة مراجعة اختبار سهولة الوصول توفر القائمة التالية قائمة مراجعة يمكنك اتباعها للتأكد من أنك أجريت اختبار سهولة الوصول الموصَى به لمشروعك: تأكد من أنّ شيفرة HTML صحيحة دلاليًا قدر الإمكان، إذ يُعَدّ التحقق من صحتها بدايةً جيدةً، كما هو الحال مع استخدام أداة تدقيق. تأكد من أنّ المحتوى يبدو منطقيًا عند إيقاف تشغيل شيفرة CSS. تأكد من أنّ وظائفك تشمل مستخدِمِي لوحة المفاتيح واختبرها باستخدام مفاتيح Tab أو Return أو Enter. تأكد من أنّ المحتوى غير النصي يحتوي على نصوص بديلة، إذ تُعَدّ أداة التدقيق مفيدةً في اكتشاف مثل هذه المشاكل. تأكد من أنّ تباين ألوان موقعك مقبول باستخدام أداة فحص مناسبة. تأكد من أنّ المحتوى المخفي مرئي لقارئات الشاشة. تأكد من أنّ الوظائف قابلة للاستخدام بدون شيفرة جافاسكربت حيثما أمكن ذلك. استخدم مواصفات ARIA لتحسين سهولة الوصول في المكان المناسب. شغّل موقعك من خلال أداة تدقيق. اختبره باستخدام قارئ الشاشة. ضمّن سياسة أو بيان سهولة وصول في مكان ما يمكن العثور عليه على موقعك لتقول ما فعلته. العثور عن المساعدة هناك العديد من المشاكل الأخرى التي ستواجهها مع سهولة الوصول، ولكن أهم شيء يجب معرفته هو كيفية العثور على إجابات عبر الإنترنت، ويمكنك الاعتماد على قسم الأسئلة والأجوبة في أكاديمية حسوب للحصول على بعض المساعدة والنصائح الجيدة من مبرمجين أكفاء. الخلاصة اطلعنا على كيفية اختبار مشاكل سهولة الوصول الرئيسية التي يمكن أن تواجهها وتعرفنا في هذا المقال كيفية التغلب عليها عبر عرض أهم الأدوات اللازمة لتحقيق سهولة الوصول، وسنلقي نظرةً في المقال التالي على اكتشاف دعم المتصفح للميزات بمزيد من التفصيل. ترجمة -وبتصرُّف- لقسم من المقال Handling common accessibility problems. اقرأ أيضًا المقال السابق: معالجة مشاكل سهولة الوصول Accessibility الشائعة للتوافق مع المتصفحات الدلالات المضمنة لعناصر صفحة الويب ودورها في تعزيز سهولة الوصول التحقق من سهولة الوصول لصفحات الويب
  15. سنوجّه اهتمامنا في هذا المقال إلى سهولة الوصول أو سهولة الوصول Accessibility -أي شمولية كل المستخدمين وتسهيل وصولهم واستخدامهم الموقع بمن فيهم ذوي الاحتياجات الخاصة- وتوفير معلومات حول المشاكل الشائعة وكيفية إجراء اختبار بسيط والاستفادة من أدوات التدقيق أو الاختبارات الآلي للعثور على مشاكل سهولة الوصول للتوافق مع المتصفحات Cross Browser. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML وCSS وجافاسكربت JavaScript، وأن يكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات. الهدف: القدرة على تشخيص مشاكل سهولة الوصول الشائعة واستخدام الأدوات والتقنيات المناسبة لإصلاحها. ما هي سهولة الوصول Accessibility؟ يفكر معظم الناس مباشرةً عندما نقول سهولة الوصول في سياق تقنيات الويب في التأكد من أن مواقع أو تطبيقات الويب يمكن أن يستخدمها الأشخاص ذوي الاحتياجات الخاصة بسهولة مثل: الأشخاص المعاقون بصريًا الذين يستخدِمون قارئات الشاشة أو التكبير أو التقريب للوصول إلى النص. الأشخاص الذين يعانون من إعاقات في الوظائف الحركية ويستخدِمون لوحة المفاتيح (أو ميزات أخرى بدون استخدام الفأرة) لتنشيط وظائف موقع الويب. الأشخاص الذين يعانون من إعاقات سمعية والذين يعتمدون على التسميات أو العناوين التوضيحية أو البدائل النصية الأخرى للمحتوى الصوتي أو الفيديو. لكن من الخطأ القول أنّ سهولة الوصول تتعلق بالإعاقات فقط، فالهدف منها هو جعل مواقع أو تطبيقات الويب يمكن أن يستخدِمها أكبر عدد ممكن من الأشخاص ضمن أكبر عدد ممكن من السياقات، وليس فقط هؤلاء المستخدِمين الذين يستخدِمون حواسيب عالية المواصفات مثل: مستخدِمو الأجهزة المحمولة. مستخدِمو أجهزة تصفح بديلة مثل أجهزة التلفاز والساعات وما إلى ذلك. مستخدِمو الأجهزة القديمة التي يمكن ألّا تحتوي على أحدث المتصفحات. مستخدِمو الأجهزة ذات المواصفات الأقل والتي يمكن أن تحتوي على معالجات بطيئة. يتأكد اختبار التوافق مع المتصفحات Cross Browser Testing من أنّ أكبر عدد ممكن من الأشخاص يستخدِمون موقعك، ويدور هذا المقال بأكمله حول سهولة الوصول، إذ سنغطي التوافق مع المتصفحات المختلفة ومشاكل الاختبار التي تواجه الأشخاص ذوي الاحتياجات الخاصة وكيفية استخدامهم للويب، وتحدثنا سابقًا عن مجالات أخرى مثل التصميم المتجاوب مع الشاشات والأداء. ملاحظة: لا تُعَدّ سهولة الوصول ناجحةً بنسبة 100% مثل العديد من الأشياء الأخرى في عملية تطوير الويب، فمن المستحيل إلى حد كبير تحقيق سهولة الوصول بنسبة 100% لجميع المحتويات خاصةً وأنّ المواقع تصبح أكثر تعقيدًا، وإنما يتعلق الأمر ببذل جهد أكبر لجعل أكبر قدر ممكن من محتواك في متناول أكبر عدد ممكن من الأشخاص من خلال استخدام التكويد الدفاعي Defensive Coding والالتزام بأفضل الممارسات. مشاكل سهولة الوصول الشائعة سنشرح في هذا القسم بالتفصيل بعض مشاكل سهولة الوصول الرئيسية في صفحات الويب والمتصلة بتقنيات محددة إلى جانب أفضل الممارسات التي يجب اتباعها وبعض الاختبارات السريعة التي يمكنك إجراؤها لمعرفة سير موقعك في الاتجاه الصحيح. ملاحظة: تُعَدّ سهولة الوصول أمرًا أخلاقيًا يجب تطبيقه، كما تُعَدّ جيدة للأعمال، إذ يمثِّل عددُ المستخدِمين ذوي الاحتياجات الخاصة والمستخدِمين على الأجهزة المحمولة وغيرهم شرائحًا سوقيةً مهمةً، لكنها تُعَدّ مطلبًا قانونيًا في أجزاء كثيرة من العالم لإنشاء محتوى ويب يمكن أن يصل إليه الأشخاص ذوي الاحتياجات الخاصة. سهولة الوصول في شيفرة HTML يمكن الوصول إلى شيفرة HTML الدلالية حيث تُستخدَم العناصر لغرضها الصحيح مباشرةً، إذ يمكن أن يقرأ المشاهدون المبصرون هذا المحتوى بشرط ألا تفعل شيئًا غير منطقي مثل جعل حجم النص صغيرًا جدًا أو إخفائه باستخدام لغة CSS، ويمكن أن تقرأها التقنيات المساعدة مثل قارئات الشاشة -أي التطبيقات التي تقرأ حرفيًا صفحة الويب لمستخدميها- مع مزايا أخرى. البنية الدلالية أهم مكسب في لغة HTML الدلالية هو استخدام عناوين وفقرات لمحتواك، لأن مستخدِمي قارئات الشاشة يميلون إلى استخدام عناوين المستند بوصفها علامات إرشاديةً للعثور على المحتوى الذي يحتاجون إليه بسرعة أكبر، فإذا لم يتضمن محتواك على عناوين، فكل ما سيحصلون عليه هو جدار نصي ضخم بدون علامات إرشادية للعثور على أيّ شيء، وإليك أمثلة على بينة شيفرة HTML السيئة أولًا: <font size="7">My heading</font> <br><br> This is the first section of my document. <br><br> I'll add another paragraph here too. <br><br> <font size="5">My subheading</font> <br><br> This is the first subsection of my document. I'd love people to be able to find this content! <br><br> <font size="5">My 2nd subheading</font> <br><br> This is the second subsection of my content. I think it is more interesting than the last one. ثم إليك الجيدة: <h1>My heading</h1> <p>This is the first section of my document.</p> <p>I'll add another paragraph here too.</p> <h2>My subheading</h2> <p>This is the first subsection of my document. I'd love people to be able to find this content!</p> <h2>My 2nd subheading</h2> <p>This is the second subsection of my content. I think it is more interesting than the last one.</p> كما يجب أن يكون المحتوى منطقيًا بترتيب المصدر، بحيث يمكنك دائمًا وضعه في المكان الذي تريد استخدام شيفرة CSS فيه لاحقًا، ولكن يجب أن يكون ترتيب المصدر صحيحًا في البداية. يمكنك إيقاف تشغيل شيفرة CSS الخاصة بالموقع للاختبار ومعرفة مدى سهولة فهمه بدون هذه الشيفرة، إذ يمكنك تطبيق هذا الاختبار يدويًا من خلال إزالة شيفرة CSS من شيفرتك البرمجية، ولكن أسهل طريقة هي استخدام ميزات المتصفح، فمثلًا: في فايرفوكس Firefox: حدِّد الخيار عرض View ثم تنسيق الصفحة Page Style ثم بلا تنسيق No Style من القائمة الرئيسية. في سفاري Safari: حدِّد الخيار تطوير Develop ثم إلغاء تفعيل التنسيق Disable Styles من القائمة الرئيسية، حيث يمكن تفعيل قائمة التطوير Develop من خلال تحديد الخيار Safari ثم تفضيلات Preferences ثم خيارات متقدمة Advanced ثم إظهار Show Develop من شريط القوائم. في كروم Chrome: ثبّت الإضافة Web Developer Toolbar، ثم أعد تشغيل المتصفح، وانقر على الرمز الذي يشبه الترس الظاهر، ثم حدِّد الخيار CSS ثم إلغاء تفعيل جميع التنسيقات Disable All Styles. في إيدج Edge: حدِّد الخيار عرض View ثم التنسيق Style ثم بلا تنسيق No Style من القائمة الرئيسية. لمزيد من التفاصيل حول الدلالية في HTML، ارجع إلى مقال مدخل إلى مواصفات ARIA: إعطاء عناصر HTML دلالات خاصة لتسهيل الوصول. سهولة الوصول في استخدام لوحة المفاتيح الأصيلة يمكن تحديد بعض ميزات HTML باستخدام لوحة المفاتيح فقط، حيث يُعَدّ ذلك السلوك الافتراضي المتاح منذ الأيام الأولى للويب، كما أنّ العناصر التي لديها هذه الإمكانية هي العناصر الشائعة التي تسمح للمستخدِم بالتفاعل مع صفحات الويب والروابط والأزرار <button> وعناصر النموذج مثل <input>. يمكنك تجربة ذلك باستخدام المثال native-keyboard-accessibility.html (اطّلع على شيفرته البرمجية)، لذا افتح هذا المثال في تبويب جديد، وحاول الضغط على مفتاح Tab، ويجب أن ترى بعد بضع ضغطات أنّ تركيز التبويب ينتقل بين العناصر المختلفة القابلة للتركيز، وتُعطَى العناصر المركَّزة تنسيقًا افتراضيًا مميزًا في كل متصفح (يختلف قليلًا من متصفح إلى آخر) بحيث يمكنك تحديد العنصر الذي يُركَّز عليه. ملاحظة: في متصفح Firefox يمكنك تفعيل خيار يسمى show tapping order أو ما شابه من ضمن حزمة خيارات سهولة الوصول accessibility لإظهار ترتيب العناصر عند الضغط على مفتاح Tab. يمكنك بعد ذلك الضغط على زر Enter أو Return لاتباع رابط مُركَّز أو الضغط على زر (ضمّنا شيفرة جافاسكربت لجعل الأزرار تعطي رسالة تنبيه) أو البدء في الكتابة لإدخال نص في حقل الإدخال، كما أنّ عناصر النموذج الأخرى لها عناصر تحكم مختلفة، إذ يمكن عرض خيارات العنصر <select> مثلًا والتبديل بينها باستخدام مفتاحَي الأسهم للأعلى وللأسفل. لاحظ أن المتصفحات يمكن أن تحتوي على خيارات تحكم مختلفة في لوحة المفاتيح، إذ تتبع معظم المتصفحات الحديثة نمط الضغط على مفتاح Tab الموضح سابقًا، كما يمكنك الضغط على الاختصار Shift + Tab للانتقال إلى الخلف بين العناصر القابلة للتركيز، ولكن لبعض المتصفحات خصائصها الخاصة مثل: لا يستخدِم متصفح Firefox لنظام التشغيل Mac مفتاح Tab افتراضيًا، لذلك يمكن تشغيله من خلال الانتقال إلى قائمة التفضيلات Preferences ثم خيارات متقدمة Advanced ثم عام General، ثم إلغاء تحديد الخيار "استخدم دائمًا مفاتيح المؤشر للتنقل بين الصفحات Always use the cursor keys to navigate within pages"، كما يجب بعد ذلك فتح تطبيق تفضيلاتك في نظام Mac ثم الانتقال إلى لوحة المفاتيح Keyboard ثم الاختصارات Shortcuts، ثم تحديد زر الاختيار "جميع عناصر التحكم All Controls". لا يسمح لك المتصفح Safari باستخدام مفتاح Tab للتنقل بين الروابط افتراضيًا، ولكن يمكن تفعيل ذلك من خلال فتح تفضيلات Safari، ثم الانتقال إلى الخيارات المتقدمة وتحديد مربع الاختيار "الضغط على مفتاح Tab لتمييز كل عنصر على صفحة الويب Press Tab to highlight each item on a webpage". تحذير: يجب إجراء هذا النوع من الاختبارات أو المراجعات على أيّ صفحة جديدة تكتبها، إذ يجب التأكد من أنّ لوحة المفاتيح تصل إلى وظيفة معينة، وأنّ ترتيب استخدام مفتاح Tab يوفر مسار تنقل جيد عبر المستند. يسلّط هذا المثال الضوء على أهمية استخدام العنصر الدلالي الصحيح مع الوظيفة الصحيحة، ويمكن تصميم أيّ عنصر ليبدو مثل رابط أو زر باستخدام لغة CSS، وأن يتصرف مثل رابط أو زر باستخدام لغة جافاسكربت، لكنها لن تكون روابطًا أو أزرارًا في الواقع، وستفقد كثيرًا من سهولة الوصول التي تمنحك إياها هذه العناصر مجانًا، لذا يمكنك عدم تطبيق ذلك إذا كان بإمكانك تجنبه. يمكنك التحكم في كيفية ظهور العناصر القابلة للتركيز عند التركيز عليها باستخدام الصنف الوهمي ‎:focus، إذ تُعَدّ مضاعفة تنسيقات التركيز Focus والتمرير Hover فكرةً جيدةً، وبالتالي يحصل المستخدِمون على الدليل المرئي بأن عنصر التحكم سيفعل شيئًا ما عند تنشيطه سواءً كانوا يستخدِمون الفأرة أو لوحة المفاتيح. a:hover, input:hover, button:hover, select:hover, a:focus, input:focus, button:focus, select:focus { font-weight: bold; } ملاحظة: إذا قررت إزالة تنسيق التركيز الافتراضي باستخدام شيفرة CSS، فتأكد من استبداله بشيء آخر يناسب تصميمك بصورة أفضل، لإنها أداة سهولة وصول قيّمة للغاية ولا يجب إزالتها. بناء لوحة مفاتيح سهلة الوصول لا يمكن في بعض الأحيان تجنب فقدان سهولة الوصول إلى لوحة المفاتيح، فلنفترض أنك حصلت على موقع لا تكون فيه الدلالات جيدةً جدًا مثل استخدام نظام CMS سيء ينشئ أزرارًا باستخدام عناصر <div>، أو أنك تستخدِم عنصر تحكم معقّد لا يحتوي على سهولة وصول مبنية مسبقًا للوصول إلى لوحة المفاتيح مثل العنصر <video>، ولكن يُعَدّ متصفح أوبرا Opera هو المتصفح الوحيد الذي يسمح لك بالانتقال بين عناصر تحكم المتصفح الافتراضية الخاصة بالعنصر <video> باستخدام مفتاح Tab، ولديك عدد قليل من الخيارات لعناصر التحكم تلك مثل: أنشئ عناصر تحكم مخصَّصة باستخدام عناصر الأزرار <button> -التي يمكننا الانتقال إليها افتراضيًا باستخدام مفتاح Tab- وباستخدام شيفرة جافاسكربت لتوصيل وظائفها. أنشئ اختصارات لوحة المفاتيح باستخدام جافاسكربت، بحيث يمكن تنشيط الوظيفة عند الضغط على مفاتيح معينة على لوحة المفاتيح. استخدِم بعض الأساليب لتزييف سلوك الأزرار، واطّلع على المثال fake-div-buttons.html وعلى شيفرته البرمجية، إذ أعطينا في هذا المثال أزرارَ العنصر <div> المزيفة القدرةَ على التركيز -باستخدام مفتاح Tab مثلًا- من خلال إعطاء كل زر السمة tabindex="0"‎، مما يسمح لنا بالانتقال إلى الأزرار باستخدام مفتاح Tab، ولكن ليس لتنشيطها عبر مفتاح Enter أو Return، إذ يجب في هذه الحالة إضافة الجزء التالي من شيفرة جافاسكربت: document.onkeydown = function(e) { if(e.keyCode === 13) { // ‫مفتاح Enter أو Return document.activeElement.onclick(e); } }; أضفنا هنا مستمعًا إلى الكائن document لاكتشاف وقت الضغط على زر من لوحة المفاتيح، ويمكن التحقق من الزر المضغوط باستخدام الخاصية keyCode الخاصة بكائن الحدث، فإذا كانت الخاصية keyCode هي الخاصية التي تتطابق مع مفتاح Return أو Enter، فسنشغّل الدالة المخزنة في معالج الحدث onclick الخاص بالزر باستخدام document.activeElement.onclick()‎، وتعطينا الخاصية activeElement العنصرَ المُركَّز عليه في الصفحة حاليًا. ملاحظة: لن تعمل هذه التقنية إلا إذا ضبطتَ معالجات الأحداث الأصلية باستخدام خاصيات معالج الأحداث مثل onclick، إذ لن تعمل الخاصية addEventListener، وستظهر كثير من المشاكل الإضافية عند إعادة بناء الوظائف، ولا بدّ أن تكون هناك مشاكل أخرى معها، لذا يُفضَّل استخدام العنصر المناسب مع الوظيفة المناسبة من البداية. النصوص البديلة تُعَدّ النصوص البديلة مهمةً جدًا لسهولة الوصول فإذا عانى المستخدِم من إعاقة بصرية أو سمعية تمنعه من رؤية أو سماع بعض المحتوى، فهذه مشكلة، وأبسط نص بديل متاح هو السمة alt والتي يجب أن نضعها في جميع الصور التي تحتوي على محتوى ذي صلة بالنص البديل، إذ يجب أن تحتوي السمة alt على وصف الصورة الذي ينقل المعنى والمحتوى بنجاح على الصفحة لتلتقطها قارئات الشاشة وتقرأها للمستخدِم، كما يمكن اختبار النص البديل المفقود بعدة طرق مثل استخدام أدوات تدقيق سهولة الوصول Accessibility Auditing. يُعَدّ النص البديل أكثر تعقيدًا إلى حد ما للمحتوى الصوتي والفيديو، فهناك طريقة لتحديد مسارات النص -مثل النصوص التوضيحية Subtitles- وعرضها عند تشغيل الفيديو بصيغة العنصر <track> وبتنسيق WebVTT، كما يُعَدّ توافق المتصفح مع هذه الميزات جيدًا إلى حد ما، ولكن إذا أردت توفير بدائل نصية للصوت أو دعم المتصفحات القديمة، فيمكن أن يكون عرض نسخة صوتية بسيطة في مكان ما على الصفحة أو على صفحة منفصلة فكرةً جيدةً. علاقات وسياق العنصر توجد ميزات معينة وممارسات أفضل في لغة HTML مصمَّمة لتوفير السياق والعلاقات بين العناصر حيث لا توجد طريقة أخرى لذلك، والأمثلة الثلاثة الأكثر شيوعًا لذلك هي الروابط وتسميات أو عناوين Labels النماذج وجداول البيانات. الفاصل في أن يكون نص الرابط سهل الوصول هو أن يستخدِم مستخدِمو قارئات الشاشة ميزةً شائعةً حيث يسحبون قائمةً بجميع الروابط على الصفحة، لذا يجب أن يكون نص الرابط منطقيًا في هذه الحالة، إذ تُعَدّ قائمة الروابط المسماة "انقر هنا" مثلًا سيئةً في تحقيق سهولة الوصول، إذ يُفضَّل أن يكون نص الرابط منطقيًا ضمن السياق وخارجه. يُعَدّ عنصر النموذج <label> أحد الميزات المركزية التي تجعل النماذج شاملة، وتكمن مشكلة النماذج في أنك بحاجة إلى تسميات <label> توضّح البيانات التي يجب إدخالها في كل حقل إدخال في النموذج، كما يجب تضمين كل تسمية ضمن العنصر <label> لربطها بوضوح بحقل الإدخال المقابل لها في النموذج، إذ يجب أن تتطابق السمة for الخاصة بالعنصر <label> مع قيمة معرّف id عنصر النموذج، وسيكون ذلك منطقيًا حتى لو لم يكن ترتيب المصدر منطقيًا تمامًا. يمكن كتابة جدول البيانات الأساسي باستخدام توصيف بسيط جدًا (اطلع على المثال bad-table.html مباشرةً وشيفرته البرمجية)، ولكن توجد مشاكل في هذا المثال، إذ لا توجد طريقة لمستخدِم قارئ الشاشة لربط الصفوف أو الأعمدة معًا بوصفها مجموعات من البيانات، لذا يجب معرفة ما هي صفوف العناوين، وما إذا كانت عناوينًا للصفوف أو للأعمدة وما إلى ذلك، إذ لا يمكن تطبيق ذلك إلّا بطريقة مرئية لمثل هذا الجدول. إذا اطّلعت على المثال punk-bands-complete.html مباشرةً أو اطّلعت على شيفرته البرمجية، فيمكنك رؤية بعض أدوات سهولة الوصول المساعدة مثل عناوين الجدول (العنصر <th> والسمات scope) والعنصر <caption>. سهولة الوصول في شيفرة CSS توفّر لغة CSS ميزات سهولة الوصول الأساسية بمقدار أقل بكثير من لغة HTML، ولكن لا تزال بإمكانها إحداث القدر نفسه من الضرر لسهولة الوصول إذا استخدِمت بطريقة غير صحيحة، وإليك بعض النصائح المتعلقة بسهولة الوصول المتضمنة في شيفرة CSS: استخدِم العناصر الدلالية الصحيحة لكتابة محتوًى مختلف في شيفرة HTML، فإذا أردت إنشاء تأثير مرئي مختلف، فاستخدم شيفرة CSS دون العبث بعنصر HTML للحصول على الشكل الذي تريده، فإذا أردت نصًا أكبر، فاستخدم الخاصية font-size وليس العنصر <h1>. تأكد من أنّ ترتيب المصدر منطقي بدون شيفرة CSS، إذ يمكنك دائمًا استخدام CSS لتنسيق الصفحة بالطريقة التي تريدها بعد ذلك. يجب عليك التأكد من أنّ العناصر التفاعلية مثل الأزرار والروابط لها حالات تركيز أو تمرير أو تنشيط مناسبة لإعطاء المستخدِم أدلة مرئية عن وظيفتها، فإذا أزلتَ الإعدادات الافتراضية لأسباب تتعلق بالتنسيق، فتأكد من تضمين بعض التنسيقات البديلة. الألوان وتباينها يجب أن تتأكد من أنّ لون النص (المقدمة) متباين جيدًا مع لون الخلفية عند اختيار نظام ألوان موقع الويب، فيمكن أن يكون تصميمك رائعًا، ولكنه ليس جيدًا إذا لم يستطع الأشخاص الذين يعانون من إعاقات بصرية مثل عمى الألوان قراءة محتوى موقعك، لذا استخدِم أداةً مثل الأداة Color Contrast Checker من WebAIM للتحقق مما إذا كان نظام ألوانك متباينًا بدرجة كافية. لا تعتمد على الألوان وحدها لإظهار الإشارات الدلالية أو المعلومات، لأنها لن تكون مفيدةً لمن لا يستطيعون رؤيتها، لذا ميّز مثلًا حقول النموذج الإلزامية بعلامة النجمة واللون الأحمر بدلًا من تميزها باللون الأحمر فقط. ملاحظة: تسمح نسبة التباين العالية لأيّ شخص يستخدِم هاتفًا ذكيًا أو جهازًا لوحيًا بقراءة الصفحات بصورة أفضل عندما يكون في بيئة مضاءة مثل ضوء الشمس. إخفاء المحتوى هناك العديد من الحالات التي يتطلب فيها التصميم المرئي عدم عرض كل المحتوى دفعةً واحدةً، فلدينا مثلًا ثلاث لوحات من المعلومات في مثال مربع المعلومات المبوب (اطّلع على شيفرته البرمجية)، لكننا نضعها فوق بعضها البعض ونوفّر تبويبات يمكن النقر عليها لإظهار محتواها، ويمكن الوصول إليها من خلال لوحة المفاتيح أو يمكنك استخدام مفتاح Tab و Enter/Return لتحديدها. لا يهتم مستخدِمو قارئات الشاشة بهذا الشيء، فهم سعداء بالمحتوى طالما أنّ ترتيب المصدر منطقي ويمكنهم الوصول إليه، كما يُنظَر إلى الموضع المطلق Absolute Positioning -كما هو مستخدَم في مثالنا- على أنه أحد أفضل آليات إخفاء محتوى التأثيرات المرئية، لأنه لا يمنع قارئات الشاشة من الوصول إلى هذا المحتوى، ولا يجب من ناحية أخرى استخدام الخاصيتين visibility:hidden و display:none لأنهما تخفيان المحتوى عن قارئات الشاشة ما لم يكن هناك سبب وجيه لإخفاء هذا المحتوى عنها. مشاكل سهولة الوصول المتعلقة بشيفرة جافاسكربت تمتلك شيفرة جافاسكربت النوع نفسه من المشاكل التي تواجهها في شيفرة CSS فيما يتعلق بسهولة الوصول، إذ يمكن أن تكون شيفرة جافاسكربت كارثيةً بالنسبة لسهولة الوصول إذا استخدِمت بطريقة سيئة أو مفرطة، وقد أشرنا سابقًا إلى بعض مشاكل سهولة الوصول المتعلقة بشيفرة جافاسكربت خاصةً في شيفرة HTML الدلالية، إذ يجب دائمًا استخدام شيفرة HTML دلالية مناسبة لتنفيذ الوظائف أينما كانت متاحةً مثل استخدام الروابط والأزرار حسب الحاجة، كما لا تُستخدَم عناصر <div> مع شيفرة جافاسكربت لتزييف الوظائف إذا كان ذلك ممكنًا، فهي عرضة للخطأ وتحتاج عملًا أكثر من استخدام الوظائف المجانية التي توفرها شيفرة HTML. الوظائف البسيطة يجب أن تعمل الوظائف البسيطة باستخدام شيفرة HTML فقط، إذ يجب استخدام شيفرة جافاسكربت لتحسين الوظائف فقط، وليس لبنائها بالكامل، وتشمل الاستخدامات الجيدة لشيفرة جافاسكربت ما يلي: توفير التحقق من صحة النموذج من طرف العميل الذي ينبّه المستخدِمين بالمشاكل المتعلقة بإدخالات النماذج بسرعة دون الحاجة إلى الانتظار حتى يتحقق الخادم من البيانات، فإذا لم يكن ذلك متاحًا، فسيبقى النموذج يعمل بنجاح، ولكن يمكن أن يكون التحقق من صحته أبطأ. توفير عناصر تحكم مخصصة لعناصر <video> التي يمكن أن يصل إليها مستخدِمو لوحة المفاتيح فقط، إذ لا يمكن الوصول إلى عناصر التحكم الافتراضية في المتصفح من خلال لوحة المفاتيح في معظم المتصفحات. يمكن أن تؤدي تطبيقات جافاسكربت الأكثر تعقيدًا إلى حدوث مشاكل في سهولة الوصول، لذا يجب أن تفعل ما بوسعك، فليس معقولًا مثلًا أن تتوقع إنشاء لعبة ثلاثية الأبعاد معقدة مكتوبة باستخدام واجهة WebGL يمكن أن يصل إليها شخص كفيف بنسبة 100%، ولكن يمكنك تنفيذ عناصر تحكم لوحة المفاتيح بحيث يمكن أن يستخدمها المستخدمِون الذين لا يستعملون الفأرة، وجعل نظام الألوان متباينًا بدرجة كافية ليتمكّن الأشخاص الذين يعانون من عمى الألوان من الوصول إليها. الوظائف المعقدة تُعَدّ التطبيقات المعقدة التي تتضمن عناصر تحكم معقدة في النماذج (مثل عنصر اختيار التاريخ Date Pickers) وتتضمن المحتوى الديناميكي الذي يُحدَّث بصورة متكررة ومتزايدة أحدَ المجالات الرئيسية التي تمثِّل مشكلة لسهولة الوصول. تمثِّل عناصر التحكم المعقدة غير الأصيلة في النموذج مشكلةً لأنها تتضمّن الكثير من عناصر <div> المتداخلة، ولا يعرف المتصفح ما يجب فعله بها افتراضيًا، فإذا أنشأت عناصر التحكم المعقدة بنفسك، فيجب التأكد من أنه يمكن الوصول إليها من خلال لوحة المفاتيح، وإذا استخدَمت إطار عمل خارجي، فراجع بعناية الخيارات المتاحة لمعرفة مدى إمكانية الوصول إليها قبل المتابعة، ويبدو إطار عمل Bootstrap على سبيل المثال جيدًا إلى حد ما لعملية سهولة الوصول بالرغم من بعض مشاكله التي تتعلق بتباين الألوان بصورة أساسية. يمكن أن يمثل المحتوى الديناميكي المُحدَّث بانتظام مشكلةً لأن مستخدِمي قارئات الشاشة يمكن أن يفوتهم ذلك خاصةً إذا جرى تحديثه بطريقة غير متوقعة، فإذا كان لديك تطبيق مؤلف من صفحة واحدة مع لوحة محتوى رئيسية يُحدَّث بانتظام باستخدام XMLHttpRequest أو Fetch، فيمكن أن يفوّت مستخدِمو قارئات الشاشة هذه التحديثات. WAI-ARIA إذا احتجتَ استخدام مثل هذه الوظائف المعقدة أو احتاجتها شيفرة HTML الدلالية القديمة، فعليك التفكير في استخدام مواصفات WAI-ARIA (تطبيقات الإنترنت الغنية سهلة الوصول Accessible Rich Internet Applications)، وهي مواصفات توفر دلالات Semantics بصورة سمات HTML جديدة لعناصر مثل عناصر التحكم المعقدة في النموذج ولوحات التحديث التي يمكن أن تفهمها معظم المتصفحات وقارئات الشاشة. يمكن التعامل مع عناصر النماذج المعقدة من خلال استخدام سمات ARIA مثل السمة roles لتحديد الدور الذي تلعبه العناصر المختلفة في عنصر واجهة المستخدِم (مثل هل هو تبويب أم لوحة تبويب؟)، والسمة aria-disabled التي تخبرنا فيما إذا كان عنصر التحكم معطلًا أم لا. يمكن التعامل مع مناطق المحتوى المحدّثة بانتظام من خلال استخدام السمة aria-live التي تحدد منطقة التحديث، وتشير قيمتها إلى مدى ضرورة أن يقرأ قارئ الشاشة هذا المحتوى، حيث يمكن أن تكون قيمتها إحدى القيم التالية: off: القيمة الافتراضية التي تمثّل أنه لا ينبغي الإعلان عن التحديثات. polite: يجب الإعلان عن التحديثات فقط إذا كان المستخدِم خاملًا. assertive: يجب الإعلان عن التحديثات للمستخدِم في أسرع وقت ممكن. rude: يجب الإعلان عن التحديثات مباشرةً حتى إذا تسبب ذلك في مقاطعة المستخدِم. إليك المثال التالي: <p><span id="LiveRegion1" aria-live="polite" aria-atomic="false"></span></p> يمكنك رؤية المثال العملي ARIA (Accessible Rich Internet Applications) Live Regions في موقع Freedom Scientific، حيث يجب أن تحدَّث الفقرة المُحدَّدة محتواها كل 10 ثوانٍ، ويجب على قارئ الشاشة قراءة ذلك للمستخدِم، ويمثل المثال ARIA Live Regions - Atomic مثالًا آخر مفيدًا. الخلاصة اطلعنا في هذا المقال على كيفية اختبار مشاكل سهولة الوصول الرئيسية التي يمكن أن تواجهها من أجل التغلب عليها، وسنلقي نظرةً في المقال التالي على أهم الأدوات اللازمة لتحقيق سهولة الوصول والتغلب على تلك المشاكل. ترجمة -وبتصرُّف- لقسم من المقال Handling common accessibility problems. اقرأ أيضًا المقال السابق: معالجة المشاكل الشائعة للتوافق مع المتصفحات في شيفرة جافاسكربت سهولة وصول جميع الزوار لمواقع وتطبيقات الويب سهولة الوصول في CSS شمولية التصميم: سهولة وصول جميع المستخدمين لصفحات موقع الويب
  16. سنطّلع في هذا المقال على مشاكل التوافق مع المتصفحات Cross-browser الشائعة التي ستواجهها في شيفرة جافاسكربت JavaScript وكيفية إصلاحها، إذ يتضمن ذلك معلومات حول استخدام أدوات تطوير المتصفح لتعقّب المشاكل وإصلاحها، واستخدام تعويض نقص دعم المتصفحات Polyfill والمكتبات لحل المشاكل، والحصول على ميزات جافاسكربت الحديثة التي تعمل في المتصفحات القديمة وغير ذلك. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML وCSS وجافاسكربت JavaScript، وأن تكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات. الهدف: أن تكون قادرًا على تشخيص مشاكل شيفرة جافاسكربت الشائعة على المتصفحات المختلفة واستخدام الأدوات والأساليب المناسبة لإصلاحها. مشاكل شيفرة جافاسكربت عانت لغة جافاسكربت في السابق من مشاكل التوافق مع المتصفحات المختلفة، إذ طُبِّقت سكربتات خيارات المتصفح الرئيسية في التسعينات (متصفح Internet Explorer وNetscape) باستخدام لغات مختلفة، فقد طُبِّق Netscape باستخدام لغة جافاسكربت، واحتوى المتصفح IE على لغة JScript مع وجود لغة VBScript على أساس خيارآخر، كما تُعَدّ كل من لغتَي جافاسكربت و JScript متوافقتين إلى حد ما، إذ يعتمد كلاهما على مواصفات لغة ECMAScript، ولكن طُبِّقت الأشياء بطرق متضاربة وغير متوافقة في أغلب الأحيان، مما تسبب في العديد من المشاكل للمطورين. استمرت مشاكل عدم التوافق في أوائل الألفينات، حيث كانت المتصفحات القديمة لا تزال قيد الاستخدام و بحاجة إلى الدعم، وهذا هو أحد الأسباب الرئيسية لظهور مكتبات مثل مكتبة jQuery لتجريد الاختلافات في تطبيقات المتصفح، بحيث يتعين على المطورين كتابة جزء بسيط من الشيفرة البرمجية فقط، وبالتالي ستعالج مكتبة jQuery -أو أي مكتبة أخرى تستخدِمها- بعد ذلك الاختلافات في الخلفية، لذلك لن تضطر إلى معالجة هذه الاختلافات. تحسنت الأمور بصورة ملحوظة منذ ذلك الحين، إذ تدعم المتصفحات الحديثة ميزات جافاسكربت القديمة، وقد تضاءلت متطلبات استخدام مثل هذه الشيفرة البرمجية نظرًا لتقليل متطلبات دعم المتصفحات القديمة بالرغم من مراعاة أنها لم تختفِ تمامًا، ويمكن ملاحظة معظم مشاكل جافاسكربت مع المتصفحات المختلفة حاليًا كما يلي: عندما تمنع شيفرة التعرف على المتصفح Browser Sniffing ذات الجودة الرديئة وشيفرة اكتشاف الميزات واستخدام بادئة المصنِّع المتصفحاتِ من تشغيل الشيفرة البرمجية التي يمكن استخدامها بطريقة جيدة. عندما يستخدِم المطورون ميزات جافاسكربت الجديدة وواجهات برمجة تطبيقات الويب الحديثة في شيفرتهم البرمجية ثم يكتشفون أنّ هذه الميزات لا تعمل في المتصفحات القديمة. إصلاح مشاكل شيفرة جافاسكربت العامة يجب أن تتأكد من عمل شيفرتك البرمجية بنجاح قبل الاستمرار في التركيز على مشاكل التوافق مع المتصفحات، فإذا لم تعرف مسبقًا أساسيات استكشاف أخطاء شيفرة جافاسكربت وإصلاحها، فيجب الاطلاع على مقال الزلات البرمجية وأخطاء في جافاسكربت قبل المضي قدمًا، وهناك عدد من مشاكل جافاسكربت الشائعة التي يجب أن تضعها في حساباتك مثل ما يلي: مشاكل الصياغة والمنطق الأساسية. التأكد من تعريف المتغيرات وغيرها ضمن النطاق الصحيح، وأنك لا تواجه تعارضًا بين العناصر المُصرَّح عنها في أماكن مختلفة. الارتباك عند استخدام this من حيث النطاق الذي يُطبَّق فيه، وبالتالي الارتباك حول ما إذا كانت قيمته هي ما قصدته، كما يمكنك الاطلاع على أمثلة مثل هذا المثال الذي يُظهر نمطًا نموذجيًا لحفظ نطاق this في متغير منفصل، ثم استخدام هذا المتغير في الدوال المتداخلة حتى تتمكن من التأكد من أنك تطبّق الدالة على نطاق this الصحيح. الاستخدام غير الصحيح للدوال ضمن الحلقات التي تتكرر باستخدام متغير عام أو استخدام نطاقه بطريقة خاطئة، إذ تُنفَّذ الحلقة عبر 10 تكرارات باستخدام متغير معرَّف باستخدام var في المثال bad-for-loop.html (اطّلع على شيفرته البرمجية) في كل مرة ننشئ فيها فقرة ونضيف معالج الحدث onclick إليها، إذ نريد أن تعرض كل فقرة رسالة تنبيه تحتوي على رقمها (قيمة i عند إنشائه) عند النقر عليها، ولكن تعطي جميعها القيمة 11 للمتغير i لأن الحلقة for تنفّذ جميع تكراراتها قبل استدعاء الدوال المتداخلة. ملاحظة: الحل الأسهل هو التصريح عن متغير التكرار باستخدام let بدلًا من var، بحيث تكون قيمة i المرتبطة بالدالة فريدةً لكل تكرار، لكن لسوء الحظ لا تعمل هذه الطريقة بصورة صحيحة في المتصفح IE11، لذلك لم نستخدِمها في حلقة for الجيدة. إذا أردت نجاح هذه الطريقة، فيمكنك تعريف دالة منفصلة لإضافة معالج الحدث واستدعاؤها في كل تكرار وتمرير قيمتَي para و i الحاليتين إليها في كل مرة، ويمكنك الاطلاع على المثال good-for-loop.html لمعرفة الإصدار الذي يعمل بنجاح، والاطلاع على شيفرته البرمجية. التأكد من إعادة العمليات غير المتزامنة قبل محاولة استخدام القيم التي تعيدها، إذ يتحقق مثال Ajax من اكتمال الطلب وإعادة الاستجابة قبل محاولة استخدامها لأي شيء آخر، وقد أصبح التعامل مع هذا النوع من العمليات أسهل من خلال استخدام الوعود Promises في لغة جافاسكربت. منقحات الصياغة Linters يمكنك ضمان جودة أفضل وشيفرة جافاسكربت أقل عرضة للأخطاء باستخدام منقّح الصياغة Linter كما هو الحال مع شيفرة HTML و CSS، حيث يؤشر هذا المنقّح إلى الأخطاء ويمكنه إعطاء التحذيرات حول الممارسات السيئة وغير ذلك، ويمكن تخصيصه ليكون أكثر أو أقل صرامةً في الإبلاغ عن الأخطاء أو التحذيرات، ومن منقّحات صياغة شيفرة JavaScript/ECMAScript التي نوصي بها JSHint و ESLint التي يمكن استخدامها بعدة طرق، وسنشرح بعضها بالتفصيل لاحقًا. تنقيح الصياغة على الإنترنت توفر صفحة JSHint الرئيسية رابطًا عبر الإنترنت يسمح لك بإدخال شيفرة جافاسكربت على اليسار ويوفِّر الخرج على اليمين بما في ذلك المقاييس والتحذيرات والأخطاء. إضافات محرر الشيفرة البرمجية لا يُعَدّ اضطرارك إلى نسخ شيفرتك ولصقها في صفحة ويب للتحقق من صحتها عدة مرات أمرًا ملائمًا، فما تريده حقًا هو منقّح صياغة يتناسب مع سير عملك بأقل قدر من المتاعب، إذ تحتوي العديد من محررات الشيفرات على إضافات لتنقيح الشيفرة البرمجية مثل المحرر Atom من GitHub الذي يتضمن الإضافة JSHint، ويمكن تثبيت الإضافة كما يلي: ثبّت Atom إذا لم يكن لديك إصدار حديث مثبَّت مسبقًا. انتقل إلى مربع حوار تفضيلات Atom من قائمة Atom ثم التفضيلات Preferences على نظام Mac أو من قائمة ملف File ثم التفضيلات Preferences في نظامَي التشغيل Windows و Linux، ثم اختر خيار التثبيت Install في القائمة اليسرى. اكتب "jslint" في حقل نص البحث عن الحزم Search Packages واضغط على الزر Enter/Return للبحث عن الحزم المتعلقة بتنقيح الصياغة. يجب عليك رؤية حزمة بالاسم lint في أعلى القائمة، وبالتالي ثبّتها باستخدام زر التثبيت Install، إذ تعتمد منقّحات الصياغة الأخرى عليها في العمل، ثم ثبّت الإضافة linter-jshint. حمّل ملف جافاسكربت بعد انتهاء تثبيت الحزم، حيث سترى أي مشاكل مميزة باللون الأخضر (للتحذيرات) مع وجود الدوائر الحمراء بجوار أرقام الأسطر (للأخطاء) ولوحة منفصلة في الأسفل توفر أرقام الأسطر ورسائل الخطأ والقيم المقترحة أو الإصلاحات الأخرى في بعض الأحيان. تتوفر حزم تنقيح مماثلة في المحررات المشهورة الأخرى، ويمكنك مراجعة قسم "إضافات محررات النصوص وبيئات التطوير IDE" في صفحة تثبيت JSHint. استخدامات أخرى هناك طرق أخرى لاستخدام هذه المنقّحات، حيث يمكنك الاطلاع على صفحات تثبيت JSHint و ESLint، كما يمكنك تثبيتها باستخدام واجهة سطر الأوامر Command Line Interface -أو CLI اختصارًا- عبر مدير الحزم Node Package Manager -أو اختصارًا npm، ولكن يجب تثبيت NodeJS أولًا. يثبّت الأمر التالي المنقّح JSHint: npm install -g jshint يمكنك بعد ذلك الإشارة إلى هذه الأدوات في ملفات جافاسكربت التي تريد تنقيحها كما يلي: يمكنك استخدام هذه الأدوات مع أداة تشغيل/بناء المهام مثل Gulp أو Webpack لتنقيح أخطاء شيفرة جافاسكربت تلقائيًا أثناء التطوير، إذ تدعم الأداة Grunt المنقّح JSHint الذي تدعمه أدوات أخرى مثل محمِّل JSHint الخاص بالأداة Webpack. ملاحظة: يحتاج المنقّح ESLint إعدادًا وضبطًا أكبر قليلًا من المنقّح JSHint، ولكنه أقوى. أدوات المطور في المتصفحات تحتوي أدوات المطور في المتصفحات على العديد من الميزات المفيدة للمساعدة في تصحيح أخطاء شيفرة جافاسكربت، حيث ستعطي أولًا طرفية جافاسكربت تقريرًا بأخطاء شيفرتك البرمجية. أنشئ نسخةً محليةً من المثال broken-ajax.html واطلع على شيفرته البرمجية، إذ سترى في الطرفية رسالة الخطأ "Uncaught TypeError: can't access property "length", heroes is undefined"، ورقم السطر المشار إليه هو 49، والذي يمثِّل قسم الشيفرة التالي: function showHeroes(jsonObj) { let heroes = jsonObj['members']; for (const hero of heroes) { // … } // … } لذا ستفشل الشيفرة البرمجية بمجرد محاولة الوصول إلى خاصية jsonObj التي يُفترَض أن تكون كائن JSON، وتُجلَب من ملف ‎.json خارجي باستخدام استدعاء XMLHttpRequest التالي: let requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json'; let request = new XMLHttpRequest(); request.open('GET', requestURL); request.send(); let superHeroes = request.response; populateHeader(superHeroes); showHeroes(superHeroes); ولكن سيفشل هذا الاستدعاء. واجهة برمجة تطبيقات الطرفية Console API يُحتمَل أننا نعرف ما هو الخطأ في هذه الشيفرة، ولكن لنتعرّف أكثر على كيفية تحرّي هذا الأمر، فهناك واجهة برمجة تطبيقات طرفية تسمح لشيفرة جافاسكربت البرمجية بالتفاعل مع طرفية جافاسكربت الخاصة بالمتصفح التي تحتوي على عدد من الميزات المتاحة، ولكن الميزة الرئيسية التي ستستخدِمها في أغلب الأحيان هي console.log()‎ والتي تطبع رسالةً مخصصةً في الطرفية. حاول إدخال السطر التالي بعد السطر رقم 31 مباشرةً: console.log('Response value: ', superHeroes); حدّث الصفحة في المتصفح وستحصل على الخرج "Response value:‎" في الطرفية، بالإضافة إلى رسالة الخطأ نفسها التي رأيناها سابقًا. يُظهِر خرج التابع console.log()‎ أنّ الكائن superHeroes لا يبدو أنه يحتوي على أيّ شيء، وهناك مشكلة شائعة جدًا في الطلبات غير المتزامنة عندما تحاول تطبيق شيء ما مع الكائن response قبل إعادته من الشبكة، إذًا لنصلح هذه المشكلة من خلال تشغيل الشيفرة البرمجية بمجرد إطلاق الحدث load، لذا أزل سطر console.log()‎ وعدّل كتلة الشيفرة البرمجية التالية: let superHeroes = request.response; populateHeader(superHeroes); showHeroes(superHeroes); إلى ما يلي: request.onload = function() { let superHeroes = request.response; populateHeader(superHeroes); showHeroes(superHeroes); } إذا لم يعمل شيء ما في أيّ وقت ولم تكن القيمة كما يجب أن تكون في الشيفرة البرمجية، فيمكنك استخدام التابع console.log()‎ لطباعتها ومعرفة ما يحدث. استخدام مصحح أخطاء جافاسكربت لا يزال الخطأ نفسه موجودًا ولم تختفِ المشكلة لسوء الحظ، فلنتحقق من ذلك باستخدام ميزة أكثر تعقيدًا لأدوات مطور المتصفح وهي مصحح أخطاء جافاسكربت JavaScript debugger كما يطلق عليه في Firefox. ملاحظة: تتوفر أدوات مماثلة في متصفحات أخرى مثل تبويب المصادر Sources في Chrome و Debugger في Safari (اطلّع على أدوات تطوير الويب في Safari) وغيرها. يبدو تبويب Debugger في Firefox كما يلي: يمكنك تحديد السكربت الذي تريد تصحيحه على اليسار (لدينا سكربت واحد فقط في حالتنا). تعرض اللوحة المركزية الشيفرة في السكربت المحدَّد. تعرض اللوحة اليمنى تفاصيل مفيدة تتعلق بالبيئة الحالية مثل نقاط الإيقاف Breakpoints ومكدس الاستدعاءات Callstack والنطاقات Scopes النشطة حاليًا. الميزة الرئيسية لهذه الأدوات هي القدرة على إضافة نقاط توقف إلى الشيفرة البرمجية، وهي النقاط التي يتوقف فيها تنفيذ الشيفرة، ويمكنك فحص البيئة في حالتها الحالية ومعرفة ما يجري عند هذه النقطة. يُعطَى خطأ في السطر رقم 51، لذا انقر على هذا السطر في اللوحة المركزية لإضافة نقطة توقف إليه، حيث سترى سهمًا أزرق يظهر عليه، ثم حدّث الصفحة باستخدام الاختصار "Cmd/Ctrl + R"، حيث سيتوقف المتصفح مؤقتًا عن تنفيذ الشيفرة في السطر رقم 51، وستُحدَّث اللوحة اليمنى لإظهار بعض المعلومات المفيدة. سترى تفاصيل نقطة التوقف التي ضبطتها ضمن نقاط التوقف Breakpoints. سترى بعض المدخلات ضمن مكدس الاستدعاءات Call Stack الذي هو قائمة بسلسلة الدوال المستدعاة التي تؤدي إلى استدعاء الدالة الحالية، فلدينا في الأعلى الدالة showHeroes()‎ التي نستخدمها حاليًا، ثم لدينا الدالة onload التي تخزن دالة معالج الأحداث التي تحتوي على استدعاء الدالة showHeroes()‎. سترى ضمن النطاقات Scopes النطاق النشط حاليًا للدالة التي نبحث عنها. لدينا حاليًا ثلاثة نطاقات فقط هي showHeroes وblock وWindow (النطاق العام)، كما يمكن توسيع كل نطاق لإظهار قيم المتغيرات ضمن النطاق عند توقف تنفيذ الشيفرة البرمجية. يمكننا العثور على بعض المعلومات المفيدة مثل: يمكنك أن ترى عند توسيع نطاق showHeroes أنّ المتغير heroes غير معرَّف undefined، مما يشير إلى أن الوصول إلى الخاصية members في الكائن jsonObj (السطر الأول من الدالة) لم ينجح. يمكنك أن ترى أنّ المتغير jsonObj يخزن سلسلةً نصيةً وليس كائن JSON. انقر على نطاق onload في قسم مكدس الاستدعاءات Call Stack، مما يؤدي إلى تحديث العرض لإظهار الدالة request.onload في اللوحة المركزية ونطاقاتها في قسم النطاقات Scopes. إذا وسّعت نطاق onload، فسترى أنّ المتغير superHeroes هو سلسلة نصية أيضًا وليس كائنًا، إذ يعيد استدعاء XMLHttpRequest سلسلة JSON نصية وليس كائن JSON. حاول حل هذه المشكلة بنفسك، إذ يمكنك إما إخبار كائن XMLHttpRequest صراحةً بإعادة تنسيق JSON أو تحويل النص المُعاد إلى JSON بعد وصول الاستجابة، فإذا واجهتك مشكلة، فيمكنك الرجوع إلى المثال fixed-ajax.html. ملاحظة: يحتوي التبويب debugger على العديد من الميزات المفيدة الأخرى التي لم نناقشها هنا مثل نقاط التوقف الشرطية وتعابير المراقبة. مشاكل الأداء بما أن تطبيقاتك تصبح أكثر تعقيدًا وتستخدم مزيدًا من شيفرة جافاسكربت، فيمكن أن تبدأ في مواجهة مشاكل في الأداء خاصة عند عرض التطبيقات على أجهزة أبطأ، ويُعَدّ الأداء موضوعًا ضخمًا، لذلك لن نغطيه في هذا المقال بالتفصيل، ولكن إليك بعض النصائح السريعة: يمكنك تجنب تحميل المزيد من شيفرة جافاسكربت أكثر مما تحتاج إليه من خلال تجميع سكربتاتك في ملف واحد باستخدام حل ما مثل Browserify، كما يُعَدّ تقليل عدد طلبات HTTP مفيدًا جدًا للأداء. صغّر ملفاتك قبل تحميلها على خادم الإنتاج، إذ يؤدي التصغير إلى ضغط الشيفرة البرمجية بأكملها في سطر واحد ضخم، مما يجعلها تشغل حجم ملف أقل بكثير، فيمكن أن تبدو الشيفرة غير مقروءة بعد التصغير، ولكن لا داعي لقراءتها عند الانتهاء منها، كما يُفضَّل تطبيق التصغير باستخدام أداة تصغير مثل Uglify، وهناك أدوات عبر الإنترنت مثل JSCompress.com. تأكّد عند استخدام واجهات برمجة التطبيقات API من إيقاف تشغيل ميزات واجهة برمجة التطبيقات عند عدم استخدامها، إذ يمكن أن تكون بعض استدعاءات API مكلفةً وتؤثر على قوة المعالجة، فعند عرض بث فيديو مثلًا، تأكّد من إيقاف تشغيله عند عدم رؤيته، وتأكّد عند تتبّع موقع الجهاز باستخدام استدعاءات تحديد الموقع الجغرافي المتكررة من إيقاف تشغيله عندما يتوقف المستخدم عن استخدامه. يمكن أن تؤثر التحريكات animations على الأداء كثيرًا لأنه مكلف حقًا، إذ توفِّر الكثير من مكتبات جافاسكربت إمكانات خاصةً بالتحريك ومبرمَجةً باستخدام لغة جافاسكربت، ولكن يُعَدّ إجراء التحريك باستخدام ميزات المتصفح الأصيلة Native مثل CSS Animations أو واجهة برمجة تطبيقات Web Animations أكثر فعاليةً من حيث التكلفة. مشاكل شيفرة جافاسكربت للتوافق مع المتصفحات سنلقي نظرةً في هذا القسم على بعض مشاكل شيفرة جافاسكربت الأكثر شيوعًا للتوافق مع المتصفحات، حيث سنقسمه إلى ما يلي: استخدام ميزات لغة جافاسكربت الأساسية الحديثة. استخدام ميزات واجهة برمجة تطبيقات الويب Web API الحديثة. استخدام شيفرة التعرف على المتصفح السيئة. مشاكل الأداء. استخدام ميزات JavaScript/API الحديثة شرحنا في المقال السابق بعض الطرق التي يمكن من خلالها معالجة أخطاء HTML و CSS والميزات غير المعروفة بسبب طبيعة هذه اللغات، ولغة جافاسكربت ليست متساهلةً مثل لغتَي HTML وCSS، ولكن إذا واجه محرك جافاسكربت أخطاءً أو صياغةً غير معروفة، فسيؤدي ذلك إلى ظهور أخطاء في كثير من الأحيان. هناك عدد من ميزات لغة جافاسكربت الحديثة المُعرَّفة في الإصدارات الحديثة من المواصفات والتي لن تعمل في المتصفحات القديمة، فبعضها هي عبارة عن صيغة أبسط أي طريقة أسهل وألطف لكتابة ما يمكنك فعله باستخدام الميزات الموجودة مسبقًا، وبعضها يقدِّم إمكانات جديدة مثل: تُعَدّ الوعود Promises ميزةً جديدةً رائعةً لأداء العمليات غير المتزامنة وللتأكد من اكتمال هذه العمليات قبل استخدام الشيفرة التي تعتمد على نتائج هذه العمليات لشيء آخر، إذ تستخدِم واجهة Fetch API (المكافئ الحديث للكائن XMLHTTPRequest) على سبيل المثال وعودًا لجلب الموارد عبر الشبكة والتأكّد من إعادة الاستجابة قبل استخدامها مثل عرض صورة ضمن العنصر <img>، كما لم تُدعَم الوعود في المتصفح IE على الإطلاق ولكنها مدعومة على جميع المتصفحات الحديثة. توفِّر الدوال السهمية Arrow Functions صيغةً أقصر وأكثر ملاءمةً لكتابة دوال مجهولة الاسم، ويمكنك مراجعة المثال arrow-function.html، والاطلاع على شيفرته البرمجية، كما تُدعَم الدوال السهمية على جميع المتصفحات الحديثة باستثناء المتصفح IE. يؤدي التصريح عن الوضع الصارم Strict Mode في أعلى شيفرة جافاسكربت إلى تحليلها باستخدام مجموعة قواعد أكثر صرامةً، مما يعني ظهور مزيد من التحذيرات والأخطاء، وستُرفَض بعض الأشياء التي كانت مقبولةً سابقًا، ويُعَدّ استخدام الوضع الصارم فكرةً جيدةً لأنه ينشئ شيفرةً أفضل وأكثر كفاءةً، ولكن تدعمه المتصفحات دعمًا محدودًا أو غير مكتمل. تسمح مصفوفات النوع Typed Arrays لشيفرة جافاسكربت بالوصول إلى البيانات الثنائية الخام ومعالجتها، وهو أمر ضروري لأن واجهات برمجة تطبيقات المتصفح مثلًا تبدأ في معالجة تدفقات بيانات الفيديو والصوت الخام، إذ تتوفر هذه المصفوفات في الإصدار IE10 وما فوق وجميع المتصفحات الحديثة. هناك أيضًا العديد من واجهات برمجة التطبيقات الجديدة التي تظهر في المتصفحات الحديثة، والتي لا تعمل في المتصفحات القديمة مثل: واجهة IndexedDB API وواجهة Web Storage API وغيرها لتخزين بيانات موقع الويب على طرف العميل. واجهة Web Workers API لتشغيل شيفرة جافاسكربت في خيط Thread منفصل، مما يساعد على تحسين الأداء. واجهة WebGL API للرسومات ثلاثية الأبعاد الحقيقية. واجهة Web Audio API لمعالجة الصوت المتقدمة. واجهة WebRTC API لاتصال الفيديو/الصوت متعدد الأشخاص في الوقت الحقيقي مثل مؤتمرات الفيديو. واجهة WebVR API لهندسة تجارب الواقع الافتراضي في المتصفح مثل التحكم في عرض ثلاثي الأبعاد مع إدخال البيانات من أجهزة الواقع الافتراضي VR. هناك بعض الاستراتيجيات لمعالجة حالات عدم التوافق مع المتصفحات المتعلقة بدعم الميزات، فلنتعرف على أكثرها شيوعًا. ملاحظة: لا توجد هذه الاستراتيجيات بمعزل عن بعضها البعض، إذ يمكنك دمجها حسب الحاجة، فيمكنك مثلًا استخدام استراتيجية اكتشاف الميزات لتحديد ما إذا كانت الميزة مدعومةً؛ فإذا لم تكن كذلك، فيمكنك تشغيل الشيفرة البرمجية لتحميل تعويض نقص دعم المتصفحات Polyfill أو لتحميل مكتبة لمعالجة نقص الدعم. اكتشاف دعم المتصفحات للميزات تكمن الفكرة وراء اكتشاف الميزات في أنه يمكنك إجراء اختبار لتحديد ما إذا كانت ميزة جافاسكربت مدعومةً في المتصفح الحالي، ثم تشغيل الشيفرة البرمجية شرطيًا لتوفير تجربة مقبولة في كل من المتصفحات التي تدعم الميزة والمتصفحات التي لا تدعمها، إذ تملك واجهة Geolocation API مثلًا والتي تعرض بيانات الموقع المتاحة للجهاز الذي يعمل عليه متصفح الويب، نقطةَ دخول رئيسية لاستخدامها وهي الخاصية geolocation التي يوفرها الكائن العام Navigator، لذا يمكنك اكتشاف ما إذا كان المتصفح يدعم الخاصية geolocation أم لا باستخدام ما يلي: if ("geolocation" in navigator) { navigator.geolocation.getCurrentPosition(function(position) => { // اعرض الموقع على الخريطة باستخدام‫ واجهة Google Maps API مثلًا }); } else { // اجعل المستخدِم يختار من بين الخرائط الساكنة بدلًا من ذلك } كما يمكنك تطبيق مثل هذا الاختبار على ميزة CSS مثل اختبار وجود الخاصية element.style مثل paragraph.style.transform !== undefined، ولكن يُفضَّل بالنسبة لكل من CSS وجافاسكربت استخدام مكتبة لاكتشاف الميزات مُنشأَة مسبقًا بدلًا من كتابة مكتبتك الخاصة، وتُعَدّ مكتبة مودرنيزر Modernizr هي المعيار الصناعي لاختبارات اكتشاف الميزات. لا تخلط بين اكتشاف الميزات والتعرف على المتصفح Browser Sniffing (اكتشاف المتصفح المحدد الذي يصل إلى الموقع) الذي يُعَدّ ممارسةً سيئةً يجب عدم تطبيقها، ولاحظ أنه توجد بعض الميزات التي لا يمكن اكتشافها. ملاحظة: سنغطي اكتشاف الميزات بمزيد من التفصيل في مقال لاحق. المكتبات تُعَدّ مكتبات جافاسكربت وحدات شيفرة خارجية يمكنك ربطها بصفحتك، مما يوفر لك مجموعةً كبيرةً من الوظائف الجاهزة التي يمكن استخدامها مباشرةً، وبالتالي ستوفر الكثير من الوقت في هذه العملية، كما يمكن أن يكون سبب ظهور الكثير من مكتبات جافاسكربت أنّ مطورها قد أراد كتابة مجموعة من الدوال المساعدة لتوفير الوقت عند كتابة المشاريع المستقبلية ثم قرر إطلاقها لأن المطورين الآخرين قد وجدوها مفيدةً. تمتلك مكتبات جافاسكربت عدة أنواع رئيسية، ويمكن أن تخدم بعض المكتبات أكثر من غرض واحد من الأغراض التالية: المكتبات المساعدة Utility Libraries: توفِّر مجموعةً من الدوال التي تجعل المهام العادية وإدارتها أسهل، إذ توفر مكتبة jQuery مثلًا محدّدات كاملة الميزات ومكتبات معالجة نموذج DOM للسماح بتحديد نوع محدد CSS للعناصر في شيفرة جافاسكربت وتسهيل بناء نموذج DOM، كما ليس مهمًا الآن أن تكون لدينا ميزات حديثة مثل توابع Document.querySelector()‎ أو Document.querySelectorAll()‎ أو Node المتاحة على المتصفحات المختلفة، ولكن لا يزال ممكنًا أن تكون هذه الميزات مفيدةً عندما تحتاج المتصفحات القديمة إلى الدعم. مكتبات الملاءمة Convenience Libraries: تسهّل تطبيق الأشياء الصعبة، إذ تُعَدّ واجهة WebGL API معقدةً وصعبة الاستخدام عند كتابتها مباشرةً، لذا فإن مكتبة Three.js (وغيرها) مبنية على WebGL وتوفر واجهة برمجة تطبيقات أسهل كثيرًا لإنشاء كائنات وإضاءة وخامات ثلاثية الأبعاد، كما تُعَدّ واجهة Service Worker API معقدةً للغاية في الاستخدام، لذلك بدأت مكتبات الشيفرات في الظهور لتسهيل تطبيق حالات استخدامات واجهة Service Worker الشائعة. مكتبات التأثيرات Effects Libraries: صُمِّمت هذه المكتبات للسماح بإضافة تأثيرات خاصة بسهولة إلى مواقع الويب، وقد كانت هذه المكتبات أكثر فائدةً عندما كانت "DHTML" كلمةً شائعةً، حيث تضمَّن تطبيق تأثير ما الكثيرَ من شيفرة جافاسكربت المعقدة، ولكن المتصفحات حاليًا لديها الكثير من ميزات CSS وواجهات برمجة التطبيقات المبنية مسبقًا لتطبيق التأثيرات بسهولة أكبر. مكتبات واجهة المستخدِم UI Libraries: توفر توابعًا لتطبيق ميزات واجهة المستخدِم المعقدة التي يمكن أن يكون تطبيقها على المتصفحات المختلفة أمرًا صعبًا مثل Foundation و Bootstrap و Material-UI (تُعَدّ الأخيرة مجموعةً من المكونات للاستخدام مع إطار عمل React)، كما تُستخدَم هذه المكتبات بوصفها أساسًا لتخطيط الموقع بأكمل وليس لمجرد ميزة واجهة مستخدِم واحدة. مكتبات التوحيد Normalization Libraries: تمنحك صياغةً بسيطةً تسمح لك بإكمال مهمة بسهولة دون الحاجة إلى القلق بشأن الاختلافات بين المتصفحات، إذ ستعالِج هذه المكتبة واجهات برمجة التطبيقات المناسبة في الخلفية، لذا ستعمل الوظيفة مهما كان المتصف، كما تُعَدّ مكتبة LocalForage مثلًا مكتبة لتخزين البيانات من طرف العميل، حيث توفر صياغةً بسيطةً لتخزين البيانات واستردادها، وتستخدِم في الخلفية أفضل واجهة برمجة تطبيقات متاحة للمتصفح لتخزين البيانات سواءً كانت واجهة IndexedDB أو واجهة Web Storage أو حتى واجهة WebSQL (المهمَلة حاليًا، ولكنها لا تزال مدعومة في بعض الإصدارات القديمة من Safari و IE)، كما تُعَدّ مكتبة jQuery مثالًا آخر عن مكتبات التوحيد. تأكّد عند اختيار مكتبة لاستخدامها من أنها تعمل على مجموعة المتصفحات التي تريد دعمها واختبر تطبيقها بدقة وتأكّد من أن المكتبة منتشرة ومدعومة جيدًا ومن غير المحتمل أن تصبح قديمةً الأسبوع المقبل، وتحدَّث إلى المطورين الآخرين لمعرفة ما يوصون به واطلع على مقدار النشاط وعدد المساهمين في المكتبة على GitHub أو في أيّ مكان آخر مُخزَّنة فيه. تُستخدَم المكتبة من خلال تنزيل ملفات المكتبة (ملفات جافاسكربت وبعض ملفات CSS أو الاعتماديات الأخرى) وربطها بصفحتك -باستخدام العنصر <script> مثلًا- بالرغم من وجود العديد من خيارات الاستخدام الأخرى لمثل هذه المكتبات مثل تثبيتها بوصفها مكونات Bower أو تضمينها بوصفها اعتماديات باستخدام أداة تحزيم الوحدات Webpack، كما يجب عليك قراءة صفحات التثبيت الخاصة بهذه المكتبات لمزيد من المعلومات. ملاحظة: ستصادف أطر عمل جافاسكربت في عملك على الويب مثل إطار عمل Ember وAngular، كما يمكن أن تكون المكتبات قابلة للاستخدام في حل المشاكل الفردية وإطلاقها في المواقع القائمة مسبقًا، ولكن تميل أطر العمل إلى أن تكون أكثر انسجامًا مع الحلول الكاملة لتطوير تطبيقات الويب المعقدة. تعويض نقص دعم المتصفحات Polyfills يتكون تعويض نقص دعم المتصفحات Polyfill من ملفات جافاسكربت خارجية يمكنك وضعها في مشروعك، ولكنه يختلف عن المكتبات، إذ تحسّن المكتبات الوظائف الحالية وتسهّل الأمور، ولكن يوفر تعويض نقص دعم المتصفحات وظائفًا غير موجودة على الإطلاق. يستخدِم تعويض نقص دعم المتصفحات لغة جافاسكربت أو تقنيات أخرى لبناء دعم لميزة لا يدعمها المتصفح محليًا، حيث يمكنك تعويض نقص دعم المتصفحات مثل es6-promise لإنشاء وعود تعمل في المتصفحات حيث تكون غير مدعومة بطريقة أصيلة، ولا تنسى أنه يجب عليك البحث عن تعويض دعم المتصفحات والتأكد من عمله وقابلية صيانته قبل استخدامه. سنستخدِم في المثال التالي Fetch Polyfill لتقديم الدعم لواجهة Fetch API في المتصفحات القديمة، وسنستخدِم es6-promise لأن واجهة Fetch تستخدِم الوعود كثيرًا، وبالتالي ستبقى المتصفحات التي لا تدعمها في مشكلة. أنشئ أولًا نسخةً محليةً من المثال fetch-polyfill.html ومن صورة الزهور في مجلد جديد، إذ سنكتب شيفرةً لجلب صورة الزهور وعرضها في الصفحة. احفظ بعد ذلك نسخةً من شيفرة Fetch Polyfill في مجلد ملف HTML نفسه. طبّق سكربتات تعويض دعم المتصفحات Polyfill على الصفحة باستخدام الشيفرة التالية التي يجب أن تضعها قبل العنصر <script> الموجود مسبقًا بحيث تكون متاحةً على الصفحة عندما نبدأ باستخدام واجهة Fetch، كما أننا نحمّل تعويض دعم الوعود في المتصفحات Promise Polyfill من CDN بحيث يدعم المتصفح IE11 الوعود التي تتطلبها عملية الجلب: <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script> <script src="fetch.js"></script> أضِف الشيفرة التالية ضمن العنصر <script> الأصلي: const myImage = document.querySelector('.my-image'); fetch('flowers.jpg').then((response) => { response.blob().then((myBlob) => { const objectURL = URL.createObjectURL(myBlob); myImage.src = objectURL; }); }); إذا حمّلت Fetch Polyfill الآن في متصفح لا يدعم Fetch مثل المتصفح IE، فيجب أن ترى صورة الزهور. ملاحظة: يمكنك العثور على نسخة هذا المثال النهائية على fetch-polyfill-finished.html كما يمكنك الاطلاع على شيفرته البرمجية. ملاحظة: هناك العديد من الطرق للاستفادة من تعويض نقص دعم المتصفحات Polyfill المختلفة، ولا تنسى الاطلاع على توثيق كل منها قبل استخدامها. لا بد أنك تتساءل عن سبب تحميل شيفرة تعويض نقص دعم المتصفحات إذا لم تكن بحاجتها، حسنًا، بما أنّ الموقع يصبح أكثر تعقيدًا وسيستخدِم مزيدًا من المكتبات وتعويضات نقص دعم المتصفحات Polyfills وغير ذلك، فلا بد أنك ستحمّل كثيرًا من الشيفرات البرمجية الإضافية، والتي يمكن أن تؤثر على الأداء خاصةً على الأجهزة ذات الإمكانات الأقل، لذا من المنطقي تحميل الملفات حسب الحاجة فقط، لكن يتطلب ذلك بعض الإعدادات الإضافية في شيفرة جافاسكربت، فأنت بحاجة إلى اختبار اكتشاف الميزات الذي يكتشف ما إذا كان المتصفح يدعم الميزة التي تحاول استخدامها كما يلي: if (browserSupportsAllFeatures()) { main(); } else { loadScript('polyfills.js', main); } function main(err) { // ضع شيفرة التطبيق الفعلية هنا } سنشغّل أولًا شرطًا يتحقق مما إذا كانت الدالة browserSupportsAllFeatures()‎ تعيد القيمة true، فإذا كان الأمر كذلك، فسنشغّل الدالة main()‎ التي ستحتوي على جميع شيفرات تطبيقك، وتبدو الدالة browserSupportsAllFeatures()‎ كما يلي: function browserSupportsAllFeatures() { return window.Promise && window.fetch; } اختبرنا هنا ما إذا كان الكائن Promise والدالة fetch()‎ موجودان في المتصفح، فإذا كان الشرطان محققين، فستعيد الدالة القيمة true. وإذا أعادت الدالة القيمة false، فسنشغّل الشيفرة في الجزء الثاني من الشرط، وهذا يؤدي إلى تشغيل دالة تسمى loadScript()‎ التي تحمّل تعويض نقص دعم المتصفحات Polyfills في الصفحة، ثم تُشغَّل الدالة main()‎ بعد انتهاء التحميل، إذ تبدو الدالة loadScript()‎ كما يلي: function loadScript(src, done) { const js = document.createElement('script'); js.src = src; js.onload = function() { done(); }; js.onerror = function() { done(new Error('Failed to load script ' + src)); }; document.head.appendChild(js); } تنشئ الدالة عنصر <script> جديد ثم تضبط سمته src على المسار الذي حددناه على أنه أول وسيط وهو 'polyfills.js' عندما استدعينا الدالة loadScript()‎ سابقًا، وسنشغّل الدالة التي حددناها على أنها الوسيط الثاني (main()‎) عند تحميل السكربت، فإذا حدث خطأ أثناء تحميل السكربت، فسنستدعي الدالة أيضًا، ولكن مع وجود خطأ مخصص يمكننا استرداده للمساعدة في تصحيح المشكلة في حال حدوثها. لاحظ أنّ الملف polyfills.js هو نوعان من تعويض نقص دعم المتصفحات Polyfills اللذين نستخدمهما معًا في ملف واحد، حيث طبّقنا ذلك يدويًا، ولكن هناك حلول أذكى تنشئ حزمًا تلقائيًا مثل أداة Browserify، ويُعَدّ تجميع ملفات JS في ملف واحد أمرًا جيدًا، مما يؤدي إلى تقليل عدد طلبات HTTP التي تحتاجها لتحسين أداء موقعك. يمكنك رؤية هذه الشيفرة قيد التنفيذ في المثال fetch-polyfill-only-when-needed.html والاطلاع على شيفرته البرمجية التي كتبها فيليب والتون Philip Walton. ملاحظة: هناك بعض الخيارات الخارجية التي يجب مراعاتها مثل Polyfill.io، وهي مكتبة meta-polyfill التي ستنظر في إمكانات كل متصفح وتطبق تعويض Polyfill حسب الحاجة اعتمادًا على واجهات برمجة التطبيقات وميزات جافاسكربت التي تستخدِمها في شيفرتك البرمجية. تحويل شيفرة جافاسكربت JavaScript transpiling هناك خيار آخر أصبح شائعًا للأشخاص الذين يرغبون في استخدام ميزات جافاسكربت الحديثة، وهو تحويل الشيفرة البرمجية التي تستخدِم ميزات ECMAScript 6/ECMAScript 2015 إلى إصدار يعمل في المتصفحات القديمة. ملاحظة: تسمَّى هذه العملية بالتحويل transpiling لأنها لا تصرِّف compiling الشيفرة البرمجية إلى مستوى أدنى لتشغيلها على حاسوب كما تفعل مع شيفرة سي C، وإنما تغيِّر هذه الشيفرة إلى صياغة موجودة على المستوى نفسه من التجريد بحيث يمكن استخدامها بالطريقة نفسها ولكن في ظروف مختلفة قليلًا، وهي في حالتنا تحويل إحدى صيغ شيفرة جافاسكربت إلى صيغة أخرى. تحدثنا سابقًا عن الدوال السهمية (اطلع على تنفيذ المثال arrow-function.html وعلى شيفرته البرمجية)، حيث تعمل هذه الدوال في أحدث المتصفحات فقط. addEventListener("click", () => { }); يمكننا تحويل هذه الدالة إلى دالة تقليدية قديمة مجهولة الاسم بحيث ستعمل في المتصفحات القديمة كما يلي: addEventListener("click", function() { /* … */ }); الأداة الموصَى بها حاليًا لتحويل شيفرة جافاسكربت هي Babel التي توفر إمكانات التحويل لسمات اللغة المناسبة للتحويل، وتقدّم تعويض نقص دعم المتصفحات Polyfills لتوفير الدعم بالنسبة إلى الميزات التي لا يمكن تحويلها بسهولة إلى مكافئ أقدم، وأسهل طريقة لتجربة Babel هي استخدامها عبر الإنترنت، مما يسمح لك بإدخال شيفرتك البرمجية على اليسار، وإخراج نسخة مُحوَّلة على اليمين. ملاحظة: هناك العديد من الطرق لاستخدام Babel مثل مشغّلات المهام وأدوات الأتمتة وغيرها. استخدام شيفرة التعرف على المتصفح السيئة تحتوي جميع المتصفحات على سلسلة وكيل المستخدِم User-agent النصية التي تحدد ما هو المتصفح (الإصدار والاسم ونظام التشغيل وأمور أخرى)، وقد اعتاد المطورون على استخدام ما يسمى بشيفرة التعرف على المتصفح Browser Sniffing سابقًا عندما استخدم الجميع تقريبًا متصفحات Netscape أو Internet Explorer لاكتشاف المتصفح الذي يستخدمه المستخدِم ومنحه الشيفرة المناسبة للعمل عليه، وقد بدت الشيفرة كما يلي بالرغم من أنه مثال مبسّط: let ua = navigator.userAgent; if(ua.includes('Firefox')) { // تشغيل الشيفرة البرمجية الخاصة بمتصفح‫ Firefox } else if(ua.includes('Chrome')) { // تشغيل الشيفرة البرمجية الخاصة بمتصفح‫ Chrome } كانت الفكرة جيدةً إلى حد ما، إذ اكتشفت هذه الطريقة المتصفحَ الذي يعرض الموقع، وشغّلت الشيفرة البرمجية المناسبة للتأكد من أنّ المتصفح سيتمكن من استخدام موقعك بصورة جيدة. ملاحظة: جرب فتح طرفية جافاسكربت الآن وشغّل الخاصية navigator.userAgent لترى ما ستحصل عليه. لكن بدأ المطورون مع مرور الوقت برؤية مشاكل كبيرة في هذه الطريقة، إذ كانت الشيفرة عرضة للأخطاء، ولنفترض أنك تعلم أنّ إحدى الميزات لا تعمل في الإصدار Firefox 10 وما قبله مثلًا وطبّقتَ شيفرةً لاكتشاف ذلك، ثم ظهر الإصدار Firefox 11 الذي يُحتمَل ألّا يكون مدعومًا لأنه ليس الإصدار Firefox 10، لذا يجب عليك تغيير كل شيفرة التعرف على المتصفح بانتظام. طبّق العديد من المطورين شيفرة سيئة للتعرف على المتصفح ولم يجروا صيانةً عليها، ثم بدأت المتصفحات في منع استخدام مواقع الويب التي تحتوي على هذه الميزات التي طبّقوها منذ ذلك الحين، وقد أصبح ذلك شائعًا لدرجة أنّ المتصفحات بدأت في الكذب بشأن المتصفحات الموجودة في سلاسل وكيل المستخدِم User-agent النصية (أو ادعت بأن جميعها متصفحات) للتحايل على الشيفرات البرمجية، كما طبّقت المتصفحات تسهيلات للسماح للمستخدِمين بتغيير سلسلة وكيل المستخدِم التي أبلغ عنها المتصفح عند الاستعلام باستخدام شيفرة جافاسكربت، مما جعل المتصفح أكثر عرضةً للخطأ وبلا فائدة في النهاية. الدرس الذي يجب تعلّمه هنا هو أنه يجب ألّا تستخدِم أبدًا التعرف على المتصفح، فحالة الاستخدام الحقيقية الوحيدة لشيفرة التعرف على المتصفح حاليًا هي عند إصلاح خلل في إصدار محدد جدًا من متصفح معيّن، ولكن يمكن إصلاح معظم الأخطاء بسرعة كبيرة في دورات الإصدار السريع لبائعِي المتصفحات حاليًا، لذا فلن تستخدِم شيفرة التعرف على المتصفح إلا نادرًا، كما يُعَدّ اكتشاف الميزات دائمًا خيارًا أفضل، فإذا اكتشفت ما إذا كانت الميزة مدعومةً، فلن تحتاج لتغيير الشيفرة البرمجية عند ظهور إصدارات جديدة من المتصفح، وستكون الاختبارات أكثر وثوقيةً. إذا صادفت متصفحًا يستخدِم شيفرة التعرف على المتصفحات عند الانضمام إلى مشروع قائم، فابحث عما إذا كان يمكن استبداله بشيء أكثر منطقيةً، إذ يتسبب التعرف على المتصفح في حدوث كل أنواع الأخطاء مثل الخطأ 1308462. التعامل مع بادئات جافاسكربت تحدّثنا في المقال السابق بالتفصيل حول التعامل مع بادئات CSS، كما تستخدِم تطبيقات جافاسكربت الجديدة البادئات Prefixes في بعض الأحيان بالرغم من أنّ لغة جافاسكربت تستخدِم الأحرف بحالة سنام الجمل بدلًا من الواصلة التي تستخدمها لغة CSS، فإذا اُستخدِمت بادئة مع كائن واجهة jshint API جديد بالاسم Object، فسيحدث ما يلي: سيستخدِم Mozilla البادئة mozObject. ستستخدِم Chrome/Opera/Safari البادئة webkitObject. سيستخدِم Microsoft البادئة msObject. إليك مثال مأخوذ من النسخة violent-theremin واطلع على شيفرته البرمجية، حيث يستخدِم هذا المثال مزيجًا من واجهة Canvas API وواجهة Web Audio API لإنشاء أداة رسم ممتعة (وصاخبة): const AudioContext = window.AudioContext || window.webkitAudioContext; const audioCtx = new AudioContext(); دُعِمت نقاط الدخول الرئيسية في حالة واجهة Web Audio API لاستخدام واجهة برمجة التطبيقات المدعومة في Chrome/Opera عبر الإصدارات ذات البادئة webkit (مع دعم الإصدارات التي بدون بادئة حاليًا)، فالطريقة السهلة للتغلب على هذا الموقف هي إنشاء إصدار جديد من الكائنات ذات البادئات في بعض المتصفحات، وجعلها مساوية للإصدار الذي بدون بادئة أو الإصدار ذي البادئة (أو أيّ إصدارات أخرى ذات بادئات يجب وضعها في الحسبان)، حيث سيُستخدَم الإصدار الذي يدعمه المتصفح الذي يعرض الموقع حاليًا، ويمكننا بعد ذلك استخدام هذا الكائن لمعالجة واجهة برمجة التطبيقات بدلًا من الكائن الأصلي، حيث ننشئ في هذه الحالة باني واجهة AudioContext معدلة، ثم ننشئ نسخةً جديدةً منها لاستخدامها في شيفرة Web Audio. يمكن تطبيق هذا النمط على أيّ ميزة جافاسكربت ذات بادئة، وتستفيد مكتبات وتعويض نقص دعم المتصفحات Polyfill في شيفرة جافاسكربت من هذا النوع من الشيفرة البرمجية لتجريد اختلافات المتصفحات للمطور قدر الإمكان. لم يكن من المفترض أبدًا استخدام الميزات ذات البادئات في مواقع الإنتاج، لأنها عرضة للتغيير أو الإزالة دون سابق إنذار وتتسبب في حدوث مشاكل في التوافق مع المتصفحات، فإذا أردت استخدام الميزات ذات البادئات، فتأكد من استخدام الميزات الصحيحة، كما يمكنك البحث عن المتصفحات التي تتطلب بادئات لميزات JavaScript/API المختلفة على موسوعة حسوب أو MDN أو caniuse.com مثلًا، فإذا لم تكن متأكدًا، فيمكنك معرفة ذلك عن طريق إجراء بعض الاختبارات مباشرةً في المتصفحات. حاول مثلًا الدخول إلى طرفية مطور المتصفح واكتب ما يلي مثلًا: window.AudioContext إذا كانت هذه الميزة مدعومةً في متصفحك، فستُستكمَل تلقائيًا. العثور عن مساعدة هناك العديد من المشاكل الأخرى التي ستواجهها في شيفرة جافاسكربت، ولكن أهم شيء يجب معرفته هو كيفية العثور على إجابات عبر الإنترنت، يمكنك إضافة أي سؤال برمجي يوجهك في قسم الأسئلة والأجوبة في أكاديمية حسوب للحصول على إجابة من مبرمجين ذوي خبرة. الخلاصة يجب أن يمنحك هذا المقال المبادئ وبعض الأفكار حول كيفية معالجة المشاكل المتعلقة بشيفرة جافاسكربت التي يمكن أن تواجهها للتوافق مع المتصفحات. ترجمة -وبتصرُّف- للمقال Handling common JavaScript problems. اقرأ أيضًا المقال السابق: معالجة المشاكل الشائعة للتوافق مع المتصفحات في HTML و CSS اكتشاف دعم المتصفحات لميزات HTML5 تأمين متصفحات الويب في العالم الرقمي
  17. يُعَد لارافيل Laravel إطار عمل بلغة PHP، وهو مفتوح المصدر ويوفر مجموعة من الأدوات والموارد لبناء تطبيقات PHP الحديثة. نمت شعبية لارافيل بسرعة في السنوات القليلة الماضية مع وجود نظام بيئي كامل يستفيد من ميزاته المبنية مسبقًا، واعتماد العديد من المطورين له بوصفه إطار العمل المفضل لديهم لعمليات التطوير الفعالة. سنثبّت ونضبط في هذا المقال تطبيق لارافيل جديد على خادم لينكس أوبنتو Ubuntu 22.04 باستخدام مدير الحزم Composer لتنزيل وإدارة اعتماديات إطار العمل وخادم Nginx لتخديم التطبيق، وسيكون لديك في النهاية تطبيق لارافيل تجريبي عملي يسحب المحتوى من قاعدة بيانات MySQL 8. المتطلبات الأساسية يجب أولًا تنفيذ المهام التالية على خادم لينكس أوبنتو: أنشئ مستخدم sudo وفعّل ufw. ثبّت حزمة LEMP مع قاعدة بيانات MySQL 8. ثبّت مدير الحزم Composer الذي سنستخدمه لتثبيت لارافيل واعتمادياته. الخطوة الأولى: تثبيت وحدات PHP المطلوبة يجب تثبيت بعض وحدات PHP التي يتطلبها إطار العمل قبل أن تتمكن من تثبيت لارافيل، حيث سنستخدم الأمر apt لتثبيت وحدات PHP، وهي وحدات php-mbstring و php-xml و php-bcmath. توفر هذه التوسّعات الخاصة بلغة PHP دعمًا إضافيًا للتعامل مع تشفير المحارف وتنسيق XML والدقة الرياضية. إذا كانت هذه هي المرة الأولى التي تستخدم فيها الأمر apt في هذه الجلسة، فيجب عليك أولًا تشغيل أمر update التالي لتحديث جدول الحزم: sudo apt update يمكنك الآن تثبيت الحزم المطلوبة باستخدام الأمر التالي: sudo apt install php-mbstring php-xml php-bcmath أصبح نظامك جاهزًا لتنفيذ تثبيت لارافيل باستخدام مدير الحزم Composer، ولكن ستحتاج إلى قاعدة بيانات لتطبيقك قبل ذلك. الخطوة الثانية: إنشاء قاعدة بيانات للتطبيق سننشئ تطبيق قائمة السفر Travel List الذي يعرض قائمة بالأماكن التي يرغب المستخدم في السفر إليها وقائمة بالأماكن التي زارها فعليًا لتوضيح تثبيت لارافيل واستخدامه الأساسي، حيث يمكن تخزين هذه القوائم في جدول الأماكن Places مع حقل للمواقع التي سنسميه name وحقل آخر لتمييز المواقع بوصفها مُزارة visited أو غير مُزارة not visited، حيث سنسميه هذا الحقل visited، وسنضمّن حقل المعرّف id لتحديد كل إدخال بصورة فريدة. سننشئ مستخدم MySQL مخصص ونمنحه صلاحيات كاملة في قاعدة البيانات travellist للاتصال بقاعدة البيانات من تطبيق لارافيل. لا تدعم المكتبة mysqlnd -وهي مكتبة MySQL PHP الأصيلة- التابعَ caching_sha2_authentication الذي هو تابع الاستيثاق الافتراضي لقاعدة بيانات MySQL 8، لذا يجب إعداد مستخدم قاعدة البيانات باستخدام تابع الاستيثاق mysql_native_password حتى نتمكّن من الاتصال بقاعدة بيانات MySQL من PHP. أولًا، سجّل الدخول إلى طرفية MySQL بوصفك مستخدم قاعدة البيانات الجذر باستخدام الأمر التالي: sudo mysql شغّل الأمر التالي من طرفية MySQL لإنشاء قاعدة بيانات جديدة: CREATE DATABASE travellist; يمكنك الآن إنشاء مستخدم جديد ومنحه الصلاحيات الكاملة في قاعدة البيانات المخصصة التي أنشأتها، حيث سننشئ في مثالنا مستخدم باسم travellist_user مع كلمة مرور password بالرغم من أنه يجب عليك تغييرها إلى كلمة مرور آمنة من اختيارك: CREATE USER 'travellist_user'@'%' IDENTIFIED WITH mysql_native_password BY 'password'; يجب الآن أن نمنح هذا المستخدم إذنًا في قاعدة بيانات travellist كما يلي: GRANT ALL ON travellist.* TO 'travellist_user'@'%'; مما يؤدي إلى إعطاء المستخدم travellist_user الصلاحيات الكاملة في قاعدة بيانات travellist، مع منع هذا المستخدم من إنشاء أو تعديل قواعد بيانات أخرى على خادمك. اخرج بعد ذلك من صدفة MySQL: exit يمكنك الآن اختبار ما إذا كان المستخدم الجديد لديه الأذونات المناسبة من خلال تسجيل الدخول إلى طرفية MySQL مرة أخرى باستخدام ثبوتيات المستخدم المخصَّص هذه المرة: mysql -u travellist_user -p لاحظ الخيار ‎-p في هذا الأمر، والتي ستطلب كلمة المرور المُستخدَمة عند إنشاء المستخدم travellist_user. تأكد بعد تسجيل الدخول إلى طرفية MySQL من أنه يمكنك الوصول إلى قاعدة بيانات travellist كما يلي: SHOW DATABASES; وسيظهر الخرج التالي: +--------------------+ | Database | +--------------------+ | information_schema | | travellist | +--------------------+ 2 rows in set (0.01 sec) أنشئ بعد ذلك جدولًا بالاسم places في قاعدة البيانات travellist من خلال تشغيل التعليمة التالية من طرفية MySQL: CREATE TABLE travellist.places ( id INT AUTO_INCREMENT, name VARCHAR(255), visited BOOLEAN, PRIMARY KEY(id) ); املأ الجدول places ببعض البيانات كما يلي: INSERT INTO travellist.places (name, visited) VALUES ("Tokyo", false), ("Budapest", true), ("Nairobi", false), ("Berlin", true), ("Lisbon", true), ("Denver", false), ("Moscow", false), ("Olso", false), ("Rio", true), ("Cincinnati", false), ("Helsinki", false); شغّل التعليمة التالية لتأكيد حفظ البيانات بنجاح في جدولك: SELECT * FROM travellist.places; وسترى خرجًا مشابهًا لما يلي: +----+-----------+---------+ | id | name | visited | +----+-----------+---------+ | 1 | Tokyo | 0 | | 2 | Budapest | 1 | | 3 | Nairobi | 0 | | 4 | Berlin | 1 | | 5 | Lisbon | 1 | | 6 | Denver | 0 | | 7 | Moscow | 0 | | 8 | Oslo | 0 | | 9 | Rio | 1 | | 10 | Cincinnati| 0 | | 11 | Helsinki | 0 | +----+-----------+---------+ 11 rows in set (0.00 sec) يمكنك الخروج من طرفية MySQL بعد التأكد من وجود بيانات صالحة في جدول اختبارك: exit أصبحتَ الآن جاهزًا لإنشاء التطبيق وضبطه للاتصال بقاعدة البيانات الجديدة. الخطوة الثالثة: إنشاء تطبيق لارافيل جديد ستنشئ الآن تطبيق لارافيل جديد باستخدام الأمر composer create-project، إذ يُستخدَم هذا الأمر لتشغيل التطبيقات الجديدة بناءً على أطر العمل وأنظمة إدارة المحتوى الحالية. سنستخدم في هذا المقال الاسم travellist للتطبيق، ولكن يمكنك تغييره إلى أي شيء آخر. سيعرض تطبيق travellist قائمةً بالمواقع المسحوبة من خادم MySQL محلي بهدف توضيح ضبط تطبيق لارافيل الأساسي والتأكد من قدرتك على الاتصال بقاعدة البيانات. انتقل أولًا إلى مجلد المنزل كما يلي: cd ~ ينشئ الأمر التالي مجلد travellist جديد يحتوي على تطبيق لارافيل هيكلي استنادًا إلى الإعدادات الافتراضية: composer create-project --prefer-dist laravel/laravel travellist وسترى خرجًا مشابهًا لما يلي: Creating a "laravel/laravel" project at "./travellist" Installing laravel/laravel (v10.1.1) - Downloading laravel/laravel (v10.1.1) - Installing laravel/laravel (v10.1.1): Extracting archive Created project in /tmp/travellist > @php -r "file_exists('.env') || copy('.env.example', '.env');" Loading composer repositories with package information Updating dependencies Lock file operations: 106 installs, 0 updates, 0 removals - Locking brick/math (0.11.0) - Locking dflydev/dot-access-data (v3.0.2) - Locking doctrine/inflector (2.0.6) - Locking doctrine/lexer (3.0.0) - Locking dragonmantank/cron-expression (v3.3.2) - Locking egulias/email-validator (4.0.1) ... انتقل إلى مجلد التطبيق عند انتهاء التثبيت وشغّل أمر لارافيل artisan للتحقق من تثبيت جميع المكونات بنجاح: cd travellist php artisan وسترى خرجًا مشابهًا لما يلي: Laravel Framework 10.9.0 Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question --env[=ENV] The environment the command should run under -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug ... يؤكد هذا الخرج أن ملفات التطبيق في مكانها المناسب، وأن أدوات سطر أوامر لارافيل تعمل كما هو متوقع، ولكن ما زلنا بحاجة إلى ضبط التطبيق لإعداد قاعدة البيانات وبعض التفاصيل الأخرى. الخطوة الرابعة: ضبط تطبيق لارافيل توجد ملفات ضبط لارافيل في مجلد بالاسم config ضمن المجلد الجذر للتطبيق، وإذا ثَبّتَ لارافيل باستخدام مدير الحزم Composer، فسينشئ ملف بيئة Environment File، حيث يحتوي هذا الملف على إعدادات خاصة بالبيئة الحالية التي يعمل بها التطبيق، وستكون لها الأفضلية على القيم المحددة في ملفات الضبط العادية الموجودة في المجلد config. يتطلب كل تثبيت في بيئة جديدة ملف بيئة مخصص لتعريف أشياء مثل إعدادات اتصال قاعدة البيانات وخيارات تنقيح الأخطاء وعنوان URL للتطبيق وعناصر أخرى تختلف اعتمادًا على البيئة التي يعمل بها التطبيق. تحذير: يحتوي ملف ضبط البيئة على معلومات حساسة حول خادمك، بما في ذلك ثبوتيات قاعدة البيانات ومفاتيح الأمان، لذلك لا يجب أبدًا مشاركة هذا الملف علنًا. سنعدّل ملف ‎.env لتخصيص خيارات الضبط لبيئة التطبيق الحالية. افتح ملف ‎.env باستخدام محرر سطر الأوامر الذي تختاره، حيث سنستخدم هنا nano: nano .env توجد العديد من متغيرات الضبط في هذا الملف، ولكنك لن تحتاج إلى إعدادها جميعًا الآن. تحتوي القائمة التالية على نظرة عامة على المتغيرات التي يجب الاهتمام بها: APP_NAME: اسم التطبيق المُستخدَم للإشعارات والرسائل. APP_ENV: بيئة التطبيق الحالية. APP_KEY: يُستخدم لتوليد السلاسل النصية الإضافية Salts والقيم المُعمَّاة المختصَرة Hashes، ويُنشَأ هذا المفتاح الفريد تلقائيًا عند تثبيت لارافيل باستخدام مدير الحزم Composer، لذلك لا حاجة إلى تغييره. APP_DEBUG: يحدد إظهار معلومات تنقيح الأخطاء من طرف العميل أم لا. APP_URL: عنوان URL الأساسي للتطبيق، ويُستخدَم لتوليد روابط التطبيق. DB_DATABASE: اسم قاعدة البيانات. DB_USERNAME: اسم المستخدم للاتصال بقاعدة البيانات. DB_PASSWORD: كلمة المرور للاتصال بقاعدة البيانات. تُضبَط هذه القيم افتراضيًا لبيئة التطوير المحلية التي تستخدم بيئة تطوير Homestead، وهي بيئة أداة Vagrant المُحزَّمة مسبَقًا التي يوفّرها إطار عمل لارافيل، حيث سنغيّر هذه القيم لتمثل إعدادات البيئة الحالية لتطبيقنا. إذا أردتَ تثبيت لارافيل في بيئة تطوير development أو اختبار testing، فيمكنك ترك الخيار APP_DEBUG مفعّلًا، حيث سيعطيك هذا الخيار معلومات تنقيح الأخطاء المهمة أثناء اختبار التطبيق من المتصفح، ويجب ضبط المتغير APP_ENV على القيمة development أو testing في هذه الحالة. بينما إذا أردتَ تثبيت لارافيل في بيئة الإنتاج، فيجب عليك تعطيل الخيار APP_DEBUG، لأنه يعرض معلومات حساسة حول تطبيقك للمستخدم النهائي، ويجب ضبط المتغير APP_ENV على القيمة production في هذه الحالة. ملاحظة: يحتوي المتغير APP_KEY على مفتاح فريد ينشأ تلقائيًا عند تثبيت لارافيل باستخدام مدير الحزم Composer، ولست بحاجة إلى تغيير هذه القيمة. إذا أردتَ إنشاء مفتاح أمان جديد، فيمكنك استخدام الأمر php artisan key:generate. يضبط ملف ‎.env التالي تطبيقنا من أجل عملية التطوير كما يلي: APP_NAME=TravelList APP_ENV=development APP_KEY=APPLICATION_UNIQUE_KEY_DONT_COPY APP_DEBUG=true APP_URL=http://domain_or_IP LOG_CHANNEL=stack DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=travellist DB_USERNAME=travellist_user DB_PASSWORD=password ... عدّل متغيراتك وفقًا لذلك، ثم احفظ الملف وأغلقه للاحتفاظ بالتغييرات التي أجريتها، حيث إذا كنت تستخدم المحرر nano، فيمكنك تطبيق ذلك باستخدام CTRL+X ثم اضغط على Y و Enter للتأكيد. ضبطنا الآن تطبيق لارافيل، ولكننا ما زلنا بحاجة إلى ضبط خادم الويب حتى نتمكّن من الوصول إليه من المتصفح، حيث سنضبط خادم Nginx لخدمة تطبيق لارافيل في الخطوة التالية. الخطوة الخامسة: إعداد خادم Nginx ثبّتنا لارافيل في مجلد محلي من المجلد الرئيسي للمستخدم البعيد، إذ يعمل ذلك جيدًا مع بيئات التطوير المحلية، إلا أنه ليس ممارسة موصًى بها لخوادم الويب المفتوحة لشبكة الإنترنت العامة. سننقل مجلد التطبيق إلى ‎/var/www، وهو الموقع المعتاد لتطبيقات الويب التي تعمل على خادم Nginx. أولًا، استخدم الأمر mv لنقل مجلد التطبيق بكل محتوياته إلى ‎/var/www/travellist: sudo mv ~/travellist /var/www/travellist يجب الآن منح مستخدم خادم الويب إذن الوصول للكتابة في مجلدات storage و cache حيث يخزّن لارافيل الملفات التي أنشأها التطبيق كما يلي: sudo chown -R www-data.www-data /var/www/travellist/storage sudo chown -R www-data.www-data /var/www/travellist/bootstrap/cache أصبحت ملفات التطبيق مرتبة الآن، ولكننا ما زلنا بحاجة إلى ضبط خادم Nginx لخدمة المحتوى، لذا سننشئ ملف ضبط مضيف وهمي جديد في ‎/etc/nginx/sites-available: sudo nano /etc/nginx/sites-available/travellist يحتوي ملف الضبط التالي على الإعدادات الموصَى بها لتطبيقات لارافيل على خادم Nginx: server { listen 80; server_name server_domain_or_IP; root /var/www/travellist/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } } انسخ هذا المحتوى إلى الملف ‎/etc/nginx/sites-available/travellist، واضبط القيم server_domain_or_IP و travellist التي تمثل اسم الخادم والمجلد الجذر لتتماشى مع ضبطك الخاص إن لزم الأمر. احفظ الملف وأغلقه عند الانتهاء من التعديل. أنشئ وصلة رمزية إلى travellist في الملف sites-enabled لتنشيط ملف ضبط المضيف الوهمي الجديد كما يلي: sudo ln -s /etc/nginx/sites-available/travellist /etc/nginx/sites-enabled/ ملاحظة: إذا كان لديك ملف مضيف وهمي آخر مضبوط مسبقًا على اسم الخادم server_name نفسه المُستخدَم في المضيف الوهمي travellist، فيمكن أن تحتاج إلى تعطيل الضبط القديم من خلال إزالة الرابط الرمزي المقابل في /etc/nginx/sites-enabled/. يمكنك استخدام الأمر التالي للتأكد من أن الضبط لا يحتوي على أيّ أخطاء صياغية: sudo nginx -t ويجب أن ترى خرجًا يشبه ما يلي: Outputnginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful أعِد تحميل خادم Nginx باستخدام الأمر التالي لتطبيق التغييرات: sudo systemctl reload nginx انتقل الآن إلى متصفحك وادخل إلى التطبيق باستخدام اسم نطاق أو عنوان IP الخاص بالخادم كما حدّده الموجّه server_name في ملف ضبطك الخاص: http://server_domain_or_IP وسترى صفحة تشبه ما يلي: يؤكد ذلك أن خادم Nginx مضبوط بصورة صحيحة لخدمة تطبيق لارافيل، ويمكنك بعد ذلك البدء في إنشاء تطبيقك على التطبيق الهيكلي الذي يوفره التثبيت الافتراضي. سنعدّل في الخطوة التالية طريق التطبيق الرئيسي للاستعلام عن البيانات في قاعدة البيانات باستخدام واجهة DB في إطار عمل لارافيل. الخطوة السادسة: إنشاء صفحتك الرئيسية يجب أن يكون لديك تطبيق لارافيل يعمل بنجاح وجدول قاعدة بيانات بالاسم places يحتوي على بعض البيانات بافتراض أنك اتبعت جميع الخطوات الواردة في هذا المقال حتى الآن، وسنعدّل الآن مسار التطبيق الرئيسي للاستعلام عن قاعدة البيانات وإعادة المحتويات إلى عرض التطبيق. افتح ملف الموجهات Routes الرئيسي routes/web.php: nano routes/web.php يحتوي هذا الملف افتراضيًا على المحتوى التالي: <?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); تُحدَّد الطرق ضمن هذا الملف باستخدام التابع الساكن Route::get الذي يأخذ مسارًا Path ودالة رد نداء بوصفهما وسائطًا. تحل الشيفرة البرمجية الآتية محل دالة رد نداء الطريق الرئيسي، حيث تجري استعلامَين إلى قاعدة البيانات باستخدام الراية visited لترشيح النتائج، وتعيد النتائج إلى عرض بالاسم travellist الذي سننشئه لاحقًا. انسخ المحتوى التالي إلى الملف routes/web.php بدلًا من الشيفرة البرمجية الموجودة مسبقًا: <?php use Illuminate\Support\Facades\DB; Route::get('/', function () { $visited = DB::select('select * from places where visited = ?', [1]); $togo = DB::select('select * from places where visited = ?', [0]); return view('travellist', ['visited' => $visited, 'togo' => $togo ] ); }); احفظ الملف وأغلقه عند الانتهاء من التعديل. سننشئ الآن العرض الذي سيعرض نتائج قاعدة البيانات للمستخدِم، لذا أنشِئ ملف عرض جديد ضمن المجلد resources/views: nano resources/views/travellist.blade.php يُنشِئ القالب التالي قائمتين من الأماكن بناءً على المتغيرات visited و togo. انسخ المحتوى التالي إلى ملف العرض الجديد: <html> <head> <title>Travel List</title> </head> <body> <h1>My Travel Bucket List</h1> <h2>Places I'd Like to Visit</h2> <ul> @foreach ($togo as $newplace) <li>{{ $newplace->name }}</li> @endforeach </ul> <h2>Places I've Already Been To</h2> <ul> @foreach ($visited as $place) <li>{{ $place->name }}</li> @endforeach </ul> </body> </html> احفظ الملف وأغلقه عند الانتهاء، ثم انتقل إلى متصفحك وأعِد تحميل التطبيق، وسترى الصفحة التالية: لديك الآن تطبيق لارافيل عملي يسحب المحتويات من قاعدة بيانات MySQL. الخلاصة ضبطنا في هذا المقال تطبيق لارافيل جديد باستخدام حزمة LEMP أو (Linux و Nginx و MySQL و PHP) ويعمل على خادم أوبنتو 22.04، وخصّصنا الطريق الافتراضي للاستعلام عن محتوى قاعدة البيانات وعرض النتائج في عرض مُخصَّص. يمكنك الآن إنشاء طرق وعروض جديدة للصفحات الإضافية التي يحتاجها تطبيقك. اطلع على توثيق لارافيل على موسوعة حسوب لمزيد من المعلومات حول الطرق Routes والعروض Views ودعم قاعدة البيانات، وإذا أردتَ النشر في بيئة الإنتاج، فيمكنك الاطلاع على قسم التحسين Optimization للتعرف على بعض الطرق المختلفة التي يمكنك من خلالها تحسين أداء تطبيقك. ترجمة -وبتصرُّف- للمقال How To Install and Configure Laravel with Nginx on Ubuntu 20.04 (LEMP) لصاحبته Erika Heidi. اقرأ أيضًا مدخل إلى Laravel 5 تثبيت Laravel 5 وإعداده على Windows وUbuntu [فيديو] تثبيت وضبط خادم Nginx كيف تؤمّن خادم ويب NGINX على أوبنتو 16.04 ما هو نظام التشغيل لينكس؟
  18. يوفر وجود الربط الديناميكي Dynamic Linking بعض المزايا التي يمكننا الاستفادة منها وبعض المشاكل الإضافية التي يجب حلها للحصول على نظام فعّال. إصدارات المكتبات إحدى المشاكل المُحتمَلة هي وجود إصدارات مختلفة للمكتبات. لكن هناك احتمال أقل بكثير لوجود مشاكل عند استخدام المكتبات الساكنة، حيث تُدمَج شيفرة المكتبة البرمجية مباشرةً في الملف الثنائي الخاص بالتطبيق. إن أردتَ استخدام إصدار جديد من المكتبة، فيجب إعادة تصريفها في ملف ثنائي جديد لتحل محل الإصدار القديم. يُعَد ذلك أمرًا غير عملي إلى حد ما بالنسبة للمكتبات الشائعة وأكثرها شيوعًا مكتبه libc والمُضمَّنة في معظم التطبيقات. إذا كانت المكتبة متوفرة فقط بوصفها مكتبة ساكنة، فيجب إعادة بناء كل تطبيق في النظام عند أي تعديل فيها. يمكن أن تسبّب التعديلات في طريقة عمل المكتبة الديناميكية مشاكلًا متعددة. تكون التعديلات في أحسن الأحوال متوافقة تمامًا دون تغيير أيّ شيء مرئي خارجيًا، ولكن يمكن أن تتسبب التعديلات في تعطل التطبيق مثل تغير الدالة التي تأخذ النوع int لتأخذ النوع int *‎. الأسوأ من ذلك هو أن يغيّر إصدارُ المكتبة الجديد الدلالات ويعيد قيمًا مختلفة وخاطئة فجأةً. يمكن أن يكون هذا خطأً يصعب تعقّبه، حيث إن تعطل أحد التطبيقات، فيمكنك استخدام منقّح أخطاء Debugger لعزل مكان حدوث الخطأ، بينما يمكن أن يظهر تلف البيانات أو تعديلها فقط في أجزاء أخرى من التطبيق. يتطلب الرابط الديناميكي طريقة لتحديد إصدار المكتبات في النظام بحيث يمكن التعرّف على التعديلات الأحدث. هناك عدد من الأنظمة التي يمكن للرابط الديناميكي الحديث استخدامها للعثور على الإصدارات الصحيحة من المكتبات التي سنوضّحها فيما يلي. نظام sonames يُستخدَم نظام sonames لإضافة بعض المعلومات الإضافية إلى مكتبة للمساعدة في تحديد الإصدارات. يسرد التطبيق المكتبات التي يريدها في الحقول DT_NEEDED ضمن القسم الديناميكي للملف الثنائي، وتوجد المكتبة الفعلية في ملف على القرص الصلب ضمن المجلد ‎/lib لمكتبات النظام الأساسية أو المجلد ‎/usr/lib للمكتبات الاختيارية. يتطلب وجودُ إصدارات متعددة من المكتبة على القرص الصلب استخدامَ أسماء ملفات مختلفة. لذا يستخدم نظام sonames مجموعة من الأسماء وروابطًا إلى نظام الملفات لبناء تسلسل هرمي من المكتبات من خلال تقديم مفهوم التعديلات الرئيسية Major والثانوية Minor للمكتبة. يُعَد التعديل الثانوي تعديلًا متوافقًا مع إصدار سابق من المكتبة، ويتكون من إصلاحاتٍ للأخطاء فقط. بينما يُعَد التعديل الرئيسي أي تعديل غير متوافق مثل تغيير دخل الدوال أو الطريقة التي تتصرف بها الدالة. تشكّل الحاجة إلى الاحتفاظ بكل تعديل مكتبة رئيسي أو ثانوي في ملف منفصل على القرص الصلب أساسَ تسلسل المكتبات الهرمي. يكون اسم المكتبة هو libNAME.so.MAJOR.MINOR حسب العِرف المتبع، حيث يمكنك اختياريًا الحصول على إطلاق Release بوصفه معرفًا نهائيًا بعد العدد الثانوي، ويكفي ذلك لتمييز جميع إصدارات المكتبة المختلفة. مع ذلك، إذا رُبِط كل تطبيق بهذا الملف مباشرةً، فسنواجه المشكلة نفسها التي واجهناها مع المكتبة الساكنة، إذ يجب إعادة بناء التطبيق للإشارة إلى المكتبة الجديدة في كل مرة يحدث فيها تعديل ثانوي. ما نريده هو أن نشير إلى ما يمثله العدد الرئيسي Major من المكتبة الذي إن تغير، فيجب إعادة تصريف Recompile تطبيقنا، لأننا نحتاج إلى التأكد من أن برنامجنا لا يزال متوافقًا مع المكتبة الجديدة. يكون soname بالشكل libNAME.so.MAJOR، ويجب ضبطه في الحقل DT_SONAME من القسم الديناميكي لمكتبة مشتركة، حيث يمكن لمؤلف المكتبة تحديد هذا الإصدار عند إنشاء المكتبة. يمكن أن يحدّد كل ملف مكتبة للإصدار الثانوي على القرص الصلب رقمَ الإصدار الرئيسي نفسه في الحقل DT_SONAME، مما يسمح للرابط الديناميكي بمعرفة أن ملف المكتبة يطبّق تعديلًا رئيسيًا معينًا لواجهتي API و ABI الخاصتين بالمكتبة. لذا يُشغَّل تطبيق اسمه ldconfig لإنشاء روابط رمزية للإصدار الرئيسي إلى أحدث إصدار ثانوي على النظام. يعمل تطبيق ldconfig من خلال تشغيل جميع المكتبات التي تطبّق رقم إصدار رئيسي معين، ثم يختار المكتبة التي تحتوي على أعلى رقم تعديل ثانوي، ثم ينشِئ رابطًا رمزيًا من libNAME.so.MAJOR إلى ملف المكتبة الفعلي الموجود على القرص الصلب مثل libNAME.so.MAJOR.MINOR. الجزء الأخير من التسلسل الهرمي هو اسم تصريف Compile Name المكتبة. إن أردت تصريف برنامجك لربطه بمكتبة، فيمكنك استخدام الراية ‎-lNAME التي تبحث عن الملف libNAME.so في مسار بحث المكتبة. لاحظ أننا لم نحدد أي رقم إصدار، لأننا نريد فقط الربط بأحدث مكتبة على النظام. يعود الأمر إلى إجراء التثبيت الخاص بالمكتبة لإنشاء رابط رمزي بين اسم التصريف libNAME.so وأحدث شيفرة مكتبة على النظام، ويمكن التعامل مع ذلك باستخدام نظام إدارة الحزم dpkg أو rpm. لا يُعَد ذلك عملية آلية، إذ يُحتمَل ألّا تكون أحدث مكتبة على النظام هي المكتبة التي ترغب في تصريفها دائمًا، فمثلًا يمكن أن تكون أحدث مكتبة مُثبَّتة إصدارًا تطويريًا غير مناسب للاستخدام العام. يوضح الشكل التالي العملية العامة لنظام sonames: كيف يبحث الرابط الديناميكي عن المكتبات يبحث الرابط الديناميكي في الحقل DT_NEEDED للعثور على المكتبات المطلوبة عند بدء تشغيل التطبيق، حيث يحتوي هذا الحقل على اسم soname الخاص بالمكتبة، لذا فالخطوة التالية هي أن يمر الرابط الديناميكي على جميع المكتبات في مسار بحثه بحثًا عن المكتبة المطلوبة. تتضمن هذه العملية من الناحية النظرية خطوتين. أولًا، يجب أن يبحث الرابط الديناميكي في جميع المكتبات للعثور على تلك المكتبات التي تطبّق نظام soname المحدد. ثانيًا، يجب مقارنة أسماء الملفات الخاصة بالتعديلات الثانوية للعثور على أحدث إصدار والذي يكون جاهزًا للتحميل لاحقًا. ذكرنا سابقًا أن هناك رابطًا رمزيًا أعدّه برنامج ldconfig بين اسم soname الخاص بالمكتبة والتعديل الثانوي الأخير، وبالتالي يجب أن يتبع الرابط الديناميكي هذا الرابط فقط للعثور على الملف الصحيح المراد تحميله بدلًا من الاضطرار إلى فتح جميع المكتبات الممكنة وتحديد المكتبات التي تريد استخدامها في كل مرة يكون التطبيق مطلوبًا فيها. يُعَد الوصول إلى نظام الملفات بطيئًا جدًا، لذا ينشئ برنامج ldconfig ذاكرة مخبئية للمكتبات المُثبَّتة في النظام، حيث تكون هذه الذاكرة المخبئية ببساطة قائمةً بأسماء soname الخاصة بالمكتبات المتاحة للرابط الديناميكي ومؤشرًا لرابط الإصدار الرئيسي على القرص الصلب، مما يوفر على الرابط الديناميكي قراءة مجلدات كاملة مليئة بالملفات لتحديد الرابط الصحيح. يمكنك تحليل ذلك باستخدام ‎/sbin/ldconfig -p الموجود ضمن الملف ‎/etc/ldconfig.so.cache. إن لم يُعثَر على المكتبة في الذاكرة المخبئية، فسيعود الرابط الديناميكي إلى الخيار الأبطأ المتمثل في المرور على نظام الملفات، وبالتالي يجب إعادة تشغيل برنامج ldconfig عند تثبيت مكتبات جديدة. البحث عن الرموز ناقشنا كيف حصل الرابط الديناميكي على عنوان دالة المكتبة ووضعه في جدول PLT ليستخدمه البرنامج، ولكننا لم نناقش حتى الآن كيف يجد الرابط الديناميكي عنوان الدالة. تُسمَّى هذه العملية بالارتباط Binding، لأن اسم الرمز مرتبط بالعنوان الذي يمثله. يحتوي الرابط الديناميكي على أجزاء من المعلومات مثل الرمز الذي يبحث عنه وقائمة المكتبات التي يمكن أن يكون هذا الرمز فيها كما هو محدَّد باستخدام حقول DT_NEEDED في الملف الثنائي. تحتوي كل مكتبة كائنات مشتركة على قسم يسمى ‎.dynsym مميَّز على أنه SHT_DYNSYM، حيث يُعَد هذا القسم الحد الأدنى من مجموعة الرموز المطلوبة للربط الديناميكي، وهو أيّ رمز في المكتبة يمكن أن يستدعيه برنامج خارجي. جدول الرموز الديناميكي هناك ثلاثة أقسام تلعب جميعها دورًا في وصف الرموز الديناميكية. لنلقِ أولًا نظرة على تعريف رمز من مواصفات ملف ELF كما يلي: typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym; الحقل القيمة st_name فهرس إلى جدول السلاسل النصية st_value القيمة الموجودة في كائن مشترك قابل للنقل، حيث تحتفظ هذه القيمة بالإزاحة عن قسم الفهرس المعطى في الحقل st_shndx st_size أي حجم مرتبط بالرمز st_info معلومات عن ارتباط Binding الرمز الذي سنشرحه لاحقًا ويكون نوع هذا الرمز دالة أو كائن أو غير ذلك st_other غير مُستخدَم حاليًا st_shndx فهرس القسم الذي يوجد فيه الرمز (اطّلع على الحقل st_value) تكون السلسلة النصية الفعلية لاسم الرمز ضمن قسم منفصل هو القسم ‎.dynstr، حيث تحتوي المدخلة في هذا القسم فهرسًا إلى قسم السلاسل النصية فقط، مما يؤدي إلى ظهور مستوًى معين من الحِمل على الرابط الديناميكي، إذ يجب أن يقرأ الرابط الديناميكي جميع مدخلات الرموز في القسم ‎.dynstr، ثم يتبع مؤشر الفهرس للعثور على اسم الرمز للمقارنة. يمكن تسريع هذه العملية من خلال تقديم قسم ثالث يسمى ‎.hash يحتوي على جدول تعمية Hash Table لأسماء رموز مدخلات جدول الرموز. يُحسَب جدول التعمية مسبقًا عند إنشاء المكتبة ويسمح للرابط الديناميكي بالعثور على مدخلة الرمز بصورة أسرع باستخدام عملية بحث واحدة أو اثنتين فقط. ارتباط الرموز Symbol Binding تشير عملية العثور على عنوان رمز إلى عملية ارتباط هذا الرمز، ولكن ارتباط الرموز Symbol Binding له معنًى منفصل، إذ تفرض عملية ارتباط الرموز رؤيتها خارجيًا أثناء عملية الربط الديناميكي. يُعَد الرمز المحلي Local Symbol غير مرئي خارج ملف الكائن المُعرَّف ضمنه، بينما يُعَد الرمز العام Global Symbol مرئيًا لملفات الكائنات الأخرى ويمكن أن يحقِّق المراجعَ غير المُعرَّفة في كائنات أخرى. يكون المرجع الضعيف Weak Reference نوعًا خاصًا من المراجع العامة ذات الأولوية المنخفضة، مما يعني أنه مُصمَّم لتجاوزه كما سنرى لاحقًا. يوضح المثال التالي برنامجًا بلغة سي C نحلّله لفحص ارتباطات الرموز: $ cat test.c static int static_variable; extern int extern_variable; int external_function(void); int function(void) { return external_function(); } static int static_function(void) { return 10; } #pragma weak weak_function int weak_function(void) { return 10; } $ gcc -c test.c $ objdump --syms test.o test.o: file format elf32-powerpc SYMBOL TABLE: 00000000 l df *ABS* 00000000 test.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000038 l F .text 00000024 static_function 00000000 l d .sbss 00000000 .sbss 00000000 l O .sbss 00000004 static_variable 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g F .text 00000038 function 00000000 *UND* 00000000 external_function 0000005c w F .text 00000024 weak_function $ nm test.o U external_function 00000000 T function 00000038 t static_function 00000000 s static_variable 0000005c W weak_function لاحظ استخدام ‎#pragma لتعريف الرمز الضعيف، حيث يُعَد pragma طريقة لإيصال معلومات إضافية إلى المصرِّف Compiler واستخدامه غير شائع، ولكن يكون في بعض الأحيان مطلوبًا لإخراج المصرِّف من العمليات المعتادة. يمكن فحص الرموز باستخدام أداتين مختلفتين كما هو موضَّح في المثال السابق، حيث يظهر الارتباط في العمود الثاني في كلتا الحالتين، ويجب أن تكون الشيفرات البرمجية واضحة تمامًا. تجاوز الرموز Overriding Symbols يجب أن يكون المبرمج قادرًا على تجاوز رمز في مكتبة، مما يعني تخريب الرمز العادي بتعريفٍ مختلف. ذكرنا أن ترتيب البحث في المكتبات مُحدَّدٌ حسب ترتيب حقول DT_NEEDED داخل المكتبة، ولكن يمكن إدخال مكتبات لتكون المكتبات الأخيرة التي يجري البحث عنها، وهذا يعني أنه سيُعثَر على أيّ رموز ضمنها بوصفها مرجعًا نهائيًا. يمكن تحقيق ذلك باستخدام متغير بيئة يسمى LD_PRELOAD يحدد المكتبات التي يجب أن يحمّلها الرابط في النهاية كما في المثال التالي: $ cat override.c #define _GNU_SOURCE 1 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <dlfcn.h> pid_t getpid(void) { pid_t (*orig_getpid)(void) = dlsym(RTLD_NEXT, "getpid"); printf("Calling GETPID\n"); return orig_getpid(); } $ cat test.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { printf("%d\n", getpid()); } $ gcc -shared -fPIC -o liboverride.so override.c -ldl $ gcc -o test test.c $ LD_PRELOAD=./liboverride.so ./test Calling GETPID 15187 تجاوزنا في المثال السابق الدالة getpid لطباعة عبارة صغيرة عند استدعائها. نستخدم الدالة dlysm التي توفرها مكتبة libc مع وسيط يخبرها بالاستمرار والعثور على الرمز التالي المسمَّى getpid. الرموز الضعيفة الرمز الضعيف هو الرمز المُميَّز بأنه له أولوية أقل ويمكن تجاوزه برمز آخر، حيث إن لم يُعثَر على تقديم Implementation آخر أبدًا، فسيكون الرمز الضعيف هو الرمز المُستخدَم. لذا يجب أن يحمّل المحمل الديناميكي جميع المكتبات ويتجاهل الرموز الضعيفة الموجودة في تلك المكتبات لصالح الرموز العادية الموجودة في مكتبات أخرى، حيث كانت هذه هي الطريقة المتبعة لتقديم معالجة الرموز الضعيفة في لينكس باستخدام مكتبة glibc سابقًا. لكن كان ذلك غير صحيح بالنسبة لنص معيار يونكس في ذلك الوقت SysVr4 الذي يفرض أنه يجب أن يتعامل الرابط الساكن مع الرموز الضعيفة التي يجب أن تظل بعيدة عن الرابط الديناميكي. تطابق تقديم لينكس الخاص بجعل الرابط الديناميكي يتجاوز الرموز الضعيفة مع منصة IRIX الخاصة بشركة SGI واختلف عن الأنظمة الأخرى مثل Solaris و AIX في ذلك الوقت. لذا لغى المطورون هذا السلوك عندما أدركوا أنه ينتهك المعيار، وتغير السلوك القديم ليتطلّب ضبط راية بيئة خاصة LD_DYNAMIC_WEAK. تحديد ترتيب الارتباط رأينا كيف يمكننا تجاوز دالة في مكتبة من خلال التحميل المسبق لمكتبة مشتركة أخرى لها الرمز المحدد نفسه. يُعَد الرمز الذي يُحلَّل بوصفه الرمز الأخير بأن له المرتبة الأخيرة في ترتيب تحميل المحمل الديناميكي للمكتبات، حيث تُحمَّل المكتبات بالترتيب المحدَّد في الراية DT_NEEDED الخاصة بالملف االثنائي، ويُحدَّد هذا الترتيب بدوره من خلال ترتيب تمرير المكتبات في سطر الأوامر عند بناء الكائن. يبدأ الرابط الديناميكي عند تحديد موقع الرموز بآخر مكتبة مُحمَّلة ويعمل بصورة عكسية حتى العثور على الرمز المطلوب. لكن تحتاج بعض المكتبات المشتركة إلى طريقة لتجاوز هذا السلوك، إذ يجب أن تخبر هذه المكتبات الرابط الديناميكي بأنه يجب أن ينظر أولًا بداخلها عن هذه الرموز بدلًا من العمل بصورة عكسية من آخر مكتبة مُحمَّلة. يمكن للمكتبات ضبط الراية DT_SYMBOLIC في ترويسة القسم الديناميكي للحصول على هذا السلوك، إذ يمكن ضبط هذه الراية من خلال تمرير الراية ‎-Bsymbolic عبر سطر أوامر الروابط الساكنة عند بناء المكتبة المشتركة، حيث تتحكم هذه الراية برؤية الرمز Symbol Visibility. لا يمكن تجاوز الرموز الموجودة في المكتبة، لذا يمكن عَدُّها خاصةً بالمكتبة المُحمَّلة. لكن يؤدي ذلك إلى فقدان قدر كبير من التفاصيل نظرًا لتمييز المكتبة بهذا السلوك أو عدم تمييزها، إذ سيسمح النظام الأفضل بجعل بعض الرموز خاصة وبعض الرموز عامة. تحديد إصدار الرموز Symbol Versioning يأتي النظام الأفضل من خلال استخدام تحديد إصدار الرموز، حيث يمكننا تحديد بعض المدخلات الإضافية للرابط الساكن لمنحه بعض المعلومات الإضافية حول الرموز في المكتبة المشتركة كما يلي: $ cat Makefile all: test testsym clean: rm -f *.so test testsym liboverride.so : override.c $(CC) -shared -fPIC -o liboverride.so override.c libtest.so : libtest.c $(CC) -shared -fPIC -o libtest.so libtest.c libtestsym.so : libtest.c $(CC) -shared -fPIC -Wl,-Bsymbolic -o libtestsym.so libtest.c test : test.c libtest.so liboverride.so $(CC) -L. -ltest -o test test.c testsym : test.c libtestsym.so liboverride.so $(CC) -L. -ltestsym -o testsym test.c $ cat libtest.c #include <stdio.h> int foo(void) { printf("libtest foo called\n"); return 1; } int test_foo(void) { return foo(); } $ cat override.c #include <stdio.h> int foo(void) { printf("override foo called\n"); return 0; } $ cat test.c #include <stdio.h> int main(void) { printf("%d\n", test_foo()); } $ cat Versions {global: test_foo; local: *; }; $ gcc -shared -fPIC -Wl,-version-script=Versions -o libtestver.so libtest.c $ gcc -L. -ltestver -o testver test.c $ LD_LIBRARY_PATH=. LD_PRELOAD=./liboverride.so ./testver libtest foo called 100000574 l F .text 00000054 foo 000005c8 g F .text 00000038 test_foo يمكننا ذكر ما إذا كان الرمز عامًا أم محليًا في أبسط الحالات على النحو الوارد في المثال السابق. تكون الدالة foo دالة دعم للدالة test_foo، ويمكن أن نكون سعداء بتجاوز الوظيفة الكلية للدالة test_foo، ولكن إن استخدمنا إصدار المكتبة المشتركة، فيجب الوصول إليها دون تعديل، إذ لا ينبغي لأحدٍ تعديل دالة الدعم. يسمح ذلك بالحفاظ على فضاء أسمائنا منظمًا بطريقة أفضل، إذ يمكن أن ترغب العديدُ من المكتبات في تقديم شيء يمكن تسميته باسم دالة شائعة مثل read أو write، ولكن إن فعلت ذلك، فيمكن أن يكون الإصدار الفعلي الممنوح للبرنامج خاطئًا تمامًا. يمكن للمطور من خلال تحديد الرموز بأنها محلية التأكدُ من عدم تعارض أي شيء مع هذا الاسم الداخلي دون أن يؤثر الاسم الذي يختاره على أيّ برنامج آخر. جاء مفهوم تحديد إصدار الرموز Symbol Versioning من تلك الفكرة، حيث يمكنك تحديد إصدارات متعددة من الرمز نفسه ضمن المكتبة نفسها. يُلحِق الرابط الساكن بعض معلومات الإصدار بعد اسم الرمز مثل ‎@VER الذي يصف الإصدار المعطى للرمز. إن قدّم المطور دالة لها الاسم نفسه تقديمًا ثنائيًا أو برمجيًا مختلفًا، فيمكنه زيادة رقم الإصدار. تلتقط التطبيقات الجديدة أحدث إصدار من الرمز عند بنائها بمقابل المكتبة المشتركة. لكن ستطلب التطبيقات المبنية بمقابل الإصدارات السابقة من المكتبة نفسها إصدارات أقدم، فمثلًا سيكون لها سلاسل ‎@VER أقدم في اسم الرمز الذي تطلبه، وبالتالي ستحصل على التقديم الأصلي. ترجمة -وبتصرُّف- للقسم Working with libraries and the linker من فصل Dynamic Linking من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand. اقرأ أيضًا المقال السابق: المكتبات وكيفية استدعاء دوالها ديناميكيا في معمارية الحاسوب مفهوم الربط الديناميكي Dynamic Linking في معمارية الحاسوب أهم المفاهيم التي تنظم العمليات وتعالجها في معمارية الحاسوب الحديثة أنظمة المعالجات في معمارية الحاسوب
  19. لقد سئم المطورون من الاضطرار إلى كتابة كل شيء من البداية، لذلك كانت المكتبات من أولى اختراعات علوم الحاسوب، فالمكتبة هي ببساطة مجموعة من الدوال التي يمكنك استدعاؤها من برنامجك. تتمتع المكتبة بالعديد من المزايا مثل أنه يمكنك توفير الكثير من الوقت عن طريق إعادة استخدام العمل الذي أنجزه شخص آخر، وتكون أكثر ثقة في أنها تحتوي على أخطاء أقل بسبب وجود أشخاص آخرين استخدموا هذه المكتبات مسبقًا، وبالتالي ستستفيد من عثورهم على الأخطاء وإصلاحها. تشبه المكتبة الملف القابل للتنفيذ تمامًا باستثناء استدعاء دوال المكتبة باستخدام معاملات من ملفك القابل للتنفيذ بدلًا من تشغيلها مباشرةً. المكتبات الساكنة Static Libraries الطريقة الأكثر مباشرة لاستخدام دالة المكتبة هي ربط ملفات الكائنات من المكتبة مباشرة بملفك النهائي القابل للتنفيذ كما هو الحال مع تلك الملفات التي صرَّفتها بنفسك، وعندها تُسمَّى المكتبة مكتبة ساكنة، لأن المكتبة ستبقى دون تغيير ما لم يُعاد تصريف البرنامج. تُعَد هذه الطريقة لاستخدام مكتبة الطريقة الأسهل لأن النتيجة النهائية هي ملف قابل للتنفيذ بسيط بدون اعتماديات. تُعَد المكتبة الساكنة مجموعةً من ملفات الكائنات، حيث يُحتفَظ بملفات الكائنات في سجل Archive، مما يؤدي إلى استخدام لاحقتها المعتادة ‎.a. يمكنك التفكير في هذه السجلات بوصفها ملفًا مضغوطًا ولكن بدون ضغط. يوضّح المثال التالي كيفية إنشاء مكتبة ساكنة بسيطة ويقدم بعض الأدوات الشائعة للتعامل مع المكتبات: $ cat library.c /* دالة مكتبة */ int function(int input) { return input + 10; } $ cat library.h /* تعريف الدالة */ int function(int); $ cat program.c #include <stdio.h> /* ترويسة ملف المكتبة */ #include "library.h" int main(void) { int d = function(100); printf("%d\n", d); } $ gcc -c library.c $ ar rc libtest.a library.o $ ranlib ./libtest.a $ nm --print-armap ./libtest.a Archive index: function in library.o library.o: 00000000 T function $ gcc -L . program.c -ltest -o program $ ./program 110 أولًا، نصرّف مكتبتنا إلى ملف كائن كما رأينا سابقًا. لاحظ أننا نحدد واجهة API الخاصة بالمكتبة في ترويسة الملف، حيث تتكون واجهة API من تعريفات الدوال الموجودة في المكتبة حتى يعرف المُصرِّف أنواع الدوال عند إنشاء ملفات الكائنات التي تشير إلى المكتبة مثل الملف program.c الذي يُضمَّن باستخدام ‎#include في ترويسة الملف. ننشئ سجل مكتبة باستخدام الأمر ar الذي يمثل اختصارًا للكلمة "سجل Archive". تُسبَق أسماء ملفات المكتبة الساكنة بالبادئة lib ويكون لها اللاحقة ‎.a حسب العرف المتَّبع. يخبر الوسيطُ c البرنامجَ بإنشاء السجل Archive، ويخبر a السجل بإضافة ملفات الكائنات المحددة في ملف المكتبة. تنبثق السجلات المُنشَأة باستخدام الأمر ar في أماكن مختلفة من أنظمة لينكس بخلاف إنشاء مكتبات ساكنة. أحد التطبيقات المستخدمة على نطاق واسع هي التطبيقات المُستخدَمة في صيغة حزم ‎.deb مع أنظمة دبيان Debian وأوبنتو Ubuntu وبعض أنظمة لينكس الأخرى، حيث تستخدم ملفات deb السجلات للاحتفاظ بجميع ملفات التطبيق مع بعضها البعض في ملف حزمة واحد. تستخدم حزم RedHat RPM صيغةً بديلةً ولكنها مشابهة لصيغة deb وتُسمَّى cpio. يُعَد ملف tar التطبيقَ الأساسي لحفظ الملفات مع بعضها بعضًا، وهو صيغة شائعة لتوزيع الشيفرة المصدرية. نستخدم بعد ذلك تطبيق ranlib لإنشاء ترويسة في المكتبة باستخدام رموز محتويات ملف الكائن، مما يساعد المصرِّف على الإشارة إلى الرموز بسرعة، إذ يمكن أن تبدو هذه الخطوة زائدة في حالة وجود رمز واحد فقط ، ولكن يمكن أن تحتوي مكتبة كبيرة على آلاف الرموز مما يعني أن الفهرس يمكن أن يسارع بصورة كبيرة في العثور على المراجع. نفحص هذه الترويسة الجديدة باستخدام تطبيق nm. لاحظ وجود الرمز function الخاص بالدالة function()‎ عند إزاحة بمقدار صفر كما هو متوقع. يمكنك بعد ذلك تحديد المكتبة للمصرِّف باستخدام الخيار ‎-lname حيث يكون الاسم هو اسم ملف المكتبة بدون البادئة lib. كما نوفر مجلد بحث إضافي للمكتبات وهو المجلد الحالي (‎-L .‎)، لأنه لا يمكن البحث عن المكتبات في المجلد الحالي افتراضيًا. النتيجة النهائية هي ملف قابل للتنفيذ مع المكتبة الجديدة المُضمَّنة. عيوب الربط الساكن يُعَد الربط الساكن أمرًا سهلًا للغاية، ولكن له عدد من العيوب، فهناك نوعان من العيوب الرئيسية أولهما أنه يجب عليك إعادة تصريف برنامجك إلى ملف تنفيذي جديد عند تحديث شيفرة المكتبة لإصلاح خطأ مثلًا، وثانيهما احتواء كل برنامج يستخدم تلك المكتبة في النظام على نسخة في ملفه القابل للتنفيذ. يُعَد ذلك غير فعال وخاصة إذا وجدت خطأ واضطررت إلى إعادة تصريفه. تُضمَّن مكتبة C التي هي glibc مثلًا في جميع البرامج، وتوفر جميع الدوال الشائعة مثل printf. المكتبات المشتركة تُعَد المكتبات المشتركة طريقةً للتغلب على المشاكل التي تشكّلها المكتبات الساكنة. تُحمَّل المكتبة المشتركة ديناميكيًا في وقت التشغيل لكل تطبيق يحتاجها، حيث يستخدم التطبيق مؤشرات تتطلب مكتبة معينة، وتُحمَّل المكتبة في الذاكرة وتُنفَّذ عند استدعاء الدالة. إن حُمِّلت المكتبة لتطبيق آخر، فيمكن مشاركة الشيفرة البرمجية بين التطبيقين، مما يوفر موارد كبيرة مع المكتبات شائعة الاستخدام. يُعَد الربط الديناميكي الذي تحدثنا عنه سابقًا أحد الأجزاء الأكثر تعقيدًا في نظام التشغيل الحديث. جدول البحث عن الإجراءات Procedure Lookup Table يمكن أن تحتوي المكتبات على العديد من الدوال، ويمكن أن يحتوي البرنامج على العديد من المكتبات لإنجاز عمله. يستخدم البرنامج دالة أو دالتين فقط من كل مكتبة من المكتبات المتعددة المتاحة، ويمكن أن تستخدم الشيفرة البرمجية بعض الدوال دون غيرها اعتمادًا على مسار وقت التشغيل. تحتوي عملية الربط الديناميكي Dynamic Linking الكثير من العمليات الحسابية، لأنها تتضمن النظر والبحث عبر العديد من الجداول، لذا يمكن تحسين الأداء عند تطبيق أيّ شيء لتقليل هذا الحِمل الناتج عن هذه العمليات الحسابية الكثيرة. يسهّل جدول البحث عن الإجراءات Procedure Lookup Table -أو PLT اختصارًا- ما يسمى بالارتباط الكسول Lazy Binding في البرامج، حيث يُعَد الارتباط Binding مرادفًا لعملية إصلاح المتغيرات الموجودة في جدول GOT الموضحة سابقًا، إذ يُقال أن المدخلة مرتبطة بعنوانها الفعلي عند إصلاحها. يتضمن البرنامج في بعض الأحيان دالةً من مكتبة، ولكنه لا يستدعيها أبدًا اعتمادًا على دخل المستخدم. تحتوي عملية الارتباط الخاصة بهذه الدالة الكثير من العمليات لتطبيقها، لأنها تتضمن تحميل الشيفرة البرمجية والبحث في الجداول والكتابة في الذاكرة، لذا تُعَد المتابعة في عملية ارتباط دالة غير مُستخدَمة مضيعة للوقت، حيث يؤجِّل الارتباط الكسول هذه العملية حتى يستدعي جدولُ PLT الدالةَ الفعلية. لكل دالة في مكتبةٍ مدخلةٌ في جدول PLT تؤشّر في البداية إلى بعض الشيفرات البرمجية الوهمية Dummy Code الخاصة. إن استدعى البرنامج الدالة، فهذا يعني أنه يستدعي مدخلة من جدول PLT باستخدام الطريقة نفسها للإشارة إلى المتغيرات في جدول GOT نفسها. تحمّل هذه الدالة الوهمية بعض المعاملات التي تريد تمريرها إلى الرابط الديناميكي لتتمكن من تحليل الدالة ثم استدعاء دالة بحث خاصة بالرابط الديناميكي. يجد الرابط الديناميكي عنوان الدالة الفعلي، ويكتب هذا الموقع في استدعاء الملف الثنائي في أعلى استدعاء الدالة الوهمية، وبالتالي يمكن تحميل العنوان دون الحاجة إلى العودة إلى المحمل الديناميكي مرة أخرى في المرة التالية لاستدعاء الدالة. إن لم تُستدعَى دالةٌ مطلقًا، فلن تُعدَّل مدخلة جدول PLT أبدًا ولكن لن يكون هناك وقت تشغيل إضافي. كيفية عمل جدول PLT يجب أن تبدأ الآن في إدراك أن هناك قدرًا لا بأس به من العمل في تحليل رمز ديناميكي. لنطلع على تطبيق "hello World" البسيط الذي يجري استدعاء مكتبة واحد فقط هو استدعاء الدالة printf لعرض السلسلة النصية للمستخدم كما يلي: $ cat hello.c #include <stdio.h> int main(void) { printf("Hello, World!\n"); return 0; } $ gcc -o hello hello.c $ readelf --relocs ./hello Relocation section '.rela.dyn' at offset 0x3f0 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 6000000000000ed8 000700000047 R_IA64_FPTR64LSB 0000000000000000 _Jv_RegisterClasses + 0 6000000000000ee0 000900000047 R_IA64_FPTR64LSB 0000000000000000 __gmon_start__ + 0 Relocation section '.rela.IA_64.pltoff' at offset 0x420 contains 3 entries: Offset Info Type Sym. Value Sym. Name + Addend 6000000000000f10 000200000081 R_IA64_IPLTLSB 0000000000000000 printf + 0 6000000000000f20 000800000081 R_IA64_IPLTLSB 0000000000000000 __libc_start_main + 0 6000000000000f30 000900000081 R_IA64_IPLTLSB 0000000000000000 __gmon_start__ + 0 يمكننا أن نرى في المثال السابق أن لدينا الانتقال R_IA64_IPLTLSB للرمز printf الذي يمثل وضع عنوان رمز هذه الدالة في عنوان الذاكرة 0x6000000000000f10. يجب أن نبدأ في البحث بصورة أعمق للعثور على الإجراء الدقيق الذي يعطينا الدالة. سنلقي في المثال التالي نظرة على تفكيك الدالة الرئيسية main()‎ الخاصة بالبرنامج: 4000000000000790 <main>: 4000000000000790: 00 08 15 08 80 05 [MII] alloc r33=ar.pfs,5,4,0 4000000000000796: 20 02 30 00 42 60 mov r34=r12 400000000000079c: 04 08 00 84 mov r35=r1 40000000000007a0: 01 00 00 00 01 00 [MII] nop.m 0x0 40000000000007a6: 00 02 00 62 00 c0 mov r32=b0 40000000000007ac: 81 0c 00 90 addl r14=72,r1;; 40000000000007b0: 1c 20 01 1c 18 10 [MFB] ld8 r36=[r14] 40000000000007b6: 00 00 00 02 00 00 nop.f 0x0 40000000000007bc: 78 fd ff 58 br.call.sptk.many b0=4000000000000520 <_init+0xb0> 40000000000007c0: 02 08 00 46 00 21 [MII] mov r1=r35 40000000000007c6: e0 00 00 00 42 00 mov r14=r0;; 40000000000007cc: 01 70 00 84 mov r8=r14 40000000000007d0: 00 00 00 00 01 00 [MII] nop.m 0x0 40000000000007d6: 00 08 01 55 00 00 mov.i ar.pfs=r33 40000000000007dc: 00 0a 00 07 mov b0=r32 40000000000007e0: 1d 60 00 44 00 21 [MFB] mov r12=r34 40000000000007e6: 00 00 00 02 00 80 nop.f 0x0 40000000000007ec: 08 00 84 00 br.ret.sptk.many b0;; يجب أن يكون استدعاء العنوان 0x4000000000000520 هو استدعاء الدالة printf، حيث يمكننا معرفة مكان هذا العنوان من خلال الاطلاع الأقسام Sections باستخدام الأداة readelf كما يلي: $ readelf --sections ./hello There are 40 section headers, starting at offset 0x25c0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 ... [11] .plt PROGBITS 40000000000004c0 000004c0 00000000000000c0 0000000000000000 AX 0 0 32 [12] .text PROGBITS 4000000000000580 00000580 00000000000004a0 0000000000000000 AX 0 0 32 [13] .fini PROGBITS 4000000000000a20 00000a20 0000000000000040 0000000000000000 AX 0 0 16 [14] .rodata PROGBITS 4000000000000a60 00000a60 000000000000000f 0000000000000000 A 0 0 8 [15] .opd PROGBITS 4000000000000a70 00000a70 0000000000000070 0000000000000000 A 0 0 16 [16] .IA_64.unwind_inf PROGBITS 4000000000000ae0 00000ae0 00000000000000f0 0000000000000000 A 0 0 8 [17] .IA_64.unwind IA_64_UNWIND 4000000000000bd0 00000bd0 00000000000000c0 0000000000000000 AL 12 c 8 [18] .init_array INIT_ARRAY 6000000000000c90 00000c90 0000000000000018 0000000000000000 WA 0 0 8 [19] .fini_array FINI_ARRAY 6000000000000ca8 00000ca8 0000000000000008 0000000000000000 WA 0 0 8 [20] .data PROGBITS 6000000000000cb0 00000cb0 0000000000000004 0000000000000000 WA 0 0 4 [21] .dynamic DYNAMIC 6000000000000cb8 00000cb8 00000000000001e0 0000000000000010 WA 5 0 8 [22] .ctors PROGBITS 6000000000000e98 00000e98 0000000000000010 0000000000000000 WA 0 0 8 [23] .dtors PROGBITS 6000000000000ea8 00000ea8 0000000000000010 0000000000000000 WA 0 0 8 [24] .jcr PROGBITS 6000000000000eb8 00000eb8 0000000000000008 0000000000000000 WA 0 0 8 [25] .got PROGBITS 6000000000000ec0 00000ec0 0000000000000050 0000000000000000 WAp 0 0 8 [26] .IA_64.pltoff PROGBITS 6000000000000f10 00000f10 0000000000000030 0000000000000000 WAp 0 0 16 [27] .sdata PROGBITS 6000000000000f40 00000f40 0000000000000010 0000000000000000 WAp 0 0 8 [28] .sbss NOBITS 6000000000000f50 00000f50 0000000000000008 0000000000000000 WA 0 0 8 [29] .bss NOBITS 6000000000000f58 00000f50 0000000000000008 0000000000000000 WA 0 0 8 [30] .comment PROGBITS 0000000000000000 00000f50 00000000000000b9 0000000000000000 0 0 1 [31] .debug_aranges PROGBITS 0000000000000000 00001010 0000000000000090 0000000000000000 0 0 16 [32] .debug_pubnames PROGBITS 0000000000000000 000010a0 0000000000000025 0000000000000000 0 0 1 [33] .debug_info PROGBITS 0000000000000000 000010c5 00000000000009c4 0000000000000000 0 0 1 [34] .debug_abbrev PROGBITS 0000000000000000 00001a89 0000000000000124 0000000000000000 0 0 1 [35] .debug_line PROGBITS 0000000000000000 00001bad 00000000000001fe 0000000000000000 0 0 1 [36] .debug_str PROGBITS 0000000000000000 00001dab 00000000000006a1 0000000000000001 MS 0 0 1 [37] .shstrtab STRTAB 0000000000000000 0000244c 000000000000016f 0000000000000000 0 0 1 [38] .symtab SYMTAB 0000000000000000 00002fc0 0000000000000b58 0000000000000018 39 60 8 [39] .strtab STRTAB 0000000000000000 00003b18 0000000000000479 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) يوجد هذا العنوان في القسم ‎.plt كما هو متوقع حيث يوجد استدعاؤها في جدول PLT. لكن لنواصل البحث أكثر ولنفكك القسم ‎.plt لنرى ما يفعله هذا الاستدعاء كما يلي: 40000000000004c0 <.plt>: 40000000000004c0: 0b 10 00 1c 00 21 [MMI] mov r2=r14;; 40000000000004c6: e0 00 08 00 48 00 addl r14=0,r2 40000000000004cc: 00 00 04 00 nop.i 0x0;; 40000000000004d0: 0b 80 20 1c 18 14 [MMI] ld8 r16=[r14],8;; 40000000000004d6: 10 41 38 30 28 00 ld8 r17=[r14],8 40000000000004dc: 00 00 04 00 nop.i 0x0;; 40000000000004e0: 11 08 00 1c 18 10 [MIB] ld8 r1=[r14] 40000000000004e6: 60 88 04 80 03 00 mov b6=r17 40000000000004ec: 60 00 80 00 br.few b6;; 40000000000004f0: 11 78 00 00 00 24 [MIB] mov r15=0 40000000000004f6: 00 00 00 02 00 00 nop.i 0x0 40000000000004fc: d0 ff ff 48 br.few 40000000000004c0 <_init+0x50>;; 4000000000000500: 11 78 04 00 00 24 [MIB] mov r15=1 4000000000000506: 00 00 00 02 00 00 nop.i 0x0 400000000000050c: c0 ff ff 48 br.few 40000000000004c0 <_init+0x50>;; 4000000000000510: 11 78 08 00 00 24 [MIB] mov r15=2 4000000000000516: 00 00 00 02 00 00 nop.i 0x0 400000000000051c: b0 ff ff 48 br.few 40000000000004c0 <_init+0x50>;; 4000000000000520: 0b 78 40 03 00 24 [MMI] addl r15=80,r1;; 4000000000000526: 00 41 3c 70 29 c0 ld8.acq r16=[r15],8 400000000000052c: 01 08 00 84 mov r14=r1;; 4000000000000530: 11 08 00 1e 18 10 [MIB] ld8 r1=[r15] 4000000000000536: 60 80 04 80 03 00 mov b6=r16 400000000000053c: 60 00 80 00 br.few b6;; 4000000000000540: 0b 78 80 03 00 24 [MMI] addl r15=96,r1;; 4000000000000546: 00 41 3c 70 29 c0 ld8.acq r16=[r15],8 400000000000054c: 01 08 00 84 mov r14=r1;; 4000000000000550: 11 08 00 1e 18 10 [MIB] ld8 r1=[r15] 4000000000000556: 60 80 04 80 03 00 mov b6=r16 400000000000055c: 60 00 80 00 br.few b6;; 4000000000000560: 0b 78 c0 03 00 24 [MMI] addl r15=112,r1;; 4000000000000566: 00 41 3c 70 29 c0 ld8.acq r16=[r15],8 400000000000056c: 01 08 00 84 mov r14=r1;; 4000000000000570: 11 08 00 1e 18 10 [MIB] ld8 r1=[r15] 4000000000000576: 60 80 04 80 03 00 mov b6=r16 400000000000057c: 60 00 80 00 br.few b6;; إذًا لنمر على التعليمات، حيث أضفنا أولًا القيمة 80 إلى القيمة الموجودة في المسجّل r1، وخزّناها في المسجّل r15. سيؤشّر المسجل r1 إلى جدول GOT، مما يعني تخزين المسجل r15 الذي يحتوي على 80 بايت في جدول GOT. ثانيًا، حمّلنا القيمة المخزنة في هذا الموقع من جدول GOT إلى المسجّل r16، ثم زدنا القيمة الموجودة في المسجل r15 بمقدار 8 بايتات. ثالثًا، خزّنا المسجّل r1 -أو موقع جدول GOT- في المسجّل r14 وضبطنا القيمة الموجودة في المسجل r1 لتكون القيمة الموجودة في 8 بايتات التالية للمسجّل r15، ثم نتفرّع إلى المسجل r16. ناقشنا سابقًا كيفية استدعاء الدوال باستخدام واصف الدالة Function Descriptor الذي يحتوي على عنوان الدالة وعنوان المؤشر العام. يمكننا أن نرى أن مدخلة جدول PLT تحمّل أولًا قيمة الدالة، مما يؤدي إلى الانتقال بمقدار 8 بايتات إلى الجزء الثاني من واصف الدالة ثم تحميل تلك القيمة في مسجّل العملية Op Register قبل استدعاء الدالة. نعلم أن المسجل r1 سيؤشّر إلى جدول GOT، ثم سنذهب بمقدار 80 بايت بعد جدول GOT أي بمقدار (0x50). $ objdump --disassemble-all ./hello Disassembly of section .got: 6000000000000ec0 <.got>: ... 6000000000000ee8: 80 0a 00 00 00 00 data8 0x02a000000 6000000000000eee: 00 40 90 0a dep r0=r0,r0,63,1 6000000000000ef2: 00 00 00 00 00 40 [MIB] (p20) break.m 0x1 6000000000000ef8: a0 0a 00 00 00 00 data8 0x02a810000 6000000000000efe: 00 40 50 0f br.few 6000000000000ef0 <_GLOBAL_OFFSET_TABLE_+0x30> 6000000000000f02: 00 00 00 00 00 60 [MIB] (p58) break.m 0x1 6000000000000f08: 60 0a 00 00 00 00 data8 0x029818000 6000000000000f0e: 00 40 90 06 br.few 6000000000000f00 <_GLOBAL_OFFSET_TABLE_+0x40> Disassembly of section .IA_64.pltoff: 6000000000000f10 <.IA_64.pltoff>: 6000000000000f10: f0 04 00 00 00 00 [MIB] (p39) break.m 0x0 6000000000000f16: 00 40 c0 0e 00 00 data8 0x03b010000 6000000000000f1c: 00 00 00 60 data8 0xc000000000 6000000000000f20: 00 05 00 00 00 00 [MII] (p40) break.m 0x0 6000000000000f26: 00 40 c0 0e 00 00 data8 0x03b010000 6000000000000f2c: 00 00 00 60 data8 0xc000000000 6000000000000f30: 10 05 00 00 00 00 [MIB] (p40) break.m 0x0 6000000000000f36: 00 40 c0 0e 00 00 data8 0x03b010000 6000000000000f3c: 00 00 00 60 data8 0xc000000000 إذا أضفنا القيمة 0x50 إلى العنوان 0x6000000000000ec0، فسنصل إلى العنوان 0x6000000000000f10 أو القسم ‎.IA_64.pltoff. يمكننا فك شيفرة خرج البرنامج objdump لنتمكّن من رؤية ما جرى تحميله بالضبط. يؤدي تبديل ترتيب البايت لأول 8 بايتات f0 04 00 00 00 00 00 40 إلى الحصول على العنوان 0x4000000000004f0، إذ يبدو هذا العنوان مألوفًا، حيث إذا نظرنا إلى الوراء في ناتج التجميع الخاص بجدول PLT ، فسنرى ذلك العنوان. أولًا تضع الشيفرة البرمجية الموجودة عند العنوان 0x4000000000004f0 قيمة صفرية في المسجل r15، ثم تتفرع مرة أخرى إلى العنوان 0x40000000000004c0، ولكن يُعَد هذا العنوان بداية القسم PLT. يمكننا تتبّع هذه الشيفرة البرمجية، إذ نحفظ أولًا قيمة المؤشر العام في المسجل r2، ثم نحمل ثلاث قيم بحجم 8 بايتات في المسجلات r16 وr17 وr1، ثم نتفرع إلى العنوان الموجود في المسجل r17، حيث يمثّل تلك العملية الاستدعاء الفعلي للرابط الديناميكي. يجب أن نتعمق قليلًا في فهم واجهة ABI التي تعطينا مفهومين لنفهم بالضبط ما يجري تحميله الآن، وهذا المفهومان هما أنه يجب أن تحتوي البرامج المرتبطة ديناميكيًا على قسم خاص يسمى القسم DT_IA_64_PLT_RESERVE الذي يمكنه الاحتفاظ بثلاث قيم بحجم 8 بايتات، ويوجد مؤشر في مكان وجود هذه المنطقة المحجوزة في المقطع الديناميكي للملف الثنائي الموضّح في المثال التالي: Dynamic segment at offset 0xcb8 contains 25 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6.1] 0x000000000000000c (INIT) 0x4000000000000470 0x000000000000000d (FINI) 0x4000000000000a20 0x0000000000000019 (INIT_ARRAY) 0x6000000000000c90 0x000000000000001b (INIT_ARRAYSZ) 24 (bytes) 0x000000000000001a (FINI_ARRAY) 0x6000000000000ca8 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x0000000000000004 (HASH) 0x4000000000000200 0x0000000000000005 (STRTAB) 0x4000000000000330 0x0000000000000006 (SYMTAB) 0x4000000000000240 0x000000000000000a (STRSZ) 138 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000015 (DEBUG) 0x0 0x0000000070000000 (IA_64_PLT_RESERVE) 0x6000000000000ec0 -- 0x6000000000000ed8 0x0000000000000003 (PLTGOT) 0x6000000000000ec0 0x0000000000000002 (PLTRELSZ) 72 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x4000000000000420 0x0000000000000007 (RELA) 0x40000000000003f0 0x0000000000000008 (RELASZ) 48 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000006ffffffe (VERNEED) 0x40000000000003d0 0x000000006fffffff (VERNEEDNUM) 1 0x000000006ffffff0 (VERSYM) 0x40000000000003ba 0x0000000000000000 (NULL) 0x0 لاحظ أننا حصلنا على قيمة جدول GOT نفسه، وهذا يعني أن أول ثلاث مدخلات بحجم 8 بايتات في جدول GOT تمثل المنطقة المحجوزة، وبالتالي سيُؤشَّر إليها دائمًا باستخدام المؤشر العام. يجب أن يملأ الرابط الديناميكي هذه القيم عند بدء تشغيله، حيث تحدّد واجهة ABI أنه يجب ملء القيمة الأولى بواسطة الرابط الديناميكي الذي يمنح هذه الوحدة معرفًا فريدًا، والقيمة الثانية هي قيمة المؤشر العام للرابط الديناميكي، والقيمة الثالثة هي عنوان الدالة التي تبحث عن الرمز وتصلحه. يوضّح المثال التالي شيفرة برمجية في الرابط الديناميكي لإعداد قيم خاصة من المكتبة libc أو من sysdeps/ia64/dl-machine.h: ‫/* ‫إعداد الكائن المحمَّل الموصوف باستخدام المتغير L حتى تقفز مدخلات جدول PLT التي ليس لها انتقالات إلى ‫شيفرة الإصلاح البرمجية عند الطلب في ملف dl-runtime.c. */ static inline int __attribute__ ((unused, always_inline)) elf_machine_runtime_setup (struct link_map *l, int lazy, int profile) { extern void _dl_runtime_resolve (void); extern void _dl_runtime_profile (void); if (lazy) { register Elf64_Addr gp __asm__ ("gp"); Elf64_Addr *reserve, doit; /* * ‫احذر من تبديل الأنواع Typecast هنا أو ستُضاف عناصر مؤشر l-l_addr */ reserve = ((Elf64_Addr *) (l->l_info[DT_IA_64 (PLT_RESERVE)]->d_un.d_ptr + l->l_addr)); /* تعريف هذا الكائن المشترك */ reserve[0] = (Elf64_Addr) l; ‫/* ستُستدعَى هذه الدالة لتطبيق الانتقال‫ Relocation */ if (!profile) doit = (Elf64_Addr) ((struct fdesc *) &_dl_runtime_resolve)->ip; else { if (GLRO(dl_profile) != NULL && _dl_name_match_p (GLRO(dl_profile), l)) { ‫/* ‫هذا هو الكائن الذي نبحث عنه. لنفترض أننا نريد استخدام التشخيص Profiling مع بدء المؤقتات */ GL(dl_profile_map) = l; } doit = (Elf64_Addr) ((struct fdesc *) &_dl_runtime_profile)->ip; } reserve[1] = doit; reserve[2] = gp; } return lazy; } يمكننا أن نرى كيفية إعداد هذه القيم بواسطة الرابط الديناميكي من خلال النظر في الدالة التي تطبّق ذلك للملف الثنائي. يُضبَط المتغير reserve من مؤشر القسم PLT_RESERVE في الملف الثنائي. تمثل القيمة الفريدة الموضوعة في reserve[0]‎ عنوان خارطة الربط Link Map لهذا الكائن، حيث تُعَد خارطة الربط التمثيل الداخلي ضمن مكتبة glibc للكائنات المشتركة. نضع بعد ذلك عنوان الدالة ‎_dl_runtime_resolve في القيمة الثانية بافتراض أننا لا نستخدم عملية التشخيص Profiling، ثم تُضبط قيمة reserve[2]‎ على gp التي يمكن العثور عليها في المسجل r2 باستخدام الاستدعاء __asm__. إذا عدنا إلى الوراء في واجهة ABI، فسنرى أنه يجب وضع فهرس انتقال للمدخلة في المسجل r15 ويجب تمرير المعرّف الفريد في المسجل r16. ضُبِط المسجل r15 مسبقًا في الشيفرة الاختبارية Stub Code قبل العودة إلى بداية جدول PLT. ألقِ نظرة على المدخلات، ولاحظ كيف تحمِّل كل مدخلة في جدول PLT المسجل r15 مع قيمة متزايدة، إذ لا ينبغي أن يكون ذلك مفاجئًا إذا نظرت إلى عمليات الانتقال، حيث يكون لانتقال الدالة printf العدد صفر. نحمّل المسجل r16 من القيم التي هيّأها الرابط الديناميكي، ثم يمكننا تحميل عنوان الدالة والمؤشر العام والفرع في الدالة، ثم نشغّل دالة الرابط الديناميكي ‎_dl_runtime_resolve التي تعثر على الانتقال. يستخدم الانتقال اسم الرمز الذي حدّده للعثور على الدالة الصحيحة، حيث يمكن يتضمن ذلك تحميل المكتبة من القرص الصلب إن لم تكن موجودة في الذاكرة، وإلّا فيجب مشاركة الشيفرة البرمجية. يوفر سجلُ الانتقال للرابط الديناميكي العنوانَ الذي يجب إصلاحه، حيث كان هذا العنوان موجودًا في جدول GOT ثم حمّلته شيفرة PLT الاختبارية، وهذا يعني أنه يمكن الحصول على عنوان الدالة المباشر أو ما يسمى بتقصير دورة الرابط الديناميكي بعد المرة الأولى التي تُستدعَى فيها الدالة أي في المرة الثانية لتحميلها. رأينا الآلية الدقيقة لعمل جدول PLT والعمل الداخلي للرابط الديناميكي. النقاط المهمة التي يجب تذكرها هي: تستدعي استدعاءات المكتبة في برنامجك الشيفرة الاختبارية في جدول PLT الخاص بالملف الثنائي. تحمّل هذه الشيفرة الاختبارية عنوانًا وتقفز إليه. يؤشّر هذا العنوان إلى دالةٍ في الرابط الديناميكي قادرةٍ على البحث عن الدالة الحقيقية من خلال النظر إلى المعلومات الواردة في مدخلة الانتقال لتلك الدالة. يعيد الرابط الديناميكي كتابة العنوان الذي تقرأه الشيفرة الاختبارية، بحيث تنتقل الدالة مباشرة إلى العنوان الصحيح في المرة التالية لاستدعائها. ترجمة -وبتصرُّف- للقسمين Libraries و Libraries من الفصلين Behind the process و Dynamic Linking من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand. اقرأ أيضا المقال السابق: مفهوم الربط الديناميكي Dynamic Linking في معمارية الحاسوب المقال التالي: طريقة عمل الرابط الديناميكي مع المكتبات في معمارية الحاسوب أنظمة المعالجات في معمارية الحاسوب تعرف على وحدة المعالجة المركزية وعملياتها في معمارية الحاسوب أهم المفاهيم التي تنظم العمليات وتعالجها في معمارية الحاسوب الحديثة دور نظام التشغيل وتنظيمه في معمارية الحاسوب
  20. تُعَد شيفرة نظام التشغيل البرمجية للقراءة فقط وتكون منفصلة عن البيانات، لذا إن لم تتمكن البرامج من تعديل الشيفرة البرمجية مع وجود كميات كبيرة من الشيفرة البرمجية المشتركة أمرًا منطقيًا، إذ يجب مشاركتها بين العديد من الملفات القابلة للتنفيذ بدلًا من تكرارها لكل ملف منها. يمكن تطبيق ذلك بسهولة باستخدام الذاكرة الوهمية، إذ يمكن الرجوع بسهولة إلى صفحات الذاكرة الحقيقية التي جرى تحميل شيفرة المكتبة البرمجية إليها من خلال عدد من الصفحات الوهمية في عددٍ من فضاءات العناوين. لذا يمكن لكل عملية الوصول إلى شيفرة المكتبة البرمجية باستخدام أيّ عنوان وهمي تريده، بينما يكون لديك نسخة حقيقية واحدة فقط من هذه الشيفرة في ذاكرة النظام. وبذلك توصل المبرمجون بسرعة إلى فكرة المكتبة المشتركة Shared Library التي -كما يوحي الاسم- يمكن مشاركتها بين العديد من الملفات القابلة للتنفيذ. يحتوي كل ملف قابل للتنفيذ على مرجع يقول: "أحتاج مكتبة Foo مثلًا"، حيث يُترَك الأمر للنظام عند تحميل البرنامج للتحقق من وجود برنامج آخر حمّل شيفرة هذه المكتبة في الذاكرة ثم مشاركتها من خلال ربط صفحات الملف القابل للتنفيذ مع الذاكرة الحقيقية، أو يمكنه تحميل المكتبة في ذاكرة الملف القابل للتنفيذ. تسمى هذه العملية بالربط الديناميكي Dynamic Linking، لأنها تطبّق جزءًا من عملية الربط مباشرةً عند تنفيذ البرامج في النظام. تفاصيل المكتبة الديناميكية تشبه المكتبات إلى حدٍ كبير برنامجًا لا يُشغَّل أبدًا، إذ لديها قسم الشيفرة البرمجية وقسم البيانات (الدوال والمتغيرات) تمامًا مثل الملفات القابل للتنفيذ، ولكن لا يمكن تشغيلها، فهي توفر فقط مكتبة من الدوال للمطورين لاستدعائها. لذا يمكن ملف ELF أن يمثل مكتبة ديناميكية تمامًا كما يمثل ملفًا قابلًا للتنفيذ مع وجود بعض الاختلافات الأساسية مثل عدم وجود مؤشر للمكان الذي يجب أن يبدأ فيه التنفيذ، ولكن تُعَد جميع المكتبات المشتركة مجرد كائنات بصيغة ELF مثل أي ملف آخر قابل للتنفيذ. تحتوي ترويسة ملف ELF على رايتين حصريتين هما ET_EXEC وET_DYN لتمييز ملف ELF بوصفه ملفًا قابلًا للتنفيذ أو ملفَ كائن مشترك. تضمين المكتبات في ملف قابل للتنفيذ يمكن تضمين المكتبات في ملف قابل للتنفيذ ولكن يجب أن نراعي مسألتين مهمتين متعلقتين بالمصرِّف والرابط الديناميكي كما سنوضح الآن. التصريف Compilation تمتلك ملفات الكائنات مراجعًا إلى دوال المكتبة تمامًا كما هو الحال مع أيّ مرجع خارجي آخر عندما تصرِّف برنامجك الذي يستخدم مكتبة ديناميكية. يجب تضمين ترويسة المكتبة ليعرف المصرِّف الأنواع المحددة للدوال التي تستدعيها، إذ يحتاج المصرِّف فقط معرفةَ الأنواع المرتبطة بالدالة (مثل أن تأخذ الدالة النوع int وتعيد النوع char *‎) بحيث يمكنه تخصيص مساحة لاستدعاء الدالة بصورة صحيحة. لم يكن هذا هو الحال دائمًا مع معايير لغة C، إذ افترضت المصِّرفات سابقًا أن أيّ دالة غير معروفة تعيد قيمة من النوع int. يكون لحجم المؤشر في نظام 32 بت حجم النوع int نفسه، لذلك لا توجد مشكلة في ذلك، ولكن يكون حجم المؤشر ضعف حجم int في نظام 64 بت، لذلك إذا أعادت الدالة مؤشرًا، فستُدمَّر قيمتها. يُعَد ذلك الأمر غير مقبول، لأن المؤشر لن يؤشّر إلى ذاكرة صالحة، ولكن تغيّر معيار C99 بحيث يُطلَب منك تحديد أنواع الدوال المُضمَّنة. الربط Linking يطبّق الرابط الديناميكي الكثير من العمل للمكتبات المشتركة، ولكن لا يزال الرابط التقليدي يلعب دورًا هامًا في إنشاء الملف القابل للتنفيذ، إذ يجب أن يضع الرابط التقليدي مؤشرًا في الملف القابل للتنفيذ حتى يعرف الرابط الديناميكي المكتبة التي ستحقّق الاعتماديات Dependencies في وقت التشغيل. يتطلب القسم dynamic من الملف القابل للتنفيذ مدخلة مطلوبة NEEDED لكل مكتبة مشتركة يعتمد عليها الملف القابل للتنفيذ. يمكننا فحص هذه الحقول باستخدام برنامج readelf. سنلقي فيما يلي نظرة على ملف ثنائي معياري ‎/bin/ls يمثل تحديد المكتبات الديناميكية: $ readelf --dynamic /bin/ls Dynamic segment at offset 0x22f78 contains 27 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [librt.so.1] 0x0000000000000001 (NEEDED) Shared library: [libacl.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6.1] 0x000000000000000c (INIT) 0x4000000000001e30 ... snip ... يمكنك أن ترى أن المثال السابق يحدد ثلاث مكتبات. المكتبة الأكثر شيوعًا التي تتشارك بها أغلبية البرامج الموجودة على النظام -إن لم تكن جميعها- هي مكتبة libc، وهناك بعض المكتبات الأخرى التي يحتاجها البرنامج ليعمل بصورة صحيحة. تكون قراءة ملف ELF المباشرة مفيدةً أحيانًا، ولكن الطريقة المعتادة لفحص ملف قابل للتنفيذ مرتبط ديناميكيًا هي باستخدام أداة ldd التي تمر على اعتماديات المكتبات، حيث ستظهِر لك إذا كانت المكتبة معتمدة على مكتبة أخرى كما يلي: $ ldd /bin/ls librt.so.1 => /lib/tls/librt.so.1 (0x2000000000058000) libacl.so.1 => /lib/libacl.so.1 (0x2000000000078000) libc.so.6.1 => /lib/tls/libc.so.6.1 (0x2000000000098000) libpthread.so.0 => /lib/tls/libpthread.so.0 (0x20000000002e0000) /lib/ld-linux-ia64.so.2 => /lib/ld-linux-ia64.so.2 (0x2000000000000000) libattr.so.1 => /lib/libattr.so.1 (0x2000000000310000) $ readelf --dynamic /lib/librt.so.1 Dynamic segment at offset 0xd600 contains 30 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6.1] 0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0] ... snip ... يمكننا أن نرى في المثال السابق أن مكتبة libpthread مطلوبة من مكان ما، حيث إذا تعمّقنا قليلًا، فيمكننا أن نرى أنها مطلوبة من المكتبة librt. الرابط الديناميكي Dynamic Linker الرابط الديناميكي هو البرنامج الذي يدير المكتبات الديناميكية المشتركة بدلًا من الملف القابل للتنفيذ، ويعمل على تحميل المكتبات في الذاكرة وتعديل البرنامج في وقت التشغيل لاستدعاء الدوال الموجودة في المكتبة. يسمح ملف ELF للملفات القابلة للتنفيذ بتحديد المفسّر Interpreter الذي هو برنامج يجب استخدامه لتشغيل الملف القابل للتنفيذ. يضبط المصرِّف Compiler والرابط الساكن Static Linker مفسّر الملفات القابلة للتنفيذ الذي يعتمد على المكتبات الديناميكية ليكون الرابط الديناميكي. يوضَح المثال التالي كيفية التحقق من مفسّر البرنامج: ianw@lime:~/programs/csbu$ readelf --headers /bin/ls Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x4000000000000040 0x4000000000000040 0x0000000000000188 0x0000000000000188 R E 8 INTERP 0x00000000000001c8 0x40000000000001c8 0x40000000000001c8 0x0000000000000018 0x0000000000000018 R 1 [Requesting program interpreter: /lib/ld-linux-ia64.so.2] LOAD 0x0000000000000000 0x4000000000000000 0x4000000000000000 0x0000000000022e40 0x0000000000022e40 R E 10000 LOAD 0x0000000000022e40 0x6000000000002e40 0x6000000000002e40 0x0000000000001138 0x00000000000017b8 RW 10000 DYNAMIC 0x0000000000022f78 0x6000000000002f78 0x6000000000002f78 0x0000000000000200 0x0000000000000200 RW 8 NOTE 0x00000000000001e0 0x40000000000001e0 0x40000000000001e0 0x0000000000000020 0x0000000000000020 R 4 IA_64_UNWIND 0x0000000000022018 0x4000000000022018 0x4000000000022018 0x0000000000000e28 0x0000000000000e28 R 8 يمكنك أن ترى في المثال السابق أن المفسّر مضبوط ليكون ‎/lib/ld-linux-ia64.so.2 أي يمثل الرابط الديناميكي. تتحقق النواة Kernel عندما تحمّل الملف الثنائي للتنفيذ مما إذا كان الحقل PT_INTERP موجودًا، حيث إذا كان موجودًا، فيجب تحميل ما يؤشّر إليه في الذاكرة وتشغيله. ذكرنا أن للملفات القابلة للتنفيذ المرتبطة ديناميكيًا مراجع يجب إصلاحها باستخدام معلومات غير متوفرة حتى وقت التشغيل مثل عنوان دالة موجودة في مكتبة مشتركة، وتسمَّى هذه المراجع بالانتقالات Relocations. الانتقالات Relocations يتمثل الجزء الأساسي من الرابط الديناميكي في إصلاح العناوين في وقت التشغيل الذي يُعَد المرة الوحيدة التي يمكنك أن تعرف فيها مكان تحميلك في الذاكرة بالضبط. يمكن التفكير في الانتقالات بأنها ملاحظة أن عنوانًا معينًا يجب إصلاحه في وقت التحميل، حيث يجب قراءة جميع الانتقالات وإصلاح العناوين التي تشير إليها للإشارة إلى المكان الصحيح قبل أن تصبح الشيفرة البرمجية جاهزة للتشغيل. إليك مثال عن عملية انتقال: العنوان الحدث 0x123456 عنوان الرمز "x" 0x564773 الدالة X هناك العديد من أنواع الانتقالات لكل معمارية، حيث يُوثَّق السلوك الدقيق لكل نوع بوصفه جزءًا من واجهة ABI الخاصة بالنظام. يُعَد تعريف الانتقالات واضحًا وسهلًا كما في المثال التالي: typedef struct { Elf32_Addr r_offset; <--- address to fix Elf32_Word r_info; <--- symbol table pointer and relocation type } typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend; } Elf32_Rela يشير الحقل r_offset إلى الإزاحة في الملف التي يجب إصلاحها، ويحدد الحقل r_info نوع الانتقال الذي يصِف بالضبط ما يجب تطبيقه لإصلاح هذه الشيفرة البرمجية. تُعَد قيمة الرمز أبسط عملية انتقال مُعرَّفة لمعماريةٍ ما، حيث يمكنك ببساطة في هذه الحالة استبدال عنوان الرمز في الموقع المحدد، وبالتالي سيكتمل إصلاح عملية الانتقال. يوجد نوعان من الطرق المُستخدمة لتشغيل الانتقالات أحدهما مع قيمة مُضافة والآخر بدونها. القيمة المضافة هي ببساطة شيء يجب إضافته إلى العنوان الذي جرى إصلاحه للعثور على العنوان الصحيح، فمثلًا إذا كان الانتقال للرمز i مثلًا، فستُضبَط القيمة المُضافة على القيمة 8 لأن الشيفرة البرمجية الأصلية تطبّق شيئًا مثل i[8]‎، وهذا يعني العثور على عنوان i ثم تجاوزه بمقدار 8. يجب تخزين هذه القيمة المضافة في مكان ما، فمثلًا تُخزَّن في صيغة REL القيمة المُضافة في شيفرة البرنامج ضمن المكان الذي يجب أن يكون فيه العنوان الذي جرى إصلاحه. لذا يجب إصلاح العنوان بصورة صحيحة من خلال قراءة الذاكرة التي تريد إصلاحها للحصول على أيّ قيمة مضافة وتخزينها والعثور على العنوان الحقيقي وإضافة القيمة المضافة إليه ثم كتابته مرة أخرى فوق القيمة المضافة. بينما تحدد الصيغة RELA مكان القيمة المضافة في الانتقال مباشرةً. هناك بعض المقايضات لكلٍ من هاتين الصيغتين، إذ يجب في صيغة REL استخدام مرجع ذاكرة إضافي للعثور على القيمة المضافة قبل الإصلاح، لكنك لا تهدر مساحةً في الملف الثنائي لأنك تستخدم الذاكرة الهدف لعملية الانتقال. بينما يمكنك في صيغة RELA الاحتفاظ بالقيمة المضافة مع الانتقال، ولكنك تهدر هذه المساحة في ملف القرص الصلب الثنائي. تستخدم معظم الأنظمة الحديثة انتقالات RELA. كيفية عمل الانتقالات يوضح المثال التالي كيفية عمل الانتقالات، حيث سننشئ مكتبتين مشتركتين بسيطتين ونشير إلى إحدى المكتبتين من المكتبة الأخرى: $ cat addendtest.c extern int i[4]; int *j = i + 2; $ cat addendtest2.c int i[4]; $ gcc -nostdlib -shared -fpic -s -o addendtest2.so addendtest2.c $ gcc -nostdlib -shared -fpic -o addendtest.so addendtest.c ./addendtest2.so $ readelf -r ./addendtest.so Relocation section '.rela.dyn' at offset 0x3b8 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 0000000104f8 000f00000027 R_IA64_DIR64LSB 0000000000000000 i + 8 لدينا عملية انتقال واحدة في addendtest.so من النوع R_IA64_DIR64LSB الذي إن بحثت عنه في واجهة IA64 ABI، فيمكن تقسيمه إلى ما يلي: R_IA64: تبدأ جميع الانتقالات بهذه البادئة. DIR64: انتقال من النوع 64 بت المباشر. LSB: بما أن IA64 يمكن أن تعمل في أنماط Big Endian على تخزين البتات الأقل أهمية أولًا، و في أنماط Little Endian على تخزين البتات الأكثر أهمية أولًا، فسيكون هذا الانتقال Little Endian والذي يعني البايت الأقل أهميةً Least Significant Byte. تقول واجهة ABI أن هذا الانتقال يمثل قيمة الرمز الذي يؤشّر إليه مع أيّ قيمة مضافة. يمكننا أن نرى أن لدينا قيمة مضافة مقدارها 8 ، حيث أن حجم النوع int يساوي 4 أو sizeof(int) == 4، ونقلنا قيمتين من النوع int في المصفوفة أي ‎*j = i + 2. لذا يمكن إصلاح هذا الانتقال في وقت التشغيل من خلال العثور على عنوان الرمز i ووضع قيمته بالإضافة إلى القيمة 8 في 0x104f8. استقلال المواقع يُعطَى مقطع الشيفرة البرمجية ومقطع البيانات في ملف قابل للتنفيذ عنوانًا أساسيًا محددًا في الذاكرة الوهمية، لذا لا يمكن مشاركة شيفرة الملف القابل للتنفيذ الذي يحصل على فضاء عناوين جديد خاص به، وهذا يعني أن المصرِّف يعرف بالضبط مكان قسم البيانات ويمكنه الرجوع إليه مباشرةً. لا تمتلك المكتبات مثل هذا الضمان، إذ يمكنها معرفة أن قسم البيانات الخاص بها سيكون إزاحة محددة عن العنوان الأساسي، ولكن لا يمكن بالضبط معرفة مكان ذلك العنوان الأساسي إلّا في وقت التشغيل. لذا يجب إنشاء جميع المكتبات باستخدام شيفرة برمجية يمكن تنفيذها بغض النظر عن مكان وضعها في الذاكرة، ويُعرَف ذلك باسم الشيفرة المستقلة عن الموقع Position Independent Code، أو PIC اختصارًا. لاحظ أن قسم البيانات لا يزال يمثل إزاحة ثابتة عن قسم الشيفرة البرمجية، ولكن يجب إضافة الإزاحة إلى عنوان التحميل للعثور على عنوان البيانات. جدول الإزاحة العام Global Offset Table لا بد أنك لاحظت مشكلة خطيرة في الانتقالات Relocations عند التفكير في أهداف المكتبة المشتركة، حيث ذكرنا سابقًا أن ميزة المكتبة المشتركة مع الذاكرة الوهمية هي أن البرامج المتعددة يمكنها استخدام الشيفرة البرمجية الموجودة في الذاكرة من خلال مشاركة الصفحات. تنبع هذه المشكلة من حقيقة أن المكتبات ليس لديها أي ضمان حول مكان وضعها في الذاكرة، حيث سيجد الرابط الديناميكي المكان الأكثر ملاءمة في الذاكرة الوهمية لكل مكتبة مطلوبة ويضعها هناك. لكن إن لم يحدث ذلك، فستطلب كل مكتبة في النظام جزءها الخاص من الذاكرة الوهمية حتى لا تتداخل مع غيرها. تتطلب كل مكتبة جديدة في النظام تخصيصًا لها عند إضافتها، ويمكن كتابة مكتبة ضخمة لا تترك مساحةَ كافية للمكتبات الأخرى مع احتمالية ألّا يرغب برنامجك أبدًا في استخدام هذه المكتبة على أيّ حال. إذا عدّلتَ شيفرة مكتبة مشتركة لديها انتقال، فلن تصبح هذه الشيفرة البرمجية قابلةً للمشاركة، وبالتالي سنفقد ميزة المكتبة المشتركة. لنفترض أننا نأخذ قيمة رمزٍ ما، حيث سيكون لدينا باستخدام الانتقالات فقط رابط ديناميكي يبحث عن عنوان الذاكرة لهذا الرمز ويعيد كتابة الشيفرة البرمجية لتحميل هذا العنوان. يمكن تحسين هذا الموقف من خلال تخصيص مساحة في الملف الثنائي للاحتفاظ بعنوان هذا الرمز وجعل الرابط الديناميكي يضع العنوان هناك بدلًا من وضعه في الشيفرة البرمجية مباشرةً، وبالتالي لا نحتاج أبدًا إلى تعديل جزء الشيفرة البرمجية في الملف الثنائي. المنطقة المخصصة لهذه العناوين تسمى بجدول الإزاحة العام Global Offset Table -أو GOT اختصارًا- الذي يتواجد في القسم ‎.got من ملف ELF. يوضح الشكل التالي كيفية الوصول إلى الذاكرة باستخدام جدول GOT: يُعَد جدول GOT خاصًا بكل عملية، ويجب أن يكون للعملية أذونات للكتابة خاصة بها. بينما يمكن مشاركة شيفرة المكتبة ويجب أن يكون للعملية فقط أذونات قراءة وتنفيذ الشيفرة البرمجية، وإلا فسيكون هناك خرق أمني خطير إذا تمكنت العملية من تعديل الشيفرة البرمجية. كيفية عمل جدول GOT يوضح المثال التالي كيفية استخدام جدول GOT: $ cat got.c extern int i; void test(void) { i = 100; } $ gcc -nostdlib -shared -o got.so ./got.c $ objdump --disassemble ./got.so ./got.so: file format elf64-ia64-little Disassembly of section .text: 0000000000000410 <test>: 410: 0d 10 00 18 00 21 [MFI] mov r2=r12 416: 00 00 00 02 00 c0 nop.f 0x0 41c: 81 09 00 90 addl r14=24,r1;; 420: 0d 78 00 1c 18 10 [MFI] ld8 r15=[r14] 426: 00 00 00 02 00 c0 nop.f 0x0 42c: 41 06 00 90 mov r14=100;; 430: 11 00 38 1e 90 11 [MIB] st4 [r15]=r14 436: c0 00 08 00 42 80 mov r12=r2 43c: 08 00 84 00 br.ret.sptk.many b0;; $ readelf --sections ./got.so There are 17 section headers, starting at offset 0x640: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .hash HASH 0000000000000120 00000120 00000000000000a0 0000000000000004 A 2 0 8 [ 2] .dynsym DYNSYM 00000000000001c0 000001c0 00000000000001f8 0000000000000018 A 3 e 8 [ 3] .dynstr STRTAB 00000000000003b8 000003b8 000000000000003f 0000000000000000 A 0 0 1 [ 4] .rela.dyn RELA 00000000000003f8 000003f8 0000000000000018 0000000000000018 A 2 0 8 [ 5] .text PROGBITS 0000000000000410 00000410 0000000000000030 0000000000000000 AX 0 0 16 [ 6] .IA_64.unwind_inf PROGBITS 0000000000000440 00000440 0000000000000018 0000000000000000 A 0 0 8 [ 7] .IA_64.unwind IA_64_UNWIND 0000000000000458 00000458 0000000000000018 0000000000000000 AL 5 5 8 [ 8] .data PROGBITS 0000000000010470 00000470 0000000000000000 0000000000000000 WA 0 0 1 [ 9] .dynamic DYNAMIC 0000000000010470 00000470 0000000000000100 0000000000000010 WA 3 0 8 [10] .got PROGBITS 0000000000010570 00000570 0000000000000020 0000000000000000 WAp 0 0 8 [11] .sbss NOBITS 0000000000010590 00000590 0000000000000000 0000000000000000 W 0 0 1 [12] .bss NOBITS 0000000000010590 00000590 0000000000000000 0000000000000000 WA 0 0 1 [13] .comment PROGBITS 0000000000000000 00000590 0000000000000026 0000000000000000 0 0 1 [14] .shstrtab STRTAB 0000000000000000 000005b6 000000000000008a 0000000000000000 0 0 1 [15] .symtab SYMTAB 0000000000000000 00000a80 0000000000000258 0000000000000018 16 12 8 [16] .strtab STRTAB 0000000000000000 00000cd8 0000000000000045 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) يوضّح المثال السابق كيفية إنشاء مكتبة مشتركة بسيطة تشير إلى رمز خارجي. لا نعرف عنوان هذا الرمز في وقت التصريف، لذلك نتركه للرابط الديناميكي لإصلاحه في وقت التشغيل. لكننا نريد أن تبقى الشيفرة البرمجية قابلةً للمشاركة في حالة رغبة العمليات الأخرى في استخدام هذه الشيفرة، حيث يوضّح التفكيك Disassembly كيفية تطبيق ذلك باستخدام القسم ‎.got. يُعرف المسجّل r1 في معمارية IA64 التي صُرِّفت المكتبة من أجلها بالمؤشر العام Global Pointer الذي يؤشّر دائمًا إلى مكان تحميل القسم ‎.got في الذاكرة. إذا ألقينا نظرة على خرج الأداة readelf، فيمكننا أن نرى أن القسم ‎.got يبدأ عند عنوان يبعد بمقدار 0x10570 بايت عن مكان تحميل المكتبة في الذاكرة. لذا إذا حُمِّلت المكتبة في الذاكرة عند العنوان 0x6000000000000000، فسيكون القسم ‎.got موجودًا عند العنوان 0x6000000000010570، وسيؤشّر المسجّل r1 دائمًا إلى هذا العنوان. كما يمكننا أن نرى أننا نخزن القيمة 100 في عنوان الذاكرة الموجود في المسجّل r15 الذي يحتوي على قيمة عنوان الذاكرة المخزن في المسجل 14، حيث حمّلنا هذا العنوان من خلال إضافة عدد صغير إلى المسجّل 1. يُعَد جدول GOT مجرد قائمة طويلة من المدخلات، حيث تكون كل مدخلة خاصةً بمتغير خارجي، مما يعني أن مدخلة جدول GOT الخاصة بالمتغير الخارجي i تخزّن 24 بايتًا أو 3 عناوين بحجم 64 بتًا. $ readelf --relocs ./got.so Relocation section '.rela.dyn' at offset 0x3f8 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000010588 000f00000027 R_IA64_DIR64LSB 0000000000000000 i + 0 كما يمكننا التحقق من انتقال مدخلة جدول GOT، حيث يمثل الانتقالُ استبدالَ القيمة عند الإزاحة 10588 بموقع الذاكرة الذي خُزِّن فيه الرمز i. يبدأ القسم ‎.got عند الإزاحة 0x10570 عن الخرج السابق، حيث رأينا كيف تحمّل الشيفرة البرمجية عنوانًا يبعد عن القسم ‎.got بمقدار 0x18 (أو 24 في النظام العشري)، مما يمنحنا عنوانًا مقداره 0x10570 + 0x18 = 0x10588 يمثّل العنوان الذي طُبِّق الانتقال لأجله. لذا يجب أن يصلح الرابط الديناميكي الانتقال قبل أن يبدأ البرنامج للتأكد من أن قيمة الذاكرة عند الإزاحة 0x10588 هي عنوان المتغير العام i. ترجمة -وبتصرُّف- للأقسام Code Sharing و The Dynamic Linker و Global Offset Tables من فصل Dynamic Linking من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand. اقرأ أيضًا المقال السابق: تطبيق عملي لبناء برنامج تنفيذي من شيفرة مصدرية بلغة C المقال التالي: المكتبات وكيفية استدعاء دوالها ديناميكيًا في معمارية الحاسوب أنظمة المعالجات في معمارية الحاسوب العمليات وعناصرها في نظام تشغيل الحاسوب
  21. ذكرنا سابقًا أن البرنامج لا يبدأ بالدالة الرئيسية main()‎، حيث سنختبر في هذا المقال ما يحدث لبرنامج مرتبط ديناميكيًا عند تحميله وتشغيله. تخصّص النواة أولًا البنى لعملية جديدة وتقرأ ملف ELF المُحدَّد من القرص الصلب استجابةً لاستدعاء النظام exec. ذكرنا أن صيغة ELF لديها حقل لمفسّر Interpreter البرنامج هو PT_INTERP الذي يمكن ضبطه لتفسير البرنامج، حيث يكون المفسِّر بالنسبة للتطبيقات المرتبطة ديناميكيًا هو الرابط الديناميكي Dynamic Linker -أو ld.so- الذي يسمح بإجراء بعض عمليات الربط مباشرةً قبل بدء البرنامج. كما تقرأ النواة شيفرة الرابط الديناميكي، وتبدأ البرنامج من عنوان نقطة الدخول entry point الذي تحدده. سنختبر دور الرابط الديناميكي بالتفصيل لاحقًا، ولكن يكفي أن نقول أنه يضبط بعض الإعدادات مثل تحميل المكتبات التي يتطلبها التطبيق كما هو محدد في القسم الديناميكي من الملف الثنائي، ثم يبدأ تنفيذ البرنامج الثنائي عند عنوان نقطة الدخول أي الدالة ‎_init. اتصال النواة بالبرامج تحتاج النواة Kernel إلى توصيل بعض الأشياء للبرامج عند بدء تشغيلها مثل وسائط البرنامج arguments ومتغيرات البيئة الحالية environment variables وبنية خاصة اسمها المتجه المساعد Auxiliary Vector أو auxv اختصارًا. يمكنك أن تطلب من الرابط الديناميكي أن يُظهر لك بعضًا من خرج تنقيح الأخطاء من البنية auxv من خلال تحديد قيمة البيئة كما يلي LD_SHOW_AUXV=1. تسمح الوسائط والبيئة والأشكال المختلفة من استدعاء النظام exec بتحديد هذه الأشياء للبرنامج التي يمكن للنواة توصيلها من خلال وضع جميع المعلومات المطلوبة في المكدس ليلتقطها البرنامج المُنشَأ حديثًا، وبالتالي يمكن للبرنامج عند بدء تشغيله استخدام مؤشر المكدس الخاص به للعثور على جميع معلومات بدء التشغيل المطلوبة. المتجه المساعد هو بنية خاصة لنقل المعلومات مباشرةً من النواة إلى البرنامج المُشغَّل حديثًا، ويحتوي على معلومات خاصة بالنظام يمكن أن تكون مطلوبة مثل الحجم الافتراضي لصفحة الذاكرة الوهمية على النظام أو إمكانات العتاد، وهذه هي الميزات التي تحددها النواة للعتاد الأساسي ويمكن أن تستفيد منها برامج مساحة المستخدمين. مكتبة النواة Kernel Library ذكرنا سابقًا أن استدعاءات النظام بطيئة وأن الأنظمة الحديثة لديها آليات لتجنب الحِمل الناتج عن استدعاء مصيدة Trap للمعالج، حيث يمكن تنفيذ ذلك في نظام لينكس من خلال استخدام حيلة بين المحمل الديناميكي والنواة المتصلَين ببنية AUXV، إذ تضيف النواة مكتبة مشتركة صغيرة إلى فضاء العناوين لكل عملية مُنشَأة حديثًا وتحتوي على دالة تجري استدعاءات النظام نيابة عنك. يكمن جمال هذا النظام في أنه إذا دعم العتاد الأساسي آلية استدعاء نظام سريعة، فيمكن للنواة استخدامها لكونها منشئة المكتبة، وإلّا فيمكنها استخدام النظام القديم لإنشاء مصيدة. تسمى هذه المكتبة linux-gate.so.1 لأنها بوابة إلى عمل النواة الداخلي. تضيف النواة مدخلةً إلى البنية auxv تسمَّى AT_SYSINFO_EHDR عندما تبدأ الرابط الديناميكي، وهذه المدخلة هي العنوان الموجود في الذاكرة الذي توجد فيه مكتبة النواة الخاصة. يمكن للرابط الديناميكي عندما يبدأ البحثَ عن المؤشر AT_SYSINFO_EHDR، فإن وُجد، فستُحمَّل تلك المكتبة للبرنامج. لا يملك البرنامج أيّ فكرة عن وجود هذه المكتبة، لأنها تُعَد ترتيبًا خاصًا بين الرابط الديناميكي والنواة. ذكرنا أن المبرمجين يجرون استدعاءات النظام بطريقة غير مباشرة من خلال استدعاء الدوال في مكتبات النظام libc التي يمكنها التحقق مما إذا كان ملف النواة الثنائي الخاص محمَّلًا أم لا، فإذا كان الأمر كذلك، فيجب استخدام الدوال الموجودة ضمنه لإجراء استدعاءات النظام. إذا حددت النواة أن العتاد يمتلك القدرة المطلوبة، فيجب استخدام طريقة استدعاء النظام السريع. بدء برنامج تمرّر النواةُ المفسّرَ بعد تحميله إلى نقطة الدخول كما هو مذكور في ملف المفسّر (لاحظ عدم اختبار كيفية بدء الرابط الديناميكي). سيقفز الرابط الديناميكي إلى عنوان نقطة الدخول كما هو مذكور في ملف ELF الثنائي. يوضح المثال التالي نتيجة تفكيك Disassembley بدء تشغيل البرنامج: $ cat test.c int main(void) { return 0; } $ gcc -o test test.c $ readelf --headers ./test | grep Entry Entry point address: 0x80482b0 $ objdump --disassemble ./test [...] 080482b0 <_start>: 80482b0: 31 ed xor %ebp,%ebp 80482b2: 5e pop %esi 80482b3: 89 e1 mov %esp,%ecx 80482b5: 83 e4 f0 and $0xfffffff0,%esp 80482b8: 50 push %eax 80482b9: 54 push %esp 80482ba: 52 push %edx 80482bb: 68 00 84 04 08 push $0x8048400 80482c0: 68 90 83 04 08 push $0x8048390 80482c5: 51 push %ecx 80482c6: 56 push %esi 80482c7: 68 68 83 04 08 push $0x8048368 80482cc: e8 b3 ff ff ff call 8048284 <__libc_start_main@plt> 80482d1: f4 hlt 80482d2: 90 nop 80482d3: 90 nop 08048368 <main>: 8048368: 55 push %ebp 8048369: 89 e5 mov %esp,%ebp 804836b: 83 ec 08 sub $0x8,%esp 804836e: 83 e4 f0 and $0xfffffff0,%esp 8048371: b8 00 00 00 00 mov $0x0,%eax 8048376: 83 c0 0f add $0xf,%eax 8048379: 83 c0 0f add $0xf,%eax 804837c: c1 e8 04 shr $0x4,%eax 804837f: c1 e0 04 shl $0x4,%eax 8048382: 29 c4 sub %eax,%esp 8048384: b8 00 00 00 00 mov $0x0,%eax 8048389: c9 leave 804838a: c3 ret 804838b: 90 nop 804838c: 90 nop 804838d: 90 nop 804838e: 90 nop 804838f: 90 nop 08048390 <__libc_csu_init>: 8048390: 55 push %ebp 8048391: 89 e5 mov %esp,%ebp [...] 08048400 <__libc_csu_fini>: 8048400: 55 push %ebp [...] يمكننا أن نرى في المثال البسيط السابق باستخدام أداة readelf أن نقطة الدخول هي الدالة ‎_start في الملف الثنائي، ويمكننا أن نرى في عملية التفكيك دفع بعض القيم إلى المكدس. تمثل القيمة الأولى 0x8048400 الدالة ‎__libc_csu_fini، وتمثل القيمة 0x8048390 الدالة ‎__libc_csu_init، وتمثل القيمة 0x8048368 الدالة الرئيسية main()‎، ثم تُستدعَى قيمة الدالة ‎__libc_start_main. الدالة ‎__libc_start_main مُعرَّفة في مصادر مكتبة glibc ضمن sysdeps/generic/libc-start.c، وتُعَد معقدةً جدًا ومخفيةً بين عدد كبير من التعريفات، حيث يجب أن تكون قابلة للنقل عبر عدد كبير جدًا من الأنظمة والمعماريات التي يمكن لمكتبة glibc العمل عليها. تطبّق هذه الدالة عددًا من الأشياء المحدَّدة المتعلقة بإعداد مكتبة C والتي لا يحتاج المبرمج العادي للقلق بشأنها. النقطة التالية التي تستدعي فيها المكتبةُ البرنامجَ هي عند التعامل مع شيفرة init. تُعَد الدالتان init وfini مفهومين خاصين يستدعيان أجزاءً من الشيفرة البرمجية الموجودة في المكتبات المشتركة والتي يمكن أن تحتاج لاستدعائها قبل أن تبدأ المكتبة أو عند إلغاء تحميل المكتبة على التوالي. يمكنك أن ترى كيف يمكن أن يكون ذلك مفيدًا لمبرمجي المكتبات لإعداد المتغيرات عند بدء تشغيل المكتبة أو لتنظيفها في النهاية. كان البحث عن الدالتين ‎_init و‎_fini في المكتبة ممكنًا سابقًا، ولكن أصبح ذلك مقيدًا إلى حد ما حيث كان كل شيء مطلوبًا في هاتين الدالتين. سنوضح فيما يلي كيفية عمل الدالتين init وfini فقط. يمكننا أن نرى الآن أن الدالة ‎__libc_start_main ستتلقى عددًا من معاملات الدخل في المكدس stack، إذ سيكون بإمكانها أولًا الوصول إلى وسائط البرنامج ومتغيرات البيئة والمتجه المساعد من النواة، ثم ستدفع دالة التهيئة إلى عناوين المكدس الخاصة بالدوال للتعامل مع الدالتين init وfini ثم عنوان الدالة الرئيسية نفسها. نحتاج طريقةً ما للإشارة إلى أنه يجب استدعاء دالةٍ ما باستخدام init أوfini في الشيفرة المصدرية. نستخدم مع gcc سمات Attributes لتمييز دالتين بأنهما بانيتان Constructors أو ومدمرتان Destructors في برنامجنا الرئيسي. تُستخدَم هذه المصطلحات بصورة أكثر شيوعًا مع اللغات كائنية التوجه لوصف دورات حياة الكائن. إليك مثال عن الباني والمدمر: $ cat test.c #include <stdio.h> void __attribute__((constructor)) program_init(void) { printf("init\n"); } void __attribute__((destructor)) program_fini(void) { printf("fini\n"); } int main(void) { return 0; } $ gcc -Wall -o test test.c $ ./test init fini $ objdump --disassemble ./test | grep program_init 08048398 <program_init>: $ objdump --disassemble ./test | grep program_fini 080483b0 <program_fini>: $ objdump --disassemble ./test [...] 08048280 <_init>: 8048280: 55 push %ebp 8048281: 89 e5 mov %esp,%ebp 8048283: 83 ec 08 sub $0x8,%esp 8048286: e8 79 00 00 00 call 8048304 <call_gmon_start> 804828b: e8 e0 00 00 00 call 8048370 <frame_dummy> 8048290: e8 2b 02 00 00 call 80484c0 <__do_global_ctors_aux> 8048295: c9 leave 8048296: c3 ret [...] 080484c0 <__do_global_ctors_aux>: 80484c0: 55 push %ebp 80484c1: 89 e5 mov %esp,%ebp 80484c3: 53 push %ebx 80484c4: 52 push %edx 80484c5: a1 2c 95 04 08 mov 0x804952c,%eax 80484ca: 83 f8 ff cmp $0xffffffff,%eax 80484cd: 74 1e je 80484ed <__do_global_ctors_aux+0x2d> 80484cf: bb 2c 95 04 08 mov $0x804952c,%ebx 80484d4: 8d b6 00 00 00 00 lea 0x0(%esi),%esi 80484da: 8d bf 00 00 00 00 lea 0x0(%edi),%edi 80484e0: ff d0 call *%eax 80484e2: 8b 43 fc mov 0xfffffffc(%ebx),%eax 80484e5: 83 eb 04 sub $0x4,%ebx 80484e8: 83 f8 ff cmp $0xffffffff,%eax 80484eb: 75 f3 jne 80484e0 <__do_global_ctors_aux+0x20> 80484ed: 58 pop %eax 80484ee: 5b pop %ebx 80484ef: 5d pop %ebp 80484f0: c3 ret 80484f1: 90 nop 80484f2: 90 nop 80484f3: 90 nop $ readelf --sections ./test There are 34 section headers, starting at offset 0xfb0: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0 4 [ 3] .hash HASH 08048148 000148 00002c 04 A 4 0 4 [ 4] .dynsym DYNSYM 08048174 000174 000060 10 A 5 1 4 [ 5] .dynstr STRTAB 080481d4 0001d4 00005e 00 A 0 0 1 [ 6] .gnu.version VERSYM 08048232 000232 00000c 02 A 4 0 2 [ 7] .gnu.version_r VERNEED 08048240 000240 000020 00 A 5 1 4 [ 8] .rel.dyn REL 08048260 000260 000008 08 A 4 0 4 [ 9] .rel.plt REL 08048268 000268 000018 08 A 4 11 4 [10] .init PROGBITS 08048280 000280 000017 00 AX 0 0 4 [11] .plt PROGBITS 08048298 000298 000040 04 AX 0 0 4 [12] .text PROGBITS 080482e0 0002e0 000214 00 AX 0 0 16 [13] .fini PROGBITS 080484f4 0004f4 00001a 00 AX 0 0 4 [14] .rodata PROGBITS 08048510 000510 000012 00 A 0 0 4 [15] .eh_frame PROGBITS 08048524 000524 000004 00 A 0 0 4 [16] .ctors PROGBITS 08049528 000528 00000c 00 WA 0 0 4 [17] .dtors PROGBITS 08049534 000534 00000c 00 WA 0 0 4 [18] .jcr PROGBITS 08049540 000540 000004 00 WA 0 0 4 [19] .dynamic DYNAMIC 08049544 000544 0000c8 08 WA 5 0 4 [20] .got PROGBITS 0804960c 00060c 000004 04 WA 0 0 4 [21] .got.plt PROGBITS 08049610 000610 000018 04 WA 0 0 4 [22] .data PROGBITS 08049628 000628 00000c 00 WA 0 0 4 [23] .bss NOBITS 08049634 000634 000004 00 WA 0 0 4 [24] .comment PROGBITS 00000000 000634 00018f 00 0 0 1 [25] .debug_aranges PROGBITS 00000000 0007c8 000078 00 0 0 8 [26] .debug_pubnames PROGBITS 00000000 000840 000025 00 0 0 1 [27] .debug_info PROGBITS 00000000 000865 0002e1 00 0 0 1 [28] .debug_abbrev PROGBITS 00000000 000b46 000076 00 0 0 1 [29] .debug_line PROGBITS 00000000 000bbc 0001da 00 0 0 1 [30] .debug_str PROGBITS 00000000 000d96 0000f3 01 MS 0 0 1 [31] .shstrtab STRTAB 00000000 000e89 000127 00 0 0 1 [32] .symtab SYMTAB 00000000 001500 000490 10 33 53 4 [33] .strtab STRTAB 00000000 001990 000218 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) $ objdump --disassemble-all --section .ctors ./test ./test: file format elf32-i386 Contents of section .ctors: 8049528 ffffffff 98830408 00000000 ............ كانت دالة التهيئة ‎__libc_csu_init هي القيمة الأخيرة المدفوعة إلى المكدس من أجل الدالة ‎__libc_start_main. إذا اتبعنا سلسلة الاستدعاءات ابتداءً من ‎__libc_csu_init، فيمكننا أن نرى أنها تجري بعض الإعدادات ثم تستدعي الدالة ‎_init في الملف القابل للتنفيذ. تستدعي الدالة ‎_init في النهاية دالة تسمى ‎__do_global_ctors_aux، حيث إذا نظرنا إلى تفكيك هذه الدالة، فيمكننا أن نرى أنها تبدأ من العنوان 0x804952c ثم تتكرر وتقرأ قيمة وتستدعيها. هذا العنوان الذي يمثل البداية موجود في القسم ‎.ctors من الملف، حيث إذا ألقينا نظرة عليه، فسنرى أنه يحتوي على القيمة الأولى ‎-1 وعنوان الدالة بصيغة Big Endian أي تخزين البتات الأقل أهمية أولًا والقيمة صفر. العنوان بصيغة Big Endian هو 0x08048398 أو عنوان الدالة program_init، لذا فإن صيغة القسم ‎.ctors هي ‎-1 أولًا ثم عنوان الدوال المطلوب استدعاؤها عند التهيئة، وأخيرًا القيمة صفر للإشارة إلى اكتمال القائمة. ستُستدعَى كل مدخلة، ولدينا في هذه الحالة دالة واحدة فقط. أخيرًا، تستدعي الدالة ‎__libc_start_main الدالةَ الرئيسية main()‎ بمجرد اكتمالها باستدعاء الدالة ‎_init. تذكر أن هذه الدالة تمتلك إعداد المكدس الأولي باستخدام الوسائط ومؤشرات البيئة من النواة، وهذه هي الطريقة التي تحصل بها الدالة الرئيسية على الوسائط argc, argv[], envp[]‎. تعمل العملية بعد ذلك وتكتمل مرحلة الإعداد. تحدث عملية مماثلة مع القسم ‎.dtors للمدمرين Destructors عند إنهاء البرنامج، حيث تستدعيها الدالة ‎__libc_start_main عند اكتمال الدالة الرئيسية main()‎. لاحظ تطبيق الكثير من العمل قبل أن يبدأ البرنامج وحتى بعد أن تعتقد أنه انتهى بقليل. ترجمة -وبتصرُّف- للقسم Starting a process من فصل Behind the process من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand. اقرأ أيضًا المقال السابق: مفاهيم متقدمة متعلقة بصيغة ملفات ELF القابلة للتنفيذ كيفية إنشاء ملف قابل للتنفيذ Executable File من شيفرة برمجية مصدرية Source Code
  22. سننتقل في هذا المقال إلى إجراء اختبارات التوافق مع المتصفحات Cross Browser Testing والنظر في تحديد الجمهور المستهدف مثل المتصفحات والأجهزة والمجالات الأخرى التي يجب التأكد من اختبارها واستراتيجيات اختبار Lo-fi مثل الحصول على مجموعة من الأجهزة وبعض الآلات الافتراضية وإجراء الاختبارات المخصَّصة Ad-hoc عند الحاجة، واستراتيجيات التقنيات المتقدمة مثل الاختبارات الآلية واستخدام تطبيقات الاختبار المخصَّصة، والاختبار مع مجموعات المستخدِمين. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML وCSS وجافاسكربت JavaScript، وأن يكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات. الهدف: اكتساب فهم للمفاهيم عالية المستوى المتضمّنة في اختبار مشاريع الويب للتوافق مع المتصفحات. هل يجب اختبار كل شيء؟ يجب وضع قائمة بالمتصفحات التي تريد اختبارها عند إجراء اختبار التوافق مع المتصفحات، إذ لا توجد طريقة يمكنك من خلالها اختبار مجموعة المتصفحات والأجهزة التي يمكن أن يستخدِمها المستخدِمون لعرض موقعك، فهناك عدد كبير جدًا منها مع ظهور متصفحات وأجهزة أخرى جديدة طوال الوقت، لذا يجب عليك محاولة التأكد من أنّ موقعك يعمل على أهم المتصفحات والأجهزة المُستهدَفة، ثم أنشئ شيفرة دفاعية Defensive Code لمنح موقعك أكبر قدر من الدعم الذي يمكن توقعه. نعني هنا بالشيفرة الدفاعية محاولة بناء تراجعات ذكية بحيث إذا لم تعمل الميزة أو التنسيق في المتصفح، فسيكون الموقع قادرًا على الرجوع إلى إصدار أقدم ولكنه لا يزال يوفر تجربة مستخدِم مقبولة مع توفّر المعلومات الأساسية حتى إذا لم يكن يبدو جيدًا تمامًا. هدفنا هو بناء مخطط للمتصفحات/الأجهزة الذي يمكنك الرجوع إليه أثناء الاختبار، كما يمكنك جعل هذا المخطط بسيطًا أو معقدًا كما يحلو لك، إذ تتمثل الطريقة الشائعة لذلك في إنشاء درجات متعددة من مستوى الدعم مثلًا كما يلي: الدرجة A: المتصفحات المشهورة والحديثة والمعروفة بقدرتها والتي يجب أن تختبرها بدقة وتقدّم لها الدعم الكامل. الدرجة B: المتصفحات الأقدم ذات الإمكانات الأقل التي يجب اختبارها وتقديم تجربة أساسية تتيح الوصول الكامل إلى المعلومات والخدمات الأساسية. الدرجة C المتصفحات النادرة وغير المعروفة التي يمكنك ألّا تختبرها، إذ يجب أن يعمل موقعك بالكامل على الأقل مع الاحتياطات التي توفرها الشيفرة الدفاعية. سننشئ مخطط دعم بهذا الشكل في الأقسام التالية. ملاحظة: جعل ياهو Yahoo هذا الأسلوب شائعًا في البداية من خلال استخدام أسلوب دعم المتصفح المُصنَّف Graded Browser Support. التخمينات المتعلمة يمكنك تسميتها بالافتراضات، فهي لا تُعَدّ نهجًا علميًا دقيقًا، ولكن ستكون لديك بصفتك شخصًا لديه خبرة في مجال الويب فكرةً جيدةً عن بعض المتصفحات التي يجب عليك اختبارها على الأقل، ويمكن أن يشكّل ذلك أساسًا جيدًا لمخطط الدعم. إذا كنت من سكان أوروبا الغربية أو أمريكا الشمالية مثلًا، فستعرف أن الكثير من الأشخاص يستخدِمون الحواسيب المكتبية أو المحمولة التي تعمل باستخدام نظام ويندوز وماك Mac بحيث تكون المتصفحات الرئيسية هي كروم Chrome وفايرفوكس Firefox وسفاري Safari و IE و Edge، وتريد مثلًا اختبار أحدث إصدارات المتصفحات الثلاثة الأولى فقط، حيث تتلقى هذه المتصفحات تحديثات منتظمة، ويمكن أن ترغب في اختبار إصدارَي Edge و IE الأخيرين، وبالتالي يجب أن تنتقل هذه المتصفحات إلى مستوى الدرجة A. يستخدِم الكثير من الأشخاص نظامَي iOS وأندرويد Android، لذا لا بد أنك ترغب في اختبار أحدث إصدارات iOS Safari، وآخر إصدارات متصفح Android القديم، والمتصفحين Chrome و Firefox لنظامَي التشغيل iOS و Android، كما يجب عليك اختبارها بطريقة مثالية على كل من الهاتف والجهاز اللوحي للتأكد من أن التصميمات المتجاوبة مع الشاشات تعمل بنجاح. يمكن أن يكون عددٌ من الأشخاص يستخدِمون المتصفح IE 9 الذي يُعَدّ قديمًا وخارج نطاق الدعم والتطوير من مايكروسوفت ويمتلك إمكانات أقل، لذلك سنضعه في الدرجة B. يعطينا ذلك مخطط الدعم التالي حتى الآن: الدرجة A: تتضمن Chrome و Firefox لنظامَي التشغيل Windows و Mac، و Safari لنظام التشغيل Mac، وEdge لنظام التشغيل ويندوز، و iOS Safari لأجهزة iPhone و iPad، ومتصفح Android القديم (الإصداران الأخيران) على الهاتف أو الجهاز اللوحي، و Chrome و Firefox لنظام Android (الإصداران الأخيران) على الهاتف اللوحي. الدرجة B: تتضمن IE لنظام التشغيل ويندوز و Opera Mini. الدرجة ? غير متوفر. إذا كنت تعيش في مكان آخر أو تعمل على موقع يخدّم مكانًا آخر مثل بلدان أو مناطق معينة، فيُحتمَل أن يكون لديك متصفحات مختلفة لاختبارها. إحصائيات دعم المتصفح أحد الإجراءات المفيدة التي يمكنك استخدامها لاختبار المتصفحات هو إحصائيات دعم المتصفح، وهناك عدد من المواقع التي تقدم مثل هذه الإحصائيات مثل: Statcounter. استخدام التحليلات يأتي مصدر البيانات الأدق من تطبيقات التحليلات مثل Google Analytics الذي يمنحك إحصائيات دقيقة حول المتصفحات التي يستخدِمها الأشخاص لتصفح موقعك، ويعتمد ذلك على أن يكون لديك موقع مسبقًا لاستخدام هذا التطبيق عليه، لذلك لا يُعَدّ جيدًا للمواقع الجديدة كليًا. لكن يمكن أن يكون سجل التحليلات مفيدًا في العثور على إحصائيات الدعم للتأثير على إصدار جديد من موقع الشركة أو الميزات الجديدة التي تضيفها إلى موقع قائم، ويُعَدّ ذلك -إذا توفّر- أدق بكثير من إحصائيات المتصفح العامة التي ذكرناها سابقًا. كما يمكنك التفكير في استخدام منصات التحليلات مفتوحة المصدر التي تركز على الخصوصية مثل Open Web Analytics و Matomo التي تتوقع منك استضافة منصة التحليلات بنفسك. إعداد Google Analytics يمكنك إعداد Google Analytics باتباع الخطوات التالية: ستحتاج أولًا إلى حساب جوجل لتسجيل الدخول إلى Google Analytics. اختر خيار Google Analytics للويب وانقر على زر التسجيل Sign Up. أدخِل تفاصيل موقعك أو تطبيقك في صفحة التسجيل، حيث يكون الإعداد بسيطًا، ولكن أهم حقل هو عنوان URL لموقع الويب الذي يجب أن يكون عنوان URL الجذر لموقعك أو تطبيقك. اضغط بعد ذلك على زر الحصول على معرّف التتبّع Get Tracking ID ثم اقبل شروط الخدمة التي ستظهر. توفِّر لك الصفحة التالية بعض مقتطفات الشيفرة البرمجية وإرشادات أخرى، فذا كان لديك موقع ويب أساسي، فما عليك فعله هو نسخ كتلة شيفرة تتبع موقع الويب Website Tracking ولصقها في جميع الصفحات التي تريد تتبعها باستخدام Google Analytics على موقعك، كما يمكنك وضع مقتطفات الشيفرة البرمجية بعد وسم الإغلاق <‎/body> أو في مكان آخر مناسب لا يتداخل مع شيفرة تطبيقك. حمّل التغييرات على خادم التطوير أو في أيّ مكان آخر تحتاج فيه لشيفرتك البرمجية. يجب أن يكون موقعك جاهزًا الآن لبدء إصدار تقارير بالبيانات التحليلية. دراسة البيانات التحليلية يجب الآن أن تكون قادرًا على العودة إلى صفحة تحليلات الويب Analytics Web والاطلاع على البيانات التي جمعتها حول موقعك، ويجب بالطبع أن تنتظر قليلًا من الوقت لجمع بعض البيانات. يُفترَض أن ترى تبويب إعداد التقارير Reporting افتراضيًا كما يلي: هناك قدر هائل من البيانات التي يمكنك الاطلاع عليها باستخدام Google Analytics مثل التقارير المخصَّصة ضمن فئات مختلفة وغير ذلك، كما يجب النظر إلى الخيارات المختلفة على الجانب الأيسر ومعرفة أنواع البيانات التي يمكنك اكتشافها، إذ يمكنك مثلًا معرفة المتصفحات وأنظمة التشغيل التي يستخدمها المستخدمون من خلال تحديد خيار القائمة Audience ثم Technology ثم Browser & OS من القائمة اليسرى. ملاحظة: يجب أن تكون حذرًا عند استخدام Google Analytics إذا كان لديك شيء مثل النتيجة "لا يوجد لدينا مستخدِمون للمتصفح Firefox Mobile" التي يمكن أن تقودك إلى عدم دعم Firefox mobile، ولكن لن يكون لديك أيٌّ من مستخدِمي المتصفح Firefox Mobile إذا كان موقعك معطَّلًا على متصفح Firefox mobile بالأصل. هناك أمور أخرى يجب عليك تضمينها، إذ يجب بالتأكيد تضمين سهولة الوصول Accessibility على أساس شرط اختبار من الدرجة A، وسنغطي بالضبط ما يجب عليك اختباره في مقال معالجة مشاكل الشمولية الشائعة للتوافق مع المتصفحات لاحقًا. إذا أردت إنشاء شبكة داخلية Intranet خاصة بالشركة لتوصيل أرقام المبيعات للمديرين مع تزويد جميع المديرين بهواتف ويندوز مثلًا، فيمكن أن ترغب في إعطاء الأولوية لدعم المتصفح IE على الهواتف المحمولة. مخطط الدعم النهائي سيكون مخطط الدعم النهائي بالصورة التالية: الدرجة A: تتضمن Chrome و Firefox لنظامَي التشغيل Windows و Mac، و Safari لنظام التشغيل Mac و Edge لنظام التشغيل Windows (الإصداران الأخيران) و iOS Safari لأجهزة iPhone و iPad ومتصفح Android القديم (الإصداران الأخيران) على الهاتف والجهاز اللوحي و Chrome و Firefox لنظام Android (الإصداران الأخيران) على الهاتف اللوحي، مع النجاح في اختبارات الشمولية الشائعة. الدرجة B: تتضمن IE 8 و9 لنظام التشغيل Windows و Opera Mini. الدرجة ? تتضمن Opera والمتصفحات الحديثة المتخصصة الأخرى. ماذا يجب أن نختبر؟ إذا كان لديك إضافة جديدة إلى قاعدة شيفرتك البرمجية والتي يجب اختبارها، فيجب كتابة قائمة بمتطلبات الاختبار التي يجب اجتيازها قبل أن تبدأ الاختبار حتى تُقبَل، ويمكن أن تكون هذه المتطلبات مرئيةً أو وظيفيةً، إذ يجتمع هذان النوعان لإنشاء ميزة موقع ويب قابلة للاستخدام. لنفترض المثال التالي (اطلع على الشيفرة البرمجية): يمكن كتابة معايير الاختبار لهذه الميزة على النحو التالي: الدرجة A و B: يجب أن يكون الزر قابلًا للتنشيط باستخدام آلية التحكم الأساسية للمستخدِم، كما يجب أن يشمل ذلك مستخدِمي الفأرة ولوحة المفاتيح وشاشات اللمس. يجب أن يؤدي تبديل الزر إلى ظهور أو إخفاء مربع المعلومات. يجب أن يكون النص مقروءًا. يجب أن يتمكن المستخدِمون المعاقون بصريًا الذين يستخدِمون قارئات الشاشة من الوصول إلى النص. الدرجة A: يجب أن يتحرك مربع المعلومات بسلاسة عندما يظهر أو يختفي. يجب أن يظهر التدرج اللوني وظل النص لتحسين مظهر المربع. لاحظ في هذا المثال أنّ النص لن يعمل في المتصفح IE8، ويُعَدّ ذلك مشكلةً وفقًا لمخطط الدعم ويجب إصلاحها باستخدام مكتبة اكتشاف الميزات مثلًا لتطبيق الوظيفة بطريقة مختلفة إذا لم يدعم المتصفح انتقالات CSS، كما يمكن ألّا يكون الزر قابلًا للاستخدام لمستخدِمي لوحة المفاتيح فقط، ويجب معالجة ذلك من خلال استخدام شيفرة جافاسكربت لتطبيق التحكم بلوحة المفاتيح لتبديل الزر أو باستخدام طريقة أخرى. تُعَدّ هذه المعايير للاختبار مفيدةً للأسباب التالية: تعطي مجموعةً من الخطوات التي يجب اتباعها عند إجراء الاختبارات. يمكن تحويلها بسهولة إلى مجموعات من التعليمات لمجموعات المستخدِمين لاتباعها عند إجراء الاختبارات مثل "حاول تنشيط الزر باستخدام الفأرة ثم باستخدام لوحة المفاتيح …". يمكنها توفير أساس لكتابة الاختبارات الآلية، ويمكن كتابة مثل هذه الاختبارات بسهولة إذا عرفتَ بالضبط ما تريد اختباره وما هي شروط النجاح. إنشاء مختبر لإجراء الاختبارات أحد الخيارات لإجراء اختبارات المتصفحات هو إجراء الاختبار بنفسك من خلال استخدام مجموعة من الأجهزة الحقيقية الفعلية والبيئات المقلِّدة باستخدام إما مقلِّد أو محاكٍ Emulator أو آلة افتراضية. الأجهزة الحقيقية يُفضَّل أن يكون لديك جهاز حقيقي يشغّل المتصفح الذي تريد اختباره، وهذا يوفر أكبر قدر من الدقة من حيث السلوك وتجربة المستخدِم الإجمالية، كما يمكن استخدام جهاز منخفض المستوى ليكون مختبرًا مقبولًا كما يلي: جهاز Mac مثبَّتٌ عليه المتصفحات التي تريد اختبارها، ويمكن أن يشمل ذلك المتصفحات Firefox و Chrome و Opera و Safari. حاسوب يعمل بنظام ويندوز مثبَّت عليه المتصفحات التي يجب اختبارها، ويمكن أن يشمل ذلك Edge أو IE و Chrome و Firefox و Opera. هاتف وجهاز لوحي يعملان بنظام Android عالي المواصفات مع تثبيت المتصفح الذي تريد اختباره، ويمكن أن يشمل ذلك المتصفحات Chrome و Firefox و Opera Mini لنظام Android، بالإضافة إلى متصفح Android الأصلي القديم. هاتف وجهاز لوحي يعمل بنظام iOS عالي المواصفات مع تثبيت المتصفحات التي تريد اختبارها، ويمكن أن يشمل ذلك المتصفحات iOS Safari و Chrome و Firefox و Opera Mini لنظام التشغيل iOS. الخيارات التالية هي خيارات جيدة أيضًا إذا كان بإمكانك الحصول عليها: حاسوب يعمل بنظام لينكس Linux في حالة احتياجك لاختبار الزلات البرمجية الخاصة بإصدارات لينكس للمتصفحات، إذ يستخدِم مستخدِمو نظام لينكس المتصفحات Firefox و Opera و Chrome، فإذا كان لديك جهاز واحد فقط متاح، فيمكنك التفكير في إنشاء جهاز ذي إقلاع مزدوج Dual Boot يعمل بنظام التشغيل لينكس Linux وويندوز Windows على قطاعات منفصلة، حيث يسهّل مُثبِّت نظام أبونتو Ubuntu هذا الإعداد. هواتف محمولة منخفضة المواصفات، بحيث يمكنك اختبار أداء الميزات مثل الحركة على معالجات أقل قوة. كما يمكن أن يكون جهاز عملك الرئيسي مكانًا لتثبيت أدوات أخرى لأغراض محددة مثل أدوات تدقيق سهولة الوصول وقارئات الشاشة والمحاكيات أو الآلات الافتراضية Virtual Machines. تمتلك بعض الشركات الكبيرة مختبرات للأجهزة التي تحتوي على مجموعة كبيرة جدًا من الأجهزة المختلفة، مما يمكّن المطورين من تعقب الأخطاء في مجموعات محددة جدًا من المتصفحات والأجهزة. الشركات الصغيرة والأفراد غير قادرين على تحمّل تكلفة مثل هذا المختبر المعقد، لذلك يميلون إلى التعامل مع المختبرات الصغيرة والمحاكيات والآلات الافتراضية وتطبيقات الاختبار التجارية. ملاحظة: هناك بعض الجهود المبذولة لإنشاء مختبرات عامة لاختبار سهولة وصول الأجهزة، إذ يجب وضعها في الحسبان، فهناك عدد من الأدوات المفيدة التي يمكنك تثبيتها على جهازك لتسهيل اختبار سهولة الوصول، ولكننا سنغطيها لاحقًا. المحاكيات Emulators تُعَدّ المحاكيات أو المقلّدات برامجًا تُشغَّل ضمن حاسوبك وتقلّد جهازًا أو حالة جهاز معيّن من نوع ما، مما يسمح بإجراء بعض اختباراتك بصورة ملائمة أكثر من الاضطرار إلى العثور على مجموعة معينة من العتاد أو البرمجيات لاختبارها. يمكن أن يكون المحاكي بسيطًا مثل اختبار حالة الجهاز، فإذا أردت مثلًا إجراء بعض الاختبارات السريعة وغير المتوقَّعة لاستعلامات وسائط Media Queries العرض أو الارتفاع للتصميم المتجاوب مع الشاشات، فيمكنك استخدام وضع التصميم المتجاوب Responsive Design Mode في فايرفوكس Firefox، كما يحتوي المتصفح Safari على وضع مشابه يمكن تفعيله من خلال الانتقال إلى قائمة Safari ثم التفضيلات Preferences وتحديد قائمة إظهار التطوير Show Develop، ثم خيار التطوير Develop ثم إدخال وضع التصميم المتجاوب Enter Responsive Design Mode، كما يحتوي Chrome على شيء مشابه أيضًا هو وضع الجهاز Device mode. يجب عليك في كثير من الأحيان تثبيت محاكٍ، حيث تكون الأجهزة/المتصفحات الأكثر شيوعًا التي تريد اختبارها على هذه المحاكيات هي كما يلي: تُعَدّ بيئة التطوير Android Studio IDE الرسمية لتطوير تطبيقات Android ثقيلة الوزن بعض الشيء لمجرد اختبار مواقع الويب على Google Chrome أو متصفح Android القديم، ولكنها تأتي مع محاكٍ قوي، فإذا أردت شيئًا خفيف الوزن قليلًا، فإنّ المحاكي Andy هو خيار مقبول يعمل على كل من نظامَي Windows و Mac. توفِّر Apple تطبيقًا يسمى Simulator يعمل على بيئة تطوير XCode، ويحاكي أجهزة iPad و iPhone و Apple Watch و Apple TV، ويتضمن ذلك متصفح iOS Safari الأصلي، ولكنه للأسف يعمل فقط على أجهزة Mac. يمكنك في أغلب الأحيان العثور على محاكيات Simulators لبيئات الهواتف المحمولة الأخرى مثل: يمكنك محاكاة المتصفح Opera Mini بمفرده إذا أردت اختباره. تتوفر محاكيات لأنظمة تشغيل Windows Mobile مثل المحاكي Windows Phone لنظام Windows Phone 8 والمحاكي Microsoft Emulator لنظام التشغيل Windows 10 Mobile، حيث تعمل هذه المحاكيات على نظام ويندوز فقط. ملاحظة: تتطلب العديد من المحاكيات استخدام آلة افتراضية، حيث تُعطَى الإرشادات لذلك في أغلب الأحيان و/أو يُدمَج استخدام الآلة الافتراضية في مثبِّت المحاكي. الآلات الافتراضية تُعَدّ الآلات الافتراضية تطبيقات تعمل على حاسوبك وتسمح بتشغيل عمليات المحاكاة لأنظمة التشغيل بأكملها، حيث يكون لكل نظام قسمه الخاص من القرص الصلب الافتراضي، إذ يُمثَّل في أغلب الأحيان بملف واحد كبير موجود على القرص الصلب للجهاز المضيف، كما يتوفر عدد من تطبيقات الآلات الافتراضية الشائعة مثل Parallels و VMWare و Virtual Box المجاني. ملاحظة: يجب أن يكون لديك مساحة كبيرة على القرص الصلب لتشغيل عمليات محاكاة الآلة الافتراضية، إذ يمكن أن يستهلك كل نظام تشغيل تريد محاكاته قدرًا كبيرًا من الذاكرة، كما يمكنك اختيار مساحة القرص الصلب التي تريدها لكل تثبيت، حيث يمكنك الحصول على 10 جيجابايتات، ولكن توصي بعض المصادر بمساحة تصل إلى 50 جيجابايت أو أكثر ليعمل نظام التشغيل على نحو موثوق، كما توفِّر معظم تطبيقات الآلات الافتراضية مجموعة من الخيارات الجيدة مثل إنشاء قرص صلب مخصَّص ديناميكيًا يكبر ويصغر حسب الحاجة. تحتاج ما يلي لاستخدام برنامج Virtual Box: احصل على قرص أو صورة مثل ملف ISO لتثبيت لنظام التشغيل الذي تريد محاكاته، فإن Virtual Box غير قادر على توفير ذلك، لأنها مثل أنظمة تشغيل ويندوز والتي تُعَدّ منتجات تجارية لا يمكن توزيعها مجانًا. نزِّل برنامج التثبيت المناسب لنظام التشغيل لديك ثم ثبّته. افتح التطبيق وسيظهر ما يلي: اضغط على زر "جديد New" أعلى الزاوية اليسرى لإنشاء آلة افتراضية جديدة. اتبع التعليمات واملأ مربعات الحوار التالية بطريقة مناسبة كما يلي: أدخِل اسمًا للآلة الافتراضية الجديدة. اختر نظام التشغيل والإصدار الذي تريد تثبيته. حدّد مقدار الذاكرة RAM التي يجب تخصيصها، إذ نوصي باستخدام 2048 ميجابايت أو 2 جيجابايت. أنشئ قرصًا صلبًا افتراضيًا، واختر الخيارات الافتراضية عبر مربعات الحوار الثلاثة التي تحتوي على "إنشاء قرص صلب افتراضي الآن Create a virtual hard disk now" و VDI -أي صورة قرص افتراضي Virtual Disk Image- و"التخصيص الديناميكي Dynamically Allocated". اختر موقع وحجم ملف القرص الصلب الافتراضي، واختر اسمًا وموقعًا معقولًا للاحتفاظ به، وحدِّد الحجم إلى حوالي 50 جيجابايت أو بالمقدار الذي ترغب فيه. يجب الآن أن يظهر المربع الافتراضي الجديد في القائمة اليسرى لنافذة واجهة مستخدِم Virtual Box الرئيسية، ويمكنك النقر نقرًا مزدوجًا لفتحه وسيبدأ تشغيل الآلة الافتراضية، ولكن لن يُثبَّت نظام التشغيل OS عليه بعد، إذ يجب توجيه مربع الحوار إلى صورة أو قرص التثبيت، وسيُشغَّل على خطوات لتثبيت نظام التشغيل تمامًا كما هو الحال على أيّ جهاز حقيقي. تحذير: يجب التأكد من توفر صورة نظام التشغيل التي تريد تثبيتها على الآلة الافتراضية وتثبيتها مباشرةً، فإذا ألغيت العملية في هذه المرحلة، فيمكن أن يؤدي ذلك إلى جعل الآلة الافتراضية غير قابلة للاستخدام، وبالتالي يجب حذفها وإنشاؤها مرةً أخرى. يجب بعد اكتمال العملية أن يكون لديك آلة افتراضية تشغّل نظام تشغيل ضمن نافذة على الحاسوب المضيف. يمكنك التعامل مع تثبيت نظام التشغيل الافتراضي تمامًا كما تفعل مع أيّ تثبيت حقيقي، إذ يمكنك مثلًا تثبيت برنامج مكافحة الفيروسات لحمايته من الفيروسات بالإضافة إلى تثبيت المتصفحات التي تريد اختبارها. يُعَدّ امتلاك آلات افتراضية متعددة مفيدًا جدًا خاصة بالنسبة لاختبار Windows IE/Edge، إذ لا يمكنك على نظام ويندوز تثبيت إصدارات متعددة من المتصفح الافتراضي مع بعضها بعضًا، لذلك يمكن أن ترغب في إنشاء مكتبة من الآلات الافتراضية للتعامل مع اختبارات مختلفة حسب الحاجة مثل: Windows 10 مع Edge 14. Windows 10 مع Edge 13. ملاحظة: هناك شيء جيد آخر تتمتع به الآلات الافتراضية، وهو أن صور القرص الصلب الافتراضي مستقلة بذاتها إلى حد ما، فإذا كنت تعمل ضمن فريق، فيمكنك إنشاء صورة قرص افتراضي واحدة ثم نسخها وتمريرها، لذا تأكد فقط من أنه لديك التراخيص المطلوبة لتشغيل جميع نسخ ويندوز أو أيّ شيء آخر تريد تشغيله إذا كان منتجًا مرخصًا. الاختبارات الآلية والتطبيقات التجارية يمكنك التخلص من معاناة اختبار المتصفحات باستخدام نظام اختبار آلي، إذ يتطلب إعداد نظام اختبار آلي مثل التطبيق Selenium بعض الإعداد، ولكنه يستحق هذا العناء عند تشغيله، كما توجد أدوات تجارية متاحة مثل Sauce Labs و Browser Stack و LambdaTest المفيدة في هذا المجال دون الحاجة إلى القلق بشأن الإعداد إن أردت استثمار بعض المال في الاختبار، وهناك بديل آخر وهو استخدام أدوات الاختبار الآلية بدون شيفرة برمجية مثل Endtest. اختبار المستخدمين سننهي هذا المقال بالحديث قليلًا عن اختبار المستخدِم User Testing الذي يمكن أن يكون خيارًا جيدًا إذا كان لديك مجموعة مستخدِمين راغبين في اختبار وظائفك الجديدة، وضَع في الحسبان أنّ هذا الاختبار يمكن أن يكون بسيطًا أو معقدًا حسب رغبتك، إذ يمكن أن تكون مجموعة المستخدِمين مجموعةً من الأصدقاء أو الزملاء أو مجموعة من المتطوعين بدون أجر أو بأجر اعتمادًا على ما إذا كان لديك أية أموال تريد إنفاقها على الاختبار. سينظر المستخدموِن إلى الصفحة أو العرض الذي يحتوي على الوظيفة الجديدة الموجودة على خادم تطوير، لذا لا تضع الموقع النهائي أو تجعله حيًا مباشرًا حتى تنتهي من الاختبار، ويجب أن تجعلهم يتبعون بعض الخطوات ويعطون تقريرًا بالنتائج التي يحصلون عليها، ويُعَدّ تقديم مجموعة من الخطوات التي تسمى سكربتًا في بعض الأحيان أمرًا مفيدًا بحيث تحصل على نتائج أكثر وثوقية فيما يتعلق بما تحاول اختباره، كما يمكن تحويل معايير الاختبار المفصلة إلى خطوات يجب اتباعها مثل الخطوات التالية التي تصلح لمستخدِم يمكنه الرؤية: انقر على زر علامة الاستفهام باستخدام الفأرة على حاسوبك عدة مرات وحدّث نافذة المتصفح. حدّد زر علامة الاستفهام وفعّله باستخدام لوحة المفاتيح على حاسوبك عدة مرات. اضغط على زر علامة الاستفهام عدة مرات على جهازك الذي يعمل باللمس. يجب أن يؤدي تبديل الزر إلى ظهور أو إخفاء مربع المعلومات. هل يحصل ذلك في جميع الحالات الثلاث السابقة؟ هل النص مقروء؟ هل يتحرك مربع المعلومات بسلاسة عندما يظهر أو يختفي؟ كما تُعَدّ الإجراءات التالية مفيدة عند إجراء الاختبارات: اضبط ملف تعريف منفصل للمتصفح مع تعطيل ملحقات هذا المتصفح، وأجرِ اختباراتك في هذا الملف التعريفي. استخدِم وظيفة وضع التصفح الخاص أو المتخفي للمتصفح مثل التصفح الخاص Private Browsing في فايرفوكس Firefox ووضع التصفح المتخفي Incognito Mode في كروم Chrome عند إجراء الاختبارات حتى لا تُحفَظ أشياء مثل ملفات الارتباط Cookies والملفات المؤقتة. صُمِّمت هذه الخطوات للتأكد من أنّ المتصفح الذي تختبره "نقي" قدر الإمكان، أي أنه لا يوجد شيء مُثبَّت يمكن أن يؤثر على نتائج الاختبارات. ملاحظة: يوجد خيار آخر مفيد من استراتيجية اختبار Lo-fi إذا كان لديك الأجهزة المتاحة، وهو اختبار موقع الويب على الهواتف أو الأجهزة الأخرى منخفضة الإمكانات، فهناك فرصة أكبر لتباطؤ الموقع نظرًا لأن المواقع تكبر وتزيد فيها التأثيرات مع الوقت، لذلك عليك أن تبدأ في منح الأداء مزيدًا من الاهتمام. ستؤدي محاولة تشغيل وظائفك على جهاز منخفض الإمكانات إلى زيادة احتمالية أن تكون التجربة جيدةً على الأجهزة المتطورة. ملاحظة: توفِّر بعض بيئات التطوير من جانب الخادم آليات مفيدةً لنشر تغييرات الموقع لمجموعة محددة من المستخدِمين فقط، مما يوفر آلية مفيدة للحصول على ميزة تختبرها مجموعة من المستخدِمين دون الحاجة إلى استخدام خادم تطوير منفصل، ومن الأمثلة على ذلك الميزة Django Waffle Flags. الخلاصة يجب أن يكون لديك الآن فكرة جيدة عما يمكنك تطبيقه لتحديد جمهورك المستهدَف أو قائمة المتصفحات المستهدفة، ثم إجراء اختبار التوافق مع المتصفحات على تلك القائمة بفعالية، وسنوجِّه اهتمامنا في المقال التالي إلى مشاكل الشيفرة البرمجية الفعلية التي يكشف عنها الاختبار بدءًا من مشاكل HTML و CSS. ترجمة -وبتصرُّف- للمقال Strategies for carrying out testing. اقرأ أيضًا مدخل إلى اختبار مشاريع الويب للتوافق مع المتصفحات أدوات مطوري الويب المدمجة في المتصفحات
  23. سنطّلع في هذا المقال على مشاكل التوافق مع المتصفحات Cross-browser الشائعة التي ستواجهها في شيفرة HTML و CSS، بالإضافة إلى الأدوات التي يمكن استخدامها لمنع حدوثها أو إصلاحها، إذ يتضمن ذلك كشف الأخطاء المحتملة في الشيفرة البرمجية والتعامل مع بادئات CSS واستخدام أدوات تطوير المتصفح لتعقّب المشاكل واستخدام تعويض نقص دعم المتصفحات Polyfill لإضافة الدعم إليها، ومعالجة مشاكل التصميم المتجاوب مع الشاشات وغير ذلك. المتطلبات الأساسية: يجب أن تتعلم أساسيات لغات HTML و CSS وجافاسكربت JavaScript، وأن تكون لديك فكرة عن المبادئ عالية المستوى لاختبار التوافق مع المتصفحات. الهدف: أن تكون قادرًا على تشخيص مشاكل التوافق مع المتصفحات المختلفة الشائعة في شيفرة HTML و CSS واستخدام الأدوات والتقنيات المناسبة لإصلاحها. مشاكل لغتي HTML و CSS تكمن مشاكل لغتَي HTML و CSS في حقيقة أنهما لغتان بسيطتان إلى حد ما، ولا يأخذها المطورون على محمل الجد في أغلب الأحيان من حيث التأكد من أن الشيفرة مُعَدّة جيدًا وفعالة وتصف الغرض من الميزات الموجودة في الصفحة. تُستخدَم لغة جافاسكربت في أسوأ الحالات لإنشاء محتوى صفحة الويب وتنسيقها بالكامل، مما يجعل صفحاتك غير شاملة وذات أداء ضعيف، ويُعَد إنشاء عناصر DOM مكلفًا، كما لا تُدعَم الميزات الجديدة في حالات أخرى باستمرار على متصفحات متعددة، مما يجعل بعض الميزات والتنسيقات لا تعمل عند بعض المستخدِمين. تُعَدّ مشاكل التصميم المتجاوب مع الشاشات شائعًا أيضًا، إذ يمكن أن يوفر الموقع الذي يبدو جيدًا في متصفح على حاسوب تجربةً سيئةً على هاتف محمول، لأن المحتوى صغير جدًا بحيث لا يمكن قراءته أو بسبب كون الموقع بطيئًا بسبب الحركات الثقيلة. لننتقل الآن إلى كيفية تقليل الأخطاء على المتصفحات التي تنتج عن شيفرة HTML أو CSS. إصلاح المشاكل العامة يُعَدّ اختبار بعض المتصفحات الحديثة على الحاسوب وعلى الهاتف المحمول استراتيجيةً جيدةً للتأكد من عمل شيفرتك بنجاح قبل التركيز على مشاكل التوافق مع المتصفحات. قدّمنا في مقالَي تنقيح أخطاء شيفرة HTML وتنقيح أخطاء شيفرة CSS بعض الإرشادات الأساسية حول تنقيح أخطاء شيفرة HTML و CSS، فإذا لم تكن على دراية بالأساسيات، فيجب عليك بالتأكيد مراجعة هذين المقالين قبل المتابعة، إذ يتعلق الأمر بالتحقق مما إذا كانت شيفرة HTML و CSS منسقةً جيدًا ولا تحتوي على أيّ أخطاء صياغية. ملاحظة: تظهر إحدى المشاكل الشائعة في لغتَي CSS و HTML عندما تبدأ قواعد CSS المختلفة في التعارض مع بعضها البعض، ويمكن أن يشكّل ذلك مشكلةً خاصةً عند استخدام شيفرة خارجية مثل استخدام إطار عمل خاص بلغة CSS ثم تجد أن أحد أسماء الأصناف Class التي يستخدِمها هذا الإطار يتعارض مع اسم استخدَمته مسبقًا لغرض مختلف، أو يمكن أن تجد أن شيفرة HTML التي تنشئها واجهة برمجة تطبيقات جهة خارجية -مثل إنشاء إعلانات- تتضمن اسم صنف أو معرّف ID استخدمته مسبقًا لغرض مختلف، ويمكنك ضمان عدم حدوث ذلك من خلال البحث في الأدوات التي تستخدِمها أولًا وتصميم شيفرتك على أساسها، ويمكنك استخدام فضاء أسماء Namespace في CSS إذا كان لديك عنصر واجهة مستخدم Widget مثلًا، ولكن تأكد من أنه يحتوي على صنف مميز، ثم استخدم المحدّدات Selectors التي تحدد العناصر ضمن عنصر واجهة المستخدم باستخدام هذا الصنف، وبالتالي تقل احتمالية التعارضات مثل ‎.audio-player ul a. التحقق من صحة شيفرة HTML و CSS يتضمن التحققُ من صحة شيفرة HTML التأكدَ من أن جميع الوسوم Tags مغلقة ومتداخلة بطريقة صحيحة، وأنك تستخدِم DOCTYPE والوسوم لغرضها الصحيح، كما يجب التحقق من صحة الشيفرة البرمجية بانتظام، وإحدى الخدمات التي يمكنها التحقق من صحة الشيفرة البرمجية هي خدمة Markup Validation Service من W3C التي تسمح لك بالإشارة إلى شيفرتك البرمجية ثم تعيد قائمةً بالأخطاء: كما تحتاج إلى شيفرة CSS للتحقق من أنّ أسماء الخاصيات مكتوبة بطريقة صحيحة وأنّ قيم الخاصيات صحيحة وصالحة مع الخاصيات المستخدَمة فيها، وللتحقق من عدم فقدان أي أقواس معقوصة وما إلى ذلك، كما تحتوي W3C على أداة التحقق CSS Validator أيضًا. منقحات الصياغة Linters لا يشير تطبيق منقّح الصياغة Linter إلى الأخطاء فقط، وإنما يمكنه إعطاء تحذيرات حول الممارسات السيئة في شيفرة CSS، ويمكن تخصيص منقّحات الصياغة لتكون أكثر أو أقل صرامةً في تقارير الأخطاء أو التحذيرات الناتجة. هناك العديد من تطبيقات منقحات الصياغة على الإنترنت وأفضلها تطبيق Dirty Markup لشيفرات HTML و CSS وجافاسكربت وتطبيق CSS Lint لشيفرة CSS فقط، إذ تسمح لك هذه التطبيقات بلصق شيفرتك في نافذة ما ثم ستضع علامة x على الأخطاء، ويمكن بعد ذلك تحريك مؤشر الفأرة فوقها للحصول على رسالة خطأ تخبرك ما هي المشكلة، ويسمح لك تطبيق Dirty Markup بإجراء إصلاحات على شيفرة HTML باستخدام زر التنظيف "Clean". ولكن لا يُعَدّ ذلك ملائمًا، إذ ستضطر إلى نسخ شيفرتك ولصقها في صفحة ويب للتحقق من صحتها عدة مرات، إذ تريد منقّح صياغة يتناسب مع سير عملك بأقل قدر من المتاعب، كما تحتوي العديد من محرّرات الشيفرات البرمجية على إضافات تنقيح الصياغة، إذ يحتوي المحرّر Atom من GitHub مثلًا على نظام بيئي غني بالإضافات مع العديد من خيارات تنقيح الأخطاء، وإليك مثال عن كيفية عمل هذه الإضافات Plugins: ثبّت المحرّر Atom إذا لم يكن لديك إصدار حديث مثبَّت مسبقًا. انتقل إلى مربع حوار تفضيلات Atom من خلال اختيار قائمة Atom ثم التفضيلات Preferences على نظام Mac أو من قائمة ملف File ثم Preferences في نظامَي Windows و Linux، ثم اختر خيار التثبيت Install في القائمة اليسرى. اكتب الكلمة "lint" في حقل البحث عن الحزم Search Packages واضغط على الزر Enter أو Return للبحث عن الحِزم المتعلقة بتنقيح الصياغة. يجب أن ترى حزمة تسمى "lint" في أعلى القائمة، لذا ثبّتها باستخدام زر التثبيت Install، إذ تعتمد منقّحات الصياغة الأخرى عليها في العمل، ثم ثبّت الإضافة linter-csslint لتنقيح صياغة شيفرة CSS والإضافة linter-tidy لتنقيح صياغة شيفرة HTML. حاول تحميل ملف HTML وملف CSS بعد انتهاء تثبيت الحزم، حيث سترى المشاكل مميزةً باللون الأخضر للتحذيرات وسترى دوائر حمراء للأخطاء بجوار أرقام الأسطر، ولوحةً منفصلةً في الأسفل توفِّر أرقام الأسطر ورسائل الخطأ وقيمًا مقترحة أو إصلاحات أخرى في بعض الأحيان. يتوفر لدى المحررات المشهورة الأخرى حزم تنقيح صياغة مماثلة مثل: SublimeLinter للمحرّر Sublime Text. Notepad++ Linter. VSCode linters. أدوات المطور في المتصفحات تتميز أدوات المطور المُضمَّنة في معظم المتصفحات بأدوات مفيدة لتعقب الأخطاء خاصة في شيفرة CSS. ملاحظة: لا تظهر أخطاء شيفرة HTML بسهولة في أدوات المطور، إذ سيحاول المتصفح تصحيح الوسوم ذات التنسيق السيئ تلقائيًا، حيث تُعَدّ أداة التحقق W3C Validator أفضل طريقة للعثور على أخطاء HTML. يعرض فاحص CSS في المتصفح Firefox مثلًا تصريحات CSS غير المُطبَّقة مشطوبة مع مثلث تحذير بجانبها، وسيوفر تحريك الفأرة فوق مثلث التحذير رسالةً تشرح الخطأ كما يلي: تمتلك أدوات التطوير في المتصفحات الأخرى ميزات مماثلة أيضًا. المشاكل الشائعة للتوافق مع المتصفحات لننتقل الآن إلى إلقاء نظرة على بعض مشاكل HTML و CSS الأكثر شيوعًا للتوافق مع المتصفحات، فالمجالات الرئيسية التي سنركز عليها هي نقص دعم الميزات الحديثة ومشاكل التخطيط. لا تدعم المتصفحات القديمة الميزات الحديثة تُعَدّ هذه المشكلة شائعةً خاصةً عندما تحتاج إلى دعم المتصفحات القديمة مثل إصدارات IE القديمة، أو إذا أردت استخدام ميزات تُطبَّق باستخدام بادئات CSS، إذ تعمل معظم وظائف HTML و CSS الأساسية مثل عناصر HTML وتنسيق CSS الأساسي للألوان والنصوص على معظم المتصفحات التي تريد دعمها، ولكن يظهر مزيد من المشاكل عندما تبدأ في استخدام ميزات أحدث من HTML و CSS. يُفضَّل البحث عن المتصفحات المدعومة والتقنيات ذات الصلة المفيدة بعد تحديد قائمة بالتقنيات غير المدعومة بصورة عامة التي ستستخدمها. سلوك HTML الاحتياطي يمكن حل بعض المشاكل بمجرد الاستفادة من الطريقة التي تعمل بها لغة HTML و CSS. تُعامَل عناصر HTML التي لا يتعرّف عليها المتصفح بوصفها عناصر مضمنةً مجهولة الاسم، أي هي عناصر مضمنة فعالة بدون قيمة دلالية مثل عناصر <span>، ولا يزال بإمكانك الإشارة إليها باستخدام أسماءها وتنسيقها باستخدام لغة CSS، فما عليك سوى التأكد من أنها تتصرف كما تريدها أن تتصرف مثل إعداد الخاصية display‎ إلى شيء آخر غير inline إن كان هنالك حاجة. تحتوي عناصر HTML الأكثر تعقيدًا مثل <video> و <audio> و <canvas> على آليات احتياطية لإضافتها، والتي تعمل باستخدام المبدأ السابق نفسه، إذ يمكنك إضافة محتوًى احتياطيًا بين وسمَي الفتح والإغلاق، وستتجاهل المتصفحات غير الداعمة العنصر الخارجي بفعالية وتشغّل المحتوى المتداخل. إليك المثال التالي: <video id="video" controls preload="metadata" poster="img/poster.jpg"> <source src="video/tears-of-steel-battle-clip-medium.mp4" type="video/mp4"> <source src="video/tears-of-steel-battle-clip-medium.webm" type="video/webm"> <!-- Offer download --> <p>Your browser does not support HTML5 video; here is a link to <a href="video/tears-of-steel-battle-clip-medium.mp4">view the video</a> directly.</p> </video> يتضمن هذا المثال رابطًا بسيطًا يسمح لك بتنزيل الفيديو إذا لم يعمل مشغل فيديو HTML، لذا لا يزال على الأقل بإمكان المستخدِم الوصول إلى الفيديو. تعرض عناصر نموذج HTML صفات احتياطية، إذ أدخلت لغة HTML5 أنواعًا خاصة من عناصر <input> لإدخال معلومات محددة في النماذج مثل الأوقات والتواريخ والألوان والأعداد وما إلى ذلك أو يستعمل النوع النصي type="text"‎ إن لم يدعم المتصفح نوعًا حديثًا يقدم ميزة جديدة، وهي مفيدة جدًا لا سيما على منصات الهاتف المحمول، إذ توفر طريقةً لإدخال البيانات التي تُعَدّ مهمةً جدًا لتجربة المستخدِم، كما توفر المنصات الداعمة أدوات واجهة مستخدِم خاصةً عند استخدام هذه الأنواع من حقول الإدخال مثل أداة التقويم لإدخال التواريخ. يوضِّح المثال التالي حقول إدخال التاريخ والوقت: <form> <div> <label for="date">Enter a date:</label> <input id="date" type="date"> </div> <div> <label for="time">Enter a time:</label> <input id="time" type="time"> </div> </form> يكون خرج الشيفرة السابقة كما يلي: ملاحظة: يمكنك مشاهدة هذا الخرج قيد التشغيل مباشرةً على forms-test.html، كما يمكنك الاطلاع على شيفرة هذا المثال على GitHub. إذا عرضت هذا المثال على متصفح داعم مثل المتصفح Chrome للحاسوب ولنظام Android أو iOS Safari، فسترى عناصر واجهة المستخدِم أو الميزات الخاصة قيد التشغيل أثناء إدخال البيانات. بينما ستعود المدخلات إلى مدخلات نص عادية على منصة غير داعمة مثل Internet Explorer، لذا لا يزال بإمكان المستخدِم إدخال بعض المعلومات على الأقل. سلوك CSS الاحتياطي يمكن القول أنّ لغة CSS أفضل من لغة HTML في الإجراءات الاحتياطية، فإذا صادف المتصفح تصريحًا أو قاعدةً لا يفهمها، فسيتخطاها دون تطبيقها أو ارتكاب خطأ ما، ويمكن أن يكون ذلك محبطًا لك وللمستخدِمين إذا انتقل هذا الخطأ إلى شيفرة الإنتاج، ولكن هذا يعني على الأقل عدم انهيار الموقع بأكمله بسبب خطأ واحد. لنلقِ نظرةً على المثال التالي الذي يحوي مربعًا بسيطًا مُنسَّقًا باستخدام CSS المختلفة: ملاحظة: يمكنك مشاهدة هذا المثال قيد التشغيل مباشرةً على button-with-fallback.html. يحتوي الزر على عدد من التصريحات الخاصة بتنسيقه، ولكن أكثر ما نهتم به هو ما يلي: button { ... background-color: #ff0000; background-color: rgba(255,0,0,1); box-shadow: inset 1px 1px 3px rgba(255,255,255,0.4), inset -1px -1px 3px rgba(0,0,0,0.4); } button:hover { background-color: rgba(255,0,0,0.5); } button:active { box-shadow: inset 1px 1px 3px rgba(0,0,0,0.4), inset -1px -1px 3px rgba(255,255,255,0.4); } استخدمنا هنا الخاصية background-color من النمط RGBA التي تغير العتامة Opacity عند التمرير على الزر لمنح المستخدم تلميحًا إلى أنّ الزر تفاعلي، وبعض الظل شبه الشفاف الداخلي box-shadow لإعطاء الزر ملمسًا وعمقًا. تكمن المشكلة في أن ألوان RGBA وخاصية الظلال box-shadow لا تعمل في إصدارات IE الأقدم من الإصدار 9، إذ لن تظهر الخلفية على الإطلاق في الإصدارات القديمة، لذا سيكون النص غير قابل للقراءة وسيئًا جدًا. يمكن حل هذه المشكلة من خلال إضافة تصريح ثانٍ للخاصية background-color الذي يحدد فقط اللون السداسي عشر المدعوم في المتصفحات القديمة، ويعمل بوصفه بديلًا احتياطيًا عن الميزات الحديثة إذا لم تعمل بنجاح، فما يحدث هو أن المتصفح الذي يزور هذه الصفحة يطبّق أولًا القيمة الأولى للخاصية background-color، ثم يبدّل القيمة الأولية بقيمة التصريح الثاني عندما يصل إليه إذا دعم هذا المتصفح ألوان RGBA، فإذا لم يكن الأمر كذلك، فسيتجاهل التصريح بالكامل وسيمضي قدمًا. ملاحظة: ينطبق الأمر نفسه على ميزات CSS الأخرى مثل استعلامات الوسائط Media Queries وكتل ‎@font-face و ‎@supports، فإذا لم تكن مدعومةً، فسيتجاهلها المتصفح فقط. دعم المحددات Selector لن تُطبَّق ميزات CSS على الإطلاق إذا لم تستخدِم محددات CSS الصحيحة لتحديد العنصر الذي تريد تنسيقه، فإذا كتبتَ محددًا في قائمة محددات مفصولة بفاصلة بصورة غير صحيحة، فقد لا يُحدد أو يطابق أي عنصر، وإذا كان المُحدد غير صالح، فسيجري تجاهل كل القائمة مع كتلة التنسيقات طبعًا، لذلك أضف لاحقة الصنف الزائف ‎:-moz-‎ فقط في قائمة محددات متسامحة forgiving selector list مثل ‎:where(::-moz-thumb)‎ واحذر أن تضيفها أو تستعملها في قائمة محددات ليست ضمن ‎:is()‎ أو ‎:where()‎ التي تتسامح مع محددات غير صالحة أو صحيحة لأن كل المتصفحات سوى متصفح Firefox سيراها غير صالحة وبالتالي يتجاهل كل القائمة، ولاحظ أن كلا من ‎:is()‎ أو ‎:where()‎ يمكن تمريرهما ضمن قائمة محددات أخرى من ضمنها ‎:has()‎ و ‎:not()‎. وقد وجدنا أن فحص العنصر الذي تحاول تنسيقه باستخدام أدوات التطوير في المتصفح أمر مفيد، ثم يجب النظر إلى مسار تنقل شجرة DOM الذي تميل أدوات فحص DOM إلى توفيره لمعرفة ما إذا كان المحدِّد منطقيًا بالمقارنة به. يمكنك الحصول على هذا النوع من الخرج أسفلَ أداة فحص DOM مثلًا في أدوات تطوير فايرفوكس Firefox: إذا أردت استخدام المحدد التالي مثلًا، فستتمكن من رؤية أنه لن يحدد العنصر input كما تريد: form > #date لا يُعَدّ عنصر الإدخال date في النموذج ابنًا مباشرًا للعنصر <form>، لذا يُفضَّل استخدام محدد حفيد Descendant Selector عام بدلًا من محدد ابن Child Selector. التعامل مع بادئات CSS تأتي مجموعة أخرى من المشاكل من استخدام البادئات Prefixes الخاصة بلغة CSS، وهي آلية مستخدمة للسماح لبائعي المتصفحات بتطبيق إصدارهم من ميزة CSS أو جافاسكربت أثناء وجود التقنيات في حالة تجريبية ليتمكنوا من تعديلها لتكون صحيحة دون التعارض مع تطبيقات المتصفح الأخرى أو عمليات التطبيق النهائية التي تكون بدون بادئات مثل ما يلي: يستخدِم Mozilla البادئة -moz-. تستخدِم المتصفحات Chrome و Opera و Safari و Edge البادئة -webkit-. يستخدِم Microsoft البادئة -ms-. تستخدِم الإصدارات الأصلية من المتصفح Opera البادئة o-. لا يُفترَض أبدًا استخدام الميزات ذات البادئات في مواقع الإنتاج، فهي عرضة للتغيير أو الإزالة دون سابق إنذار، وتتسبب في حدوث مشاكل على المتصفحات المختلفة عندما يقرر المطورون استخدام الإصدار -webkit- فقط من خاصية ما، مما يعني أن الموقع لن يعمل في متصفحات أخرى، ويحدث ذلك كثيرًا لدرجة أنّ المتصفحات الأخرى بدأت في تنفيذ إصدارات مع البادئة -webkit- من خاصيات CSS المختلفة، لذلك ستعمل مع مثل هذه الشيفرة البرمجية، وقد انخفض استخدام البادئات لدى بائعي المتصفحات مؤخرًا بسبب هذه الأنواع من المشاكل، ولكن لا يزال هناك بعض البادئات التي يجب الاهتمام بها. إذا أصريت على استخدام الميزات ذات البادئات، فتأكد من استخدام الميزات الصحيحة (استعمل مثلًا موقع caniuse.com)، فإذا لم تكن متأكدًا من ذلك، فيمكنك التأكد من خلال إجراء بعض الاختبارات في المتصفحات مباشرةً. وحاول تضمين الإصدار الذي لا يحوي أي بادئات بعد الذي يحوي بادئات، حيث سيُتجاهل إن لم يكن مدعومًا ويُستخدَم إن كان مدعومًا مثل: .masked { -webkit-mask-image: url(MDN.svg); mask-image: url(MDN.svg); -webkit-mask-size: 50%; mask-size: 50%; } جرب المثال البسيط التالي: افتح موقع google.com أو أيّ موقع آخر يحتوي على عنوان بارز أو أيّ عنصر كتلي آخر Block-level Element. انقر بزر الفأرة الأيمن أو اضغط على مفتاح Cmd مع الضغط على العنصر، ثم اختر فحص Inspect أو فحص العنصر Inspect element أو أيًا كان الخيار الموجود في متصفحك، إذ يجب أن يؤدي ذلك إلى فتح أدوات التطوير في متصفحك مع تحديد العنصر في أداة فحص نموذج DOM. ابحث عن ميزة يمكنك استخدامها لتحديد هذا العنصر، فقد كان لشعار جوجل الرئيسي مثلًا المعرّف hplogo في وقت كتابة هذا المقال. خزّن مرجعًا لهذا العنصر في متغير كما يلي: const test = document.getElementById("hplogo"); حاول الآن ضبط قيمة جديدة لخاصية CSS التي تهتم بها في هذا العنصر، إذ يمكنك تطبيق ذلك باستخدام الخاصية style الخاصة بالعنصر، أي اكتب ما يلي في طرفية جافاسكربت مثلًا: test.style.transform = 'rotate(90deg)' يجب أن تكمِل طرفية جافاسكربت تلقائيًا أسماء الخاصيات الموجودة في المتصفح ومطابقة ما كتبته حتى الآن عندما تكتب تمثيل اسم الخاصية بعد النقطة الثانية (لاحظ كتابة أسماء خاصيات CSS في جافاسكربت بحالة أحرف سنام الجَمل وليس باستخدام الواصلة - )، ويُعَدّ ذلك مفيدًا لمعرفة إصدارات الخاصية المُطبَّقة في ذلك المتصفح. إن أردت تضمين ميزة حديثة، فاختبر دعمها باستعمال ‎@support التي تساعدك على معرفة دعم الميزة من المتصفح أساسًا من عدمه حتى تعرف إن كنت بحاجة إلى بادئات أو حلول أخرى. مشاكل التصميم المتجاوب مع الشاشات Responsive Design التصميم المتجاوب مع الشاشات هو إنشاء تخطيطات الويب التي تتغير لتناسب عوامل أشكال الأجهزة المختلفة مثل عروض الشاشات المختلفة أو الاتجاهات (عموديًا أو أفقيًا) أو درجات الدقة، إذ سيبدو تخطيط الحاسوب مثلًا سيئًا عند عرضه على هاتف محمول، لذا يجب توفير تخطيط مناسب للهاتف المحمول باستخدام استعلامات الوسائط Media Queries والتأكد من تطبيقه بطريقة صحيحة باستخدام إطار العرض Viewport. تُعَدّ الدقة Resolution مشكلةً كبيرةً أيضًا، فليس مرجحًا أن تحتاج الأجهزة المحمولة إلى صور ثقيلة وكبيرة مثل الحواسيب، ويمكن أن يكون لديها اتصالات إنترنت أبطأ وخطط بيانات باهظة الثمن تجعل عرض النطاق الترددي الضائع مشكلةً أكبر، كما يمكن أن تحتوي الأجهزة المختلفة على مجالات من درجات الدقة المختلفة، مما يعني أنّ الصور الأصغر يمكن أن تكون غير واضحة، وهناك عدد من الأساليب التي تسمح لك بالتغلب على مثل هذه المشاكل بدءًا من استعلامات الوسائط البسيطة للهاتف المحمول أولًا وحتى تقنيات الصور المتجاوبة مع الشاشات الأكثر تعقيدًا. البحث عن المساعدة هناك العديد من المشاكل الأخرى التي ستواجهها مع شيفرة HTML و CSS، مما يجعل معرفة كيفية العثور على إجابات عبر الإنترنت لا تقدر بثمن. يمكنك طرح مشكلتك في قسم الأسئلة والأجوبة من أكاديمية حسوب للحصول على حل لمشكلتك التي تواجهها، إذ يُحتمَل أنّ المطورين الآخرين قد واجهوا المشاكل نفسها التي تواجهها أنت الآن، أو يمكنك مساعدتهم، كما يمكنك الانتقال إلى مجتمع تطوير الويب في حسوب IO، وهو مجتمع خاص بمناقشة وطرح المواضيع والقضايا العامة المتعلقة بتطوير الويب ولغاتها المختلفة. يمكنك البحث أيضًا في موسوعة حسوب والتعرف على أي ميزة أو قسم تحتاج إلى استخدامه، كما يمكنك الرجوع إلى الموقع الشهري CanIUse لمعرفة دعم الميزات في المتصفحات. الخلاصة يجب أن تكون الآن على دراية بالأنواع الرئيسية لمشاكل توافق HTML و CSS على المتصفحات التي ستواجهها في تطوير الويب وكيفية إصلاحها. ترجمة -وبتصرُّف- للمقال Handling common HTML and CSS problems. اقرأ أيضًا المقال السابق: استراتيجيات اختبارات مشاريع الويب للتوافق مع المتصفحات مدخل إلى اختبار مشاريع الويب للتوافق مع المتصفحات الأدوات المستخدمة في بناء مواقع ويب
  24. تُعَد إدارة ضبط الخادم (يُشار إليها أيضًا باسم أتمتة تقانة المعلومات IT Automation) حلًا لتحويل إدارة بنيتك التحتية إلى الشيفرة البرمجية الأساسية، ولوصف جميع العمليات اللازمة لنشر خادم في مجموعة من سكربتات الإعداد المسبق Provisioning Scripts التي يمكن إصدارها وإعادة استخدامها بسهولة، ويمكنها تحسين التكامل لأيّ بنية خادم تحتية بصورة كبيرة بمرور الوقت. تحدثنا في مقال سابق عن الفوائد الرئيسية لتنفيذ إستراتيجية إدارة الضبط لبنية الخادم التحتية، وكيفية عمل أدوات إدارة الضبط والعناصر المشتركة بين هذه الأدوات. سنوضح في هذا المقال عملية أتمتة إعداد الخادم المسبق باستخدام الأداة Chef، وهي أداة قوية لإدارة الضبط تستفيد من لغة برمجة روبي Ruby لأتمتة إدارة البنية التحتية وإعدادها المسبق، وسنركز على مصطلحات اللغة والصياغة والميزات اللازمة لإنشاء مثال مبسّط للأتمتة الكاملة لنشر خادم ويب Ubuntu 18.04 باستخدام أباتشي Apache. تحتوي القائمة التالية على جميع الخطوات التي نحتاجها للأتمتة حتى الوصول إلى هدفنا: حدّث ذاكرة apt المخبئية. ثبّت أباتشي Apache. أنشئ مجلد المستند الجذر المُخصَّص. ضع ملف index.html في المستند الجذر المخصص. طبّق قالبًا لإعداد المضيف الوهمي المخصص. أعِد تشغيل أباتشي. سنبدأ بإلقاء نظرة على المصطلحات التي تستخدمها أداة Chef، ثم سنتعرّف على ميزات اللغة الرئيسية التي يمكن استخدامها لكتابة الوصفات Recipes، وسنشاركك في النهاية المثال الكامل لتتمكّن من تجربته بنفسك. ملاحظة: يهدف هذا المقال إلى تعريفك بلغة Chef وكيفية كتابة الوصفات لأتمتة إعداد خادمك المسبق. اطّلع على كيفية ضبط الأداة Chef لمعرفة الخطوات اللازمة لتثبيت هذه الأداة والبدء باستخدامها. هذا المقال جزء من سلسلة حول إدارة ضبط الخوادم، وإليك روابط فصول السلسلة: مدخل إلى إدارة ضبط الخوادم Configuration Management مبادئ إدارة ضبط الخوادم Configuration Management: كتابة دليل تشغيل الأداة Ansible مبادئ إدارة ضبط الخوادم Configuration Management: كتابة ملفات البيان Manifests للأداة Puppet مبادئ إدارة ضبط الخوادم Configuration Management: كتابة الوصفات Recipes في الأداة Chef البدء باستخدام الأداة Chef يجب أن نتعرف أولًا على المصطلحات والمفاهيم المهمة التي قدمتها الأداة Chef قبل البدء بالعمل. مصطلحات Chef خادم Chef: خادم مركزي يخزن المعلومات ويدير الإعداد المُسبق provisioning للعقد nodes. عقدة Chef: خادم فردي يديره خادم Chef. محطة عمل Chef: جهاز متحكِّم يُنشَأ فيه الإعداد المسبق ويُحمَّل على خادم Chef. الوصفة Recipe: ملف يحتوي على مجموعة من التعليمات (الموارد) لتنفيذها، إذ يجب أن تكون الوصفة موجودة في الدليل Cookbook. المورد Resource: جزء من الشيفرة البرمجية الذي يصرّح عن عنصر في النظام والإجراء الذي يجب تنفيذه، فمثلًا نصرّح عن مورد حزمة package مع إجراء التثبيت لتثبيت حزمةٍ ما. الدليل Cookbook: مجموعة من الوصفات والملفات الأخرى ذات الصلة المنظمة بطريقة مُعرَّفة مسبقًا لتسهيل مشاركة وإعادة استخدام أجزاء من الإعداد المسبق. السمات Attributes: هي تفاصيل حول عقدة معينة يمكن أن تكون السمات مؤتمتة، ويمكن تعريفها ضمن الوصفات. السمات المؤتمتة Automatic Attributes: هي المتغيرات العامة التي تحتوي على معلومات حول النظام مثل واجهات الشبكة ونظام التشغيل (المعروفة بالحقائق Facts في الأدوات الأخرى)، وتُجمَع هذه السمات المؤتمتة باستخدام أداة تسمى Ohai. الخدمات: تُستخدَم لبدء تغييرات حالة الخدمة مثل إعادة تشغيل الخدمة أو إيقافها. تنسيق الوصفة Recipe تُكتَب وصفات Chef باستخدام لغة روبي Ruby، والوصفة هي مجموعة من تعريفات الموارد التي ستنشئ مجموعة من التعليمات خطوة بخطوة لتنفّذها العقد، ويمكن مزج تعريفات الموارد مع شيفرة روبي البرمجية لمزيد من المرونة والتقسيم إلى وحدات Modularity. إليك مثال بسيط لوصفة ستشغِّل apt-get update ثم تثبّت vim: execute "apt-get update" do command "apt-get update" end apt_package "vim" do action :install end كتابة الوصفات Recipes أصبحت الآن على دراية بالمصطلحات الأساسية والتنسيق العام للوصفات في Chef، لذا سنتعرف على بعض ميزات الوصفات. التعامل مع المتغيرات يمكن تعريف المتغيرات المحلية ضمن الوصفات بوصفها متغيرات روبي المحلية العادية، حيث يوضح المثال التالي كيفية إنشاء متغير محلي يُستخدَم ضمن تعريف المورد لاحقًا: package = "vim" apt_package package do action :install end لهذه المتغيرات نطاق محدود، وتكون صالحة فقط ضمن الملف الذي عُرِّفت فيه، لذلك إذا أردتَ إنشاء متغير وإتاحته بصورة عامة، بحيث يمكنك استخدامه من أيٍّ من أدلتك أو وصفاتك، فيجب تعريف سمة مُخصَّصة. استخدام السمات Attributes تمثل السمات تفاصيلًا حول العقدة، حيث تحتوي أداة Chef على سمات مؤتمتة، وهي سمات تجمعها أداة تسمى Ohai وتحتوي على معلومات حول النظام (مثل المنصة واسم المضيف وعنوان IP الافتراضي)، ولكنها تتيح لك تعريف سماتك المُخصَّصة. للسمات مستويات أولوية مختلفة تُحدَّد حسب نوع السمة التي تنشِئها، تُعَد السمات default الاختيار الأكثر شيوعًا، حيث لا يزال ممكنًا تعديلها لتكون أنواع سمات أخرى عند الرغبة في ذلك. يوضح المثال التالي كيف سيبدو المثال السابق مع سمة العقدة default بدلًا من متغير محلي: node.default['main']['package'] = "vim" apt_package node['main']['package'] do action :install end الممارسة الموصَى بها عند تعريف متغيرات العقدة هي تنظيمها بوصفها قيمًا مُعمَّاة مختصَرة Hashes باستخدام الدليل Cookbook الحالي المستخدَم بوصفه مفتاحًا. استخدمنا main في حالتنا، لأن لدينا دليل بالاسم نفسه، مما يؤدي إلى تجنب الارتباك إذا أردت العمل مع أدلة متعددة يكون لها سمات بأسماء متماثلة. لاحظ أننا استخدمنا node.default عند تعريف السمة، ولكن استخدمنا node مباشرةً عند الوصول إلى قيمتها لاحقًا. يحدِّد استخدام node.default أننا ننشئ سمة من النوع الافتراضي، ويمكن تعديل قيمة هذه السمة لتكون نوع سمة آخر ذي أولوية أعلى مثل السمات العادية normal أو سمات التجاوز override. يمكن أن تكون أولوية السمات مربكة بعض الشيء في البداية، ولكنك ستعتادها بعد التدريب عليها، ولتوضيح هذا السلوك اطّلع على المثال التالي: node.normal['main']['package'] = "vim" node.override['main']['package'] = "git" node.default['main']['package'] = "curl" apt_package node['main']['package'] do action :install end الحزمة التي ستُثبَّت في المثال السابق هي الحزمة git، إذ ستجعل الأولوية الأعلى للنوع override تقييم node['main']['package']‎ ليكون git بغض النظر عن ترتيب تعريف السمات. استخدام الحلقات Loops تُستخدَم الحلقات عادةً لتكرار مهمة باستخدام قيم دخل مختلفة، فمثلًا يمكنك إنشاء مهمة واحدة واستخدام حلقة لتكرار المهمة مع جميع الحزم المختلفة التي تريد تثبيتها بدلًا من إنشاء 10 مهام لتثبيت 10 حزم مختلفة. تدعم Chef جميع بنى حلقات روبي لإنشاء حلقات ضمن الوصفات، حيث تُعَد تعليمة each خيارًا شائعًا للاستخدام البسيط: ['vim', 'git', 'curl'].each do |package| apt_package package do action :install end end يمكنك إنشاء متغير أو سمة لتعريف المعاملات التي تريد استخدامها ضمن الحلقة بدلًا من استخدام مصفوفة مُضمَّنة، مما يجعل الأشياء أكثر تنظيمًا وأسهل في القراءة. يستخدم المثال السابق نفسه متغيرًا محليًا لتعريف الحزم التي يجب تثبيتها فيما يلي: packages = ['vim', 'git', 'curl'] packages.each do |package| apt_package package do action :install end end استخدام التعليمات الشرطية يمكن استخدام التعليمات الشرطية لتحديد ما إذا كان يجب تنفيذ كتلة من الشيفرة البرمجية أم لا ديناميكيًا بناءً على متغير أو خرج أمرٍ ما مثلًا. تدعم الأداة Chef جميع تعليمات روبي الشرطية لإنشاء تعليمات شرطية ضمن الوصفات، وتدعم جميع أنواع الموارد خاصيتين تقيّيمان تعبيرًا قبل تحديد ما إذا كان يجب تنفيذ المهمة أم لا وهما: if_only و not_if. سيتحقق المثال التالي من وجود php قبل محاولة تثبيت التوسّع php-pear، إذ سيستخدم الأمر which للتحقق مما إذا كان هناك ملف php تنفيذي مُثبَّت حاليًا على هذا النظام، فإذا أعاد الأمر which php القيمة false، فلن تُنفَّذ هذه المهمة: apt_package "php-pear" do action :install only_if "which php" end إذا أردنا تطبيق العكس أي تنفيذ أمر في جميع الأوقات باستثناء الوقت الذي يُقيَّم فيه الشرط على أنه true، فسنستخدم التعليمة not_if بدلًا من ذلك، حيث سيثبت المثال التالي php5 ما لم يكن النظام CentOS: apt_package "php5" do action :install not_if { node['platform'] == 'centos' } end لنطبّق الآن تقييمات أكثر تعقيدًا، فمثلًا إذا أردتَ تنفيذ عدة مهام وفق شرط محدّد، فيمكنك استخدام أيٍّ من تعليمات روبي الشرطية المعيارية. ينفّذ المثال التالي فقط apt-get update عندما يكون النظام إما دبيان Debian أو أوبنتو Ubuntu: if node['platform'] == 'debian' || node['platform'] == 'ubuntu' execute "apt-get update" do command "apt-get update" end end تُعَد السمة node['platform']‎ سمة مؤتمتة من الأداة Chef، حيث استخدمنا المثال السابق فقط لتوضيح بنية شرطية أكثر تعقيدًا، ولكن يمكن استبدالها باختبار بسيط باستخدام السمة node['platform_family']‎ التي ستعيد "debian" لكل من نظامي دبيان وأوبنتو. استخدام القوالب templates تُستخدَم القوالب عادةً لإعداد ملفات الضبط، مما يسمح باستخدام المتغيرات والميزات الأخرى التي تهدف إلى جعل هذه الملفات أكثر تنوعًا وقابلية لإعادة الاستخدام. تستخدم الأداة Chef قوالب روبي المُضمَّنة Embedded Ruby -أو ERB اختصارًا، وهو التنسيق نفسه الذي تستخدمه الأداة Puppet، حيث تدعم هذه القوالب التعليمات الشرطية والحلقات والميزات الأخرى في لغة روبي. يوضح المثال التالي قالب ERB لإعداد مضيف أباتشي الوهمي باستخدام متغير لتحديد المستند الجذر لهذا المضيف: <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot <%= @doc_root %> <Directory <%= @doc_root %>> AllowOverride All Require all granted </Directory> </VirtualHost> يجب إنشاء مورد template لتطبيق القالب، ويوضح المثال التالي الطريقة التي ستطبِّق بها هذا القالب لاستبدال مضيف أباتشي الوهمي الافتراضي: template "/etc/apache2/sites-available/000-default.conf" do source "vhost.erb" variables({ :doc_root => node['main']['doc_root'] }) action :create end تضع الأداة Chef بعض الافتراضات عند التعامل مع الملفات المحلية لفرض التنظيم والتقسيم إلى وحدات Modularity، حيث ستبحث الأداة Chef عن ملف قالب vhost.erb ضمن مجلد القوالب templates الذي يجب أن يكون في الدليل نفسه الذي توجد فيه هذه الوصفة. تمتلك الأداة Chef نطاقًا أكثر صرامة للمتغيرات على عكس أدوات إدارة الضبط الأخرى التي رأيناها حتى الآن، وهذا يعني أنه يجب تقديم أيّ متغيرات تريد استخدامها ضمن القالب صراحةً عند تعريف المورد template. استخدمنا في مثالنا التابع variables لتمرير السمة doc_root التي نحتاجها في قالب المضيف الوهمي. تعريف وبدء الخدمات تُستخدَم موارد الخدمة service للتأكد من تهيئة الخدمات وتفعيلها، ولبدء إعادة تشغيل الخدمة، ويجب التصريح عن موارد الخدمة في الأداة Chef قبل محاولة إعلامها، وإلّا فسيظهر خطأ. لنأخذ المثال السابق لاستخدام القالب حيث ضبطنا مضيف أباتشي الوهمي. إذا أردتَ التأكد من إعادة تشغيل أباتشي بعد تغيير مضيف وهمي، فيجب أولًا إنشاء مورد خدمة service لخدمة أباتشي، ويوضح المثال التالي طريقة تعريف هذا المورد في Chef: service "apache2" do action [ :enable, :start ] end يجب الآن تضمين الخيار notify لبدء إعادة التشغيل عند تعريف مورد القالب: template "/etc/apache2/sites-available/000-default.conf" do source "vhost.erb" variables({ :doc_root => node['main']['doc_root'] }) action :create notifies :restart, resources(:service => "apache2") end مثال عن وصفة Recipe لنلقِ نظرة على الوصفة التي ستؤتمِت عملية تثبيت خادم ويب أباتشي على نظام لينكس أوبنتو Ubuntu كما ناقشنا سابقًا. يمكنك العثور على المثال الكامل بما في ذلك ملف القالب لإعداد أباتشي وملف HTML ليخدّمه خادم الويب على Github، ويحتوي المجلد على الملف Vagrantfile الذي يتيح لك اختبار الوصفة في إعداد مبسّط باستخدام آلة افتراضية تديرها أداة Vagrant. إليك الوصفة الكاملة: node.default['main']['doc_root'] = "/vagrant/web" execute "apt-get update" do command "apt-get update" end apt_package "apache2" do action :install end service "apache2" do action [ :enable, :start ] end directory node['main']['doc_root'] do owner 'www-data' group 'www-data' mode '0644' action :create end cookbook_file "#{node['main']['doc_root']}/index.html" do source 'index.html' owner 'www-data' group 'www-data' action :create end template "/etc/apache2/sites-available/000-default.conf" do source "vhost.erb" variables({ :doc_root => node['main']['doc_root'] }) action :create notifies :restart, resources(:service => "apache2") end لنتعرّف على كل جزء من الوصفة السابقة بمزيد من التفصيل: تبدأ الوصفة في السطر الأول بتعريف السمة node['main']['doc_root']‎، حيث كان بإمكاننا استخدام متغير محلي بسيط هنا، ولكن يجب أن تعرّف الوصفات المتغيرات العامة التي ستُستخدَم في الوصفات المُضمَّنة أو الملفات الأخرى في معظم حالات الاستخدام، لذا يجب إنشاء سمة بدلًا من متغير محلي، لأن المتغير المحلي له نطاق محدود. يشغّل مورد التنفيذ execute الأمرَ apt-get update في الأسطر 3-5. يثبّت المورد apt_package الحزمة apache2 في الأسطر 7-10. يفعّل مورد الخدمة service ويبدأ الخدمة apache2 في الأسطر 12-15، ويجب إعلام هذا المورد لاحقًا بإعادة تشغيل الخدمة، ويجب أن يأتي تعريف الخدمة قبل أيّ مورد يحاول إعلام الخدمة، وإلّا فستتلقّى خطأ. يستخدم المورد directory في الأسطر 17-22 القيمة التي تعرّفها السمة المخصصة node['main']['doc_root']‎ لإنشاء مجلد يكون بمثابة المستند الجذر. يُستخدَم المورد cookbook_file في الأسطر 24-29 لنسخ ملف محلي إلى خادم بعيد، إذ سينسخ هذا المورد الملف index.html ويضعه في المستند الجذر الذي أنشأناه في مهمة سابقة. يطبق مورد القالب template قالب مضيف أباتشي الوهمي ويعلِم خدمة apache2 بإعادة التشغيل في الأسطر 31-36. الخلاصة تُعَد Chef أداة قوية لإدارة الضبط وتستفيد من لغة روبي لأتمتة إعداد الخادم المسبق ونشره، وتمنحك حرية استخدام ميزات اللغة المعيارية لتحقيق أقصى قدر من المرونة مع توفير لغة DSL مُخصَّصة لبعض الموارد. ترجمة -وبتصرُّف- للمقال Configuration Management 101: Writing Chef Recipes لصاحبته Erika Heidi. اقرأ أيضًا كيفية تثبيت Chef و Ruby مع RVM على خادوم افتراضي خاص يعمل بنظام Ubuntu مرجع إلى أشهر أوامر لينكس
  25. تعرفنا في المقال السابق على الملفات القابلة للتنفيذ في نظام التشغيل وتمثيلها باستخدام الصيغة ELF وسنوضح في هذا المقال بعض المفاهيم المتعلقة بصيغة ملفات ELF مثل تنقيح الأخطاء Debugging وكيفية إنشاء أقسام مخصصة فيها وسكربتات الرابط Linker Scripts التي يستخدمها الرابط لبناء الأقسام Sections المُكوِّنة للمقاطع Segments، ولكن لنتعرّف أولًا على مفهوم ملفات ELF القابلة للتنفيذ. ملفات ELF القابلة للتنفيذ تُعَد الملفات القابلة للتنفيذ أحد الاستخدامات الأساسية لصيغة ELF. يحتوي الملف الثنائي على كل ما هو مطلوب لنظام التشغيل لتنفيذ الشيفرة البرمجية بالطريقة المطلوبة، حيث صُمِّم الملف التنفيذي لتشغيله في عملية ذات فضاء عناوين فريد، لذا يمكن للشيفرة البرمجية وضع افتراضات حول مكان تحميل أجزاء البرنامج المختلفة في الذاكرة. يوضح المثال الآتي اختبار أجزاء ملفٍ قابل للتنفيذ باستخدام أداة readelf. يمكننا أن نرى العناوين الوهمية التي يجب وضع مقاطع LOAD فيها، حيث يمكننا أن نرى أنّ أحد هذه المقاطع مخصصٌ للشيفرة البرمجية ويمتلك أذونات القراءة والتنفيذ فقط، وهناك مقطع آخر مخصصٌ للبيانات ولديه أذونات القراءة والكتابة دون وجود أذونات التنفيذ، فبدونها لن تُميَّز الصفحات التي تدعم خطأ ما بأن لها أذونات التنفيذ حتى إن سمح هذا الخطأ للمهاجم بإدخال بيانات عشوائية، وبالتالي لن تسمح معظم المعالجات بأيّ تنفيذ للشيفرة البرمجية في تلك الصفحات. $ readelf --segments /bin/ls Elf file type is EXEC (Executable file) Entry point 0x4046d4 There are 8 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001c0 0x00000000000001c0 R E 8 INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000019ef4 0x0000000000019ef4 R E 200000 LOAD 0x000000000001a000 0x000000000061a000 0x000000000061a000 0x000000000000077c 0x0000000000001500 RW 200000 DYNAMIC 0x000000000001a028 0x000000000061a028 0x000000000061a028 0x00000000000001d0 0x00000000000001d0 RW 8 NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c 0x0000000000000044 0x0000000000000044 R 4 GNU_EH_FRAME 0x0000000000017768 0x0000000000417768 0x0000000000417768 0x00000000000006fc 0x00000000000006fc R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 8 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 يجب تحميل مقاطع البرنامج على هذه العناوين، حيث تتمثل الخطوة الأخيرة للرابط Linker في تحليل معظم المنقولات Relocations وتصحيحها باستخدام العناوين المطلقة المُفترَضة، ثم تجاهل البيانات التي تصف الانتقال في الملف الثنائي النهائي دون وجود طريقة لإيجاد هذه المعلومات بعد الآن. تحتوي الملفات القابلة للتنفيذ عمومًا على اعتماديات Dependencies خارجية للمكتبات المشتركة Shared Libraries أو أجزاء من الشيفرة البرمجية المشتركة يمكن تجريدها ومشاركتها بين أجزاء النظام بأكمله، حيث تتعلق جميع الأجزاء الغريبة في المثال السابق باستخدام المكتبات المشتركة التي سنوضّحها لاحقًا. تنقيح الأخطاء Debugging يُشار تقليديًا إلى الطريقة الأساسية لتنقيح أخطاء ما بعد التعطل باسم التفريغ الأساسي Core Dump، حيث يأتي مصطلح الأساسي Core من الخصائص الفيزيائية الأصلية للذاكرة المغناطيسية الأساسية التي تستخدم اتجاه الحلقات المغناطيسية الصغيرة لتخزين الحالة. يُعَد التفريغ الأساسي لقطة كاملة للبرنامج عند عمله في وقت معين، ويمكن بعد ذلك استخدام منقح أخطاء Debugger لفحص هذا التفريغ وإعادة بناء حالة البرنامج. يوضح المثال التالي نموذجًا لبرنامج يكتب في موقع ذاكرة عشوائية لغرض التعطل، حيث ستتوقف العمليات ويُسجَّل تفريغ للحالة الحالية: $ cat coredump.c int main(void) { char *foo = (char*)0x12345; *foo = 'a'; return 0; } $ gcc -Wall -g -o coredump coredump.c $ ./coredump Segmentation fault (core dumped) $ file ./core ./core: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-style, from './coredump' $ gdb ./coredump ... (gdb) core core [New LWP 31614] Core was generated by `./coredump'. Program terminated with signal 11, Segmentation fault. #0 0x080483c4 in main () at coredump.c:3 3 *foo = 'a'; (gdb) وبالتالي فإن ملف التفريغ الأساسي هو مجرد ملف ELF يحتوي على مجموعة من الأقسام التي يفهمها منقح الأخطاء لتمثيل أجزاء من البرنامج المُشغَّل. الرموز ومعلومات تنقيح الأخطاء يتطلب منقح الأخطاء gdb الملف القابل للتنفيذ الأصلي وملف التفريغ الأساسي لإعادة بناء بيئة جلسة تنقيح الأخطاء. لاحظ أن الملف القابل للتنفيذ الأصلي أُنشئ باستخدام الراية ‎-g التي توجه المصرِّف Compiler لتضمين جميع معلومات الأخطاء، حيث يجري الاحتفاظ بالمعلومات المتعلقة بعملية تنقيح الأخطاء الإضافية في أقسام خاصة من ملف ELF، إذ تصف هذه المعلومات بالتفصيل أشياءً مثل قيم المسجّل التي تحتوي حاليًا على المتغيرات المستخدمة في الشيفرة البرمجية وحجم المتغيرات وطول المصفوفات وغير ذلك. تكون هذه المعلومات بصيغة DWARF المعيارية التي تُعَد مرادفًا لصيغة ELF تقريبًا. يمكن أن يؤدي تضمين معلومات تنقيح الأخطاء إلى جعل الملفات والمكتبات القابلة للتنفيذ كبيرة جدًا، إذ لا تزال تشغل مساحة كبيرة على القرص الصلب بالرغم من أنها ليست مطلوبة في الذاكرة للتشغيل الفعلي، وبالتالي يجب إزالة هذه المعلومات من ملف ELF. يمكن نقل كل من الملفات التي أًزيلت منها هذه المعلومات والملفات التي لم تًُزال منها هذه المعلومات، ولكن توفّر معظم طرق توزيع أو نشر الملفات الثنائية binary distribution الحالية معلومات لتنقيح الأخطاء في ملفات منفصلة. يمكن استخدام أداة objcopy لاستخراج معلومات تنقيح الأخطاء (‎--only-keep-debug) ثم إضافة رابط في الملف القابل للتنفيذ الأصلي إلى هذه المعلومات المُزالَة (‎--add-gnu-debuglink)، ثم سيكون هناك قسم خاص بالاسم ‎.gnu_debuglink موجود في الملف القابل للتنفيذ الأصلي ويحتوي على قيمة فريدة بحيث يمكن لمنقح الأخطاء عند بدء جلسات تنقيح الأخطاء التأكدَ من أنه يربط معلومات تنقيح الأخطاء الصحيحة بالملف التنفيذي الصحيح. يوضح المثال التالي إزالة معلومات تنقيح الأخطاء إلى ملفات منفصلة باستخدام الأداة objcopy: $ gcc -g -shared -o libtest.so libtest.c $ objcopy --only-keep-debug libtest.so libtest.debug $ objcopy --add-gnu-debuglink=libtest.debug libtest.so $ objdump -s -j .gnu_debuglink libtest.so libtest.so: file format elf32-i386 Contents of section .gnu_debuglink: 0000 6c696274 6573742e 64656275 67000000 libtest.debug... 0010 52a7fd0a R... تشغل الرموز مساحة أقل بكثير، ولكنها تُعَد هدفًا للإزالة من الخرج النهائي، إذ لن تكون هناك حاجة لبقاء معظم الرموز بمجرد ربط ملفات التعليمات المُصرَّفة object files لملف قابل للتنفيذ بالصورة النهائية. تُعَد الرموز مطلوبة لإصلاح مدخلات المنقولات Relocation، ولكن لن تكون الرموز بعد ذلك ضرورية تمامًا لتشغيل البرنامج النهائي. توفر سلسلة أدوات GNU لتجريد البرنامج في نظام لينكس خياراتٍ لإزالة الرموز. لاحظ أنه يجب تحليل بعض الرموز في وقت التشغيل (للربط الديناميكي Dynamic Linking)، ولكنها تُوضَع في جداول رموز ديناميكية منفصلة حتى لا تُزال وتجعل الخرج النهائي عديم الفائدة. التفريغ الأساسي Coredump يُعَد التفريغ الأساسي مجرد ملف ELF. يوضح المثال التالي مرونة صيغة ELF بوصفها صيغة ثنائية: $ readelf --all ./core ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: CORE (Core file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 52 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 15 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0 There are no sections in this file. There are no sections to group in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align NOTE 0x000214 0x00000000 0x00000000 0x0022c 0x00000 0 LOAD 0x001000 0x08048000 0x00000000 0x01000 0x01000 R E 0x1000 LOAD 0x002000 0x08049000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x003000 0x489fc000 0x00000000 0x01000 0x1b000 R E 0x1000 LOAD 0x004000 0x48a17000 0x00000000 0x01000 0x01000 R 0x1000 LOAD 0x005000 0x48a18000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x006000 0x48a1f000 0x00000000 0x01000 0x153000 R E 0x1000 LOAD 0x007000 0x48b72000 0x00000000 0x00000 0x01000 0x1000 LOAD 0x007000 0x48b73000 0x00000000 0x02000 0x02000 R 0x1000 LOAD 0x009000 0x48b75000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x00a000 0x48b76000 0x00000000 0x03000 0x03000 RW 0x1000 LOAD 0x00d000 0xb771c000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x00e000 0xb774d000 0x00000000 0x02000 0x02000 RW 0x1000 LOAD 0x010000 0xb774f000 0x00000000 0x01000 0x01000 R E 0x1000 LOAD 0x011000 0xbfeac000 0x00000000 0x22000 0x22000 RW 0x1000 There is no dynamic section in this file. There are no relocations in this file. There are no unwind sections in this file. No version information found in this file. Notes at offset 0x00000214 with length 0x0000022c: Owner Data size Description CORE 0x00000090 NT_PRSTATUS (prstatus structure) CORE 0x0000007c NT_PRPSINFO (prpsinfo structure) CORE 0x000000a0 NT_AUXV (auxiliary vector) LINUX 0x00000030 Unknown note type: (0x00000200) $ eu-readelf -n ./core Note segment of 556 bytes at offset 0x214: Owner Data size Type CORE 144 PRSTATUS info.si_signo: 11, info.si_code: 0, info.si_errno: 0, cursig: 11 sigpend: <> sighold: <> pid: 31614, ppid: 31544, pgrp: 31614, sid: 31544 utime: 0.000000, stime: 0.000000, cutime: 0.000000, cstime: 0.000000 orig_eax: -1, fpvalid: 0 ebx: 1219973108 ecx: 1243440144 edx: 1 esi: 0 edi: 0 ebp: 0xbfecb828 eax: 74565 eip: 0x080483c4 eflags: 0x00010286 esp: 0xbfecb818 ds: 0x007b es: 0x007b fs: 0x0000 gs: 0x0033 cs: 0x0073 ss: 0x007b CORE 124 PRPSINFO state: 0, sname: R, zomb: 0, nice: 0, flag: 0x00400400 uid: 1000, gid: 1000, pid: 31614, ppid: 31544, pgrp: 31614, sid: 31544 fname: coredump, psargs: ./coredump CORE 160 AUXV SYSINFO: 0xb774f414 SYSINFO_EHDR: 0xb774f000 HWCAP: 0xafe8fbff <fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov clflush dts acpi mmx fxsr sse sse2 ss tm pbe> PAGESZ: 4096 CLKTCK: 100 PHDR: 0x8048034 PHENT: 32 PHNUM: 8 BASE: 0 FLAGS: 0 ENTRY: 0x8048300 UID: 1000 EUID: 1000 GID: 1000 EGID: 1000 SECURE: 0 RANDOM: 0xbfecba1b EXECFN: 0xbfecdff1 PLATFORM: 0xbfecba2b NULL LINUX 48 386_TLS index: 6, base: 0xb771c8d0, limit: 0x000fffff, flags: 0x00000051 index: 7, base: 0x00000000, limit: 0x00000000, flags: 0x00000028 index: 8, base: 0x00000000, limit: 0x00000000, flags: 0x00000028 يمكننا أن نرى في المثال السابق اختبارًا باستخدام أداة readelf أولًا للملف الأساسي الناتج عن مثال إنشاء ملف تفريغ أساسي واستخدامه باستخدام منقح الأخطاء gdb. لا توجد أقسام أو منقولات أو معلومات أخرى غريبة في هذا الملف يمكن أن تكون مطلوبة لتحميل ملف قابل للتنفيذ أو مكتبة، إذ يتكون من سلسلة من ترويسات البرامج التي تمثل مقاطع LOAD التي هي عمليات تفريغ بيانات أولية أنشأتها النواة Kernel لتخصيصات الذاكرة الحالية. المكون الآخر لملف التفريغ الأساسي هو أقسام الملاحظات NOTE التي تحتوي على البيانات الضرورية لتنقيح الأخطاء ولكن ليس بالضرورة أن تُلتقَط في لقطة مباشرة لتخصيصات الذاكرة. يوفّر برنامج eu-readelf المُستخدَم في الجزء الثاني من المثال السابق رؤيةً أكثر اكتمالًا للبيانات من خلال فك تشفيرها. تقدّم الملاحظة PRSTATUS مجموعة من المعلومات حول العملية أثناء تشغيلها، فمثلًا يمكننا أن نرى من قيمة cursig أن البرنامج تلقى إشارة قيمتها 11 تمثل خطأ تقطيع segmentation fault. كما تتضمن بالإضافة إلى معلومات رقم العملية ملف تفريغ لجميع المسجلات الحالية. يمكن لمنقح الأخطاء بالنظر إلى قيم المسجّل إعادة بناء حالة المكدس وبالتالي توفير تعقب خلفي Backtrace، حيث يمكن لمنقح الأخطاء جنبًا إلى جنب مع الرمز ومعلومات تنقيح الأخطاء من الملف الثنائي الأصلي إظهار كيفية الوصول إلى نقطة التنفيذ الحالية. من المخرجات الأخرى المتجه المساعد الحالي Auxiliary Vector أو AUXV اختصارًا. تصف 386_TLS مدخلات جدول الواصفات العام المستخدمة في تقديم Implementation معمارية x86 لمخزن محلي قائم على الخيوط thread-local storage. سيكون هناك مدخلات مكررة لكل خيط قيد التشغيل بالنسبة للتطبيق متعدد الخيوط، حيث سيفهم منقح الأخطاء ذلك وهذه هي الطريقة التي يطبّق بها منقح الأخطاء gdb الأمر thread لإظهار الخيوط والتبديل بينها. تنشئ النواة ملف التفريغ الأساسي ضمن حدود إعدادات ulimit الحالية، إذ يمكن أن يؤدي البرنامج الذي يستخدم قدرًا كبيرًا من الذاكرة إلى وجود ملف تفريغ كبير جدًا، ويُحتمَل أن يملأ القرص الصلب ويزيد المشاكل سوءًا، ويُضبَط ulimit على مستوى منخفض أو حتى على القيمة الصفر لأن معظم الأشخاص الذين ليسوا مطورين لديهم استخدام ضئيل لملف التفريغ الأساسي. لكن يظل التفريغ الأساسي هو الطريقة الأكثر فائدة لتنقيح أخطاء حالة غير متوقعة بعد التعطل. إنشاء أقسام مخصصة يُعَد تنظيم الشيفرة والبيانات والرموز شيئًا يمكن للمبرمج ترك إعدادات سلسلة أدواته الافتراضية كما هي، ولكن يمكن في بعض الأحيان توسيع أو تخصيص الأقسام ومحتوياتها مثل وحدات نواة لينكس التي تُستخدَم لتحميل المشغّلات والميزات الأخرى ديناميكيًا في نواة التشغيل. تُعَد هذه الوحدات غير قابلة للنقل، فهي تعمل فقط مع إصدار بناء نواة ثابت واحد، لذلك يمكن أن تكون الواجهة بين الوحدات والنواة مرنةً وغير مرتبطة بمعايير معينة، وبالتالي يمكن تعريف طرق تخزين مثل تخزين معلومات الترخيص والتأليف والاعتماديات والمعاملات الخاصة بالوحدات بصورة فريدة وكاملة باستخدام النواة. يمكن لأداة modinfo فحص هذه المعلومات في وحدةٍ ما وتقديمها للمستخدم. يوضح المثال التالي استخدام مثال عن وحدة نواة لينكس fuse التي تسمح لمكتبات مساحة المستخدم بتوفير تقديمات نظام الملفات للنواة: $ cd /lib/modules/$(uname -r) $ sudo modinfo ./kernel/fs/fuse/fuse.ko filename: /lib/modules/3.2.0-4-amd64/./kernel/fs/fuse/fuse.ko alias: devname:fuse alias: char-major-10-229 license: GPL description: Filesystem in Userspace author: Miklos Szeredi <miklos@szeredi.hu> depends: intree: Y vermagic: 3.2.0-4-amd64 SMP mod_unload modversions parm: max_user_bgreq:Global limit for the maximum number of backgrounded requests an unprivileged user can set (uint) parm: max_user_congthresh:Global limit for the maximum congestion threshold an unprivileged user can set (uint) $ objdump -s -j .modinfo ./kernel/fs/fuse/fuse.ko ./kernel/fs/fuse/fuse.ko: file format elf64-x86-64 Contents of section .modinfo: 0000 616c6961 733d6465 766e616d 653a6675 alias=devname:fu 0010 73650061 6c696173 3d636861 722d6d61 se.alias=char-ma 0020 6a6f722d 31302d32 32390070 61726d3d jor-10-229.parm= 0030 6d61785f 75736572 5f636f6e 67746872 max_user_congthr 0040 6573683a 476c6f62 616c206c 696d6974 esh:Global limit 0050 20666f72 20746865 206d6178 696d756d for the maximum 0060 20636f6e 67657374 696f6e20 74687265 congestion thre 0070 73686f6c 6420616e 20756e70 72697669 shold an unprivi 0080 6c656765 64207573 65722063 616e2073 leged user can s 0090 65740070 61726d74 7970653d 6d61785f et.parmtype=max_ 00a0 75736572 5f636f6e 67746872 6573683a user_congthresh: 00b0 75696e74 00706172 6d3d6d61 785f7573 uint.parm=max_us 00c0 65725f62 67726571 3a476c6f 62616c20 er_bgreq:Global 00d0 6c696d69 7420666f 72207468 65206d61 limit for the ma 00e0 78696d75 6d206e75 6d626572 206f6620 ximum number of 00f0 6261636b 67726f75 6e646564 20726571 backgrounded req 0100 75657374 7320616e 20756e70 72697669 uests an unprivi 0110 6c656765 64207573 65722063 616e2073 leged user can s 0120 65740070 61726d74 7970653d 6d61785f et.parmtype=max_ 0130 75736572 5f626772 65713a75 696e7400 user_bgreq:uint. 0140 6c696365 6e73653d 47504c00 64657363 license=GPL.desc 0150 72697074 696f6e3d 46696c65 73797374 ription=Filesyst 0160 656d2069 6e205573 65727370 61636500 em in Userspace. 0170 61757468 6f723d4d 696b6c6f 7320537a author=Miklos Sz 0180 65726564 69203c6d 696b6c6f 7340737a eredi <miklos@sz 0190 65726564 692e6875 3e000000 00000000 eredi.hu>....... 01a0 64657065 6e64733d 00696e74 7265653d depends=.intree= 01b0 59007665 726d6167 69633d33 2e322e30 Y.vermagic=3.2.0 01c0 2d342d61 6d643634 20534d50 206d6f64 -4-amd64 SMP mod 01d0 5f756e6c 6f616420 6d6f6476 65727369 _unload modversi 01e0 6f6e7320 00 ons . تحلّل الأداة modinfo القسم ‎.modinfo المُضمَّن في ملف الوحدة لتقديم تفاصيلها. يوضح المثال التالي كيفية وضع حقل "المؤلف Author" في الوحدة، حيث تأتي هذه الشيفرة البرمجية غالبًا من include/linux/module.h: /* * ابدأ من الأسفل ثم انتقل إلى الأعلى */ ‫/* وحدات الماكرو غير المباشرة مطلوبة للصق الوسيط المُوسَّع مثل الماكرو‫ __LINE__ */ #define ___PASTE(a,b) a##b #define __PASTE(a,b) ___PASTE(a,b) #define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__) /* تحويل الماكرو غير المباشر إلى سلسلة نصية. يسمح إنشاء مستويين للمعامل بأن يكون ماكرو بحد ذاته، حيث يتحوّل‫ __stringify(FOO)‎ مثلًا إلى السلسلة "bar" عند التصريف باستخدام ‎-DFOO=bar. */ #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) #define __MODULE_INFO(tag, name, info) \ static const char __UNIQUE_ID(name)[] \ __used __attribute__((section(".modinfo"), unused, aligned(1))) \ = __stringify(tag) "=" info ‫/* معلومات عامة للصيغة‫ tag = "info"‎ */ #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) /* * ‫استخدم للمؤلفين المتعددين الذين يستخدمون "Name <email>‎" أو "Name" فقط ‫تعليمات أو سطور MODULE_AUTHOR()‎ متعددة * */ #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) /* ---- */ MODULE_AUTHOR("Your Name <your@name.com>"); لنبدأ من الجزء السفلي حيث نرى أن الوحدة MODULE_AUTHOR تغلّف الماكرو الأعم ‎__MODULE_INFO، ويمكننا أن نرى أننا نبني متغيرًا من النوع static const char []‎ ليحتوي على السلسلة النصية "author=Your Name <your@name.com>‎". لاحظ أن المتغير لديه معامل إضافي ‎__attribute__((section(".modinfo")))‎ يخبر المصرّف بعدم وضع هذا المتغير في قسم البيانات data مع المتغيرات الأخرى، ولكن يمكن إخفاؤه في قسم ELF الخاص به الذي اسمه ‎.modinfo. توقِف المعاملات الأخرى المتغير الذي يجري تحسينه لأنه يبدو غير مُستخدَم وللتأكد من أننا نضع المتغيرات بجانب بعضها بعضًا من خلال تحديد المحاذاة. هناك استخدام واسع النطاق لتحويل وحدات الماكرو إلى سلاسل نصية Stringification Macros، وهي حيل تُستخدَم في معالج لغة C المسبق لضمان أن السلاسل النصية والتعاريف يمكن أن تكون مع بعضها البعض. يوفّر المصرِّف gcc التعريف الخاص __COUNTER__ الذي يوفر قيمة فريدة ومتزايدة في كل استدعاء، مما يسمح باستدعاءات وحدة MODULE_AUTHOR متعددة في ملف واحد دون استخدام اسم المتغير نفسه. يمكننا فحص الرموز الموضوعة في الوحدة النهائية لمعرفة النتيجة النهائية كما يلي: $ objdump --syms ./fuse.ko | grep modinfo 0000000000000000 l d .modinfo 0000000000000000 .modinfo 0000000000000000 l O .modinfo 0000000000000013 __UNIQUE_ID_alias1 0000000000000013 l O .modinfo 0000000000000018 __UNIQUE_ID_alias0 000000000000002b l O .modinfo 0000000000000011 __UNIQUE_ID_alias8 000000000000003c l O .modinfo 000000000000000e __UNIQUE_ID_alias7 000000000000004a l O .modinfo 0000000000000068 __UNIQUE_ID_max_user_congthresh6 00000000000000b2 l O .modinfo 0000000000000022 __UNIQUE_ID_max_user_congthreshtype5 00000000000000d4 l O .modinfo 000000000000006e __UNIQUE_ID_max_user_bgreq4 0000000000000142 l O .modinfo 000000000000001d __UNIQUE_ID_max_user_bgreqtype3 000000000000015f l O .modinfo 000000000000000c __UNIQUE_ID_license2 000000000000016b l O .modinfo 0000000000000024 __UNIQUE_ID_description1 000000000000018f l O .modinfo 000000000000002a __UNIQUE_ID_author0 00000000000001b9 l O .modinfo 0000000000000011 __UNIQUE_ID_alias0 00000000000001d0 l O .modinfo 0000000000000009 __module_depends 00000000000001d9 l O .modinfo 0000000000000009 __UNIQUE_ID_intree1 00000000000001e2 l O .modinfo 000000000000002f __UNIQUE_ID_vermagic0 سكربتات الرابط Linker Scripts تتمثل وظيفة الرابط في بناء الأقسام Sections لتشكيل المقاطع Segments من خلال استخدام سكربت الرابط الذي يصِف مكان بدء المقاطع والأقسام الموجودة فيها ويحدد المعاملات الأخرى. يوضح المثال الآتي مقتطفًا من سكربت الرابط الافتراضي الذي سيعرضه الرابط عند إعطاء الراية التفصيلية Verbose باستخدام الرايتين ‎-Wl و‎--verbose مع gcc. السكربت الافتراضي مُضمَّنٌ في الرابط ويعتمد على تعريفات واجهة API المعيارية لإنشاء برامج عاملة لمساحة مستخدمٍ خاصة بمنصة البناء. $ gcc -Wl,--verbose -o test test.c GNU ld (GNU Binutils for Debian) 2.26 ... using internal linker script: ================================================== OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") OUTPUT_ARCH(i386:x86-64) ENTRY(_start) SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); ... SECTIONS { ‫/* أقسام للقراءة فقط مُدمَجة في مقطع النص‫ text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; .interp : { *(.interp) } .note.gnu.build-id : { *(.note.gnu.build-id) } .hash : { *(.hash) } .gnu.hash : { *(.gnu.hash) } .dynsym : { *(.dynsym) } .dynstr : { *(.dynstr) } .gnu.version : { *(.gnu.version) } .gnu.version_d : { *(.gnu.version_d) } .gnu.version_r : { *(.gnu.version_r) } .rela.dyn : { ... } PROVIDE (etext = .); .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } .rodata1 : { *(.rodata1) } ... يمكنك أن ترى في المثال السابق كيف يحدد سكربت الرابط أمورًا متعددة مثل مواقع البدء والأقسام المراد تجميعها في مقاطع مختلفة. تُستخدَم الراية ‎-Wl لتمرير الراية ‎--verbose إلى الرابط عبر gcc، إذ يمكن توفير سكربتات مخصصة للرابط باستخدام الرايات. ليس مُحتمًلًا أن يحتاج مطورو مساحة المستخدم العادية إلى تجاوز سكربت الرابط الافتراضي، ولكن تتطلب التطبيقات المخصَّصة جدًا مثل عمليات بناء النواة سكربتات مخصصة للرابط في أغلب الأحيان. ترجمة -وبتصرُّف- للقسمين ELF Executables و Extending ELF concepts من فصل Behind the process من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand. اقرأ أيضًا المقال السابق: الملفات القابلة للتنفيذ في نظام التشغيل وتمثيلها باستخدام الصيغة ELF تطبيق عملي لبناء برنامج تنفيذي من شيفرة مصدرية بلغة C
×
×
  • أضف...