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

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


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

هيأنا في الجزأ الأوّل من هذا الدرس قاعدة البيانات والتهجيرات، وأعددنا أساس التوجيه والمخطّط العام للقوالب والعروض. نكمل في هذا الجزأ الثاني إعداد بقية العناصر اللّازمة لبناء تطبيق إدارة مهام قاعدي.

إضافة المهامّ

التحقّق

لدينا الاآن استمارة في العرض resources/views/tasks/index.blade.php؛ سنحتاج لإضافة شفرة إلى الدالة TaskController@store من أجل التحقّق من المُدخلات القادمة من الاستمارة ثم إنشاء مهمة جديدة. نبدأ بالتحقّق من المدخلات.

سنضع شرطا على حقل الاسم في استمارة إضافة مهمّة جديدة؛ يتمثّل الشرط في وجوب إدخال اسم للمهمة، على ألا يتجاوز طوله 255 محرفا Characters. إن لم يتحقّق الشرط نعيد المستخدم إلى الرابط tasks/ مع إضاءة المُدخلات القديمة وإظهار أخطاء:

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

    // شفرة إنشاء المهمة 
}

تتضمّن المتحكّمات في Laravel مبدئيا السمة ValidatesRequests التي يمكن عن طريقها التحقّق من موافقة المُدخلات لشروط تُحدَّد في مصفوفة من القواعد. تأخذ دالة التحقّق validate الطّلب request$ الذي نريد التحقّق منه ومصفوفة القواعد التي نريد تطبيقها. تشير الشفرة أعلاه إلى أن الحقل name مطلوب required ويجب ألا يتجاوز طوله 255 (الشّرط max).

لن نحتاج لتحديد ما الذي سيحصُل عند إخفاق التحقّق من الطلب (مثلا عند كتابة اسم يتجاوز طوله 255 محرفا). يُوجَّه المستخدَم تلقائيا عند إخفاق التحقّق إلى المسار الذي أتى منه وستُعرَض رسائل الخطأ تلقائيا.

المتغيّر errors$

تذكّر أننا استخدمنا التعليمة ('include('common.errors@ داخل العرض tasks.index لتقديم أخطاء التحقّق عبر العرض common.errors. يمكّننا العرض common.errors من إظهار أخطاء التحقّق بسهولة وحسب نفس التنسيق في كامل التطبيق. نعرّف في ما يلي العرض resources/views/common/errors.blade.php:

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

        <br><br>

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

ملحوظة: يتوفّر المتغيّر errors$ في كلّ عرض في Laravel. يحوي المتغيّر كائنا فارغا من الصّنف ViewErrorBag إن لم توجد أخطاء.

إنشاء المهمّة

ضبطنا في الفقرة السابقة آلية التحقّق؛ يمكننا الآن إكمال الدالة store في المتحكم TaskController من أجل إنشاء مهمّة جديدة. نوجِّه المستخدم بعد إنشاء المهمّة إلى الرابط tasks/. سنلجأ إلى الإمكانيّات التي توفّرها علاقات Eloquent لإنشاء المهمّة.

تتوفّر أغلب علاقات Eloquent على دالة باسم create تأخذ مصفوفة من الخاصيّات وتضبط تلقائيا قيمة المفتاح الخارجي Foreign key بالاعتماد على النماذج التي تربط بينها العلاقة، ثم تنشئ سطرا جديدا في جدول البيانات لتخزين خاصيّات الكائن.

تستخدم الشفرة التالية متغيّرالطلب request$ للحصول على المستخدم الذي طلب إنشاء المهمة ()request->user$ ثم باستخدام الدالة create في العلاقة tasks (عرّفناها سابقا في النموذج User) نعطي القيمة request->name$ (أي الاسم الّذي أدخله المستخدم في الاستمارة) للخاصيّة name في النموذج Task. ستتولّى العلاقة ()tasks تحديد قيمة معرّف المستخدم صاحب المهمة بالاعتماد على المعلومات الواردة في الطلب عن المستخدم.

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

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

    return redirect('/tasks');
}

لاحظ كيف أن تعريف العلاقات أتاح لنا إمكانيّة فعل الكثير من الأمور بسطر برمجي واحد تقريبا.

عرض المهامّ المخزّنة

سنحتاج للتعديل على الدالة index في المتحكّم TaskController من أجل تمرير المهامّ المخزّنة إلى العرض. تقبل الدالة view إضافة إلى اسم العرض، معطى ثانيا يحوي مصفوفة من البيانات المتوفّرة للعرض. يمكن معالجة كلّ عنصر من المصفوفة بعد تمريرها إلى العرض باستخدام متغيّر بنفس الاسم، مثلا:

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

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

لكننا سنختار طريق حقن الاعتمادات التي يوفرها Laravel.

حقن الاعتمادات

يستخدم Laravel ، كما أشرنا لذلك في درس إنشاء مزود خدمة في Laravel حاويةَ خدمة Service container لإدارة الاعتمادات بين الأصناف: ماهي الأصناف التي يعتمد عليها الصّنف الجديد للعمل؟ كيف يمكنه الحصول على كائن من هذه الأصناف؟ أين يوجد الصّنف وهل سبق لإطار العمل تحميلُه إلى الذاكرة؟

