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

لارافيل للمبتدئين-الجزء الثاني: استخدام بنية MVC لإنشاء مدونة بسيطة


Ola Abbas

سنتحدث في هذا المقال عن معمارية MVC الخاصة بإطار عمل لارافيل Laravel، فبنية MVC هي مبدأ تصميم الويب الذي يتكون من النموذج ‎Model المسؤول عن التواصل مع قاعدة البيانات، والمتحكِّم ‎Controller وهو المكان الذي نخزّن فيه شيفرة تطبيقنا البرمجية، والعرض ‎View وهو الواجهة الأمامية للتطبيق.

يوجد مُوجّه Router في تطبيق الويب عادةً، وهذا ما ناقشناه في المقال السابق، حيث يؤشّر الموجّه إلى المتحكم، ويبحث المتحكم عن البيانات المطلوبة في قاعدة البيانات عبر النموذج، ثم يضع البيانات المُسترَّدة إلى الموقع المقابل في العرض، وأخيرًا يعيد هذا العرض إلى المستخدم.

laravel mvc

طبقة العرض

لنبدأ أولًا بطبقة بالعرض، حيث عرّفنا في المقال السابق الوِجهة Route التالية:

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

تؤشّر هذه الوِجهة إلى العرض welcome المُخزَّن في المجلد resources/views وله الامتداد ‎.blade.php، حيث يُعلِم هذا الامتداد لارافيل أننا نستخدم نظام قوالب Blade الذي سنتحدث عنه لاحقًا.

يمكننا إنشاء عرض آخر من خلال إنشاء ملف آخر له الامتداد نفسه في المجلد views، إذًا لننشئ العرض greetings.blade.php التالي:

<html>
 <body>
   <h1>Hello!</h1>
 </body>
</html>

تحدثنا أيضًا في المقال السابق عن إمكانية تمرير البيانات من الوِجهة إلى العرض كما يلي:

Route::get('/', function () {
   return view('greeting', ['name' => 'James']);
});

أسندنا في المثال السابق القيمة 'James' إلى المتغير name، ومرّرنا هذا المتغير إلى العرض greeting.blade.php الذي أنشأناه مسبقًا، ويمكننا استخدام الأقواس المزدوجة المتعرجة {{ }} لعرض هذه البيانات في العرض greetings.blade.php كما يلي:

<html>
 <body>
   <h1>Hello, {{ $name }}</h1>
 </body>
</html>

من الشائع تنظيم ملفات العرض في مجلدات مختلفة بالنسبة لتطبيقات الويب الكبيرة، فمثلًا يمكن أن يحتوي تطبيقك على لوحة إدارة، وبالتالي ستخزّن العروض Views المقابلة لها في مجلد فرعي اسمه admin مثلًا، وتذكّر استخدام . بدلًا من / عندما تؤشّر إلى ملفات العرض في بنية متداخلة من الموجّه، حيث يؤشّر المثال التالي إلى الملف home.blade.php الموجود في المجلد views/admin/‎:

Route::get('/admin', function () {
   return view('admin.home');
});

صيغة قوالب Blade

تستخدم عروض لارافيل قالب Blade افتراضيًا، وهو مشابه لنظام المكونات في إطار عمل Vue.js. تُعَد العروض مستندات HTML، ويضيف قالب Blade ميزات برمجية إليها مثل نظام الوراثة والتحكم في التدفق والحلقات والمفاهيم البرمجية الأخرى، مما يؤدي إلى إنشاء صفحة ويب أكثر ديناميكية مع كتابة تعليمات برمجية أقل.

التعليمة الشرطية if

يمكننا إنشاء تعليمات if باستخدام التوجيهات Directives التي هي ‎@if و ‎@elseif و ‎@else و ‎@endif، حيث تعمل هذه التعليمات مثل تعليمات if التي نراها في لغات البرمجة الأخرى.

