تطوير تطبيق مهام – مستوى متوسط

المقدمة

هذا الدليل السريع سيزودك بمقدمة متوسطة عن إطار عمل لارافيل ويضمن التالي:

  1. ترحيل لقاعدة البيانات Migrations
  2.  مخطط الكائن العلائقي (ORM (object-relational mapper
  3. المسارات Routes
  4. الوثوقية والتخويل Authentication / Authorization
  5. حقن التبعية Dependency Injection
  6. المصادقة Validation
  7. العروض views و قوالب Blade

هذه نقطة بداية رائعة في حال كنت تملك معرفة بأساسيات لارافيل  أو حتى أي من  أطر العمل الخاصة بالـphp عموماً.

لتمثيل إختيار أساسي لميزات لارافيل سوف نقوم ببناء تطبيق إدارة مهام to-do لنصل إلى هدف ما ينص عليه التطبيق وهو إدارة للمهام التي يجب أن تنجزها في حياتك، على عكس ما احتواه تطوير تطبيق المهام للمبتدئين سنضيف في هذا المقال إمكانية المستخدمين من إنشاء حسابهم الخاص وتوثيق دخولهم إلى التطبيق (تسجيل الدخول)، وطبعاً الناتج النهائي لهذا المقال التعليمي موجود على الغيت هب GitHub. 

التنصيب

بالطبع أول أمر تحتاجه هو تحميل نسخة جديدة من إطار العمل  لارافيل. من الممكن استخدام Homestead Virtual Machine  في حال رغبتك لمعرفة المزيد حول تنصيبها يمكنك مشاهدة هذا الفيديو. أو عبر مخدم PHP منصب بشكل محلي على حاسوبك باستخدام أحد التطبيقات الشهيرة او ما يعادلها WAMP MAMP XAMPP …الخ.

حالما تكون بيئة تطويرك جاهزة يمكنك تنصيب لارافيل عن طريق الكومبوسر Composer

في حال عدم معرفتك بالكومبسور أو أردت إسترجاع معلومة ما، الرجاء متابعة الفيديو الخاص به.

composer create-project laravel/laravel quickstart –prefer-dist

انت حر تماماً في حال أحببت أن تقرأ خطوة بخطوة هذا الدليل، او نسخ  المستودع Repository الذي يحوي على هذا التطبيق بإصداره النهائي وتنصيب كل ما يحتاجه من شفرات تبعية (يعتمد عليها) من الـ GitHub، عبر تشغيل الأوامر التالية:

git clone https://github.com/laravelme/quickstart-intermediate.git
cd quickstart
composer install
php artisan migrate

 

تحضير قاعدة البيانات

١- ترحيل قاعدة البيانات Migrations

أولاً دعونا نستخدم ترحيل ليقوم بتعرف جدولاً فيه جميع المهام. جزئية الترحيل في لارافيل تجعل من أمور مثل تعريف جدول وبنيته وتعديلاته من أسهل ما يكن بإستخدام  شفرات بي إتش بي php طليقة ومُعبرة، بدلاً من إخبار فريقك ــ أو زميلك المختص في هذا المجال أو المسؤول عنه ــ على أن يقوم بالتعديل بشكل يدوي على الأعمدة وقاعدة البيانات الموجودة على أجهزتهم، يمكنهم ( لفريقك أو لزميلك) ببساطة أن يقوم بتشغيل أمر المايغريشن Migration الذي قمت بإرساله لهم عن طريق تحديث المستودع الخاص بكم على أحد مواقع الـتي تدعم Git أو بإرساله لهم بطريقة ما.

جدول المستخدمين users

لطالما أننا نخطط لإضافة ميزة إنشاء حساب لزائر تطبيقنا سنحتاج إلى جدول يُخزن بداخله جميع مستخدمينا، ولحسن الحظ، لارافيل تأتي مسبقاً مع ترحيل لإنشاء جدول مستخدمين أساسي وبالتالي لن نحتاج إلى إنشاءه يدوياً. الترحيل الإفتراضي لجدول users موجود داخل المجلد

database/migrations

جدول المهام tasks

بخطوتنا التالية دعونا نبني ملف تهجير لبناء جدول المهام والذي سيقوم بإستيعاب جميع المهام. يمكن إستخدام  الحرفي (واجهة سطر الأمر) أرتيسان Artisan CLI لتوليد طيف واسع من الكلاسات Classes وسيوفر عليك الكثير من الكتابة في الوقت الذي تبني فيه مشروعاً باستخدام لارافيل ، في هذه الحالة دعونا نستخدم الأمر  make:migration لتوليد ملف جديد ترحيل  لقاعدة البيانات الخاص بجدول المهام  tasks .

php artisan make:migration create_tasks_table –create=tasks

سيتم وضع ملف الترحيل المولد في مشروعك داخل المجلد database/migrations كما من الممكن أنك قد لاحظت أن الأمر make:migration قد ولد لك عامود ترقيم تلقائي لـ ID بالإضافة إلى توليد الطوابع الزمنية timestamps  والتي هي عبارة عن عامودين crated_at و updated_at

دعونا نحرر هذا الملف لنضيف عاموداً آخر من نوع سلسلة نصية string ليحمل اسم المهام التي سنقوم بإنشائها، بالإضافة إلى عامود آخر user_id والذي سيعمل على ربط الجدولين users  و tasks ببعضها البعض، ويكون شكل الملف:

<?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');
    }
}

 

