دليل مطوّري PHP للبدء في بناء تطبيقات Laravel - الجزء الأوّل


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

يقدّم دليل الانطلاق السريع هذا، المكوّن من جزأين، مقدّمة لإطار العمل 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');
    }
}

إنشاء القوالب والعروض

يتضمّن الجزء الأساسي من التطبيق الذي نعملُ عليه عرضا واحدا توجد به استمارة لإضافة مهامّ جديدة، كما تظهر لائحة بالمهامّ الموجودة سلفا. في ما يلي لقطة من التطبيق المكتمل:

01_application_layout.png

تعريف مخطّط العرض

تستخدم غالبية تطبيقات الوِب نفس المخطّط في كلّ الصفحات. بالنسبة للتطبيق الذي نعمل عليه فهو يحتوي على شريط تصفّح علوي يوجد في جميع الصفحات (في حال وجود أكثر من صفحة). تسهّل قوالب 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.

laravel-intro.png





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن