المحتوى عن 'laravel'.



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

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

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

نوع المُحتوى


التصنيفات

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

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
    • Cordova
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • برمجة أندرويد
  • لغة Swift
  • لغة R
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات عامّة

التصنيفات

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

التصنيفات

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

التصنيفات

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

التصنيفات

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

التصنيفات

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

التصنيفات

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

أسئلة وأجوبة

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

التصنيفات

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

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

  1. يقدّم دليل الانطلاق السريع هذا، المكوّن من جزأين، مقدّمة لإطار العمل 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.
  2. يحتار المطورون في اختيار أفضل إطار لمشاريعهم وسيكون هذا تحديًا حقيقيًا للمبتدئين في الأطر الحديثة. بعد العمل على الأطر الثلاثة (Django، Laravel و Rails – والذي يُعرف باسم Ruby On -rails)، سأقارن بين هذه الأطر الرائعة على أساس شعارها، سهولة تعلمها، أدائها، قوة وضعف مكتباتها وقوالبها، دعمها، آفاقها المستقبلية، فرص العمل، التكلفة والصيانة. ملاحظة: ينتقد بعض المعجبين عند التحدث عن نقاط ضعف أطرهم، ولا أستطيع فعل أي شيء لأنه لا يمكن إخفاء الحقيقة، كل إطار لديه بعض المزايا مع بعض العيوب. المقدمة لغة البرمجة أهم فرق بين هذه الأطر هي أن Django بلغة بايثون، Laravel بلغة PHP وRails بلغة الروبي، لذا إذا كنت تنوي استخدام أي من هذه الأطر فيجب عليك تعلم لغتها أولاً، وبسبب هذا، العديد من المطورين يختارون الإطار الذي يتطابق مع اللغة التي يعرفونها. إن التحول من لغة إلى أخرى ليس صعبًا بل يحتاج إلى بعض الوقت، وإذا احترت في اختيار لغة البرمجة، فهذه مقارنة بين لغات بايثون و PHP وروبي. الشعار جميع هذه الأطر من نوع MVC وشعارها ‘لا تكرر نفسك’ أي تدعم إعادة الاستخدام وقابلية النقل، وجميعها مشاريع مجانية ومفتوحة المصدر. المواقع بعض المواقع المعروفة تستخدم Django مثل Pinterest، Instagram، Mozilla، The Washington Times، Disqus، the Public Broadcasting Service و Bitbucket. في حين أن Laravel هو إطار جديد، حيث صدر في يونيو عام 2011، لكنه أصبح مشهورا جدا، ومن بين المواقع التي تستخدمه هي Deltanet Travel، Sublimity، Neighborhood Lender، Sendity و MyRank. يعتبر Rails من الأطر الرائعة فمن المواقع التي تستخدمه Twitter، Shopify، SoundCloud، Heroku، Github، Bloomberg و Hulu. سهولة التعلم على الرغم من أن الأطر الثلاثة لديها مجتمعات كبيرة وتوثيق رسمي، إلا أن تعلم Django وLaravel أسهل بكثير من تعلم Rails، فالتوثيق الحالي ل Django يجعلها الأسهل، وإذا كنت تملك خلفية PHP فيمكنك تعلم Laravel في غضون أسبوعين أو ثلاثة أسابيع، وهذه هي الوثائق الرسمية: وثائق Django ووثائق Laravel وثائق Rails. الأداء الأمن جميع هذه الأطر آمنة جدا إذا لم يرتكب المبرمج أخطاء، فيمتلك Django برمجيات وسيطة ويمتلك Rails Active Records وأما Laravel فيمتلك برمجيات HTTP وسيطة، وتوفر كل هذه الأطر رموز csrf للنماذج. لا يوجد فرق أمني كبير بين هذه الأطر، وكل هذا يعتمد على خبرة المبرمج. تحديث:أشار بعض القراء أن المبرمجين هم بشر وسيخطئون، لذا سأقول في هذه الحالة أن Django هو الأكثر أمانا وLaravel هو الأقل أمانًا، اطلع على هذا التوثيق عن أمن Django وهذا دليل أمن Rails و هذا دليل أمن Laravel، وسأقول أيضا أنه لا يوجد إطار آمن بشكل كامل لأن المطورين هم أيضا بشر، ويمكنك زيادة الأمن لكنك لا تستطيع جعله آمن بنسبة 100%، لكن إذا كتبت التعليمات البرمجية بعناية وحذر فإن جميع الأطر متساوية من ناحية الأمن. السرعة جميع الأطر مكتوبة بشكل صحيح، لذلك سرعتها تعتمد على اللغة البرمجة المستخدمة، فDjango هو الأسرع بسبب البايثون و Laravel هي الأبطأ بسبب PHP. الوقت المطلوب لإنشاء تطبيق إذا كنت تفهم الإطار بشكل كامل فإن إنشاء تطبيق Rails هو الأسرع لأنه يوفر لك الكثير من الاختصارات وبهذا ستكتب أقل عدد من الأسطر البرمجية. ومن جهة أخرى، Laravel هو الأبطأ ولا يوفر مكتبة قوية. إذا كان المشروع معقد فإن الفرق الزمني بين تطبيقات Django وRails سيكون صغيرًا بسبب صياغة بايثون المريحة للمتابعة وأقل أرباك، أما بالنسبة لـ Laravel فيجب عليك كتابة الكثير من الأسطر البرمجية وهذا قد يسبب لك بعض الإرباك وسترتفع نسبة الأخطاء. قوة وضعف المكتبة الأشياء المشتركة في جميع الأطر: جميعها MVC (يسمى Django MTV أيضا لكن على الرغم من أن الاسم مختلف إلا أن المفهوم هو نفسه). تركز جميع الأطر على قابلية القراءة وبساطة الشيفرة البرمجية وتوزيع الملفات. جميعها تستعلم تلقائيًا من قاعدة البيانات، فلا يجب عليك كتابة استعلامات قاعدة البيانات بشكل مباشر. تبنى الجداول تلقائيا في قاعدة البيانات من النماذج (models). جميع الأطر تملك نظام توجيه سهل وآمن، وتعرض صفحات الويب بشكل حيوي. تملك جميعها أنظمة قوالب خاصة بها وكل نظام قوالب غني بالمرشحات والدوال المعرّفة مسبقًا، الفرق الوحيد في الصياغة. جميعها مرنة ومحمولة مع تقنيات حديثة أخرى. Django يمتلك Django مكتبة قوية مع المميزات التالية: يعتبر قسم الإدارة المدمجة، المزخرف (decorator)، وأصناف المناظر نقاط قوة ل Django. الاستمارات المولدة تلقائيا للنماذج مع عملية التحقيق تجعلها سهلة للغاية. يدعم الإطار خاصية التخزين المؤقت وستتمكن من استخدام أي من أساليب التخزين المؤقت المتاحة. يدعم الأصناف البرمجيات الوسطيّة والتي يمكن أن تتدخّل في مراحل مختلفة من معالجة الطلب وتُنفّذ دوال مخصصة. يسمح لك نظام مرسل (dispatcher) داخلي لمكونات التطبيق اتصال الأحداث مع بعضها البعض عبر إشارات محددة مسبقا. يملك نظام تدويل يتضمن ترجمات لمكونات Django إلى لغات مختلفة. يملك نظام تسلسل الذي يمكنك من إنتاج وقراءة تمثيل XML و/أو JSON لمثيلات نموذج Django. واجهة بايثون مدمجة في إطار اختبار الوحدة. نظام مصادقة (authentication) موسّع. واجهة إدارة حيوية. أدوات لتوليد RSS وتغذيات (feed) خلاصات Atom. إطار مواقع تسمح ل Django واحد بتشغيل مواقع متعددة، ولكل منها المحتوى والتطبيقات الخاصة به. يملك أدوات لتوليد Google Sitemap. يملك تقنيات مدمجة للتخفيف من التزوير عبر الموقع، ثغرات XSS، ثغرات حقن SQL، تكسير كلمات المرور وهجمات الويب النموذجية، ومعظمها يعمل افتراضيا. إطار لإنشاء تطبيقات GIS. Laravel على الرغم من أن مكتبات Laravel ليست قوية مثل Django وRails إلا أنها كافية لإنشاء أي نوع من المواقع. يوفر Bundles و composer عدد من حزم نظام وحدات التحزيم والاعتماديات. التوجيه (Routing) – يوّفر طريقة سهلة وبسيطة لإدارة وتوجيه الروابط إلى متحكم أو دالة تُنفَّذ عند زيارة رابط محدَّد. دعم Eloquent ORM – خدمة أخرى مقدمة لتجريد وأتمتة جزء النموذج، حيث سنطبق التقنيات المتعارف عليها على الإعدادات. التهجيرات – طريقة لإصدار سكربتات قواعد البيانات بطريقة أنيقة للغاية، فلا حاجة للحفاظ على جميع التحققات على التهجيرات، يمكن لفريق عمل المشروع سحب الهجرة المقدمة وستعيّن جميعها وستكون جاهزة للعمل. إدارة قائمة الانتظار (Queue management) – لتجريد المهام غير الضرورية ووضعهم في قائمة الانتظار وجعل وقت استجابة المستخدم أسرع بكثير. دعم Redis، ويمكن توسيعها إلى memcached. حقن الإعتماديّة – اختبار سهل وأتمتة تحميل الإعتماديّة. Artisan – لإنشاء تطبيقات سطر الأوامر في لحظة. تعلم استخدام Laravel عن طريق هذه الدروس. Rails يتضمن Rails أدوات لجعل مهام التطوير الشائعة أسهل (خارج الصندوق)، مثل scaffolding الذي يستطيع إنشاء بعض النماذج تلقائيًا والمناظر اللازمة لموقع ويب الأساسي، بالإضافة إلى WEBrick وهو خادم ويب روبي بسيط الموزع مع روبي و Rake والذي هو نظام بناء موزع كـ gem. وتوفر هذه الأدوات جنبا إلى جنب مع Rails بيئة تطوير أساسية. Active record: يلعب دورا رئيسيا في تطبيقات Rails، وهو أفضل من Eloquent ORM في Laravel ومن النماذج في Django. اختصارات: يعبر الكثير من الناس الذين يأتون من لغات برمجة أو إطارات أخرى أن هذا الإطار سحري بسبب الاختصارات الكثيرة، فأغلب الأشياء معرّفة مسبقًا ويجب عليك كتابة بعض الأسطر البرمجية لإنشاء تطبيقات معقدة. التوجيه التلقائي: بعض الدوال الشائع في جدول قاعدة البيانات مثل الإنشاء ، التعديل والعرض مُعرّفة تلقائيًا، وهذا يعني أننا لا نحتاج إلى تضييع الوقت في المهام البسيطة ويمكننا قضاء وقت أطول على الأجزاء المعقدة من المشروع. سطر الأوامر: الكثير من الأشياء يمكن إنجازها عن طريق سطر الأوامر مثل استخدام rake وهي Ruby Make، أداة روبي مستقلة تستبدل أداة يونكس 'make' وتستخدم 'Rakefile' وملفات .rake لبناء قائمة مهام. في Rails، يُستخدم Rake لمهام الإدارة الشائعة، خاصة المعقدة منها التي تبني من بعضها البعض. تحتوي وحدة ActiveModelHelper على أساليب المساعدة لإنشاء النماذج من الكائنات بسرعة التي تتبع اتفاقيات Active Model، بداية من Active Record. خدمات الاستضافة يمكنك تشغيل أي تطبيق على VPS أو على خدمة استضافة مخصصة، وهذه مجموعة من الروابط لمواقع تسمح لك باستضافة مشروعك مجانا أو على خطط الاستضافة المشتركة. Django: بعض من المواقع التي تستضيف مشاريع Django هي: WebFaction، PythonAnywhere ، Heroku ، Digital Ocean ، Bulehost ، Dreamhost ، Arvixe و Google App Engine. Laravel: يمكنك الاستضافة على Heroku ، Bulehost ، Inmotion Hosting ، Site5 ، Dreamhost ، Digital Ocean و Arvixe. Rails: مواقع لتطبيقات Rails هي: Heroku ، Bulehost ، Dreamhost ، Arvixe ، Hosting24 و Digital Ocean. معايير أخرى كل هذه الأطر جيّدة في المستقبل، ففرص العمل، التكلفة والصيانة هي تقريبا نفسها ويمتاز Rails على Django وLaravel في شروط العمل، على الرغم من سرعة نمو Laravel. خاتمة يمكنك أن تختار أي واحدة من هذه الأطر حسب لغة البرمجة والخبرة، وإذا كنت هنا لتقرر أي واحدة يجب عليك تعلمها فأنا أفضل Rails، فعلى الرغم من صعوبة تعلمها إلا أنها مريحة أثناء إنشاء التطبيقات، إذا أردت أشياء سهلة مع الكثير من المميزات فاختر Django، فصياغة بايثون ونماذجه تجعله خيار جيدا، وعلى الرغم من أن تعلم Django قد يستغرق بعض الوقت إلا أنه ليس أصعب من Rails.إذا كانت لدي خبرة في PHP أو إذا أردت التعلم بسرعة فاختر Laravel. ترجمة -وبتصرّف- للمقال Django vs Laravel vs Rails لصاحبه Harish Kumar
  3. تقدّمت منذ فترة بطلب إلى مطوّري إطار عمل Laravel لإضافة مكتبة JavaScript مشابهة لتلك الموجودة في إطار العمل Ruby on Rails. إذ تقدّم المكتبة المتوفرة في إطار Rails مجموعة من الخصائص المفيدة التي يمكن إضافتها إلى شيفرة HTML للحصول على المزيد من الوظائف التي تسهّل على المطوّر أداء الكثير من المهامّ الشائعة. أجري نقاش حول هذا الموضوع وتمخّض النقاش عن الاقتراح التالي: إما أن يصار إلى إنشاء حزمة Composer من طرف ثالث، أو أن المكتبة التابعة لإطار العمل Rails صالحة للاستخدام في Laravel. وفقًا للمقترح السابق قرّرت استخدام مكتبة UJS التابعة لـ Rails في أحد مشاريعي ورحت أتفحّص الطرق التي يمكن من خلالها استخدام نفس الخصائص في الإصدار الرابع من Laravel. ولسوء الحظ لا يمتلك Laravel نفس التوابع المساعدة الموجودة في Rails والمسؤولة عن أداء وظائف معيّنة، ولكن لا زال بالإمكان تفعيل هذه الوظائف في المكان الذي ترغب فيه باستخدام الخواص الملائمة، ومن المؤكّد أن هذه الوظائف ستعمل حسب المطلوب في أي إطار عمل آخر. البداية بعد أن تربط مكتبتي jQuery و Rails UJS مع ملف HTML ستحتاج إلى إجراء بعض التعديلات السريعة. سنحتاج أوّلًا إلى إعداد مكتبة UJS للعمل مع رمز CSRF على فرض أنّك تستخدم هذه الرموز في تطبيقاتك (أنصحك باستخدامها)، لذا سنضع وسمي <meta> في بداية المستند: <meta name="csrf-param" content="_token"> <meta name="csrf-token" content="{{ csrf_token() }}"> يخبر الوسم الأول مكتبة UJS بالاسم الذي يجب استخدامه لرمز CSRF عند إنشاء الطلب، أما الوسم الثاني فيخبر المكتبة بوسم CSRF الذي يجب استخدامه. يستخدم Laravel افتراضيًا token_ كاسم للمعامل (ألق نظرة على ملف filters.php للمزيد من المعلومات)، أما التابع ()csrf_token فيضيف رمز الحماية إلى الطلب. خاصية data-method هذه الخاصية مفيدة جدًّا عند استخدام للروابط، إذ تخطف مكتبة UJS النقرة على الرابط وتنشئ طلبًا باستخدام فعل Http الذي نقدّمه إلى الخاصية. مثال: <a href="posts/1" data-method="delete" rel="nofollow">Delete this post</a> عند النقر على الرابط، يتم إنشاء طلب من نوع DELETE بدلًا من الطلب الاعتيادي GET والذي ينشأ من مثل هذه الروابط، وبهذا يمكنك إجراء أحداث بنمط RESTful في تطبيقك دون الحاجة إلى الاستمارات. إن كنت ترغب في إنشاء روابط مشابهة لهذا الرابط في Laravel فإليك المثال التالي والذي نستخدم فيه دالة link المساعدة: {{ link_to_route('posts.destroy', 'Delete this post', $post->id, ['data-method' => 'delete', 'rel' => 'nofollow']) }} جدير بالذكر أنّه يجب إضافة الخاصية rel="nofollow" عند استخدام هذه الدالة المساعدة لضمان عدم فهرسة محركات البحث لهذا الرابط. خاصية data-confirm هذه الخاصية مفيدة للتحقّق من الموافقة على إجراء الحدث قبل الاستمرار، وتتم عملية التحقّق هذه بواسطة مربع التنبيه الذي يظهر بواسطة الدالة confirm() في JavaScript، ويمكن استبدال مربّع التنبيه هذا بآخر ذي شكل أجمل حسب الرغبة. تعمل هذه الخاصية بصورة جيّدة مع المثال السابق، فإن كان الرابط أو الزرّ يؤدّي إلى حذف شيء ما فيستحسن حينئذٍ التحقّق من الرغبة في القيام بذلك قبل إجراء عملية الحذف. <a href="posts/1" data-method="delete" data-confirm="Are you sure you want to delete this post?" rel="nofollow">Delete this post</a> إن ألغى المستخدم صندوق التأكيد هذا لن يتم تنفيذ الحدث، والعكس بالعكس. جدير بالذكر أنّك لست ملزمًا باستخدام هذه الطريقة مع خاصية data-method بل يمكن استخدامها مع أي حدث ترغب في التحقق منه قبل إجراءه. خاصية data-disable قد ترغب أحيانًا في تعطيل زرّ معين بعد النقر عليه مباشرة لإتاحة الفرصة لاكتمال الطلب وفي حال فشل الطلب تعيد تفعيل الزرّ مرة أخرى. تتيح لك مكتبة UJS القيام بهذا الأمر بواسطة خاصّية data-disable. <input type="submit" value="Save post" data-disable> هكذا سيتم تعطيل الزر بعد النقر عليه وستمنع المستخدم من النقر المستمر على الزر وإنشاء 10 سجلات جديدة في قاعدة البيانات. بالنسبة لمستخدمي Laravel يمكن الاستفادة من هذه الخاصّية باستخدام منشئ الاستمارات form builder. {{ Form::submit('Save post', ['data-disable']) }} خاصّية data-disable-with يمكن التوسع في المثال السابق وذلك بتقديم بعض المعلومات المفيدة للمستخدم، فعند تعطيل الزرّ يظهر نصّ يخبر المستخدم بأنّ الطلب في طور الإنشاء في الخلفية. <input type="submit" value="Save post" data-disable-with="Saving this post..."> والآن عندما يكون الزرّ معطلًا، يتم استبدال النص بعبارة تدلّ على أن النقر على الزرّ قد أدى إلى حدوث شيء ما في الخلفية. خاصّية data-remote عند إضافة الخاصّية data-remote إلى الاستمارة، تقوم مكتبة UJS بإرسال الاستمارة كطلب Ajax. وستأخذ المكتبة كل شيء بالحسبان (طريقة الإرسال، التحقق من الموافقة على الإجراء… الخ) قبل إرسال الاستمارة. <form action="posts" method="post" data-remote> <!-- Form goes here. --> </form> ويمكن التعامل مع الاستجابة الواردة بعد نجاح الطلب بكل سهولة، فإن استجاب المتصفّح بواسطة JavaScript فستنفّذ الاستجابة فور اكتمال الطلب. على سبيل المثال، قد تختار إعادة توجيه المستخدم بعد تنفيذ طلب للحذف: window.location.href = '{{ route('posts.index') }}'; أو لنقل مثلًا أنّك تستخدم رابطًا لحذف مقالة من صفحة تتضمن قائمة بالمقالات المتوفّرة في المدونة، وترغب في إخفاء المقالة بعد حذفها مباشرة. <a href="{{ route('posts.destroy', $post->id) }}" data-method="destroy" data-confirm="Delete this post?" rel="nofollow" data-remote>Delete this post</a> يمكن استخدام الشيفرة التالية لإخفاء المقالة: $('#post-{{ $post->id }}').fadeOut(); وهكذا، تُحذف المقالة من قاعدة البيانات والعرض في آن معًا. خاصّية data-type: إن استخدمت الخاصّية السابقة data-remote يمكنك كذلك استخدام خاصية data-type لتحديد هيئة البيانات التي يتم إرسالها إلى الخادوم. فعلى سبيل المثال يمكن إرسال الاستمارة على هيئة Json: <form data-type="json"> الأحداث البعيدة عند استخدام خاصّية data-remote يمكن الوصول إلى الأحداث المختلفة في طلب Ajax لتأدية بعض المهامّ الإضافية. إذ تطلق مكتبة UJS عددًا من الأحداث المساعدة خلال دورة حياة الطلب والتي من شأنها أن تساعد في بناء تطبيقات أكثر تعقيدًا. ajax:before ينطلق هذا الحدث قبل حدوث أي شيء، وإيقاف هذا الحدث يؤدي إلى إلغاء الطلب. ajax:beforeSend: ينطلق هذا الحدث قبل إرسال طلب Ajax مباشرة، وإيقاف الحدث يؤدي إلى إلغاء الطلب. ajax:send: ينطلق عند إرسال طلب Ajax. ajax:success: ينطلق بعد اكتمال الطلب بنجاح. ajax:error: ينطلق بعد فشل إرسال الطلب. ajax:aborted:required: ينطلق في حال وجود حقول إلزامية فارغة في الاستمارة، وفي حال إيقاف الحدث يتم إرسال الاستمارة. ajax:aborted:file: ينطلق في حال وجود حقول ملفات غير فارغة في الاستمارة، ويتم تجاهل الطلب في حال توقّف الحدث. آمل أن يكون هذا الموضوع مفيدًا لكل من يرغب في استخدام مكتبة UJS التابعة لإطار العمل Ruby on Rails مع إطارات العمل الأخرى مثل Laravel، إذ أرى أن هذه المكتبة مفيدة ولا يجوز أن تكون حكرًا على مستخدمي إطار Rails. ترجمة - وبتصرّف - للمقال Using Rails UJS in Laravel 4 (or any other framework) لصاحبه Dwight Conrad Watson.
  4. يحدُث كثيراً أن ينقسم مجتمع المطوّرين حول تقنيّات يراها بعضهم غير ذات جدوى أو على الأقل ليست بالأهميّة التي يروّج لها مؤيّدوها. تنطبق هذه الملاحظة على أطر العمل التي تعمل على ربط العلاقات بالكائنات Object relational mapping (أو ORM اختصارا)، ومن بينها Eloquent الذي يُستخدَم مبدئيًّا في Laravel؛ دون أن يعني ذلك عدم إمكانيّة استخدامه خارج Laravel كما سنرى في هذه السّلسلة. ليست أطر عمل ORM بالتقنيّة الجديدة، فهي توجد منذ سنين وأصبحت جزءًا من عمليّة التطوير بالنسبة للكثير من المبرمجين؛ كما أنها حسّنت من أدائها وتغلّبت على الكثير من النواقص مع الزمن. يتناول هذا الدّرس، الأوّل من سلسلة دروس عن الإطار Eloquent ORM، الأسباب التي أدّت إلى ظهور أطر عمل ORM والهدف من ورائها. التصوّر البرمجي Programming paradigm وإدارة البيانات العلاقيّة تُكتَب أغلب تطبيقات PHP الحديثة بأسلوب ذي تصوّر Paradigm كائنيّ التوجّه Object oriented، بينما تستخدم قواعدَ بيانات علاقيّة لإدارة البيانات وتخزينها. دور أُطُر عمل ORM هو تسهيل الانتقال بين هذيْن العالميْن أثناء تشغيل التطبيق. البرمجة كائنية التوجّه تعتمد لغات البرمجة على أفكار ونُظُم تحدّد طريقة عملها وأساليب كتابة البرامج التي تعمل بها. تُسمّى هذه الأساليب والقواعد بالتصورات البرمجيّة. يمكن للغةِ برمجة أن تُصنَّف في أكثر من تصوّر؛ إلّا أن كل لغة تركّز غالبًا على بضعة تصوّرات، ممّا يجعل كتابة برامج وفقَ هذه التصوّر ات أسهل بالنسبة لمبرمجي اللغة. تدعم لغة PHP البرمجيّة ميزات من تصوّرات برمجيّة عدّة؛ إلا أنها تُركّز على تصوّريْن: التصوّر الإجرائي Procedural الذي يمكن عدّه التصوّر الأساسي للغة منذ بداياتها. التصوّر كائنيّ التوجه Object oriented الذي اعتمده مصمّمو لغة البرمجة - فعليًّا - في الإصدار الخامس (سنة 2004). يُؤسَّس التصوّر الإجرائي على التصريح بكيفيّة إجراء العمليّات، خطوة بخطوة، واستخدام الحلقات Loops، العبارات الشرطيّة Conditions وتجميع الأوامر في سياقات محليّة Local context أو ضمن وحدات Modules يمكن استدعاءها عند الحاجة. أما التصوّر كائنيّ التوجه فيقوم على جمع البيانات والإجراءات (الدوالّ) ضمن كائن Object واحد يمكنه تطبيق الإجراءات المضمَّنة فيه على البيانات التي تخصّه. أنظمة قواعد بيانات العلاقيّة يعتمد النموذج العلاقي على نظريّة المجموعات Set theory والمنطق الرياضي من أجل تجميع البيانات ضمن جداول Tables؛ هي في الواقع علاقات Relations وفق التعريف الرياضي، ومنه التسميّة. يهدف النموذج العلاقي إلى تقديم طريقة تمكّن المستخدمين من تحديد البيانات المُعدّة للتخزين في قاعدة البيانات وتلك التي يريدون الحصول عليها من القاعدة، دون الاهتمام بالكيفيّة التي يستخدمها نظام الإدارة للإجابة على هذه الطلبات. يسمّى التصوّر البرمجي الذي يهتمّ بماهيّة المهمّة دون التركيز كثيرًا على كيفيّة أدائها بالتصوّر التقريري Declarative paradigm. يمكن عدّ لغة الاستعلام SQL تطبيقًا عمليًّا للنموذج العلاقي، وإن كانت تنحرف أحيانًا عن هذا النموذج. ربط العلاقات بالكائنات Object relational mapping تنبني شفرة التطبيقات التي تعتمد على تصوّر كائنيّ التوجه على أسس نظريّة تختلف عن تلك التي تُبنى عليها قواعد البيانات (النموذج العلاقي)؛ وهو ما يعني أنه على المطورين إيجاد طريقة للتخاطب مع قاعدة البيانات انطلاقًا من الشفرة البرمجيّة. يستخدم المبرمجون عادةً كائنًا مخصّصًا - يُسمّى كائن الوصول إلى البيانات Data access object، أو DAO اختصارا - لتخزين البيانات في القاعدة أو جلبها منها ثم تقديمها لبقيّة التطبيق بصيغة تناسبه. تُكتَب في هذا الصّنف استعلامات SQL المطلوب تنفيذها للتخاطب مع قاعدة البيانات، إما مباشرة أو عبر مكتبة وسيطة. يُساعد استخدام كائن DAO على الفصل بين أجزاء التطبيق واحترام مبادئ نمط التطوير Model-Vue-Controller (أو MVC اختصارا)؛ إلا أنّ كائنات DAO تتطلّب الكثير من الوقت، إذ يتحتّم على المطوِّر كتابة الكثير من التعليمات لتخزين البيانات في القاعدة أو جلبها منها. تأتي أطُر عمل ORM - ومن بينها Eloquent - للتخفيف من عبء هذه المهمّة. إطار العمل Eloquent ORM يسعى Laravel إلى أن يكون إطار عمل سهلَ الاستخدام يوفّر على المطوّرين الوقت ويرفع من إنتاجيّتهم؛ لذا جعل من Eloquent إطار عمل ORM الذي يستخدمه مبدئيًّا، لما يتميّز به من سهولة الاستخدام ووضوح آليّة العمل. طُوِّر Eloquent وفقا لنمط التسجيلة النشطة Active record الذي يقوم على وجود صنف Class لكلّ جدول في قاعدة البيانات، بحيث يُنشَأ كائن من هذا الصّنف لكل تسجيلة (سطر في الجدول). يعني هذا أن بيانات الكائن تحمل نسخة طبق الأصل من تسجيل الجدوَل. يُسمِّى Eloquent الصّنفَ الذي يطابق الجدول بالنموذج Model. يحوي كلّ كائن من هذا الصنف خاصيّات Attributes بعدد وأسماء توافق حقول (أعمدة) الجدول؛ وتمكّن الدوالّ التي يوفّرها من القيام بعمليّات من قبيل إدراج التسجيلة، التعديل عليها أو حذفها. إذا كان لدينا - مثلا - جدول في قاعدة البيانات باسم Address نحتفظ فيه بعناوين أشخاص، فإن إدراج عنوان جديد في قاعدة البيانات سيكافئ إنشاء كائن من الصنف (النموذج) Address وملئه بالبيانات التي نريد ثم استدعاء دالة التخزين في هذا الكائن. يضيف Eloquent وظائف مساعدة مثل التحقّق من أن الكائنات تحترم قواعد معيّنة قبل تخزينها في الجدول؛ وهو ما يعني التقليل من الأخطاء على مستوى قاعدة البيانات. قد تجد نفسك أمام حالات معقّدة لا تؤدّي فيها هذه الطريقة (تطبيق الدوالّ على الصنف المماثل لجدول قاعدة البيانات) النتيجة المطلوبة؛ يمكنك في هذه الحالة استخدام منشئ الاستعلامات Query builder وكتابة استعلامات SQL مباشرة. يُنصَح بتجنّب Eloquent عند تنفيذ سلسلة طويلة من المهامّ تلقائيًّا، دون تدخّل يدوي. يُناسب Eloquent المعاملات Transactions، وهي مجموعة إجراءات تُنفَّذ في قاعدة البيانات على أنّها وِحدة لا تتجزّأ: إما أن تُطبَّق جميع الإجراءات الموجودة في المعاملة أو لا تُطبَّق أي منها.
  5. يجد الكثير من المبتدئين استخدام PHPUnit لاختبار Test الشيفرة المصدرية التي يكتبونها أمرا معقّدا للغاية؛ بدءًا من تثبيت مكتبات الاختبار وليس انتهاء بتشخيص أخطاء عملها. يسهّل Laravel 5 الكثير من هذه التعقيدات إذ يأتي مضمّنا مبدئيّا بمكتبة PHPUnit جاهزة للعمل. يمكنك التأكّد من ذلك بالدخول إلى مجلّد المشروع ثم تنفيذ الأمر التالي: vendor/bin/phpunit --version قد تظهر لك مشكلة في اعتمادات Composer إلا أنه يمكن تجاوزها بتنفيذ الأمر على النحو التالي: vendor/phpunit/phpunit/phpunit --version تظهر بعد تنفيذ الأمر المناسب النتيجة التالية (قد يختلف رقم الإصدار لديك): PHPUnit 4.8.24 by Sebastian Bergmann and contributors. استخدم الأمر الصحيح من بين الأمريْن أعلاه في بقية هذا الدرس. يأتي Laravel مبدئيّا بمثال لاختبار؛ سننظر في هذا المثال لنرى كيف تعمل اختبارات PHPUnit. افتح الملف ExampleTest.php الموجود في المجلّد tests المتفرّع عن مجلّد المشروع: <?php use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; class ExampleTest extends TestCase { /** * A basic functional test example. * * @return void */ public function testBasicExample() { $this->visit('/') ->see('Laravel 5'); } } يمكننا تجربة الاختبار بتنفيذ الأمر التالي الذي ينفّذ جميع الاختبارات الموجودة في الملف tests المذكور أعلاه: vendor/bin/phpunit يبحث الاختبار أعلاه عن سلسلة المحارف Laravel 5 في الصفحة الرئيسية للمشروع التي يشير إليها المسار /. إن كان مشروع Laravel الذي تعمل عليه جديدا فسيظهر سطران كالتالي: Time: 118 ms, Memory: 12.00Mb OK (1 test, 1 assertion) يخبر السطر الأول بالوقت الذي استغرقه الاختبار وحجم الذاكرة المستخدمة لتنفيذه. أما السطر الثاني فيُظهر عدد الاختبارات وعدد التأكيدات Assertions ونتيجتها. لدينا اختبار واحد (ExampleTest) وتأكيد واحد (الصفحة الرئيسية تحوي سلسلة المحارف Laravel 5). بما أن نتيجة الاختبار إيجابية فإن السطر الأخير يظهر باللّون الأخضر. فلنجرّب إخفاق الاختبار. نستبدل Laravel 6 بـ Laravel 5 في ملف الاختبار ثم نعيد تنفيذ الأمر: vendor/bin/phpunit ستظهر الكثير من المُخرجات تحوي الرسائل التالية إضافة لشفرة الصفحة الرئيسية التي نختبرها: Time: 115 ms, Memory: 12.00Mb There was 1 failure: Couldn't find [Laravel 6] on the page. Check content above. FAILURES! Tests: 1, Assertions: 1, Failures: 1. يخبر السطر الأول بالمدة التي استغرقها الاختبار والحجم الذي أخذه في الذاكرة، بينما يشرح السّطر الثالث سبب الإخفاق وهو عدم القدرة على إيجاد سلسلة المحارف Laravel 6 في الصفحة. يلخّص السّطر الأخير نتيجة الأمر التي تظهر باللون الأحمر دلالة على إخفاق الاختبارات. إن حصل خطأ في الشفرة المصدرية للاختبار نفسه (نسيت مثلا ; في نهاية تعليمة) فلن يُنفَّذ الاختبار وستظهر رسالة بما حدث. اختبار استمارة Form تسجيل مستخدم يقضي المطوّرون المبتدئون الكثير من الوقت لاختبار الاستمارات على تطبيقاتهم، إلا أن Laravel يجعل الأمر في منتهى السهولة. سنجرّب في هذه الفقرة اختبار استمارة لتسجيل مستخدم جديد في التطبيق. سنستعين بأداة Artisan لإنشاء أساسيات الاستيثاق (عروض Views التسجيل، الدخول وإعادة تعيين كلمة السر بالإضافة إلى المتحكّمات Controllers والمسارات Routes الضرورية): php artisan make:auth ينشئ الأمر السابق عرضا على المسار register/ به استمارة لتسجيل مستخدم جديد. سنستخدم PHPUnit لاختبار هذه الاستمارة. نستعين بأداة Artisan من جديد لإنشاء ملف للاختبار: php artisan make:test RegistrationTest ملحوظة: تأكّد من إعداد قاعدة البيانات في مشروعك. ينشئ الأمر ملفا باسم RegistrationTest.php في المجلّد tests. نفتح الملف ونعدّله على النحو التالي: <?php use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; class RegistrationTest extends TestCase { use DatabaseTransactions; public function testNewUserRegistration() { $this->visit('/register') ->type('bob', 'name') ->type('hello1@in.com', 'email') ->type('hello1', 'password') ->type('hello1', 'password_confirmation') ->press('Register') ->seePageIs('/'); } } لاحظ أن الشفرة تطلُب زيارة المسار register/، إدخال المعطيات المذكورة في الاستمارة باستخدام الدالة type، الضغط على الزّر Register ثم النظر في النتيجة هل هي المسار /. تأخذ الدالة type معطيين، الأول قيمة حقل الاستمارة والثاني اسم الحقل. يجب أن تكون أسماء المعطيات في الدالة موافقة لحقول الاستمارة. في حالتنا توجد أربعة حقول في الاستمارة. نستخدم الطّابع DatabaseTransactions Trait في الاختبار. يمكّننا هذا الطّابع من إلغاء إدراج تسجيلات جديدة في قاعدة البيانات حتى لا نملأها من غير جدوى. كما نستخدم الطّابع DatabaseMigrations للتراجع عن التهجيرات Migrations وبالتالي حذف الجدول من قاعدة البيانات. الطابع الثالث المستخدم في الاختبار هو طابع WithoutMiddleware الذي يسمح بتخطّي بعض إجراءات الحماية لأغراض الاختبار. يمكننا الآن تنفيذ الاختبار كما يلي: vendor/bin/phpunit tests/RegistrationTest.php النتيجة: Time: 401 ms, Memory: 16.50Mb OK (1 test, 5 assertions) يمكنك قراءة المزيد عن الاختبارات على موقع PHPUnit؛ فقط تذكّر أن الصنف الذي نمدّده في Laravel هو TestCase بدلا من PHPUnit_Framework_TestCase في تطبيقات PHP أخرى. ترجمة -وبتصرّف- لمقال How Use PHPUnit Test in Laravel 5.1 لصاحبه Bill Keck.
  6. نسعى في هذا الدّرس إلى إنشاء قائمتين منسدلتين 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.
  7. اشتهر استخدام منصة 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.
  8. المسترجِعات 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.
  9. إن كنت تخطّط لتطوير مشروع على 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.
  10. كان من الشائع في بدايات التطوير للوب كتابةُ الاستمارات بوسوم 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.
  11. رأينا في درس كيف تنشئ مزود خدمة 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.
  12. تعتمد بنية 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.
  13. الأحداث 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.
  14. يكثُر استخدام المخططات البيانيّة في مواقع الويب لتقريب المعلومة للزّائر. سنرى في هذا الدرس كيفية إنشاء مخطّطات بيانية في تطبيق 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.
  15. لم تعد الصور الرمزيّة لحسابات المستخدمين أمرًا جديدًا ويندر أن تجد برنامجًا لا يسمح لمستخدميه بعرض صورة للمستخدم في صفحته الشّخصية مثلًا أو نحو ذلك، وقد ظهرت من عدّة سنوات فكرة أن يكون للمستخدم صورة رمزية يمكنه استخدامها في الكثير من المواقع والخدمات دون أن يحتاج في كلّ مرّة إلى اختيارها ورفعها، وهذا ما تمّ فعلًا في مشروع أطلق عليه اسم Gravatar اختصارًا للصورة الرمزيّة المتعارف عليها عالميًّا Globally Recognized Avatar. إنّ إضافة صورة Gravatar إلى شريط التصفّح في مشروع مبني بـ Laravel من أكثر الأعمال سهولة فلا تتطلب العملية أكثر من 4 خطوات بسيطة، ويعود الفضل في هذا إلى فريق CreativeOrange، الذين يمكن زيارة صفحة مشروعهم لقراءة الإرشادات الأصليّة، فيما إذا طرأت تغييرات ما. الخطوة الأولى لنقم بتحميل نسخة من الحزمة المطلوبة باستخدام Composer عبر سطر الأوامر: composer require creativeorange/gravatar ~1.0 أو من الممكن التعديل على ملف composer.json وإضافة اسم الحزمة والإصدار المطلوب إلى لائحة الحزم المطلوبة: "require": { "creativeorange/gravatar": "~1.0" } وفي حال تعديل الملف، فيتوجّب علينا أن نقوم بتنفيذ الأمر التّالي من سطر الأوامر ليتمّ تحديث المشروع وتحميل الحزمة: composer update الخطوة الثانية نحتاج الآن لإضافة مزوّد خدمة Service Provider إلى ملف app/config/app.php في مصفوفة providers بالشّكل: 'providers' => [ // Other Service Providers // ... App\Providers\AppServiceProvider::class, // ... // Other Service Providers ], ملاحظة: لا تنس الفاصلة في نهاية السطر إن لم تدرج السطر في نهاية المصفوفة. نلاحظ فيما سبق بأننا استخدمنا الصيغة الخاصة بـ PHP 5.6 وهي الصيغة القياسية لمصفوفة providers في الإصدار 5.* من Laravel. الخطوة الثالثة لنقم الآن بإضافة إختصار في مصفوفة aliases في ملف app.php ذاته: 'aliases' => [ // ... 'Gravatar' => Creativeorange\Gravatar\Facades\Gravatar::class, // ... ], نلاحظ بأن كلّ ما احتجنا لإضافته في الخطوتين السابقتين هو سطر واحد فقط، وهذا من الأمور المميّزة في معمارية Laravel، حيث أنّ حزّم Service Providers كأحجار البناء في التطبيق البرمجيّ ويمكن ملاحظة مدى سهولة استخدامها في البرنامج، فلا يتطلّب الأمر أكثر من سطرين. أصبح الآن بالإمكان استخدام الصفّ Gravatar في البرنامج بسهولة من خلال الواجهة التي حصلنا عليّها، الأمر الذي يمكن القيام به على النحو التالي: use Gravatar; لكن لن نحتاج لاستخدامه بهذا الشكل في مثالنا. الخطوة الرابعة من المحتمل أن الاستخدام الأسهل للإضافة Gravatar يكون عبر إدراجها مباشرة في عرض View، كالصّفحة الرّئيسية كأن تكون عنصر قائمة في شريط التصفّح الرئيسي العلويّ. سنحتاج بداية للتحقّق فيما إذا قد تمّ تسجيل الدخول أولًا أم لا، ويمكن ذلك باستخدام ()Auth::check المتاحة للاستخدام دومًا عند الحاجة لها، كما في الشّكل: @if (Auth::check()) // أظهر العنصر @endif تستخدم الشّيفرة السابقة الصّيغة القياسية لنظام القوالب Blade والذي نحتاج لمعرفة استخدامه عند اختيار إطار العمل Laravel، وتسمح صيغة blade بالمزج ما بين شيفرات HTML و PHP بسهولة عند الاستخدام، دون الحاجة لاستخدام وسوم البدء الخاصّة بـ PHP، من خلال إدراج التعليمات ما بين الأقواس {{ .. }} كما يظهر في الكود البرمجيّ التالي: @if (Auth::check()) <li> <img src="{{ Gravatar::get(Auth::user()->email) }}" style="height:50px; width:50px; border-radius:50%;"> </li> @endif والآن لنأخذ الجزء الأهمّ ما بين الأقواس ولنقم بتوضيحه: Gravatar::get(Auth::user()->email) يؤدّي استدعاء التابع ()get من الصنفّ Gravatar إلى إعادة مسار نصيّ بصيغة URL لصورة gravatar بعد أن نقوم بتزويده ببريد إلكترونيّ، ونظرًا لأنّا نرغب بعرض صورة حساب المستخدم النشط في البرنامج فإنّ بالإمكان الحصول على البريد الإلكتروني للحساب من الكائن object الذي يعيده استدعاء التابع ()Auth::user الذي يحمل معلومات عن المستخدم صاحب جلسة العمل النشطة. قد لا يكون قياس الصّورة التي نحصل عليها مناسبًا لنا، ولحلّ هذه المشكلة يمكن تغيير القياس الافتراضيّ المطلوب من خلال تعديل قيمة المتغيّر size في المصفوفة default في الملف vendor/creativeorange/gravatar/config/gravatar.php: 'default' => array( 'size' => 80, 'fallback' => 'mm', 'secure' => false, 'maximumRating' => 'g', 'forceDefault' => false, 'forceExtension' => 'jpg', ), وهناك العديد من القيم الافتراضية التي يمكن تغييرها لتتناسب مع احتياجاتنا في هذا الملف، كالصّورة الافتراضيّة التي يتم عرضها في حال لم يكن للمستخدم صورة Gravatar، أو طلب الصورة بصيغة مختلفة عن JPG، وغير ذلك. ترجمة -وبتصرّف- للمقال Laravel 5.1 Gravatar Plugin لصاحبه Bill Keck.
  16. يستخدم 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.
  17. يقدّم هذا الدرس كيفية إنشاء نموذج Model قاعدي في Laravel ومن ثم استخدامه. النماذج هي الجزء من بنية MVC الذي تُعالَج فيه البيانات وتُنفَّذ عليها قواعد التطبيق. يتطلّب الدرس تثبيت Laravel وإعداده مع قاعدة البيانات. نبدأ بإنشاء النموذج باستخدام أمر artisan على النحو التالي: php artisan make:model Widget -m سمّينا النموذج بـWidget وأضفنا خيار m- لإنشاء التهجير في نفس الوقت. يختزل الأمر بهذه الطريقة الكثير من الوقت ويجعلنا نركّز على الأهم: عمل النموذج. ستجد بعد اكتمال تنفيذ الأمر ملفا باسم Widget.php في مجلّد app المتفرّع عن مجلّد المشروع، وملفًّا للتهجير في المجلّد database/migrations. يظهر اسما الملفّيْن في مخرجات تنفيذ الأمر السّابق. نفتح ملفّ Widget.php للنظر في محتواه: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Widget extends Model { // } هذا كلّ ما يوجد الملف! هيكل نموذج يمكننا الاستفادة منه لإنشاء ما نريد. بالانتقال إلى مجلّد database/migrations نجد ملفًّا يشبه التالي: 2016_03_19_163722_create_widgets_table.php يظهر في بداية اسم الملف ختم زمني بتاريخ إنشائه. يفترض Laravel أن اسم النموذج كلمة مفردة (Widget مثلا) تبدأ بحرف كبير Uppercase، في حين يتوقّع أن يكون اسم الجدول Table جمعًا (widgets) يبدأ بحرف صغير. يمكن تفسير الأمر بأن النموذج يُرجِع نظيرا واحدا لتسجيلات الجدول. إذا نظرنا إلى ملفّ التهجيرات فسنجد أن لدينا قاعدة يمكننا البناء عليها لأمور أكثر تقدّما: use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateWidgetsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('widgets', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('widgets'); } } ينشئ Laravel تلقائيا حقل المعرّف في قاعدة البيانات id ويجعله يتقدّم تلقائيا فور إدراج تسجيلة جديدة في الجدول (autoincrement) وذلك باستخدام الدّالة increments. يضيف Laravel كذلك ختمين زمنيّين لتاريخَيْ إنشاء التسجيلة وتحديثها timestamps. سنضيف عمودا Column جديدا إلى قاعدة البيانات؛ لذا نضيف التعليمة التالية إلى دالّة up ضمن ملفّ التهجير: $table->string('widget_name')->unique(); يضيف السّطر أعلاه عمودا جديدا للجدول باسم widget_name من نوع string ويقيّده بـunique لكي لا توجد تسجيلتان في الجدول بنفس الاسم. تصبح دالّة up في ملف التهجير بعد إضافة العمود على النحو التالي: public function up() { Schema::create('widgets', function (Blueprint $table) { $table->increments('id'); $table->string('widget_name')->unique(); $table->timestamps(); }); } ثم ننفّذ التهجير: php artisan migrate ستلاحظ بعد تنفيذ الأمر إنشاءَ جدول جديد في قاعدة البيانات لديك. بما أننا نخطّط لإدراج تسجيلات إلى قاعدة البيانات بالتطبيق فيجب أن نضيف الخاصيّة التالية إلى النموذج Widget: /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['widget_name']; تخبر هذه التعليمة Laravel أن العمود widget_name يدعم الإسناد الشّامل Mass assignment (تحديد قيم معطياتٍ عدّةٍ مرة واحدة). إن لم نضف هذه التعليمة فلن يمكننا باستخدام التطبيق إدراجُ تسجيلات جديدة في الجدول. سنستخدم في الدرس التالي النموذج الذي أنشأناه أعلاه مع معمل النماذج Model factory في Laravel لملْء تسجيلات في جدول widgets. ترجمة -وبتصرّف- للمقال How to Make a Model in Laravel 5.1 لصاحبه Bill Keck.
  18. في هذا الدرس سنتعرف على طريقة إضافة زر يسمح للمستخدم بالدخول إلى تطبيق الويب المكتوب بإطار العمل Laravel دون الحاجة للمرور بخطوة إنشاء حساب يدوياً. سنعتمد في شرحنا هذا على إطار العمل Laravel 5.2 وسوف نأخذ فيس بوك كمثال عن شبكة اجتماعية للسماح لمستخدمي تطبيقنا بالدخول عبرها. لكن يمكنك تطبيق الخطوات نفسها المشروحة في هذا الدرس لإنشاء زر دخول عبر تويتر أو GitHub أو Google أو Bitbucket أو LinkedIn. تم اختبار خطوات هذا الدرس على Laravel 5.1 وأيضاً 5.2. مبدأ الدخول عبر الشبكات الاجتماعية الغالب أنك تعرف خطوات الدخول إلى المواقع باستخدام حسابك على فيس بوك أو تويتر، فالعديد من المواقع والتطبيقات توفر ميزة الدخول عبر الشبكات الاجتماعية لتسريع عملية الدخول إلى التطبيق بدلاً من الطلب من المستخدم إنشاء حساب جديد يدوياً. ومع ذلك دعنا نسرد هذه الخطوات سريعاً: عندما تريد الدخول إلى موقع يحتاج لتسجيل الدخول يعرض لك استمارة الدخول؛ تجد ضمن الاستمارة السابقة زر "دخول عبر فيس بوك" (أو شبكة اجتماعية أخرى)؛ عند النقر على هذا الزر تنتقل إلى موقع فيس بوك (أو تفتح نافذة جديدة صغيرة للمتصفح ويفتح موقع فيس بوك داخلها)؛ يطلب منك فيس بوك منح صلاحيات مُحدّدة للتطبيق الخاص بالموقع (مثلاً السماح للتطبيق بالاطلاع على معلوماتك الشخصية العامة أو بريدك الإلكتروني) بعد الموافقة يعاد توجيهك إلى الموقع الأصلي وتجد نفسك قد دخلت إلى الموقع وكأنك تملك حساب. الفكرة هي أن فيس بوك يمرر للموقع الآخر معلومات معينة عن حسابك بحيث يتمكن هذا الموقع من إنشاء حساب لك دون الحاجة لتعبئة أي معلومات يدوياً، كما ويمكن استخدام هذه المعلومات في عمليات الدخول اللاحقة للتعرف عليك والسماح لك بالوصول إلى حسابك نفسه (وليس إنشاء حساب جديد مرة أخرى!) دون أن تكتب كلمة سر للدخول. تبادل المعلومات هذا بين المواقع له معايير. المعيار الأكثر شهرة والذي نعتمد عليه في هذا المقال هو معيار OAuth بنسختيه 1.0 و2.0. بيئة العمل سنفترض أن لديك تطبيق Laravel 5.1 سابق، وتريد إضافة ميزة الدخول عبر الشبكات الاجتماعية إليه. إذا لم يكن إطار العمل Laravel مثبتاً لديك راجع درس تثبيت Laravel: تثبيت Laravel وإعداده على ويندوز وأوبنتو. بالنسبة لي أستخدم نظام ويندوز مع XAMPP للعمل، لكن يمكنك تطبيق الخطوات على لينكس أو أي نظام تشغيل آخر بالطبع. سنحتاج أيضاً لتثبيت Socialite، وهي حزمة تسهل علينا عملية الاستيثاق عبر بروتوكول OAuth كثيراً. لتثبيت Socialite أضف السطر التالي إلى ملف composer.json في مشروع Laravel الخاص بك: "require": { ... "laravel/socialite": "~2.0" } أو بدلاً من ذلك يمكنك استخدام الأمر: composer require laravel/socialite بعد ذلك نفذ الأمر التالي لتنزيل الملفات المطلوبة: composer update بعد انتهاء عملية التنزيل، عليك تعديل ملف الضبط config/app.php وإضافة السطر التالي إلى مصفوفة Service Providers (اسمها providers ضمن الملف): Laravel\Socialite\SocialiteServiceProvider::class, والسطر التالي إلى مصفوفة Aliases: 'Socialite' => Laravel\Socialite\Facades\Socialite::class, الآن أصبح موقعك جاهزًا للتعامل مع الشبكات الاجتماعية. كما ذكرنا في بداية هذا الدرس هذه الطريقة تعمل مع العديد من الشبكات الاجتماعية التي توفر ميزة الاستيثاق عبر OAuth، إذ أن حزمة Socialite تدعم ست شبكات اجتماعية حالياً (فيس بوك وتويتر ولينكدإن و Google وGitHub وBitbucket). الخطوة الأولى: إنشاء مسارات التوجيه سنحتاج في عملية الدخول هذه إلى استدعائين: عندما يطلب المستخدم الدخول إلى الموقع، وهنا سوف نحوله إلى موقع فيس بوك للموافقة على منح الصلاحيات لتطبيقنا. بعد أن يوافق المستخدم على منح الصلاحيات للتطبيق سيقوم فيس بوك بإعادة توجيه المستخدم إلى موقعنا لإتمام عملية الدخول. بالنسبة لي فقد اخترت أن يكون هذان الاستدعائان على مسار واحد (ولكن يمكنك فصلهما إلى مسارين)، واخترت أن يكون المسار هو auth/facebook. لقد أضفت السطر التالي إلى ملف routes.php لدي: Route::get('auth/facebook', 'SocialAuthController@facebookLogin'); كما تلاحظ فقد طلبت استخدام المتحكم SocialAuthController لمعالجة الطلبات الواردة إلى هذا المسار. هذا المتحكم عبارة عن مُتحكّم Controller عادي يرث من الصف App\Http\Controllers\Controller: <?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; class SocialAuthController extends Controller {} الآن دعنا نعرف الدالة facebookLogin. ما الذي نريد أن يحدث عندما يصل المستخدم إلى المسار auth/facebook؟ في الواقع نحن نريد إضافة زر دخول عبر فيس بوك، وهذا الزر سيوجه المستخدم إلى هذا المسار وهنا نريد أن ينتقل المستخدم إلى موقع فيس بوك ليمنح تطبيقنا الصلاحيات اللازمة. تعليمة توجيه المستخدم إلى فيس بوك هي التالية، فقط أضفها ضمن دالة facebookLogin: return Socialite::with('facebook')->redirect(); طبعاً ستحتاج عدد من التعديلات قبل أن تعمل هذه التعليمة عندك: عليك التأكد من تضمين Socialite في ترويسة الصف: use Laravel\Socialite\Facades\Socialite; أين هو تطبيق الفيس بوك الخاص بك؟ عليك إنشاؤه الآن أو التوجه إلى صفحة إعدادات التطبيق الخاص بك إذا كنت تملك تطبيقاً بالفعل. لإنشاء تطبيق جديد أو إدارة تطبيقاتك السابقة على فيس بوك توجه إلى المسار التالي: https://developers.facebook.com/apps على موقع فيس بوك عليك العثور على خيار Callback Url واستخدام نفس المسار المحلي الذي اخترته، انظر كيف كانت القيمة لدي: من موقع فيس بوك عليك العثور على معلومات تطبيقك: نحتاج للحصول على معرف التطبيق (Client ID) وأيضاً الجملة السرية (Client Secret). الآن اذهب إلى config/services.php وأضف ما يلي إلى الملف: 'facebook' => [ 'client_id' => 'معرف التطبيق الذي حصلت عليه من فيس بوك', 'client_secret' => 'الجملة السرية الخاصة بتطبيقك على فيس بوك', 'redirect' => 'رابط التحويل الذي سيتجه إليه المستخدم بعد منح الصلاحيات اللازمة على فيس بوك', ], تحذير: لا تستخدم الدالة المساعدة url ضمن ملف services.php. هذا يؤدي لتوقف أداة artisan عن العمل. يمكنك بدلاً من كتابة المتغيرات السابقة ضمن ملف services أن تستخدم السطور التالية وبعدها تعرف المتغيرات ضمن ملف env. لديك: 'facebook' => [ 'client_id' => env('FACEBOOK_CLIENT_ID'), 'client_secret' => env('FACEBOOK_CLIENT_SECRET'), 'redirect' => env('FACEBOOK_REDIRECT_URL'), ], الميزة هنا هي أن ملف services يضاف غالباً إلى مستودع التّطبيق، بينما ملف.env يبقى على الجهاز المحلي وهذا يفيد إذا لم تكن تريد السماح للمطورين الآخرين بالإطلاع على هذه المعلومات لسبب ما. بالنسبة لي فقد اخترت أيضاً نقل قيمة redirect إلى ملف env. لأن هذا الرابط سيتغير حتماً ما بين بيئة التطوير وبيئة الإنتاج (Production)، وبالتالي من المفيد وضعه مع بقية الإعدادات الخاصة بكل بيئة. الآن إذا حاولت الوصول إلى الرابط auth/facebook يجب أن تنتقل إلى موقع فيس بوك ويطلب منك الموافقة على منح الصلاحيات لتطبيقك هناك: بعد الموافقة، ستجد غالباً أن متصفح الوب لديك سيعطيك خطأ ويشتكي أن الصفحة فيها مشكلة في إعادة التوجيه. لا تقلق، أنت على الطريق الصحيح. ما الذي حدث؟ عندما طلب المستخدم المسار auth/facebook تحول إلى موقع فيس بوك ووافق على منح الصلاحيات، بعدها أعاد فيس بوك توجيهه إلى موقعنا على نفس المسار ولكن هذه المرة هناك متغيرات مع الطلب. المشكلة هنا هي أن موقعنا لا يعالج عملية استقبال المستخدم بعد عودته من فيس بوك وإنما يعيد توجيهه إلى فيس بوك مرة أخرى والذي يعيد توجيهه إلى موقعنا وهكذا في حلقة لا نهائية. علينا الآن معالجة الحالة التي يصل فيها المستخدم إلى المسار auth/facebook ومعه المتغيرات اللازمة للسماح له بالدخول. الخطوة الثانية: التعرف على المستخدم دعنا نعدل دالة facebookLogin لتصبح كالتالي: public function facebookLogin(Request $request) { // توجيه المستخدم إلى فيس بوك إذا لم يكن لديه كود الدخول if( ! $request->has('code') ) return Socialite::with('facebook')->redirect(); // وإلا علينا التعرف على المستخدم وتسجيل دخوله إلى الموقع $oauthUser = Socialite::with('facebook')->user(); dd($oauthUser); } حالياً إذا نجحت في الخطوات السابقة يجب أن تطبع لديك كافة معلومات المستخدم الذي حصلنا عليها من فيس بوك. تهانينا! لم يبق إلا البحث في قاعدة بيانات موقعنا لنرى إذا كان المستخدم موجوداً من قبل أم لا؛ فإذا عثرنا عليه سمحنا له بالدخول، وإلا فسوف ننشئ حساباً جديداً له ونسمح له بالدخول على حسابه الجديد. هناك حقول كثيرة يعطينا إياها فيس بوك، وهذه الحقول قد تختلف في الواقع بين شبكة اجتماعبة وأخرى، لكن هناك حقول ثابتة دوماً: id: رقم تعريف المستخدم لدى فيس بوك. ثابت دوماً ولا يتغير ولذلك سنعتمد عليه في التعرف على المستخدم. name: اسم المستخدم الحقيقي (وليس username). قد يتغير إذا غيره المستخدم على موقع فيس بوك. nickname: لقب المستخدم. فيس بوك يعيد null هنا حسب ما لاحظت، ولكن تويتر يعطي قيمة username. avatar و avatar_original: رابط صورة حساب المستخدم. عادة تكون صورة original أكبر حجماً. email: البريد الإلكتروني للمستخدم. لاحظ أنه قد تحصل على قيمة البريد الإلكتروني للمستخدم وقد تحصل على قيمة null، ولذلك لا يمكنك الاعتماد على البريد الإلكتروني لمعرفة المستخدم إذا كان جديداً أو مستخدمًا سابقًا لموقعك. السبب هو أن بعض الشبكات الاجتماعية مثل فيس بوك تسمح للمستخدم بالتسجيل عبر رقم الهاتف وقد لا يملك فيس بوك عنوان البريد الإلكتروني للمستخدم، أو قد يختار المستخدم عدم الإفصاح عن عنوان بريده الإلكتروني لتطبيقك أثناء مرحلة اختيار الصلاحيات ومنحها للتطبيق. عليك الآن التعديل على قاعدة البيانات الخاصة بالموقع وإضافة عمود اسمه facebook_id إلى جدول المستخدمين، سيكون الحقل unique ويسمح بقيم null (لأن هناك مستخدمين قد يستخدمون عملية التسجيل التقليدية للدخول وبالتالي لا يملك النظام معرف حسابهم على فيس بوك). الآن نعود إلى دالة facebookLogin وبدلاً من تعليمة dd سنضع السطور التالية: $user = $this->findOrCreateUser($oauthUser); Auth::login($user, true); return redirect('home'); ما فعلناه هو استدعاء دالة ضمن الصف نفسه هي دالة findOrCreateUser تأخذ معلومات حساب المستخدم على فيس بوك وتعيد لنا كائنًا من نموذج User ‏(User Model)، الذي استدعينا عليه دالة Auth::login لتسجيل دخوله في الجلسة الحالية ومررنا قيمة true كمتغير ثان حتى نتذكر المستخدم (remember me). أما عن دالة findOrCreateUser فهي كما يلي: private function findOrCreateUser($oauthUser) { if ($user = User::where('facebook_id', $oauthUser->id)->first()) return $user; return User::create([ 'name' => $oauthUser->name, 'email' => $oauthUser->email, 'facebook_id' => $oauthUser->id ]); } دالة بسيطة جدًا، تبحث عن المستخدم ضمن جدول المستخدمين اعتمادًا على رقم تعريف حسابه لدى فيس بوك، وإذا لم تعثر عليه تنشئ مستخدمًا جديدًا بالاعتماد على المعلومات التي حصلنا عليها من فيس بوك. لا تنس التعديل على User.php بما يناسب. إذا استخدمت دالة إنشاء المستخدم هذه ستحتاج إضافة الحقل facebook_id إلى مصفوفة fillable. الآن، يمكنك اختبار العملية بشكل كامل: ضع رابطًا في مكان مناسب يشير إلىauth/facebook وانقر عليه وتابع خطوات عملية التسجيل، في نهاية العملية يجب أن يعاد توجيهك إلى صفحة homeبعد دخولك إلى الموقع. ملاحظات إذا أردت تطبيق هذه الخطوات على تويتر أو أي شبكة أخرى فالعملية نفسها تماماً، عدا أن تويتر يعتمد بروتوكول OAuth 1.0 وليس 2، لذلك عليك فحص وجود متغير اسمه oauth_token بدلاً من code؛ أما الخطوات الأخرى فلا يوجد أي اختلافات. إذا أردت تذكّر الصفحة التي طلبها المستخدم قبل ظهور استمارة تسجيل الدخول وتوجيهه إليها بعد الدخول بدلاً من توجيهه إلى home في كل مرة، فقط استبدل السطر التالي: return redirect()->intended('defaultpage'); بالسطر القديم: return redirect('home'); لا تقيد نفسك بالطريقة المذكورة هنا، يمكنك إعادة تنظيم الشيفرات السابقة بعدة أساليب. مثلاً يمكنك وضع الدوال السابقة ضمن متحكم Auth\AuthController بدلاً من إنشاء متحكم جديد، أو إنشاء خدمة (Service) ووضعها فيها، الخ. أخطاء واجهتني أثناء تطبيق هذه الخطوات واجهتني عدة أخطاء سأذكرها هنا مع حلولها. المشكلة 0 FatalErrorException in AbstractProvider.php line 134: Call to a member function set() on null هذه المشكلة ظهرت عندما حاولت تطبيق هذه الخطوات لأول مرة على Laravel 5.2 ولم أنتبه إلى أنني وضعت تعريف المسار auth/facebook خارج middleware المسمى "web". هذا يعني أن Session غير متاحة في هذا النطاق، ولذلك واجهني هذا الخطأ. الحل: بكل بساطة نقلت تعليمة Route إلى داخل مجموعة التوجيه الخاصة بـ web" middleware" كالتالي: Route::group(['middleware' => 'web'], function () { ... Route::get('/auth/facebook', 'SocialAuthController@facebookLogin'); }); المشكلة 1 RequestException in CurlFactory.php line 187: cURL error 60: SSL certificate problem: unable to get local issuer certificate ما الذي تقوله هذه الرسالة؟! لا يمكن التحقق من شهادة SSL الخاصة بموقع فيس بوك! يا للكارثة شهادة SSL لديهم فيها مشكلة… كيف يمكنني إخبارهم بهذا؟ الواقع أنني استغربت ظهور هذه الرسالة عند محاولة الدخول عبر فيس بوك فقط ولم تظهر عند الدخول عبر تويتر. المشكلة هي أن إضافة cURL لدي ليس لديها معلومات ناشري شهادات SSL حتى تتمكن من التحقق من صلاحية شهادة فيس بوك، وكان الحل بتنزيل هذه المعلومات وتعديل ملف php.ini بحيث تتمكن cURL من قراءة هذه المعلومات والتحقق من شهادة فيس بوك. هناك حل ثان وهو تعطيل خيار التحقق من شهادة SSL، ولكن هذا الحل لا ينصح به. قد لا تواجهك هذه المشكلة، فهي متعلقة بطريقة ضبط php لديك، على أي حال إذا كنت تستخدم XAMPP على ويندوز مثلي وواجهتك هذه المشكلة إليك الطريقة التي استخدمتها لتجاوزها: أولاً حصلت على ملف cacert.pem الذي يحوي معلومات CA (أي Certificate Authorities وهي الهيئات التي تصادق على شهادات SSL لمواقع الوب) من هذا الرابط: http://curl.haxx.se/docs/caextract.html. بعد ذلك نسخته إلى مجلد xampp/php وأخيراً عدلت ملف php.ini وأضفت ما يلي إليه: [cURL] curl.cainfo="C:\xampp\php\cacert.pem" تستخدم cURL هذا المتغير للبحث عن ملف معلومات CA وقراءته، بعد ذلك ستتمكن من التحقق من سلامة شهادة فيس بوك بنجاح وستختفي هذه المشكلة. تنبيه: يجب إعادة تشغيل خادوم الوب حتى تأخذ هذه التغييرات مفعولها. المشكلة 2 InvalidStateException in AbstractProvider.php line 191 وهذه نفسها تعطي أحيانًا رسالة Client Error. تظهر هذه المشكلة إذا استدعيت التعليمة التالية أكثر من مرة في تطبيقك لسبب أو لآخر: Socialite::with('facebook')->user() لا يمكنك استدعاء هذه التعليمة سوى مرة واحدة فقط بعد توجيه المستخدم إلى فيس بوك عبر تعليمة: Socialite::with('facebook')->redirect() والحصول على كود الدخول. الحل: عليك الاحتفاظ بالكائن oauthUser$ الذي حصلت عليه من هذه التعليمة في متغير وتمريره إلى الدوال الأخرى إذا كنت تحتاجه بدلاً من طلبه كل مرة عبر Socialite. المشكلة 3 عند إضافة زر "دخول عبر تويتر" استمرت الصفحة في الدخول في حلقة إعادة توجيه لا نهائية رغم فحص وجود المتغير code في الطلب. تبين لي بعدها أن تويتر يستخدم النسخة 1.0 من بروتوكول OAuth ولذلك فهو يمرر متغيرات مختلفة. الحل: غيرت الشرط بحيث يفحص وجود المتغير oauth_token بدلاً من code. الختام في هذا الدرس اطلعنا على حزمة Socialite الخاصة بإطار Laravel التي تسمح بالاستيثاق من هوية المستخدمين بسهولة تامة عبر حساباتهم على الشبكات الاجتماعية. يمكنك تنزيل مشروع Laravel المستخدم في هذا الدرس والاطلاع على الشفرة البرمجية فيه. إذا واجهتك مشكلة في تطبيق هذا الدرس فلا تتردد في طرح سؤالك في تعليق أو على منصة الأسئلة والأجوبة التابعة لأكاديمية حسوب.
  19. في الحالة العادية، سوف تقوم بكتابة تابع (store method) أولا، ومن ثم تترك عملية التحقق في النهاية حتى يعمل store بشكل جيد، لكن في حالتنا هذه سيكون الأمر معقدا قليلا بما أننا نتعامل مع ملفات منفصلة يجب حفظها، ففي حالة الصور، سوف نحتاج إلى التحقق أولا. صنف Requestسوف نبدأ بإنشاء صنف (request class) والذي سيقوم بالتعامل مع عملية التحقق من استمارة الإنشاء. سوف نبدأ بكتابة هذا الأمر على سطر الأوامر: php artisan make:request CreateImageRequestهذا الأمر سيقوم بإنشاء الملف ووضعه داخل مجلد app/Http/Requests. بعد ذلك، عدل الملف كالتالي: <?php namespace App\Http\Requests; use App\Http\Requests\Request; use App\Marketingimage; class CreateImageRequest extends Request { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'image_name' => 'alpha_num | required | unique:marketing_images', 'mobile_image_name' => 'alpha_num | required | unique:marketing_images', 'is_active' => 'boolean', 'is_featured' => 'boolean', 'image' => 'required | mimes:jpeg,jpg,bmp,png | max:1000', 'mobile_image' => 'required | mimes:jpeg,jpg,bmp,png | max:1000' ]; } }واحد من الأشياء التي أحبها في إطار Laravel هو كيفية تعامله مع عملية التحقق، فإذا كانت هذه المرة الأولى التي ستتعامل فيها مع عملية التحقق، سوف تجد أن هذه العملية بسيطة للغاية. لاحظ أننا قمنا بكتابة السطر التالي في تابع authorize: return true;هذا السطر سوف يسمح للطلب أن يمر، فإذا وضعنا شرطا عليه فسنستطيع التحكم فيه، فعلى سبيل المثال، يمكننا استخدامه في تسجيل الدخول للأعضاء فقط. بعد ذلك لدينا قواعد التابع، الذي سوف يتم فرضها على عملية التحقق في حقول الاستمارة، لن نتعمق أكثر هنا، فإذا أردت المزيد يمكنك الإطلاع على توثيق Laravel. للتذكير فإننا نفصل ما بين قواعد التّحقق بمحرف "|" ولما نضيف قاعدة "unique" فإننا نُرفقها باسم الجدول الذي يجب أن نبحث فيه (يعني الجدول الذي نرغب في أن يكون السّجل record الذي نقوم بحفظه وحيدًا "unique"). ومن الطبيعي أننا أننا قمنا بإضافة قيد على حجم الملفات. يمكنك إيجاد قائمة كاملة من قواعد التحقق في الوثائق. تابع Storeعلى أي حال، سوف ننتقل الآن لتابع store في MarketingImageController، عدل التابع كما يلي: public function store(CreateImageRequest $request) { //create new instance of model to save from form $marketingImage = new Marketingimage([ 'image_name' => $request->get('image_name'), 'image_extension' => $request->file('image')->getClientOriginalExtension(), 'mobile_image_name' => $request->get('mobile_image_name'), 'mobile_extension' => $request->file('mobile_image')->getClientOriginalExtension(), 'is_active' => $request->get('is_active'), 'is_featured' => $request->get('is_featured'), ]); //define the image paths $destinationFolder = '/imgs/marketing/'; $destinationThumbnail = '/imgs/marketing/thumbnails/'; $destinationMobile = '/imgs/marketing/mobile/'; //assign the image paths to new model, so we can save them to DB $marketingImage->image_path = $destinationFolder; $marketingImage->mobile_image_path = $destinationMobile; // format checkbox values and save model $this->formatCheckboxValue($marketingImage); $marketingImage->save(); //parts of the image we will need $file = Input::file('image'); $imageName = $marketingImage->image_name; $extension = $request->file('image')->getClientOriginalExtension(); //create instance of image from temp upload $image = Image::make($file->getRealPath()); //save image with thumbnail $image->save(public_path() . $destinationFolder . $imageName . '.' . $extension) ->resize(60, 60) // ->greyscale() ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension); // now for mobile $mobileFile = Input::file('mobile_image'); $mobileImageName = $marketingImage->mobile_image_name; $mobileExtension = $request->file('mobile_image')->getClientOriginalExtension(); //create instance of image from temp upload $mobileImage = Image::make($mobileFile->getRealPath()); $mobileImage->save(public_path() . $destinationMobile . $mobileImageName . '.' . $mobileExtension); // Process the uploaded image, add $model->attribute and folder name flash()->success('Marketing Image Created!'); return redirect()->route('marketingimage.show', [$marketingImage]); }حسنا، دعونا نبدأ بفهم هذه الشفرة الطويلة، سنبدأ مع توقيع الدالة: public function store(CreateImageRequest $request)هذا الجزء واضح، فلقد قمنا فقط بسحب نسخة (instance) من كائن request يحمل الاسم request$ وهذا سوف يساعدنا على إنشاء نسخة جديدة للنموذج: //create new instance of model to save from form $marketingImage = new Marketingimage([ 'image_name' => $request->get('image_name'), 'image_extension' => $request->file('image')->getClientOriginalExtension(), 'mobile_image_name' => $request->get('mobile_image_name'), 'mobile_extension' => $request->file('mobile_image')->getClientOriginalExtension(), 'is_active' => $request->get('is_active'), 'is_featured' => $request->get('is_featured'), ]);لاحظ أنني أقوم بالتعليق كثيرا على هذا التابع (method)، لأنه من السهل أن يربكك. على أي حال، قمنا بإنشاء مثيل جديد لـ Marketingimage ووضعنا خصائص النموذج باستخدام مثيل request$. أما البقية الشيفرة، فلقد قُمنا بسحب البيانات من الاستمارة إذا نجحت في عملية التحقق. ثم سوف نحتاج إلى تعريف مسارات الصور: //define the image paths $destinationFolder = '/imgs/marketing/'; $destinationThumbnail = '/imgs/marketing/thumbnails/'; $destinationMobile = '/imgs/marketing/mobile/';ملاحظة: حاولت أن أجعل هذه الأسماء بديهية قدر الإمكان. بعد ذلك، سوف نقوم بتعيينها إلى النموذج حتى نتمكن من حفظهم في قاعدة البيانات. //assign the image paths to new model, so we can save them to DB $marketingImage->image_path = $destinationFolder; $marketingImage->mobile_image_path = $destinationMobile;نستطيع فعل هذا بطريقة أخرى، فيمكننا أن نقوم بتقديم معلومات المسار في الاستمارة أو يمكننا تخطي هذا كليا، لكن وجدت أن إبقاء المسار في قاعدة البيانات يجعل العمل مع الصور أسهل عندما نحتاج إلى استخدامها في العروض (views)، وهذا هو سبب استخدامنا هذه الطريقة. ثم سنقوم بتهيئة (format) قيم خانة اختيار ومن ثم نقوم بحفظها: // format checkbox values and save model $this->formatCheckboxValue($marketingImage); $marketingImage->save(); يمكنك أن ترى أنني أقوم بتسليم مثيل النموذج marketingImage$ إلى تابع formatCheckboxValue، وهو تابع قمت بعمله للتأكد من أن خانة الاختيار يتم التعامل معها بشكل جيد: public function formatCheckboxValue($marketingImage) { $marketingImage->is_active = ($marketingImage->is_active == null) ? 0 : 1; $marketingImage->is_featured = ($marketingImage->is_featured == null) ? 0 : 1; }بسبب نوع البيانات في قاعدة البيانات، يمكننا توفير فقط رقمين 0 و 1، لذلك نحتاج إلى التأكد من أننا نقوم بالتحويل بشكل صحيح من خانة الاختيار (checkbox) في الاستمارة (form). لقد قمنا بتسليمها إلى مثيل النموذج ومن ثم سيقوم بوضع الخصائص وفقا لذلك. بعد ذلك نعود إلى تابع store، ونقوم ببساطة بالحفظ: $marketingImage->save();وهذا سوف يهتم بسجلات قاعدة البيانات، ومع ذلك، لانزال بحاجة إلى التعامل مع الملف. في البداية سوف نعمل على بعض أجزاء الصورة التي سنحتاجها: //parts of the image we will need $file = Input::file('image'); $imageName = $marketingImage->image_name; $extension = $request->file('image')->getClientOriginalExtension();بدلا من ذلك، يمكنك جعل بعض هذه الشفرات في سطر واحد (inline) لكنك ستحصل على سطور طويلة جدا بالإضافة إلى أن إبقائها هكذا أسهل للتتبع والفهم. الآن سوف نحتاج إلى استخدام مكتبة الصور (Image library) لمساعدتنا على الخروج من هنا، سنقوم بإنشاء مثيل لهذه الصورة من الصور المرفوعة: //create instance of image from temp upload $image = Image::make($file->getRealPath());بعد ذلك، سنقوم بحفظها وإنشاء صورة مصغرة والتي سنقوم بحفظها عن طريق سَلسَلة التوابع (chaining the methods): //save image with thumbnail $image->save(public_path() . $destinationFolder . $imageName . '.' . $extension) ->resize(60, 60) // ->greyscale() ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);سوف نقوم بتقسيم هذه الشيفرة إلى أجزاء حتى نفهمها بشكل أفضل، أول جزء يقوم بحفظ الصور الأولية: $image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)استخدمنا ()public_path للوصول إلى المكان الذي يمكننا تعريف فيه مجلد الهدف (مجلد الصور) والذي هو imgs/marekting/. بعد ذلك قمنا بسَلسَلة أجزاء الصورة (مسار ملف الصورة) التي نحتاجها وهذا سوف يعطينا كل شيء نحتاجه وسيسهل علينا عملية متابعة الشيفرة البرمجية. بما أننا نحتاج أيضا إلى إنشاء صورة مصغرة، قمنا بسَلسَلة ذلك التابع: ->resize(60, 60)وهذا السطر سوف ينشئ لنا نسخة بحجم 60 × 60 من الصورة، وإذا أردت تغيير حجم النسخة يمكنك فعل هذا بسهولة بتغيير الأرقام، ويمكنك أيضا إنشاء حقل في الاستمارة للمستخدمين لتحديد ارتفاع و عرض الصور المصغر. (لن نقوم بهذا في هذا الدرس.) قمتُ بإرفاق (على شكل تعليق) تابع متسَلسَل لجعل الصورة المصغرة ذات تدرج رمادي، فإذا أردت صور مصغرة بيضاء وسوداء اجعل السطر شيفرة برمجية (عن طريق إزالة //). والآن سنقوم بحفظ الصورة المصغرة: ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);وسنقوم بنفس الشيء لصور الهاتف: // create instance of image from temp upload $mobileImage = Image::make($mobileFile->getRealPath()); $mobileImage->save(public_path() . $destinationMobile . $mobileImageName . '.' . $mobileExtension);يما أنها مثل الصور الأولية لكن بدون صورة مصغرة، سوف نتركها هكذا. حسنا، بعد ذلك لدينا رسالة الفلاش (flash message): flash()->success('Marketing Image Created!');هذه الشيفرة لن تعمل إلا إذا قمت بتثبيت حزمة الفلاش لجفري ويِ (Jefferey Wey) وقمت باستدعائها في عرض (view) الصفحة الرئيسية، وإذا لم تقم بتثبيت هذه الحزمة فلا تكتب هذه الشيفرة. في النهاية، بعد حفظ كُل من النموذج وملفات الصور، سوف نقوم بإعادة التوجيه نحو صفحة العرض: return redirect()->route('marketingimage.show', [$marketingImage]);الآن يمكنك تجربة رفع وحفظ الصور وعمل صورة مصغرة وصورة للهاتف عن طريق زيارة الاستمارة في العنوان التالي: yourdomain.com/marketingimage/createيمكنك التأكد من إنشاء الصور من خلال النظر إلى المجلدات حيث يُفترض أن تكون الصور. سوف ترى أن تابع store يقوم بالكثير من الأشياء، لكن ينقصه آلية للتحقق من الأخطاء، يمكنك فعل هذا لجعل التطبيق أكثر قوة على الرغم من أنني أعتقد أن أمر ليس ضروري. بما أن Laravel يقوم بوظيفة جيدة عن طريق فصل استمارة التحقق من تابع store، لن يتوقف التابع إذا فشلت عملية التحقق. حسنا ماذا يمكن أن يفشل أيضا ؟ حسنا ربما تفشل عملية حفظ الملف وهذا يمكن أن يكون بسبب امتلاء النظام، أو بسبب مشاكل في الصلاحيات أو خطأ في كتابة المسارات وأسماء المجلدات. هذه الأخطاء صعبة الاكتشاف والتصحيح بسبب أنه ربما قد لا تحصل على رسائل الخطأ المناسبة، وعلى أية حال، هذه هي أشهر الأخطاء إذا أردت اكتشافها وتصحيحها. يمكنك أن ترى أنه على الرغم من أنه لا شيء صعب في إدارة الصورة إلا أن الصعوبة موجودة بشكل ما. الخطوة المنطقية التالية هي إنشاء تابع show في المتحكم وإنشاء العرض الخاص به. تابع showحسنا، هذه هي شيفرة تابع العرض: public function show($id) { $marketingImage = Marketingimage::findOrFail($id); return view('marketingimage.show', compact('marketingImage')); }هذه الشيفرة واضحة للغاية، يقوم Laravel باسترجاع مثيل النموذج ثم يقوم بإرجاعه إلى العرض view، لاحظ استخدام findOrFail والتي تقوم بإرجاع ModelNotFoundException، والتي يمكنك التعامل معها من خلال ملف Handler.php في app/Exceptions. عرض showحسنا، بعدما انتهينا من التابع ننتقل إلى العرض view: @extends('layouts.master') @section('content') {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage', "show $marketingImage->image_name.$marketingImage->image_extension" ]) !!} <div> {{ $marketingImage->image_name }} : <br> <img src="/imgs/marketing/{{ $marketingImage->image_name . '.' . $marketingImage->image_extension . '?'. 'time='. time() }}"> </div> <div> {{ $marketingImage->image_name }} - thumbnail : <br> <img src="/imgs/marketing/thumbnails/{{ 'thumb-' . $marketingImage->image_name . '.' . $marketingImage->image_extension . '?'. 'time='. time() }}"> </div> <div> {{ $marketingImage->mobile_image_name }} - mobile : <br> <img src="/imgs/marketing/mobile/{{ $marketingImage->mobile_image_name . '.' . $marketingImage->mobile_extension . '?'. 'time='. time() }}"> </div> @endsectionملاحظة: يمكنك إزالة مساعدات Breadcrumb إذا لم تكن تستخدم تلك الحزمة. لم نجعل الواجهة الأمامية فاخرة هنا، قمنا فقط بعمل صفحة بسيطة لإظهار الصور. وبما أننا أرسلنا مثيل marketingImage$ إلى المتحكم، فإننا استخدمنا تركيبة blade لاستدعاء اسم الصورة: {{ $marketingImage->image_name }}ثم نقوم بإستدعاء الصور الأولية: <img src="/imgs/marketing/{{ $marketingImage->image_name . '.' . $marketingImage->image_extension . '?'. 'time='. time() }}">سترى أننا استخدمنا تركيبة blade طباعة اسم الصورة وامتدادها والتي هي مسجلة في قاعد البيانات، وبإبقاء الامتداد في قاعدة البيانات يمكننا استخدام امتدادات مختلفة بدلا من فرض استخدام jpg. على سبيل المثال. سوف تلاحظ أيضا أننا أضفنا: '?'. 'time='. time() للحصول على متغير، وهذا الأمر سيمنع عملية تخزين المؤقت للصور نظرا لأنه تم إلحاق الوقت في نهاية الرابط. إن التخزين المؤقت للصور يمكن أن يكون مشكلة بالنسبة لإدارة الصور عندما تقوم برفع الصور. وبالطبع هذا الأمر اختياري ويمكنك تجاوزه. بقية الشيفرة البرمجية تقريبا نفسها مع بعض التغييرات للصور المصغرة والصور الهواتف. لن تكون الصفحة جميلة جدا، لكنها تقوم بالمهمة. والآن إذا كان لديك صورة قمت برفعها لحفظها في قاعدة البيانات، يمكنك مشاهدتها عن طريق الذهاب إلى: yourproject.com/marketingimage/1يمكنك تغيير الرقم في النهاية لأن هذا الرقم هو رقم الصورة في سجل البيانات. حسنا، بعد ذلك، قبل أن نعمل على edit و update، لنقم ببناء تابع index في المتحكم ثم عرض (index view). تابع indexفي MarketingImageController، عدل تابع index كما يلي: public function index() { $images = Marketingimage::all(); return view('marketingimage.index', compact('images')); }يمكنك أن ترى أن الشيفرة سهلة للغاية، فلقد قمنا فقط باسترجاع جميع السجلات إلى كائن images$، والتي يتم تمريرها إلى العرض view عن طريق تابع compact. عرض Indexبعد ذلك، سنقوم بتعديل views/marketingimages/index.blade.php كما يلي: @extends('layouts.master') @section('content') {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage']) !!} <br> <div> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading">List of Marketing Images </div> <div class="panel-body"> <a href="/marketingimage/create"> <button type="button" class="btn btn-lg btn-success">Create New </button> </a> </div> <!-- Table --> <table class="table"> <tr> <th>Id </th> <th>Name </th> <th>Thumbnail </th> <th>Edit </th> <th>Delete </th> </tr> @foreach($images as $image ) <tr> <td>{{ $image->id }} </td> <td>{{ $image->image_name }} </td> <td> <a href="/marketingimage/{{ $image->id }}"> <img src="/imgs/marketing/thumbnails/{{ 'thumb-'. $image->image_name . '.' . $image->image_extension . '?'. 'time='. time() }}"> </a> </td> <td><a href="/marketingimage/{{ $image->id }}/edit"> <span class="glyphicon glyphicon-edit" aria-hidden="true"> </span> </a> </td> <td>{!! Form::model($image, ['route' => ['marketingimage.destroy', $image->id], 'method' => 'DELETE']) !!} <div class="form-group"> {!! Form::submit('Delete', array('class'=>'btn btn-danger', 'Onclick' => 'return ConfirmDelete();')) !!} </div> {!! Form::close() !!} </td> </tr> @endforeach </table> </div> </div> @endsection @section('scripts') <script> function ConfirmDelete() { var x = confirm("Are you sure you want to delete?"); if (x) return true; else return false; } </script> @endsectionلقد بدأنا بتوسيع layouts.master ومن ثم قمنا بفتح content كما يلي: @section('content')ومن ثم استخدمنا Breadcrumb. (مرة أخرى، إذا كنت لا تستخدم الحزمة فاحذف هذا السطر.) بعد ذلك قمنا باستخدام شيفرات Bootstrap أساسية، والتي حصلنا عليها من الموقع الرسمي. وفي النهاية سنجد جدول داخل مُكوّن panel لـ bootstrap بالإضافة إلى زر لإنشاء صورة جديدة، وأزرار أخرى للتعديل والحذف لكل صورة، وستظهر الصور المصغرة لكل صورة في الجدول. سوف ترى في رأسية مُكوّن panel كيف قمنا بالتعامل مع زر create: <a href="/marketingimage/create"> <button type="button" class="btn btn-lg btn-success"> Create New </button> </a>كل شيء واضح، فالجدول ليس معقدا، لكنه تطلب حلقة foreach لطباعة جميع الصفوف (rows): <!-- Table --> <table class="table"> <tr> <th>Id </th> <th>Name </th> <th>Thumbnail </th> <th>Edit </th> <th>Delete </th> </tr> @foreach($images as $image ) <tr> <td>{{ $image->id }} </td> <td>{{ $image->image_name }} </td> <td> <a href="/marketingimage/{{ $image->id }}"> <img src="/imgs/marketing/thumbnails/{{ 'thumb-'. $image->image_name . '.' . $image->image_extension . '?'. 'time='. time() }}"> </a> </td> <td> <a href="/marketingimage/{{ $image->id }}/edit"> <span class="glyphicon glyphicon-edit" aria-hidden="true"> </span> </a> </td> <td>{!! Form::model($image, ['route' => ['marketingimage.destroy', $image->id], 'method' => 'DELETE' ]) !!} <div class="form-group"> {!! Form::submit('Delete', array('class'=>'btn btn-danger', 'Onclick' => 'return ConfirmDelete();')) !!} </div> {!! Form::close() !!} </td> </tr> @endforeach </table>لا توجد أية صعوبة في الشيفرة، يمكنك أن ترى أنني أضفت مساعدات النموذج form helper حول رابط الحذف delete، وهذا لأسباب أمنية، يجب علينا الحذف باستخدام POST بدل السماح بأن تكون مفتوحة بشكل واسع من خلال استعمال GET. سوف تلاحظ أيضا أننا قمنا باستخدام التالي: 'Onclick' => 'return ConfirmDelete();'والتي استخدمناها مع الجافاسكربت لفتح صندوق التأكيد confirm box، حتى لا يقوم المستخدمون بحذف شيء عن طريق الخطأ. ثم في ('section('scripts@ أضفنا شيفرات جافاسكربت التالية، وكملاحظة سريعة، افترضنا أنه لديك ('yeld('scripts@ في صفحتك الرئيسية (master page)، وإذا لم يكن لديك هذا السطر، يمكنك ببساطة إرفاق الجافاسكربت قبل endsection@ التابعة لـ ('section('content@. هذه هي شيفرة الجافاسكربت: <script> function ConfirmDelete() { var x = confirm("Are you sure you want to delete?"); if (x) return true; else return false; } </script>شيفرة بسيطة للغاية. والآن، يجب أن تحصل على صفحة index تعمل بدون مشاكل، قم بتجربة ذلك عن طريق الذهاب بمتصفحك إلى: yourproject.com/marketingimageنُكمل في الجزء الثالث من الدرس إن شاء الله. ترجمة -وبتصرّف- للمقال Basic Image Management Part 2 لصاحبه Bill Keck. حقوق الصورة البارزة: Designed by Freepik.
  20. سنواصل في هذا الدّرس حديثنا حول نشر تطبيق PHP باستخدام Ansible. هذا الدّرس هو الجزء الثّاني للسلسلة، إذا لم تقرأ الجزء الأول فمن الأفضل أن تشرع به أوّلا، حيث نُعالج فيه تثبيت Ansible وإعداده. إذا لم تكن لديك أيّة خلفية مُسبقة حول Ansible فمن الأفضل أن تبدأ مع مقال كيفيّة تثبيت وإعداد Ansible على Ubuntu. سنتحدث في هذا القسم من الدّرس عن كيفية الحصول على إطار عمل Laravel وإعداده وإعداد خادوم Nginx لتخديم تطبيق PHP. سنستخدم إطار عمل Laravel كمثال عن تطبيق PHP ولكن يُمكِن تعديل هذه التعليمات بسهولة لتدعم أطر عمل وتطبيقات أخرى في حال كانت متواجدة لديك. المتطلبات الأساسيةيبدأ هذا الدّرس مباشرة من حيث انتهى الجزء الأول من هذه السلسلة، ونحتاج كافّة الملفّات والإعدادات التي تم الحصول عليها في ذلك الجزء، إن لم تقم بإكمال أول درس من هذه السلسلة فنرجو أن تفعل ذلك قبل متابعة هذا الدّرس. الخطوة الأولى – استنساخ مستودع Gitفي هذا القسم سنقوم باستنساخ clone مستودع إطار عمل Laravel إلى الخادوم الخاص بنا باستخدام Git، سنشرح مثل الخطوة الثالثة كافّة الأقسام التي سنقوم بإضافتها إلى الـ playbook، ونضمّن بعدها كامل ملف php.yml من أجلك لتقوم بنسخه ولصقه. وقبل أن نستنسخ مستودع Git الخاص بنا نحتاج إلى التأكّد من وجود الدليل var/www/، نستطيع فعل هذا عن طريق إنشاء مهمّة مع وحدة الملف: - name: create /var/www/ directory file: dest=/var/www/ state=directory owner=www-data group=www-data mode=0700 نحتاج كما أشرنا سابقًا إلى استخدام الوحدة Git لاستنساخ المستودع إلى الخادوم الخاص بنا، وهي عمليّة بسيطة لأنّ كل ما نحتاجه بشكل طبيعي من أجل الأمر git clone هو مستودع المصدر source repository، في هذه الحالة سنعرّف أيضًا الوجهة destination ونخبر Ansible بعدم تحديث المستودع في حال وجوده مسبقًا عن طريق تعيين update=no، وبما أنّنا نستخدم Laravel سنستعمل رابط مستودع git التالي: https://github.com/laravel/laravel.git نحتاج مع ذلك إلى تشغيل المهمّة عن طريق المستخدم www-data للتأكّد من أنّ الصلاحيّات صحيحة، ولعمل هذا نستطيع إخبار Ansible أن ينفّذ الأمر كمستخدم محدّد باستخدام sudo، ستبدو المهمّة النهائية كما يلي: - name: Clone git repository git: > dest=/var/www/laravel repo=https://github.com/laravel/laravel.git update=no sudo: yes sudo_user: www-data ملاحظة: من أجل المستودعات المعتمدة على SSH نستطيع إضافة accept_hostkey=yes لمنع التحقّق من المضيف على SSH من تعليق المهمّة. نفتح الملف php.yml لتحريره: nano php.yml نضيف المهام السابقة إلى الـ playbook، يجب أن يبدو الملف النهائي كما يلي: ... - name: enable php5 mcrypt module shell: php5enmod mcrypt args: creates: /etc/php5/cli/conf.d/20-mcrypt.ini - name: create /var/www/ directory file: dest=/var/www/ state=directory owner=www-data group=www-data mode=0700 - name: Clone git repository git: > dest=/var/www/laravel repo=https://github.com/laravel/laravel.git update=no sudo: yes sudo_user: www-data handlers: - name: restart php5-fpm service: name=php5-fpm state=restarted - name: restart nginx service: name=nginx state=restarted نحفظ الـ playbook ونغلقها، ثمّ نقوم بتشغيلها: ansible-playbook php.yml --ask-sudo-pass الخطوة الثانية – إنشاء تطبيق باستخدام Composerسنستخدم في هذه الخطوة Composer لتثبيت تطبيق PHP واعتمادياته dependencies. يمتلك Composer الأمر create-project الذي يقوم بتثبيت كافّة الاعتماديات المطلوبة ومن ثمّ يُشغِّل خطوات إنشاء المشروع المُعرَّفة في القسم post-create-project-cmd من الملف composer.json، وهي الطريق المفضّلة للتأكّد من إعداد التطبيق بشكل صحيح لاستخدامه لأول مرّة. نستطيع استخدام مهمّة Ansible التالية لتنزيل وتثبيت Composer بشكل عمومي كـ usr/local/bin/composer/، سيكون بعدها قابلًا للنفاذ من قبل أي شخص يستخدم الخادوم بما فيهم Ansible: - name: install composer shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer args: creates: /usr/local/bin/composer ومع وجود Composer مُثبّتًا لدينا فهنالك وحدة لـ Composer يُمكننا استخدامها، في حالتنا نريد إخبار Composer عن مكان مشروعنا (باستخدام المُعامِل working_dir)، ونريد أيضًا تنفيذ الأمر create-project، نحتاج أيضًا لإضافة المُعامِل optimize_autoloader=no لأنّ هذا العَلَم غير مدعوم من قبل الأمر create-project، وكما في حالة الأمر git يجب علينا تنفيذ هذا عن طريق المستخدم www-data للتأكّد من صلاحيّة الأذونات permissions، وبوضع كل ذلك معًا نحصل على هذه المهمّة: - name: composer create-project composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no sudo: yes sudo_user: www-data ملاحظة: قد تستغرق المهمّة create-project قدرًا كبيرًا من الوقت على خادوم جديد، حيث يمتلك الـ Composer ذاكرة تخزين مؤقّت فارغة ويحتاج إلى تنزيل كل شيء من جديد. نفتح الآن الملف php.yml لتحريره: nano php.yml نضيف المهام السابقة في نهاية القسم tasks فوق handlers بحيث تتوافق نهاية الـ playbook مع التالي: ... - name: Clone git repository git: > dest=/var/www/laravel repo=https://github.com/laravel/laravel.git update=no sudo: yes sudo_user: www-data - name: install composer shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer args: creates: /usr/local/bin/composer - name: composer create-project composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no sudo: yes sudo_user: www-data handlers: - name: restart php5-fpm service: name=php5-fpm state=restarted - name: restart nginx service: name=nginx state=restarted نقوم أخيرًا بتشغيل الـ playbook: ansible-playbook php.yml --ask-sudo-pass ما الذي سيحدث إن قمنا بتشغيل Ansible مرّة أخرى الآن؟ سيتم تشغيل composer create-project مرّة أخرى، وفي حالة Laravel يعني هذا APP_KEY جديد، لذا نريد بدلًا من ذلك تعيين المهمّة لتعمل فقط بعد استنساخ clone جديد، نستطيع ضمان أنّها تعمل فقط مرّة واحدة عن طريق تسجيل متغيّر يحتوي نتائج المهمّة git clone ومن ثمّ التحقّق من هذه النتائج داخل المهمّة composer create-project، إن تمّ تغيير المهمّة git clone فسيتم تنفيذ composer create-project، أمّا إن لم يحدث ذلك فسيتم تخطّيها. ملاحظة: يبدو أنّه يوجد خطأ برمجي في بعض إصدارات وحدة composer في Ansible، وربّما تعيد الخَرْج OK بدلًا من Changed، حيث تتجاهل أنّه تمّ تنفيذ scripts على الرغم من أنّه لم يتم تثبيت اعتماديات. نفتح الملف php.yml من أجل تحريره: nano php.yml نبحث عن المهمّة git clone، نضيف الخيار register لحفظ نتائج المهمّة إلى المتغير cloned كما يلي: - name: Clone git repository git: > dest=/var/www/laravel repo=https://github.com/laravel/laravel.git update=no sudo: yes sudo_user: www-data register: cloned </code>نبحث بعدها عن المهمّة composer create-project، نضيف الخيار when للتحقّق من المتغيّر cloned لنرى إن تغيّرت قيمته أم لا: - name: composer create-project composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no sudo: yes sudo_user: www-data when: cloned|changed نقوم بحفظ الـ playbook وتشغيلها: ansible-playbook php.yml --ask-sudo-pass سيوقف الـ Composer الآن تغيير APP_KEY في كل مرّة يتم تشغيله فيها. الخطوة الثالثة – تحديث متغيرات البيئةفي هذه الخطوة سنقوم بتحديث متغيّرات البيئة Environment Variables من أجل تطبيقنا. يأتي Laravel مع ملف env. بشكل افتراضي والذي يقوم بتعيين قيمة APP_ENV إلى local وقيمة APP_DEBUG إلى true، نحتاج إلى تبديل هذه القيم إلى production و false على الترتيب، يُمكِن فعل هذا ببساطة باستخدام الوحدة lineinfile مع المهام التالية: - name: set APP_DEBUG=false lineinfile: dest=/var/www/laravel/.env regexp='^APP_DEBUG=' line=APP_DEBUG=false - name: set APP_ENV=production lineinfile: dest=/var/www/laravel/.env regexp='^APP_ENV=' line=APP_ENV=production نفتح الملف php.yml لتحريره: nano php.yml نضيف هذه المهمّة إلى الـ playbook، يجب أن تبدو نهاية الملف متطابقة مع التالي: ... - name: composer create-project composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no sudo: yes sudo_user: www-data when: cloned|changed - name: set APP_DEBUG=false lineinfile: dest=/var/www/laravel/.env regexp='^APP_DEBUG=' line=APP_DEBUG=false - name: set APP_ENV=production lineinfile: dest=/var/www/laravel/.env regexp='^APP_ENV=' line=APP_ENV=production handlers: - name: restart php5-fpm service: name=php5-fpm state=restarted - name: restart nginx service: name=nginx state=restarted نقوم بحفظ الـ playbook وتشغيلها: ansible-playbook php.yml --ask-sudo-pass إنّ الوحدة lineinfile مفيدة جدًا من أجل التطويعات tweaks السريعة لأي ملف نصّي، وهي رائعة للتأكّد من تعيين متغيّرات بيئة كهذه بشكل صحيح. الخطوة الرابعة – إعداد Nginxسنقوم في هذا القسم بإعداد Nginx لتخديم تطبيق PHP. إن زرت الآن الخادوم الخاص بك في متصفّح الإنترنت (على سبيل المثال http://your_server_ip )، فسترى صفحة Nginx الافتراضيّة بدلاً من صفحة المشروع الجديد في Laravel، حدث هذا لأنّنا لا زلنا نحتاج إلى إعداد خادوم ويب Nginx لدينا لتخديم التطبيق من الدليل var/www/laravel/public/، لفعل هذا نحتاج لتحديث إعدادات Nginx الافتراضيّة بهذا الدليل وإضافة دعم من أجل php-fpm بحيث يستطيع التعامل مع PHP scripts. نقوم بإنشاء ملف جديد يُدعى nginx.conf: nano nginx.conf نحفظ كتلة الخادوم التالية بداخل هذا الملف، تستطيع متابعة الخطوة الرابعة من هذا الدّرس من أجل المزيد من التفاصيل حول إعدادات Nginx، تُحدِّد التعديلات التالية مكان دليل Laravel العام وتتحقّق من استخدام Nginx لاسم المضيف الذي عرّفناه في الملف hosts كـ server_name مع المتغيّر inventory_hostname. nginx.conf server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /var/www/laravel/public; index index.php index.html index.htm; server_name {{ inventory_hostname }}; location / { try_files $uri $uri/ =404; } error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/laravel/public; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } نحفظ الملف nginx.conf ونغلقه. بإمكاننا الآن استخدام وحدة القالب template module لدفع ملف الإعدادات الجديدة عبرها، تبدو وحدة template مشابهة جدًا للوحدة copy، ولكن يوجد فرق كبير بينهما، فالوحدة copy ستنسخ ملف أو عدّة ملفّات بدون القيام بأيّة تغييرات، بينما تنسخ الوحدة template ملفًّا واحدًا وتقوم بتحليل كافّة الملفّات الموجودة بداخله، ولأنّنا استخدمنا {{ inventory_hostname }} داخل ملف إعداداتنا سنستخدم الوحدة template بحيث يتم تحليلها بداخل عنوان IP الذي استخدمناه في الملف hosts، وبهذه الطريقة لا يتوجّب علينا كتابة شيفرة محدّدة لملفّات الإعدادات التي تستخدمها Ansible. ومع ذلك فكما هو معتاد عند كتابة المهام نحتاج إلى أن نأخذ بعين الاعتبار ما سيحدث على الخادوم، ولأنّنا نقوم بتغيير إعدادات Nginx نحتاج لإعادة تشغيل Nginx و php-fpm، يتم هذا باستخدام الخيار notify: - name: Configure nginx template: src=nginx.conf dest=/etc/nginx/sites-available/default notify: - restart php5-fpm - restart nginx نفتح ملف php.yml: nano php.yml نضيف مهمّة nginx هذه في نهاية قسم المهام، ينبغي أن يبدو الملف php.yml كما يلي: php.yml --- - hosts: php sudo: yes tasks: - name: install packages apt: name={{ item }} update_cache=yes state=latest with_items: - git - mcrypt - nginx - php5-cli - php5-curl - php5-fpm - php5-intl - php5-json - php5-mcrypt - php5-sqlite - sqlite3 - name: ensure php5-fpm cgi.fix_pathinfo=0 lineinfile: dest=/etc/php5/fpm/php.ini regexp='^(.*)cgi.fix_pathinfo=' line=cgi.fix_pathinfo=0 notify: - restart php5-fpm - restart nginx - name: enable php5 mcrypt module shell: php5enmod mcrypt args: creates: /etc/php5/cli/conf.d/20-mcrypt.ini - name: create /var/www/ directory file: dest=/var/www/ state=directory owner=www-data group=www-data mode=0700 - name: Clone git repository git: > dest=/var/www/laravel repo=https://github.com/laravel/laravel.git update=no sudo: yes sudo_user: www-data register: cloned - name: install composer shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer args: creates: /usr/local/bin/composer - name: composer create-project composer: command=create-project working_dir=/var/www/laravel optimize_autoloader=no sudo: yes sudo_user: www-data when: cloned|changed - name: set APP_DEBUG=false lineinfile: dest=/var/www/laravel/.env regexp='^APP_DEBUG=' line=APP_DEBUG=false - name: set APP_ENV=production lineinfile: dest=/var/www/laravel/.env regexp='^APP_ENV=' line=APP_ENV=production - name: Configure nginx template: src=nginx.conf dest=/etc/nginx/sites-available/default notify: - restart php5-fpm - restart nginx handlers: - name: restart php5-fpm service: name=php5-fpm state=restarted - name: restart nginx service: name=nginx state=restarted نقوم بحفظ الـ playbook وتشغيلها مرّة أخرى: ansible-playbook php.yml --ask-sudo-pass حالما تكتمل نعود إلى متصفحنا ونقوم بتحديثه، ينبغي أن نرى الآن صفحة المشروع الجديد في Laravel. الخاتمةيُغطّي هذا الدرس نشر تطبيق PHP مع مستودع عام، وبينما يكون هذا مثاليًّا من أجل تعلّم كيفيّة عمل Ansible، فإنّك لن تعمل دائمًا على مشاريع مفتوحة المصدر بشكل كامل مع مستودعات مفتوحة، ويعني هذا أنّك ستحتاج إلى استيثاق git clone في الخطوة الثالثة مع مستودعك الخاص، يُمكِن فعل هذا بسهولة باستخدام مفاتيح SSH. على سبيل المثال بعدما تقوم بإنشاء وإعداد مفاتيح نشر SSH على مستودعك تستطيع استخدام Ansible لنسخها وإعدادها على خادومك قبل مهمّة git clone: - name: create /var/www/.<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr>/ directory file: dest=/var/www/.<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr>/ state=directory owner=www-data group=www-data mode=0700 - name: copy private <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr> key copy: src=deploykey_rsa dest=/var/www/.<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr>/id_rsa owner=www-data group=www-data mode=0600يجب أن يسمح هذا للخادوم بالاستيثاق بشكل صحيح ونشر تطبيقك. لقد قمنا للتو بنشر تطبيق PHP بسيط على خادوم ويب Nginx معتمد على Ubuntu باستخدام Composer لإدارة الاعتماديّات، وتمّ كل هذا بدون الحاجة للدخول مباشرة إلى الخادوم الخاصّ بنا وتنفيذ أي أمر بشكل يدوي. ترجمة -وبتصرّف- لـ How To Deploy a Basic PHP Application Using Ansible on Ubuntu 14.04 لصاحبه Stephen Rees-Carter.
  21. إدارة الصور هي واحدة من الأشياء التي غالبا ما أجدها صعبة، فأنا معتاد على التعامل مع النماذج وجداول قواعد البيانات، لكن التعامل مع الملفات ليس سهلا بالنسبة لي، فإذا كنت قد بدأت للتو مع Laravel وبدأت تشعر بالصعوبة والضيق، فأنا أشعر بألمك. لحسن الحظ، أساسيات إدارة الصور في Laravel لن تكون صعبة إذا فهمت بعض الأمور الأساسية. ملاحظة: إذا كنت جديدا في Laravel، فلا أنصحك بالبدء بهذا الدرس، أنصحك بقراءة بعض الدروس والدورات حول Laravel قبل المتابعة. سوف تلاحظ أن الدرس طويل لذلك قمت بتقسيمه إلى 3 أجزاء، وسوف تلاحظ أيضا أنني لست بارعا في تصميم الواجهة الأمامية (front-end)، فلذلك فالواجهات التي سنقوم بعملها لن تكون جميلة جدا، يمكنك تزيينها بنفسك لاحقا، فالهدف الأساسي هنا هو تعلم أساسيات إدارة الصور. لذلك على أية حال، ماهي هذه الأساسيات ؟ فكرتُ بالمتطلبات التي قد تحتاجها في تطبيقك وكتبتها في هذه القائمة: إنشاء الصورتخزين الصورتعديل الصورتحديث الصورإنشاء صور مصغرةتعديل صور مصغرةإنشاء وتعديل صور الهواتف بشكل منفصلبالنسبة للذين اعتادوا على إجراءات المتحكِّمات (controller actions) المريحة، ستظهر الاستمارات عند إجراءات (actions) الإنشاء create والتعديل edit وأما إجراءات التخزين store والتحديث update فوظيفتهم إنشاء وتعديل السجلات والملفات. سنتعامل مع شيئين مهمين عند تعاملنا مع الصور، الشيء الأول هو النموذج الذي يعمل على البيانات مثل اسم الصورة و مسارها، وأما الشيء الثاني فهو ملف الصورة نفسه الذي سوف يتم تخزينه في مجلد الذي سنقوم بإسناده له. تثبيت Interventionسوف نبدأ بتثبيت حزمة Intervention/image، فإذا لم تقُم بذلك، عدل على ملف composer.json وأضف التعليمة التالية في جزء الاستدعاء (require) : "intervention/image": "~2.2"ثم أضف السطر التالي ضمن مصفوفة Providers في ملف app\config\app.php (انتبه للفواصل) : Intervention\Image\ImageServiceProvider::class,في نفس الملف أضف السطرين التاليين لمصفوفة Aliases: Intervention\Image\ImageServiceProvider::class,أنصحك بالإطلاع على صفحة Intervention للتأكد من الإصدار الأخير للحزمة. في آخر مرة تأكدتُ فيها،كانت تعليمات التَدَخّل تستخدم النمط القديم للإشارة إلى المرجع: 'Intervention\Image\ImageServiceProvider'يمكنك أن تلاحظ في الأعلى أننا في كلتا الحالتين استخدمنا ::class والتي هي ممارسة جيدة تعلمتها من Laracasts.com، فإذا كنت تستخدم على سبيل المثال PHP Storm، فسيكون باستطاعتك الوصول إلى الفئة class الأساسية. إن حزمة Intervention تقوم بإعطائنا صياغة (syntax) وطريقة سهلة لصناعة الصور المصغرة بالإضافة إلى الكثير من التوابع (method) الأخرى الرائعة، لذلك سوف نستخدمها في هذا الدرس، ولمزيد من المعلومات حول هذه الحزمة أنصحك بالإطلاع على التوثيق الرسمي. لاحظ أيضا أننا سنقوم باستخدام حزمة laravelcollective/html وحزمة patricktalmadge/bootstrapper، ولذلك قم بتثبيتهم قبل أن تتابع الدرس. إنشاء نموذجسوف نبدأ بإنشاء نموذج Marketingimage، يمكننا فعل ذلك عن طريق artisan من سطر الأوامر بكتابة السطر التالي: php artisan make:model Marketingimage -mسوف تلاحظ علم m- الذي سيخبر Laravel أنك تريد إنشاء تهجير migration في نفس الوقت، لذلك فهذه الميزة مفيدة للغاية. حسنا، قم بتنفيذ الأمر السابق وستحصل على ملف النموذج والمسمى Marketingimage.php مباشرة تحت دليل تطبيقك وستحصل أيضا على ملف التهجير في مجلد database/migrations. دعونا نقوم بتعديل التابع في أعلى ملف التهجير إلى ما يلي: public function up() { Schema::create('marketing_images', function(Blueprint $table) { $table->increments('id'); $table->boolean('is_active')->default(false); $table->boolean('is_featured')->default(false); $table->string('image_name')->unique(); $table->string('image_path'); $table->string('image_extension', 10); $table->string('mobile_image_name')->unique(); $table->string('mobile_image_path'); $table->string('mobile_extension', 10); $table->timestamps(); }); }أول شيئ يمكنك رؤيته أنني قد غيرت اسم الجدول، فأنا افضل فصل الكلمات في جدول الأسماء بسطر سفلي underscore. أنت حر في اختيار الطريقة التي تعجبك، لكن يجب أن نختار صيغة الجمع لإتباع قواعد Laravel بشكل صحيح. ففي Laravel، النموذج يكتب بصيغة المفرد وأما اسم الجدول فيكتب بصيغة الجمع. بعد عمود المعرف الرقمي، قمنا باستخدام عمودين من نوع المنطقي boolean والتي سوف تسمح لنا بمعرفة هل الصورة نشطة أو مميزة، هذه القيود المفيدة سوف تساعد على العمل مع الصور في وقت لاحق. ثم قمنا بإضافة أعمدة الاسم، المسار والامتدادات للصور وصور الهاتف، وهذا سيسمح لنا بالمرونة الكافية إذا أردت حفظ صورة مختلفة للهاتف، وهذا الأمر ضروري لأن تغيير حجم الصورة قد لا ينتج لنا النتائج المرجوة. وبما أننا سوف نقوم بإنشاء صور مصغرة من الصور الأصلية، لن نحتاج إلى حفظ أية بيانات لذلك. عن طريق حفظ مسار وإمتداد الصورة، سيكون لدينا مرجع سهل نستطيع استخدامه لإظهار الصورة في تطبيقنا، بالإضافة إدارة الصورة في قائمة الصور. عدل على التابع ليبدو على النحو التالي: public function down() { Schema::drop('marketing_images'); }بمجرد أن تقوم بذلك، قم بتنفيذ أمر php artisan migrate من سطر الأوامر وتأكد من أن الجدول قد تم إنشاءه. بعد هذا، سوف نقوم بتعديل نموذج Marketingimage كما يلي: <?php namespace App; use Illuminate\Database\Eloquent\Model; class Marketingimage extends Model { protected $table = 'marketing_images'; protected $fillable = ['is_active', 'is_featured', 'image_name', 'image_path', 'image_extension', 'mobile_image_name', 'mobile_image_path', 'mobile_extension' ]; }سوف تلاحظ أننا قمنا بإخبار النموذج الجدول الذي سيتخذه كمرجع، بالإضافة إلى توفير أعمدة مملوءة تلقائيا، حتى لا نواجه مشكلة الإحالة الكتلية mass-assignment. المتحكمجيد، نحن الآن مستعدين للاستمرار للخطوة القادمة، سنقوم بإنشاء المتحكِّم بإستخدام artisan: php artisan make:controller MarketingImageControllerوبهذا سوف تحصل على متحكِّم في app/Http/Controllers مع التوابع التالية: indexcreatestoreshoweditupdatedestroyوسوف نستخدم جميع هذه التوابع. وكنصيحة مفيدة للمبتدئين، قُم بوضع السطر التالي في تابع index: return 'Here is the index method.';فهذا سوف يعطيك فرصة لتجربة هذا الطريق route. (يستطيع بقية المبرمجين المحترفين تجاوز هذه الخطوة إذا أرادوا) وبعد ذلك، سنقوم بتثبيت الطرق routes. عدل على ملف app/Http/routes.php وأضف التعليمة التالية: Route::resource('marketingimage', 'MarketingImageController');سوف ترى أننا قد قمنا بإضافة مورد resource، والذي سوف يقوم بإعطائنا الطرق routes لجميع الإجراءات actions بطريقة مريحة للغاية. حسنا، سوف تستطيع الآن الذهاب إلى yourproject.com/marketingimage وسوف تحصل على النتيجة التالية: Here is the index method.الخطوة المنطقية التالية هي إعداد العروض views، أنشئ مجلدا باسم marketingimage أسفل resources/views، ثم أنشئ الملفات الفارغة التالية داخل مجلد marketingimages: create.blade.phpedit.blade.phpindex.blade.phpshow.blade.phpإعداد المجلداتسوف نعود إلى تلك الملفات في وقت لاحق، في الوقت الحالي، سنقوم بإنشاء مكان لتخزين صورنا الحالية، سوف أجعل هذا الأمر سهلا، أنشئ مجلدا باسم imgs مباشرة تحت مجلدك العام (public folder)، وبداخل مجلد imgs، أنشئ مجلد marketing، وبداخله أنشئ مجلدا باسم mobile وآخر بإسم thumbnails. الآن قمنا بإنشاء جميع المجلدات للصور. عرض الإنشاء The Create Viewحسنا، دعونا الآن نتعامل مع عرض الإنشاء The Create View. أضف الأسطر التالية داخل ملف `create.blade.php`: @extends('layouts.master') @section('content') {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage', 'create']) !!} <h1>Upload a Photo </h1> <hr/> @if (count($errors) > 0) <div class="alert alert-danger"> <strong>Whoops! </strong> There were some problems with your input. <br> <br> <ul> @foreach ($errors->all() as $error) <li>{{ $error }} </li> @endforeach </ul> </div> @endif {!! Form::open(array('route' => 'marketingimage.store', 'class' => 'form', 'files' => true)) !!} <!-- image name Form Input --> <div class="form-group"> {!! Form::label('image name', 'Image name:') !!} {!! Form::text('image_name', null, ['class' => 'form-control']) !!} </div> <!-- mobile_image_name Form Input --> <div class="form-group"> {!! Form::label('mobile_image_name', 'Mobile Image Name:') !!} {!! Form::text('mobile_image_name', null, ['class' => 'form-control']) !!} </div> <!-- is_something Form Input --> <div class="form-group"> {!! Form::label('is_active', 'Is Active:') !!} {!! Form::checkbox('is_active') !!} </div> <!-- is_featured Form Input --> <div class="form-group"> {!! Form::label('is_featured', 'Is Featured:') !!} {!! Form::checkbox('is_featured') !!} </div> <!-- form field for file --> <div class="form-group"> {!! Form::label('image', 'Primary Image') !!} {!! Form::file('image', null, array('required', 'class'=>'form-control')) !!} </div> <!-- form field for file --> <div class="form-group"> {!! Form::label('mobile_image', 'Mobile Image') !!} {!! Form::file('mobile_image', null, array('required', 'class'=>'form-control')) !!} </div> <div class="form-group"> {!! Form::submit('Upload Photo', array('class'=>'btn btn-primary')) !!} </div> {!! Form::close() !!} @endsectionلاحظ أن الشيفرة في الأعلى ليست صعبة ويمكنك فهمها بسهولة دون أن تضطر إلى قراءة الشرح، ولاحظ أيضا أننا نقوم بتوسيع صفحتنا الرئيسية master page، التي بداخلها مجلد المخططات في مجلد العروض. والتي قمنا باستدعائها عن طريق هذه التعليمة: @extends('layouts.master')إذا كانت لديك صفحة رئيسية أخرى أو أنها موجودة في مكان مختلف، قم بتعديل ذلك حسب الحاجة، وإذا كنت لا تعرف مفهوم الصفحة الرئيسية master page، قُم بالبحث عن دروس حوله وتعلمه قبل أن تتابع الدرس. إذا كنت لا تستخدم حزمة `Bootstrapper`، قم بحذف السطر التالي: {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage', 'create']) !!}لاحظ أيضا، لهذا الدرس، قمت بتضمين `if` لطباعة أخطاء الإدخال، لكن في العادة، يجب وضع هذا الجزء في جزئية العرض view partial ومن ثم الإشارة إليه بشيء مثل هذا: @include('errors.errors')يمكنك أيضا ملاحظة أننا نستخدم مساعد الاستمارة Form helper من حزمة `laravelcollective/html`، فلقد وجدت أن مساعد الاستمارة مفيد جدا خاصة عند استخدامه لفتح الاستمارة: {!! Form::open(array('route' => 'marketingimage.store', 'class' => 'form', 'files' => true)) !!}تستطيع أن ترى أننا قمنا بتضمين 'files => 'true والتي تسمح لنا برفع ملفات متعددة. إذا كنت جديدا في استخدام مساعد الاستمارة، فيمكنك ملاحظة أننا لا نحتاج إلى استخدام POST خاصة وأننا لا نحتاج إلى استدعاء رمز CSFR لأنه يتم ذلك تلقائيا. ثم لدينا مدخلات المختلفة للاستمارة، لا شيء مجنون للغاية هنا، ولدينا أيضا `Form::submit` والتي نستخدمها كزر، بالإضافة إلى `Form::close()`. يمكنك أن ترى من مساعد الاستمارة أن الطريق route تم تعيينه إلى `marketingimage.store`، لذلك سوف نعرف من مورد الطريق في `routes.php` أن هذا سوف يأخذنا إلى تابع `store` في `MarketingImageController.php` والذي هو دليل المتحكِّمات. نُكمل في الجزء الثاني من الدرس. ترجمة -وبتصرّف- للمقال Basic Image Management Part 1 لصاحبه Bill Keck. حقوق الصورة البارزة: Designed by Freepik.
  22. حتى الآن تعلمنا كيفية تثبيت وإعداد Laravel، بالإضافة إلى إعداد بعض المشاريع والمهام والموارد المضمّنة و عرضهم على المستخدم، كما أنشأنا وظائف الإنشاء والتعديل والحذف، وفي هذا الفصل سوف نختم الدرس عن طريق إضافة استمارة التحقق. استمارة التحقق من جانب الخادمعلى الرغم من أن استمارات الإنشاء والتعديل تعمل إلا أننا لم نقم بالتحقق من ما يتم إدخاله، وهذا ما سنقوم بعمله اليوم. هنالك طرق متعددة للتعامل مع استمارة التحقق، بعضها أفضل من الآخر، ولهذا التطبيق الصغير نقترح أن نستخدم وظيفة المتحكِّمات ()validate مع كائن Illuminate\Http\Request مثل هذا، في ملف app/Http/Controllers/ProjectsController.php/: // /app/Http/Controllers/ProjectsController.php use Illuminate\Http\Request; class ProjectsController extends Controller { protected $rules = [ 'name' => ['required', 'min:3'], 'slug' => ['required'], ]; /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return Response */ public function store(Request $request) { $this->validate($request, $this->rules); $input = Input::all(); Project::create( $input ); return Redirect::route('projects.index')->with('message', 'Project created'); } /** * Update the specified resource in storage. * * @param \App\Project $project * @param \Illuminate\Http\Request $request * @return Response */ public function update(Project $project, Request $request) { $this->validate($request, $this->rules); $input = array_except(Input::all(), '_method'); $project->update($input); return Redirect::route('projects.show', $project->slug)->with('message', 'Project updated.'); }وأما في ملف app/Http/Controllers/TasksController.php/: // /app/Http/Controllers/TasksController.php use Illuminate\Http\Request; class TasksController extends Controller { protected $rules = ['name' => ['required', 'min:3'], 'slug' => ['required'], 'description' => ['required'], ]; /** * Store a newly created resource in storage. * * @param \App\Project $project * @param \Illuminate\Http\Request $request * @return Response */ public function store(Project $project, Request $request) { $this->validate($request, $this->rules); $input = Input::all(); $input['project_id'] = $project->id; Task::create( $input ); return Redirect::route('projects.show', $project->slug)->with('Task created.'); } /** * Update the specified resource in storage. * * @param \App\Project $project * @param \App\Task $task * @param \Illuminate\Http\Request $request * @return Response */ public function update(Project $project, Task $task, Request $request) { $this->validate($request, $this->rules); $input = array_except(Input::all(), '_method'); $task->update($input); return Redirect::route('projects.tasks.show', [$project->slug, $task->slug])->with('message', 'Task updated.'); }سوف نحتاج أيضا إلى مكان لعرض أية أخطاء، لذلك قُم بفتح resources/views/app.blade.php/ وأضف السطور التالية أسفل ('yield('content@: <div class="content"> @if (Session::has('message')) <div class="flash alert-info"> <p>{{ Session::get('message') }}</p> </div> @endif @if ($errors->any()) <div class='flash alert-danger'> @foreach ( $errors->all() as $error ) <p>{{ $error }}</p> @endforeach </div> @endif @yield('content') </div>إذا أردت قائمة كاملة من قواعد التحقق، أنصحك بزيارة التوثيق الرسمي. من المفترض أن يعمل كل شيء الآن، وإذا كان عكس ذلك، فسوف تقوم ()this->validate$ بتوجيهك إلى الصفحة الحالية مع الأخطاء التي سوف تظهر على الصفحة. خاتمةلقد قمنا في هذه الدورة بتعلم الكثير من الأشياء حول Laravel، مثل كيفية تثبيت وإعداد Laravel 5 بالإضافة إلى بعض المفاهيم المتقدمة مثل الربط بين الطريق والنموذج route model binding والحماية من CSRF، وعلى الرغم من بساطة التطبيق الذي قمنا به إلا أنه بداية جيدة لكل من يريد احتراف Laravel. ترجمة -وبتصرّف- للمقال Creating a Basic ToDo Application in Laravel 5 – Part 4. حقوق الصورة البارزة: Designed by Freepik.