لتنفيذ ترحيلات تطبيقنا سنستخدم الأمر migrate الخاص ب الأداة أرتيسان Artisan ، في حال إستخدامك للهومستيد Homestead عليك تشغل الأمر التالي من داخل الهومستيد والسبب أن قاعدة البيانات ليست على جهازك المُستضيف وإنما على بيئة التطوير هومستيد والتي هي نظام متكامل يعمل وهمياً.

php artisan migrate

 

هذا الأمر سيقوم بإنشاء جميع جداول قاعدة البيانات، اذا قمت بتفحص جداول قاعدة البيانات بإستخدام أداة انت تختارها، يجب أن ترى جدولين tasks و users واللذان يحملان بداخلها الحقول التي عرفناها مسبقاً.

٢- موديلات إلكونت  Eloquent Models

العم بليغ Eloquent وهو إصطلاحاً مخطط الكائن العلائقي (ORM (object-relational mapper وهو الإفتراضي في لارافيل للربط مابين جداول قاعدة البيانات وتطبيقك ويكون على شكل كائن Object تستخدمه برمجياً كما أي كائن قمت ببناءه في تطبيقك وبالتالي يٌشعرك برفع الكلفة ويُبقيك مركزاً على البرمجة بحد ذاتها. عادةً كل كائن بليغ Eloquent يرتبط بشكل مباشر بجدول مقابل له في قاعدة البيانات.

موديل المستخدم User model

أولاً نحن نحتاج إلى مودل لجدول المستخدم users في قاعدة البيانات، ولكن اذا ألقيت نظرة على مجلد app في مشروعك سترى أنّ لارافيل مسبقاً تأتي مع مودل User وبالتالي لا نحتاج إلى توليد واحداً يدوياً.

موديل المهمة Task model

دعونا نعرف مودل Task والذي يدل على جدول المهام tasks في قاعدة البيانات الذي قمنا بإنشاءه قبل قليل، مرة أخرى يمكننا إستخدام أمر توليد مودل من الحرفي Artisan ،في هذه الحالة سنستخدم الأمر make:model

كما في السطر :

php artisan make:model Task

المودل Model المولد سيتم وضعه في مجلد app داخل مشروعك. في الحالة الإفتراضية الصف Class لهذا المودل فارغ. لا نحتاج عنوةً لإخبار إلكونت Eloquent أي جدول هو المقابل لك في قاعدة البيانات، لأنه سيقوم بإفتراض أنّ اسم الجدول في قاعدة البيانات هو الجمع لاسم المودل، وبالتالي مودل المهمة Task سيفترض إلكونت أن الجدول المقابل له هو tasks، وفي حالتنا نعم فهو كذلك 🙂

دعونا نضيف بعض الأمور إلى هذه المودل، أولاً سنجعل حالة اسم المهمة   name قابل للإسناد الهائل mass-assignable

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name'];
}

 

سنتعلم سويتاً بشكل أكبر كيف نستخدم مودلات إلكونت Eloquent Models حالما نضيف المسارات الخاصة بمشروعنا.

٣- علاقات إلكونت  Eloquent Relations

الآن بعد أن قمنا بتعريف الموديلات التي نحتاجها نحتاج إلى أن نربطهم ببعضهم البعض، مثلاً: مستخدمنا يمكنه تملك عدد من المهام، بينما المهمة فهي مسندة إلى مستخدم واحد. تعريف العلاقة سوف يمنحنا حرية الحركة مابين الموديلات كما في هذا المثال:

$user = App\User::find(1);

foreach ($user->tasks as $task) {
    echo $task->name;
}

 

علاقة المهام tasks

بدايةً دعونا نعرف علاقة المهام في موديل المستخدم User . علاقات إلكونت معرفة على شكل دوال داخل الموديلات. إلكونت يدعم عدة أنواع مختلفة من العاقات وبالتالي تأكد من المرور على الشرح الكامل في موقع إطار عمل اللارافيل. في هذه الحالة سنقوم بتعريف دالة tasks داخل موديل المستخدم User والذي بدوره يستدعي الدالة hasMany المزودة من قِبل إلكونت:

<?php

namespace App;

// Namespace Imports...

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    // Other Eloquent Properties...

    /**
     * Get all of the tasks for the user.
     */
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
}

 

علاقة المستخدم user

في خطوتنا التالية دعونا نقوم بتعريف علاقة المستخدم User مع موديل المهمة Task ، أيضاً سنقوم بتعريف هذه العلاقة على شكل دالة داخل مويل المهمة Task ، في هذه الحالة سنستخدم الدالة belongsTo المزودة من قبل إلكونت Eloquent لتعريف العلاقة:

<?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);
    }
}

 

رائع! وبما أنه تم تعريف العلاقات التي نحتاجها في تطبيقنا يمكننا البدء في بناء المتحكمات Controllers التي سنحتاجها

المسارات

في المقال السابق تطوير تطبيق إدارة المهام Todo قمنا بتعريف كل المنطق بإستخدامات دالات مجهولة ( دالة غير معرفة Anonymous Functions) داخل ملف المسارات Routes.php ، ولأسباب تتعلق بحجم التطبيق سنقوم بإستخدام متحكمات Controllers لتنظيم مساراتنا.

المتحكمات ستسمح لنا بإستلام طلب الـ HTTP و بمعالجة ما يحتاج من منطق على عدة ملفات وهذا لأغراض تنظيمية أفضل!

إظهار عرض view

سنقوم بإستخدام مسار واحد فيه دالة مجهولة/غير معرفة وهو مسار الجذر الخاص بتطبيقنا laravelme.app/ 

ومن هذا المسار سنقوم بتصيير قالب HTML يحتوي على صفحة الترحيب Welcome

في لارافيل جميع قوالب الـ HTML مخزنة في المجلد resources/views  ويمكننا إستخدام الدالة المساعدة view() ليعيد لنا أحد هذه القوالب من داخل مسارنا:

Route::get('/', function () {
    return view('welcome');
});

 

طبعاً نحتاج فعلياً إلى بناء هذا العرض وسنقوم بفعل هذا بعد قليل.

الوثوقية Authentication

تذكر نريد أيضاً أن ندع المستخدمين ينشؤون حساباتهم وأن يقومو بتسجيل الدخول إلى تطبيقنا! يجدر هنا بالذكر أن جزئية إنشاء طبقة وثوقية كاملة في تطبيق ويب هي مهمة مضجرة ، وبما أن مثل هذا الأمر ذو احتياج واسع، لارافيل تحاول أن تجعل من هذه العملية عملية غير متعبة إطلاقاً.

لنبدأ دعونا نلاحظ وجود متحكم وثوقية موجود مسبقاً مضمناً داخل تطبيق اللارافيل الخاص بك.

app/Http/Controllers/AuthController.php

هذا المتحكم يستخدم تريت (سمة) trait خاص والذي يحوي بدوره على كل ما قد تحتاج من من المنطق اللازم لإنشاء وتوثيق مستخدمين.

مسارات الوثوقية Authentication Routes

في هذه النقطة برأيكم ما هو الباقي علينا من إنجاز؟

مازلنا في الحاجة إلى إنشاء قوالب لستجيل دخول و إنشاء حسابات جديدة للمستخدمين بالإضافة إلى تعريف مسارات تصل روابط موقعنا مع المتحكمات المناسبة! دعونا نقوم بفعل هذا أولاً ، لنحرر ملف المسارت: 

app/Http/routes.php

// Authentication Routes...
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

// Registration Routes...
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

 

عروض التوثيق Authentication Views

جزئية التوثيق تحتم علينا إنشاء عرضين login.blade.php و register.blade.php الموجودين بدورهم في المجلد resources/views/auth وبالطبع موضوع شكل ومظهر هذه القوالب ليس بالمهم، ولكن على الأقل يجب أن يحتوا على حقول بسيطة.

قالب التسجيل register.blade.php يجب أن يحتوي على الحقول التالية: name ، email ،  password بالإضافة إلى password_confirmation

ويجب أن يكون مضبوط على أن يرسل طلب من نوع بوست POST إلى المسار

laravelme.com/auth/register 

أما بالنسبة لقالب تسجيل الدخول login.blade.php يجب أن يحتوي على الحقول:

email  ، password

وعلى أن يكون مضبوط على إرسال طلب من نوع بوست POST إلى laravelme.com/auth/login 


ملاحظة: في حال رغبتك بمشاهدة الشفرات الكاملة لا تنسى أنه يمكنك زيارة المستودع الخاص بهذا المقال على الغيت هاب GitHub

متحكم المهمة TaskController
لطالما نعلم أننا سنقوم بجلب المهام المسجلة مسبقاً دعونا ننشئ متحكم للمهام TaskController بإستخدام أرتيسان Artisan  والذي سيقوم بوضع ملف (متحكم) داخل المجلد الخاص به في المسار app/Http/Controllers

php artisan make:controller TaskController –plain

الآن وبعد أن تم توليد ملف متحكم المهمة TaskController  دعونا نخطو إلى الأمام ونقوم بإنشاء مزيد من المسارات لتطبيقنا داخل ملف المسارات لنشير بها إلى متحكمنا الوليد:

Route::get('/tasks', 'TaskController@index');
Route::post('/task', 'TaskController@store');
Route::delete('/task/{task}', 'TaskController@destroy');

 