@if ($num == 1)
<p>The number is one.</p>
@elseif ($num == 2)
<p>The number is two.</p>
@else
<p>The number is three.</p>
@endif

يمكننا اختبار هذه الشيفرة البرمجية باستخدام الوِجهة كما يلي:

Route::get('/number/{num}', function ($num) {
   return view('view', ['num' => $num]);
});

نفترض هنا أن المتغير num يمكن أن يكون قيمته 1 أو 2 أو 3 فقط.

التعليمة switch

تكون بنية هذه التعليمة كما يلي:

@switch($i)
 @case(1)
     First case. . .
     @break

 @case(2)
     Second case. . .
     @break

 @default
     Default case. . .
@endswitch

الحلقات

يمكن أن نمثل الحلقات بحلقة for بسيطة:

@for ($i = 0; $i < 10; $i++)
 The current value is {{ $i }}
@endfor

أو بحلقة foreach، حيث يساوي المتغير ‎$user العنصر التالي من ‎$users لكل تكرار في المثال التالي:

@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach

يمكن أن نمثّل الحلقات بحلقة while أيضًا:

@while (true)
<p>I'm looping forever.</p>
@endwhile

تُعَد هذه التعليمات بسيطة جدًا، وتعمل تمامًا مثل نظيراتها في لغة PHP، ولكن يوجد شيء خاص بهذه الحلقات هو المتغير ‎$loop، فمثلًا ليكن لدينا شيء نريد عرضه مرة واحدة فقط، حيث يمكنك تطبيق ما يلي في التكرار الأول:

@foreach ($users as $user) @if ($loop->first)
<p>This is the first iteration.</p>
@endif

<p>This is user {{ $user->id }}</p>
@endforeach

سيُعرَض عنصر الفقرة <p>This is the first iteration.</p> مرة واحدة فقط في التكرار الأول، وتوجد العديد من الخاصيات الأخرى التي يمكنك الوصول إليها، لذا اطلع عليها في توثيق لارافيل الرسمي. لاحظ أنه لا يمكن الوصول إلى المتغير ‎$loop إلا ضمن الحلقة.

الصنف الشرطي Conditional Class

يُعَد الصنف Class الطريقة الأكثر استخدامًا لإسناد التنسيقات Styles إلى عناصر HTML المختلفة، ويمكننا بسهولة تغيير مظهر هذا العنصر من خلال تغيير صنفه، حيث يقدّم لارافيل طريقة لإسناد الأصناف ديناميكيًا بالاعتماد على المتغيرات كما يلي:

<span @class([
   'p-4',
   'font-bold' => $isActive,
   'bg-red' => $hasError,
])></span>

يعتمد الصنفان font-bold و bg-red في المثال السابق على قيمة المتغيرين ‎$isActive و ‎$hasError‎.

بناء تخطيط الصفحة Layout

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

وراثة القوالب

نحاول في المثال التالي تعريف صفحة رئيسية، وتكون الوِجهة كما يلي:

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

يستورد العرض layout.blade.php ملفات CSS وجافاسكريبت JavaScript الضرورية، بالإضافة إلى شريط التنقل وتذييل الصفحة التي ستظهر في جميع الصفحات، وسنرث layout.blade.php في الملف home.blade.php. لنضع أولًا ما يلي في الملف layout.blade.php:

<html>
 <head>
   @yield('title')
 </head>

 <body>
   <div class="container">@yield('content')</div>
 </body>
</html>

ولنضع الآن ما يلي في الملف home.blade.php:

@extends('layout') @section('title')
<title>Home Page</title>
@endsection @section('content')
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. . .</p>
@endsection

لاحظ أن ‎@section('title')‎ يقابل ‎@yield('title')‎، وأن ‎@section('content')‎ يقابل ‎@yield('content')‎. سيرى لارافيل أولًا التوجيه ‎@extends('layout')‎ عند عرض الصفحة الرئيسية في مثالنا، وسيعرف أنه يجب عليه الانتقال إلى الملف layout.blade.php. سيحدد لارافيل بعد ذلك موقع ‎@yield('title')‎ ويضع القسم title مكانه، ثم يبحث عن التوجيه ‎@yield('content')‎ ويضع القسم content مكانه.

