مقالات علاء عامر

نقدم مجموعة شاملة من مقالات التعليمية الهامة في تطوير الويب لتحويل أفكارك إلى واقع رقمي

نمط MVC في Laravel: فهم وتطبيق الهيكل الأساسي

Laravel 2026-01-01 علاء عامر

نمط 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

  1. فصل المسؤوليات - كل مكون له دور واضح ومحدد
  2. استخدام Form Requests للتحقق من البيانات
  3. تطبيق Resource Controllers للعمليات CRUD
  4. استخدام Route Model Binding لتبسيط الكود
  5. تنظيم Views في مجلدات منطقية
  6. استخدام Eloquent Relationships بكفاءة
  7. تطبيق Authorization للأمان

الخطوة التالية

تعلم Blade Templates المتقدم و Database Migrations لبناء تطبيقات أكثر تعقيداً.

📩 هل تحتاج مساعدة في تطبيق نمط MVC؟

Laravel MVC Model View Controller Architecture Design Pattern
قسم المقالة
Laravel

نمط MVC في Laravel: فهم وتطبيق الهيكل الأساسي

شرح مفصل لنمط Model-View-Controller في Laravel مع أمثلة عملية وأفضل الممارسات لبناء تطبيقات منظمة.

نمط MVC في Laravel: فهم وتطبيق الهيكل الأساسي
01

التواصل والاستشارة

تواصل مباشر عبر الواتساب أو الهاتف لفهم احتياجات مشروعك بدقة.

02

التخطيط والجدولة

وضع خطة عمل واضحة مع جدول زمني محدد لكل مرحلة من المشروع.

03

البرمجة والتطوير

تطوير المشروع بأحدث التقنيات لضمان الأداء والأمان العاليين.

04

المراجعة والتسليم

ختبار شامل ومراجعة دقيقة قبل التسليم النهائي للمشروع.

علاء عامر
علاء عامر

مطور ويب محترف بخبرة تزيد عن 10 سنوات في بناء حلول رقمية مبتكرة.

هل تحتاج هذه الخدمة؟

تواصل معي الآن للحصول على استشارة مجانية وعرض سعر

تواصل عبر واتساب رضاكم هو هدفنا الأسمى

عروض إضافية

  • صيانة وتحديث المواقع

    نحافظ على موقعك آمنًا ومحدّثًا دائمًا

  • ربط الأنظمة وواجهات البرمجة

    نربط أنظمتك بواجهات برمجية قوية ومرنة

  • تصميم وتحسين قواعد البيانات

    استعلامات أسرع وهيكلة أوضح وأخطاء أقل

  • تأمين المواقع والحماية المتقدمة

    حماية موقعك من التهديدات السيبرانية

  • أتمتة العمليات والمهام البرمجية

    نؤتمت المهام المتكررة ونوفّر وقتك دائمًا

لديك استفسار؟

اتصل بنا الآن

00201014714795

راسلنا عبر البريد الإلكتروني

[email protected]