توثيق جميع مسارات المهمة Task
لغرض هذا التطبيق نريد إضافة طلب وثوقية من قبل المستخدم لجميع المسارات المتعلقة بالمهمة Task ، بكلمات أخرى، يتحتم على المستخدم تسجيل الدخول لكي يكون بمقدوره إنشاء مهمة جديدة ، وبالتالي نحتاج إلى قصر الوصول إلى روابط المهمة task للمستخدمين المسجلين دخولهم مسبقاً، لارافيل تجعل من هذا الأمر قمة في السهولة عبر إستخدام الطبقات الوسيطة Middlewares.

لإضافة الحاجة إلى مستخدمين مسجلين لجميع الأحداث على متحكم ما، نستطيع إضافة إستدعاء إلى دالة middleware داخل الباني او الكونستركتور Contstructor لهذا المتحكم. جميع روابط الطبقات الوسيطة middleware route يتم تعريفها بشكل مسبق في ملف النواة الموجود في app/Http/Kernel.php  ، في هذه الحالة نريد إسناد مدلوير الوثوقية auth middleware  إلى جميع دالات هذا المتحكم:

 

<?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');
    }
}

 

بناء الواجهات والعروض

هذ التطبيق يحوي على واجهة واحدة فقط والتي تحتوي على نموذج لإضافة مهام جديدة بالإضافة إلى سرد المهام الموجودة بشكل مسبق في تطبيقنا ولمساعدة توضيح هذا الأمر بشكل رسومي في الأسفل سترى كيف هو شكل هذا العرض عندما نتهي منه مع إستخدامنا لمكتبة Bootstrap

app-main-view

تعريف الواجهة Layout

نسبياً جميع المواقع تتشارك نفس الواجهة أو البنية في جميع صفحاتها، مثلاً : هذا التطبيق يحتوي على شريط تنقل علوي  والذي بدوره سيكون موجوداً في كل صفحة ( في حال كان في تطبيقنا  أكثر من واحدة ). لارافيل تجعل من السهل جداً مشاركة الميزات المشتركة ـ  كالشريط العلوي ـ  في جميع الصفحات عبر إستخدام ميزة الواجهات من محركها الخاص بالقوالب Blade

كما ناقشنا سابقا فإن لارافيل تقوم بتخزين جميع العروض في المجلد resources/views

وبالتالي دعونا نعرف واجهة جديدة أو عرض جديد تحت المسار والمسمى resources/views/layouts/app.blade.php

الإمتداد blade.php    يُعلم إطار العمل على لزوم استخدام محرك القوالب Blade لتصيير العرض، بالطبع يمكنك استخدام قوالب PHPعادية مع لارافيل! ولكن بليد Balde يزود شفرات مختصرة لكتابة قوالب نظيفة.

العرض app.blade.php   يجب أن يكون محتواه كالتالي:

// resources/views/layouts/app.blade.php

<!DOCTYPE html><span class="hiddenSpellError" pre="" data-mce-bogus="1">Laravel</span> Quickstart - Intermediate

        

        @yield('content')
    

 

لاحظ الجزئية @yield    هذه تعليمة خاصة بمحرك Blade والتي تساعده على  تحديد جميع الصفحات التي ستقوم بوراثة هذا القالب الأب أي تمكن لهذه الصفحات الوريثة من حقن محتواهم الخاص، وبالتالي دعونا نقوم  بتعريف واجهة وريثة (ابن) تقوم بإستخدام القالب الأساسي (الأب) وبحقن محتواها الأساسي تماماً مكان هذه التعليمة المذكورة في الأعلى

تعريف عرض وريث ( ابن ) Child View

عظيم! واجهات تطبيقنا انتهت، لنقوم ببناء عرض يحتوي على النموذج الخاص بالإنشاء بالإضافة لجدول لسرد المهام وليكن:

resources/views/index.blade.php

والذي يطابق الدالة index في المتحكم  TaskController

سنقوم هنا بتخطي بعض الأمور المتعلقة بالبنية المسبقة التي تأتي مع  Bootstrap CSS   وبالمقابل سنقوم بالتركيز على الأمور الأهم ، وتذكر أنه يمكنك تحميل شفرة المصدر للتطبيق هذا بشكل كامل من المستودع الخاص بها على الـ GitHub.

// resources/views/tasks/index.blade.php

@extends('layouts.app')

@section('content')

    <!-- Bootstrap Boilerplate... -->

    <div class="panel-body">
        <!-- Display Validation Errors -->
        @include('common.errors')

        <!-- New Task Form -->
        <form action="/task" method="POST" class="form-horizontal">
            {{ csrf_field() }}

            

<input type=”text” name=”name” id=”task-name” class=”form-control”>

            
        
    

    
@endsection

 

بعض الشروحات

قبل أن نكمل في تطوير تطبيقنا دعونا نتحدث قليلاً عن القالب المسرود في الأعلى، أولاً الأمر @extends   يقوم بإطلاع لارافيل على أننا نستخدم الواجهة المعرفة في

resources/views/layouts/app.blade.php

جميع المحتوى المحصور مابين

@section(‘content’)  و @endsection 

سيتم حقنه (إستبداله) في السطر

@yield(‘contents’)