سنحتاج في بعض الأحيان إلى تحميل عرض آخر من العرض الحالي، فمثلًا إذا أردنا إضافة شريط جانبي إلى صفحتنا الرئيسية، وهو شيء سنضمّنه في بعض الصفحات دون صفحات أخرى، فلن نضعه في الملف layout.blade.php، ولكن سيكون من الصعب جدًا صيانته إذا أنشأتَ شريطًا جانبيًا لكل صفحة تتطلب ذلك. يمكننا في مثالنا إنشاء الملف sidebar.blade.php، ثم استخدام التوجيه ‎@include لاستيراد هذا الملف في الصفحة الرئيسية كما يلي:

@extends('layout') @section('title')
<title>Home Page</title>
@endsection @section('content')
<p>. . .</p>

@include('sidebar') @endsection

المكونات Components

يشبه نظام المكونات في لارافيل إلى حد كبير النظام الموجود في إطار عمل Vue.js. سنبني صفحة رئيسية مع تخطيطها، ولكننا سنعرّف التخطيط بوصفه مكونًا، لذا سننشئ المجلد components ضمن المجلد resources/views/‎، ثم ننشئ الملف layout.blade.php بداخله.

لنضع أولًا ما يلي في الملف views/components/layout.blade.php:

<html>
 <head>
   . . .
 </head>

 <body>
   <div class="container">{{ $slot }}</div>
 </body>
</html>

يمكن استخدام هذا المكون من خلال استخدام الوسم <x-layout>، وتذكّر دائمًا البادئة x-‎.

<x-layout>
 <p>
   Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eu elit
   semper ex varius vehicula.
 </p>
</x-layout>

سيُسنَد المحتوى الموجود ضمن العنصر <x-layout> إلى المتغير ‎$slot تلقائيًا، ولكن إذا كان لدينا فتحات Slots متعددة كما في مثالنا الذي يحتوي على قسم title وقسم content، فيمكن حل هذه المشكلة من خلال تعريف عنصر <x-slot> كما يلي:

<x-layout>
 <x-slot name="title">
   <title>Home Page</title>
 </x-slot>

 <p>
   Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eu elit
   semper ex varius vehicula.
 </p>
</x-layout>

سيُسنَد محتوى <x-slot name="title"‎> إلى المتغير ‎$title وسيُسنَد المحتوى المتبقي إلى المتغير ‎$slot كما يلي:

<html>
 <head>
   {{ $title }}
 </head>

 <body>
   <div class="container">{{ $slot }}</div>
 </body>
</html>

التعامل مع قواعد البيانات في لارافيل

سنناقش أولًا كيفية التعامل مع قواعد البيانات في لارافيل قبل أن نتحدث بمزيد من التفصيل عن طبقة النموذج والمتحكم في بنية MVC. يمكن أن نستخدم ملف ‎.txt بسيط لتخزين المعلومات عند استخدام جافاسكريبت JavaScript  و Node.js، ولكن لن ينجح ذلك في تطبيق الويب الواقعي، لذا سنحتاج لاستخدام قاعدة بيانات لتخزين ومعالجتها المعلومات بكفاءة.

استخدمنا في مقالنا الحالي أداة Sail الخاصة بلارافيل لإنشاء هذا المشروع، لذا أعدت هذه الأداة قاعدة بيانات MySQL، إذ يجب أن توجد متغيرات البيئة التالية في ملف ‎.env:

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=curl_demo
DB_USERNAME=sail
DB_PASSWORD=password

عمليات تهجير Migrations قاعدة البيانات