إنشاء مستودع في Laravel

تمكّن المستودعات Repositories من تخليص المتحكّم من ضرورة معرفة مصدر البيانات؛ إذ تجرّد المصدر الذي تأتي منه (قاعدة بيانات، ملفّ، واجهة تطبيقات API). تساعد المستودعات - بهذه الحيلة - في زيادة قابليّة التطبيق للصيانة والاختبار.

نودّ أن نعرّف مستودعا باسم TaskRepository للوصول إلى بيانات نموذج المهمة Task. تفيد هذه الطريقة كثيرا عند تزايد حجم المشروع والحاجة لتشارك استعلامات Eloquent عبر التطبيق. سننشئ مجلدا باسم Repositories في المجلّد app. تذكّر أن Laravel يحمّل تلقائيا جميع مجلدات التطبيق؛ لذا يمكنك إنشاء المجلدات حسب الحاجة.ننشئ داخل مجلد المستودعات الصنف TaskRepository:

<?php

namespace App\Repositories;

use App\User;

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

حقن المستودع

يمكننا الآن تمرير المستودع، بعد استدعائه، إلى مشيّد المتحكم TaskController ثم استخدامه في الدالة index. تصبح شفرة المتحكّم TaskController على النحو التالي:

<?php

namespace App\Http\Controllers;

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


// استدعاء المستودع
use App\Repositories\TaskRepository;


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

    /**
    * Create a new controller instance.
    *
    * @param  TaskRepository  $tasks
    * @return void
    */
    // التمرير إلى المتحكّم
    public function __construct(TaskRepository $tasks)
    {
        $this->middleware('auth');

        $this->tasks = $tasks;
    }

    /**
    * Display a list of all of the user's task.
    *
    * @param  Request  $request
    * @return Response
    */
    public function index(Request $request)
    {
    // الاستخدام في الدالة
        return view('tasks.index', [
            'tasks' => $this->tasks->forUser($request->user()),
        ]);
    }

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

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

        return redirect('/tasks');
    }
}

إظهار المهامّ

يمكننا، بعد تمرير البيانات إلى العرض tasks/index.blade.php من المتحكّم، إظهار المهامّ في جدول. تُستخدَم التعليمة foreach@ في قوالب Blade لكتابة حلقات تكرارية يُحوّلها Laravel شفرة PHP.

ملحوظة: سبق لنا في الجزأ الأول من الدرس تعريف استمارة إنشاء مهمة جديدة. توضع الشفرة أدناه مكان التعليق الأخير في قالب tasks/index.blade.php الذي أنشأناه سابقا.

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

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

                    <!-- عناوين الجدول -->
                    <thead>
                        <th>Task</th>
                        <th> </th>
                    </thead>

                    <!-- المهام الموجودة -->
                    <tbody>
                        @foreach ($tasks as $task)
                            <tr>
                                <!-- اسم المهمة -->
                                <td class="table-text">
                                    <div>{{ $task->name }}</div>
                                </td>

                                <td>
                                    <!-- سنضع هنا الشفرة الخاصة بحذف مهمة -->
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif
    </div>
   </div>

شارف التطبيق على النهاية؛ ولكن تنقصنا طريقة تمكّن المستخدم من حذف مهام أضافها سابقا.

حذف المهام

زرّ حذف مهمة

تركنا في العرض tasks/index.blade.php تعليقا للدلالة على المكان المفترض لزرّ حذف مهمة. سنضيف الآن، لكلّ سطر في جدول المهامّ، زرّا يمكّن من حذف المهمة الموجودة في السطر. يُرسَل، عند النقر على زرّ الحذف، طلب بإجراء DELETE إلى المسار task/، يستقبل المسار الطلب ويحيله إلى الدالة destroy في المتحكّم TaskController. نعدّل العرض tasks/index.blade.php بتعديل شفرة أسطُر الجدول لتصبح على النحو التالي:

<tr>
    <!-- اسم المهمة -->
    <td class="table-text">
        <div>{{ $task->name }}</div>
    </td>

    <!-- زرّ حذف المهمة -->
    <td>
        <form action="{{ url('task/'.$task->id) }}" method="POST">
            {{ csrf_field() }}
            {{ method_field('DELETE') }}

            <button type="submit" id="delete-task-{{ $task->id }}" class="btn btn-danger">
                <i class="fa fa-btn fa-trash"></i>Delete
            </button>
        </form>
    </td>
</tr>

