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

إنشاء تطبيق Todo List بسيط باستخدام Laravel 5 - الجزء الثاني


هشام رزق الله

بعد أن انتهينا من إعداد قواعد البيانات والطرق (routes)، سوف نتحدث في هذا الجزء عن المتحكِّمات (Controllers)، النماذج (مع العلاقات)، والعروض views. (بما في ذلك نظام blade ومخططات المحتوى).

laravel5-todo-app.png

مساعدات النماذج Form Helpers في Laravel

بعد كل هذه الإعدادات، إذا ذهبنا الآن (في المتصفح) إلى projects/ فسوف نحصل على صفحة فارغة فما هو السبب؟ حسنا، لنكتشف ذلك، قم بتنفيذ الأمر php artisan route:list على سطر الأوامر مرة أخرى وأنظر إلى هذا السطر:

+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+
| Domain | Method   | URI                                    | Name                   | Action                                          | Middleware |
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+
|        | GET|HEAD | projects                               | projects.index         | App\Http\Controllers\ProjectsController@index   |            |
+--------+----------+----------------------------------------+------------------------+-------------------------------------------------+------------+

يبدو أن رابط projects/ يقوم بتحميل الوظيفة Index الخاصة بـ ProjectsController، لذلك سنقوم بفتح app/Http/controllers/ProjectsController.php/ وسنقوم بتحديث التابع (method) لنقوم بربطه إلى عرض سوف نقوم بإنشائه. قم بإضافة السطر ;('return view('projects.index إلى الوظيفة ()index كالتالي:

public function index()
{
    return view('projects.index');
}

بما أننا نستخدم في هذا الدرس نظام Blade للقَوْلَبة سنقوم بإنشاء ملف resources/views/projects/index.blade.php/ وسنكتب نص قصير داخله، ثم سنقوم مرة أخرى بالذهاب إلى projects/ في المتصفح، إذا كان كل شيء يعمل جيدا فسوف تجد النص الذي كتبته بالأعلى.
والآن قُم بنفس الشيء لتابع المتحكِّم create عن طريق إضافة السطر ;('return view('projects.create إلى التابع ()create كالتالي:

public function create()
{
    return view('projects.create');
}

إن إظهار محتويات العرض view هو أمر رائع، لكن ماذا لو كنا نملك أكثر من صفحة واحدة في موقعنا ؟ في هذه الحالة، سوف نحتاج إلى قالب ثابت لجميع الصفحات، أي أننا نحتاج إلى عرض محتويات العرض view داخل قالب HTML بسيط، وسنقوم بهذا عن طريق مخططات المتحكِّمات.

من أجل عمل مخططات المتحكِّمات سنقوم بالخطوات التالية:

  • إنشاء مخطط العرض، وبما أن Laravel توفر لنا واحدة جيدة تدعى app.blade.php لذلك سنوفر الوقت وسنقوم بإستخدامها. لاحظ أن قرب أسفل المخطط هنالك سطر ('@yield('content وهذه دالة ستقوم بتحميل محتوانا الحالي.
  • الإشارة إلى مخططك في عرضك view باستخدام ('extends('app@ ولفها عن طريق كتلة ('section('content@، مثل التالي:

    @extends('app')
    
    @section('content')
    
    This is my /resources/views/projects/index.blade.php file!
    @endsection
    

سنقوم الآن بتنفيذ هذه الخطوات بإنشاء عروض show.blade.php و create.blade.php و index.blade.php في مجلد resources/views/projects/ مع الشيفرات (markup) المذكورة أعلاه وقُم بتغيير اسم الملف إذا كان الأمر ضروريا، ثم قُم بعمل تحديث للصفحة projects/ في متصفحك، وسوف ترى هيكل app.blade.php حول محتويات عرضك (view).

الربط بين الطريق (route) والنموذج

سيوفر Laravel بشكل افتراضي معرف رقمي ID للعديد من توابع متحكِّمات الموارد مثل ()show و ()edit و ()update و ()destroy، وهذا الأمر سيضيف العديد من الشيفرات الجاهزة (boilerplate) التي نحتاج إلى كتابتها، مثل الحصول على مثيل النموذج والتأكد من وجودها وغيرها. ولهذا سوف نستخدم إحدى مميزات Laravel والتي تدعى الربط بين الطريق و النموذج route model binding، فبدل من توفير متغير id$، سوف نقوم بإعطاء الوظيفة (method) كائن project$ أو task$.

قُم بفتح app/Http/routes.php/ وأضف هذين السطرين (أول سطرين بعد التعليق):

// Provide controller methods with object instead of ID
Route::model('tasks', 'Task');
Route::model('projects', 'Project'); 

// Use slugs rather than IDs in URLs
Route::bind('tasks', function($value, $route) {
    return App\Task::whereSlug($value)->first();
});

Route::bind('projects', function($value, $route) {
    return App\Project::whereSlug($value)->first();
});

Route::resource('projects', 'ProjectsController');
Route::resource('projects.tasks', 'TasksController');

وفي TasksController و ProjectsController (ملفات app/Http/Controllers/ProjectsController.php/ و app/Http/Controllers/TasksController.php/) قُم باستبدال كل وظيفة (method) مُعرف بمرجع id$ بـ task$ أو project$ مثل التالي:

// public function edit($id)
public function edit(Project $project)

لا تنسَ إضافة use App\Task و use App\Project في أعلى المتحكِّمات حتى نشير إلى هذه النماذج. لا تقلق إذا لم تفهم هذه التغييرات، فسنقوم بعرض الشيفرة البرمجة الكاملة لـ TasksController و ProjectsController لاحقا.
وفي هذه المرحلة يمكنك تمرير الكائن (object) إلى عرضه في كل متحكِّم لوظيفة (method) الإظهار والتعديل والتحديث حتى نستطيع استخدامهم لاحقا:

public function edit(Project $project)
{
    return view('projects.show', compact('project'));
}

سوف يحتاج TasksController إلى بضعة تعديلات طفيفة أخرى، وبما أننا نستخدم الموارد المضمّنة nested resources، سيخبرنا php artisan route:list أن جميع طرق task تحتوي على قناع {projects} بالإضافة إلى أن بعضها يستقبل قناع {tasks} أيضا. و كنتيجة لذلك، سيتم تمرير مثيل Project كمُعامل أول لتوابع المتحكِّمات (controller methods)، لذلك قُم بتحديثها وقٌم بتمرير متغير project$ الجديد.
إذا لم تفهم خطوات الإضافة لـ TasksController و ProjectsController فهذه هي الشيفرة الكاملة للملفين بعد كل التعديلات:

// /app/Http/Controllers/ProjectsController.php
<?php namespace App\Http\Controllers;

use App\Project;
use App\Http\Requests;
use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

class ProjectsController extends Controller {

    /**
     * Display a listing of the resource.
     *
     * @return Response
     */
    public function index()
    {
        $projects = Project::all();
        return view('projects.index', compact('projects'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return Response
     */
    public function create()
    {
        return view('projects.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store()
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function show(Project $project)
    {
        return view('projects.show', compact('project'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function edit(Project $project)
    {
        return view('projects.edit', compact('project'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function update(Project $project)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function destroy(Project $project)
    {
        //
    }

}


// /app/Http/Controllers/TasksController.php
<?php namespace App\Http\Controllers;

use App\Project;
use App\Task;
use App\Http\Requests;
use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

class TasksController extends Controller {

    /**
     * Display a listing of the resource.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function index(Project $project)
    {
        return view('tasks.index', compact('project'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function create(Project $project)
    {
        return view('tasks.create', compact('project'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \App\Project $project
     * @return Response
     */
    public function store(Project $project)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Project $project
     * @param  \App\Task    $task
     * @return Response
     */
    public function show(Project $project, Task $task)
    {
        return view('tasks.show', compact('project', 'task'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Project $project
     * @param  \App\Task    $task
     * @return Response
     */
    public function edit(Project $project, Task $task)
    {
        return view('tasks.edit', compact('project', 'task'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \App\Project $project
     * @param  \App\Task    $task
     * @return Response
     */
    public function update(Project $project, Task $task)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Project $project
     * @param  \App\Task    $task
     * @return Response
     */
    public function destroy(Project $project, Task $task)
    {
        //
    }

}

لاحظ أنه إذا قمت بتحديث صفحة projects/project-1/ سيبقى كل شيء يعمل.

عرض نماذجنا

صفحة عرض قائمة المشاريع، حان الآن وقت البدء بعرض المشاريع والمهام، قٌم بفتح projects/ في متصفحك. وبالاعتماد على php artisan route:list، ستكون هذه الصفحة هي صفحة عرض المشاريع، لذلك قُم بفتح resources/views/projects/index.blade.php/ وأضف التالي:

@extends('app')

@section('content')
    <h2>Projects</h2>
     @if ( !$projects->count() )
        You have no projects
    @else
        <ul>
            @foreach( $projects as $project )
                <li><a href="{{ route('projects.show', $project->slug) }}">{{ $project->name }}</a></li>
            @endforeach
        </ul>
    @endif
@endsection

سوف أشرح بعض النقاط في هذه الشفرة البرمجية:

  • استخدمنا في هذه الشفرة لغة Blade للقَوْلَبة، لذلك فإن if و foreach تتحكم في تدفق flow الدوال بإضافة إلى أنها دالة طباعة (الأقواس المعقوفة المزدوجة).
  • تأكدنا إن كان هنالك أي مشروع لعرضه، إن كان هنالك أية مشاريع فسيقوم بعرضها وإن لم يكن تظهر رسالة تخبرك بذلك.
  • استدعينا مساعد ()route مع طريق ذا اسم (يمكنك رؤية قائمة الطرق ذات الاسم عن طريق php artisan route:list) لربط كل مشروع مع صفحة تفاصيله.

سوف تحتاج أيضا إلى تمرير متغير projects$ لهذا العرض view وإلا ستحصل على خطأ متغير غير معرف undefined variable error. قُم بفتح app/Http/controllers/ProjectsController.php/ وقُم بتحديث الوظيفة ()index إلى:

public function index()
{
    $projects = Project::all(); return view('projects.index', compact('projects'));
}

قُم بتحديث الصفحة (Refresh) وستجد قائمة من مشاريعك.

علاقات النموذج - صفحة تفاصيل المشروع

في صفحة تفاصيل المشروع نحتاج إلى عرض قائمة من مهام المشاريع، وللقيام بذلك نحتاج إلى تعريف علاقة "واحد إلى الكثير one-to-many" في نموذج Project للسماح له بالحصول على المهام tasks.

قُم بفتح app/Project.php/ وأضف التابع ()tasks كالتالي:

public function tasks()
{
    return $this->hasMany('App\Task');
}

بشكل عكسي، يمكننا إضافة علاقة "الكثير إلى واحد many-to-one" لنموذج المهام Task:

public function project()
{
    return $this->belongsTo('App\Project');
}

للتأكد من أنها تعمل اكتب الأمر:

$ php artisan tinker

وستكون النّتيجة كالتي حسب المدخلات:

>App\Project::whereSlug('project-1')->first()->tasks->count();
5
>App\Project::whereSlug('project-2')->first()->tasks->count();
2
>App\Task::first()->project->name;
Project 1

ممتاز، يمكننا الآن تحديث عرض resources/views/projects/show.blade.php/:

@extends('app')

@section('content')
    <h2>{{ $project->name }}</h2>
     @if ( !$project->tasks->count() )
        Your project has no tasks.
    @else
        <ul>
            @foreach( $project->tasks as $task )
                <li><a href="{{ route('projects.tasks.show', [$project->slug, $task->slug]) }}">{{ $task->name }}</a></li>
            @endforeach
        </ul>
    @endif
@endsection

اضغط على أي مشروع في صفحة عرض قائمة المشاريع في متصفحك وسوف يتم عرض المشروع مع جميع المهام المرتبطة به.
وأخير تَبقى لدينا صفحة عرض المهام (resources/views/tasks/show.blade.php/)، وهذه سهلة للغاية:

@extends('app')

@section('content')
    <h2>
        {!! link_to_route('projects.show', $project->name, [$project->slug]) !!} -
        {{ $task->name }}
    </h2>

    {{ $task->description }}
@endsection

ملاحظة: كُن حذرا عندما تستخدم علاقات النماذج (models)، فإنه من السهل إنشاء عدد كبير من استعلامات SQL، وتسمى هذه المشكلة بـ "مشكلة N+1".

الخاتمة

اليوم تعلمنا الكثير من الأشياء الجديدة مثل:

  • الربط بين الطريق (route) و النموذج
  • النماذج (مع علاقة واحد إلى الكثير one-to-many)
  • المتحكِّمات (مع الربط بين الطريق و النموذج)
  • العروض (مع لغة blade للقَوْلَبة والمخططات)

أصبحت الصفحة تعرض لنا قائمة من المشاريع والمهام، وفي الدرس القادم سوف نركز على تعديل وإضافة وحذف المشاريع والمهام.

ترجمة -وبتصرّف- للمقال Creating a Basic ToDo Application in Laravel 5 – Part 2.

حقوق الصورة البارزة: Designed by Freepik.


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...