الموجود في الواجهة

app.blade.php

الآن قد عرفنا عرض و واجهة أساسية لتطبيقنا دعونا نستمر بإعادة  هذه الواجهة  عند طلب الدالة index في متحكم المهمة TaskController

/**
 * Display a list of all of the user's task.
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
    return view('tasks.index');
}

 

نحن الأن جاهزون لإضافة إرسال طلب من نوع بوست POST على الرابط

laravelme.app/task

ليقوم بإستلام الطلبات القادمة  من نموذج اللإضافة والتي هي حقول الإدخال الموجودة بنموذج الإضافة Inputs وبالتالي إضافة مهمة جديدة على قاعدة البيانات

ملاحظة: السطر

@include('common.errors')

سيقوم بإضافة محتوى القالب الموجود في


resources/views/common/errors.blade.php


لم نقم بعد بتعريف محتوى هذا القالب! ولكن سنقوم بالخطوات القليلة القادمة 🙂

إضافة مهام

المصادقة  Validation
الآن نحن نملك نموذج في عرضنا ونحتاج إلى إضافة شفرات إلى الدالة store الموجودة داخل متحكم المهمة TaskController 
لتقوم بمصادقة الطلب القادم من النموذج وإضافة مهمة جديدة، ولكن أولاً دعونا نقوم بمصادقة محتوى الدخل.
لهذا النموذج سنقوم بإنشاء حقل name على أن يكون مطلوباً وعلى أن يحوي عدد محارف أقل من 255 و في حال  فشل هذه المصادقة سنقوم بإعادة التوجيه إلى صفحة المهام

laravelme.com/tasks

وسنقوم بإسناد القيم القديمة للدخل مع الخطأ داخل محتول الجلسة Session:

/**
 * Create a new task.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'required|max:255',
    ]);

    // Create The Task...
}

 

في حال متابعتك للسلسة السابقة للمبتدئين ستلاحظ أنّ شفرات المصادقة هذه مختلفة بشكل بسيط! لطالما أنّنا نكتب شفراتنا داخل متحكم يمكننا الإستفادة من التسهيلات ValidatesRequests وهي عبارة عن سمة (تريت trait في حال عدم معرفتك بها قد بزيارة هذا الرابط أو اترك لي تعليقاً، ساقوم بالرد مباشرةً)  والذي بدوره مُدرج مسبقاً كجزء من متحكمات لارافيل، هذه السمة تعرض دالة بسيطة validate والتي تأخذ الطلب و مصفوفة من قواعد المصادقة على الدخل القادم في الطلب كمعاملات.

لن نحتاج إلى التحديد يدوياً في ما إذا فشلت المصادقة أو القيام بإعادة توجيه يدوياً. اذا فشلت المصادقة للقواعد المعطاه، سيتم إعادة توجيه المستخدم للخلف من حيث أتى بشكل أوتوماتيكي وسيتم سرد الأخطاء إلى المتغير المعرف بشكل عام سيشن session ، جميل أليس كذلك؟!

متحول الأخطاء Errors

الآن دعونا نتذكر سويتاً أننا قمنا بإستخدام التعليمة

@include(‘common.errors’)

في العرض الذي أنشأناه ليقوم بتصيير الأخطاء الخاصة بالمُصادقة على النموذج. التعليمة common.errors ستمكننا بشكل مبسط من عرض الاخطاء جميعها بنفس الشكل في جميع الصفحات. دعونا نعرف محتوى هذا العرض الآن:

// resources/views/common/errors.blade.php

@if (count($errors) > 0)
    <!-- Form Error List -->
    <div class="alert alert-danger">
        <strong>Whoops! Something went wrong!</strong>

        <br><br>

        <ul>
            @foreach ($errors->all() as $error)
  • {{ $error }}
@endforeach @endif

 

لاحظ أنه من الممكن الوصول إلى  المتحول $errors  في جميع عروض Views تطبيقنا ببساطة هو عبارة عن مشتق Instance لـ ViewErrorBag في حال عدم وجود أية أخطاء خلال عملية المصادقة

إضافة مهام

الآن وبما أنه تمت  مصادقة الدخل Input Validation ، لنقوم الآن بشكل حقيقي بإنشاء مهمة جديدة عبر إكمال محتوى ملف المسارات  Routes ، عندما يتم إكمال عملية إنشاء مهمة جديدة  سنقوم بإعاة التوجيه إلى صفحة المهام

laravelme.app/tasks

لإنشاء المهمة الجديدة ( تخزينها في قاعدة البيانات ) سنقوم بالإستفادة من قوة علاقات إلكونت Eloquent  . أغلب علاقات لارافيل تعرض الدالة save والتي تستقبل بدورها إنستنس Instance ـ لندعوه مشتق ـ  من مودل آخر على علاقة بمودل المهمة، في حالتنا مودل المستخدمين، دالة التخزين هذه ستقوم بإسناد الترقيم التلقائي للمستخدم user_id الذي يقوم بإنشاء المهمة بشكل تلقائي إلى الموديل المطبقة عليه، ويمكننا الوصول للمستخدم عبر الطلب

 $request->user()

ليكون الناتج كالتالي:

/**
 * Create a new task.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'required|max:255',
    ]);

    $request->user()->tasks()->create([
        'name' => $request->name,
    ]);

    return redirect('/tasks');
}

 

عظيم  يمكننا الآن إنشاء مهام جديدة بنجاح، الخطوة القادمة لتكن بإكمال إنشائنا لهذا العرض، عن طريق سرد الأعمال التي تم إنشاءها من قبل.

عرض المهام الموجودة

أولاً نحتاج إلى تحرير الدالة TaskController@index لتمرر لنا جميع المهام الموجودة إلى العرض View ، بكل سهولة الدالة view()  تأخذ قيمة إضافية غير الاولى ( اسم ملف العرض ذو الإمتداد Blade) والتي هي عبارة عن مصفوفة من البيانات التي ستكون متاحة للإستخدام في العرض View بحيث كل مفتاح في هذه المصفوفة سيصبح متحول في العرض View . وبالتالي يمكننا كتابة الشفرات كما يلي:

/**
 * Display a list of all of the user's task.
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
    $tasks = Task::where('user_id', $request->user()->id)->get();

    return view('tasks.index', [
        'tasks' => $tasks,
    ]);
}

ومع ذلك دعونا نستكشف بعض إمكانيات الحقن التَبَعيّ dependency injection لـلارافيل، وذلك لحقن TaskRepository إلى TaskController والذي سنستخدمه لاحقاً في جميع عمليات الوصول إلى بياناتنا

 

حقن التبعية Dependency Injection

لارافيل في وعاء الخدمة تقتن أهم وأقوى ميزة من ميزاتها كإطار عمل، تأكد من قراءة المستندات الخاصة على موقعها الرسمي. سأقوم بتغطيته في الفيديوهات والقراءات القادمة إن شاء الله 🙂

إنشاء المستودع Repository

من المهم التوضيح من البداية أن المستودع هنا ليس هو نفسه مستودع الـGit الذي يحوي على ملفات مشروعك، سنقوم هنا باستخدام مفهموم برمجي وحل مشهور رائع للمشاريع الضخمة يدعى المستودعات، وكما ذكرنا في وقت سابق نريد تعريف مستودع مهام TaskRepository والذي سيحوي بدوره كل المنطق للوصول إلى البيانات الخاصة بالمودل Task، هذا سيكون مفيد جداً خاصةً إذا توسع تطبيقنا واحتجنا إلى مشاركة استعلامات Quiries عبر تطبيقنا.

بالتالي دعونا ننشئ مجلد مستودعات داخل مجلد التطبيق app/Repositories ونضيف بداخله كلاس TaskRepository، وتذكر أنّ جميع المجلدات داخل المجلد app يتم تحميلهم تلقائياً بإستخدام المعيار PSR-4 للتحميل التلقائي وبالتالي انت حر بإنشاء ما شئت من المجلدات الإضافية على حسب حاجتك إليها:

<?php

namespace App\Repositories;

use App\User;
use App\Task;

class TaskRepository
{
    /**
     * Get all of the tasks for a given user.
     *
     * @param  User  $user
     * @return Collection
     */
    public function forUser(User $user)
    {
        return Task::where('user_id', $user->id)
                    ->orderBy('created_at', 'asc')
                    ->get();
    }
}

 

 حقن المستودع Injecting The Repository

