نمط MVC في Laravel: فهم وتطبيق الهيكل الأساسي
نمط MVC في Laravel: فهم وتطبيق الهيكل الأساسي
دليل تخصصي من علاء عامر – مطور ومصمم مواقع وتطبيقات محترف
نمط Model-View-Controller هو الأساس الذي يقوم عليه Laravel. فهم هذا النمط ضروري لبناء تطبيقات منظمة وقابلة للصيانة.
2️⃣ إنشاء وإدارة Models
إنشاء Model أساسي:
# إنشاء Model بسيط
php artisan make:model Post
# إنشاء Model مع Migration
php artisan make:model Post -m
# إنشاء Model مع Controller و Migration
php artisan make:model Post -mc
# إنشاء Model كامل (Model + Migration + Controller + Seeder + Factory)
php artisan make:model Post -a
تطوير نموذج Post متقدم:
<?php
// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Carbon\Carbon;
class Post extends Model
{
use HasFactory, SoftDeletes;
// الجدول المرتبط بالنموذج
protected $table = 'posts';
// الحقول القابلة للتعبئة الجماعية
protected $fillable = [
'title',
'slug',
'content',
'excerpt',
'featured_image',
'status',
'user_id',
'category_id',
'published_at',
'meta_title',
'meta_description'
];
// تحويل أنواع البيانات
protected $casts = [
'published_at' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'status' => 'string'
];
// الحقول المخفية من JSON
protected $hidden = [
'deleted_at'
];
// الحقول المضافة للـ JSON
protected $appends = [
'reading_time',
'is_published'
];
// العلاقات
public function user()
{
return $this->belongsTo(User::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tag');
}
public function comments()
{
return $this->hasMany(Comment::class)->where('status', 'approved');
}
// Scopes (نطاقات الاستعلام)
public function scopePublished($query)
{
return $query->where('status', 'published')
->where('published_at', '<=', now());
}
public function scopeByAuthor($query, $userId)
{
return $query->where('user_id', $userId);
}
public function scopeInCategory($query, $categoryId)
{
return $query->where('category_id', $categoryId);
}
public function scopeRecent($query, $limit = 5)
{
return $query->orderBy('published_at', 'desc')->limit($limit);
}
// Accessors (محولات الوصول)
public function getReadingTimeAttribute()
{
$wordsPerMinute = 200;
$wordCount = str_word_count(strip_tags($this->content));
return ceil($wordCount / $wordsPerMinute);
}
public function getIsPublishedAttribute()
{
return $this->status === 'published' &&
$this->published_at <= now();
}
public function getExcerptAttribute($value)
{
return $value ?: \Str::limit(strip_tags($this->content), 160);
}
// Mutators (محولات التعديل)
public function setTitleAttribute($value)
{
$this->attributes['title'] = $value;
$this->attributes['slug'] = \Str::slug($value);
}
public function setContentAttribute($value)
{
$this->attributes['content'] = $value;
// إنشاء excerpt تلقائياً إذا لم يكن موجوداً
if (empty($this->attributes['excerpt'])) {
$this->attributes['excerpt'] = \Str::limit(strip_tags($value), 160);
}
}
// دوال مساعدة
public function getRouteKeyName()
{
return 'slug'; // استخدام slug بدلاً من id في الروابط
}
public function publish()
{
$this->update([
'status' => 'published',
'published_at' => now()
]);
}
public function unpublish()
{
$this->update([
'status' => 'draft',
'published_at' => null
]);
}
public function incrementViews()
{
$this->increment('views_count');
}
}
4️⃣ إنشاء Views باستخدام Blade
القالب الأساسي (Layout):
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="rtl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content="{{ csrf_token() }}" />
<title>@yield('title', config('app.name'))</title>
<!-- Meta Tags للـ SEO -->
<meta
name="description"
content="@yield('meta_description', 'موقع Laravel احترافي')"
/>
<meta
name="keywords"
content="@yield('meta_keywords', 'Laravel, PHP, تطوير ويب')"
/>
<!-- Open Graph Tags -->
<meta property="og:title" content="@yield('og_title', '@yield('title')')" />
<meta
property="og:description"
content="@yield('og_description', '@yield('meta_description')')"
/>
<meta
property="og:image"
content="@yield('og_image', asset('images/default-og.jpg'))"
/>
<meta property="og:url" content="{{ url()->current() }}" />
<!-- Fonts & Styles -->
<link
href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;600;700&display=swap"
rel="stylesheet"
/>
@vite(['resources/css/app.css', 'resources/js/app.js']) @stack('styles')
</head>
<body class="font-cairo antialiased">
<div id="app">
<!-- Header -->
@include('partials.header')
<!-- Main Content -->
<main class="min-h-screen">
<!-- Flash Messages -->
@if(session('success'))
<div
class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4"
>
{{ session('success') }}
</div>
@endif @if(session('error'))
<div
class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"
>
{{ session('error') }}
</div>
@endif @yield('content')
</main>
<!-- Footer -->
@include('partials.footer')
</div>
@stack('scripts')
</body>
</html>
صفحة فهرس المقالات:
{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')
@section('title', 'المقالات')
@section('meta_description', 'تصفح جميع المقالات والموضوعات المفيدة')
@section('content')
<div class="container mx-auto px-4 py-8">
<!-- Header Section -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8">
<div>
<h1 class="text-4xl font-bold text-gray-900 mb-2">المقالات</h1>
<p class="text-gray-600">اكتشف أحدث المقالات والموضوعات المفيدة</p>
</div>
@auth
@can('create', App\Models\Post::class)
<a href="{{ route('posts.create') }}"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200">
إنشاء مقال جديد
</a>
@endcan
@endauth
</div>
<!-- Filters Section -->
<div class="bg-white rounded-lg shadow-sm p-6 mb-8">
<form method="GET" action="{{ route('posts.index') }}" class="space-y-4 md:space-y-0 md:flex md:space-x-4">
<!-- Search -->
<div class="flex-1">
<input type="text"
name="search"
value="{{ request('search') }}"
placeholder="البحث في المقالات..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
</div>
<!-- Category Filter -->
<div>
<select name="category" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">جميع الفئات</option>
@foreach($categories as $category)
<option value="{{ $category->id }}"
{{ request('category') == $category->id ? 'selected' : '' }}>
{{ $category->name }} ({{ $category->posts_count }})
</option>
@endforeach
</select>
</div>
<!-- Sort Options -->
<div>
<select name="sort" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="published_at" {{ request('sort') == 'published_at' ? 'selected' : '' }}>
الأحدث
</option>
<option value="views_count" {{ request('sort') == 'views_count' ? 'selected' : '' }}>
الأكثر مشاهدة
</option>
<option value="title" {{ request('sort') == 'title' ? 'selected' : '' }}>
ترتيب أبجدي
</option>
</select>
</div>
<button type="submit"
class="bg-gray-800 hover:bg-gray-900 text-white px-6 py-2 rounded-lg transition duration-200">
بحث
</button>
</form>
</div>
<!-- Posts Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
@forelse($posts as $post)
<article class="bg-white rounded-lg shadow-sm hover:shadow-md transition duration-200 overflow-hidden">
<!-- Featured Image -->
@if($post->featured_image)
<div class="aspect-w-16 aspect-h-9">
<img src="{{ Storage::url($post->featured_image) }}"
alt="{{ $post->title }}"
class="w-full h-48 object-cover">
</div>
@endif
<div class="p-6">
<!-- Category Badge -->
<div class="mb-3">
<span class="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{{ $post->category->name }}
</span>
</div>
<!-- Title -->
<h2 class="text-xl font-semibold text-gray-900 mb-2 line-clamp-2">
<a href="{{ route('posts.show', $post) }}"
class="hover:text-blue-600 transition duration-200">
{{ $post->title }}
</a>
</h2>
<!-- Excerpt -->
<p class="text-gray-600 mb-4 line-clamp-3">
{{ $post->excerpt }}
</p>
<!-- Meta Info -->
<div class="flex items-center justify-between text-sm text-gray-500">
<div class="flex items-center space-x-2">
<span>{{ $post->user->name }}</span>
<span>•</span>
<span>{{ $post->published_at->diffForHumans() }}</span>
</div>
<div class="flex items-center space-x-2">
<span>{{ $post->reading_time }} دقائق</span>
<span>•</span>
<span>{{ number_format($post->views_count) }} مشاهدة</span>
</div>
</div>
<!-- Tags -->
@if($post->tags->count() > 0)
<div class="mt-4 flex flex-wrap gap-2">
@foreach($post->tags->take(3) as $tag)
<span class="inline-block bg-gray-100 text-gray-700 text-xs px-2 py-1 rounded">
{{ $tag->name }}
</span>
@endforeach
</div>
@endif
</div>
</article>
@empty
<div class="col-span-full text-center py-12">
<div class="text-gray-500 text-lg">
@if(request('search') || request('category'))
لم يتم العثور على مقالات تطابق معايير البحث.
<br>
<a href="{{ route('posts.index') }}" class="text-blue-600 hover:underline mt-2 inline-block">
عرض جميع المقالات
</a>
@else
لا توجد مقالات متاحة حالياً.
@endif
</div>
</div>
@endforelse
</div>
<!-- Pagination -->
@if($posts->hasPages())
<div class="mt-12">
{{ $posts->links() }}
</div>
@endif
</div>
@endsection
@push('styles')
<style>
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
@endpush
💡 أفضل الممارسات في MVC
- فصل المسؤوليات - كل مكون له دور واضح ومحدد
- استخدام Form Requests للتحقق من البيانات
- تطبيق Resource Controllers للعمليات CRUD
- استخدام Route Model Binding لتبسيط الكود
- تنظيم Views في مجلدات منطقية
- استخدام Eloquent Relationships بكفاءة
- تطبيق Authorization للأمان
الخطوة التالية
تعلم Blade Templates المتقدم و Database Migrations لبناء تطبيقات أكثر تعقيداً.
📩 هل تحتاج مساعدة في تطبيق نمط MVC؟
قسم المقالة
نمط MVC في Laravel: فهم وتطبيق الهيكل الأساسي
شرح مفصل لنمط Model-View-Controller في Laravel مع أمثلة عملية وأفضل الممارسات لبناء تطبيقات منظمة.
التواصل والاستشارة
تواصل مباشر عبر الواتساب أو الهاتف لفهم احتياجات مشروعك بدقة.
التخطيط والجدولة
وضع خطة عمل واضحة مع جدول زمني محدد لكل مرحلة من المشروع.
البرمجة والتطوير
تطوير المشروع بأحدث التقنيات لضمان الأداء والأمان العاليين.
المراجعة والتسليم
ختبار شامل ومراجعة دقيقة قبل التسليم النهائي للمشروع.