تكون قاعدة البيانات فارغة حاليًا، لذا سنضيف بعض جداول قاعدة البيانات لاستخدامها، حيث ستحتوي هذه الجداول على أعمدة، وستكون للأعمدة أسماء وبعض المتطلبات الخاصة، وتسمى عملية الإعداد هذه بتشغيل عمليات التهجير. تُخزَّن جميع ملفات التهجير في لارافيل ضمن المجلد database/migrations، ويمكننا إنشاء ملف تهجير جديد من خلال استخدام الأمر البسيط التالي، ولكن إذا استخدمتَ أداة Sail لإنشاء مشروع لارافيل، فضع ‎./vendor/bin/sail بدلًا من php لتشغيل PHP داخل حاوية دوكر Docker، وإن لم تستخدم أداة Sail، فتابع كالمعتاد:

php artisan make:migration create_flights_table

يمكن تطبيق ملف التهجير كما يلي:

php artisan migrate

وإذا أردتَ التراجع عن عملية التهجير السابقة، فاستخدم الأمر التالي:

php artisan migrate:rollback

أو إذا أردتَ إعادة ضبط عمليات التهجير بالكامل والتراجع عن جميع التغييرات المطبقة على قاعدة البيانات، فاستخدم الأمر التالي:

php artisan migrate:reset

إنشاء جدول

لنلقِ نظرة على المثال التالي قبل إنشاء ملف التهجير. افتح ملف التهجير database/migrations/2014_10_12_000000_create_users_table.php التالي الذي يأتي مع لارافيل:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
   /**
    * تشغيل عمليات التهجير
    */
   public function up(): void
   {
       Schema::create('users', function (Blueprint $table) {
           $table->id();
           $table->string('name');
           $table->string('email')->unique();
           $table->timestamp('email_verified_at')->nullable();
           $table->string('password');
           $table->rememberToken();
           $table->timestamps();
       });
   }

   /**
    * عكس عمليات التهجير
    */
   public function down(): void
   {
       Schema::dropIfExists('users');
   }
};

يحتوي هذا الملف على صنف class له تابعان مختلفان، حيث يُستخدَم التابع ‎up()‎ لإنشاء جداول وأعمدة جديدة، بينما يمكن استخدام التابع ‎down()‎ لعكس العمليات التي يطبّقها التابع ‎up()‎. يستخدم لارافيل داخل التابع ‎up()‎ التابع create()‎ ضمن الصنف Schema لإنشاء جدول جديد بالاسم 'users'. وينشئ كل سطر من السطور 15 إلى 21 في الشيفرة البرمجية السابقة عمودًا مختلفًا مع اسم ونوع مختلفين، فمثلًا ينشئ السطر ‎$table->string('name');‎ عمودًا بالاسم name ضمن الجدول users، ويجب أن يخزّن هذا العمود سلاسلًا نصية.

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

لاحظ السطر 17 في الشيفرة البرمجية السابقة ‎$table->string('email')->unique();‎، حيث يوجد التابع unique()‎ بعد التصريح عن اسم العمود ونوعه، حيث يُسمَّى هذا التابع بمُعدِّل العمود Column Modifier، لأنه يضيف قيودًا إضافية إلى هذا العمود، ويتأكد في هذه الحالة من أن العمود لا يمكن أن يحتوي على قيم متكررة، ويجب أن يكون كل بريد إلكتروني يقدّمه المستخدم فريدًا.

اطّلع على القائمة الكاملة لمُعدِّلي الأعمدة المتوفرة في لارافيل.

إجراء تغييرات على الجدول

يمكن أيضًا تحديث الجداول باستخدام التابع table()‎ كما يلي، حيث تضيف الشيفرة البرمجية التالية عمودًا جديدًا هو العمود age إلى الجدول users الموجود مسبقًا:

Schema::table('users', function (Blueprint $table) {
   $table->integer('age');
});

كما يمكننا إعادة تسمية الجدول باستخدام التابع rename()‎:

Schema::rename($from, $to);

ويمكننا حذف جدول نهائيًا كما يلي:

Schema::drop('users');

Schema::dropIfExists('users');

البذر أو توليد البيانات Seeding

يُستخدَم مولّد البيانات Seeder لإنشاء بيانات وهمية لقاعدة بياناتك، وبالتالي يسهّل عليك إجراء الاختبارات، حيث يمكنك إنشاء مولّد بيانات من خلال تشغيل الأمر التالي:

php artisan make:seeder UserSeeder

يولّد الأمر السابق مولّد بيانات للجدول users، وسيُوضَع في المجلد database/seeders، حيث وضع ما يلي في الملف UserSeeder.php:

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class UserSeeder extends Seeder
{
   /**
    * تشغيل مولّدات بيانات قاعدة البيانات
    */
   public function run(): void
   {
       DB::table('users')->insert([
           'name' => Str::random(10),
           'email' => Str::random(10).'@gmail.com',
           'password' => Hash::make('password'),
       ]);
   }
}

شغّل الأمر التالي لتنفيذ مولّد البيانات السابق وملء جداول قاعدة البيانات بالبيانات الافتراضية:

php artisan db:seed UserSeeder

باني الاستعلامات Query Builder

باني الاستعلامات هو واجهة تسمح لنا بالتفاعل مع قاعدة البيانات، فمثلًا يمكننا استخدام التابع table()‎ الذي يوفره الصنف DB لاسترداد جميع الصفوف الموجودة في الجدول كما يلي:

use Illuminate\Support\Facades\DB;

$users = DB::table('users')->get();

يمكننا إضافة مزيدٍ من القيود من خلال إضافة التابع where()‎ إلى سلسلة التوابع كما يلي، حيث ستحصل الشيفرة البرمجية التالية على جميع الصفوف التي تكون فيها القيمة المخزنة في العمود age أكبر من أو تساوي 21:

$users = DB::table('users')->where('age', '>=', 21)->get();

توجد الكثير من التوابع الأخرى إلى جانب التابعين where()‎ و get()‎، والتي سنتحدث عنها لاحقًا.

طبقة النموذج Model

يُعَد النموذج مفهومًا مهمًا جدًا في تصميم الويب الحديث، فهو مسؤول عن التفاعل مع قاعدة بيانات تطبيق الويب، وهو جزء من نظام رابط الكائنات بالعلاقات Object-Relational Mapper -أو ORM اختصارًا- في لارافيل الذي هو نظام Eloquent، حيث يمكن عَدّه باني استعلام مع بعض الميزات الإضافية. يمكننا استخدام الأمر make:model لإنشاء نموذج جديد كما يلي:

php artisan make:model Post

إذا أردتَ توليد ملف تهجير مقابل للنموذج، فاستخدم الخيار ‎--migration:

php artisan make:model Post --migration

وسيتضمّن ملف النموذج app/Models/Post.php ما يلي:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
   use HasFactory;
   . . .
}

يفترِض النموذج السابق وجود الجدول posts في قاعدة البيانات افتراضيًا، وإذا أردتَ تغيير هذا الإعداد، فاضبط الخاصية ‎$table كما يلي:

class Post extends Model
{
   /**
    * الجدول المرتبط بالنموذج
    *
    * @var string
    */
   protected $table = 'my_posts';
}

استرداد النموذج

يُعَد نموذج Eloquent بانيَ استعلامات قويًا ويسمح بالتواصل مع قاعدة البيانات، فمثلًا يمكننا استخدام التابع all()‎ لاسترداد كافة السجلات في الجدول كما يلي، ولاحظ أننا استوردنا النموذج Post بدلًا من الصنف DB، حيث سيتصل النموذج Post تلقائيًا بالجدول posts:

use App\Models\Post;

$posts = Post::all();