حالما يتم تعريف مستودعنا نستطيع ببساطة كتابة تلميحه “type-hint” في الباني Constructor الخاص بـ TaskController ومن ثم نستخدمه في المسار index ، منذ أن لارافيل تستخدم وعاء الخدمة لفصل جميع المتحكمات، تبعياتُنا جميعها سيتم حقنها بشكل أوتوماتيكي مع أي إنستنس instance يتم إنشاءه من هذا المتحكم:

<?php

namespace App\Http\Controllers;

use App\Task;
use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Repositories\TaskRepository;

class TaskController extends Controller
{
    /**
     * The task repository instance.
     *
     * @var TaskRepository
     */
    protected $tasks;

    /**
     * Create a new controller instance.
     *
     * @param  TaskRepository  $tasks
     * @return void
     */
    public function __construct(TaskRepository $tasks)
    {
        $this->middleware('auth');

        $this->tasks = $tasks;
    }

    /**
     * Display a list of all of the user's task.
     *
     * @param  Request  $request
     * @return Response
     */
    public function index(Request $request)
    {
        return view('tasks.index', [
            'tasks' => $this->tasks->forUser($request->user()),
        ]);
    }
}

 

عرض المهمات

حالما يتم تمرير المعطيات نستطيع أن نقوم بالدوران على المهام وبالتالي عرضها في جدول  داخل العرض tasks/index.blade.php ، التعليمة @foreach تمكننا من بناء حلقة موجزة والتي بدورها ستتحول بعد تنفيذ تطبيقنا إلى شفرات بي اتش بي عادية سريعة جداً تنفذ كما تشتعل النار بالهشيم!

 

