سنتحدث في هذا المقال عن معمارية MVC الخاصة بإطار عمل لارافيل Laravel، فبنية MVC هي مبدأ تصميم الويب الذي يتكون من النموذج Model المسؤول عن التواصل مع قاعدة البيانات، والمتحكِّم Controller وهو المكان الذي نخزّن فيه شيفرة تطبيقنا البرمجية، والعرض View وهو الواجهة الأمامية للتطبيق.
يوجد مُوجّه Router في تطبيق الويب عادةً، وهذا ما ناقشناه في المقال السابق، حيث يؤشّر الموجّه إلى المتحكم، ويبحث المتحكم عن البيانات المطلوبة في قاعدة البيانات عبر النموذج، ثم يضع البيانات المُسترَّدة إلى الموقع المقابل في العرض، وأخيرًا يعيد هذا العرض إلى المستخدم.
طبقة العرض
لنبدأ أولًا بطبقة بالعرض، حيث عرّفنا في المقال السابق الوِجهة 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.
اقرأ أيضًا
- المقال السابق: لارافيل للمبتدئين-الجزء الأول: البدء في إنشاء مدونة بسيطة
- كيف تستخدم منشئ الاستعلامات Query builder للتخاطب مع قاعدة البيانات في Laravel
- تعرف على إطار عمل تطوير الويب لارافيل Laravel
- تجريد إعداد قواعد البيانات في لارافيل باستعمال عملية التهجير Migration والبذر Seeder
- علاقات Eloquent والتحميل الحثيث في Laravel 5
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.