بما أن النموذج هو باني استعلامات، لذا يمكن أن تصل النماذج إلى جميع توابع باني الاستعلامات كما يلي:

$posts = Post::where('published', true)
              ->orderBy('title')
              ->take(10)
              ->get();

إذا استخدمتَ التابعين all()‎ أو get()‎ لاسترداد البيانات، فلن تكون القيمة المُعادة مصفوفةً أو كائنًا بسيطًا، ولكنها ستكون نسخة من الصنف Illuminate\Database\Eloquent\Collection، حيث يوفّر الصنف Collection عدة توابع أقوى من الكائنات البسيطة.

يمكن استخدام التابع find()‎ مثلًا لتحديد موقع سجل باستخدام مفتاح رئيسي هو id عادة.

$posts = Post::all();

$post = $posts->find(1);

سيكون ‎$post في هذا المثال هو المنشور ذو المعرّف id==1.

إدراج وتحديث النموذج

يمكننا أيضًا إدراج السجلات أو تحديثها باستخدام النموذج، ولكن تأكد أولًا من أن النموذج المقابل له الخاصية ‎$fillable وتأكد من سرد جميع الأعمدة التي يجب أن تكون قابلة للملء.

class Post extends Model
{
   /**
    * السمات‫ Attributes التي يمكن إسنادها بطريقة جماعية
    *
    * @var array
    */
   protected $fillable = ['title', 'content'];
}

يمكننا بعد ذلك استخدام التابع create()‎ لإنشاء سجل جديد كما يلي:

$flight = Post::create([
   'title' => '. . .',
   'content' => '. . .',
]);

أو يمكننا تحديث السجلات الموجودة مسبقًا باستخدام التابع update()‎:

Flight::where('published', true)->update(['published' => false]);

حذف النموذج

يوجد تابعان يسمحان بحذف السجلات، حيث إذا أردتَ حذف سجل واحد، فاستخدم التابع delete()‎:

$posts = Post::all();

$post = $posts->find(1);

$post->delete();

وإذا أردتَ حذف السجلات بطريقة جماعية، فاستخدم التابع truncate()‎:

$posts = Post::where('published', true)
              ->orderBy('title')
              ->take(10)
              ->get();

$posts->truncate();

علاقات قاعدة البيانات

لا تكون جداول قاعدة البيانات في تطبيقات الويب مستقلة بل تكون مترابطة بعلاقات فيما بينها، فمثلًا يمكن أن يكون لدينا مستخدم لديه منشورات ويمكن أن يكون لدينا منشوراتٌ تخص مستخدمًا ما، لذا يقدّم لارافيل طريقة لتعريف هذه العلاقات باستخدام نماذج Eloquent.

يُحتمَل أن يكون القسم التالي صعبًا بعض الشيء بالنسبة للمبتدئين، ولكن لا تقلق بهذا الشأن، إذ سنعود إلى هذا الموضوع لاحقًا عندما نبدأ في إنشاء تطبيق المدونة، حيث سنوضّح فقط ما نحتاج إلى استخدامه لبناء نظام تدوين بسيط.

علاقة واحد إلى واحد

تُعَد علاقة واحد إلى واحد One to One العلاقة الأساسية، فمثلًا يرتبط كل مستخدم User بحيوان أليف Pet واحد. يمكن تعريف هذه العلاقة من خلال وضع التابع pet في النموذج User كما يلي:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
   /**
    * الحصول على الحيوان الأليف المرتبط بالمستخدم
    */
   public function pet()
   {
       return $this->hasOne(Pet::class);
   }
}

إذا أردنا أن نطبّق العكس، حيث يكون معكوس العلاقة "يملك" has one هو "يعود إلى" belongs to one، مما يعني أن كل حيوان أليف Pet يعود إلى مستخدم User واحد، ويمكن تعريف معكوس العلاقة واحد إلى واحد من خلال وضع التابع user في النموذج Pet كما يلي:

class Pet extends Model
{
   /**
    * الحصول على المستخدم الذي يملك الحيوان الأليف
    */
   public function user()
   {
       return $this->belongsTo(User::class);
   }
}

لكننا لم ننتهِ بعد، إذ يجب أيضًا إجراء بعض التغييرات على جداول قاعدة البيانات المتقابلة، إذ يفترض النموذج User وجود العمود pet_id ضمن الجدول users، ويخزّن معرّف id الحيوان الأليف الذي يملكه المستخدم. يجب أيضًا إجراء تعديلات على الجدول pets المقابل، إذ يجب وجود العمود user_id الذي يخزّن معرّف id المستخدم الذي يعود إليه هذا الحيوان الأليف.

علاقة واحد إلى متعدد

تُستخدم علاقة واحد إلى متعدد One-to-Many لتعريف العلاقات التي يمتلك فيها نموذج واحد نسخًا متعددة من نموذج آخر، فمثلًا يمكن أن تحتوي فئة Category واحدة على العديد من المنشورات posts، ويمكن تعريف هذه العلاقة من خلال وضع التابع posts في النموذج Category كما يلي:

class Category extends Model
{
   public function posts()
   {
       return $this->hasMany(Post::class);
   }
}

ولكن يجب في بعض الأحيان العثور على الفئة عبر المنشور، فمعكوس العلاقة "لديه العديد" has many هو "ينتمي إلى" belongs to، ويمكن تعريف معكوس العلاقة واحد إلى متعدد من خلال وضع التابع category في النموذج Post كما يلي:

class Post extends Model
{
   public function category()
   {
       return $this->belongsTo(Category::class);
   }
}

تفترض هذه العلاقة أن الجدول posts يحتوي على العمود category_id الذي يخزّن معرّف id الفئة التي ينتمي إليها هذا المنشور.

علاقة متعدد إلى متعدد

تُعَد العلاقة متعدد إلى متعدد Many-to-Many أصعب بعض الشيء، فمثلًا يمكن أن يكون لدينا مستخدم User لديه العديد من الأدوار، ويمكن أن يكون لدينا دور Role له العديد من المستخدمين كما يلي:

class User extends Model
{
   /**
    * الأدوار التي تخص المستخدم
    */
   public function roles()
   {
       return $this->belongsToMany(Role::class);
   }
}
class Role extends Model
{
   /**
    * المستخدمون الذين ينتمون إلى الدور
    */
   public function users()
   {
       return $this->belongsToMany(User::class);
   }
}

تفترض هذه العلاقة وجود الجدول role_user في قاعدة البيانات واحتواء الجدول role_user على العمودين user_id و role_id، وبالتالي يمكننا مطابقة المستخدم مع دوره والعكس صحيح.

لنفترض أن لدينا الجدول role_user كما يلي:

user_id role_id
1 1
2 1
3 2
1 2
2 3

يمتلك المستخدم الذي له المعرّف id=1 دورَين لهما المعرّف id=1 والمعرّف id=2. إذا أردنا عكس الأمر للعثور على المستخدمين عبر الدور، فيمكننا أن نرى مستخدمَين لهما المعرّف id=3 والمعرّف id=1 بالنسبة للدور ذي المعرّف id=2.

طبقة المتحكم Controller

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

Route::get('/number/{num}', function ($num) {
   return view('view', ['num' => $num]);
});

يبدو الأمر جيدًا في الأمثلة البسيطة، ولكن إذا كان لديك مشروع ضخم، فسيؤدي وضع كل الشيفرة البرمجية في ملف الوِجهة Route File إلى جعل الأمر فوضويًا جدًا، والحل الأفضل هو التأكّد من أن الوِجهة تؤشّر دائمًا إلى تابع في المتحكم مع وضع الشيفرة البرمجية ضمن هذا التابع.

 

use App\Http\Controllers\UserController;

Route::get('/users/{name}', [UserController::class, 'show']);

