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

محمد أحمد العيل

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

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

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

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

    27

كل منشورات العضو محمد أحمد العيل

  1. تقدّم تقنية KVM (اختصار Kernel-based Virtual Machine؛ الأجهزة الافتراضيّة المعتمدة على نواة لينكس) آلية لتشغيل الحوسبة الافتراضيّة (التخيّليّة أو الوهميّة) Virtualization على الأنظمة العاملة بلينكس. تمكّن هذه الآلية - التي تتوفّر على هيئة وحدة Module في نواة لينكس منذ الإصدار 2.6.20 - من استخدام أنظمة تشغيل مستقلّة ومنفصلة في وقت واحد على نفس العتاد Hardware. يتولّى نظامٌ يُعرَف بالمضيف Host تشغيل العتاد Hardware عن طريق برنامج يُعرَف بالمراقِب Hypervisor. يوفّر المراقب موارد (معالج، ذاكرة عشوائية، أجهزة طرفية وغيرها) للآلات الافتراضيّة. يُسمّى النظام الذي يُشغّل الآلة الافتراضيّة بالنظام الضيف Guest. يقدّم KVM الميزات التاليّة: التعهّد الزائد Over-commiting: يعني تخصيص موارد افتراضيّة (المعالجات والذاكرة العشوائية) أكثر من تلك التي يوفّرها العتاد. التموين السريع Thin provisionning: يسمح بالتوزيع المرن للمساحة التخزينية المتوفّرة والبحث عن أمثل طريقة لمشاركتها بين الآلات الافتراضية. تحجيم الإدخال/الإخراج من القرص Disk I/O throttling: يوفّر إمكانية تعريف حد لطلبات الإدخال والإخراج بالنسبة لكل آلة افتراضيّة. إمكانيّة زيادة قدرات المعالجة الخاصّة بالآلات الافتراضية حسب الطّلب دون الحاجة لإيقافها عن العمل. يتطرّق هذا المقال، الأوّل في سلسلة من أربعة أجزاء، لكيفية استخدام KVM والأدوات التي يوفّرها على توزيعة Ubuntu 16.04. تشترك توزيعات لينكس عموما في الخطوط العريضة، وكثير من الخطوات الواردة في هذه السلسلة. المتطلّبات يجب أن تكون لدى العتاد الذي يعمل عليه النظام المُضيف القدرةُ على تنفيذ طلبات المُراقب عن طريق ما يُعرَف بامتدادات العتاد الخاصّة بالحوسبة الافتراضية Hardware virtualization extensions؛ وهو ما توفّره أغلب الحواسيب الحديثة. يمكّن الأمر kvm-ok من التأكد من جاهزيّة جهازك لاستخدام KVM: $ kvm-ok INFO: /dev/kvm exists KVM acceleration can be used يظهر من نتيجة الأمر أعلاه أن الجهاز قادر على استخدام التقنيّة. تأكّد، إن كانت النتيجة مخالفة، من أن امتدادات العتاد موجودة ومفعّلة في محمّل الإقلاع BIOS (أو UEFI) الخاص بجهازك. تثبيت أدوات KVM ونشر الآلات الافتراضية سنحتاج لبضعة حزم قبل أن يمكننا البدء في إنشاء آلات افتراضية باستخدام KVM. يُثبّت الأمر التالي هذه الحزم: $ sudo apt install qemu-kvm libvirt-bin bridge-utils virt-manager تعمل حزمة أدوات kvm-qemu على تشغيل وحدة KVM في النواة لتنفيذ الشفرة البرمجية الخاصّة بالآلة الافتراضية. كما أن هذه الأدوات تحجز الذاكرة العشوائية والموارد التي تحتاجها الآلات الافتراضيّة للعمل؛ زيادة على كونها تعمل على محاكاة عمل الأجهزة الطرفية Peripherals الملموسة التي يتوفّر عليها النظام المُضيف من أجل إتاحة أجهزة طرفية افتراضية للنظام الضيف. تتيح الحزمة libvirt-bin مجموعة من الأدوات للتخاطب مع قدرات الحوسبة الافتراضية التي يتوفّر عليها الجهاز المضيف. نذكر مثلا الأداة virsh التي يمكن من خلالها إنشاء آلات افتراضية، تعليقها (توقيف مؤقّت) أو إيقافها عن العمل. تحوي الحزمة bridge-utils الأدوات الضرورية لإنشاء أجهزة طرفية تعمل جسورا Bridge بين واجهات شبكة عدّة، ولإدارة هذه الأجهزة. من المهمّ توفّر هذه الميزة حتى يمكننا ضبط شبكة حواسيب تشمل الآلات الافتراضية. توفّر حزمة virt-manage واجهة رسومية لإدارة الآلات الافتراضية. الخطوة التاليّة هي إضافة المستخدم الحالي إلى مجموعة المستخدمين libvirtd حتى يمكنه استخدام الآلات الافتراضيّة التي ننشئها عن طريق KVM؛ إذ لا تُتاح هذه الوظيفة لغير أعضاء هذه المجموعة والحساب الجذر: $ sudo adduser zeine77 libvirtd نطلُب، من أجل التأكد من أن كل شيء على ما يُرام، سردَ الآلات الافتراضية المتوفّرة؛ وذلك باستخدام الأمر virsh على النحو التالي: $ virsh -c qemu:///system list Id Name State ---------------------------------------------------- لا توجد آلات افتراضية لحد الساعة، لذا لن تظهر معلومات عن هذه الآلات؛ وسيكتفي الأمر بعرض أسماء أعمدة (المعرّف Id، الاسم Name والحالة State) تصف معلومات تتعلّق بالآلات الافتراضية. تشير هذه النتيجة إلى أننا مستعدون الآن للبدء في استخدام KVM. إنشاء آلات افتراضية باستخدام KVM سنستخدم في هذا الجزء من المقال الأداة virt-manager للتخاطب مع KVM من أجل إنشاء آلات افتراضية. يمكن تشغيل virt-manager من قوائم سطح المكتب الذي تستخدمه (Gnome، KDE أو غيرهما) أو بتنفيذ الأمر virt-manager في سطر الأوامر. تظهر النافذة التاليّة بعد تشغيل الأداة. يظهر في واجهة البرنامج أنه متّصل بمضيف محلي يستخدم المُراقب QEMU/KVM. يمكن من هذه الواجهة الاتّصال بمضيفات Hosts بعيدة (توجد على أجهزة مغايرة للجهاز الذي تعمل عليه الأداة virt-manager)، وذلك عن طريق القائمة …File -> Add Connection ثم تحديد الخانة Connect to remote host وذكر البيانات المطلوبة. ننقر على زر Create a new virtual machine في واجهة virt-manager لإنشاء آلة افتراضية جديدة. تظهر نافذة جديدة تطلُب تحديد طريقة لتثبيت نظام التشغيل الضّيف على الآلة الافتراضية. توجد أكثر من طريقة كما يظهر في لقطة الشاشة أعلاه. سنحدّد خيار Local install media (وسيط تثبيت محليّ). تظهر النافذة التاليّة. يمكن عبر الواجهة الظاهرة في الصورة أعلاه الاختيار بين قرص مُدمَج (CD أو DVD) أو ملفّ ISO. سنحدّد الخيار الثاني (ملف ISO) وننقر على الزّر Browse. تظهر النافذة التالية. نستطيع عبر هذه النافذة إنشاء مساحة تخزين Storage volume افتراضية لاستخدامها مع الآلة؛ إلا أننا لن نفعل ذلك الآن وسننقر على الزّر Browse Local لتحديد المسار Path الذي يوجد عليه ملفّ ISO. يعود البرنامج بعد تحديد المسار إلى واجهة الاختيار الخاصة بوسيط التثبيت. نلاحظ أن البرنامج تعرّف تلقائيا على نوعية نظام التشغيل وإصداره اعتمادا على اسم وسيط التثبيت. ثم يطلب منا البرنامج ضبط إعدادات المعالج والذاكرة العشوائية الخاصّين بالآلة الافتراضيّة. نختار القيم المناسبة اعتمادا على العتاد المتوفّر على المُضيف ثم ننتقل لإعدادات التخزين. يمكن تحديد أحد خيارين؛ إما إنشاء مساحة تخزين افتراضية جديدة أو اختيار مساحة تخزين افتراضية موجودة سلفا. بما أننا لم نضبُط مساحة تخزين قبلًا فسنحدّد الخيار الأول. ثم ننتقل للخطوة الأخيرة وهي تحديد اسم للآلة الافتراضية. إن أردت تخصيص عمليّة التثبيت فبإمكانك تحديد الخيار Customize configuration before install (تخصيص الإعدادات قبل التثبيت). ثم ننقر على زر Finish. تبدأ الآلة الافتراضية بالعمل مباشرة وتظهر في واجهة برنامج virt-manager شاشةُ التثبيت الخاصّة بنظام التشغيل الضيف على الآلة الافتراضية. توجد في واجهة virt-manager أزرار للتحكّم في الآلة الافتراضية: إعادة التشغيل Reboot، الإيقاف Shut Down، فرض إعادة التعيين Force reset، فرض الإيقاف Force off (يشبه فصل حاسوب عن الطاقة دون إيقاف نظام التشغيل) وحفظ حالة الآلة Save. كما يمكن عن طريق زرّ Open فتح نافذة خاصّة للآلة الافتراضية يمكن عبرها أخذ لقطات سريعة Snapshots. تخزّن اللقطات السريعة جميع البيانات الموجودة على الآلة ويمكن الرجوع إلى هذه اللقطات في حالات مثل حدوث مشكل مع الآلة الافتراضية، توقفها عن العمل أو لإنشاء آلة افتراضية مطابقة لها تماما. ترجمة - بتصرّف - لمقال How to Create Virtual Machines in Linux Using KVM (Kernel-based Virtual Machine) – Part 1 لصاحبه Mohammad Dosoukey.
  2. استخدم اسم المشروع الذي خصّصه لك Heroku بعد تنفيذ الأمر create (راجع مخرجات الأمر heroku create)؛ nameless-chamber-90421 هو اسم المشروع الذي أعطاه Heroku لي عند كتابة الدرس وليست لديك صلاحيات الوصول إليه.
  3. تستخدم بعض توزيعات لينكس، خصوصا تلك المعتمدة على Fedora مثل CentOS وRed Hat Enterprise Linux، حزم RPM لتثبيت البرامج وإدارتها. ملف rpm. هو عبارة عن تجميع لبرامج ومكتبات تحتاجها هذه البرامج في حزمة تُستخدَم أداة باسم rpm لتثبيتها. الأداة rpm هي أداة مفتوحة المصدر تحتفظ ببيانات الحزم المثبّتة على النظام في المجلّد var/lib/rpm/. من المهم الانتباه إلى أن أداة rpm لا تستطيع التعامل مع البرامج التي ثُبِّتت انطلاقا من المصدر Source. يحتوي ملفّ rpm. على معلومات من قبيل ماهية الحزمة، من أين تأتي، الاعتماديات التي تحتاجها للعمل، الإصدار… إلخ. توجد خمسة أوضاع أساسية لأداة rpm: التثبيت i-: يُستخدَم لتثبيت حزم RPM. الحذف e-: يُستخدَم لحذف حزمة وإلغاء تثبيتها. الترقية U-: يُحدّث حزمة مثبّتة (تثبيت إصدار جديد من الحزمة). التحقق V-: يُستخدَم للتحقّق من حزمة RPM. الاستعلام q-: يُستخدَم للاستعلام عن حزم RPM. حزم RPM هي حزم قائمة بذاتها؛ تمكن الاستفادة من المواقع التالية للحصول على حزم RPM الخاصّة ببرنامج تريده: redhat.com rpmfind.net. rpm.pbone.net rpmseek.com يقدّم هذا المقال أمثلة لأوامر RPM من أجل المساعدة في تثبيت البرامج، تحديثها أو حذفها على توزيعة تستخدم RPM للتحزيم. ملحوظة: يجب أن تكون لديك صلاحيات الجذر حتى تستطيع إدارة الحزم. التحقق من توقيع حزمة يجب أولا التحقّق من مصدر الحزمة وموثوقيتها قبل تثبيتها. يُستخدم الخيار checksig-- مع الأمر rpm لهذا الغرض. لكي نستطيع التحقق من حزمة فإنه يجب أولا استيراد المفاتيح العمومية Public keys الخاصّة بالنظام الذي نعمل عليه (Fedora، CentOS، RHEL أو غيرها). بالنسبة لتوزيعة Red Hat Enterprise Linux فالأمر على النحو التالي: rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-* نطلب في الأمر التالي التحقق من الحزمة pidgin-1.5.1-6.el3.x86_64.rpm: [root@academy1 ~]# rpm --checksig pidgin-1.5.1-6.el3.x86_64.rpm pidgin-1.5.1-6.el3.x86_64.rpm: (sha1) dsa sha1 md5 gpg OK لاحظ أن نتيجة التحقق هي OK. تثبيت حزمة يطلُب الأمر التالي تثبيت الحزمة pidgin-1.5.1-6.el3.x86_64.rpm التي تحققنا من موثوقيتها أعلاه: [root@academy1 ~]# rpm -ivh pidgin-1.5.1-6.el3.x86_64.rpm Preparing... ########################################### [100%] 1:pidgin ########################################### [100%] تظهر في الأمر أعلاه الخيارات التالية: i-: وضع التثبيت، v-: طلب إظهار رسائل بتقدم عملية التثبيت، h-: طلب إظهار علامات # للدلالة على التقدم الحاصل في تنفيذ خطوات التثبيت. الخياران الأخيران v- وh- ليسا ضروريين. التحقق من اعتماديات حزمة قبل تثبيتها تتيح أداة rpm التحقق من اعتماديات حزمة قبل تثبيتها، مثلا للتحقق من اعتماديات الحزمة pidgin-2.11.0-1.fc24.x86_64.rpm: [root@academy1 ~]# rpm -qpR pidgin-1.5.1-6.el3.x86_64.rpm /bin/sh /bin/sh glib2 >= 2.2 htmlview libICE.so.6()(64bit) libSM.so.6()(64bit) libX11.so.6()(64bit) libXext.so.6()(64bit) libao.so.2()(64bit) libatk-1.0.so.0()(64bit) libaudiofile.so.0()(64bit) (...) يشير الخيار q- إلى أننا في وضع الاستعلام عن الحزمة. يطلب الخيار p- معلومات عن حزمة غير مثبّتة ويسرُد الخيار R- اعتماديّات الحزمة. تثبيت حزمة بغض النظر عن اعتمادياته لا يقبل rpm في الحالة العادية تثبيت حزمة إلا إذا كانت جميع اعتمادياتها مثبتة؛ إلا أنه يمكن إجباره على ذلك باستخدام الخيار nodeps--: [root@academy1 ~]# rpm -ivh --nodeps pidgin-1.5.1-6.el3.x86_64.rpm Preparing... ################################# [100%] Updating / installing... 1:pidgin-1.5.1-6.el3 ################################# [100%] يُثبّت الأمر أعلاه الحزمة بتجاهل الأخطاء المتعلقة باعتماديّاتها؛ إلا أن البرنامج لن يعمل إلا بعد تثبيت الاعتماديات المطلوبة. التحقق من أن حزمة مثبّتة استخدم وضع الاستعلام q- للتأكد من أن حزمة مّا مثبتة على النظام: [root@academy1 ~]# rpm -q BitTorrent package BitTorrent is not installed [root@academy1 ~]# rpm -q pidgin pidgin-1.5.1-6.el3.x86_64.rpm سرد قائمة بجميع ملفات حزمة مثبتة أضف خيار السرد l- إلى وضع الاستعلام q- للحصول على قائمة بملفات حزمة مثبتة على النظام: [root@academy1 ~]# rpm -ql pidgin /etc/gconf/schemas/purple.schemas /usr/bin/gaim /usr/bin/pidgin /usr/lib64/pidgin /usr/lib64/pidgin/convcolors.so /usr/lib64/pidgin/extplacement.so /usr/lib64/pidgin/gestures.so /usr/lib64/pidgin/gtkbuddynote.so (...) سرد الحزم المُثبتة يسرُد الخيار a- عند استخدامه في وضع الاستعلام قائمة بأسماء جميع الحزم المثبتة: [root@academy1 ~]# rpm -qa libsss_nss_idmap-1.13.0-40.el7_2.9.x86_64 pygobject3-base-3.14.0-3.el7.x86_64 libfprint-0.5.0-3.el7.x86_64 man-pages-3.53-5.el7.noarch redhat-release-server-7.2-9.el7.x86_64 rsyslog-7.4.7-12.el7.x86_64 libsane-hpaio-3.13.7-6.el7_2.1.x86_64 (...) يمكن ترتيب القائمة لتظهر الحزم المثبتة مؤخرا في الأعلى باستخدام الخيار last--: [root@academy1 ~]# rpm -qa --last pidgin-1.5.1-6.el3.x86_64.rpm Sun 04 Sep 2016 09:47:32 PM GMT glibc-devel-2.17-106.el7_2.8.x86_64 Tue 02 Aug 2016 11:42:29 PM GMT nss-pam-ldapd-0.8.13-8.el7.x86_64 Tue 02 Aug 2016 11:42:23 PM GMT nscd-2.17-106.el7_2.8.x86_64 Tue 02 Aug 2016 11:42:17 PM GMT glibc-headers-2.17-106.el7_2.8.x86_64 Tue 02 Aug 2016 11:42:14 PM GMT glibc-common-2.17-106.el7_2.8.x86_64 Tue 02 Aug 2016 11:42:01 PM GMT (...) تحديث حزمة يُستخدَم وضع الترقية U- لتحديث إصدار حزمة مثبتة. يحتفظ أمر rpm عند تحديث حزمة بنسخة احتياطية من الإصدار المثبت للعودة إليها في حال لم تعمل الحزمة حسب المطلوب بعد تحديثها: [root@academy1 ~]# rpm -Uvh pidgin-1.5.1-6.el3.x86_64.rpm Preparing... ########################################### [100%] 1:pidgin ########################################### [100%] حذف حزمة استخدم الخيار e- مع الأمر rpm لحذف حزمة بذكر اسمها. مثلا؛ لحذف حزمة pidgin السابقة: [root@academy1 ~]# rpm -e pidgin تمكن أيضا إضافة الخيار v إلى الأمر لعرض رسائل بما يحدُث: [root@academy1 ~]# rpm -ev pidgin Preparing packages... pidgin-1.5.1-6.el3.x86_64.rpm كما يمكن الإبقاء على اعتماديات الحزمة باستخدام الخيار nodeps-- مع أمر الحذف وهو ما ينتج عنه حذف الحزمة فقط دون اعتمادياتها: [root@academy1 ~]# rpm -ev --nodeps pidgin انتبه إلى أن حذف حزمة من النظام يمكن أن يؤدي إلى عدم استقراره؛ لذا تأكد من حاجتك فعلا لحذف الحزمة. معرفة الحزمة التي ينتمي إليها ملف استخدم الخيار f- في وضع الاستعلام لمعرفة الحزمة التي ينتمي إليها ملف. مثلا؛ تخبرنا نتيجة الأمر التالي أن الملف usr/bin/htpasswd/ ينتمي للحزمة httpd-tools-2.4.6-40.el7_2.4.x86_64. [root@academy1 ~]# rpm -qf /usr/bin/htpasswd httpd-tools-2.4.6-40.el7_2.4.x86_64 الحصول على معلومات عن حزمة يعرض الخيار i- عند استخدامه في وضع الاستعلام معلومات عن حزمة مثبتة؛ مثلا بالنسبة لحزمة vsftpd: [root@academy1 ~]# rpm -qi vsftpd Name : vsftpd Version : 3.0.2 Release : 11.el7_2 Architecture: x86_64 Install Date: Thu 21 Jul 2016 03:29:37 PM GMT Group : System Environment/Daemons Size : 355788 License : GPLv2 with exceptions Signature : RSA/SHA256, Fri 18 Mar 2016 01:08:35 AM GMT, Key ID 199e2f91fd431d51 Source RPM : vsftpd-3.0.2-11.el7_2.src.rpm Build Date : Wed 24 Feb 2016 11:06:41 AM GMT Build Host : x86-019.build.eng.bos.redhat.com Relocations : (not relocatable) Packager : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla> Vendor : Red Hat, Inc. URL : https://security.appspot.com/vsftpd.html Summary : Very Secure Ftp Daemon Description : vsftpd is a Very Secure FTP daemon. It was written completely from scratch. أما إن أردت الحصول على معلومات عن حزمة قبل تثبيتها فالأمر هو التالي: [root@academy1 ~]# rpm -qip pidgin-1.5.1-6.el3.x86_64.rpm Name : pidgin Version : 1.5.1 Release : 6.el3 Architecture: x86_64 Install Date: (not installed) Group : Applications/Internet Size : 11168813 License : GPL Signature : DSA/SHA1, Thu 29 Oct 2009 08:14:44 AM GMT, Key ID 219180cddb42a60e Source RPM : pidgin-1.5.1-6.el3.src.rpm Build Date : Wed 28 Oct 2009 03:52:51 PM GMT Build Host : x86-003.build.bos.redhat.com Relocations : (not relocatable) Packager : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla> Vendor : Red Hat, Inc. URL : http://www.pidgin/im/ Summary : A Gtk+ based multiprotocol instant messaging client Description : Pidgin allows you to talk to anyone using a variety of messaging protocols, including AIM, ICQ, IRC, Yahoo!, Novell Groupwise, MSN Messenger, Jabber, Gadu-Gadu, Napster, and Zephyr. These protocols are implemented using a modular, easy to use design. To use a protocol, just add an account using the account editor. Pidgin supports many common features of other clients, as well as many unique features, such as perl scripting, TCL scripting and C plugins. Pidgin is NOT affiliated with or endorsed by America Online, Inc., Microsoft Corporation, Yahoo! Inc., or ICQ Inc. [root@academy1 ~]# الاستعلام عن التوثيق Documentation الخاص بحزمة مثبتة يمكن الحصول على مستندات التوثيق الخاصّة بحزمة باستخدام الخيارين df- في وضع الاستعلام. يأخذ الأمر معطى عبارة عن مسار الحزمة؛ وهو ما يمكن الحصول عليه عن طريق الأمر which. نطلب في المثال التالي معرفة مسار الحزمة vsftpd ثم نستعلم عن مستدات التوثيق الخاصّة بها: [root@academy1 ~]# which vsftpd /sbin/vsftpd [root@academy1 ~]# rpm -qdf /sbin/vsftpd /usr/share/doc/vsftpd-3.0.2/AUDIT /usr/share/doc/vsftpd-3.0.2/BENCHMARKS /usr/share/doc/vsftpd-3.0.2/BUGS /usr/share/doc/vsftpd-3.0.2/COPYING /usr/share/doc/vsftpd-3.0.2/Changelog (...) التحقق من حزمة تقارن أداة rpm عند طلب التحقق من حزمة المعلومات المستقاة من الملفات المثبتة مع تلك الموجودة في قاعدة بيانات rpm. يُستخدم الخيار V- لوضع التحقق وp لتمرير حزمة إلى الأمر: [root@academy1 ~]# rpm -Vp vsftpd-3.0.3-2.fc24.x86_64.rpm warning: vsftpd-3.0.3-2.fc24.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 81b46521: NOKEY .......T. c /etc/logrotate.d/vsftpd .......T. c /etc/pam.d/vsftpd .......T. c /etc/vsftpd/ftpusers .......T. c /etc/vsftpd/user_list .......T. c /etc/vsftpd/vsftpd.conf .......T. /etc/vsftpd/vsftpd_conf_migrate.sh .......T. /usr/lib/systemd/system-generators/vsftpd-generator .......T. /usr/lib/systemd/system/vsftpd.service .......T. /usr/lib/systemd/system/vsftpd.target .......T. /usr/lib/systemd/system/vsftpd@.service S.5....T. /usr/sbin/vsftpd missing /usr/share/doc/vsftpd missing d /usr/share/doc/vsftpd/AUDIT missing d /usr/share/doc/vsftpd/BENCHMARKS missing d /usr/share/doc/vsftpd/BUGS (...) يمكن طلب التحقق من جميع الحزم في النظام بالخيار a في وضع التحقق على النحو التالي: [root@academy1 ~]# rpm -Va S.5....T. c /etc/os-release S.5....T. /usr/src/kernels/3.10.0-327.22.2.el7.x86_64/include/config/auto.conf S.5....T. /usr/src/kernels/3.10.0-327.22.2.el7.x86_64/include/config/auto.conf.cmd S.5....T. /usr/src/kernels/3.10.0-327.22.2.el7.x86_64/include/config/tristate.conf S.5....T. /usr/src/kernels/3.10.0-327.22.2.el7.x86_64/include/generated/autoconf.h (...) سرد جميع المفاتيح العمومية المستوردة يمكنك إن أردت سرد جميع المفاتيح العمومية للحزم المثبتة على نظامك، وذلك على النحو التالي: [root@academy1 ~]# rpm -qa gpg-pubkey* gpg-pubkey-2fa658e0-45700c69 gpg-pubkey-37017186-45761324 gpg-pubkey-897da07a-3c979a7f gpg-pubkey-f21541eb-4a5233e7 gpg-pubkey-42193e6b-4624eff2 gpg-pubkey-fd431d51-4ae0493b gpg-pubkey-db42a60e-37ea5438 تصحيح قاعدة بيانات RPM معطوبة يحدُث أن تُصاب قاعدة البيانات الخاصّة بحزم RPM بعُطب مما ينتُج عنه غياب وظائف من أداة rpm. يمكنك في هذه الحالة محاولة بناء قاعدة البيانات من جديد بالأوامر التالية: [root@academy1 ~]# cd /var/lib/rpm [root@academy1 rpm]# rm __db* [root@academy1 rpm]# rpm --rebuilddb [root@academy1 rpm]# /usr/lib/rpm/rpmdb_verify Packages BDB5105 Verification of Packages succeeded. ترجمة - وبتصرف - لمقال 20 Practical Examples of RPM Commands in Linux لصاحبه Ravi Saive.
  4. يتغيّر إطار العمل Laravel باستمرار بإدراج تحديثات جديدة على كلّ إصدار من أجل تحسين تجربة التطوير وإضافة ميزات جديدة تسهّل عمل المطوّر. سنتناول في هذا الدرس، الأول من سلسلة مكونة من ستة أجزاء، التغييرات الحاصلة في هيكلة مشاريع في الإصدار 5.3 من إطار العمل Laravel. سنستخدم لأغراض الشرح مشروعي Laravel؛ الأول يعمل بالإصدار 5.2 والثاني بالإصدار 5.3 (الأخير أثناء كتابة هذا المقال). ننشئ المشروعين بمدير الاعتمادات composer على النحو التالي: بالنسبة للإصدار 5.2: composer create-project --prefer-dist laravel/laravel laravel52 "5.2.*" بالنسبة للإصدار 5.3: composer create-project --prefer-dist laravel/laravel laravel53 "5.3.*" يظهر الإصدار المستهدَف في كل من الأمرين أعلاه (5.2 و5.3 على التوالي). ملف المسارات Routes تعدّ المسارات الخطوة الأولى في معالجة الطلبات التي يتلقاها التطبيق. في الإصدارات السابقة من Laravel (قبل 5.3) كانت المسارات تُضبَط في الملف app/Http/routes.php بغضّ النظر عن طبيعة الطلبات التي تتعامل معها. إن بحثت عن هذا الملف في الإصدار الجديد فلن تجده؛ إذ أنه اختفى مع إنشاء مجلّد جديد باسم routes يوجد في جذر المشروع ويحوي ملفّين: ملف web.php: يحوي هذا الملف المسارات الخاصّة بواجهة الوِب. تعتمد المسارات الموجودة في هذا الملفّ على وسيط برمجي Middleware يحمل نفس الاسم (web) ويوفّر خاصيّات مثل حالة الجلسة Session state (الاحتفاظ ببيانات العميل لاستخدامها في الطلبات الواردة منه أثناء صلاحية الجلسة) والحماية من هجمات تزوير الطلب عبر الموقع Cross-site request forgery, CSRF. ملف api.php: يتوجّه هذا الملف للتطبيقات التي توفّر واجهة تطبيقات برمجية Application programming interface. تعتمد المسارات الموجودة في الملف api.php على وسيط برمجي أخف من السابق (يُسمّى هذا الوسيط api)؛ إذ لا يحتوي على الخاصيّات الموجودة في الوسيط web والتي لا تحتاجها واجهات التطبيقات البرمجية. يمكن القول إن الملف web.php معدّ للتعامل مع الطلبات ذات الحالة Stateful، بينما الملف api.php معدّ للطلبات عديمة الحالة Stateless. من الجيد الانتباه إلى أن جميع المسارات المذكورة في الملف api تُطبَّق عليها لاحقة api. لو نظرنا إلى الملف api.php المبدئي فسنجد أن محتواه على النحو التالي: <?php use Illuminate\Http\Request; Route::get('/user', function (Request $request) { return $request->user(); })->middleware('auth:api'); وبالنسبة للملف web.php: <?php Route::get('/', function () { return view('welcome'); }); إن نفذنا أمر سرد مسارات المشروع فستكون على النحو التالي: لاحظ كيف أن Laravel أضاف تلقائيا اللاحقة api إلى المسار user/ مع تحديد الوسيط البرمجي المستخدم بـ api, auth:api. فلنضف مسارا باسم user/ إلى الملف web.php: Route::get('/user', function () { return "1,2,3,4"; }); ثم نسرُد المسارات المتوفرة: يظهر المسار الجديد في القائمة، ولكن بدون اللاحقة api، ومع تحديد الوسيط بـweb. تغيير الهيكلة المبدئية للمجلدات تخَلّص الإصدار 5.3 من Laravel من مجلدات كانت تأتي مبدئيا مع إطار العمل. يمكن ملاحظة هذا الأمرحيث تظهر، في الصورة أدناه، المجلدات الفرعية من المجلد app لكل من الإصدارين 5.2 (في اليسار) و5.3 (في اليمين). نلاحظ إخفاء المجلدات Listeners، Jobs، Events و Policies. لا يعني الإخفاء المبدئي لهذه الملفات أن الوظائف المرتبطة بها لم تعد موجودة؛ بل يتعلّق الأمر بالتخفيف من المجلدات الموجودة مبدئيا وإنشائها فقط عند الحاجة لذلك. مثلا؛ إن أردنا إنشاء حدث Event باسم ContactHandlerAction فسنفّذ الأمر التالي: php artisan make:event ContactHandlerAction إن نفذت الأمر أعلاه فستجد أن Laravel أنشأ مجلدا فرعيا في app باسم Events وأنشأ داخله الملف ContactHandlerAction.php. نفس الشيء بالنسبة للمستمعات Listeners: php artisan make:listener EmailContactListener --event="ContactHandlerAction" يظهر جليا أن Laravel 5.3 تخفّف من مجلداتٍ مبدئيا؛ إلا أنه ينشئها إن احتاج إليها.
  5. يتكوّن نظام التمهيد systemd من مجموعة من الأدوات التي تمثّل منصة مركزية لإدارة نظام لينكس وإعداده. تُستخدم أداة systemctl للتحكّم في systemd وإدارة الخدمات المرتبطة به. يهدف هذا المقال إلى إلقاء الضوء على كيفية إدارة النظام والخدمات على نظام تشغيل يستخدم systemd للتمهيد. أساسيات systemd وsystemctl التحقق من أن systemd مثبّت وإصداره: $ systemd --version systemd 229 +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ -LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN يظهر من المثال أعلاه أن نظام التمهيد systemd مثبت وأن لدينا الإصدار 229. التحقق من مكان تواجد الملفات التنفيذية والمكتبات البرمجية الخاصّة بـ وsystemctl: $ whereis systemd systemd: /usr/lib/systemd /bin/systemd /etc/systemd /lib/systemd /usr/share/systemd /usr/share/man/man1/systemd.1.gz $ whereis systemctl systemctl: /bin/systemctl /usr/share/man/man1/systemctl.1.gz التحقق من حالة عمل systemd يمكن استخدام الأمر ps لسرد العمليات Processes النشطة ثم عزل تلك المتعلقة بـsystemd، وذلك على النحو التالي: $ ps -eaf | grep [s]ystemd root 1000 1 0 12:23 ? 00:00:00 /lib/systemd/systemd-journald root 1029 1 0 12:23 ? 00:00:00 /lib/systemd/systemd-udevd systemd+ 2639 1 0 12:23 ? 00:00:00 /lib/systemd/systemd-timesyncd message+ 2781 1 0 12:23 ? 00:00:01 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation root 2862 1 0 12:23 ? 00:00:00 /lib/systemd/systemd-logind sddm 3284 1 0 12:23 ? 00:00:00 /lib/systemd/systemd --user academy 3532 1 0 12:25 ? 00:00:00 /lib/systemd/systemd --user تحمل عمليّة systemd المعرِّف 1. استخدمنا الخيار e- مع الأمر ps لسرد جميع العمليات، ثم حدّدنا العمليات التي لا ترأس جلسة Session leaders (العمليات التي يساوي معرّف العمليّة السّلف Parent Id معرّفَ الجلسة Session Id). استخدمنا الخيار f- لتهيئة مخرجات الأمر التي طبّقنا عليه الأمر grep لتحديد تلك التي توافق التعبير النمطي s]ystemd]. تحليل متتالية الإقلاع في systemd تُستخدَم أداة systemd-analyze لتحليل متتالية الإقلاع: $ systemd-analyze Startup finished in 9.314s (firmware) + 2.897s (loader) + 9.290s (kernel) + 46.672s (userspace) = 1min 8.175s نضيف المعطى blame للأمر أعلاه من أجل الحصول على الوقت الذي استغرقته كل خدمة للإقلاع: $ systemd-analyze blame 40.880s systemd-suspend.service 25.010s apt-daily.service 14.173s dev-sda7.device 10.530s dnsmasq.service 10.228s click-system-hooks.service 7.909s accounts-daemon.service 7.908s ModemManager.service 7.120s NetworkManager-wait-online.service 7.084s gpu-manager.service (...) يمكن أيضا تحليل الوقت المستغرق للعمليات الحرجة باستخدام المعطى critical-chain الذي يطبع شجرة بتتالي العمليات التي تنتظر بقية العمليات إقلاعها: $ systemd-analyze critical-chain The time after the unit is active or started is printed after the "@" character. The time the unit takes to start is printed after the "+" character. graphical.target @41.348s └─multi-user.target @41.348s └─apache2.service @34.735s +6.612s └─network-online.target @34.717s └─NetworkManager-wait-online.service @27.596s +7.120s └─NetworkManager.service @21.672s +5.913s └─dbus.service @21.471s └─basic.target @21.470s └─sockets.target @21.470s └─snapd.socket @21.441s +17ms └─sysinit.target @21.391s └─apparmor.service @20.785s +605ms └─local-fs.target @20.772s └─run-user-123.mount @35.991s └─local-fs-pre.target @15.110s └─systemd-remount-fs.service @15.038s +27ms └─systemd-journald.socket @3.530s └─-.slice @3.497s تظهر بعد علامة @ المدة التي فُعلت بعدها الوحدة، في ما يظهر الوقت الذي استغرقته الوحدة للإقلاع بعد علامة +. يمكن تمرير اسم وحدة إلى الأمر أعلاه لقصر السّرد على العمليات التي تنتظر الوحدة إقلاعها: $ systemd-analyze critical-chain apache2.service The time after the unit is active or started is printed after the "@" character. The time the unit takes to start is printed after the "+" character. └─network-online.target @35.533s └─NetworkManager-wait-online.service @29.424s +6.108s └─NetworkManager.service @22.174s +7.234s └─dbus.service @20.998s └─basic.target @20.976s └─sockets.target @20.976s └─snapd.socket @20.919s +46ms └─sysinit.target @20.902s └─apparmor.service @19.691s +1.193s └─local-fs.target @19.681s └─run-user-123.mount @37.402s └─local-fs-pre.target @14.915s └─systemd-remount-fs.service @14.866s +32ms └─system.slice @3.541s └─-.slice @3.508s سرد الوحدات يعمل الأمر systemctl عند استخدام المعطى list-unit-files على سرد قائمة بجميع الوحدات الموجودة في النظام: $ systemctl list-unit-files UNIT FILE STATE proc-sys-fs-binfmt_misc.automount static org.freedesktop.hostname1.busname static org.freedesktop.locale1.busname static org.freedesktop.login1.busname static org.freedesktop.network1.busname static org.freedesktop.resolve1.busname static org.freedesktop.systemd1.busname static org.freedesktop.timedate1.busname static dev-hugepages.mount static dev-mqueue.mount static proc-sys-fs-binfmt_misc.mount static sys-fs-fuse-connections.mount static sys-kernel-config.mount static sys-kernel-debug.mount static acpid.path enabled cups.path enabled (...) ملحوظة: يمكن للوحدة أن تكون خدمة، نقطة تركيب Mount point، مقبس شبكة Socket أو جهازا طرفيا Device. يمكنك الاقتصار على الوحدات العاملة حاليا بالمعطى list-units: $ systemctl list-units UNIT LOAD ACTIVE SUB DESCRIPTION proc-sys-fs-binfmt_misc.automount loaded active running Arbitrary Executable File Formats File System Automoun sys-devices-pci0000:00-0000:00:02.0-drm-card0-card0\x2deDP\x2d1-intel_backlight.device loaded active plugged /sys/devices/pci0000: sys-devices-pci0000:00-0000:00:03.0-sound-card0.device loaded active plugged Broadwell-U Audio Controller sys-devices-pci0000:00-0000:00:14.0-usb1-1\x2d3-1\x2d3:1.0-net-enx582c80139263.device loaded active plugged E353/E3131 sys-devices-pci0000:00-0000:00:14.0-usb1-1\x2d4-1\x2d4:1.0-bluetooth-hci0.device loaded active plugged /sys/devices/pci0000:00/000 sys-devices-pci0000:00-0000:00:1b.0-sound-card1.device loaded active plugged Wildcat Point-LP High Definition Audio Controller (…) كما يمكن عزل الوحدات التي أخفقت بالمعطى failed--: $ systemctl --failed UNIT LOAD ACTIVE SUB DESCRIPTION ● snapd.refresh.service loaded failed failed Automatically refresh installed snaps ● systemd-suspend.service loaded failed failed Suspend LOAD = Reflects whether the unit definition was properly loaded. ACTIVE = The high-level unit activation state, i.e. generalization of SUB. SUB = The low-level unit activation state, values depend on unit type. 2 loaded units listed. Pass --all to see loaded but inactive units, too. To show all installed unit files use 'systemctl list-unit-files'. التحقق من تفعيل خدمة يطلُب الأمر التالي التحقق من أن خدمة الجدولة Cron مفعَّلة (تعمل مع إقلاع النظام): $ systemctl is-enabled cron.service enabled يظهر من نتيجة الأمر أن الخدمة مفعَّلة enabled. التحقق من حالة خدمة نتحقق في الأمر التالي من حالة الخدمتين mysql وapache2: systemctl status apache2.service ● apache2.service - LSB: Apache2 web server Loaded: loaded (/etc/init.d/apache2; bad; vendor preset: enabled) Drop-In: /lib/systemd/system/apache2.service.d └─apache2-systemd.conf Active: active (running) since Wed 2016-09-14 12:23:56 GMT; 4h 31min ago يظهر في نتيجة تنفيذ الأمرين أعلاه أن mysql غير مفعَّلة وليست نشطة أثناء تنفيذ الأمر؛ أما الخدمة apache2 فهي مفعَّلة وتعمل منذ أربع ساعات. التحكم في الخدمات وإداراتها بـ systemctl سرد جميع الخدمات يسرُد الأمر التالي جميع خدمات النظام بغضّ النظر عن حالتها (مفعَّلة أو معطَّلة): $ systemctl list-unit-files --type=service UNIT FILE STATE accounts-daemon.service enabled acpid.service disabled apport-forward@.service static apt-daily.service static atd.service enabled autovt@.service enabled bootlogd.service masked bootlogs.service masked bootmisc.service masked cgmanager.service enabled cgproxy.service enabled checkfs.service masked checkroot-bootclean.service masked checkroot.service masked (...) تشغيل خدمة (apache2)، إيقافها، إعادة تشغيلها أو إعادة تحميلها: تبدأ الأوامر التالية تشغيل الخدمة apache2، تعيد تحميلها، تعيد تشغيلها وتوقفها على التوالي (تحتاج لصلاحيات إدارية): # sudo systemctl start apache2 # sudo systemctl restart apache2 # sudo systemctl stop apache2 # sudo systemctl reload apache2 apache2.service is not active, cannot reload. # sudo systemctl start apache2 # sudo systemctl reload apache2 # sudo systemctl status apache2 ● apache2.service - LSB: Apache2 web server Loaded: loaded (/etc/init.d/apache2; bad; vendor preset: enabled) Drop-In: /lib/systemd/system/apache2.service.d └─apache2-systemd.conf Active: active (running) since Wed 2016-09-14 17:15:11 GMT; 17s ago لا تُظهر الأوامر أعلاه مخرجات عند تنفيذها بدون مشاكل. استخدم أمر التحقق status لمعرفة حالة الخدمة. تفعيل خدمة أو تعطيلها تُسمّى الخدمات التي تبدأ بالعمل مع إقلاع النظام بالخدمات المفعَّلة Enabled، وعكسُها الخدمات المعطّلة Disabled. أما الخدمات النشطة Active فهي الخدمات العاملة حاليا (تُشغَّل بالأمر start). يستفسر الأمر التالي عن نشاط الخدمة apache2 (تعمل أم لا): $ systemctl is-active apache2.service active نستخدم الأمر disable لتعطيل خدمة وenable لتفعيلها: # systemctl enable apache2.service # systemctl disable apache2.service إخفاء خدمة يمنع الإخفاء Masking تشغيل خدمة. يمكن إخفاء الخدمة apache2 على النحو التالي: # systemctl mask apache2.service Created symlink from /etc/systemd/system/apache2.service to /dev/null. ولإلغاء الإخفاء: $ sudo systemctl unmask apache2.service Removed symlink /etc/systemd/system/apache2.service. الإيقاف الإجباري لخدمة بـsystemctl # systemctl kill apache2 # sudo systemctl status apache2 ● apache2.service - LSB: Apache2 web server Loaded: loaded (/etc/init.d/apache2; bad; vendor preset: enabled) Drop-In: /etc/systemd/system/apache2.service.d └─50-CPUShares.conf /lib/systemd/system/apache2.service.d └─apache2-systemd.conf Active: inactive (dead) Docs: man:systemd-sysv-generator(8) التحكم في نقاط التركيب وإداراتها بـsystemctl سرد جميع نقاط التركيب في النظام $ systemctl list-unit-files --type=mount UNIT FILE STATE dev-hugepages.mount static dev-mqueue.mount static proc-sys-fs-binfmt_misc.mount static sys-fs-fuse-connections.mount static sys-kernel-config.mount static sys-kernel-debug.mount static tmp.mount disabled 7 unit files listed. إدارة نقاط التركيب يمكن بالأوامر التالية تشغيل نقطة تركيب، فصلها، إعادة تركيبها والتحقق من حالتها على التوالي: # systemctl start tmp.mount # systemctl stop tmp.mount # systemctl restart tmp.mount # systemctl reload tmp.mount # systemctl status tmp.mount tmp.mount - Temporary Directory Loaded: loaded (/usr/lib/systemd/system/tmp.mount; disabled) Active: active (mounted) since Wed 2016-09-14 17:36:11 GMT;; 2min 48s ago Where: /tmp What: tmpfs (...) تفعيل نقطة تركيب أو تعطيلها يمكن بطريقة مشابهة للخدمات تفعيل نقاط التركيب أوتعطيلها: # systemctl enable tmp.mount # systemctl disable tmp.mount إخفاء نقطة تركيب يُستخدَم الأمر mask لإخفاء نقطة تركيب على النحو التالي: # systemctl mask tmp.mount Created symlink from /etc/systemd/system/tmp.mount to /dev/null. ولإلغاء الإخفاء: # systemctl unmask tmp.mount Removed symlink /etc/systemd/system/tmp.mount التحكّم في مقابس الشبكة بـsystemctl سرد جميع المقابس $ systemctl list-unit-files --type=socket UNIT FILE STATE acpid.socket enabled apport-forward.socket enabled avahi-daemon.socket enabled cups.socket enabled dbus.socket static lxd.socket masked saned.socket disabled snapd.socket enabled syslog.socket static systemd-bus-proxyd.socket static systemd-fsckd.socket static systemd-initctl.socket static systemd-journald-audit.socket static systemd-journald-dev-log.socket static systemd-journald.socket static systemd-networkd.socket disabled systemd-rfkill.socket static systemd-udevd-control.socket static systemd-udevd-kernel.socket static uuidd.socket enabled 20 unit files listed. تشغيل مقبس (cups)، إعادة تشغيله، إعادة تحميله أو إيقافه: # systemctl start cups.socket # systemctl restart cups.socket # systemctl reload cups.socket # systemctl stop cups.socket $ systemctl status cups.socket ● cups.socket - CUPS Scheduler Loaded: loaded (/lib/systemd/system/cups.socket; enabled; vendor preset: enabled) Active: active (listening) since Thu 2016-09-15 14:39:47 GMT; 5s ago Listen: /var/run/cups/cups.sock (Stream) تفعيل مقبس أو تعطيله: # systemctl enable cups.socket # systemctl disable cups.socket إخفاء مقبس $ sudo systemctl mask cups.socket Created symlink from /etc/systemd/system/cups.socket to /dev/null. $ sudo systemctl unmask cups.socket Removed symlink /etc/systemd/system/cups.socket. استخدام المعالج حصة خدمة من المعالج يمكن استخدام systemctl لعرض عدد الحصص الحاليّة المحدّدة لخدمة من المعالج: # systemctl show -p CPUShares apache2.service CPUShares=18446744073709551615 تعرّف قيمة CPUShares الوقت المخصّص للخدمة من المعالج. تعني زيادة هذه القيمة الرفع من أولويّة للخدمة. يمكن تحديد الحصّة بتغيير قيمة المتغيّر على النحو التالي: # systemctl set-property apache2.service CPUShares=1500 $ systemctl show -p CPUShares apache2.service CPUShares=1500 ينشئ النظام عند تحديد حصّة خدمة مجلّدا يوافق اسمه اسمَ الخدمة (مثلا apache2.service.d) وينشئ بداخله ملف إعداد يبدأ برقم يتبعه اسم المتغيّر (مثلا 50-CPUShares.conf): $ cat /etc/systemd/system/apache2.service.d/50-CPUShares.conf [Service] CPUShares=1500 عرض تفاصيل الإعداد الخاصّة بخدمة $ systemctl show apache2 Type=forking Restart=no NotifyAccess=none RestartUSec=100ms TimeoutStartUSec=5min TimeoutStopUSec=5min RuntimeMaxUSec=infinity WatchdogUSec=0 WatchdogTimestampMonotonic=0 FailureAction=none PermissionsStartOnly=no RootDirectoryStartOnly=no RemainAfterExit=no GuessMainPID=no MainPID=0 ControlPID=0 FileDescriptorStoreMax=0 NFileDescriptorStore=0 StatusErrno=0 (...) عرض اعتماديّات خدمة $ systemctl list-dependencies apache2.service apache2.service ● ├─system.slice ● ├─network-online.target ● │ ├─networking.service ● │ └─NetworkManager-wait-online.service ● └─sysinit.target ● ├─apparmor.service ● ├─brltty.service ● ├─console-setup.service ● ├─dev-hugepages.mount ● ├─dev-mqueue.mount ● ├─friendly-recovery.service ● ├─keyboard-setup.service ● ├─kmod-static-nodes.service ● ├─plymouth-read-write.service ● ├─plymouth-start.service ● ├─proc-sys-fs-binfmt_misc.automount ● ├─resolvconf.service ● ├─setvtrgb.service (...) سرد مجموعات التحكّم Control groups يُستخدَم الأمر systemd-cgls لسرد مجموعات التحكّم هرميّا على النحو التالي: # systemd-cgls ├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 21 ├─user.slice │ └─user-1000.slice │ └─session-1.scope │ ├─ 2292 gdm-session-worker [pam/gdm-password] │ ├─ 2308 /usr/bin/gnome-keyring-daemon --daemonize --login │ ├─ 2351 gnome-session --session gnome-classic │ ├─ 2358 dbus-launch --sh-syntax --exit-with-session │ ├─ 2359 /bin/dbus-daemon --fork --print-pid 4 --print-address 6 --sessi... │ ├─ 2424 /usr/libexec/gvfsd │ ├─ 2428 /usr/libexec/gvfsd-fuse /run/user/1000/gvfs -f -o big_writes يُمكن سرد هذه المجموعات حسب استخدام المعالج والذاكرة العشوائية أو حسب المُدخَلات والمُخرجات بالأمر systemd-cgtop: Path Tasks %CPU Memory Input/s Output/s / 216 37.7 1.6G - - /system.slice - 7.1 - - - /system.slice/gdm.service 2 7.1 - - - /system.slice/rngd.service 1 0.0 - - - /system.slice/tuned.service 1 0.0 - - - /system.slice/vboxadd-service.service 1 0.0 - - - /system.slice/httpd.service 6 0.0 - - - التحكم في مستويات التشغيل Runlevels الإقلاع في وضع الإنقاذ Rescue mode أو وضع الطوارئ Emergency mode يمكن في حال حدوث مشكل الإقلاعُ في وضع الإنقاذ بالأمر التالي: # systemctl rescue كما يُستخدم الأمر systemctl للإقلاع في وضع الطوارئ: # systemctl emergency وللعودة إلى وضع الإقلاع المبدئي: # systemctl default عرض مستوى التشغيل الحالي $ systemctl get-default graphical.target الإقلاع على مستوى تشغيل يمكن تحديد مستوى التشغيل الذي نريد الإقلاع عليه بالأمر systemctl isolate؛ مثلا: # systemctl isolate runlevel5.target # systemctl isolate runlevel3.target أو بذكر اسم المستوى: # systemctl isolate graphical.target # systemctl isolate multiuser.target تعيين مستوى التشغيل المبدئي # systemctl set-default runlevel3.target # systemctl set-default runlevel5.target مستويات التشغيل هي التالية: المستوى 0 runlevel0: إيقاف تشغيل النظام وفصله عن الطاقة. المستوى 1 runlevel1: وضع الإنقاذ والصيانة. المستوى 2 runlevel2: تعدّد المستخدمين. المستوى 3 runlevel3: تعدّد المستخدمين مع تشغيل خدمات الشبكة. المستوى 4 runlevel4: مستوى تشغيل محجوز لما يعرّفه المستخدم. المستوى 5 runlevel5: المستوى 3 + واجهة رسومية. المستوى 6 runlevel6: إعادة تشغيل النظام. إعادة تشغيل النظام، إيقافه، تعليقه وإسباته # systemctl reboot # systemctl halt # systemctl suspend # systemctl hibernate كما يمكن جعل النظام في وضع السبات المزدوج (مزيج من وضع السبات والتعليق) بالأمر: # systemctl hybrid-sleep ترجمة - بتصرّف - لمقال How to Manage ‘Systemd’ Services and Units Using ‘Systemctl’ in Linux.
  6. يتناول هذا المقال كيفية تثبيت الحزم، تحديثها، حذفها، البحث عنها وإدارة مستودعاتها Repositories على توزيعة لينكس تستخدم مدير الحزم YUM الذي تُطوّره شركة Red Hat. يُمكن استخدام الأمثلة المذكورة في هذا الدّرس، والتي جُرِّبت على خادوم Red Hat Enterprise Linux 7، لأغراض الدراسة، التحضير لشهادة متخصّصة أو لاستكشاف طرق جديدة لتثبيت الحزم والإبقاء على نظامك محدَّثا. يجب، لكي تتبع الأمثلة المقدَّمة في هذا الدرس، أن يكون لديك فهم لأساسيات عمل الأوامر في لينكس وتوزيعة تستخدم YUM للتجربة عليها، Fedora أو Red Hat Enterprise Linux 7 على سبيل المثال. ما هو مدير الحزم YUM؟ يُستخدَم مدير الحزم YUM (اختصار لـYellowdog Updater Modified) لإدارة البرامج المُحزَّمة بصيغة RPM (RedHat Package Manager). يمكن استخدام YUM من سطر الأوامر أو عبر واجهة رسومية. يتيح مدير الحزم YUM لمدير النظام تثبيت الحزم، تحديثها، حذفها أو البحث عنها. يُوزَّع مدير الحزم YUM حسب رخصة غنو GNU العمومية. تثبيت الحزم يُستخدَم الأمر install في مدير الحزم YUM على النحو التالي لتثبيت حزمة برنامج يُسمَّى Firefox. يتولّى مدير الحزم تثبيت الاعتمادات Dependencies، أي البرامج والمكتبات Libraries التي يتطلب عملُ الحزمة Firefox وجودَها (يجب أن تكون لديك صلاحيات المستخدم الجذر لتثبيت الحزم): [root@academy1 ~]# yum install firefox Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Resolving Dependencies --> Running transaction check ---> Package firefox.x86_64 0:45.3.0-1.el7_2 will be installed --> Finished Dependency Resolution Dependencies Resolved ========================================================================= Package Arch Version Repository Size ========================================================================= Installing: firefox x86_64 45.3.0-1.el7_2 rhel-7-server-eus-rpms 76 M Transaction Summary ========================================================================= Install 1 Package Total download size: 76 M Installed size: 142 M Is this ok [y/d/N]: y Downloading packages: (...) Installed: firefox.x86_64 0:45.3.0-1.el7_2 Complete! يطلُب الأمر أعلاه التأكيد (Is this ok) قبل البدء في تنزيل الحزمة وتثبيتها (اكتب y ثم اضغط على زرّEnter للتأكيد). استخدم الخيار y- إن كنت ترغب في تثبيت الحزم تلقائيا دون طلب التأكيد: # yum -y install firefox حذف حزمة يحذف الأمر التالي الحزمة firefox تماما من النظام (بما في ذلك اعتماداتها): [root@academy1 ~]# yum remove firefox Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Resolving Dependencies --> Running transaction check ---> Package firefox.x86_64 0:45.3.0-1.el7_2 will be erased --> Finished Dependency Resolution Dependencies Resolved =========================================================================================== Package Arch Version Repository Size =========================================================================================== Removing: firefox x86_64 45.3.0-1.el7_2 @rhel-7-server-eus-rpms 142 M Transaction Summary =========================================================================================== Remove 1 Package Installed size: 142 M Is this ok [y/N]: y Downloading packages: Running transaction check Running transaction test Transaction test succeeded Running transaction Erasing : firefox-45.3.0-1.el7_2.x86_64 1/1 Verifying : firefox-45.3.0-1.el7_2.x86_64 1/1 Removed: firefox.x86_64 0:45.3.0-1.el7_2 Complete! يطلُب الأمر التأكيد قبل حذف الحزمة. استخدم الخيار y- لتعطيل طلب التأكّد كالتالي: # yum -y remove firefox تحديث حزمة فلنفترض أن لديك حزمة MySQL قديمة، لم تُحدَّث منذ زمن؛ وترغب في تحديثها إلى آخر إصدار. الأمر التالي يؤدي المهمة: # yum update mysql سرد قائمة بالحزم يُستخدَم أمر السرد list للبحث عن حزمة معيّنة عن طريق اسمها. يبحث الأمر التالي عن حزمة باسم openssh: [root@academy1 ~]# yum list openssh Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Installed Packages openssh.x86_64 6.6.1p1-25.el7_2 @rhel-7-server-eus-rpms يمكن جعل البحث أكثر دقة بذكر إصدار الحزمة إن عُرِف؛ مثلا: # yum list openssh-6.6.1p1 البحث عن حزمة يمكن استخدام الأمر search للبحث عن حزمة لا تتذكر اسمها بالضبط؛ مثلا نبحث عن ftp: [root@academy1 ~]# yum search ftp Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager ================== N/S matched: ftp ================================= ftp.x86_64 : The standard UNIX FTP (File Transfer Protocol) client tftp.x86_64 : The client for the Trivial File Transfer Protocol (TFTP) tftp-server.x86_64 : The server for the Trivial File Transfer Protocol (TFTP) vsftpd.x86_64 : Very Secure Ftp Daemon curl.x86_64 : A utility for getting files from remote servers (FTP, HTTP, and others) lftp.i686 : A sophisticated file transfer program lftp.x86_64 : A sophisticated file transfer program wget.x86_64 : A utility for retrieving files using the HTTP or FTP protocols Name and summary matches only, use "search all" for everything. الحصول على معلومات عن حزمة استخدم الأمر info للحصول على معلومات عن حزمة؛ مثلا قبل تثبيتها: [root@academy1 ~]# yum info firefox Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Available Packages Name : firefox Arch : x86_64 Version : 45.3.0 Release : 1.el7_2 Size : 76 M Repo : rhel-7-server-eus-rpms/7Server/x86_64 Summary : Mozilla Firefox Web browser URL : http://www.mozilla.org/projects/firefox/ License : MPLv1.1 or GPLv2+ or LGPLv2+ Description : Mozilla Firefox is an open-source web browser, designed for standards : compliance, performance and portability. سرد جميع الحزم المتوفّرة يسرُد الأمر list عند استخدامها دون معطيات جميع الحزم المتوفرة في المستودعات؛ وهي كثيرة؛ لذا أعد توجيه نتيجة أمر السّرد إلى الأمر less لتيسير عمليّة التصفح: [root@academy1 ~]# yum list | less استخدم المعطى installed إن أردت أن يقتصر السّرد على الحزم المثبّتة: [root@academy1 ~]# yum list installed | less العثور على حزمة انطلاقا من ملفّ يُستخدَم الأمر provides في مدير الحزم YUM لمعرفة الحزمة التي يتبع لها ملفّ مّا. مثلا؛ نريد معرفة الحزمة التي توفّر ملفّ الإعداد etc/httpd/conf/httpd.conf/، لذا ننفذ الأمر بالطريقة التالية: [root@academy1 ~]# yum provides /etc/httpd/conf/httpd.conf Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager httpd-2.4.6-17.el7.x86_64 : Apache HTTP Server Repo : rhel-7-server-eus-rpms Matched from: Filename : /etc/httpd/conf/httpd.conf httpd-2.4.6-17.el7.x86_64 : Apache HTTP Server Repo : rhel-7-server-rpms Matched from: Filename : /etc/httpd/conf/httpd.conf يُظهر الأمر اسم الحزمة والمستودع الذي تأتي منه. التحقق من وجود تحديثات وتحديث النظام استخدم الأمر yum check-update لمعرفة الحزم المثبّتة التي تتوفّر لها تحديثات. أما إن أردت تحديث الحزم إلى آخر إصدار متوفّر فالأمر yum update يؤدي المهمة. سرد مجموعات الحزم تُجمَّع كثير من الحزم في مجموعات لتسهيل التعامل معها؛ فبدلا من تثبيت هذه الحزم الواحدة تلو الآخرى، أو كتابة أسمائها جميعا، يمكن تثبيتها دفعة واحدة عن طريق اسم المجموعة. استخدم الأمر grouplist لسرد مجموعات الحزم المتوفّرة: [root@academy1 ~]# yum grouplist Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Available Environment Groups: Minimal Install Infrastructure Server File and Print Server Basic Web Server Virtualization Host Server with GUI Installed Groups: Development Tools Graphical Administration Tools Legacy UNIX Compatibility Security Tools System Administration Tools Available Groups: Compatibility Libraries Console Internet Tools RT Scientific Support Smart Card Support System Management Done تثبيت مجموعة حزم استخدم الأمر groupinstall لتثبيت مجموعة حزم. يثبّت الأمر التالي مجموعة حزم File and Print Server (خادوم الطباعة وتشارك الملفات): [root@academy1 ~]# yum groupinstall 'File and Print Server' Dependencies Resolved ====================================================================================== Package Arch Version Repository Size ====================================================================================== Installing for group install "File and Storage Server": cifs-utils x86_64 6.2-7.el7 rhel-7-server-eus-rpms 84 k nfs4-acl-tools x86_64 0.3.3-14.el7 rhel-7-server-eus-rpms 47 k targetcli noarch 2.1.fb41-3.el7 rhel-7-server-eus-rpms 61 k targetd noarch 0.7.1-1.el7 rhel-7-server-eus-rpms 48 k Installing for dependencies: PyYAML x86_64 3.10-11.el7 rhel-7-server-eus-rpms 153 k libyaml x86_64 0.1.4-11.el7_0 rhel-7-server-eus-rpms 55 k lvm2-python-libs x86_64 7:2.02.130-5.el7_2.5 rhel-7-server-eus-rpms 166 k pyparsing noarch 1.5.6-9.el7 rhel-7-server-eus-rpms 94 k python-configshell noarch 1:1.1.fb18-1.el7 rhel-7-server-eus-rpms 67 k python-kmod x86_64 0.9-4.el7 rhel-7-server-eus-rpms 57 k python-rtslib noarch 2.1.fb57-3.el7 rhel-7-server-eus-rpms 88 k python-setproctitle x86_64 1.1.6-5.el7 rhel-7-server-eus-rpms 15 k python-urwid x86_64 1.1.1-3.el7 rhel-7-server-eus-rpms 654 k Transaction Summary ========================================================================================= Install 4 Packages (+9 Dependent packages) Total download size: 1.6 M Installed size: 5.7 M Is this ok [y/d/N]: تحديث مجموعة حزم يُستخدَم الأمر groupupdate على النحو التالي لتحديث مجموعة حزم سبق تثبيتها: [root@academy1 ~]# yum groupupdate 'File and Print Server' حذف مجموعة حزم استخدم الأمر groupremove لحذف مجموعة حزم مثبتة؛ مثلا لحذف مجموعة الحزم السابقة: [root@academy1 ~]# yum groupremove 'File and Print Server' سرد مستودعات YUM يمكن سرد المستودعات المفعَّلة في مدير الحزم YUM باستخدام الأمر التالي: [root@academy1 ~]# yum repolist Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager repo id repo name status !rhel-7-server-eus-rpms/7Server/x86_64 Red Hat Enterprise Linux 7 Server - Extended Update Support (RPMs) 11,241 !rhel-7-server-rpms/7Server/x86_64 Red Hat Enterprise Linux 7 Server (RPMs) 11,250 !rhel-7-server-rt-beta-rpms/x86_64 Red Hat Enterprise Linux for Real Time Beta (RHEL 7 Server) (RPMs) 15 !rhel-7-server-rt-rpms/7Server/x86_64 Red Hat Enterprise Linux for Real Time (RHEL 7 Server) (RPMs) 129 !rhel-ha-for-rhel-7-server-eus-rpms/7Server/x86_64 Red Hat Enterprise Linux High Availability (for RHEL 7 Server) - Extended Update Support (RP 224 !rhel-ha-for-rhel-7-server-rpms/7Server/x86_64 Red Hat Enterprise Linux High Availability (for RHEL 7 Server) (RPMs) 224 !rhel-rs-for-rhel-7-server-eus-rpms/7Server/x86_64 Red Hat Enterprise Linux Resilient Storage (for RHEL 7 Server) - Extended Update Support (RP 279 !rhel-rs-for-rhel-7-server-rpms/7Server/x86_64 Red Hat Enterprise Linux Resilient Storage (for RHEL 7 Server) (RPMs) 279 repolist: 23,641 أضف الخيار all إلى الأمر السابق إن أردت سرد جميع المستودعات بما فيها تلك غير المفعَّلة: [root@academy1 ~]# yum repolist all تثبيت حزمة مع تحديد المستودع يُستخدم الخيار enablerepo-- في أمر التثبيت لتحديد المستودع الذي نريد تنزيل الحزمة منه: [root@academy1 ~]# yum --enablerepo=rhel-7-server-rpms install postgresql Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Resolving Dependencies --> Running transaction check ---> Package postgresql.x86_64 0:9.2.15-1.el7_2 will be installed --> Processing Dependency: postgresql-libs(x86-64) = 9.2.15-1.el7_2 for package: postgresql-9.2.15-1.el7_2.x86_64 --> Processing Dependency: libpq.so.5()(64bit) for package: postgresql-9.2.15-1.el7_2.x86_64 --> Running transaction check ---> Package postgresql-libs.x86_64 0:9.2.15-1.el7_2 will be installed --> Finished Dependency Resolution Dependencies Resolved ======================================================================================= Package Arch Version Repository Size ======================================================================================= Installing: postgresql x86_64 9.2.15-1.el7_2 rhel-7-server-eus-rpms 3.0 M Installing for dependencies: postgresql-libs x86_64 9.2.15-1.el7_2 rhel-7-server-eus-rpms 231 k Transaction Summary ======================================================================================== Install 1 Package (+1 Dependent package) Total download size: 3.2 M Installed size: 16 M Is this ok [y/d/N]: صدفة YUM تفاعلية يتيح مدير الحزم YUM صدفة Shell لتنفيذ أوامر عدّة لإدارة الحزم: [root@academy1 ~]# yum shell Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager > update > repolist repo id repo name status rhel-7-server-eus-rpms/7Server/x86_64 Red Hat Enterprise Linux 7 Server - Extended Update Support (RPMs) 11,241 rhel-7-server-rpms/7Server/x86_64 Red Hat Enterprise Linux 7 Server (RPMs) 11,250 rhel-7-server-rt-beta-rpms/x86_64 Red Hat Enterprise Linux for Real Time Beta (RHEL 7 Server) (RPMs) 15 rhel-7-server-rt-rpms/7Server/x86_64 Red Hat Enterprise Linux for Real Time (RHEL 7 Server) (RPMs) 129 rhel-ha-for-rhel-7-server-eus-rpms/7Server/x86_64 Red Hat Enterprise Linux High Availability (for RHEL 7 Server) - Extended Update Support (RPM 224 rhel-ha-for-rhel-7-server-rpms/7Server/x86_64 Red Hat Enterprise Linux High Availability (for RHEL 7 Server) (RPMs) 224 rhel-rs-for-rhel-7-server-eus-rpms/7Server/x86_64 Red Hat Enterprise Linux Resilient Storage (for RHEL 7 Server) - Extended Update Support (RPM 279 rhel-rs-for-rhel-7-server-rpms/7Server/x86_64 Red Hat Enterprise Linux Resilient Storage (for RHEL 7 Server) (RPMs) 279 > exit Leaving Shell عرض سجلّ أوامر YUM يُستخدَم الأمر history على النحو التالي لعرض قائمة بآخر أوامر YUM المنفّذة: [root@academy1 ~]# yum history Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager ID | Login user | Date and time | Action(s) | Altered ------------------------------------------------------------------------------- 14 | Mohamed ... <meyil> | 2016-09-01 23:57 | Erase | 1 13 | Mohamed ... <meyil> | 2016-09-01 23:48 | Install | 1 12 | Mohamed ... <meyil> | 2016-08-02 23:41 | I, U | 6 11 | Mohamed ... <meyil> | 2016-07-31 23:19 | Install | 2 10 | Mohamed ... <meyil> | 2016-07-21 22:09 | Install | 1 9 | Mohamed ... <meyil> | 2016-07-21 15:29 | Install | 4 8 | Mohamed ... <meyil> | 2016-07-21 14:18 | I, U | 75 7 | Mohamed ... <meyil> | 2016-06-11 01:32 | Erase | 1 6 | Mohamed ... <meyil> | 2016-06-07 13:51 | Update | 1 5 | Mohamed ... <meyil> | 2016-06-05 02:57 | Install | 2 4 | Mohamed ... <meyil> | 2016-06-05 02:56 | Update | 1 3 | Mohamed ... <meyil> | 2016-05-28 03:16 | Install | 1 2 | Mohamed ... <meyil> | 2016-05-28 02:45 | I, U | 190 EE 1 | System <unset> | 2016-05-27 20:43 | Install | 1293 history list تنظيف تخبئة Cache مدير الحزم يحتفظ مدير الحزم YUM بنسخ من البيانات المتعلقة بالحزم ضمن مجلّد var/cache/yum/؛ يمكن حذف جميع هذه النسخ بتنفيذ الأمر التالي: [root@academy1 ~]# yum clean all Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Cleaning repos: rhel-7-server-eus-rpms rhel-7-server-rpms rhel-7-server-rt-beta-rpms rhel-7-server-rt-rpms rhel-ha-for-rhel-7-server-eus-rpms : rhel-ha-for-rhel-7-server-rpms rhel-rs-for-rhel-7-server-eus-rpms rhel-rs-for-rhel-7-server-rpms Cleaning up everything ترجمة - بتصرّف - لمقال20 Linux YUM (Yellowdog Updater, Modified) Commands for Package Management لصاحبه Ravi Saive.
  7. المسترجِعات Accessors والمعدِّلات Mutators في Laravel هي مجموعة دوالّ مخصّصة يعرّفها المستخدم لتهيئة خواص النماذج Models. تُستخدم المسترجعات لتهيئة خواصّ النموذج عند طلب الحصول عليها من قاعدة البيانات، بينما تُستخدَم المعدّلات لتهيئة الخواصّ قبل حفظ النموذج في القاعدة. تعريف مسترجع تُسمى الدوال المسترجعة بالصيغة ()getFooAttribute حيث Foo هو اسم الخاصيّة التي نريد تهيئتها في الدالة، مع الانتباه إلى حالة الحرف الأول من الخاصية (حرف كبير Uppercase). سيستدعي Laravel تلقائيا هذه الدالة في كلّ مرة تبحث فيها عن قيمة الخاصيّة foo. لا يقتصر استخدام المسترجعات على سلاسل المحارف، بل يمكنك استخدامها لتهيئة أي نوع من البيانات تواريخ، أثمان، …إلخ. سنفترض أن لدينا نموذجا باسم Member (عضو) يحوي الخاصيّات التاليّة: first_name last_name email password last_visit settings created_at updated_at توجد من ضمن اللائحة أعلاه بضعة خاصيّات سيكون مفيدا لنا تطبيق المسترجعات عليها. على سبيل المثال، لا يهتمّ المستخدمون كثيرا بطريقة كتابة أسمائهم، لذا يمكننا استخدام المسترجعات للتأكد من أن الأسماء تُكتب بطريقة صحيحة. يحاكي المثال التالي إضافة مستخدم لم ينتبه كثيرا لطريقة كتابة بياناته: $user = App\Member::create([ 'first_name' => 'mirza', 'last_name' => 'pasic', 'email' => 'mirza.pasic@bosnadev.com', 'password' => '!supersecretpassword!', 'last_login' => Carbon\Carbon::now(), 'settings' => ['two_factor_aut' => false, 'session_time' => 1200], 'created_at' => Carbon\Carbon::now(), 'updated_at' => Carbon\Carbon::now() ]); يمكننا استخدام المسترجعات لتهيئة مُدخلات المستخدم: use Illuminate\Database\Eloquent\Model; /** * Class Member * @package App */ class Member extends Model { /** * التأكد من تهيئة الاسم الشخصي بعد الحصول على النموذج من قاعدة البيانات * * @param $value * @return string */ public function getFirstNameAttribute($value) { return ucfirst($value); } /** * التأكد من تهيئة الاسم العائلي بعد الحصول على النموذج من قاعدة البيانات * * @param $value * @return string */ public function getLastNameAttribute($value) { return ucfirst($value); } } سيعمل المسترجعان getFirstNameAttribute و getLastNameAttribute على تهيئة الحقلين first_name (الاسم الشخصي) وlast_name (الاسم العائلي) على التوالي لجعل الحرف الأول في كلّ واحد منهما يُكتب كبيرا (Uppercase)، بغضّ النظر عن الطريقة التي أدخلهما بها المستخدم. يمكن أيضا استخدام المسترجعات لدمج خاصيّتين في النموذج لتكوّنا واحدة جديدة. يمكن مثلا تعريف الاسم الكامل full_name بناء على الاسم الشخصي والاسم العائلي: /** * Get members full name * * @return string */ public function getFullNameAttribute() { return ucfirst($this->first_name) . ' ' . ucfirst($this->last_name); } ثم يمكننا استخدامه كما لو كان فعلا يوجد حقل باسم full_name في النموذج: $member = App\Member::find(1); echo $member->full_name; يمكنك إن أردت استخدام المسترجعات على أنواع بيانات أخرى. مثلا، لتهيئة التاريخ. نستخدم في المثال التالي الدالة getLastLoginAttribute لتهيئة تاريخ آخر دخول (الحقل last_login) بحيث يظهر بالصيغة d.m.Y (يمثّل الحرف d اليوم، الحرفm الشهر وY السنة مكتوبة بأربعة أرقام): /** * Custom format for the last login date * * @param $value * @return string */ public function getLastLoginAttribute($value) { return \Carbon\Carbon::parse($value)->format('d.m.Y.'); } ثم نستخدم الحقل last_login كما في المثال التالي: $member = App\Member::find(1); echo $member->last_login; يظهر الحقل كما يلي: 11.07.2016. تعريف معدل تشبه آلية تسميّة المعدِّل تعريفَ المسترجع، مع فرق إحلال set مكان get. إذا أردنا تعريف معدِّل للخاصيّة foo فسيكون اسمُه setFooAttribute. نعود لمثال النموذج Member السابق. نريد الآن أن نتأكد من تهيئة اسم العضو قبل حفظ النموذج في قاعدة البيانات: /** * التأكد من تهيئة الاسم الشخصي **قبل** حفظ النموذج * * @param $value * @return string */ public function setFirstNameAttribute($value) { $this->attributes['first_name'] = ucfirst($value); } /** * التأكد من تهيئة الاسم العائلي **قبل** حفظ النموذج * * @param $value * @return string */ public function setLastNameAttribute($value) { $this->attributes['last_name'] = ucfirst($value); } ربما لاحظت أن الدالة لا ترجِع أية قيمة. ما يحدُث هو أننا نضبُط الخاصيّة بالقيمة التي نريد مباشرة. نتأكد بهذه الطريقة أن الاسمين الشّخصي والعائلي سيكونان مهيأيْن دائما بالصيغة التي نريد. أمثلة على المسترجعات والمعدلات رأينا في الفقرات السابقة أمثلة لاستخدامات سهلة للمسترجعات والمعدّلات؛ إلا أن هذه الدوال يمكن أن تُستخدَم لأمور أكثر تخصيصا كما سنرى في الأمثلة التالية. حماية كلمات السّر يمكن استخدام المعدِّلات لتجزئة كلمة السّر Password hashing قبل حفظها في قاعدة البيانات؛ مثلا: public function setPasswordAttribute($value) { $this->attributes['password'] = Hash::make($value); } تتأكد بهذه الطريقة أن كلمة السّر لن تُحفَظ أبدا بصيغتها المقروءة في قاعدة البيانات، وهو ما يعدّ إجراء أمنيًّا أساسيا. استخدمنا في المثال السابق صنف Hash الذي يوفّره Laravel مبدئيا، ولكن يمكنك استخدام دوال تعميّة إن أردت، مثل ()bcrypt. الترميز بصيغة Json إن نظرت إلى نموذج Member السابق فستجد أنه يحوي خاصيّة باسم settings تُستخدَم لحفظ تفضيلات العضو الشخصيّة وخيارات أخرى يتيحها التطبيق. يمكن استخدام مصفوفة لإدارة هذه الخيارات وحفظها في قاعدة البيانات بصيغة Json. تتيح قواعد بيانات مثل PostgreSQL نوع البيانات Json في حقول الجدول منذ زمن، كما أن MySQL أتاحت نوع البيانات هذا في الإصدار 5.7.8. ملحوظة: دعم نوع البيانات Json في MySQL لا زال في بداياته، وقد تواجه مشاكل عند استخدامه. بالنسبة لخاصيّة settings فسنحتاج لمعدِّل يخزّن البيانات بصيغة Json في القاعدة ومسترجِع لتحويل Json إلى مصفوفة من سلسلة محارف: /** * Make sure that we get an array from JSON string * * @param $value * @return array */ public function getSettingsAttribute($value) { return json_decode($value, true); } /** * Encode an array to a JSON string * * @param $value */ public function setSettingsAttribute($value) { $this->attributes['settings'] = json_encode($value); } المثال كاملا في ما يلي الخطوات اللازمة لإنشاء مثال عملي يعتمد على النموذج المقدّم في هذا الدرس. نفترض وجود Laravel مثبّت بقاعدة بيانات مضبوطة. إنشاء التهجير والنموذج php artisan make:model Member -m Model created successfully. Created Migration: 2016_07_04_020531_create_members_table التعديل على التهجير public function up() { Schema::create('members', function (Blueprint $table) { $table->increments('id'); $table->string('first_name'); $table->string('last_name'); $table->string('email'); $table->string('password'); $table->date('last_login'); $table->string('settings'); $table->timestamps(); }); } التعديل على النموذج في ما يلي الشفرة الكاملة للنموذج Member مع المسترجعات والمعدِّلات. <?php namespace App; use Illuminate\Support\Facades\Hash; use Illuminate\Database\Eloquent\Model; /** * Class Member * @package App */ class Member extends Model { /** * @var array */ protected $fillable = ['first_name', 'last_name', 'email', 'password', 'last_login', 'settings', 'created_at', 'updated_at']; /** * التأكد من تهيئة الاسم الشخصي بعد الحصول على النموذج من قاعدة البيانات * * @param $value * @return string */ public function getFirstNameAttribute($value) { return ucfirst($value); } /** * التأكد من تهيئة الاسم العائلي بعد الحصول على النموذج من قاعدة البيانات * * @param $value * @return string */ public function getLastNameAttribute($value) { return ucfirst($value); } /** * الاسم الكامل للعضو * * @return string */ public function getFullNameAttribute() { return ucfirst($this->first_name) . ' ' . ucfirst($this->last_name); } /** * تهيئة تاريخ آخر زيارة * * @param $value * @return string */ public function getLastLoginAttribute($value) { return \Carbon\Carbon::parse($value)->format('d.m.Y.'); } /** * التأكد من تهيئة الاسم الشخصي **قبل** حفظ النموذج * * @param $value * @return string */ public function setFirstNameAttribute($value) { $this->attributes['first_name'] = ucfirst($value); } /** * التأكد من تهيئة الاسم العائلي **قبل** حفظ النموذج * * @param $value * @return string */ public function setLastNameAttribute($value) { $this->attributes['last_name'] = ucfirst($value); } /** * تهيئة كلمة السّر **قبل** حفظها * * @param $value */ public function setPasswordAttribute($value) { $this->attributes['password'] = Hash::make($value); } /** * الحصول على مصفوفة من حقل بصيغة * Json * * @param $value * @return array */ public function getSettingsAttribute($value) { return json_decode($value, true); } /** * الترميز بصيغة * Json * قبل حفظ الحقل * @param $value */ public function setSettingsAttribute($value) { $this->attributes['settings'] = json_encode($value); } } استخدام النموذج Route::get('members', function() { $user = App\Member::create([ 'first_name' => 'mirza', 'last_name' => 'pasic', 'email' => 'mirza.pasic@bosnadev.com', 'password' => '!supersecretpassword!', 'last_login' => Carbon\Carbon::now(), 'settings' => ['two_factor_aut' => true, 'session_time' => 1200], 'created_at' => Carbon\Carbon::now(), 'updated_at' => Carbon\Carbon::now() ]); $member = App\Member::find(1); echo $member->last_login . '<br>'; echo $member->full_name . '<br>'; }); ترجمة -وبتصرّف- للمقال Laravel Accessors and Mutators لصاحبه Mirza Pasic.
  8. يتكرّر أحيانا تنفيذ مهامّ وعمليات بحيث يستهلك إتمامها يدويا الكثير من الوقت. يمكن في هذه الحالة اللجوء إلى جدولة المهام لتنفيذها دوريّا في الخلفية، دون الحاجة للتدخل اليدوي، حسب معايير زمنية محدّدة مسبقا. سنرى في هذا المقال كيف يمكننا استخدام أداة Cron والأوامر المصاحبة لها من أجل تنفيذ إجراءات دوريا في الخلفية. يكثُر استخدام Cron لتنفيذ مهامّ النسخ الاحتياطي Backup، تحديث الحزم ومزامنة Synchronization الملفات؛ على سبيل المثال. يقوم مبدأ عمل Cron على التحقق كلّ دقيقة من العناصر الموجودة في الجدول الخاصّ به (ملفّ)، ويُسمّى Crontab (اختصار لـCron table؛ جدول Cron). تحدّد عناصر Crontab المهامّ المُجدوَلة والمجالات الزمنية لتنفيذها. ملحوظة: يُفعَّل Cron مبدئيا لجميع المستخدمين، فيحصُل كل مستخدم على جدول Cron خاصّ به؛ إلا أنه يمكن، عبر الملفّ etc/cron.deny/، تغيير هذا الإعداد وتقييد إمكانيّة جدولة المهامّ بالنسبة للمستخدمين. توجد في ملفّ Crontab أسطر تتكوّن من ستة حقول يُفصل بينها بمسافة أو علامة جدولة (زرّ Tab في لوحة المفاتيح). تُمثّل الحقول الخمسة الأولى من الملفّ المجال الزمني لتنفيذ الأمر الموجود في الحقل السادس؛ وهي على النحو التالي (ابتداءً من اليسار): الدقيقة: يأخذ هذا الحقل قيما عددية في المجال من 0 إلى 59. الساعة: يأخذ قيما تتراوح بين 0 و23. اليوم (في الشهر): يأخذ قيمة من 1 إلى 31. الشهر: يأخذ قيمة عددية في المجال من 1 إلى 12؛ كما يمكن تحديد الأحرف الأولى من اسم الشهر (Jan وDec ليناير ودجمبر على التوالي). اليوم (في الأسبوع): يأخذ قيمة في المجال من 0 إلى 6. يمثّل العدد 0 يوم الأحد، 1 الاثنين.. و6 السبت. كما يمكن تعيين الأحرف الثلاثة الأولى من اليوم (Wed مثلا بالنسبة ليوم الأربعاء). بالنسبة للحقل السادس فهو - كما أسلفنا - الأمر الذي نريد تنفيذه في الوقت المحدّد عن طريق الحقول الزمنية السابقة. سرد المهامّ الموجودة في جدول Cron يُستخدَم الخيار l- مع الأمر crontab لسرد قائمة بأسطر (مهامّ) المستخدم الحالي المضبوطة في جدول Crontab: $ crontab -l 00 10 * * * /bin/ls >/ls.txt التعديل على المهام المُجدولة يتيح الخيّار e- مع الأمر crontab التعديل على المهامّ المجدولة بتغيير إعداداتها في الملف Crontab: $ crontab -e يفتح الأمر جدول المهامّ بمحرّر النصوص المبدئي (أو يُظهر قائمة بالمحرّرات المتاحة للاختيار منها). يمكن بعدها تغيير الإعدادات ثم حفظ الملف. سرد المهام المجدولة لمستخدم يُحدّد الخيار u- المستخدم الذي نريد سرد مهامّه؛ وذلك على النحو التالي: $ crontab -u academy -l no crontab for academy يطلُب الأمر أعلاه سرد المهام المجدولة الخاصّة بالمستخدم academy، وتشير النتيجة إلى عدم وجود مهام مجدولة لهذا المستخدم. ملحوظة: لا يمكن لمستخدم عادي سرد مهام مستخدم آخر؛ لا تُتاح هذه الصلاحية سوى للمستخدم الجذر. حذف المهام المجدولة ينبغي الحذر عند استخدام الخيار r- مع الأمر crontab إذ أنه يحذف جميع المهام المجدولة، دون طلب تأكيد الأمر. أضف الخيار i-، الذي يطلُب تأكيد الحذف، بدلا من استخدام r- لوحده: $ crontab -i -r crontab: really delete academy's crontab? تخصيص المدة الزمنية باستخدام المحارف الخاصّة (*، -، / و#) تُستخدَم هذه المحارف لتخصيص الحقول الزمنية في أسطر جدول المهام: يعني استخدام العلامة * في أحد الحقول الخمسة الأولى من السّطر أننا نريد تنفيذ الأمر مهما كانت قيمة هذا الحقل. مثلا؛ إن استُخدمت في الحقل الثاني فهذا يعني أن المطلوب تنفيذ الأمر بغض النظر عن الساعة. تُستخدَم العلامة - لتعريف مجال بالنسبة للحقل. مثلا 9-0 في حقل الدقيقة تعني أن المطلوب تنفيذ الأمر في الدقائق من 0 إلى 9. يُساعد الخط المائل / في تقسيم الحقل. مثلا 10/ في حقل الدقائق تعني كل عشر دقائق. تفرّق الفاصلة , بين عناصر عدّة في نفس الحقل. مثلا؛ 1,3,5 في الحقل الخامس تعني أننا نريد تنفيذ المهمة أيام الاثنين، الأربعاء والخميس. جدولة المهام على مستوى النظام يُمكن لمديري الأنظمة استخدام مجلدات معدّة مسبقا لتنفيذ مهام على مستوى النظام. تُنفَّذ السكربتات الموجودة في المجلدات التاليّة -على الترتيب - مرة كلّ يوم، مرة كلّ ساعة، مرة في الشّهر ومرة في الأسبوع: /etc/cron.daily /etc/cron.hourly /etc/cron.monthly /etc/cron.weekly كما يُمكن استخدام المجلّد etc/cron.d/ لجدولة المهام بوضع ملفات تشبه في صيغتها Crontab؛ مع زيادة حقل، بعد الحقول الزمنية وقبل الأمر (ليصبح العدد الإجمالي للحقول سبعة)، للمستخدم الذي تُنفَّذ باسمه المهمة. تنفيذ أمر عند وقت معيَّن نستخدم في المثال أدناه الأمر crontab -e للتعديل على جدول المهام ثم نضيف سطرا جديدا لمهمة تحذف جميع الملفات الفارغة في المجلّد tmp/. تُنفَّذ المهمة يوميا عند الساعة 00 (منتصف الليل) و30 دقيقة. # crontab -e 30 0 * * * find /tmp -type f -empty -delete المدد الزمنية الشائعة يمكنك استخدام سلاسل المحارف التالية مكانَ الحقول الزمنية الخمسة سلسلة المحارف العمل reboot@ تنفيذ الأمر عندما يعاد تشغيل النظام daily@ مرة في اليوم. يمكن أيضا استخدام @midnight للتنفيذ عند منتصف الليل. weekly@ مرة في الأسبوع. yearly@ مرة في السنة. تنفيذ أوامر عدّة تُستخدم العلامة && في حقل الأمر لتحديد أكثر من أمر للتنفيذ؛ مثلا: @daily command1 && command2 يطلب السّطر السابق تنفيذ الأمر command1 وcommand2 يوميا. تعطيل إشعارات البريد يُرسل Cron مبدئيا رسائل بريد إلى المستخدم عند تنفيذ المهام المجدولة (في حال كان النظام الذي يعمل عليه قادرا على ذلك). يمكن تعطيل هذه الإشعارات بإضافة السطر التالي إلى Crontab (عن طريق الأمر crontab -e): * * * * * >/dev/null 2>&1 ترجمة - بتصرّف - لمقال 11 Cron Scheduling Task Examples in Linux لصاحبه Ravi Saive.
  9. اشتهر استخدام منصة Heroku السحابية ضمن مجتمع مطوري Ruby on Rails ونالت فيه شعبية واسعة؛ إلا أن الخدمة لم تقتصر على مطوري Ruby وما يدور حولها بل تجاوزتهم إلى تقديم الدعم للغات برمجة مثل Node.js ،Java و PHP؛ من بين أخرى. توفّر Heroku خطّة اشتراك مجانية يمكن استخدامها لإدارة مشاريع صغيرة جدا أو لأغراض الاختبار. سنشرح في هذا الدرس كيفية الاستفادة من Heroku عبر هذه الخطة لنشر Deploying تطبيقات Laravel. إنشاء حساب على Heroku يجب أولا أن ننشئ حسابا على موقع الخدمة حتى يمكننا الاستفادة منها. التسجيل مجاني ولا يستغرق سوى دقائق. ستُعرَض عليك خلال عملية التسجيل خيارات للغة البرمجة التي تريد استخدامها، اختر PHP؛ مع العلم أن هذا الخيار لا يؤثّر على إمكانية استعمالك للغات برمجة أخرى مستقبلا. تثبيت Heroku Toolbelt الخطوة الثانية بعد إنشاء الحساب هي تثبيت أداة Heroku Toolbelt. تُستخدَم هذه الأداة، التي تعمل عبر سطر الأوامر، لإدارة جوانب عدّة من المشاريع المضافة إلى Heroku؛ ومن ضمنها إدارة عمليّة النشر، تهجير Migrating قاعدة البيانات والتخاطب مع خادوم Heroku. تتوفّر الأداة على وندوز، ماك وتوزيعة أوبونتو لينكس. نفّذ بعد تثبيت الأداة الأمر التالي: $ heroku login heroku-cli: Installing CLI... 21.83MB/21.83MB Enter your Heroku credentials. Email: mail@example.com Password (typing will be hidden): Logged in as mail@example.com انتظر قليلا حتى يكتمل تنزيل عميل Heroku ثمّ أدخل معلومات الاستيثاق الخاصّة بك (عنوان البريد وكلمة السر). نشر تطبيق Laravel على Heroku يمكننا الآن بعد التسجيل في Heroku وتثبيت الأداة Heroku Toolbelt نشرُ مشروع Laravel. نبدأ بإنشاء مشروع Laravel جديد (على الحاسوب الشخصي): $ composer create-project laravel/laravel dev.herokutest.com dev-develop Installing laravel/laravel (dev-develop 083db95...dac46617) - Installing laravel/laravel (dev-develop develop) Cloning develop ... Compiling views Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? Y Application key [9UCBk7IDjvAGrkLOUBXw43yYKlymlqE3Y] set successfully. ثم ننشئ ملفا باسم Procfile ونضعه في المجلد الجذر لمشروع Laravel. طريقة كتابة اسم الملفّ مهمة جدا (الحرف الأول كبير، ولا وجود لامتداد للملف). يقرأ Heroku هذا الملفّ لتحديد نوع العمليات التي سيجريها بعد نشر التطبيق. في حالة تطبيق Laravel فإن نوع العمليات المطلوب هو وِب web. نحدّد نوع خادوم الوِب المستخدم لتقديم التطبيق (Apache) والمجلّد الذي توجد به ملفات التطبيق المخصّصة للعرض، وهو في حالة Laravel المجلد public. يصبح ملفّ Procfile على النحو التالي: web: vendor/bin/heroku-php-apache2 public هذا فقط مثال على الخيارات المتاحة، يمكنك تغيير هذه الخيارات (خادوم الوِب مثلا) إن أردت ذلك. الخطوة الموالية لإنشاء ملفّ Procfile هي وضع المشروع تحت تصرّف نظام إدارة الإصدارات Git بإنشاء مستودع للمشروع: $ git init Initialized empty Git repository in /home/zeine77/dev.herokutest.com/.git/ $ git add . $ git commit -m "First commit" يسهّل استخدامُ Git كثيرا عمليةَ النشر، فإطار العمل Laravel يأتي مبدئيا بميزات خاصّة بنظام إدارة الإصدارات Git (مثل ملفات gitignore.)؛ كما أن Heroku أيضا يتفاعل مع مستودع Git المحلي لتسهيل النشر. راجع هذه المقالات لمعرفة المزيد عن Git. نحن الآن جاهزون لنشر التطبيق على Heroku. نستخدم Heroku Toolbelt لهذه المهمة: $ heroku create Heroku CLI submits usage information back to Heroku. If you would like to disable this, set `skip_analytics: true` in /home/zeine77/.heroku/config.json Creating app... done, ⬢ nameless-chamber-90421 https://nameless-chamber-90421.herokuapp.com/ | https://git.heroku.com/nameless-chamber-90421.git ينشئ الأمر heroku create اسما جديدا لمشروعك (أعطاني الاسم nameless-chamber-90421) ويعرّف رابطا يمكن عبره الوصول إلى التطبيق. لا يوجد على الرابط - لحد السّاعة - سوى صفحة مبدئية من Heroku. زيادة على الرابط والاسم، أنشأ الأمر السابق مستودع Git بعيدا. مستودع Git بعيد هو مستودع يضم ملفات مشروعك ولكنه يوجد في مكان آخر غير جهازك؛ يمكن دفع التغييرات إلى المشروع البعيد أو جلبها منه. سنكتفي في حالة Heroku بدفع التعديلات إلى المستودع البعيد، كما سنرى بعد قليل. يستخدم Heroku ملفات تسمى buildpacks (حزم بناء) لمعرفة البرامج التي يجب عليه إعدادها على الخادوم بعد تثبيت التطبيق؛ لذا يجب أن نحدّد واحدا. اخترنا ملفّ buildpack الرسمي من Heroku الخاص بتطبيقات PHP؛ نستخدم الأمر config:add في Heroku Toolbelt لتحديد هذا الملف: $ heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-php Setting BUILDPACK_URL and restarting ⬢ nameless-chamber-90421... done, v3 BUILDPACK_URL: https://github.com/heroku/heroku-buildpack-php تستخدم تطبيقات Laravel مفتاح تعميّة Encryption key لتعمية معلومات جلسة المستخدم ومعلومات أخرى؛ توجد قيمة هذا المفتاح في متغيّر البيئة APP_KEY. يوجد المتغيّر APP_KEY في الملفّ env.، إلا أن الملف env. لا يدخل في إطار الملفات التي يتعامل معها Git، نظرا لوجوده في الملفات المحدّدة في gitignore.. سنحتاج إذن لتوليد مفتاح تعميّة لاستخدامه على خادوم Heroku. تُستخدم أداة artisan لتوليد مفتاح تعمية على النحو التالي: php artisan key:generate --show يولّد الأمر أعلاه مفتاح تعميّة ويعرضه على سطر الأوامر؛ إلا أننا نريد أن نخزّن المفتاح على الخادوم؛ نستخدم Toolbelt لهذا الغرض على النحو التالي: heroku config:set APP_KEY=$(php artisan --no-ansi key:generate --show) Setting APP_KEY and restarting ⬢ nameless-chamber-90421... done, v7 APP_KEY: base64:jp40IC7SV5eJ5IhjQYwqk/KXJG0uS+ZhCqSGwkwgELs= يضبط الأمر السابق قيمة المتغيّر APP_KEY على الخادوم لتساوي نتيجة تنفيذ الأمر php artisan --no-ansi key:generate --show. حان الآن وقت النشر فعليا؛ ندفع التغييرات في المستودع المحلي إلى المستودع البعيد باستخدام Git على النحو التالي: $ git push heroku master Counting objects: 103, done. Delta compression using up to 4 threads. Compressing objects: 100% (84/84), done. Writing objects: 100% (103/103), 43.87 KiB | 0 bytes/s, done. Total 103 (delta 5), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Fetching set buildpack https://github.com/heroku/heroku-buildpack-php... done remote: -----> PHP app detected remote: remote: ! WARNING: Your 'composer.json' contains a non-'stable' setting remote: for 'minimum-stability'. This may cause the installation of remote: unstable versions of runtimes and extensions during this deploy. remote: It is recommended that you always use stability flags instead, remote: even if you have 'prefer-stable' enabled. For more information, remote: see https://getcomposer.org/doc/01-basic-usage.md#stability remote: remote: -----> Bootstrapping... remote: -----> Installing platform packages... remote: - php (7.0.7) remote: - ext-mbstring (bundled with php) remote: - apache (2.4.20) remote: - nginx (1.8.1) remote: -----> Installing dependencies. (...) remote: -----> Compressing... remote: Done: 15.5M remote: -----> Launching... remote: Released v4 remote: https://nameless-chamber-90421.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/nameless-chamber-90421.git * [new branch] master -> master إن نظرت إلى لوحة التحكّم في Heroku ستجد سجلا بالإجراءات السابقة. هذا كلّ ما في الأمر؛ اكتمل نشرُ تطبيقك الآن ويمكنك الوصول إليه على الرابط المذكور أعلاه. يجب أن تظهر صفحة Laravel المبدئية. تذكّر أن الرابط المنشَأ سابقا موجود فقط لأغراض الاختبار والتجربة، عندما يكون تطبيقك جاهزا لتلقي الجماهير يمكنك استخدام نطاقك الخاص وربطه بالتطبيق. تهجير قاعدة البيانات نشرنا في الخطوات السابقة تطبيق Laravel جديدا؛ إلا أن هذه الخطوات لن تكون كافية إن كان التطبيق يعتمد على قاعدة بيانات. ستحتاج في هذه الحالة إلى التأكد من تنفيذ جميع التهجيرات العالقة بعد كلّ عملية نشر. بما أن التطبيق جديد على Heroku فسنحتاج لتموين Provision قاعدة البيانات؛ نستخدم Toolbelt لهذا الغرض: $ heroku addons:create heroku-postgresql:hobby-dev Creating postgresql-rigid-59415... done, (free) Adding postgresql-rigid-59415 to nameless-chamber-90421... done Setting DATABASE_URL and restarting nameless-chamber-90421... done, v10 Database has been created and is available ! This database is empty. If upgrading, you can transfer ! data from another database with pg:copy Use `heroku addons:docs heroku-postgresql` to view documentation. ينشئ الأمر السابق قاعدة بيانات PostgreSQL معرّفة بالخطة المجانيّة hobby-dev التي تمكننا من إنشاء قاعدة بيانات مجانية على Heroku، على ألا يتجاوز عدد أسطرها العشرة آلاف. توجد خطط أخرى يمكن النظر فيها لإيجاد ما يناسبك إن كنت تريد استخدام Heroku بيئةً للإنتاج. يعود السبب في اختيارنا لقاعدة بيانات PostgreSQL بدلا من MySQL إلى أن دعم الأخيرة على Heroku محدود، ستجد لهذا السبب أن الكثير من التوثيق حول خدمة Heroku يعتمد على قاعدة البيانات PostgreSQL. ثم نفذ الأمر التالي للحصول على المزيد من التفاصيل عن قاعدة البيانات ومن ضمنها الاستيثاق (تأكد من اسم التطبيق): $ heroku config --app nameless-chamber-90421 | grep DATABASE_URL DATABASE_URL: postgres://USERNAME:PASSWORD@HOSTNAME:PORT/DATABASE يظهر في نتيجة الأمر اسمُ المستخدم USERNAME، كلمة السّر PASSWORD، اسم المضيف HOSTNAME، المنفذ PORT واسم قاعدة البيانات DATABASE. لن يكون ضروريا حفظ هذه الإعدادات فالمتغير DATABASE_URL الذي يخزّنها محفوظ تلقائيا في إعدادات الخادوم. يمكنك تسهيل إدارة قاعدتي البيانات على طرفيْ العمل (بيئة التطوير وبيئة الإنتاج) بضبط متغيّر بيئة باسم DATABASE_URL على جهازك المحلي وتخزين إعدادات قاعدة البيانات فيه ثم استخدامه لإعداد اتصال Laravel بقاعدة البيانات. ستحتاج لتثبيت PostgreSQL على جهازك إن لم يكن مثبتا مسبقا. يستعمل Laravel مبدئيا قاعدة بيانات MySQL، لذا يجب أن نعدّه لاستخدام PostgreSQL. نفتح ملفّ إعداد الاتصال بقاعدة البيانات config/database.php ونبحث عن السطر التالي: 'default' => env('DB_CONNECTION', 'mysql'), ثم نعدّله على النحو التالي: 'default' => env('DB_CONNECTION', 'pgsql'), ثم ننتقل إلى المقطع الخاصّ بإعداد PostgreSQL ضمن ملفّ إعداد الاتصال ونعدّله ليصبح كالتالي: 'pgsql' => [ 'driver' => 'pgsql', 'host' => parse_url(getenv("DATABASE_URL"))["host"], 'database' => substr(parse_url(getenv("DATABASE_URL"))["path"], 1), 'username' => parse_url(getenv("DATABASE_URL"))["user"], 'password' => parse_url(getenv("DATABASE_URL"))["pass"], 'charset' => 'utf8', 'prefix' => '', 'schema' => 'public', ], احفظ التعديلات ثم تأكد من أن اتصال التطبيق بقاعدة البيانات (محليّا)؛ يمكنك إنشاء نموذج في Laravel وتهجير مرافق له لغرض اختبار الاتصال بقاعدة البيانات. نفذ بعد التأكد من إعداد قاعدة البيانات التعديلات التي أضفتها إلى منطقة الإدارج في Git ثم أودعها في المستودع وأرسلها إلى المستودع البعيد على الخادوم: $ git add . $ git commit -m "Updated database configuration" $ git push heroku master يمكنك الآن تنفيذ التهجير على قاعدة البيانات على الخادوم عن طريق Toolbelt كالتالي: $ heroku run php artisan migrate --app nameless-chamber-90421 Running `php artisan migrate` attached to terminal... up, run.6981 ************************************** * Application In Production! * ************************************** Do you really wish to run this command? [y/N] y Migration table created successfully. Migrated: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_100000_create_password_resets_table Migrated: 2015_01_30_032004_create_todolists_table.php ترجمة -وبتصرّف- للمقال Deploying a Laravel Application to Heroku لصاحبه W. Jason Gilmore.
  10. إن كنت تخطّط لتطوير مشروع على Laravel فإطار العمل - ولغة البرمجة PHP - لن يكونا كلّ ما تحتاجه للبدء في العمل؛ بل ستكون بحاجة إلى برنامج لإدارة قاعدة البيانات (مثلا MySQL أو PostgreSQL)، خادوم وِب (Nginx أو Apache) وربما تقنيات أخرى حسب الحاجة. قد يمثّل إعداد بيئة عمل بكلّ هذه التقنيات الكثير من العمل المرهق بالنسبة لك؛ خصوصا إن كنت ممّن يفضّل كتابة الشفرة البرمجية بدلا من التعامل مع مشاكل الإعدادات. مثّل ظهور الآلات الافتراضية Virtual machines, VM وانتشارها في السنوات الأخيرة فرصة للتغلب على الكثير من مشاكل الإعدادات. الآلات (الأجهزة) الافتراضية هي مجموعة برمجيات تعمل على نظام تشغيل لمحاكاة عمل جهاز فعلي. يمكن بهذه الطريقة مثلا، تشغيل أوبونتو لينكس داخل وندوز أو العكس. كما تمكن تجربة نظام مخصّص للخواديم داخل حاسوب شخصي؛ زيادة على أمور أخرى كثيرة. تتيح الآلات الافتراضية إعداد نظام تشغيل مخصّص، تثبيت برامج عليه، إعدادها؛ ثم توزيع النظام بعد ذلك للمهتمين للبدء في استعماله كما هو. استفاد مطورو Laravel من تقنية الآلات الافتراضية وأنشؤوا نظاما مخصّصا معدّا بجميع ما تحتاجه للبدء في التطوير على Laravel؛ أعطى فريق Laravel لهذه الآلات الافتراضية اسم Homestead. تستخدم Homestead برنامج إدارة الإعدادات Vagrant لتشغيلها. تأتي آلة Homestead معدّة بنظام تشغيل أوبونتو 14.04، لغة البرمجة PHP 7، خادوم وِب Nginx، نظامي إدارة قواعد بيانات MySQL وPostgreSQL وأدوات متفرقة أخرى. لا يتطلّب إعداد Vagrant لتشغيل Homestead الكثير من الوقت، كما أنه يعمل على OSX، لينكس ووندوز. تثبيت Homestead الخطوة الأولى لتثبيت Homestead هي تثبيت VirtualBox وVagrant على التوالي. يمكن من موقعي البرنامجيْن الحصولُ على مثبّتات Installers سهلة لأنظمة التشغيل المختلفة. افتح بعد تثبيت VirtualBox وVagrant سطر الأوامر ونفّذ الأمر التالي (قد يستغرق تنزيل الآلة الافتراضية وقتا، حسب سرعة اتصالك بالإنترنت): $ vagrant box add laravel/homestead ==> box: Loading metadata for box 'laravel/homestead' box: URL: https://atlas.hashicorp.com/laravel/homestead This box can work with multiple providers! The providers that it can work with are listed below. Please review the list and choose the provider you will be working with. 1) virtualbox 2) vmware_desktop Enter your choice: 1 ==> box: Adding box 'laravel/homestead' (v0.4.4) for provider: virtualbox box: Downloading: https://vagrantcloud.com/laravel/boxes/homestead/versions/0.4.4/providers/virtualbox.box ==> box: Successfully added box 'laravel/homestead' (v0.4.4) for 'virtualbox'! يثبت الأمر أعلاه صندوق Homestead. يُستخدَم المصطلح صندوق Box للإشارة إلى حزمة في Vagrant. يعدّ مجتمع Vagrant آلات افتراضية بأنظمة تشغيل ثُبتت عليها برامج مختلفة - حسب الغرض - لتكون الآلة جاهزة للعمل فورا. يمكنك الحصول على لائحة بالآلات الافتراضية الشائعة الاستخدام التي يشرف مجتمع Vagrant على صيانتها عبرهذا الرابط. ثم ننتقل لتثبيت Homestead باستنساخ مستودع Github في المجلّد الشخصي للمستخدم كالتالي: cd ~ git clone https://github.com/laravel/homestead.git Homestead ثم ننفذ، بعد نسخ المستودع، الأمر التالي الذي ينشئ ملفّ الإعداد Homestead.yaml ضمن مجلّد مخفي باسم homestead.: bash init.sh ملحوظة: يجب تنفيذ الأمر من داخل المجلد Homestead. إعداد Homestead تحدّد التعليمة provider في ملفّ الإعداد homestead/Homestead.yaml. المزوّد الذي سيستخدمه Vagrant؛ إما virtualbox أو vmware_desktop. في حالتنا اخترنا virtualbox. الخطوة التالية هي إعداد مجلّد المشروع الذي ستتشاركه مع الآلة الافتراضية. تتطلّب مشاركةُ مجلد المشروع تحديد مكان مفتاح SSH العمومي الخاص بك. تُستخدَم التعمية Encryption المعتمدة على المفاتيح العموميّة والخصوصيّة لتأمين تشارك المجلّد مع الآلة الافتراضية. افتح ملفّ Homestead.yaml الموجود في مجلّد homestead. وحدّد مكان السّطر التالي: authorize: ~/.ssh/id_rsa.pub إن كنت تعمل على لينكس أو OS X فلن تحتاج - على الأرجح - للتعديل على هذا السطر، نظرا لكون الملفات الخاصة بمفاتيح SSH تُخزَّن عادة في مجلّد باسم ssh. ضمن مجلّد المستخدم. إن استخدمتَ مجلدا مخصّصا لمفاتيح SSH فسيتوجّب عليك تحديث المسار بمقتضى ذلك. أما إذا كنت تستخدم وندوز فستحتاج إلى تحديث السطر ليوافق صيغة المسارات في وندوز، مثلا كالتالي: authorize: c:/Users/wjgilmore/.ssh/id_rsa.pub ملحوظة: استخدم برنامج PuTTY على وندوز لتوليد مفاتيح التعميّة. نحتاج الآن لتعديل قائمة المجلدات في ملفّ إعداد Homestead لتعيين مسار التطبيق. توجد فقرتان في ملفّ الإعدادات تتوليان التعامل مع مجلدات التطبيق وهما folders وsites. تبدو الفقرتان مبدئيا على النحو التالي: folders: - map: ~/Code to: /home/vagrant/Code sites: - map: homestead.app to: /home/vagrant/Code/Laravel/public تعرفّ الخاصيّة map في الفقرة folders المسار الذي توجد عليه ملفات مشروع Laravel الذي تعمل عليه. القيمة المبدئية لهذه الخاصيّة هي Code/~؛ بمعني أن Homestead يتوقّع وجود ملفات مشروعك ضمن مجلّد باسم Code متفرّع من المجلّد الشخصي للمستخدم. يمكنك تغيير قيمة هذه الخاصيّة حسب رغبتك؛ لكن تذكر أن المسار يجب أن يحيل إلى المجلد الجذر لمشروع Laravel. بالنسبة للخاصية to في الفقرة folders فتعرّف مسار المجلّد الذي ستُزامن معه محتويات المجلّد المعرَّف في الخاصيّة map السابقة. يمكن للآلة الافتراضية قراءة محتويات المجلد المعرَّف على المسار to عكس محتويات المجلّد المعرَّف في map. نتيجة لهذا الإعداد يُزامن محتوى المجلد الأخير (المعرَّف في to) تلقائيا مع محتوي المجلّد الأول (المعرَّف في map)؛ لتتمكن الآلة الافتراضية من قراءته. ملحوظة: من الأفضل ألا تحتوي أسماء المجلدات على مسافات. توجد في الفقرة sites خاصيتان تحملان نفس الاسميْن السابقيْن، map وto. تعرّف الخاصيّة map في الفقرة sites اسم النطاق الذي ستستخدمه للوصول إلى التطبيق؛ في المثال أعلاه تصل إلى التطبيق بكتابة homestead.app في شريط عنوان المتصفح. أما الخاصيّة to فتعرّف مجلّد الوِب الجذر في مشروع Laravel، وهو مبدئيا المجلّد public الموجود في المجلّد الجذر للمشروع. ملحوظة: المسارات المحدّدة في خاصيّة to في كلتا الحالتين هي بالنسبة لمجلدات موجودة داخل الآلة الافتراضية. سننشئ مجلّدا باسم app.homestead نخصّصه للمشروع الذي سنعمل عليه؛ نضيف مسار هذا المجلّد إلى الخاصية map في الفقرة folders التي تصبح كالتالي: folders: - map: /home/zeine77/Documents/projects/app.homestead to: /home/vagrant/Code ثم ننشئ داخل المجلد app.homestead مجلدا باسم Laravel وداخل هذا الأخير مجلدا باسم public حتى نوافق مسار المجلد home/vagrant/Code/Laravel/public/ الذي يشير إليه اسم النطاق (الخاصيّة to في الفقرة sites). ملحوظة: تذكّر أن المجلّد home/vagrant/Code/ هو نسخة طبق الأصل من المجلّد home/zeine77/Documents/projects/app.homestead/، ولكنه موجود داخل الآلة الافتراضية. يعني هذا أن المجلّد /Laravel/public المستخدَم في الخاصيّة to من الفقرة sites (القيمة /home/vagrant/Code/Laravel/public/) يجب أن يكون موجودا في الأصل (أي في المجلّد home/zeine77/Documents/projects/app.homestead/). هذا هو السبب الذي جعلنا ننشئ المجلد Laravel/public بالطريقة المذكورة أعلاه. سننشئ الآن داخل المجلّد public ملفا باسم index.php ونضع فيه الشفرة التالية: <?php echo "Hello from Homestead!"; ?> احفظ الملفّ ثم أغلقه. يعني هذا أننا عند تشغيل الآلة الافتراضية والدخول إلى العنوان homestead.app ستظهر لدينا العبارة !Hello from Homestead. الخطوة الأخيرة قبل الانتقال لتشغيل الآلة الافتراضية هي تعديل ملف المضيفات ليتعرّف النظام على العنوان homestead.app. يوجد ملف المضيفات على المسار etc/hosts/ بالنسبة للينكس و C:\Windows\System32\drivers\etc\hosts بالنسبة لوندوز. أضف السّطر التالي إلى الملفّ: 192.168.10.10 homestead.app ملحوظة: تأكّد من موافقة العنوان (192.168.10.10 في السطر أعلاه) لعنوان IP الموجود في بداية الملفّ Homestead.yaml. حان الوقت - بعد إكمال الخطوات المشروحة أعلاه - لتشغيل الآلة الافتراضية وتجربة عمل الإعدادات. ننتقل إلى المجلد Homestead (حيثُ نسخنا المشروع من Github) ثم ننفّذ الأم التالي: $ vagrant up يستغرق تشغيل الآلة الافتراضية بعض الوقت وتظهر مخرجات الأمر: Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'laravel/homestead'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'laravel/homestead' is up to date... (...) ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat default: Adapter 2: hostonly ==> default: Forwarding ports... default: 80 (guest) => 8000 (host) (adapter 1) default: 443 (guest) => 44300 (host) (adapter 1) default: 3306 (guest) => 33060 (host) (adapter 1) default: 5432 (guest) => 54320 (host) (adapter 1) default: 22 (guest) => 2222 (host) (adapter 1) (...) ==> default: nginx stop/waiting ==> default: nginx start/running, process 1940 ==> default: php7.0-fpm stop/waiting ==> default: php7.0-fpm start/running, process 1959 (...) ==> default: Running provisioner: shell... default: Running: /tmp/vagrant-shell20160628-13381-s5ikfi.sh يمكنك الآن فتح المتصفّح وتجربة عمل الإعدادت بزيارة الرابط http:homestead.app. إن جرت الأمور على ما يُرام فستظهر العبارة التالية في المتصفّح: Hello from Homestead ! إن تعذّر تشغيل الآلة الافتراضية (خطأ في مخرجات الأمر vagrant up) فتأكد من ملف الإعداد Homestead.yaml. إن اشتغلت الآلة ولم تستطع إظهار محتوى الصفحة فتأكد من إعدادات المضيف، وخصوصا عنوان IP وموافقته للعنوان الموجود في ملفّ الإعداد Homestead.yaml؛ ينبغي التأكد كذلك من المسارات ومن حالة كتابة الأحرف (كبيرة أو صغيرة). أنشأنا المجلّد Laravel/public وأضفنا إليه الملفّ index.php للتأكد من أن Homestead يعمل من دون مشاكل؛ لكن هدفنا هو استخدام Homestead لعرض مشاريع بـLaravel؛ وهو ما سنعدّه بعد قليل. يمكننا الآن توقيف عمل الآلة الافتراضية بتنفيذ الأمر (من داخل المجلد Homestead): $ vagrant destroy --force ==> default: Forcing shutdown of VM... ==> default: Destroying VM and associated drives... ثم حذف المجلّد Laravel (وبالتالي المجلد public المتفرع منه). الولوج إلى الآلة الافتراضية عن طريق SSH نظرا لكون Homestead مبني على توزيعة أوبونتو فيمكن الولوج إليه عن طريق SSH، تماما كما هي الحال بالنسبة لأي خادوم آخر. قد تودّ الدخول إلى الآلة الافتراضية لأسباب منها على سبيل المثال تغيير إعدادات قاعدة البيانات أو خادوم الوِب، إضافة برامج جديدة أو لإجراء تعديلات أخرى. استخدم الأمر التالي، بعد تشغيل الآلة الافتراضية ، للدخول عن طريق SSH: $ vagrant ssh تظهر بعد تنفيذ الأمر رسالة ترحيبية ومحثّ Prompt أوامر: Welcome to Ubuntu 14.04.4 LTS (GNU/Linux 3.19.0-25-generic x86_64) * Documentation: https://help.ubuntu.com/ vagrant@homestead:~$ سيُسجَّل دخولك باسم المستخدم vagrant؛ يمكنك بعدها تنفيذ أوامر لينكس حسب الحاجة. ستجد أنه يوجد مجلّد باسم Code داخل المجلّد الشخصي للمستخدم vagrant. نفّذ الأمر exit إن أردت الخروج من الآلة الافتراضية. إنشاء تطبيق Laravel تأكدنا في الخطوات السابقة من إعداد Homestead وأنه جاهز للعمل. يمكننا الآن إنشاء مشروع Laravel على Homestead. لذا سنستخدم composer لإنشاء مشروع Laravel جديد داخل المجلّد app.homestead. اختر اسما مناسبا للمشروع؛ مثلا demo: $ composer create-project laravel/laravel demo لدينا الآن مشروع Laravel على المسار home/zeine77/Documents/projects/app.homestead/demo/؛ ينبغي أن نعدّل ملفّ الإعداد Homestead.yaml ليشير إلى هذا المجلد. نفتح الملفّ لتحريره؛ ونغيّر قيمة الخاصيّة to ضمن الفقرة sites. القيمة القديمة هي التالية (أعددناها في خطوة سابقة): sites: - map: homestead.app to: /home/vagrant/Code/Laravel/public لتصبح كالتالي: sites: - map: homestead.app to: /home/vagrant/Code/demo/public احفظ الملفّ ثم أغلقه. نعود لمجلّد Homestead وننفذ الأمر التالي لاعتماد التعديلات (إن كانت الآلة الافتراضية تعمل): $ vagrant provision أما إن لم تكن الآلة الافتراضية تعمل فيجب تشغيلها: $ vagrant up انتظر اعتماد التعديلات أو تشغيل الآلة الافتراضية؛ ثم توجّه إلى المتصفّح وأدخل العنوان http://homestead.app، وستظهر الصفحة المبدئية لمشروع Laravel. خاتمة لست ملزما بمشروع Laravel واحد على آلة Homestead؛ بل يمكنك إنشاء العدد الذي تريد. كل ما يجب عليك فعله هو تحديث قيم map وto في فقرتي folders وsites من ملف الإعداد Homestead.yaml بالمسارات المناسبة. يمكن مثلا اتخاذ مجلد لمشاريع Laravel تستخدمه لإنشاء المشاريع الجديدة داخله ثم تعدّ الخاصيّة map في folders للإشارة إلى المجلد المشترك؛ وعند إضافة مشروع جديد تضيف نطاقا جديدا له في sites؛ مع تحديث قيمة to لتشير إلى المجلد الموافق له في المجلد المشترك كما في المثال التالي: folders: - map: /home/zeine77/Documents/projects/app.homestead to: /home/vagrant/Code sites: - map: homestead.app to: /home/vagrant/Code/demo/public - map: anotherhomestead.app to: /home/vagrant/Code/anotherdemo/public لا تنس تحديث ملف المضيفات (hosts) بإضافة اسم النطاق الجديد (anotherhomestead.app) في المثال أعلاه: 192.168.10.10 anotherhomestead.app ترجمة - بتصرّف - لمقال Installing and Configuring Homestead 2.0 for Laravel 5. حقوق المقال محفوظة لصاحبه W. Jason Gilmore.
  11. هيأنا في الجزأ الأوّل من هذا الدرس قاعدة البيانات والتهجيرات، وأعددنا أساس التوجيه والمخطّط العام للقوالب والعروض. نكمل في هذا الجزأ الثاني إعداد بقية العناصر اللّازمة لبناء تطبيق إدارة مهام قاعدي. إضافة المهامّ التحقّق لدينا الاآن استمارة في العرض resources/views/tasks/index.blade.php؛ سنحتاج لإضافة شفرة إلى الدالة TaskController@store من أجل التحقّق من المُدخلات القادمة من الاستمارة ثم إنشاء مهمة جديدة. نبدأ بالتحقّق من المدخلات. سنضع شرطا على حقل الاسم في استمارة إضافة مهمّة جديدة؛ يتمثّل الشرط في وجوب إدخال اسم للمهمة، على ألا يتجاوز طوله 255 محرفا Characters. إن لم يتحقّق الشرط نعيد المستخدم إلى الرابط tasks/ مع إضاءة المُدخلات القديمة وإظهار أخطاء: /** * Create a new task. * * @param Request $request * @return Response */ public function store(Request $request) { $this->validate($request, [ 'name' => 'required|max:255', ]); // شفرة إنشاء المهمة } تتضمّن المتحكّمات في Laravel مبدئيا السمة ValidatesRequests التي يمكن عن طريقها التحقّق من موافقة المُدخلات لشروط تُحدَّد في مصفوفة من القواعد. تأخذ دالة التحقّق validate الطّلب request$ الذي نريد التحقّق منه ومصفوفة القواعد التي نريد تطبيقها. تشير الشفرة أعلاه إلى أن الحقل name مطلوب required ويجب ألا يتجاوز طوله 255 (الشّرط max). لن نحتاج لتحديد ما الذي سيحصُل عند إخفاق التحقّق من الطلب (مثلا عند كتابة اسم يتجاوز طوله 255 محرفا). يُوجَّه المستخدَم تلقائيا عند إخفاق التحقّق إلى المسار الذي أتى منه وستُعرَض رسائل الخطأ تلقائيا. المتغيّر errors$ تذكّر أننا استخدمنا التعليمة ('include('common.errors@ داخل العرض tasks.index لتقديم أخطاء التحقّق عبر العرض common.errors. يمكّننا العرض common.errors من إظهار أخطاء التحقّق بسهولة وحسب نفس التنسيق في كامل التطبيق. نعرّف في ما يلي العرض resources/views/common/errors.blade.php: @if (count($errors) > 0) <!-- Form Error List --> <div class="alert alert-danger"> <strong>Whoops! Something went wrong!</strong> <br><br> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif ملحوظة: يتوفّر المتغيّر errors$ في كلّ عرض في Laravel. يحوي المتغيّر كائنا فارغا من الصّنف ViewErrorBag إن لم توجد أخطاء. إنشاء المهمّة ضبطنا في الفقرة السابقة آلية التحقّق؛ يمكننا الآن إكمال الدالة store في المتحكم TaskController من أجل إنشاء مهمّة جديدة. نوجِّه المستخدم بعد إنشاء المهمّة إلى الرابط tasks/. سنلجأ إلى الإمكانيّات التي توفّرها علاقات Eloquent لإنشاء المهمّة. تتوفّر أغلب علاقات Eloquent على دالة باسم create تأخذ مصفوفة من الخاصيّات وتضبط تلقائيا قيمة المفتاح الخارجي Foreign key بالاعتماد على النماذج التي تربط بينها العلاقة، ثم تنشئ سطرا جديدا في جدول البيانات لتخزين خاصيّات الكائن. تستخدم الشفرة التالية متغيّرالطلب request$ للحصول على المستخدم الذي طلب إنشاء المهمة ()request->user$ ثم باستخدام الدالة create في العلاقة tasks (عرّفناها سابقا في النموذج User) نعطي القيمة request->name$ (أي الاسم الّذي أدخله المستخدم في الاستمارة) للخاصيّة name في النموذج Task. ستتولّى العلاقة ()tasks تحديد قيمة معرّف المستخدم صاحب المهمة بالاعتماد على المعلومات الواردة في الطلب عن المستخدم. /** * Create a new task. * * @param Request $request * @return Response */ public function store(Request $request) { $this->validate($request, [ 'name' => 'required|max:255', ]); $request->user()->tasks()->create([ 'name' => $request->name, ]); return redirect('/tasks'); } لاحظ كيف أن تعريف العلاقات أتاح لنا إمكانيّة فعل الكثير من الأمور بسطر برمجي واحد تقريبا. عرض المهامّ المخزّنة سنحتاج للتعديل على الدالة index في المتحكّم TaskController من أجل تمرير المهامّ المخزّنة إلى العرض. تقبل الدالة view إضافة إلى اسم العرض، معطى ثانيا يحوي مصفوفة من البيانات المتوفّرة للعرض. يمكن معالجة كلّ عنصر من المصفوفة بعد تمريرها إلى العرض باستخدام متغيّر بنفس الاسم، مثلا: /** * Display a list of all of the user's task. * * @param Request $request * @return Response */ public function index(Request $request) { $tasks = $request->user()->tasks()->get(); return view('tasks.index', [ 'tasks' => $tasks, ]); } لكننا سنختار طريق حقن الاعتمادات التي يوفرها Laravel. حقن الاعتمادات يستخدم Laravel ، كما أشرنا لذلك في درس إنشاء مزود خدمة في Laravel حاويةَ خدمة Service container لإدارة الاعتمادات بين الأصناف: ماهي الأصناف التي يعتمد عليها الصّنف الجديد للعمل؟ كيف يمكنه الحصول على كائن من هذه الأصناف؟ أين يوجد الصّنف وهل سبق لإطار العمل تحميلُه إلى الذاكرة؟ إنشاء مستودع في Laravel تمكّن المستودعات Repositories من تخليص المتحكّم من ضرورة معرفة مصدر البيانات؛ إذ تجرّد المصدر الذي تأتي منه (قاعدة بيانات، ملفّ، واجهة تطبيقات API). تساعد المستودعات - بهذه الحيلة - في زيادة قابليّة التطبيق للصيانة والاختبار. نودّ أن نعرّف مستودعا باسم TaskRepository للوصول إلى بيانات نموذج المهمة Task. تفيد هذه الطريقة كثيرا عند تزايد حجم المشروع والحاجة لتشارك استعلامات Eloquent عبر التطبيق. سننشئ مجلدا باسم Repositories في المجلّد app. تذكّر أن Laravel يحمّل تلقائيا جميع مجلدات التطبيق؛ لذا يمكنك إنشاء المجلدات حسب الحاجة.ننشئ داخل مجلد المستودعات الصنف TaskRepository: <?php namespace App\Repositories; use App\User; class TaskRepository { /** * Get all of the tasks for a given user. * * @param User $user * @return Collection */ public function forUser(User $user) { return $user->tasks() ->orderBy('created_at', 'asc') ->get(); } } حقن المستودع يمكننا الآن تمرير المستودع، بعد استدعائه، إلى مشيّد المتحكم TaskController ثم استخدامه في الدالة index. تصبح شفرة المتحكّم TaskController على النحو التالي: <?php namespace App\Http\Controllers; use App\Task; use App\Http\Requests; use Illuminate\Http\Request; use App\Http\Controllers\Controller; // استدعاء المستودع use App\Repositories\TaskRepository; class TaskController extends Controller { /** * The task repository instance. * * @var TaskRepository */ protected $tasks; /** * Create a new controller instance. * * @param TaskRepository $tasks * @return void */ // التمرير إلى المتحكّم public function __construct(TaskRepository $tasks) { $this->middleware('auth'); $this->tasks = $tasks; } /** * Display a list of all of the user's task. * * @param Request $request * @return Response */ public function index(Request $request) { // الاستخدام في الدالة return view('tasks.index', [ 'tasks' => $this->tasks->forUser($request->user()), ]); } /** * Create a new task. * * @param Request $request * @return Response */ public function store(Request $request) { $this->validate($request, [ 'name' => 'required|max:255', ]); $request->user()->tasks()->create([ 'name' => $request->name, ]); return redirect('/tasks'); } } إظهار المهامّ يمكننا، بعد تمرير البيانات إلى العرض tasks/index.blade.php من المتحكّم، إظهار المهامّ في جدول. تُستخدَم التعليمة foreach@ في قوالب Blade لكتابة حلقات تكرارية يُحوّلها Laravel شفرة PHP. ملحوظة: سبق لنا في الجزأ الأول من الدرس تعريف استمارة إنشاء مهمة جديدة. توضع الشفرة أدناه مكان التعليق الأخير في قالب tasks/index.blade.php الذي أنشأناه سابقا. @if (count($tasks) > 0) <div class="panel panel-default"> <div class="panel-heading"> Current Tasks </div> <div class="panel-body"> <table class="table table-striped task-table"> <!-- عناوين الجدول --> <thead> <th>Task</th> <th> </th> </thead> <!-- المهام الموجودة --> <tbody> @foreach ($tasks as $task) <tr> <!-- اسم المهمة --> <td class="table-text"> <div>{{ $task->name }}</div> </td> <td> <!-- سنضع هنا الشفرة الخاصة بحذف مهمة --> </td> </tr> @endforeach </tbody> </table> </div> </div> @endif </div> </div> شارف التطبيق على النهاية؛ ولكن تنقصنا طريقة تمكّن المستخدم من حذف مهام أضافها سابقا. حذف المهام زرّ حذف مهمة تركنا في العرض tasks/index.blade.php تعليقا للدلالة على المكان المفترض لزرّ حذف مهمة. سنضيف الآن، لكلّ سطر في جدول المهامّ، زرّا يمكّن من حذف المهمة الموجودة في السطر. يُرسَل، عند النقر على زرّ الحذف، طلب بإجراء DELETE إلى المسار task/، يستقبل المسار الطلب ويحيله إلى الدالة destroy في المتحكّم TaskController. نعدّل العرض tasks/index.blade.php بتعديل شفرة أسطُر الجدول لتصبح على النحو التالي: <tr> <!-- اسم المهمة --> <td class="table-text"> <div>{{ $task->name }}</div> </td> <!-- زرّ حذف المهمة --> <td> <form action="{{ url('task/'.$task->id) }}" method="POST"> {{ csrf_field() }} {{ method_field('DELETE') }} <button type="submit" id="delete-task-{{ $task->id }}" class="btn btn-danger"> <i class="fa fa-btn fa-trash"></i>Delete </button> </form> </td> </tr> لاحظ أن الاستمارة في خلية الجدول حيث يوجد زرّ الحذف تستخدم الإجراء POST بينما نجيب على الطلب بمسار يستخدم الإجراء DELETE. لا تسمح الاستمارات في HTML باستخدام سوى الإجراءيْن POST وGET، أي أننا يجب أن نجد طريقة لإرسال طلب بإجراء DELETE. يكمُن الحل في تنفيذ الدالة ('method_field('DELETE داخل الاستمارة. تولّد هذه الدالة حقل إدخال input مخفيّا يتعرّف عليه Laravel ويستخدمه في إلغاء الإجراء المستخدَم في طلب HTTP. يبدو الحقل المولَّد كالتالي: <input type="hidden" name="_method" value="DELETE"> ربط النموذج والمسار نحن الآن جاهزون لتعريف الدالة destroy في المتحكّم TaskController: /** * Destroy the given task. * * @param Request $request * @param Task $task * @return Response */ public function destroy(Request $request, Task $task) { // } سنلقي، قبل أن نكمل تعريف الدالة destroy، نظرة على المسار الخاصّ بحذف مهمة: Route::delete('/task/{task}', 'TaskController@destroy'); نظرا لكون المتغيّر {task} في المسار يوافق المتغيّر task$ في دالة المتحكم فإن Laravel سيربط تلقائيا بين الاثنين ويحقن كائنا مناسبا من النموذج Task في المسار؛ أي أن الكائن سيكون متاحا للمتحكّم لتنفيذ العمليات عليه. التصريح لدى المتحكّم الآن نسخة من نموذج المهمة Task لاستخدامه في الدالة destroy، إلا أننا لسنا متأكدين من أن المستخدم هو فعلا صاحب المهمة التي طلب حذفها. يمكن، على سبيل المثال، تمرير معرّف عشوائي إلى الرابط {tasks/{task، وستحذف الدالة المهمة ذات المعرّف {task}، إن وُجدت. نستخدم قدرات إدارة التصريحات Authorization في Laravel لحلّ هذه المشكلة، والتأكد من أن المستخدَم المُسجَّل هو فعلا صاحب المهمة التي يطلُب حذفها. إنشاء سياسة تصريح يستخدم Laravel سياسات Policies لتنظيم التصريحات في أصناف صغيرة وسهلة. تتعلّق كلّ سياسة - عادة - بنموذج. نستخدم artisan لإنشاء سياسة تصريحات للنموذج Task كالتالي: php artisan make:policy TaskPolicy سينشئ الأمر ملفا باسم TaskPolicy.php على المسار /app/Policies. نضيف دالة باسم destroy إلى سياسة التصريح TaskPolicy. تتلقى هذه الدالة كائنا من صنف User وآخر من صنف Task، ثمّ تتحقّق من أن معرّف المستخدم يطابق الحقل user_id في المهمة. تُرجع الدالة destroy في الصّنف TaskPolicy قيمة منطقية (falseأو true)، كما هي الحال في جميع دوال سياسات التصريح: <?php namespace App\Policies; use App\User; use App\Task; use Illuminate\Auth\Access\HandlesAuthorization; class TaskPolicy { use HandlesAuthorization; /** * Determine if the given user can delete the given task. * * @param User $user * @param Task $task * @return bool */ public function destroy(User $user, Task $task) { return $user->id === $task->user_id; } } نحتاج الآن لإخبار Laravel بأن سياسة التصريح TaskPolicy تنطبق على النموذج Task. يتمّ هذا الأمر عبر إضافة عنصر جديد إلى خاصيّة policies$ في مزوّد خدمات الاستيثاق app/Providers/AuthServiceProvider.php. سيعرف Laravel بهذه الطريقة سياسة التصريح التي يجب عليه استخدامها للسماح بإجراء مّا (أو رفضه) على النموذج Task: /** * The policy mappings for the application. * * @var array */ protected $policies = [ 'App\Task' => 'App\Policies\TaskPolicy', ]; السماح بحذف المهمة سياسة التصريح جاهزة، ويمكننا بالتالي استخدامها في المتحكّم TaskController. يمكن لجميع المتحكمات في Laravel استخدام الدالة authorize التي توفّرها السمة AuthorizesRequest: /** * Destroy the given task. * * @param Request $request * @param Task $task * @return Response */ public function destroy(Request $request, Task $task) { $this->authorize('destroy', $task); // حذف المهمة } المعطى الأوّل المُمرَّر للدالة authorize هو اسم الدالة التي نريد استدعاءها في سياسة التصريح؛ المعطى الثاني هو الكائن الذي نريد التعامل معه. تذكّر أننا أخبرنا Laravel للتو أن سياسة التصريح TaskPolicy تنطبق على النموذج Task، أي أن إطار العمل يعرف في أي سياسة تصريح سيبحث عن الدالة التي حدّدنا اسمها في المعطى الأول المُمرَّر للدالة authorize. بالنسبة للمستخدم فإن المستخدم الحالي سيُرسَل تلقائيا إلى الدالة destroy في الصّنف TaskPolicy. إن تحقّق الشرط (المستخدم صاحب المهمة هو من طلب حذفها) فسيستمرّ تنفيذ الدالة destroy في المتحكّم TaskController بصورة طبيعية؛ وإلا فسينطلق استثناء Exception برمز الخطأ 403 (غياب التصريح) وتظهر صفحة خطأ. ملحوظة: ليست هذه هي الطريقة الوحيدة للتخاطب مع خدمات التصريح التي يوفرها Laravel. حذف المهمة أخذنا الاحتياطات اللازمة، ويمكننا الآن إضافة الشفرة الخاصة بحذف مهمة من قاعدة البيانات. نستخدم دالة delete التي تتوفّر عليها نماذج Eloquent؛ ثم نوجّه المستخدم بعد حذف المهمة إلى المسار tasks/. تصبح الدالة destroy في المتحكّم TaskController كما يلي: /** * Destroy the given task. * * @param Request $request * @param Task $task * @return Response */ public function destroy(Request $request, Task $task) { $this->authorize('destroy', $task); $task->delete(); return redirect('/tasks'); } ترجمة - بتصرّف - للجزء الثاني من مقال Intermediate Task List.
  12. كان من الشائع في بدايات التطوير للوب كتابةُ الاستمارات بوسوم HTML ثم إضافة شفرة مخصَّصة للتأكد من مُدخَلات الزائر. وفّرت لغة البرمجة PHP بعد ذلك دوالَّ معيارية للتحقّق من المُدخلات؛ خفّفت هذه الدوّال قليلا من العبء على المطوِّر إلا أنها تركت له مهمّة تعريف طريقة للتحقّق من المُدخلات ومن ثمّ معالجتها أو إشعار الزائر بالبيانات غير الصالحة وعرض الاستمارة من جديد. يزيح Laravel هذه المتاعب بتوفير شيفرة للتحقّق وطريقة للتعامل مع المُدخلات. يقدّم هذا المقال دليلا لكيفيّة إنشاء استمارات والتحقّق من مدخلات الزائر في Laravel. سنأخذ مثالا لإضافة تصنيف Category ضمن جدول تصنيفات Categories في تطبيق؛ يمكن أن يتعلّق الأمر بتصنيف منشورات مدوّنة، منتجات متجر أو أي شيء مماثل. تهيئة تطبيق Laravel نفترض أن Laravel مثبَّت وجاهز للعمل مع قاعدة البيانات. نهيّئ التطبيق قبل أن نشرع في إنشاء الاستمارة وأدوات التحقّق المصاحبة لها. إنشاء نموذج التصنيف والتهجير الموافق له سيتكون نموذج التصنيف من حقل للاسم إضافة إلى حقل المعرّف id والأختام الزمنية Timestamps. نستخدم artisan لتوليد النموذج والتهجير: $ php artisan make:model Category -m Model created successfully. Created Migration: 2016_06_13_010501_create_categories_table ثم نفتح ملفّ التهجير (داخل المجلّد database/migrations) ونحدّث الدالة up على النحو التالي: public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); } ثم ننفّذ التهجير: $ php artisan migrate Migration table created successfully. Migrated: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_100000_create_password_resets_table Migrated: 2016_06_13_010501_create_categories_table ننتقل الآن للعمل على المتحكّم Controller الذي سيكون المسؤول عن معالجة محتوى الاستمارة. إنشاء المتحكم وتعريف المسارات نستخدم أداة artisan لإنشاء المتحكّم الذي سنستخدمه - من بين أمور أخرى - لعرض النموذج ومعالجة بياناته: $ php artisan make:controller CategoriesController.php --resource Controller created successfully. ينشئ أمر make:controller عند استخدام المعطى resource--متحكّما بدوال update، edit، store، create، show``index وdestroy. راجع درس أساسيات بناء التطبيقات في إطار العمل Laravel 5 للمزيد عن هذه الدوال. بقي لنا تعريف المسارات في الملفّ app/Http/routes.php؛ نستخدم Route::resource لهذا الغرض: Route::resource('categories', 'CategoriesController'); إنشاء العروض Views نركّز لأغراض هذا الدّرس على الدالتيْن create وstore المسؤولتيْن على التوالي عن عرض الاستمارة وتخزين بياناتها (في حال تجاوز اختبار التحقّق؛ كما سنرى). تبدو الدالتان لحدّ الساعة فارغتيْن: public function create() { // } public function store(Request $request) { // } ... نحدّث الدالة create كالتالي: public function create() { return view('categories.create'); } أي أننا نطلُب تقديم العرض create.blade.php الموجود في المجلّد resources/views/categories. ننشئ المجلّد categories وننشئ بداخله الملفّ create.blade.php. سنكتفي الآن بإضافة الوسوم التالية إلى ملفّ العرض: <h1>Create a Category</h1> يمكنك بعد حفظ الملفّ زيارة الرابط categories/create/ وسيظهر العنوان أعلاه. إنشاء استمارة في Laravel نريد أن نعرض للزائر استمارة لملئها؛ يمكن أن ننشئ هذه الاستمارة بكتابة وسوم HTML المطلوبة في ملفّ العرض create؛ إلا أنه يمكننا الاستفادة من حزمة laravelcollective/html لتولّي هذه المهمّة. نضيف الحزمة إلى مشروع Laravel بتنفيذ الأمر التالي داخل مجلّد المشروع: $ composer require laravelcollective/html ثم نفتح ملفّ الإعداد config/app ونضيف السطر التالي إلى مصفوفة providers: Collective\Html\HtmlServiceProvider::class, لتبدو المصفوفة كالتالي (نزعنا بعض محتويات المصفوفة للاختصار): 'providers' => [ ... App\Providers\AuthServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, Collective\Html\HtmlServiceProvider::class, ] نكمل مع نفس الملفّ بالانتقال إلى المصفوفة aliases التي نضيف إليها السّطر التالي: 'Form' => Collective\Html\FormFacade::class لتصبح كما يلي: 'aliases' => [ ... 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, 'Form' => Collective\Html\FormFacade::class ] احفظ الملفّ. أكملنا تثبيت الحزمة وإعدادها. نعيد فتح ملفّ العرض create ونعدّل محتواه ليصبح على النحو التالي: <h1>Create a Category</h1> {!! Form::open( array( 'route' => 'categories.store', 'class' => 'form') ) !!} @if (count($errors) > 0) <div class="alert alert-danger"> There were some problems adding the category.<br /> <ul> @foreach ($errors->all() as $error) <li></li> @endforeach </ul> </div> @endif <div class="form-group"> {!! Form::label('Category') !!} {!! Form::text('name', null, array( 'class'=>'form-control', 'placeholder'=>'List Name' )) !!} </div> <div class="form-group"> {!! Form::submit('Create Category!', array('class'=>'btn btn-primary' )) !!} </div> {!! Form::close() !!} </div> تضيف حزمة laravelcollective/html تعليمات خاصّة بإنشاء الاستمارات إلى نظام Blade للقوالب. يشير مسار الاستمارة إلى categories/store لأن دالّة store في المتحكّم CategoriesController هي التي ستتولّى معالجة بيانات الاستمارة. تستخدم التعليمة Form::open مبدئيا إجراء POST؛ لذا لا حاجة لتعيينه. يُستخدَم المقطع if..@endif@ لتقديم معلومات إلى المستخدم عن أخطاء التحقق. سنفصّل هذا الأمر بعد قليل. مرّرنا لأغراض جمالية أصنافا وخاصيّات من Bootstrap إلى الاستمارة ؛ هذا ليس ضروريا وستعمل الاستمارة بدونه. تظهر الآن الاستمارة بالذهاب إلى categories/create/. يمكنك إدخال اسم تصنيف وإرسال الاستمارة وستُوجَّه إلى categories/store/ لكنّ شيئا لن يحدُث لأننا لم نكتب حتى الآن شيئا في الدالة store من المتحكّم CategoriesController. معالجة الاستمارة نعود للدالة store في المتحكم CategoriesController. هذه هي الدالة التي تُرسَل إليها بيانات الاستمارة بعد النقر على زرّ الإرسال. نحدّث المتحكّم كالتالي: use App\Category; ... public function store(Request $request) { $category = new Category; $category->name = $request->get('name'); $category->save(); return \Redirect::route('categories.show', array($category->id)); } بدأنا أولا باستيراد النموذج Category في المتحكّم ثم استخدمناه في الدالة store لتخزين تسجيلة Record جديدة في جدول قاعدة البيانات اعتمادا على محتويات الاستمارة؛ ثم بعد التخزين نعيد توجيه المستخدم إلى الدالة show ضمن المتحكّم CategoriesController مع معرّف التصنيف الذي أضفناه للتو. يمكننا تعديل الدالة show كالتالي لعرض رسالة تفيد بنجاح إضافة التصنيف: public function show($id) { // $category = Category::find($id); $message = "$category->name has been added succefully"; return $message; } من الملاحظ أننا لم نتحقّق في العمليّة السابقة من مُدخلات المستخدم. يعني هذا أنه يمكن - مثلا - ترك الحقل الخاصّ باسم التصنيف فارغا وسيُحفظ تصنيف بدون اسم في قاعدة البيانات؛ طبعا هذا غير مقبول. التحقق من الاستمارة قبل الإرسال يوفّر Laravel 5 ميزة تُسمى طلب الاستمارة Form request تسمح بالتحقق من حقول الاستمارة دون تلويث المتحكّم. يدير ملفّ منفصل قواعد التحقّق من الاستمارة؛ نستخدم أمر artisan التالي لإنشاء هذا الملفّ: $ php artisan make:request CreateCategoryFormRequest Request created successfully. ينشئ الأمر ملفا باسم CreateCategoryFormRequest.php في المجلد app/Http/Requests. يبدو الملفّ كالتالي: <?php namespace App\Http\Requests; use App\Http\Requests\Request; class CreateCategoryFormRequest extends Request { public function authorize() { return false; } public function rules() { return [ // ]; } } عدّل الدالة authorize كالتالي: public function authorize() { return true; } نحتاج لتحديد القيمة true بالنسبة للدالة authorize لأنه في حال كانت القيمة false فلن تُعالج بيانات الاستمارة. ثم نعدّل الدالة rules: public function rules() { return [ 'name' => 'required' ]; } تعرّف الدالة قواعد التحقّق؛ عرّفنا أعلاه الحقل name ضمن الاستمارة بأنه مطلوب required؛ أي أن الاستمارة لن تُرسَل إلا إذا كانت توجد قيمة لهذا الحقل. تمكن إضافة أكثر من شرط على الحقل كالتالي: 'name' => 'required|alpha' نحتاج الآن لدمج طلب الاستمارة في الدالة store ضمن المتحكّم CategoriesController. نبدأ باستيراد الصنف في المتحكّم كالتالي: use App\Http\Requests\CreateCategoryFormRequest; ثم نحدّث الدالة store ليكون الكائن الممرَّر إليها من الصّنف CreateCategoryFormRequest: public function store(CreateCategoryFormRequest $request) { ... } ابتداءً من الآن سيتحقّق Laravel من بيانات الحقل قبل إرسالها؛ وفي حال الإخفاق في التحقق من الشرط سيعيد الزائر إلى صفحة الاستمارة مع عرض رسالة بوجود خطأ. ترجمة -وبتصرّف- للمقال Creating and Validating a Laravel 5 Form: The Definitive Guide. حقوق المقال محفوظة لصاحبه W. Jason Gilmore.
  13. يقدّم دليل الانطلاق السريع هذا، المكوّن من جزأين، مقدّمة لإطار العمل Laravel. سنغطّي خلال هذه المقدّمة مواضيع تهجير قواعد البيانات Database migrations، ربط كائنات التطبيق بجداول قاعدة البيانات باستخدام Eloquent، التوجيه، الاستيثاق Authentication، التصريح Authorization، حقن الاعتمادات، التحقّق، العروض Views وقوالب Blade. هذه المقدّمة مناسبة إن كنت متعوّدا على أساسيات إطار العمل Laravel أو أطر العمل الأخرى التي تستخدم PHP. سنعمل في هذا الدرس على بناء تطبيق قاعدي، عبارة عن قائمة مهامّ، نوضّح من خلاله مجموعة مختارة من أهمّ ميزات Laravel. المصدر الكامل للمشروع متوفّر على GitHub. التثبيت نحتاج أولا لإنشاء مشروع Laravel جديد. يمكنك استخدام آلة Homestead افتراضية أو بيئة PHP محليّة على جهازك. استخدم Composer بعد تجهيز بيئة العمل لإنشاء مشروع Laravel جديد على النحو التالي: composer create-project laravel/laravel quickstart --prefer-dist "5.2.*" يمكنك، إن أردت، تنزيل الشفرة المصدريّة النهائية للمشروع لتشغيلها على حاسوبك الشخّصي باستنساخ مستودع Git وتثبيت الاعتمادات الخاصّة بالمشروع على النحو التالي (حدّدنا الفرع 5.2 من المشروع؛ وهو الإصدار المستقرّ من المستودع أثناء كتابة هذا الدرس): git clone -b 5.2 --single-branch https://github.com/laravel/quickstart-intermediate/ quickstart-final cd quickstart-final composer install php artisan migrate كما يمكنك متابعة خطوات هذا الدرس للحصول في النهاية على نتيجة مشابهة، مع شرح الميزات المستخدمة. تهيئة قاعدة البيانات التهجيرات سنبدأ بإنشاء تهجيرات لتعريف جدول قاعدة البيانات الذي سيخزّن جميع المهامّ التي سيديرها التطبيق. توفّر التهجيرات في Laravel وسيلة سهلة ومعبّرة لتعريف جداول البيانات باستخدام شفرة PHP. تفيد التهجيرات - مثلا - أثناء العمل في فريق للحصول على نفس البنية في قاعدة البيانات؛ فبدلا من أن ينشئ كل عضو يدويا قاعدة بيانات محليّة لاحتياجات التطوير، فإنه ينفّذ التهجيرات للحصول على قاعدة بيانات بنفس البنية الموجودة لدى بقية الفريق. جدول المستخدمين نخطّط لجعل التطبيق يتيح للمستخدمين إنشاء حسابات، أي أنه يتوجّب علينا إنشاء جدول لحفظ بيانات هؤلاء المستخدمين. يأتي Laravel مبدئيا بتهجير لإنشاء جدول للمستخدمين، مما يغنينا عن إنشائه يدويا. يوجد التهجير المبدئي للمستخدمين في المجلّد database/migrations. جدول المهامّ العنصر الثاني في تطبيقنا هو المهامّ؛ التي يتوجّب علينا حفظ معلومات عنها. لذا، سننشئ جدولا خاصّا بالمهامّ. تُستخدَم أداة artisan التي تعمل من سطر الأوامر لتوليد أنواع مختلفة من الأصناف Classes وتختصر الكثير من الوقت أثناء تطوير التطبيقات في Laravel. سنستعين بهذه الأداة لإنشاء التهجير الخاصّ بجدول المهامّ، وذلك على النحو التالي: php artisan make:migration create_tasks_table --create=tasks يطلُب أمر artisan أعلاه توليد تهجيرٍ باسم create_tasks_table ويحدّد اسم جدول البيانات الذي نريد إنشاءه create=tasks--. يوضَع التهجير المولَّد في المجلّد database/migrations الموجود في مجلّد المشروع. إن فتحت ملفّ التهجير create_tasks_table (اسم الملفّ يبدأ بختم زمني يوافق وقت توليد الملفّ) ستجد أن الأمر أضاف حقلا لمعرّف يزداد تلقائيا Auto-incrementing ID وأختاما زمنيّة Timestamps ضمن حقول الجدول. نحرّر الملفّ المولَّد ونضيف عمودا من نوع سلسلة محارف String لأسماء المهامّ وعمودا آخر باسم user_id لربط المهامّ بالمستخدمين (حتى نعرف صاحب المهمة): <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTasksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('tasks', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->index(); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('tasks'); } } تأكد من إعداد متطلّبات التهجير لتمكين الاتصال بين التطبيق وقاعدة البيانات وبين أداة artisan وقاعدة البيانات (ليكن اسمُ قاعدة البيانات quickstartdb، مثلا). ثم بعد التأكد نفّذ أمر التهجير على النحو التالي (إن كنت تستخدم Homestead فسيتوجب عليك تنفيذ الأمر من داخل الآلة الافتراضية): php artisan migrate سينشئ الأمر السابق الجداول التي عرّفناها في التهجيرات. يمكنك التأكد من الأمر بالنظر في قاعدة البيانات وسرد الجداول الموجودة فيها. ننتقل الآن لتعريف نماذج Eloquent الخاصّة بالمشروع. نماذج Eloquent يستخدم Laravel مبدئيا إطار العمل Eloquent لربط الكائنات في التطبيق بالجداول في قاعدة البيانات Object-relational mapping. يسهّل Eloquent استرجاع البيانات وتخزينها في قاعدة البيانات، وذلك عن طريق نماذج معرَّفة بوضوح. يوافق كلّ نموذج Eloquent عادة جدولًا في قاعدة البيانات. نموذج المستخدم نحتاج أولا إلى نموذج Model ليربط الكائنات التي تمثّل المستخدمين في التطبيق بجدول المستخدمين users في قاعدة البيانات. إن نظرت في مجلّد التطبيق app داخل مشروعك فستجد أن Laravel يأتي مبدئيا بنموذج مستخدم User.php؛ أي أننا لن نحتاج لإنشاء واحد. نموذج المهمة أنشأنا أثناء إعداد التهجيرات جدولا للمهامّ باسم tasks. حسب نفس مبدأ نموذج المستخدم سنحتاج لنموذج في التطبيق لربط الكائنات من صنف المهام بجدول البيانات tasks. نستخدم أداة artisan لهذا الغرض على النحو التالي: php artisan make:model Task ينشئ الأمر نموذجا فارغا باسم Task.php ويضعه في المجلد app. لن تحتاج لإخبار Eloquent بجدول البيانات الذي يتوافق مع النموذج لأن Eloquent يفترض أن اسم الجدول هو جمع لاسم النموذج. بمعنى أن النموذج User يتوافق مع الجدول users والنموذج Task مع الجدول tasks (تبدأ أسماء النماذج بأحرف كبيرة، فيما تبدأ أسماء الجداول بأحرف صغيرة). نعدّل على ملفّ النموذج Task لإتاحة إمكانية الإسناد الشامل Mass assignment للحقل name: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Task extends Model { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['name']; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'user_id' => 'int', ]; } سنعود لاستخدام النماذج عند الحديث عن تعريف مسارات Routes التطبيق. ملحوظة: عند الاستعلام عن بيانات من قاعدة بيانات MySQL في PHP فإن نوع البيانات المتحصَّل عليه هو دائما string (سلسلة محارف). استخدمنا في النموذج أعلاه المصفوفة casts للتأكيد على أن الخاصية user_id في الصنف Task يجب أن تكون من النوع int. سنتمكّن بهذه الطريقة من استخدام المعامل === (المساواة التامة، في القيمة ونوع البيانات). علاقات Eloquent عرفنا في الفقرتين السابقتين النموذجيْن User وTask؛ المستخدم والمهمة على التوالي، يجب الآن تعريف العلاقة التي تربط بين الاثنين. يمكن لمستخدم إنشاء أكثر من مهمّة، لكنّ مهمّة محدّدة لا يمكن أن تُسنَد سوى لمستخدم واحد فقط. يمكّن تعريف العلاقات بين النماذج من الحصول بسهولة على البيانات الطلوبة. مثلا؛ يمكن، بعد تعريف العلاقة بين المستخدم والمهام، إيجادُ مهامّ المستخدم ذي المعرّف 1 على النحو التالي: $user = App\User::find(1); foreach ($user->tasks as $task) { echo $task->name; } علاقة المستخدم بالمهام نحدّد أولا طبيعة العلاقة بين المستخدم والمهامّ. تعرَّف العلاقات في Eloquent بدوالّ في النموذج. تحدّد الدوال طبيعة العلاقة بين النموذجين. يمكن أن يكون للمستخدم، كما أسلفنا، أكثرُ من مهمة، تُسمّى هذه العلاقة في Eloquent بـhasMany، وتعرَّف بإضافة دالة tasks على النحو التالي إلى النموذج User: <?php namespace App; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get all of the tasks for the user. */ public function tasks() { return $this->hasMany(Task::class); } } علاقة المهمّة بالمستخدم نعرفّ، في الجانب الآخر، على مستوى النموذج Task، طبيعة العلاقة التي تربط المهمة بالمستخدم. نعرّف، كما فعلنا عند تعريف علاقة المستخدم بالمهامّ، دالةً مع تحديد طبيعة العلاقة التي هي في هذه الحالة belongsTo. بمعنى أن المهمة تتبع للمستخدم؛ أي أنه لا يمكن أن تتبع نفس المهمة لأكثر من مستخدم. يمكن تقريب الفكرة بالقول إن الناظر للعلاقة من جهة المهمة يرى أنه تسير في اتجاه واحد belongsTo (مستخدم واحد)؛ أما الناظر إليها من جهة المستخدم فيرى أنها يمكن أن تسير في عدّة اتجاهات hasMany (مهامّ متعدّدة): <?php namespace App; use App\User; use Illuminate\Database\Eloquent\Model; class Task extends Model { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['name']; /** * Get the user that owns the task. */ public function user() { return $this->belongsTo(User::class); } } التوجيه تُستخدَم المسارات Routes لتوجيه الروابط URL إلى متحكّم أو دالة تُنفَّذ عند زيارة رابط محدَّد. تُعرَّف المسارات مبدئيا في Laravel ضمن الملفّ app/Http/routes.php. تتيح المتحكمات التعامل مع طلبات Http بتوزيعها بين ملفات مختلفة من أجل تنظيم أفضل وصيانة أسهل. إظهار عرض سيكون لدينا مسار / تظهر للزائر عند طلبه صفحة ترحيبية. تنتُج الصفحة الترحيبية عن معالجة قالب HTML. مبدئيا؛ يُخزّن Laravel جميع قوالب HTML في المجلّد resources/views. ستجد عند فتح الملفّ routes.php أنه يحوي الدالة التالية: Route::get('/', function () { return view('welcome'); }); تستقبل هذه الدالة الطالبات الواردة على المسار / (مثلا www.example.com) وتقدّم إليها العرض welcome.blade.php باستخدام الدالة المساعدة view. لاحظ أننا لم نحدّد امتداد الملفّ في الدالة، اسم العرض (welcome) يكفي. سنعرّف العرض في ما بعد. الاستيثاق ذكرنا في تقديم المشروع الذي نودّ بناءه أننا نريد أن يتمكّن المستخدمون من إنشاء حسابات والولوج بها إلى التطبيق؛ أي أننا سنحتاج لآلية للاستيثاق. قد يتطلّب إنشاء آلية للاستيثاق الكثير من الجهد والوقت؛ إلا أن Laravel يحاول جعل هذه العمليّة أسهل ما يمكن. أولا؛ تمكن ملاحظة وجود المتحكّم AuthController في المجلد app/Http/Controllers/Auth. يستخدم هذا المتحكّم السمة AuthenticatesAndRegistersUsers التي تحوي الدوال المطلوبة لإنشاء المستخدمين والاستيثاق منهم. مسارات الاستيثاق وعروضه الأساسيات المطلوبة لإنشاء مستخدمين والاستيثاق منهم موجودة مبدئيا كما أسلفنا؛ ولكن يبقى تعريف قوالب (عروض) التسجيل والاستيثاق، والمسارات التي تشير إلى متحكّم الاستيثاق. يمكننا باستخدام artisan تعريفُ القوالب بسهولة: php artisan make:auth لاحظ مخرجات الأمر والعروض التي أضافها للمشروع. تمكن أيضا ملاحظة أن الأمر أضاف المسارات التالية إلى ملفّ المسارات: Route::auth(); Route::get('/home', 'HomeController@index'); تسجّل الدالة auth من الصّنف Route جميع المسارات التي نحتاجها لتسجيل حسابات جديدة للمستخدمين، ولوج مستخدمين مسجَّلين وإعادة تعيين كلمة السّر. يعرّف الأمر في المسار الثاني الرابط home/ الذي تتعامل معه الدالة index في المتحكم HomeController الذي أنشأه الأمر السابق. إن فتحت ملفّ المتحكم HomeController فستجد أن الدالة index تطلُب إظهار العرض home. ملحوظة: إن كنت ترغب في الاطّلاع على الشفرة المصدرية الكاملة للعروض فهي متوفّرة ضمن شفرة المشروع على GitHub. افتح ملفّ المتحكّم AuthController (في المجلّد app/Http/Controllers/Auth) وغيّر قيمة redirectTo$ كالتالي: protected $redirectTo = '/tasks'; تحدّد هذه الخاصّيّة المسار الذي يُوجَّه الزائر إليه بعد الاستيثاق من بياناته. يجب أيضا التعديل على الملفّ app/Http/Middleware/RedirectIfAuthenticated.php ليرجع نفس المسار بعد ولوج الزائر: return redirect('/tasks'); المتحكم في المهام ننشئ متحكّما جديدا خاصّا بالعمليات على المهامّ، مثل العثور على المهامّ المخزّنة وتخزين مهامّ جديدة. سنستخدم - كالعادة - أداة artisan لهذا الغرض: php artisan make:controller TaskController ثم نعدّل ملفّ المسارات بإضافة المسارات التالية: Route::get('/tasks', 'TaskController@index'); Route::post('/task', 'TaskController@store'); Route::delete('/task/{task}', 'TaskController@destroy'); سنستخدم المسار tasks/ مع الإجراء get للعثور على مهامّ مستخدِم، المسار task/ مع الإجراء post لإضافة مهمة جديدة والمسار {task/{task مع الإجراء delete لحذف مهمّة؛ حيثُ {task} معرّف المهمة. الاستيثاق من جميع مسارات المهامّ نريد لجميع المسارات المتعلّقة بالمهامّ أن تكون محميّة؛ بمعنى أن المستخدمين لا يمكنهم رؤيتها إلا بعد الولوج إلى التطبيق. من السهل تنفيذ هذا الأمر في Laravel، وذلك باستخدام صنف وسيط Middleware. يمكن، بإضافة نداء لصنف الاستيثاق الوسيط auth داخل مشيّد Constructor المتحكّم TaskController، جعلُ جميع الإجراءات في المتحكّم تستدعي الاستيثاق من المستخدم (أن يكون مسجّلَ الدّخول). يعرّف الملف app/Http/Kernel.php جميع الأصناف الوسيطة الممكن تطبيقها على المسارات. يصبح المتحكم TaskController بعد إضافة الوسيط إلى المشيّد كما يلي: <?php namespace App\Http\Controllers; use App\Http\Requests; use Illuminate\Http\Request; use App\Http\Controllers\Controller; class TaskController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('auth'); } } إنشاء القوالب والعروض يتضمّن الجزء الأساسي من التطبيق الذي نعملُ عليه عرضا واحدا توجد به استمارة لإضافة مهامّ جديدة، كما تظهر لائحة بالمهامّ الموجودة سلفا. في ما يلي لقطة من التطبيق المكتمل: تعريف مخطّط العرض تستخدم غالبية تطبيقات الوِب نفس المخطّط في كلّ الصفحات. بالنسبة للتطبيق الذي نعمل عليه فهو يحتوي على شريط تصفّح علوي يوجد في جميع الصفحات (في حال وجود أكثر من صفحة). تسهّل قوالب Blade تشارك العناصر بين الصفحات. يحتفظ Laravel بجميع العروض ضمن المجلّد resources/views. سنعرّف قالبا جديدا لعروض Blade في الملفّ resources/views/layouts/app.blade.php. يستخدم Laravel نظام القوالب Blade لمعالجة الملفات التي تنتهي بالامتداد blade.php.. يمكن استخدام قوالب PHP تقليدية لتقديم العروض، إلا أن نظام Blade يوفّر قوالب مختصرة ومحكمة. افتح الملفّ resources/views/layouts/app.blade.php وستجد أن محتواه يشبه التالي: <!-- resources/views/layouts/app.blade.php --> <!DOCTYPE html> <html lang="en"> <head> <title>Laravel Quickstart - Intermediate</title> <!-- شفرات CSS و JavaScript --> </head> <body> <div class="container"> <nav class="navbar navbar-default"> <!-- محتوى شريط التصفح Navbar --> </nav> </div> @yield('content') </body> </html> تحدّد التعليمة ('yield('content@ مكان الصفحات التي ستمدّد المخطّط الرئيس وتضيف محتوى خاصّا بها. راجع توريث القوالب في Laravel للمزيد من التفصيل. سننتقل الآن لتعريف العرض الإبن الذي سيستخدم المخطّط ويعرّف داخله محتوى خاصّا به. تعريف العرض المُمدِّد للمخطَّط سنحتاج لتعريف عرض يحوي استمارة لإنشاء مهمة جديدة وجدولا لعرض المهامّ الموجودة. ننشئ لهذا الغرض عرضا على المسار resources/views/tasks/index.blade.php، الذي ستطلب الدالة index في المتحكّم TaskController عرضه. سنركّز في ما يلي على التعليمات المتعلّقة بـBlade ونتجاوز شفرة Bootstrap CSS (يمكن الحصول على الشفرة كاملة من مستودع المشروع على GitHub: <!-- resources/views/tasks/index.blade.php --> @extends('layouts.app') @section('content') <div class="container"> <div class="col-sm-offset-2 col-sm-8"> <div class="panel panel-default"> <div class="panel-heading"> New Task </div> <div class="panel-body"> <!-- إظهار أخطاء التحقّق --> @include('common.errors') <!-- استمارة إنشاء مهمة --> <form action="{{ url('task') }}" method="POST" class="form-horizontal"> {{ csrf_field() }} <!-- اسم المهمة --> <div class="form-group"> <label for="task-name" class="col-sm-3 control-label">Task</label> <div class="col-sm-6"> <input type="text" name="name" id="task-name" class="form-control"> </div> </div> <!-- زرّ إضافة مهمة --> <div class="form-group"> <div class="col-sm-offset-3 col-sm-6"> <button type="submit" class="btn btn-default"> <i class="fa fa-plus"></i> Add Task </button> </div> </div> </form> </div> </div> <!-- سنضع هنا الشفرة الخاصة بعرض المهام التي أنشأها المستخدم --> @endsection تُخبر التعليمة extends@ نظام القوالب Blade أننا في طور استخدام المخطّط المعرَّف في الملفّ resources/views/layouts/app.blade.php. يضع نظامُ القوالب جميعَ المحتوى الموجود بين التعليمتيْن ('section('content@ وendsection@ مكان التعليمة ('yield('content@ في المخطّط app.blade.php. تحمّل التعليمة ('include('common.errors@ القالب resources/views/common/errors.blade.php. هذا القالب غير معرَّف لحد الساعة، ولكن سنعرّفه بعد قليل. نعود للمتحكّم TaskController ونضيف الدالة index التي تطلُب تقديم العرض resources/views/tasks/index.blade.php الذي أنشأناه للتو: /** * Display a list of all of the user's task. * * @param Request $request * @return Response */ public function index(Request $request) { return view('tasks.index'); } يشير الاسم tasks.index إلى أن المطلوب هو إظهار العرض index.blade.php الموجود في المجلد tasks الذي يوجد بدوره في مجلد العروض (أي resources/view). أصبحت أغلب عناصر التطبيق جاهزة. سنكمل في الجزء الثاني لهذا المقال بقية العناصر التي ستمكّن من إضافة مهامّ جديدة، عرض مهام موجودة أو حذفها. سنواصل في المقال القادم بناء التّطبيق. ترجمة - بتصرّف - للجزء الأول من مقال Intermediate Task List.
  14. طوّرت شركة Sun Microsystems نظام الملفّات الشبكي Network file system, NFS سنة 1980 من أجل السماح بتشارك الملفّات والمجلّدات بين الأنظمة الشبيهة بيونكس (لينكس ويونكس). يتيح نظام NFS للملفّات تركيب Mounting ملفّات عبر الشّبكة والتعامل معها كما لو كانت مركَّبة محليًّا على نفس النظام. فوائد نظام NFS يسمح نظام NFS بالوصول إلى الملفّات عن بعد. يستخدم بنية عميل-خادوم Client/Server معيارية لتشارك الملفّات بين جميع الأجهزة العاملة بالأنظمة الشبيهة بيونكس. ليس ضروريًّا عند استخدام NFS أن تكون الأجهزة تعمل على نفس نظام التّشغيل. يمكن ضبط حلول للتخزين المركزيّ بمساعدة نظام NFS. يعثُر المستخدمون على بياناتهم مهما كان تواجدها الفعلي. لا توجد حاجة للتحيين Refresh اليدوي للحصول على الملفّات الجديدة. تدعم الإصدارات الحديثة من NFS قوائم التحكّم في الوصول ACL. يمكن تأمينه عبر الجدران الناريّة Firewalls وKerberos (ميثاق للاستيثاق عبر الشبكة). تدخلّ الملفّات التاليّة في إعداد NFS: ملفّ etc/exports/: وهو ملفّ الإعداد الرئيس؛ تعرّف فيه - على الخادوم - جميع الملفّات والمجلّدات المشاركة. ملفّ etc/fstab/: يجب إدراج سطر لكلّ نظام ملفّات مُركَّب ضمن هذا الملفّ حتى يكون التركيب دائما (يبقى بعد إعادة التشغيل). ملفّ etc/sysconfig/nfs/: يعرّف المنافذ التي تنصت الخدمات للاتّصالات الواردة عبرها. إعداد NFS على خادوم أوبونتو سنعتمد في هذا الدرس على جهازين يعملان بإحدى توزيعات لينكس التاليّة: دبيان، أوبونتو أو ردهات. سنثبّت على أحد الجهازين خادوم NFS وعلى الثاني عميلا له كالتالي: خادوم NFS على العنوان 192.168.2.200. عميل NFS على العنوان 192.168.2.150. يمكنك استخدام VirtualBox لإعداد بيئة عمل بالمواصفات أعلاه. تثبيت الخادوم والعميل سنحتاج لتثبيت حزم NFS على الخادوم والعميل: # apt-get update # apt-get install nfs-kernel-server nfs-common ملحوظة: يمكن الاكتفاء بالحزمة nfs-common على العميل. تشير العلامة # إلى أن الأمر ينفَّذ بصلاحيّات الجذر. إعداد خادوم NFS الخطوة الأولى هي تحديد المجلّد الذي نريد مشاركته؛ سننشئ واحدا لهذا الغرض (يمكنك اختيّار مجلّد موجود مسبقا): # mkdir /nfsshare ثم نغيّر ملكيّته إلى nobody:nogroup: # chown nobody:nogroup /nfsshare غيّرنا ملكيّة الملفّ إلى المستخدم nobody الذي هو حساب لا يملك سوى الصلاحيّات الدنيا ويُستخدَم عادة لتنفيذ سكربتات غير مأمونة المصدر حتى لا تلحق أي ضرر بالنظام. هذه الخطوة ضروريّة وإلا فلن يكون بمقدورك إضافة ملفّات إلى المجلّد انطلاقا من العميل؛ إلا إذا أضفت خيار no_root_squash كما هو مشروح أدناه (لا يُنصَح بذلك). ليمكن تشاركُ المجلّد يجب أن نضيفه إلى ملفّ الإعداد etc/exports/؛ نفتح هذا الملفّ (بصلاحيّات الجذر) ثم نضيف إليه السّطر التالي: /nfsshare 192.168.2.150(rw,sync) يسمح السّطر أعلاه بتشارك المجلّد nfsshare مع العميل على العنوان 192.168.2.150 مع تحديد خيارات التشارك. يمكن أن تكون الخيارات على النحو التالي: ro: الوصول عبر وضع القراءة فقط؛ أي أن العميل يمكنه الوصول إلى الملفات المتشاركة فقط دون أن يكون قادرا على التعديل عليها أو إنشاء ملفات جديدة. rw: يمنح العميل القدرة على الوصول إلى الملفّات والتعديل عليها. sync: مزامنة التعديلات. يعني هذا أن أي تعديل سيُحفَظ فورا على نظام الملفّات؛ قد يؤثّر هذا الخيار على الأداء في حالة وجود الكثير من التغييرات المتزامنة على ملفّات كبيرة. no_subtree_check: يفحص نظام NFS مبدئيّا جميع المجلدات التي يتفرّع منها المجلّد المتشارَك من أجل التحقّق من الأذون وتفاصيل أخرى. يؤدّي تفعيل هذه الخاصيّة إلى تعطيل الفحص ممّا يحسّن من الأداء ولكنّه يؤدّي التقليل من الأمان. no_root_squash: تسمح هذه الخاصيّة للمستخدم الجذر على الجهاز العميل بالوصول إلى الملفّات المتشاركة على الخادوم بصلاحيّات الجذر على الخادوم. لا يُنصَح لأسباب أمنيّة باستخدام هذا الخيار إلا في حالات خاصّة. نطبّق الأمر exportfs بعد الانتهاء من تحرير الملفّ exports من أجل اعتماد التعديلات: # exportfs -r ثم نشغّل خدمة NFS كالتالي: # service nfs-kernel-server start إعداد عميل NFS على أوبونتو يمكننا الآن تركيب مجلّد التشارك على العميل. تأكد من أنه يمكن التواصل عبر الشبكة بين الجهاز الخادوم والعميل؛ مثلا باستخدام الأمر ping على العميل (حيثُ 192.168.2.200 هو عنوان الخادوم): $ ping 192.168.2.200 PING 192.168.2.200 (192.168.2.200) 56(84) bytes of data. 64 bytes from 192.168.2.200: icmp_seq=7 ttl=52 time=363 ms 64 bytes from 192.168.2.200: icmp_seq=8 ttl=52 time=322 ms نبدأ أولا بالبحث عن الملفّات المتشاركة عبر NFS على الخادوم: $ showmount -e 192.168.2.200 Export list for 192.168.2.200: /nfsshare 192.168.2.150 يظهر من نتيجة الأمر أعلاه أن المجلّد nfsshare/ متاح للتشارك مع العميل ذي العنوان 192.168.2.150. تركيب المجلد على العميل سننشئ على العميل مجلّدًا باسم mnt/nfs/nfsshare_mount/ ونستخدمه لتركيب المجلّد nfsshare/ على العميل (المجلّد nfsshare/ الموجود على الخادوم): # mkdir -p /mnt/nfs/nfsshare_mount # mount -t nfs 192.168.2.200:/nfsshare /mnt/nfs/nfsshare_mount يمكننا التأكد من تركيب المجلّد بتنفيذ الأمر mount والبحث عن نوع الملفّات nfs: $ mount | grep nfs 192.168.2.200:/nfsshare on /mnt/nfs/nfsshare_mount type nfs (rw,vers=4,addr=192.168.2.200,clientaddr=192.168.2.150) يمكن الآن إضافة ملفّات إلى مجلّد التشارك عبر نقطة التركيب: # touch /mnt/nfs/nfsshare_mount/testfile.txt يمكن التأكد من على الخادوم بسرد محتويات المجلّد nfsshare/. إن أردت جعل التركيب دائما (لا يزول مع إقلاع النظام) فيمكنك إضافة السطر التالي إلى ملفّ etc/fstab/: 192.168.2.200:/nfsshare /mnt/nfs/nfsshare_mount nfs defaults 0 0 نفّذ الأمر mount -a بعد تعديل ملفّ fstab لاعتماد التغييرات فورا. ملحوظة: تأكد من إدخال القيم الصحيحة في ملفّ etc/fstab/؛ وجود أخطاء في صياغة السّطر يمكن أن يؤدي إلى عدم إقلاع النظام. فصل المجلد من العميل استخدم الأمر umount إن أردت فصل المجلّد المركَّب، تُمرّضر نقطة التركيب للأمر على النحو التالي: # umount /mnt/nfs/nfsshare_mount نفّذ الأمر mount | grep nfs للتأكد من فصل المجلّد. أوامر أساسية للتعامل مع ملفات NFS في ما يلي أوامر يكثُر استخدامها أثناء التعامل مع المجلّدات المشارَكة عبر نظام NFS: showmount -e: يعرض جميع المجلّدات المتشاركة المتاحة على الجهاز. showmount -e <server-ip>: يسرد قائمة بالمجلّدات المشاركة المتاحة على الخادوم ذي العنوان server-ip. showmount -d <server-ip>: سرد المجلّدات المشاركة المتاحة على الخادوم والمجلدات المتفرعة منها. exportfs -v: يعرض قائمة بالمجلّدات المتشاركة الموجوة على الخادوم. exportfs -a: يطلب من الخادوم مشاركة الملفّات المحدّدة في الملفّ etc/exports/. exportfs -u: يعمل عكسَ عمل الأمر السابق، إذ يوقف مشاركة المجلدات المحدّدة في etc/exports/. exportfs -r: يحدّث قائمة المشاركات بعد التعديل على الملفّ etc/exports/. يمكن اعتماد هذا المقال الذي يقدّم أساسيات تشارك الملفّات عبر نظام NFS للانتقال إلى تفاصيل أكثر تقدّما بخصوص NFS والميزات التي يقدّمها. ترجمة -وبتصرّف- للمقال How to Setup NFS (Network File System) on RHEL/CentOS/Fedora and Debian/Ubuntu لصاحبه Tarunika Shrivastava.
  15. وعليكم السلام ورحمة الله. يبدو أن لديك مشكلة في اسم النطاق Domain، تأكد من أن اسم النطاق مضبوط (لدى الشركة التي حجزته من عندها) على عنوان IP الصحيح، حيثُ يوجد خادوم أوبونتو.
  16. يتيح برنامج Oracle VirtualBox الكثير من الإمكانيّات لبناء أنظمة افتراضية للاستخدام الشخصيّ وهو ما يوفّر عليك عناء إعداد أمور كانت ستكلفك الكثير من الوقت والجهد. شرحنا في درس سابق كيفية تثبيت توزيعة RedHat باستخدام هذا البرنامج. سنشرح في هذا المقال كيف يمكننا بناء شبكة داخليّة افتراضيّة يمكن استخدامها لمحاكاة شبكة داخليّة محليّة Local area network ,LAN. يوضح الرسم التالي الهدف النهائي الذي نريد الوصول إليه: توجد آلتان افتراضيتان، لكلّ منهما واجهتا شبكة. نستخدم الواجهة الأولى لكلّ آلة افتراضيّة للاتصال بشبكة الإنترنت عبرالمرور بواجهة شبكة خاصّة ببرنامج VirtualBox تسمّى virbr0. تنشئ الواجهة الثانيّة على الآلة الافتراضية شبكة داخليّة مع الواجهة الثانيّة على الآلة الافتراضية الأخرى. إن أردت الاكتفاء بإنشاء شبكة داخليّة بين الآلات الافتراضيّة فيمكنك تجاوز الخطوة الثانيّة من الخطوات أعلاه وتنفيذ الخطوتين الأولى والثالثة؛ ولكن لن يكون بمقدور أي من النظامين في هذه الحالة الاتصال بالإنترنت وما يعنيه ذلك من عدم القدرة على تحديث الحزم أو تثبيت حزم جديدة. يحاكي هذا المخطّط عمل الخواديم في بيئة إنتاج فعليّة حيث توجد عادة أكثر من بطاقة شبكة على الخادوم، ولكلّ بطاقة استخدام معيَّن؛ مما يتيح للخادوم الاتصال بأكثر من شبكة (شبكات داخليّة عدّة أو شبكة داخليّة وأخرى عامّة). يفترض هذا الدرس وجود توزيعة RedHat Enterprise Linux مثبّتة كما هو مشروح في الدرس المشار إليه سابقا. تبقى الخطوات التالية صالحة مهما كان النظام المثبّت مع تغيير يسير في الخطوات الأخيرة (تفعيل البطاقات على RedHat Enterprise Linux). تهيئة VirtualBox لإنشاء شبكة محلية سنحتاج أولا إلى إنشاء نظام افتراضي ثان بعد النظام الافتراضي الذي أنشأناه في درس تثبيت RHEL. يتيح VirtualBox آليّة سريعة لإنشاء الأنظمة الافتراضيّة انطلاقا من أنظمة موجودة، تُسمّى هذه الآلية بالاستنساخ Cloning. تظهر بالنقر بالجانب الفأرة الأيمن على النظام الافتراضي الذي نريد استنساخه قائمة ظرفيّة نختار منها استنسخ Clone. تظهر نافذة جديدة، أدخل فيها اسما تختاره للنظام الذي نريد إنشاءه. تأكد من تحديد خيّار أعد تهيئة عناوين MAC لجميع البطاقات Reinitialize the MAC address for all network cards. يوجد خياران للاستنساخ؛ استنساخ كامل Full أو استنساخ رمزي Linked. يُنشأ في الحالة الأولى نظام جديد بالكامل (أقرص افتراضية منفصلة) في حين ينشئ الخيار الثاني آلة افتراضية جديدة ولكنها مجرد وصلة للنظام المستنسَخ. نختار الاستنساخ الكامل ثم ننقر على زر Clone. تأخذ العمليّة بضعة دقائق نحصُل بعدها على نظام افتراضي جديد. إعداد بطاقات الشبكة على VirtualBox لدينا الآن نظامان افتراضيّان (آلتان) سنعدّهما للحصول على المخطّط الذي تحدثنا عنه أعلاه. تُطبَّق الخطوتان التاليتان على كل من النظامين. حدّد النظام الافتراضي ثمّ زرّ الإعدادات Settings، تظهر نافذة جديدة اختر منها قسم الشبكة Network. توجد في هذا القسم أربع بطاقات شبكة (يُشار إليها بـ Adapter). حدّد خيار Enable network adapter (فعِّل بطاقة الشبكة) لتفعيل البطاقة وتأكد من أن خيار وصل الكابل (Cable connected) محدّد. يعدّ VirtualBox مبدئيا بطاقات الشبكة للعمل وفق وضع ترجمة عناوين الشبكة Network address translation, NAT. يعني هذا أن البطاقة سيكون لديها عنوان IP خاصّ غير مرئيّ من الخارج ولكنها تستطيع التواصل مع الخارج عبر VirtualBox الذي يعمل وسيطا بين الاثنين. هذه هي نفس الآلية التي يعمل بها الموجّه الموجود في المنزل عادة (يُترجم العناوين بين حاسوبك ومزوّد خدمة الإنترنت). يمكّنك هذا الإعداد من الاتصال بالإنترنت إذا كان حاسوبك متصلا بها. لاحظ عنوان MAC الخاصّ بالبطاقة؛ فربما تحتاجه للتعرف عليها من داخل نظام التشغيل في ما بعد. ننتقل للبطاقة الثانيّة (Adapter 2). الأمر المختلف هنا مقارنة بالبطاقة السابقة هو أننا اخترنا وضع Internal network (شبكة داخليّة) مع السماح بتنقل البيانات بين الآلات الافتراضية عبر هذه البطاقة (Allow VMs). كرّر نفس الإعدادات على النظام الافتراضي الآخر. تفعيل بطاقات الشبكة من داخل النظام الإعدادات جاهزة الآن من جانب VirtualBox؛ بقي أن نشغّل الآلتين ونفعّل البطاقات على مستوى نظام التشغيل ثم نختبر الاتصال بينها. الخطوات المشروحة هنا مطبقة على توزيعة RedHat Enterprise Linux بواجهة رسوميّة؛ يمكن تكييفها مع عمل أي نظام تشغيل آخر. كرّر الخطوات على كلّ آلة افتراضية. اختر قائمة التطبيقات Applications، ثم أدوات النظام System tools ثم الإعدادات Settings ثم الشبكة Network. ستجد أن النظام اكتشف وجود بطاقتين كما يظهر في يسار الصورة (قد تختلف أسماء البطاقات لديك). يمكن ملاحظة أن إحدى البطاقتين تأخذ عنوان IP تلقائيا فور تفعيلها، بينما لا تُظهر الأخرى سوى عنوان MAC الخاصّ بها. يمكنك استنتاج أن البطاقة التي تأخذ عنوانا تلقائيا هي البطاقة المعدّة لاستخدام ترجمة عناوين الشبكة، بينما الأخرى هي تلك المعدّة لإنشاء شبكة داخليّة دون أن يكون لديها مصدر يمنحها عنوان IP. راجع عناوين MAC في إعدادات VirtualBox وقارنها بالعناوين التي يظهرها نظام التشغيل للتأكد. بقي لنا ضبط البطاقة الداخليّة بإعطائها عنوان IP وقناع شبكة مناسب. حدّد بطاقة الشبكة الداخليّة (enps08 في المثال لديّ) وفعّلها ثم انقر على زرّ Add profile الموجود لتظهر نافذة جديدة. عدّل الإعدادات كما في الصورة. اخترتُ العنوان 192.168.2.100 بالنسبة للجهاز الافتراضي الأول، و192.168.2.200 بالنسبة للجهاز الثاني. قناع الشبكة في الحالتين هو 255.255.255.0. يمكن التأكد من أن البطاقتين توجدان على نفس الشبكة الداخليّة باستخدام أمر ping. على الجهاز الأول (ذي العنوان 192.168.2.100 ): ping 192.168.2.200 PING 192.168.2.200 (192.168.2.100) 56(84) bytes of data. 64 bytes from 192.168.2.200: icmp_seq=1 ttl=64 time=0.760 ms 64 bytes from 192.168.2.200: icmp_seq=2 ttl=64 time=0.254 ms (...) على الجهاز الثاني (ذي العنوان 192.168.2.200 ): ping 192.168.2.100 PING 192.168.2.100 (192.168.2.100) 56(84) bytes of data. 64 bytes from 192.168.2.100: icmp_seq=1 ttl=64 time=0.980 ms 64 bytes from 192.168.2.100: icmp_seq=2 ttl=64 time=0.348 ms (...) أي أن كلا من البطاقتين يمكنها الاتصال بالأخرى.
  17. نسعى في هذا الدّرس إلى إنشاء قائمتين منسدلتين Dropdown lists ترتبط إحداهما بالأخرى. تحتوي الأولى مثلا على تصنيف والثانية على تصنيف فرعي من التصنيف الموجود في القائمة الأولى. يعني هذا أنْ تتغيّر التصنيفات الفرعيّة الموجودة في القائمة الثّانيّة عند تغيّر العنصُر المحدّد في القائمة الأولى. تبدو النتيجة بنهاية الخطوات المشروحة في هذا الدرس على النحو التالي: نستخدم Laravel في الجانب الخلفيّ Backend لتولي التعامل مع الطّلبات والتخاطب مع قاعدة البيانات للحصول على التصنيفات والتّصنيفات الفرعيّة منها. يتولّى سكربت jQuery العمل في الواجهة الأماميّة Frontend لتحديث محتويات القائمة الثانيّة عند تعديل محتوى الأولى. في ما يلي نظرة عامّة على الخطوات التي سنتّبعها: إنشاء النماذج Models والتهجيرات Migrations. تهيئة معمل نماذج Model factory وبذر Seed جداول البيانات. إعداد المسارات Routes والمتحكّمات Controllers. إعداد العروض Views. الطريقة التي نريد تنفيذها هي كالتالي: عند الدخول إلى المسار categories/ يتلقى ملف المسارات الطّلب ويحوّله إلى الدالّة categories في المتحكّم HomeController. تستقبل الداّلة الطلب وتطلب قائمة بالتّصنيفات من نموذج التّصنيف Category؛ ثم ترسل التّصنيفات إلى العرض categories الذي يعرضها في القائمة المنسدلة الأولى. نستخدم سكربت jQuery في العرض للإنصات لتغييرات القائمة المنسدلة العلويّة، وعند اختيّار أحد عناصرها يأخذ السكربت معرّف العنصر المحدَّد ثم يرسل به طلب Ajax إلى المسار api/category-dropdown/ الذي يجيب بلائحة التصنيفات الفرعيّة للتّصنيف المحدّد في القائمة الأولى. يستقبل السكربت اللائحة ويعرضها في القائمة الثانية. تمكنك مراجعة المقالات التاليّة لتفصيلات أكثرعن إنشاء النماذج، استخدام معمل النماذج و العروض. نبدأ بتثبيت Laravel بالأمر التالي: composer create-project --prefer-dist laravel/laravel laradropdown "5.3.*" انتظر اكتمال التثبيت ثم انتقل لمجلد المشروع laradropdown لمتابعة بقيّة الخطوات. سنفترض في ما يلي أن لاتصال بقاعدة البيانات مضبوط. إنشاء النماذج والتهجيرات نبدأ بإنشاء نموذجيْن Category و SubCategory. الأوّل للتصنيفات والثاني للتصنيفات الفرعيّة. ننفّذ ما يلي في مجلّد المشروع: php artisan make:model Category -m php artisan make:model SubCategory -m استخدمنا خيار m- لإنشاء التهجيرات مع إنشاء النماذج. نفتح ملفّ التهجير الخاصّ بالتصنيف ونعدّله: public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('categories') } يحوي الملفّ كما هو ظاهر أربعة حقول: معرّفا، اسما للتّصنيف وحقليْ الأختام الزمنية. ننتقل لملفّ التهجير الخاصّ بالتصنيف الفرعي: public function up() { Schema::create('sub_categories', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->integer('category_id')->unsigned(); $table->timestamps(); }); Schema::table('sub_categories', function (Blueprint $table) { $table->foreign('category_id') ->references('id') ->on('categories') ->onDelete('cascade'); }); } public function down() { Schema::table('sub_categories', function(Blueprint $table) { $table->dropForeign('sub_categories_category_id_foreign'); }); Schema::dropIfExists('sub_categories'); } يختلف ملفّ التهجير هذا قليلا عن الملفّ السّابق؛ إذ يحوي إلى جانب حقول المعرّف، الاسم والأختام الزمنيّة معرفَ التصنيف الذي يتفرّع منه. هذا المعرف هو مفتاح خارجي Foreign key يحيل إلى جدول التّصنيفات. نعرّف الحقول أولا ثم نحدّد الحقل category_id على أنه مفتاح خارجي. ينبغي أن يكون الحقل مطابقا تماما من حيث النوع للحقل الذي يحيل إليه. في الدالة down نبدأ بحذف القيد من على الحقل حتى يمكننا حذف الجدول. أنهينا إعداد التهجيرات. ننتقل لإعداد النماذج. نغيّر ملف النموذج Category على النحو التالي: class Category extends Model { protected $fillable = ['id', 'name']; public function sub_categories() { return $this->hasMany('SubCategory'); } } نحدّد أولا الحقول التي يمكن إسنادها (خاصيّة fillable) ثم نضيف الدالة sub_categories التي تعرّف العلاقة بين التّصنيف Category والتّصنيف الفرعي SubCategory. هذه العلاقة هي من النوع hasMany بمعنى أنه توجد بالتّصنيف تصنيفات فرعية تابعة له. يفيدنا تعريف العلاقات بين النماذج في الاستعلامات ويجعلها أسهل بكثير كما سنرى. نفس الشيء تقريبا بالنسبة للنموذج SubCategory: class SubCategory extends Model { protected $fillable = ['id', 'name', 'category_id']; public function category() { return $this->belongsTo('Category'); } } نعرّف في النموذج SubCategory العلاقة العكسيّة لتلك المعرّفة في النموذج Category؛ وهي belongsTo التي تعني أن هذا الصّنف يتبع للصّنف Category. النماذج والتهجيرات جاهزة؛ يمكننا الآن تنفيذ التهجيرات: php artisan migrate تهيئة معامل النماذج وبذر جداول البيانات سنستفيد من الخبرى التي تحصّلنا عليها في درس استخدام معمل النماذج (Model factory) في Laravel لتوليد بيانات الاختبار لتهيئة معملَيْ نماذج نستخدمهما لبذر البيانات في الجدوليْن categories و sub_categories. ننشئ ملفيْن في المجلّد /database/factories؛ واحدا باسم CategoryFactory.php والآخر SubCategoryFactory.php. ثم نضيف المحتوى التالي إلى CategoryFactory.php : <?php $factory->define(App\Category::class, function (Faker\Generator $faker){ return [ 'name' => $faker->unique()->word ]; }); نفس المبدأ المستخدَم في الدرس المُشار إليه أعلاه. نعرّف النموذج الذي نريد توليد بيانات له ثم نستخدم مكتبة Faker لملْء الحقل المحدَّد (name). طلبنا توليد كلمات فريدة Unique حتى نوافق الشّرط المعرَّف في تهجير قاعدة البيانات الخاصّ بالجدول categories. الأمر مختلف قليلا مع الملف SubCategoryFactory.php الذي نعدّله كالتالي: <?php $factory->define(App\SubCategory::class, function (Faker\Generator $faker){ $categories = App\Category::get()->pluck('id')->all(); return [ 'name' => $faker->unique()->word, 'category_id' => $faker->randomElement($categories), ]; }); بما أن الحقل category_id مفتاح خارجي على معرّف التّصنيف فلن تقبل قاعدة البيانات إضافة معرّف لتصنيف غير موجود في جدول التصنيفات. لتجاوز هذا القيد نبدأ بطلب معرّفات التصنيفات: $categories = App\Category::get()->pluck('id')->all(); ثم عند توليد بيانات للحقل category_id نطلب منه أن يختار واحدا عشوائيا من المعرّفات التي تحصّلنا عليها سابقا: 'category_id' => $faker->randomElement($categories), هذا كلّ شيء بالنسبة لمعمل النماذج. ننتقل للبذر. ننشئ صنفا سنستخدمه لبذر النموذجيْن Category و SubCategory: php artisan make:seeder SubCategoryTableSeeder نفتح الملف ونعدّله كما يلي: public function run() { App\SubCategory::truncate(); factory(App\Category::class, 10)->create(); factory(App\SubCategory::class, 50)->create(); } نطلُب في الملفّ توليد 10 تصنيفات و50 تصنيفا فرعيا. نعدّل ملف البذر DatabaseSeeder كما يلي: public function run() { Model::unguard(); $this->call(SubCategoryTableSeeder::class); Model::reguard(); } لا ننسى استدعاء النموذج Model في الملف DatabaseSeeder: use Illuminate\Database\Eloquent\Model; نستدعي ملفّ البذر SubCategoryTableSeeder الذي يستخدم معمليْ النماذج لتوليد البيانات. يمكننا الآن تنفيذ البذر: php artisan db:seed إعداد المسارات والمتحكمات نريد أن نصل إلى صفحة القائمتين المنسدلتين عبر الرابط categories/؛ لذا سنعرّف مسارا له في ملف مسارات الوِب routes/web.php: Route::get('/categories', 'HomeController@categories'); لا خصوصيةَ هنا؛ المسار والمتحكّم والدالة اللذان يعالجان الطّلب. إن لم يكن المتحكّم HomeController معرّفا لديك فاستخدم الأمر php artisan make:controller HomeController وأضف إليه دالة باسم categories: public function categories() { $categories = Category::orderBy('name', 'asc')->get(); return view('layouts.categories', compact('categories')); } لا تنس استيراد النموذج Category: use App\Category; نستخدم النموذج Category في الدالة للحصول على لائحة التصنيفات مرتبة تصاعديا حسب الاسم؛ ثم نرسل النتيجة إلى العرض categories الموجود في المجلّد layouts ضمن مجلّد العروض resources/views. هذا العرض غير موجود لحدّ الساعة لذا يجب أن ننشئه. لكن قبل ذلك يجب أن ننهي إعداد المتحكّمات. سنضع في العرض سكربت jQuery -كما أشرنا سابقا- للحصول على لائحة بالتصنيفات الفرعيّة للتّصنيف المحدّد في القائمة المنسدلة العلوية. يرسل السكربت طلب Ajax إلى المسار api/category-dropdown/. ننشئ هذا المسار في ملفّ المسارات الخاصّ بواجهة التطبيقات البرمجيّة routes/api.php على النحو التالي: Route::get('/category-dropdown', 'ApiController@categoryDropDownData'); يمكن أن نستخدم نفس المتحكّم السابق (HomeController) ونعرّف دالة فيه كما يمكن أن ننشئ متحكّما جديدا. اخترنا الحلّ الأخير حتى نفصل بين التحكّم في المسارات العادية والمسارات المعدّة لتكون واجهة برمجية Application programming interface, API. نستخدم الأمر التالي لإنشاء متحكّم باسم ApiController: php artisan make:controller ApiController ثم نعدّل عليه: public function categoryDropDownData() { $category_id = Input::get('category_id'); $subcategories = App\Category::find($category_id)->sub_categories; return Response::json($subcategories); } يتلقى المسار api/category-dropdown/ معرّف التّصنيف المحدّد في القائمة ضمن متغيّر category_id. نستقبل المتغيّر المُرسَل من المستخدم بالدّالة get من الصّنف Input. بما أننا عرّفنا علاقات بين التصنيف والتصنيف الفرعي في النموذجين المقابليْن فيمكننا بسهولة العثور على التّصنيفات الفرعيّة لتصنيف؛ كلّ ما علينا فعله هو استخدام الدّالة sub_categories التي عرّفناها في الصّنف Category. تعثُر التعليمة التالية على جميع التّصنيفات الفرعيّة للتّصنيف ذي المعرّف category_id. $subcategories = Category::find($category_id)->sub_categories; في الأخير نرمّز التصنيفات الفرعيّة بصيغة Json ثم نرسلها في الإجابة. لا تنس استدعاء الأصناف التي استخدمناها وإضافتها إلى بداية الملف: use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Input; use App\Category; المحصّلة: <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Input; use App\Category; class ApiController extends Controller { public function categoryDropDownData() { $category_id = Input::get('category_id'); $subcategories = Category::find($category_id)->sub_categories; return Response::json($subcategories); } } إعداد العروض أكملنا الإعدادات من جهة النهاية الخلفيّة؛ تبقى فقط إعداد العرض لاستقبال البيانات وتقديمها للزائر. ننشئ لهذا الغرض عرضا باسم categories.blade.php في المجلّد layouts. يمدّد هذا العرض عرضا رئيسا Master view اسمُه app.blade.php، يوجد في نفس المجلّد. ينفّذ categories.blade.php مقطعًا باسم content معرّفًا في العرض الرّئيس ويوجد به محتوى الصفحة. @extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">Dashboard</div> <div class="panel-body"> <div class="form-group"> <label>Category:</label><br> <select class="form-control input-lg" name="category_id" id="category_id"> <option value="">Select Category</option> @foreach($categories as $category) <option value="{{ $category->id }}"> {{$category->name}}</option> @endforeach </select> </div> <div class="form-group"> <label>Subcategory:</label><br> <select class="form-control input-lg" name="subcategory_id" id="subcategory_id"> <option value="">First Select Category</option> </select> </div> </div> </div> </div> </div> </div> <script> $('#category_id').on('change', function(e){ var cat_id = e.target.value; //ajax $.get('/api/category-dropdown?category_id=' + cat_id, function(data){ //success data $('#subcategory_id').empty(); $('#subcategory_id').append('<option value=""> Please choose one</option>'); $.each(data, function(index, subcatObj){ $('#subcategory_id').append('<option value="' + subcatObj.id+'">' + subcatObj.name + '</option>'); }); }); }); </script> @endsection يستقبل المقطع content التصنيفات في المتغيّر categories ثم يستخدم دالة foreach التكراريّة لعرضها في القائمة المنسدلة: @foreach($categories as $category) <option value="{{ $category->id }}"> {{$category->name}}</option> @endforeach نضيف في المقطع script سكربت jQuery الذي سيتولى مزامنة محتوى القائمتيْن. يأخذ الجزء الأول من السكربت العنصر category_id الذي يمثّل القائمة الأولى ويستخدم الحدث on change لمراقبة التغيّرات عليه. نحتفظ بمعرّف التصنيف في المتغيّر cat_id: $('#category_id').on('change', function(e){ var cat_id = e.target.value; ثم يأتي دور نداء Ajax الذي يرسل المتغيّر cat_id إلى المسار api/category-dropdown/ ضمن المعطى category_id: $.get('/api/category-dropdown?category_id=' + cat_id, function(data) يخزّن النداء النتيجة في المتغيّر data. عند تلقّي الطلب ننفّذ ثلاثة أمور: حذف محتوى القائمة المنسدلة الثّانية لمحو المحتوى الموجود فيها (المحتوى الأصلي أو المحتوى المرتبط بعنصُر محدد سابقا في القائمة الأولى): $('#subcategory_id').empty(); ثم نضيف تعليمة جديدة تطلب من الزائر الاختيّار: $('#subcategory_id').append(' Please choose one'); ثم في الأخير نستخدم دالة each التكرارية في jQuery للمرور على البيانات (التصنيفات الفرعيّة) الواحدة تلو الأخرى وإضافتها إلى القائمة المنسدلة الثانيّة: $.each(data, function(index, subcatObj){ $('#subcategory_id').append('' + subcatObj.subcategory_name + '</option'); }); بالنسبة للعرض الرّئيس app.blade.php فقد استخدمنا هذا الملف. لم يتبق سوى زيارة الرابط categories/ لمعاينة النتيجة. ترجمة -وبتصرّف- للمقال Dependent Dropdown List with jquery in Laravel 5.1 لصاحبه Bill Keck.
  18. تعتمد الكثير من بيئات المؤسسات التي توجد بها حواسيب بنُظم تشغيل مختلفة (توزيعات لينكس أو وندوز) على خادوم Samba لتشارك الملفّات والطّابعات أو لتقديم خدمات الاستيثاق ضمن بيئاتها الداخليّة. سنشرح في هذا المقال كيفيّة إعداد تشارك الملفّات عبر خادوم Samba على توزيعة RedHat Enterprise Linux 7. يمكن الوصول للملفّات المتشاركة بهذه الطريقة من حواسيب عميلة Clients تستخدم واحدة من توزيعات لينكس أو نظام وندوز على حدّ السواء. تثبيت خادوم Samba نستخدم لأغراض الشّرح في هذا الدّرس حاسوبين يعملان بتوزيعة RedHat Enterprise Linux؛ نثبّت على الأول خادوم Samba وعلى الثاني عميلا لنفس الغرض. نعدّ الحاسوبيْن ضمن شبكة داخليّة على النحو التالي: خادوم Samba على العنوان 192.168.2.100 (سنشير إليه بالخادوم). عميل Samba على العنوان 192.168.2.200 (سنشير إليه بالعميل). راجع مقال إنشاء شبكة داخليّة افتراضية باستخدام Oracle VirtualBox لمعرفة كيفية إنشاء بيئة اختبار لهذا الغرض باستخدام برنامج Oracle VirtualBox. سجّل الدخول إلى الخادوم بالحساب الجذر root ثم حدّث النظام وثبّت الحزم التاليّة الضروريّة لعمل Samba: $ su - Password # yum update # yum install samba samba-client samba-common إعداد تشارك الملفات على Samba سنفترض أننا نريد إعداد مجلّد مشترك بين مجموعة من المستخدمين تعمل في نفس القطاع (الماليّة Finance). ننشئ لهذا الغرض مجموعة مستخدمين باسم finance ونضيف إليها مستخدمَيْن user1 وuser2؛ ثم ننشئ الملفّ المشترك (على الخادوم) ونضبط أذونه ونعطي ملكيّته للمجموعة finance (ننفّذ الأوامر التاليّة على الخادوم بصلاحيّات الجذر): # groupadd finance # useradd user1 # useradd user2 # usermod -a -G finance user1 # usermod -a -G finance user2 # mkdir /finance # chmod 0770 /finance # chgrp finance /finance إعداد SELinux و Firewalld للسماح لـ Samba تستخدم توزيعة RedHat Enterprise Linux وتوزيعات أخرى (مثل Centos وFedora) مبدئيا SELinux، وهو وحدة Module تعمل مع نواة لينكس، لتنفيذ سياسات أمنيّة للتحكّم في الوصول. لكي يسمح SELinux لـSamba بالوصول إلى مجلّد أو ملفّ فإن هذا المجلّد يجب أن يُعلَّم باللّصيقة Label المسمّاة samba_share_t. نستخدم الأمر semanage على النحو التالي لتعليم المجلّد finance/ الذي أنشأناه سابقا والمجلّدات المتفرّعة عنه بهذه اللّصيقة: # semanage fcontext -at samba_share_t "/finance(/.*)?" ثم ننفّذ الأمر restorecon لتطبيق التعديلات: # restorecon /finance يجب أيضا التأكد من الجدار الناري Firewall يسمح بمرور البيانات من Samba وإليه عبر الشبكة. نضيف في ما يلي Samba إلى الخدمات المسموح بها على الجدار الناري ثم نعيد تحميل الإعدادات: # firewall-cmd --permanent --add-service=samba # firewall-cmd --reload إعداد مشاركة الملفات على Samba نريد أن يكون أعضاء المجموعة finance قادرين على تصفّح محتويات المجلّد finance/ الموجود على الخادوم وحفظ ملفّات عليه. ستكون الملفّات أو المجلّدات التي ينشئها أعضاء المجموعة finance على الخادوم ذات أذون 0770 ومجموعة مالكة finance. نفتح ملفّ الإعداد etc/samba/smb.conf/ ونضيف المحتوى التالي إلى آخر الملفّ: [finance] comment=Directory for collaboration of the company's finance team browsable=yes path=/finance public=no valid users=@finance write list=@finance writeable=yes create mask=0770 force create mode=0770 force group=finance احفظ الملفّ ثم نفّذ الأمر testparm للتأكد من عدم وجود أخطاء في الصّياغة. يُظهِر الأمر أخطاء عند وجود أخطاء في صياغة ملف إعداد Samba وإلا فإن النتيجة تظهر ملخّصا بإعدادات Samba. إن أردت إنشاء مجلّد متاح للجميع لتشارك الملفات فيمكنك إضافة مقطع جديد (باسم مختلف) في ملفّ إعداد Samba مع تغيير قيمة public إلى yes بدلا من no وحذف تعليمتيْ valid users وwrite list. إضافة مستخدمين إلى Samba سنضيف المستخدميْن user1 و user2 الذين أنشأناهما سابقا إلى Samba باستخدام أمر smbpasswd الذي يتعامل مع قاعدة بيانات Samba الداخليّة. يطلُب الأمر عند تنفيذه إعطاء كلمة سرّ للمستخدم؛ سيحتاج المستخدم كلمة السر هذه في ما بعد للاتصال بالخادوم والدخول إلى مجلّد التشارك: # smbpasswd -a user1 # smbpasswd -a user2 أنهينا إعداد Samba لتشارك الملفات؛ بقي لنا إعادة تشغيل الخدمة لأخذ الإعدادات في الحسبان ثم إضافتها للخدمات التي تعمل مع بدء تشغيل النظام: # systemctl start smb # systemctl enable smb ثم نختبر الاتصال بخادوم تشارك الملفات (ستُطلب منك كلمة سرّ المستخدم user1): # smbclient -L localhost -U user1 نكون بهذه الخطوة قد أنهينا تثبيت خادوم Samba وإعداده لتشارك الملفات. سننتقل الآن إلى الحاسوب الثاني (العميل) لتجربة الاتصال عن طريقه بالخادوم. تركيب مجلد Samba على عميل لينكس نبدأ أولا بتثبيت حزم Samba الضرورية على العميل كالتالي: $ su - Password # yum update # yum install samba samba-client samba-common cifs-utils ثم نتأكّد بعد انتهاء التثبيت من وجود اتّصال بين العميل والخادوم بتنفيذ الأمر التالي (على العميل، 192.168.2.100 هو عنوان IP الخاصّ بالخادوم): # smbclient –L 192.168.2.100 -U user2 نفّذ نفس الأمر مع المستخدم user1. يمكننا الآن تركيب Mount المجلّد finance/ الموجود على الخادوم (ذي العنوان 192.168.2.100) كأي وسيط تخزين آخر كالتالي (ننشئ أولا مجلدا للتركيب عليه): # mkdir /mnt/samba # mount //192.168.2.100/finance /mnt/samba -o username=user1 سيُطلَب منك إدخال كلمة سر المستخدم user1 التي أعددتها سابقا. إن لم يظهر خطأ فهذا يعني أن التركيب تمّ على ما يُرام. يمكنك التأكد من ذلك بتنفيذ الأمر mount دون خيارات والبحث عن المجلّد finance في النتائج: # mount | grep "finance" مثال على النتيجة: //192.168.2.100/finance on /mnt/samba type cifs (rw,relatime,vers=1.0,cache=strict,username=user1,domain=LOCALHOST,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.2.200,unix,posixpaths,serverino,acl,rsize=1048576,wsize=65536,actimeo=1) ننفذ الأمر التالي لفصل المجلّد: umount /mnt/samba استخدم ملفّ etc/fstab/ إن أردت دوام تركيب المجلد. افتح الملفّ وأضف إليه السّطر التالي etc/fstab/: //192.168.2.100/finance /mnt/samba cifs credentials=/media/samba/.smbcredentials,defaults 0 0 حيثُ media/samba/.smbcredentials/ ملفّ مخفيّ يحوي سطرين؛ الأول اسم مستخدم يمكنه الوصول إلى المجلّد المتشارَك والثاني كلمة سر المستخدم: username=user1 password=PasswordForUser1 نحدّد صلاحيّات الملفّ media/samba/.smbcredentials/ بـ600 وملكيّته root:root (حتى لا يمكن لغير المستخدم الجذر الاطّلاع على فحواه). يمكن تركيب المجلّد مباشرة بعد تغيير ملفّ fstab بتنفيذ الأمر mount -a. يمكننا الآن تجربة إنشاء ملفّ في مجلّد التشارك (الذي يوجد الآن على مسار التركيب mnt/samba/): # touch /mnt/samba/testfile1.txt جرّب الدخول إلى الخادوم وسرد محتويات المجلّد finance/؛ ستجد أن ملفا جديدا باسم testfile1.txt قد أنشئ: # ls -l total 0 -rwxrwx---. 1 user1 finance 0 Jun 8 03:29 testfile1.txt لاحظ أن مالك الملفّ هو user1 والمجموعة المالكة هي finance؛ كما أن الأذون هي 770. يعود السبب في ذلك إلى إعدادات المشاركة التي ضبطناها في ملفّ الإعداد etc/samba/smb.conf (تعليمتا force group=finance و Force create mode=0770). ترجمة -وبتصرّف- للمقال Setting Up Samba and Configure FirewallD and SELinux to Allow File Sharing on Linux/Windows Clients – Part 6 لصاحبه Gabriel Cánepa.
  19. رأينا في درس كيف تنشئ مزود خدمة Service provider في Laravel طريقة عمل مزوّدات الخدمة والآليّة التي تتيح بها مرونة أكبر في التعامل مع الأصناف في Laravel. سنرى في هذا الدّرس كيفيّة الاستفادة من تعليمة inject@ لإعادة استخدام مزوّد خدمة في قالب Blade. استخدمنا في الدرس السابق مزوّد خدمة داخل متحكّم على النحو التالي: public function index(RocketShipContract $rocketship) { $boom = $rocketship->blastOff(); return view('demo.index', compact('boom')); } سنفترض أن لدينا ثلاثة عروض view1 وview2 إضافة للعرض index (كلّها في نفس المجلّد): العرض view1: <!-- تعليمات خاصّة بالعرض view1 --> @include('demo.index') <!-- تعليمات خاصّة بالعرض view1 --> العرض view2: <!-- تعليمات خاصّة بالعرض view2 --> @include('demo.index') <!-- تعليمات خاصّة بالعرض view2 --> العرض index: @extends('layouts.master') @section('content') {{ $boom }} @endsection نلاحظ أن العرض index يُضمَّن في العرضيْن view1 وview2. سنفترض أيضا أن لدينا الدالتين التاليتين في المتحكّم: public function function1(RocketShipContract $rocketship) { $boom = $rocketship->blastOff(); return view('demo.view1', compact('boom')); } public function function2(RocketShipContract $rocketship) { $boom = $rocketship->blastOff(); return view('demo.view2', compact('boom')); } تعمل الطريقة أعلاه دون مشكل؛ ولكن ماذا لو أضفنا مسارا ثالثا يستدعي عرضا آخر يُضمِّن العرض index؟ إذا اتبعنا نفس الطريقة فسنضيف دالة جديدة إلى المتحكّم كالتالي: public function function3(RocketShipContract $rocketship) { $boom = $rocketship->blastOff(); return view('demo.view3', compact('boom')); } يمكن أن نلاحظ بوضوح وجود الكثير من التكرار لاستخدام الصّنف RocketShipContract. لتلافي هذا الوضع يعرّف Laravel حقن الخدمة Service injection. يقوم المبدأ على استخدام التعليمة inject@ التي تتيح استدعاء صنف مّا مباشرة في العرض. بتطبيق هذا المبدأ في الحالة أعلاه نعدّل العرض index ليصبح كالتالي: @inject('boom', 'App\Helpers\Contracts\RocketShipContract') @extends('layouts.master') @section('content') {{ $boom->blastOff() }} @endsection تأخذ التعليمة inject@ معطييْن؛ الأول (boom في المثال) اسم المتغيّر الذي سنحتفظ فيه بكائن من الصّنف الممرّر في المعطى الثاني (RocketShipContract). يمكننا بعد ذلك استخدام المتغيّر واستدعاء الدالة. بالعودة إلى دوال المتحكّم فيمكننا تغييرها كالتالي: public function function1() { return view('view1'); } public function function2() { return view('view2'); } نرى بوضوح أن الطريقة الأخيرة تتيح قابليّةً أكبر لإعادة الاستخدام. حقن الخدمة - كما رأينا - وسيلة مفيدة لإعادة استخدام وظائف من التطبيق وتقديمها في عروض، إلا أنه لا يُنصَح باستخدامها إلا عند الحاجة؛ ومن الضروريّ عدم المبالغة في استخدامها. ترجمة -وبتصرّف- للمقال How to Use @inject in Blade in Laravel 5.1 لصاحبه Bill Keck.
  20. تعتمد بنية Laravel كثيرا على مزوّدي الخدمة Service providers لتحميل الأصناف Classes إلى الذاكرة مع بدء عمل التطبيق أو عند الحاجة إليها. يستخدم Laravel حاويةَ خدمة Service container لإدارة الاعتمادات بين الأصناف: ماهي الأصناف التي يعتمد عليها الصّنف الجديد للعمل؟ كيف يمكنه الحصول على كائن من هذه الأصناف؟ أين يوجد الصّنف وهل سبق لإطار العمل تحميلُه إلى الذاكرة؟ تمكننا بإعداد مزوّد خدمة وتعريفه إضافةُ صنف إلى حاويّة الخدمة ممّا يسهّل بالتالي استخدامه؛ إذ سيتعرّف إطار العمل على ما يحتاجه هذا الصّنف (أي الاعتمادات) وينشئه إن لم يكن موجودا سلفا. سنشرح في هذا الدرس أساسيّات إنشاء مزود خدمة في Laravel. نبدأ بإنشاء مسار سنستخدمه للتوضيح: Route::get('/demo', 'DemoController@index'); ثم ننشئ المتحكّم: php artisan make:controller DemoController ثم نعرّف الدالة index في المتحكّم: public function index() { return view('demo.index'); } تطلب الدالة index تقديم العرض demo.index؛ لذا سننشئ مجلّدا باسم demo في مجلّد العروض وننشئ فيه عرضا باسم index.blade.php. نضيف المحتوى التالي للعرض index: @extends('layouts.master') @section('content') <h1>Demo Page</h1> @endsection يمدّد العرض index عرضا رئيسا باسم master ضمن مجلّد العروض layouts. أنشئ العرض الرئيس إن احتجت لذلك. لم ندخل في صميم الموضوع لحد السّاعة، فقط ضبطنا بعض الإعدادات البدائية. تظهر عند الدخول إلى الرابط demo/ صفحة بها عنوان Demo Page. إعداد مزود خدمة سنشرح في الفقرات المواليّة كيفية إنشاء مزوّد خدمة. نبدأ بإنشاء مجلّد باسم Helpers في مجلّد app ثم ننشئ مجلّدا متفرعا عن Helpers باسم Contracts؛ بداخل الأخير ننشئ ملفّا باسم RocketShipContract.php نضيف إليه المحتوى التالي: <?php namespace App\Helpers\Contracts; Interface RocketShipContract { public function blastOff(); } لاحظ تعريف الصّنف أعلاه. عرّفنا RocketShipContract على أنه واجهة Interface. تعرّف الواجهات في PHP (ولغات برمجيّة أخرى) "عقدا" يجب على الأصناف التي تطبّق الواجهة اتّباعه. يجب على الأصناف التي تنفّذ Implement الواجهة المعرَّفة أعلاه أن تحوي دالة عمومية public باسم blastOff. نأتي الآن بعد تعريف الواجهة إلى الصّنف الذي ينفّذها. تحوي الواجهة خطوطا عريضة يأتي الصّنف المنفّذ لتخصيصها. ننشئ صنفا باسم RocketShip.php في مجلّد Helpers ونضيف إليه المحتوى التالي: <?php namespace app\Helpers; use App\Helpers\Contracts\RocketShipContract; class RocketShip implements RocketShipContract { public function blastOff() { return 'Houston, we have ignition'; } } يمكن أن تعالج في هذا الصّنف الكثير من الأمور، إلا أننا لا نودّ التعقيد. كل ما تفعله هذه الدالة هو إرجاع العبارة Houston, we have ignition. عرفنا الواجهة وكيفيةً لتنفيذها. يأتي الآن دور مزوّد الخدمة الذي سننشئه بالأمر التالي: php artisan make:provider RocketShipServiceProvider ينشئ الأمر ملفّا باسم RocketShipServiceProvider.php في المجلّد. تجد عند فتح الملف ما يلي (بعد نزع التعليقات): <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class RocketShipServiceProvider extends ServiceProvider { public function boot() { } public function register() { } } عدّل على الملف ليصبح على النحو التالي: <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Helpers\RocketShip; class RocketShipServiceProvider extends ServiceProvider { protected $defer = true; public function boot() { } public function register() { $this->app->bind('App\Helpers\Contracts\RocketShipContract', function(){ return new RocketShip(); }); } public function provides() { return ['App\Helpers\Contracts\RocketShipContract']; } } أولى التعديلات هي استدعاء الصّنف RocketShip: use App\Helpers\RocketShip; ثم إضافة الخاصّية التاليّة: protected $defer = true; يعني تمكين الخاصيّة defer$ أننا نطلُب ألا يُحمَّل الصّنف إلا عند الضرورة؛ مما يسهِم في الرفع من أداء التطبيق. ثم يأتي دور الدالة register التي تربط بين الواجهة والصّنف الذي ينفّذها. public function register() { $this->app->bind('App\Helpers\Contracts\RocketShipContract', function(){ return new RocketShip(); }); } يتيح هذا الربط إمكانية استخدام الواجهة في أي مكان تريده وسيستخدم مزوّد الخدمة الصنف الذي تربطه بها في دالة register تلقائيا. يزيد هذا الإعداد من مرونة التطبيق؛ فكل ما عليك فعله لتغيير سلوك الواجهة هو تعديل الصّنف المربوط بها. التعديل الأخير هو إضافة الدالة provides التي تُرجع الواجهة. يجب تعريف هذه الدالة عند تمكين الخاصيّة defer. الخطوة الأخيرة من إعداد مزوّد الخدمة هي إضافته إلى ملف إعداد التطبيق config\app.php ضمن مصفوفة providers (أدرجنا بعضا من محتويات الملف في الشفرة أدناه، مزوّد الخدمة الخاص بنا هو الأخير): /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\RocketShipServiceProvider::class, استخدام مزود الخدمة نعود الآن للمتحكّم DemoController ونعدّل الدالة index كالتالي: public function index(RocketShipContract $rocketship) { $boom = $rocketship->blastOff(); return view('demo.index', compact('boom')); } لا تنس استيراد الواجهة: use App\Helpers\Contracts\RocketShipContract; نمرّر للدّالة index كائنا من الصنف RocketShipContract (أي الواجهة). سيعرف Laravel تلقائيا أننا نريد الصّنف RocketShip الذي ينفّذ الواجهة. يعود السبب في ذلك إلى الربط الموجود في مزوّد الخدمة. نمرّر النتيجة المتحصّل عليها من استدعاء blastOff إلى العرض لتقديمه. نعدّل العرض لاستخدام المتغيّر الذي ممرناه إليه: @extends('layouts.master') @section('content') {{ $boom }} @endsection تظهر العبارة التاليّة في المتصفّح عند زيارة الرابط demo/: Houston, we have ignition سننشئ لتوضيح المرونة التي تتيحها مزودات الخدمة صنفا آخر ينفّذ الواجهة RocketShipContract. سنسمّي هذا الصنف الجديد RocketLauncher ونضعه في نفس المجلد الذي يوجد به صنف التنفيذ السابق (Helpers): <?php namespace app\Helpers; use App\Helpers\Contracts\RocketShipContract; class RocketLauncher implements RocketShipContract { public function blastOff() { return 'Houston, we have launched!'; } } لم نغيّر الكثير بالمقارنة مع الصنف RocketShip السّابق؛ فقط العبارة. نعود إلى مزوّد الخدمة ونغيّر ربط الواجهة كالتالي: public function register() { $this->app->bind('App\Helpers\Contracts\RocketShipContract', function(){ return new RocketLauncher(); }); } دون أن ننسى استدعاء الصّنف: use App\Helpers\RocketLauncher; ستلاحظ الآن تغير العبارة الظاهرة في المتصفّح عند زيارة الرابط demo/. لم نفعل أمورا معقّدة في الخطوات السّابقة إلا أن بالإمكان رؤية أن التعامل مع الواجهات مباشرة وترك التنفيذ الفعلي للأصناف المربوطة عبر مزوّد الخدمة يضيف الكثير من المرونة إلى بنية التطبيق. يوجد أمران ينبغي الانتباه إليهما عند التعامل مع مزوّدات الخدمة في Laravel: اجعل إضافة مزوّد الخدمة إلى ملف الإعداد config/app.php هي آخر الخطوات؛ بعد تعريف الواجهة ومزوّد الخدمة. إضافة صنف غير موجود إلى ملف الإعداد تؤدي إلى التشويش على عمل artisan. من الأفضل حذف مزوّد الخدمة ثم إنشاء واحد جديد بأداة artisan؛ بدلا من إعادة تسميّته. قد تؤدي إعادة التسميّة إلى عدم تحميل مزوّد الخدمة فلا يتعرّف عليه التطبيق. ترجمة -وبتصرّف- للمقال How to Create a Service Provider in Laravel 5.1 لصاحبه Bill Keck.
  21. الأحداث Events هي إجراءات يمكن لبرنامج التعرّف عليها ومن ثمّ التعامل معها. يمكن أن نأخذ مثالا على ذاك عملية تسجيل مستخدم في الموقع. عندما يكمل زائر ملء حقول الاستمارة الخاصّة بالتسجيل في موقع فإنه يكون قد فعّل حدثا يمكننا تسميّته "التسجيل في الموقع"؛ نستطيع بعد ذلك التعامل مع هذا الحدث لنقرّر ما التالي. كانت الطريقة القديمة تقضي بكتابة شفرة برمجية مباشرةً في المتحكّم Controller للتعامل مع الحدث؛ إلا أن توسّع التطبيق وزيادة الحاجة للتعامل مع أحداث جديدة يجعل الحاجة أكبر لطريقة جديدة أنظف وأكثر قابلية للتمديد. يستجيب Laravel لهذه الحاجة بالصنف Event والأدوات المساعدة المتعلّقة به. لا تقتصر الأحداث على إرسال البريد؛ إلا أن هذا الأخير وسيلة سهلة لشرح المبدأ. سنرى في هذا الدرس الخطوات اللازمة لتعريف حدث واستخدامه. نفترض أن لدينا جدولا ببيانات الاتصال ببعض الأشخاص؛ ونريد أن نرسل لأحدهم بريدا في كل مرة يُعرَض فيها الرابط contacthandler/id/ حيث id معرّف من نرسل له البريد. سنستخدم في هذا الجدول نفس المشروع الذي أنشأناه في الدرس السابق ونزيد عليه بإنشاء نموذج Model وجدول بيانات لحفظ جهات الاتّصال. تهيئة المشروع لا تدخل الخطوات الواردة في هذا العنوان في صميم إدارة الأحداث في Laravel؛ إلا أننا سنحتاجها للحصول على نتيجة مرئية للمثال الذي نبنيه. سنمرّ على الخطوات دون الكثير من التفاصيل لورودها في دروس سابقة. إنشاء النموذج Contact ننفذ الأمر التالي في مجلّد المشروع لإنشاء نموذج باسم Contact مع ملف التهجير الخاصّ به: php artisan make:model Contact -m نعدّل ملف التهجيرعلى النحو التالي: public function up() { Schema::create('contacts', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('subject'); $table->text('body'); $table->timestamps(); }); } أضفنا الحقول التي نريد استخدامها في جدول البيانات. ننفذ أمر التهجير لإنشاء الجدول: php artisan migrate بذر البيانات سنحتاج لبيانات اتصّال للتجربة عليها. نستخدم معمل نماذج Model factory لبذر الجدول contacts. ننشئ ملفا باسم ContactFactory.php في المسار /app/factories ونضيف إليه المحتوى التالي: <?php $factory->define(App\Contact::class, function (Faker\Generator $faker){ return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'subject' => $faker->word, 'body' => $faker->text ]; }); يستدعي معمل النماذج مكتبة Faker لإدراج بيانات في جدول contacts عبر النموذج Contact. ثم ننشئ صنفا للبذر: php artisan make:seeder ContactTableSeeder نستورد النموذج Contact في صنف البذر ContactTableSeeder ونستخدم معمل النماذج لتوليد بيانات عشوائية: <?php use Illuminate\Database\Seeder; use App\Contact; class ContactTableSeeder extends Seeder { public function run() { Contact::truncate(); factory(Contact::class, 50)->create(); } } نأتي الآن للخطوة الأخيرة قبل تنفيذ أمر البذر وهي تحرير الملف DataBaseSeeder.php ليستدعي الصّنف ContactTableSeeder في الدالة run: $this->call(ContactTableSeeder::class); ثم ننفذ البذر: php artisan db:seed المسار والمتحكم نفتح ملف routes.php لإضافة مسار للرابط contacthandler/id/: Route::get('/contacthandler/{id}', 'HomeController@contactHandler'); يحيل ملف المسارات الطلب على الرابط إلى الدالة contacthandler في المتحكّم HomeController. نذهب للمتحكم ونضيف هذه الدالة: public function contactHandler($id) { } نترك الدالة خاوية لحد الساعة. التعامل مع الأحداث جهزنا المشروع ويمكننا الآن الانتقال إلى الأحداث والتعامل معها. نذكّر بأننا نريد إرسال بريد لصاحب جهة الاتصال عند طلب رابط به معرّفه. مثلا نرسل بريدا إلى جهة الاتصال ذات المعرّف 6 عند زيارة يُعرَض فيها الرابط contacthandler/6/. نحصُل على البريد الإلكتروني الذي سنرسل إليه الرسالة من خلال النموذج Contact؛ إضافة لاسمه وموضوع الرسالة وفحواها. ولدنا هذه البيانات تلقائيا أثناء البذر السابق. تعريف الحدث يتلخّص الأمر في أننا سننفّذ إجراءً عند وقوع الحدث "زيارة رابط المعرّف الخاص بجهة الاتصال". سنسميّ الصنف الموافق للحدث بـ ContactHandlerAction. نستعين بأداة artisan لإنشاء الحدث على النحو التالي: php artisan make:event ContactHandlerAction ستجد أن الأداة أنشأت الملف ContactHandlerAction.php على المسار app/Events وأضافت إليه المحتوى التالي (بعد نزع التعليقات): class ContactHandlerAction extends Event { use SerializesModels; public function __construct() { } public function broadcastOn() { return []; } } سنضيف خاصيّة إلى الصّنف ليصبح كالتالي: class ContactHandlerAction extends Event { use SerializesModels; public $contact; public function __construct($contact) { $this->contact = $contact; } public function broadcastOn() { return []; } } أضفنا متغيّرا جديدا لحفظ بيانات الاتصال واستخدامها عند الحاجة. نعود لدالّة contactHandler في المتحكّم HomeController ونعدّلها لتصبح كما يلي: public function contactHandler($id) { $contact = Contact::find($id); event (new ContactHandlerAction($contact)); return view('mail.success_view'); } نحتاج لاستيراد الصنفين المستخدمين في الدالة (النموذج والحدث) إلى المتحكّم: use App\Contact; use App\Events\ContactHandlerAction; عند استدعاء الدالة contactHandler نأخذ المعطى id المُمرَّر في الرابط ونحيله إلى الدالة find في النموذج Contact من أجل العثور عليه في قاعدة البيانات. في التعليمة التالية يبدأ عمل آلية إدارة الأحداث في Laravel: نستدعي الدالة المساعدة event ونمرّر لها كائنا من الحدث ContactHandlerAction الذي أنشأناه قبل قليل. نستخدم جهة الاتصال التي عثرنا عليها contact$ من أجل بناء الحدث. يعني هذا أننا أطلقنا الحدث “زيارة رابط المعرّف الخاص بجهة الاتصال” وزوّدناه بجهة الاتصال المعنيّة. مالذي يترتّب عن إطلاق الحدث؟ هذا هو ما سنراه الآن. التعامل مع الحدث توجد في Laravel ما تُعرَف بالمستمعات Listeners ومهمتها انتظار وقوع أحداث لتبدأ عملها. نعود لأداة artisan وننفّذ الأمر التالي: php artisan make:listener EmailContactListener --event="ContactHandlerAction" ينشئ الأمر صنفا باسم EmailContactListener للاستماع للأحداث ContactHandlerAction. يوجد الصّنف EmailContactListener في المجلّد /app/Listeners ومحتواه التالي: class EmailContactListener { public function __construct() { // } public function handle(ContactHandlerAction $event) { // } } لاحظ أن الدالة handle تنتظر معطى من نوع ContactHandlerAction. يشغّل المستمِع الدالة handle بعد تلقيه إخطارا بإطلاق الحدث ContactHandlerAction. سنعود بعد قليل للدالة handle بعد أن نعرّف الآلية التي سيُخطَر بها المستمِع بوقوع الحدث. ربط الحدث بآلية التعامل معه يوفّر Laravel مزوّد خدمة خاصًّا بالأحداث اسمه EventServiceProvider يوجد في المجلّد /app/Providers. إن فتحت الملف EventServiceProvider.php ستجد التالي (إن لم يسبق لأحد التعديل عليه): class EventServiceProvider extends ServiceProvider { protected $listen = [ 'App\Events\ContactHandlerAction' => [ 'App\Listeners\EmailContactListener', ], ]; public function boot(DispatcherContract $events) { parent::boot($events); // } } ما يهمّنا هنا هو الخاصيّة listen$. نعدّلها لتصبح على النحو التالي: protected $listen = [ 'App\Events\ContactHandlerAction' => [ 'App\Listeners\EmailContactListener', ], ]; يمكن تعريف أكثر من مستمِع لنفس الحدث (لاحظ أن قيمة App\Events\ContactHandlerAction في الخاصيّة هي مصفوفة Array). تمكننا الآن العودة إلى المنصِت بعد أن أعلمنا Laravel بالرابط بينه والحدث. تنفيذ آلية التعامل مع الحدث نعدّل الدالة handle لتصبح على النحو التالي: public function handle(ContactHandlerAction $event) { $data = [ 'name' => $event->contact->name, 'email' => $event->contact->email, 'subject' => $event->contact->subject, 'body' => $event->contact->body ]; Mail::send('mail.email_view',$data , function($message) use($data) { $message->from('hsoub@academy.com', 'Hsoub Academy'); $message->to($data['email'])->subject($data['subject']); }); if (Mail::failures()){ throw new EmailSendingException; } } نعرّف مصفوفةً باسم data$ تحوي اسم جهة الاتصال، بريدها الإلكتروني، موضوع الرسالة وفحواها. نعثُر على هذه البيانات من الكائن contact الذي مررناه للحدث event عند زيارة الرابط. نستخدم هذه البيانات لجدولة إرسال بريد عبر الدالة Mail::send. تأخذ الدالة Mail::send عرضا View تستخدمه لتنسيق الرسالة والبيانات التي أخذناها من جهة الاتصال. ثم نختبر أخطاء إرسال البريد الإلكتروني بالدالة Mail::failures؛ فإن وجدت أخطاء استخدمنا استثناءً Exception مخصّصًا لمعالجتها. يمكنك الحصول على هذا الاستثناء في الملف المرفق أو مراجعة الدرس الخاص بالاستثناءات. نحن الآن جاهزون تقريبا لتجربة الأحداث والتعامل معها في Laravel. بقي لنا إنشاء بضعة عروض لحاجات تطبيقنا المختلفة: mail.success_view في المتحكّم، mail.email_view لتنسيق الرسالة، إضافة لعروض أخرى قد تحتاجها أثناء تخصيص الاستثناء. يمكنك الحصول عليها في الملف المرفق. بقيت لنا خطوة أخيرة قبل اختبار التطبيق: إعداد Laravel لإرسال البريد. يوفّر Laravel وسيلة لمحاكاة إرسال البريد يمكننا استخدامها للتجربة. ابحث في ملف متغيّرات النظام env. عن المعطى MAIL_DRIVER وعيّن قيمته بـlog: MAIL_DRIVER=log نطلُب بهذه الطريقة من Laravel كتابة البريد في ملفّ السجل بدلا من إرساله. هذه الطريقة مفيدة أثناء الاختبار. افتح الرابط التالي في المتصفّح (ضع اسم المضيف المناسب بدلا من laraveltips): http://laraveltips.dev/contacthandler/45 ملحوظة: لا يتعدّى عدد جهات الاتصال التي أضفناها الخمسين؛ سيؤدي البحث عن جهة اتصال بمعرّف أكبر من 50 إلى خطأ يمكنك التقاطه في استثناء كما فعلنا في درس استخدام الاستثناءات Exceptions المخصَّصة في Laravel. ثم اذهب إلى ملف السجل storage/logs/laravel.log للتأكد من نجاح معالجة الحدث؛ ستجد في آخر الملف ما يشبه التالي: [2016-03-30 22:16:29] local.DEBUG: Message-ID: <8c79163440df2d3b5bbb3efd97ad549f@laraveltips.dev> Date: Wed, 30 Mar 2016 22:16:29 +0000 Subject: distinctio From: Hsoub Academy <hsoub@academy.com> To: fFranecki@example.net MIME-Version: 1.0 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable يليه محتوى العرض المستخدم لتنسيق الرسالة (mail.email_view في المثال). إن حدثت مشكلة في إرسال البريد فسيتولّى الاستثناء -إن أعددتَه - إظهار العرض المناسب للزائر؛ وفي حال سريان الأمور على الوجه الأمثل فسيُظهر المتحكّم العرض mail.success_view للدلالة على نجاح الإرسال. الملف المرفق: الأحداث في Laravel.zip ترجمة -وبتصرّف- للمقال Eventing in Laravel 5.1 لصاحبه Bill Keck.
  22. يكثُر استخدام المخططات البيانيّة في مواقع الويب لتقريب المعلومة للزّائر. سنرى في هذا الدرس كيفية إنشاء مخطّطات بيانية في تطبيق Laravel باستخدام مكتبة Morris.js. سنفترض أن لديك مشروع Laravel معدًّا وجاهزًا للعمل، وقاعدة بيانات مضبوطة. تتلخّص خطوات هذا الدرس في ما يلي: إنشاء نموذج Model للتطبيق إضافة للتهجيرات Migrations المرتبطة به. إنشاء متحكّمات Controllers وعروض Views لإظهار البيانات للزائر. إنشاء معمل نماذج Model Factory لإضافة بيانات إلى قاعدة البيانات من أجل عرضها على هيئة مخططات بيانية. استخدام Mirror.js لتنسيق البيانات وعرضها في مخطّطات. النموذج نبدأ بإنشاء نموذج نُسمّيه Widget؛ نستخدم الخيار m- لإنشاء تهجير في نفس الوقت: php artisan make:model Widget -m نفتح ملف التهجير ونعدّله ليصبح على النحو التالي: class Widget extends Model { // protected $fillable = ['widget_name']; public $timestamps = false; } أعطينا القيمة false للمتغيّر timestamps$ حتى لا يُملأ الحقلان created_at وupdated_at تلقائيا في جدول البيانات. سنتستخدم معمل النماذج في ما بعد لملْء هذين الحقلين. ننتقل لملف التهجير ونضيف حقلا جديدا باسم widget_name إلى الجدول في قاعدة البيانات؛ تصبح الدالة up على النحو التالي: public function up() { Schema::create('widgets', function (Blueprint $table) { $table->increments('id'); $table->string('widget_name')->unique(); $table->timestamps(); }); } انتهينا الآن من العمل على النموذج والتهجير. يمكنك مراجعة مقال كيف تنشئ نموذجا Model في Laravel للمزيد. نفذ التهجير بالأمر التالي في مجلد المشروع: php artisan migrate تهيئة التطبيق سنحتاج لمسارات للوصول إلى صفحات الموقع، عروض لإظهار البيانات، ومتحكمات للربط بين المسارات، النماذج والعروض. سنستخدم الأمر التالي الذي يُنشئ نموذجا للمستخدمين مع مسارات وعروض جاهزة لتسجيل المستخدمين في قاعدة البيانات وللولوج إلى التطبيق: php artisan make:auth نفتح ملفّ العرض app.blade.php الموجود في المجلّد resources/views/layouts لتحريره. هذا هو المخطّط الرئيس لعروض Blade الذي أنشأه الأمر السابق. ابحث عن التعليق التالي: <!-- Left Side Of Navbar --> والذي توجد أسفله العناصر اليسرى من القائمة العلوية. سنضيف رابطين جديدين إلى القائمة، الأول للائحة بجميع التسجيلات الموجودة في الجدول widgets (كما فعلنا في درس استخدام الاستثناءات Exceptions المخصَّصة في Laravel) والثاني لصفحة المخطَّط البياني: <li><a href="{{ url('/widgets') }}">Widgets</a></li> <li><a href="{{ url('/charts') }}">Chart</a></li> المسارات أضفنا أعلاه رابطين؛ إلا أن المسارات التي تتولى الإجابة عنهما غير موجودة حتى الآن. نفتح ملف routes.php الموجود على المسار app\Http لإضافتها: Route::group(['middleware' => 'web'], function () { Route::auth(); Route::get('/charts', 'HomeController@charts'); }); Route::group(['middleware' => 'web'], function () { Route::auth(); Route::get('/widgets', 'HomeController@widgets_list'); }); Route::group(['middleware' => 'web'], function () { Route::auth(); Route::get('/widget/{id}', 'HomeController@widget_detail'); }); جعلنا المسارات محمية لكي يصل إليها المستخدمون المسجّلون فقط. الجديد هنا (مقارنة مع درس الاستثناءات المخصّصة المُشار إليه أعلاه) هو إضافة المسار charts/ الذي تتولى دالة charts في المتحكّم HomeController الإجابة عنه. بالنسبة للمسارين الآخرين فأحدهما (widgets/) يعرض أسماء جميع التسجيلات في الجدول widgets والآخر ({widget/{id/) يعرض اسم التسجيلة ذات المعرّف id. المتحكم يوجد لدينا متحكّم واحد ناتج عن تطبيق أمر artisan الأخير. هذا المتحكّم هو HomeController. نفتح ملف المتحكّم HomeController.php الموجود على المسار app/Http/Controllers لتحريره. نضيف الدوال المذكورة في ملف المسارات السابق: public function widgets_list() { $widgets = Widget::Paginate(10); return view('widgets.widgets',array('widgets' => $widgets)); } public function widget_detail($id) { $widget = Widget::find($id); return view('widgets.widget_detail', array('widget_name' => $widget->widget_name)); } public function charts() { $yearCounts = Widget::select(DB::raw('year(created_at) as year'), DB::raw('count(widget_name) as `count`')) ->groupBy('year')->get(); $chartData = (sizeof($yearCounts) > ) ? $yearCounts : null; if ($chartData) { return view('widgets.widgets_chart',array('chartData' => $chartData)); } else { return view('widgets.nowidgets'); } } تبحث الدالة widgets_list عن محتويات الجدول widgets باستخدام دوال النموذج Widget. لاحظ أننا نريد تقسيم النتائج إلى صفحات بحيث تحوي كل صفحة عشرة نتائج، لذا استدعينا الدالة Paginate في الصّنف (النموذج) Widget. نُرسل ما عثرنا عليه إلى العرض widgets الموجود في مجلّد widgets ضمن مجلد العروض resources/views. في الدالة widget_detail نبحث عن التسجيلة ذات المعرّف id ونظهرها في العرض widget_detail ضمن مجلد widgets السابق. نأتي الآن للدالة charts التي تهيّئ البيانات لعرضها على هيئة مخطط بياني. نريد أن نعثُر على عدد التسجيلات المدرجة في جدول قاعدة البيانات مجموعة حسب السنة؛ على النحو التالي مثلا: year-|--count 2016 | 16 2015 | 12 2014 | 15 2013 | 11 يمكّننا استعلام SQL التالي من الحصول على ما نريد: SELECT COUNT(widget_name) AS count, YEAR(created_at) AS year FROM widgets GROUP BY year; يمكن أن ننفّذ هذا الاستعلام باستخدام الواجهة DB كالتالي: DB::select('SELECT COUNT(widget_name) AS count, YEAR(created_at) AS year FROM widgets GROUP BY year;'); أو على النحو التالي: Widget::select(DB::raw('year(created_at) as year'), DB::raw('count(widget_name) as `count`')) ->groupBy('year')->get(); نحصُل في كلتا الحالتين على مصفوفة Array من عمودين بناتج تنفيذ الاستعلام. راجع مقال استخدام Eloquent ORM للتعامل مع قاعدة البيانات في Laravel 5 حول الموضوع. لا تنس استيراد النموذج Widget والواجهة DB قبل استخدامهما في المتحكّم: use App\Widget; use DB; نفحص المصفوفة المتحصَّل عليها؛ في حال وجود نتائج نرسلها إلى العرض widgets_chart ضمن المجلد resources/views/widgets؛ وإلا نظهر العرض nowidgets الموجود في نفس المجلّد. سنؤخّر الحديث عن العروض إلى أن ننشئ معمل نماذج لملْء جدول قاعدة البيانات. معمل النماذج وبذر قاعدة البيانات تحدثنا في درس عن استخدام معمل النماذج Model factory لتوليد بيانات الاختبار، سنعمل بنفس المبدأ هنا. ننشئ ملفا باسم WidgetFactory.php على المسار database/factories ونضيف إليه المحتوى التالي: <?php $factory->define(App\Widget::class, function (Faker\Generator $faker){ return [ 'widget_name' => $faker->unique()->word, 'created_at' => $faker->dateTimeBetween($startDate = '-4 years', $endDate = 'now') ]; }); تنشئ الشفرة السابقة تسجيلا في جدول البيانات widgets بتوليد محتويات الحقلين widget_name وcreated_at. لاحظ أننا نستخدم مكتبة Faker. حدّدنا تاريخ created_at بالمجال من قبل أربع سنوات إلى الآن. يأتي الآن دور بذر البيانات. ننشئ صنفا لبذر الجدول widgets: php artisan make:seeder WidgetTableSeeder نفتح الملف database/seeds/WidgetTableSeeder.php ونحرّر الدالة run ليصبح كالتالي: public function run() { Widget::truncate(); factory(Widget::class, 50)->create(); } نحذف محتوى الجدول لكي لا يحصُل تعارض مع البيانات الجديدة التي سنولّدها؛ ثم نستخدم معمل النماذج لإدراج 50 تسجيلة. لا تنس استيراد النموذج Widget: use App\Widget; يمكننا الآن تنفيذ الأمر التالي لبذر البيانات في الجدول: php artisan db:seed --class=WidgetTableSeeder توجد طريقة أخرى هي التي سنستخدمها. نحرّر الملف DataBaseSeeder.php الموجود على نفس مسار WidgetTableSeeder.php ونضيف ما يلي إلى الدالة run: Widget::unguard(); $this->call(WidgetTableSeeder::class); Widget::reguard(); نطلُب نزع الحماية عن الجدول قبل بذره ثم نعيدها إليه. يؤدي النداء call مهمّة البذر. يتيح الملف DataBaseSeeder إمكانية تنفيذ أصناف بذر عدّة بنفس الأمر التالي: php artisan db:seed بقيت الخطوة الأخيرة وهي إنشاء العروض. العروض نأتي الآن للعروض التي ورد ذكرها في المتحكّم دون أن تكون أنشئت. ستجد العروض في الملف المرفق. نشير إلى استخدام التعليمة التاليّة في العرض widgets: {!! $widgets->render() !!} تظهر هذه التعليمة روابط للتنقل بين التسجيلات إذا كان عددها يفوق العشرة. هذا ناتج عن استخدامنا للدالة Paginate في المتحكم أعلاه. نأتي الآن لشرح العرض widgets_chart الذي يتضمن المخطّط البياني الذي نهدف لإنشائه في هذا الدرس. @extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">Chart</div> <div class="panel-body"> <div id="chart" style="height: 250px;"> </div> </div> </div> </div> </div> </div> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script> <script> var data = <?php echo json_encode($chartData) ?>; Morris.Line({ element: 'chart', data: data, xkey: 'year', ykeys: ['count'], labels: ['widgets created'] }); </script> @endsection يمدّد العرضُ العرض الرئيس layouts.app ويعرّف محتوى المقطع content. يوجد في هذا المقطع العنصُر التالي ذي المعرّف chart: <div id="chart" style="height: 250px;"> </div> سيُعرَض المخطّط البياني في هذا العنصُر. تأتي بعد ذلك اعتماديات مكتبة Morris: <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script> وأخيرا السكربت الذي سيرسُم المخطّط: <script> var data = <?php echo json_encode($chartData) ?>; Morris.Line({ element: 'chart', data: data, xkey: 'year', ykeys: ['count'], labels: ['widgets created'] }); </script> يستقبل السكربت البيانات التي أرسلتها الدالة charts المعرّفة في المتحكم HomeController ويحوّلها إلى صيغة JSON التي تستخدمها مكتبة Morris: var data = <?php echo json_encode($chartData) ?>; ثم نستدعي الدالة Line في مكتبة Morris: Morris.Line({ element: 'chart', data: data, xkey: 'year', ykeys: ['count'], labels: ['widgets created'] }); نمرّر للدالة معرّف العنصُر الذي سنرسم في المخطّط (المعطى element)، البيانات التي نودّ رسمها (data)، عنصُر المصفوفة الذي سيكون في المحور الأفقي للمخطّط (xkey) والعنصُر-أ و العناصر- الذي سيكون على المحور العمودي (ykeys)؛ إضافة للصيقة Label تظهر في المخطّط. نذهب الآن لرابط المشروع؛ بالنسبة لي الرابط هو: http://laraveltips.dev نسجّل مستخدما جديدا (register) ثم نضغط على الرابط Chart لنجد النتيجة التالية (قد يختلف المخطّط البياني عن الموجود في الصورة بسسب أن البيانات مولَّدة عشوائيا). الملف المرفق: laravelchart.zip ترجمة -وبتصرّف- للمقال Charts in Laravel 5.1 with Morris.js لصاحبه Bill Keck.
  23. توفّر Red Hat امتيازات خاصّة للمطوّرين يمكنك الاستفادة منها والتسجيل للحصول على رابط تنزيل ملف ISO. يمكن استخدام نفس الحساب للاشتراك والحصول على التحديثات من الشركة في إطار الاستخدام الشّخصي. ستحتاج قبل البدء في تثبيت توزيعة Red Hat Enterprise Linux إلى تنزيل الملفّ من موقع Red Hat. سنشرح في هذا المقال طريقة التثبيت باستخدام برنامج Oracle VirtualBox للأنظمة الوهمية الذي نفترض تثبيته لديك. يُنصَح بتوفّر الميزات التالية -على الأقل- من أجل تشغيل RHEL على النحو الأمثل: جهاز بمعماريّة 64 بت بعتاد يدعم الأنظمة الوهميّة (Intel VT-X أو AMD-V). نظام تشغيل يدعمه Virtualbox مثل Windows 7 ،OS X 10.8 ،Ubuntu 12.04 أو أحدث. ذاكرة عشوائية قدرها 8 غيغا بايت. مساحة قرص صلب متاحة قدرها 24 غيغابايت. إعداد VirtualBox لتثبيت RHEL نتوقّف قبل البدء في خطوات التثبيت لشرح مصطلحين يردان كثيرا عند الحديث عن الأنظمة الوهمية: المُضيف Host والضّيف Guest. في هذا الإطار نطلق عبارة المُضيف على نظام التشغيل الذي نُثبّت عليه برنامج VirtualBox بينما نعني بالضّيف نظامَ التشغيل الذي سنثبته داخل VirtualBox. هذا الأخير ليس له وجود بدون VirtualBox الذي يلعب دور الوسيط بينه وبين نظام التشغيل المثبت فعلا. في حالتي النظام المُضيف هو أوبنتو 14.04 بينما سنثبت RHEL كنظام ضيف. نبدأ بتشغيل VirtualBox ثم نضغط على زر New (جديد) لإنشاء نظام وهمي جديد؛ تظهر نافذة جديدة. نختار اسم RHEL للجهاز الوهميّ الجديد وسيتعرّف Virtualbox تلقائيا على نوع (Type) نظام التشغيل وإصداره (Version). ننتقل للخطوة الموالية (Next) لاختيار مقدار الذاكرة العشوائيّة للنّظام الوهميّ؛ الحد الأدنى هو 2096 ميغابايت (2 غيغابايت). لديّ ذاكرة عشوائية قدرها 8 غيغابايت خصصتُ منها 4 للجهاز الوهمي الذي ستعمل عليه RHEL. ثم يأتي دور القرص الصلب الوهمي. حدّد خيار Create a virtual hard disk now (أنشئ قرصا صلبا وهميًّا الآن) ثم انقر على الزر Create (أنشئ). حدّد خيار (VDI (VirtualBox Disk Image وهي صيغة الأقراص الصّلبة الوهمية المستخدمة مبدئيا في Virtualbox. انقر على التالي للخطوة الموالية. يوجد خياران؛ إما حجز مساحة ثابتة مقدّما Fixed size أو جعلها ديناميكية Dynamically allocated. في الحالة الأولى تُحجز مساحة (20 غيغابايت مثلا) على القرص الصّلب للنظام المضيف وتُخصَّص للنظام الضّيف؛ أما في الحالة الثانية فإن استغلال القرص الصلب للنظام المضيف يكون حسب الحاجة في حدود المساحة المخصَّصة. الطريقة الأولى أبطأ أثناء الحجز وقد يستغرق إعداداها بضعة دقائق، ولكنها أسرع في الاستعمال؛ والثانية أسرع أثناء الإعداد وأبطأ أثناء الاستخدام. تقترح النافذة التالية مسارا على النظام المضيف لحفظ القرص الوهميّ؛ يمكنك تغييره إن شئت. يوجد أيضا خيار لمساحة القرص الصّلب الوهمي. يجب أن تخصّص على الأقل 20 غيغابايت للنظام الوهميّ ويمكن أن تزيدها إن كنت تخطّط للعمل بكثافة على النظام (تثبيت الكثير من البرامج مثلا). بالنسبة لي اخترتُ 30 غيغابايت. نحصُل بالوصول إلى هذه النقطة على جهاز وهميّ قاعدي. نحتاج لبضعة إعدادات أخرى. ملحوظة: ربما تلاحظ وجود أكثر من جهاز وهمي في الصّورة أعلاه. يتيح VirtualBox إنشاء أكثر من نظام ضيف على نفس المُضيف. اختر الجهاز الوهمي الذي أنشأناه للتّو (RHEL) من القائمة الموجودة في يسار الشاشة ثم انقر على خانة الإعدادات العامة General في يمين الشاشة. تظهر نافذة الإعدادات؛ اختر منها تبويب Advanced. يسمح هذا الإعداد بتشارك الحافظة Clipboard بين النظامين الضّيف والمضيف. سنحتاج لتفعيل إضافات VirtualBox حتى يمكننا النسخ واللّصق بين الاثنين (خطوة موالية). يعمل الجهاز الوهمي مبدئيّا على معالج واحد؛ إلا أن بإمكانك جعله يعمل على أكثر من معالج إن كانت لديك معالجات أو أنوية متعدّدة. اختر System (النظام) من القائمة اليسرى في نافذة الإعدادات العامّة ثم تبويب المعالج Processor في الجانب الأيمن. اختر العدد المناسب بالنسبة لك (اخترتُ أن يعمل النظام الوهمي على معالجين؛ من أصل 4 متوفّرة). نأتي الآن لإعداد الجهاز الوهمي للإقلاع على ملف ISO الذي نزّلناه من موقع Red Hat. اختر Storage (التخزين) من القائمة اليسرى في نافذة الإعدادات العامّة ثم في الجانب الأيمن من النافذة، في مجموعة Storage tree (تفرعات التخزين) اختر أيقونة القرص الضوئي CD الموجودة تحت Controller: IDE (حيثُ توجد عبارة فارغ Empty دلالة على أننا لم نخصّص الإعداد بعد). انقُر على أيقونة القرص الضوئي ضمن مجموعة Attributes (الخصائص) يمينَ الشّاشة وانقر على خيار Choose Virtual Optical Disk File (اختر ملفّ قرص ضوئي وهمي). ستظهر نافذة انتقل عبرها إلى مكان ملف توزيعة RHEL الذي نزلته سابقا. يظهر اسم الملف rhel-server-7.2-x86_64-dvd.iso الآن في تفرّعات التخزين أسفل عبارة Controller: IDE بدلا من عبارة فارغ (Empty). بقيت لنا خطوة أخيرة قبل البدء في تثبيت التوزيعة؛ هذه الخطوة اختيارية ولكنها مفيدة. سننشئ مجلّدا مشتركا بين النظامين، الضيف والمضيف وهو ما يسهّل تشارك الملفات بين الاثنين. اختر Shared folders (المجلدات المشتركة) ضمن القائمة اليسرى في نافذة الإعدادات العامّة للجهاز الوهمي RHEL. انقر على زر الإضافة الموجود في يمين النافذة لتظهر نافذة صغيرة. حدّد في الحقل الأول Folder path مسار المجلّد على النظام المضيف ثم حدد له اسما بدون مساحات في الحقل الثاني. حدّد خيار Auto-mount (تركيب تلقائي) لكي يُركَّب الملف مع تشغيل النّظام. نحن الآن جاهزون لتثبيت RHEL. تثبيت RHEL على VirtualBox نختار النظام الوهميّ الذي أعددناه في الخطوات السابقة ثم ننقر على زر Start (ابدأ). يحمّل الجهاز الوهميّ ملف التوزيعة من القرص الضّوئي الوهميّ وتبدأ بالتالي عمليّة التثبيت كما لو كنا أمام جهاز حاسوب حقيقي. يظهر خياران في الشاشة؛ الأول لبدء تثبيت التوزيعة مباشرة والثاني لاختبار ملف ISO ثم بدء التثبيت. نختار الثاني للتحقّق من أنّه لا توجد عيوب في الملفّ. ملحوظة: اضغط على زرّ Ctrl الأيمن في لوحة المفاتيح لجعل المؤشّر يعمل داخل الإطار المخصّص للنظام الضّيف؛ ثم اضغط عليه مرةً أخرى إذا أردت إعادة المؤشّر للنظام المضيف. ينفّذ النظام بضعة عمليات للتحقّق من سلامة ملف ISO الخاصّ بالتوزيعة ثم تبدأ عمليّة التثبيت. الخطوة الأولى هي اختيار اللغة وتموضع لوحة المفاتيح. يأتي بعد اختيّار اللغة ملخَّص للتثبيت؛ يمكنك فيه اختيّار منطقة التوقيت، اللّغة، لوحة المفاتيح وإعدادات أخرى. بالنقر على إحدى الفقرات تُفتَح نافذة لإعدادها ثم بعد الانتهاء من الإعداد يوجد زر Done (تمّ) للعودة للملخَّص. سنذهب لزرّ Software selection (اختيّار البرامج) وننقر عليه. نحدّد خيار Server with Gui (خادوم بواجهة رسوميّة) من لائحة Base Environment (البيئة القاعديّة) الموجودة يسار الشاشة. توجد يمين الشاشة لائحة بإضافات برمجية (حزم برامج) يمكن الاختيار بينها للتثبيت مع البيئة القاعديّة المحدّدة. اختر من بينها Development tools (أدوات تطوير) التي سنحتاجها لاحقا ثم انقر زرّ Done للعودة إلى الملخّص. انقر على زرّ Network amd Hostname (الشبكة واسم المضيّف) في نافذة الملخّص لإعداد الاتّصال بالشبكة. اختر واجهة الشبكة من اللّائحة على اليسار ثم فعّل الاتّصال بالنقر على زر On. عطّل بالعودة إلى نافذة الملخّص استخدامَ KDump بنزع تحديد Enable kdump في نافذة الإعداد الخاصّة به من أجل ربح قليل من الذاكرة العشوائية. بقي لنا إعداد القرص الصّلب الذي سنثبّت عليه النظام لذا نختار زرّ Installation destination (وجهة التثبيت) في نافذة الملخّص. بما أن لدينا قرصا وهميّا فسنختاره كاملا للتجزئة. ملحوظة: ينبغي الحذر في هذه الخطوة إن كنت تثبّت التوزيعة على جهاز فعلي وليس وهميا به نظم تشغيل أخرى. ربما يؤدي اختيار تجزئة غير مناسبة إلى محو بياناتك الموجودة على القرص. يمكننا الآن بعد أن أكلنا إعداد الوجهة النقر على زرّ Begin installation لبدء التثبيت. تبدأ عملية تثبيت RHEL وتظهر نافذة جديدة. يُطلَب منّا أثناء نسخ الملفّات وتثبيت الأدوات التي اخترناها إعدادُ حساب المستخدِم الجذر Root ومستخدم آخر للمهامّ غير الإدارية. انقر على زرّ Root password (كلمة سر الجذر) لضبط كلمة السّر الخاصّة بالمستخدم الجذر؛ ثم بعد إتمام ضبطها انقر على زرّ User creation (إنشاء مستخدِم) لإعداد مستخدم آخر غير المستخدم الجذر. تبدو الصّورة على النحو التالي بعد إكمال الإعداديْن. انتظر حتى إكمال نقل الملفات وتطبيق الإعدادات (قد يستغرق ذلك دقائق طويلة حسب جهازك) ثم انقر على زرّ Reboot (أعد التشغيل). تظهر - بعد إعادة تشغيل النظام في الخطوة السّابقة - النافذة التالية والتي تطلُب قراءة شروط استخدام RHEL والموافقة عليها. اكتب الرقم 1 ثم اضغط على زر Enter لعرض معلومات الرّخصة. اكتب الرقم 2 ثم اضغط Enter لقبول الرخصة. اضغط على الزّر c في لوحة المفاتيح للخروج من شروط الاستخدام. اضغط على الزّر c لاستكمال التثبيت. ملحوظة: قد لا تظهر النافذة أعلاه؛ وتظهر بدلا منها نافذة رسومية يمكن من خلالها قراءة الترخيص وقبوله. يوجد أيضا في نفس النافذة زر للوصول المباشر إلى مدير الاشتراكات (لتسجيل الاشتراك كما في الخطوة الموالية أدناه). تظهر بعد اكتمال عمليّة الإقلاع نافذة دخول باسم الحساب العاديّ الذي أنشأته أثناء التثبيت. بقيت لنا خطوة أخيرة وهي تسجيل نسختنا من أجل الحصول على التحديثات من Red Hat. تأكّد من الاتّصال بالشبكة بالذهاب إلى قائمة Applications (التطبيقات) ثم القائمة الفرعيّة System tools (أدوات النّظام) ثم خيار Settings (الإعدادات) ثم زر Network (الشّبكة). نتوجّه بعد التأكّد من الاتّصال إلى قائمة التطبيقات مرة أخرى، أدوات النّظام ونختار منها Red Hat Subscription Manager (مدير الاشتراك في Red Hat). أدخل كلمة السرّ الخاصّة بالحساب الجذر (ليست كلمة سر حسابك العادي) إن طُلب منك ذلك. تظهر النافذة التالية. ننقر على زر Register (سجِّل) وندخل معلومات الاستيثاق الخاصّة بحساب مطوّري Red Hat (اسم المستخدِم وكلمة السّر) ثم ننتظر قليلا لتظهر النافذة التاليّة. تلخّص النافذة معلومات الاشتراك ونوعه. نضغط على زرّ Attache (أرفق) لاستخدام الاشتراك وتلقي التحديثات. لدينا الآن اشتراك مدّته سنة. إعدادات تكميلية أنهينا الآن تثبيت RHEL على VirtualBox؛ نكمل بتطبيق إعدادات تكميلية تساعدنا أثناء العمل على RHEL من داخل VirtualBox. تعطيل شاشة القفل وخيارات حفظ الطاقة تشغّل الإعدادات المبدئيّة في RHEL قفل الشّاشة عندما لا تكون مستخدمة؛ هذا مناسب أثناء العمل على جهاز فعليّ إلا أنه من الأفضل تعطيله على نظام وهميّ حيث يتولّى النّظام المضيف هذه المهمة. لتعطيل هذه الإعدادات ننقر على قائمة التطبيقات ثم نختار أدوات النظام ونحدّد الإعدادات Settings. نختار ضمن نافذة الإعدادات خيار الخصوصية Privacy، ثم قفل الشاشة Screen lock ثم نعطّل القفل التلقائي للشاشة Automatic Screen Lock. نعود لنافذة الإعدادات ونختار زرّ الطاقة Power لتظهر نافذة إعدادات الطاقة. ننقر على القائمة المنسدلة بجانب خيّار Black screen (الشاشة السوداء) ثم نختار Never من القائمة. إضافات Virtualbox تمكّن VirtualBox Guest Additions (إضافات الضّيف في VirtualBox) من تحسين التّكامل بين النظام الضّيف والنّظام المضيف. مثلا: استخدام المؤشّر دون الحاجة للضغط على زر Ctrl في لوحة المفاتيح. النسخ واللّصق بين النظامين. تشارك الملفات. القدرة على تغيير حجم الواجهة الرسوميّة داخل النظام الوهمي بتغيير حجم نافذة النّظام داخل VirtualBox. تحتاج قبل تثبيت إضافات VirtualBox إلى تثبيت أدوات التّطوير (نفّذنا هذه الخطوة أثناء تثبيت النظام) وإلى الدّخول إلى واجهة سطح المكتب. تكون خطوات التثبيت على النحو التالي. شغّل النظام الوهميّ RHEL ثم اختر قائمة Devices (أجهزة طرفيّة) في نافذة VirtualBox؛ ثم اختر Insert Guest Additions CD image. ستظهر بعد لحظات نافذة حوار تسألك ما إذا كنت متأكدا من أنك تريد تنزيل الملف. يبدأ تنزيل الملفّ بالضغط على Download (نزِّل) ثم تظهر بعد اكتمال التنزيل نافذة تطلُب تسجيل الملف وإدخاله في القرص الضّوئي الوهمي للنظام. انقر على زر Insert. تظهر بعد إدخال الملفّ النافذة التاليّة التي تطلُب تشغيله. يبدأ - بالنقر على زر Run في النافذة - تثبيتُ الإضافات. سيُطلَب منك إدخال كلمة السّر الخاصّة بحساب الجذر Root. تطلُب نافذة التثبيت الضغط على زر Enter بعد اكتمال العملية. أعد تشغيل النظام؛ ستظهر رسالة على الشاشة تخبرك أن النظام الوهمي يتعرّف الآن تلقائيا على المؤش عند مروره في النافذة الخصّة به في VirtualBox دون الحاجة للضّغط على زر Ctrl. يمكن استخدام المؤشّر على النّظام المضيف بمجرّد خروجه من حيّز الشاشة المخصَّص للنظام الضّيف. انقر على الأيقونة اليمنى في الرسالة لكي لا تظهر مجدّدا. تحديث النظام الخطوة الأخيرة هي تحديث النّظام. افتح طرفيّة Terminal (قائمة التطبيقات ثم المفضّلة Favorites) ثم نفّذ الأمر التالي للدخول في الحساب الجذر: su - ستُطلب منك كلمة سر المستخدم الجذر. ثم نفّذ الأمر التالي لتحديث الحزم والبرامج المثبّتة: yum -y update
  24. يستخدم Laravel الصنف Handler الموجود على المسار app/Exceptions للتعامل مع جميع الاستثناءات. يحوي هذا الصّنف دالتين: report وrender. تُستخدَم دالة report لتسجيل الاستثناءات أو إرسالها إلى خدمة خارجية؛ أما الدالة render فتُستخدَم لاعتراض الاستثناءات وإرسال إجابة مناسبة إلى المتصفّح ليعرضها للزّائر. ملحوظة: اعتمدنا العروض، المسارات والمتحكّم الذي أنشأناه في درس كيف تستخدم PHPUnit لاختبار تطبيقات Laravel وبنينا عليها لتطبيق الخطوات الواردة هنا. يمكنك تجربة الدّرس على مشروعك الخاص أو استخدام الملفّ المرفَق الذي يمثّل الحصيلة النهائية لهذا الدرس. إن استخدمت الملفّ المرفق فستحتاج لتسجيل مستخدم أولا عبر رابط register والدخول إلى الموقع حتى يظهر بقية المحتوى (سنترك لك اكتشاف الآلية المستخدمة في ذلك. كلمة السّر: المسارات المحميّة). تخصيص رسائل الاستثناءات يُعدّ ErrorException أحد أكثر الاستثناءات انتشارا ويظهر مثلا عند إضافة معرف كائن غير موجود إلى مسار URL، أو عند طلب خاصيّة غير موجودة في الكائن. عند محاولة دخول المسار التالي دون أن تكون التسجيلة ذات المعرّف 56 موجودة في جدول البيانات widgets: laravel.dev/widget/56 فستظهر في المتصفّح صفحة خطأ كالتالي: صفحة الخطأ هذه مفيدة للمطوّرين إلا أننا لا نرغب في أن يراها زوار الموقع؛ لذا يجب علينا التعامل مع هذا الاستثناء وقت حدوثه وعرض رسالة مغايرة للزائر. نعدّل دالة render في ملف Handler.php لتصبِح على النحو التالي: public function render($request, Exception $e) { switch($e){ case ($e instanceof ErrorException): return $this->ErrorException($e); break default: return parent::render($request, $e); } } ثم نضيف الدالة التالية إلى الملف: protected function renderErrorException($e) { return response()->view('errors.404', [], 404);/* } يمكنك أن تلاحظ أننا أضفنا عبارة switch إلى الدالة من أجل تحديد نوعيّة الاستثناء فإن كان من نوع ErrorException استدعينا الدالة renderErrorException وإلا نترك الدالة المبدئية تتكفّل بالموضوع. استخدمنا عبارة switch هنا لتسهيل إضافة معالَجات خاصّة في ما بعد لأنواع أخرى من الاستثناءات. نستخدم في الدالة renderErrorException عرضا خاصّا سميناه 404. يحوي مجلد العروض في Laravel مجلّدا خاصّا لعروض الأخطاء errors؛ ننشئ فيه العرض 404 ذي المحتوى التالي مع إضافة اللاحقة blade.php.: @extends('layouts.app') @section('content') <div class="alert alert-danger alert-dismissible alert-important" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <strong>Oh Snap!</strong> We can't find what you are looking for... </div> @endsection بقي لنا استيراد صنف الاستثناءات ErrorException في ملف Handler.php: use ErrorException; هذا كل ما نحتاجه. نعود الآن لنرى الرابط السابق: laravel.dev/widget/56 إن لم يكن الكائن ذو المعرّف موجودا فستظهر رسالة الخطأ التالية: تخصيص الاستثناءات استخدمنا في الفقرة السابقة استثناءً موجودا وخصّصنا النتيجة التي يعرضها للزائر. نريد في هذه الفقرة إنشاء استثناء مخصَّص لتلبية حاجة خاصّة بمشروعنا. سنفرض مثلا أننا نريد إظهار رسالة خطأ للزائر عند تصفّح الرابط /create/widget/، سنسمّي الاستثناء الجديد WidgetCreateException. نبدأ بإنشاء ملف WidgetCreateException.php في المجلّد Exceptions: <?php namespace App\Exceptions; class WidgetCreateException extends \Exception { } لاحظ مساحة الأسماء Namespace. لا نريد أن نعقّد الأمور، لذا سنترك الصّنف على ماهو عليه دون إضافة معالجات خاصّة، حتى نفهم المبدأ. نعود للملف Handler.php ونعدّله باستيراد الصّنف الذي أنشأناه للتّو: use App\Exceptions\WidgetCreateException; ثم نعدّل دالّة render كالتالي: public function render($request, Exception $e) { switch($e) { case ($e instanceof ErrorException): return $this->renderErrorException($e); break; case ($e instanceof WidgetCreateException): return $this->renderWidgetCreateException($e); break; default: return parent::render($request, $e); } } ونضيف أيضا الدالة التالية: protected function renderWidgetCreateException($e) { return response()->view('errors.widgetcreate', [], 404); } يعني هذا أنه في حالة ظهور استثناء من نوع WidgetCreateException فسنستدعي الدالة renderWidgetCreateException التي تُظهر العرض widgetcreate. بقي لنا الآن إنشاء ملف العرض widgetcreate.blade.php في المجلّد errors التابع لمجلد العروض: @extends('layouts.app') @section('content') <div class="alert alert-danger alert-dismissible alert-important" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <strong>Yes!</strong> You threw a Widget Creation Exception... </div> @endsection سننشئ لتجربة صنف الاستثناء الجديد داالةً في المتحكّم المناسب (HomeController في حالتي) كما يلي: public function create() { throw new WidgetCreateException; } لا تنس استيراد الصنف WidgetCreateException في المتحكم: use App\Exceptions\WidgetCreateException; وإضافة مسار لـ/create/widget في ملف routes.php: Route::group(['middleware' => 'web'], function () { Route::auth(); Route::get('/create/widget', 'HomeController@create'); }); نجرّب عمل الاستثناء بزيارة المسار التالي: http://laranew.dev/create/widget لاحظ النتيجة: قدّمنا في هذا المقال أساسيات تخصيص الاستثناءات في Laravel؛ ما زال يوجد الكثير لتعلمّه في هذا الإطار انطلاقا من هذه الأساسيات. حمل الملف المرفق لهذا الدرس. ترجمة -وبتصرّف- للمقال Handling Exceptions and Custom Exceptions in Laravel 5.1 لصاحبه Bill Keck.
  25. يأتي Laravel مبدئيا بمعمل نماذج Model factory يُستخدَم لتسريع بناء النماذج واختبارها. سنرى في هذا المقال طريقتين لإدراج تسجيلات في جدول قاعدة بيانات باستخدام معمل النماذج. سنعتمد في الخطوات الموالية على النموذج الذي أنشأناه في الدرس السابق كيف تنشئ نموذجا (Model) في Laravel. الطريقة الأولى: بذر جدول البيانات نبدأ بفتح الملف database/factories/ModelFactory.php. يأتي الملف مبدئيا بدالّة لـبذر جدول المستخدمين: $factory->define(App\User::class, function (Faker\Generator $faker) { return [ 'name' => $faker->name, 'email' => $faker->email, 'password' => bcrypt(str_random(10)), 'remember_token' => str_random(10), ]; }); لاحظ استخدام مكتبة Faker عبر المتغيّر faker$. يجب تنفيذ التهجيرات المبدئية التي تأتي مع Laravel لإنشاء الجداول في قاعدة البيانات حتى يمكن إدراج تسجيلات فيها. سنعلّق الدّالة السابقة ونضيف دالة جديدة على النحو التالي: $factory->define(App\Widget::class, function ($faker) { return [ 'widget_name' => $faker->unique()->word, ]; }); تستدعي الشفرة السابقة مكتبة faker$ لتوليد كلمة word لإدراجها في حقل widget_name، نطلُب من المكتبة التأكد أن الكلمة وحيدة uniq لنوافق القيد الموجود على حقل الاسم في الجدول. بقيت لنا خطوة قبل بذر الجدول باستخدام أمر artisan. ننتقل إلى المجلّد database/seeds، نفتح الملف DatabaseSeeder.php ونعدّله ليصبح كالتالي: <?php use Illuminate\Database\Seeder; use Illuminate\Database\Eloquent\Model; use App\Widget; class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { Model::unguard(); Widget::truncate(); factory(Widget::class, 50)->create(); Model::reguard(); } } تعمل دالة unguard على تعطيل الحماية مؤقّتا على النموذج لحين إدراج التسجيلات، بينما تعيد دالة reguard تفعيلها. تعمل الدّالة truncate على حذف جميع التسجيلات في الجدول لتهيئة عمليّة البذر. نطلُب داخل دالة factory إدراج 50 تسجيلة في جدول النموذج المذكور Widget. نحن الآن جاهزون لتنفيذ أمر البذر: php artisan db:seed الأمر سهل للغاية. يمكنك إن أردت التراجع بنفس السهولة عن الأمر وحذف التسجيلات المدرجة بتنفيذ الأمر: php artisan migrate:rollback الطريقة الثانية: استخدام الاختبارات توجد طريقة أخرى غير السابقة لإدراج بيانات وهميّة في جدول بيانات. إن لم تكن لديك فكرة عن التّطوير الموجَّه بالاختبارات Test-driven development, TDD فيمكنك أخذ فكرة عن الأساسيات في مقال كيف تستخدم PHPUnit لاختبار تطبيقات Laravel. سيكون من الجيّد لك التعوّد على استخدام الاختبارات في أعمال التطوير، خصوصا أن Laravel يسهّل الأمر كثيرا. نبدأ بالانتقال إلى ملفّ tests/ExampleTest.php ثم ننشئ نسخة منه باسم WidgetTest.php ونعدّلها لتصبح على النحو التالي: <?php use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; use App\Widget; class WidgetTest extends TestCase { use DatabaseTransactions; /** * A basic functional test example. * * @return void */ public function testWidgetFactory() { $widgets = factory(Widget::class, 50)->create(); dd($widgets); } } أنشأنا دالة اختبار تستدعي دالة المعمل لإدراج تسجيلات الجدول. بما أننا نستخدم: use DatabaseTransactions; فإن التسجيلات لن تبقى مخزّنة في قاعدة البيانات أكثر من حاجة الاختبار. يؤدي استخدام الدالة dd إلى طباعة محتوى النماذج في الطرفيّة عند نجاح الاختبار: dd($widgets); سنحتاج قبل تنفيذ الاختبار إلى حذف محتوى الجدول الناتج عن الطريقة الأولى، لذا ننفذ أمر إرجاع التهجير: php artisan migrate:rollback ثم نعيد تنفيذ التهجير لإنشاء الجدول من جديد: php artisan migrate نحن الآن جاهزون لتنفيذ الاختبار: vendor/bin/phpunit tests/WidgetTest.php أو إن كان مسار PHPUnit مختلفا كما ذكرنا في درس كيف تستخدم PHPUnit لاختبار تطبيقات Laravel: vendor/phpunit/phpunit/phpunit tests/WidgetTest.php إن كنت ترغب في إبقاء بيانات الاختبار في الجدول فيمكنك تعليق استخدام الصنف التالي: // use DatabaseTransactions; يمكن أن تظهر أخطاء عند إعادة تنفيذ الاختبار بعد إبقاء بيانات الاختبار السابق في جدول البيانات. يعود السبب في ذلك إلى أن مكتبة Faker لا تعرف مالذي يوجد في جدول البيانات وبالتالي يمكن أن تولّد بيانات لا تحترم شرط عدم التكرار في محتوى الحقل widget_name. توجد خيارات عدّة لتجاوز هذا الأمر، إما بإرجاع التهجير لحذف الجدول ثم تنفيذ التهجير مرة أخرى لإنشاء الجدول من جديد وبعدها ينفَّذ الاختبار؛ أو استخدام دالة truncate لحذف التسجيلات من الجدول قبل توليد تسجيلات جديدة. في كلتا الحالتين تُفقَد البيانات السابقة على تنفيذ الاختبار. ترجمة -وبتصرّف- لمقال Using Model Factory to make Test Data in Laravel 5.1 لصاحبه Bill Keck.
×
×
  • أضف...