@extends('layouts.app')

@section('content')
    <!-- Create Task Form... -->

    <!-- Current Tasks -->
    @if (count($tasks) > 0)
        <div class="panel panel-default">
            <div class="panel-heading">
                Current Tasks
            </div>

            <div class="panel-body">
                <table class="table table-striped task-table">

                    <!-- Table Headings -->
                    <thead>
                        <th>Task</th>
                        <th>&nbsp;
                    

                    
                    
                        @foreach ($tasks as $task)
                            
                                
{{ $task->name }}
                                

                                
                                    
                                
                            
                        @endforeach
                    
                
            
        
    @endif
@endsection

 

تطبيقنا شارف على الإنتهاء، ولكن لا يوجد عندنا طريقة لحذف المهام الموجودة مسبقاً عندما ننتهي منهم، لنضف سويةً هذه الميزة.

 

حذف المهام

إضافة زر الحذف

لقد قدمنا مسبقاً بترك مهمة لتنفذ لاحقاً داخل شفراتنا كسطراً في المكان الذي يجب أن نقوم بإنشاء زر الحذف فيه وكانت على شكر سطر  عندما كتبنا “To-Do” وبالتالي دعونا نضيف هذا الزر إلى كل سطر من سطور جدول المهام الموجود في العرض tasks/index.blade.php سنقوم بإنشاء نموذج يحوي على زر وحيد لكل مهمة في السرد هذا، وعندما يتم النقر على هذا الزر طلب من نوع حذف سيتم إرساله إلى الرابط laravelme.app/task   والذي سيقوم بتحريض دالة الحذف على العمل TaskController@destroy

<tr>
    <!-- Task Name -->
    <td class="table-text">
        <div>{{ $task->name }}</div>
    </td>

    <!-- Delete Button -->
    <td>
        <form action="/task/{{ $task->id }}" method="POST">
            {{ csrf_field() }}
            {{ method_field('DELETE') }}

            <button>Delete Task</button>
        </form>
    </td>
</tr>

 

ملاحظة حول التحايل في المسارات

لاحظ أن الطريقة المختارة في نموذج زر الحذف هي Post ، علماً اننا نقوم بالرد على الطلب بإستخدام رابط Route::delete 

وهذا يعود إلى أنّ نماذج HTML تسمح فقط للنوعين GET و  POST كمتحولات ترسل عبر بروتوكول HTTP وبالتالي احتجنا لطريقة لنحتال ونستخدم نوع حذف لطلب نموذج الحذف.

نستطيع الإحتيال على طلب الحذف عن طريق عرض ناتج الدالة

 method_field(‘DELETE')

داخل نموذجنا، هذه الدالة تقوم بتوليد دخل خفي للنموذج Hidden input والذي تقوم لارافيل بالتعرف عليه والذي سيستخدم كتجاوز على الطلب الأساسي والذي هو HTTP POST.
الناتج المولد يكون على هذا الشكل ( في حال أحببت عدم استخدام الدالة في الأعلى ، استخدم الناتج التالي )

<input type="hidden" name="_method" value=“DELETE">

ربط المودل بالمسار

والآن شارفنا على إنهاء تعريف دالة الحذف destroy ، على متحكم المهام TaskController ولكن أولاً دعونا نزور تعريفنا لهذا الرابط في ملف المسارات routes.php

Route::delete('/task/{task}', 'TaskController@destroy');

من دون إضافة أي شفرات إضافية لارافيل ستقوم بحقن ترقيم تلقائي للمهمة إلى دالة الحذف TaskController@destroy كالشكل التالي:

/**
 * Destroy the given task.
 *
 * @param  Request  $request
 * @param  string  $taskId
 * @return Response
 */
public function destroy(Request $request, $taskId)
{
    //
}

ومع ذلك فإذن أول شي نحتاج إلى فعله في هذه الدالة هو جلب نسخة أي ( إنستنس instance ) للمهمة من قاعدة البيانات عبر الرقم التسلسلي المُعطى، وبالتالي ألن يكون جميلاً في حال استطاعت لارافيل أن تقوم بحقن نسخة من المهمة تتماشى مع الرقم التسلسلي المعطى في المرتبة الأولى؟ دعونا نجعل لهذه السطور وجوداً!

في الدالة الخاصة باللإقلاع boot  للملف app/Providers/RouteServiceProvider.php دعونا نضيف السطر التالي:

$router->model('task', 'App\Task');

هذا السطر الصغير من الكود سيقوم بالإشارة للارافيل على أنها ستجلب نسخة من موديل المهمة المقابل للترقيم التلقائي ID في كل مرة ترى فيها لارافيل {task}  داخل تعريفات المسارات الموجودة في ملف المسارات routes.php

والآن يمكننا تعريف دالة حذف مهمة على الشكل التالي:

/**
 * Destroy the given task.
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    //
}

 

التخويل Authorization

الآن نملك إنستنس من المهمة محقون إلى دالة الحذف، ومع ذلك لا نملك ضماناً أن المستخدم المُسجل للدخول فعلياً يملك المهمة التي سيتم حذفها، على سبيل المثال طلب خبيث طبخ محاولة حذف لمهمة تابعة لمستخدم آخر عبر تمرير رقم ID عشوائي إلى الرابط /tasks/{task} . وهكذا فنحن نحتاج إلى ميزة التخويل في لارافيل لتقوم بالتأكيد على أن المستخدم الذي يحاول أن يقوم بعملية الحذف هو مستخدم مالك لهذه المهمة بشكل فعلي.

إنشاء سياسة Creating A Policy

لارافيل تستخدم السياسات policies لتنظيم التخويلات على شكل منطق مبسط و كلاسات صغيرة. عادة كل سياسة تقابل موديل وبالتالي دعونا ننشئ TaskPolicy بإستخدام أوامر الحرفي أرتيسنا والتي ستقوم بوضع الشفرات المُولدة في app/Policies/TaskPolicy.php

php artisan make:policy TaskPolicy

في خطوتنا التالية دعونا نضيف دالة حذف destroy إلى السياسة. هذه الدالة ستستقبل نسخة من المستخدم ونسخة من المهمة. المهمة ببساطة ستقوم بفحص في ما إذا كان رقم ID  المستخدم مطابقاً للحقل user_id الموجود في نسخة المهمة، في الحقيقة، جميع دالات السياسة يجب عليهم إرجاع true أو false

HandlesAuthorization;

class TaskPolicy
{
    use HandlesAuthorization;

    /**
     * Determine if the given user can delete the given task.
     *
     * @param  User  $user
     * @param  Task  $task
     * @return bool
     */
    public function destroy(User $user, Task $task)
    {
        return $user->id === $task->user_id;
    }
}