يمكن إنشاء متحكم جديد من خلال استخدام الأمر التالي:

php artisan make:controller UserController

تُخزَّن ملفات متحكم لارافيل ضمن المجلد app/Http/Controllers/‎.

المتحكم الأساسي

لنلقِ نظرة على المثال التالي، حيث يتوقع التابع show()‎ المتغير ‎$id، ويستخدِم هذا المعرّف id لتحديد موقع المستخدم في قاعدة البيانات، ويعيد هذا المستخدم في العرض.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
   /**
    * عرض الملف الشخصي لمستخدمٍ معين
    */
   public function show(string $name): View
   {
       return view('user.profile', [
           'user' => User::firstWhere('name', $name);
       ]);
   }
}

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

class UserController extends Controller
{
   public function __invoke(string $name): View
   {
       return view('user.profile', [
           'user' => User::firstWhere('name', $name);
       ]);
   }
}

لا حاجة الآن لتحديد اسم التابع في الوِجهة كما يلي:

Route::get('/users/{id}', UserController::class);

متحكم الموارد

المورد Resource هو مفهوم حديث آخر لتصميم الويب، حيث نرى جميع نماذج Eloquent بوصفها مواردًا، مما يعني أننا سنجري مجموعة الإجراءات نفسها عليها جميعًا، وهذه الإجراءات هي الإنشاء ‎Create والقراءة ‎Read والتحديث ‎Update والحذف ‎Delete -أو CRUD اختصارًا.

يمكن إنشاء متحكم موارد من خلال استخدام الخيار ‎--resource كما يلي:

php artisan make:controller PostController --resource

سيحتوي المتحكم الذي أنشأناه على التوابع التالية تلقائيًا:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PostController extends Controller
{
   /**
    * عرض قائمة الموارد
    */
   public function index(): Response
   {
       //
   }

   /**
    * عرض الاستمارة الخاصة بإنشاء مورد جديد
    */
   public function create(): Response
   {
       //
   }

   /**
    * تخزين المورد الذي أنشأناه حديثًا في وحدة التخزين
    */
   public function store(Request $request): RedirectResponse
   {
       //
   }

   /**
    * عرض المورد المُحدَّد
    */
   public function show(string $id): Response
   {
       //
   }

   /**
    * عرض الاستمارة الخاصة بتعديل المورد المُحدَّد
    */
   public function edit(string $id): Response
   {
       //
   }

   /**
    * تحديث المورد المحدَّد في وحدة التخزين
    */
   public function update(Request $request, string $id): RedirectResponse
   {
       //
   }

   /**
    * إزالة المورد المُحدَّد من وحدة التخزين
    */
   public function destroy(string $id): RedirectResponse
   {
       //
   }
}

تشرح التعليقات في الشيفرة البرمجية السابقة وظيفة التوابع والغرض منها.

يقدّم لارافيل أيضًا طريقة سهلة جدًا لتسجيل الوِجهات لكل من التوابع السابقة كما يلي:

Route::resource('posts', PostController::class);

ستَنشَأ الوِجهات التالية باستخدام توابع HTTP المختلفة تلقائيًا:

تابع HTTP مسار URI الإجراء اسم الوِجهة
GET ‏ ‎/posts ‏ index ‏ posts.index
GET ‏ ‎/posts/create ‏ create ‏ posts.create
POST ‏ ‎/posts ‏ store ‏ posts.store
GET ‏ ‎/posts/{post}‎ ‏ show ‏ posts.show
GET ‏ ‎/posts/{post}/edit ‏ edit ‏ posts.edit
PUT/PATCH ‏ ‎/posts/{post}‎ ‏ update ‏ posts.update
DELETE ‏ ‎/posts/{post}‎ ‏ destroy ‏ posts.destroy

ترجمة -وبتصرُّف- للمقال Laravel for Beginners #2 - The MVC Structure لصاحبه Eric Hu.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...