لاحظ أن الاستمارة في خلية الجدول حيث يوجد زرّ الحذف تستخدم الإجراء POST بينما نجيب على الطلب بمسار يستخدم الإجراء DELETE. لا تسمح الاستمارات في HTML باستخدام سوى الإجراءيْن POST وGET، أي أننا يجب أن نجد طريقة لإرسال طلب بإجراء DELETE. يكمُن الحل في تنفيذ الدالة ('method_field('DELETE داخل الاستمارة. تولّد هذه الدالة حقل إدخال input مخفيّا يتعرّف عليه Laravel ويستخدمه في إلغاء الإجراء المستخدَم في طلب HTTP. يبدو الحقل المولَّد كالتالي:

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

ربط النموذج والمسار

نحن الآن جاهزون لتعريف الدالة destroy في المتحكّم TaskController:

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

سنلقي، قبل أن نكمل تعريف الدالة destroy، نظرة على المسار الخاصّ بحذف مهمة:

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

نظرا لكون المتغيّر {task} في المسار يوافق المتغيّر task$ في دالة المتحكم فإن Laravel سيربط تلقائيا بين الاثنين ويحقن كائنا مناسبا من النموذج Task في المسار؛ أي أن الكائن سيكون متاحا للمتحكّم لتنفيذ العمليات عليه.

التصريح

لدى المتحكّم الآن نسخة من نموذج المهمة Task لاستخدامه في الدالة destroy، إلا أننا لسنا متأكدين من أن المستخدم هو فعلا صاحب المهمة التي طلب حذفها. يمكن، على سبيل المثال، تمرير معرّف عشوائي إلى الرابط {tasks/{task، وستحذف الدالة المهمة ذات المعرّف {task}، إن وُجدت. نستخدم قدرات إدارة التصريحات Authorization في Laravel لحلّ هذه المشكلة، والتأكد من أن المستخدَم المُسجَّل هو فعلا صاحب المهمة التي يطلُب حذفها.

إنشاء سياسة تصريح

يستخدم Laravel سياسات Policies لتنظيم التصريحات في أصناف صغيرة وسهلة. تتعلّق كلّ سياسة - عادة - بنموذج. نستخدم artisan لإنشاء سياسة تصريحات للنموذج Task كالتالي:

 
php artisan make:policy TaskPolicy

سينشئ الأمر ملفا باسم TaskPolicy.php على المسار /app/Policies.

نضيف دالة باسم destroy إلى سياسة التصريح TaskPolicy. تتلقى هذه الدالة كائنا من صنف User وآخر من صنف Task، ثمّ تتحقّق من أن معرّف المستخدم يطابق الحقل user_id في المهمة. تُرجع الدالة destroy في الصّنف TaskPolicy قيمة منطقية (falseأو true)، كما هي الحال في جميع دوال سياسات التصريح:

<?php

namespace App\Policies;

use App\User;
use App\Task;
use Illuminate\Auth\Access\HandlesAuthorization;

class TaskPolicy
{
    use HandlesAuthorization;

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

نحتاج الآن لإخبار Laravel بأن سياسة التصريح TaskPolicy تنطبق على النموذج Task. يتمّ هذا الأمر عبر إضافة عنصر جديد إلى خاصيّة policies$ في مزوّد خدمات الاستيثاق app/Providers/AuthServiceProvider.php. سيعرف Laravel بهذه الطريقة سياسة التصريح التي يجب عليه استخدامها للسماح بإجراء مّا (أو رفضه) على النموذج Task:

/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Task' => 'App\Policies\TaskPolicy',
];

السماح بحذف المهمة

سياسة التصريح جاهزة، ويمكننا بالتالي استخدامها في المتحكّم TaskController. يمكن لجميع المتحكمات في Laravel استخدام الدالة authorize التي توفّرها السمة AuthorizesRequest:

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

    // حذف المهمة
}

المعطى الأوّل المُمرَّر للدالة authorize هو اسم الدالة التي نريد استدعاءها في سياسة التصريح؛ المعطى الثاني هو الكائن الذي نريد التعامل معه. تذكّر أننا أخبرنا Laravel للتو أن سياسة التصريح TaskPolicy تنطبق على النموذج Task، أي أن إطار العمل يعرف في أي سياسة تصريح سيبحث عن الدالة التي حدّدنا اسمها في المعطى الأول المُمرَّر للدالة authorize. بالنسبة للمستخدم فإن المستخدم الحالي سيُرسَل تلقائيا إلى الدالة destroy في الصّنف TaskPolicy. إن تحقّق الشرط (المستخدم صاحب المهمة هو من طلب حذفها) فسيستمرّ تنفيذ الدالة destroy في المتحكّم TaskController بصورة طبيعية؛ وإلا فسينطلق استثناء Exception برمز الخطأ 403 (غياب التصريح) وتظهر صفحة خطأ.

ملحوظة: ليست هذه هي الطريقة الوحيدة للتخاطب مع خدمات التصريح التي يوفرها Laravel.

حذف المهمة

أخذنا الاحتياطات اللازمة، ويمكننا الآن إضافة الشفرة الخاصة بحذف مهمة من قاعدة البيانات. نستخدم دالة delete التي تتوفّر عليها نماذج Eloquent؛ ثم نوجّه المستخدم بعد حذف المهمة إلى المسار tasks/. تصبح الدالة destroy في المتحكّم TaskController كما يلي:

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

    $task->delete();

    return redirect('/tasks');
}

ترجمة - بتصرّف - للجزء الثاني من مقال Intermediate Task List.


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

أفضل التعليقات

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



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...