وأخيراً نحن نحتاج إلى مشاركة موديل المهمة Task مع سياسة المهمة TaskPolicy. يمكننا فعل ذلك عبر إضافة سطر إلى خاصية السياسات داخل الملف app/Providers/AuthServiceProvider.php . هذا سيعلم لارافيل أي سياسة يجب إستخدامها كلما سنقوم بتجربة تخويل حدث على نسخة من المهمة:

/**
 * The policy mappings for the application.
 *
 * @var array
 */
protected $policies = [
    Task::class => TaskPolicy::class,
];

تخويل حدث Authorizing The Action

الآن وبعد كتابة سياستنا دعونا نستخدم الدالة حذف، جميع متحكمات لارافيل مؤهلة لإستدعاء الدالة تخويل authorize والتي هي مكشوفة لها عبر استخدام التريت triat المسمى AuthorizesRequest وبالتالي يكون استخدام السياسة كما في الأسفل:

/**
 * Destroy the given task.
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    // Delete The Task...
}

لننظر في هذا الإستدعاء لدالة التخويلauthorize لبره من الوقت، المعامل (البراميتر) الأول المُعطى لها هو اسم دالة المكتوب داخل السياسة التي نريد استدعاءها. المعامل الثاني هو نسخة من المهمة ـ هنا توضع إهتمامنا ـ تذكر لقد قمنا مؤخراً بإخبار لارافيل أن موديل المهمة يقابله TaskPolicy وهكذا إيطار العمل يعلم أي دالة حذف destroy يجب عليه أن ينفذ.

المستخدم الحالي سيتم إرساله بشكل أوتوماتيكي إلى دالة السياسية، وبالتالي لن نحتاج إلى إرساله بشكل يدوي هنا.

في حال كان الحدث هو مُخول فإذن شفراتنا ستكمل في التنفيذ بشكل طبيعي، ولكن في حال كان الحدث غير مخول ( أي الدالة حذف داخل السياسة قد قامت بإرجاع قيمة false) إستثناء (إكسبشن exception )  ٤٠٣ سيتم رميه وصفحة خطأ سوف تظهر للمستخدم.

 

ملاحظة: يوجد العديد من الطرق للتفاعل مع الناتج الصادر عن خدمات التخويل في لارافيل. تأكد من إكمال محتوى مستندات التخويل.

 

حذف المهمة

وأخيراً! دعونا ننهي إضافة المنطق إلى دالة الحذف الموجودة في متحكم المهام، لنقم فعلياً بالحذف المهمة المعطا إلينا، يمكننا إستخدام دالة الحذف الخاصة بإيلكونت لحذف نسخة الموديل المعطاة من قاعدة البيانات. و حالما يتم حذف السجل سنقوم بإعادة التوجيه المستخدم للوراء إلى رابط المهام laravelme.com/tasks

/**
 * Destroy the given task.
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $request, Task $task)
{
    $this->authorize('destroy', $task);

    $task->delete();

    return redirect('/tasks');
}

 

لقد انتهينا، هنيئاً لكم على إكمال هذه القراءة وأشكركم من كل قلبي

هذا التعليم جزء من مستندات لارافيل، تمت ترجمته بعد موافقة الأخ تايلور آوت ويل