الموقع حالياً تحت التطوير (Beta) 🚀. قد تلاحظ بعض التغييرات أو مميزات قيد العمل.
لارافيل درس تعليمي

بناء أنظمة سير عمل الموافقة القوية في Laravel

Admin User
Admin User
Sep 05, 2026
8 دقيقة قراءة

Key Takeaways

  • # بناء أنظمة سير عمل الموافقة القوية في Laravel
  • تعتبر سير عمل الموافقة (Approval Workflows) ميزة حاسمة في العديد من تطبيقات الويب، حيث تتيح الإشراف على المحتوى...

بناء أنظمة سير عمل الموافقة القوية في Laravel #

تعتبر سير عمل الموافقة (Approval Workflows) ميزة حاسمة في العديد من تطبيقات الويب، حيث تتيح الإشراف على المحتوى، والتحقق من صحة المهام، أو عمليات متعددة المراحل قبل أن تصبح بيانات معينة عامة أو نشطة. سواء كنت تقوم ببناء مدونة تتطلب منشوراتها موافقة المشرف، أو منصة للتجارة الإلكترونية تتطلب قوائم المنتجات فحصًا، أو موقعًا لمحتوى من إنشاء المستخدمين، فإن نظام الموافقة القوي لا غنى عنه.

سيرشدك هذا البرنامج التعليمي خلال بناء سير عمل موافقة مرن في Laravel، يغطي تصميم قاعدة البيانات، وسياسات التخويل، ومنطق المتحكمات (Controllers)، وإشعارات المستخدمين.

1. تصميم قاعدة البيانات والترحيلات (Migrations) #

أولاً، دعنا نصمم قاعدة البيانات الخاصة بنا لاستيعاب حالة الموافقة. سنضيف عمود status إلى جدول النموذج المستهدف (مثل posts). سنقوم أيضًا بتضمين approved_by_id و approved_at لأغراض التدقيق.

لنفترض أن لديك جدول posts. إذا لم يكن كذلك، فقم بإنشاء نموذج Post وترحيل أولاً.

php artisan make:model Post -m

عدّل ترحيل create_posts_table الخاص بك لتضمين الأعمدة الجديدة:

// database/migrations/YYYY_MM_DD_create_posts_table.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('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->text('content');
            $table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
            $table->foreignId('approved_by_id')->nullable()->constrained('users')->onDelete('set null');
            $table->timestamp('approved_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

قم بتشغيل الترحيلات الخاصة بك:

php artisan migrate

2. إعداد النموذج (Model) #

قم بتحديث نموذج Post الخاص بك لتضمين السمات القابلة للتعبئة (fillable)، وعمليات التحويل (casts)، والعلاقات.

// app/Models/Post.php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'title',
        'content',
        'status',
        'approved_by_id',
        'approved_at',
    ];

    protected $casts = [
        'approved_at' => 'datetime',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function approver()
    {
        return $this->belongsTo(User::class, 'approved_by_id');
    }

    // نطاقات لسهولة الاستعلام
    public function scopeApproved(Builder $query): void
    {
        $query->where('status', 'approved');
    }

    public function scopePending(Builder $query): void
    {
        $query->where('status', 'pending');
    }

    public function scopeRejected(Builder $query): void
    {
        $query->where('status', 'rejected');
    }

    public function isPending(): bool
    {
        return $this->status === 'pending';
    }

    public function isApproved(): bool
    {
        return $this->status === 'approved';
    }

    public function isRejected(): bool
    {
        return $this->status === 'rejected';
    }

    public function approve(int $approverId)
    {
        $this->status = 'approved';
        $this->approved_by_id = $approverId;
        $this->approved_at = now();
        $this->save();
    }

    public function reject(int $approverId)
    {
        $this->status = 'rejected';
        $this->approved_by_id = $approverId;
        $this->approved_at = now(); // أو تعيينها إلى null إذا لم يعتبر الرفض حدث موافقة
        $this->save();
    }
}

3. التخويل باستخدام السياسات (Policies) #

نحتاج إلى تحديد من يمكنه الموافقة أو رفض منشور. تعتبر سياسات Laravel مثالية لذلك. دعنا ننشئ PostPolicy.

php artisan make:policy PostPolicy --model=Post

سجل السياسة في AuthServiceProvider:

// app/Providers/AuthServiceProvider.php

protected $policies = [
    Post::class => PostPolicy::class,
];

طبق طريقة approve في PostPolicy. سنفترض أن المستخدمين الذين لديهم دور محدد فقط (مثل 'moderator' أو 'admin') يمكنهم الموافقة.

// app/Policies/PostPolicy.php

namespace App\Policies;

use App\Models\User;
use App\Models\Post;

class PostPolicy
{
    /**
     * تحديد ما إذا كان المستخدم يمكنه الموافقة على أي منشورات.
     */
    public function approveAny(User $user): bool
    {
        // مثال: يمكن فقط للمستخدمين الذين لديهم دور 'admin' أو 'moderator' الموافقة.
        // قد يكون لديك طريقة `hasRole` على نموذج المستخدم الخاص بك.
        return $user->isAdmin() || $user->isModerator();
    }

    /**
     * تحديد ما إذا كان المستخدم يمكنه الموافقة على المنشور المحدد.
     */
    public function approve(User $user, Post $post): bool
    {
        // يمكن للمستخدم الموافقة على منشور إذا كان لديه إذن `approveAny`
        // والمنشور في حالة انتظار حاليًا.
        return ($user->isAdmin() || $user->isModerator()) && $post->isPending();
    }

    /**
     * تحديد ما إذا كان المستخدم يمكنه رفض المنشور المحدد.
     */
    public function reject(User $user, Post $post): bool
    {
        // نفس المنطق للرفض كما هو للموافقة.
        return ($user->isAdmin() || $user->isModerator()) && $post->isPending();
    }
}

ملاحظة: ستحتاج إلى طرق isAdmin() و isModerator() على نموذج User الخاص بك، أو حزمة أدوار/أذونات قوية مثل laravel-permission من Spatie.

4. منطق المتحكم (Controller Logic) #

الآن، دعنا ننشئ متحكمًا للتعامل مع عملية الموافقة. قد يكون لديك متحكمات منفصلة للمستخدمين الذين يقدمون المنشورات والمشرفين الذين يديرونها.

php artisan make:controller Moderator/PostApprovalController
// app/Http/Controllers/Moderator/PostApprovalController.php

namespace App\Http\Controllers\Moderator;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class PostApprovalController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth']); // التأكد من أن المستخدم مصادق عليه
    }

    public function pending(Request $request): View
    {
        $this->authorize('approveAny', Post::class); // التحقق مما إذا كان المستخدم يمكنه الموافقة على أي منشور

        $pendingPosts = Post::pending()->latest()->paginate(10);
        return view('moderator.posts.pending', compact('pendingPosts'));
    }

    public function approve(Post $post): RedirectResponse
    {
        $this->authorize('approve', $post);

        $post->approve(auth()->id());

        // اختياريًا إرسال إشعار إلى مؤلف المنشور
        // $post->user->notify(new PostApproved($post));

        return redirect()->route('moderator.posts.pending')->with('success', 'تمت الموافقة على المنشور بنجاح.');
    }

    public function reject(Post $post): RedirectResponse
    {
        $this->authorize('reject', $post);

        $post->reject(auth()->id());

        // اختياريًا إرسال إشعار إلى مؤلف المنشور
        // $post->user->notify(new PostRejected($post));

        return redirect()->route('moderator.posts.pending')->with('success', 'تم رفض المنشور.');
    }
}

5. المسارات (Routes) #

حدد المسارات لعملية الموافقة. من الممارسات الجيدة تجميع مسارات المشرفين.

// routes/web.php

use App\Http\Controllers\Moderator\PostApprovalController;
use Illuminate\Support\Facades\Route;

Route::middleware(['auth'])->prefix('moderator')->name('moderator.')->group(function () {
    Route::get('posts/pending', [PostApprovalController::class, 'pending'])->name('posts.pending');
    Route::post('posts/{post}/approve', [PostApprovalController::class, 'approve'])->name('posts.approve');
    Route::post('posts/{post}/reject', [PostApprovalController::class, 'reject'])->name('posts.reject');
});

// مثال على مسار لمستخدم يقدم منشورًا (إنشاء مبدئي)
Route::post('/posts', function (Request $request) {
    $request->user()->posts()->create($request->validate([
        'title' => 'required|string|max:255',
        'content' => 'required|string',
    ]));
    return back()->with('success', 'تم تقديم المنشور للموافقة!');
})->middleware('auth');

// مثال على مسار لعرض المنشورات الموافق عليها
Route::get('/posts', function () {
    $approvedPosts = App\Models\Post::approved()->latest()->paginate(10);
    return view('posts.index', compact('approvedPosts'));
});

6. الإشعارات (Notifications) #

إبلاغ المؤلف عند الموافقة على منشوره أو رفضه يعزز تجربة المستخدم. دعنا ننشئ فئتي إشعارات.

php artisan make:notification PostApproved
php artisan make:notification PostRejected
// app/Notifications/PostApproved.php

namespace App\Notifications;

use App\Models\Post;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class PostApproved extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(public Post $post) { }

    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
                    ->subject('تمت الموافقة على منشورك!')
                    ->line('أخبار رائعة! تمت الموافقة على منشورك بعنوان "' . $this->post->title . '" وهو الآن مباشر.')
                    ->action('عرض منشورك', url('/posts/' . $this->post->id))
                    ->line('شكراً لك على مساهمتك!');
    }
}
// app/Notifications/PostRejected.php

namespace App\Notifications;

use App\Models\Post;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class PostRejected extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(public Post $post) { }

    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
                    ->subject('تحديث بشأن تقديم منشورك')
                    ->line('نأسف لإبلاغك بأنه تم رفض منشورك بعنوان "' . $this->post->title . '".')
                    ->line('يرجى مراجعة إرشادات المحتوى الخاصة بنا إذا كنت ترغب في إعادة التقديم أو لديك أي أسئلة.')
                    ->action('الذهاب إلى لوحة التحكم', url('/dashboard')) // أو صفحة لتعديل المنشور المرفوض
                    ->line('شكراً لتفهمك.');
    }
}

تذكر إلغاء التعليق عن استدعاءات notify في PostApprovalController.

7. العروض الأساسية (Basic Views) #

إليك مثال بسيط للعرض pending للمشرف ونموذج تقديم المنشور للمؤلف.

resources/views/moderator/posts/pending.blade.php:

<!-- resources/views/moderator/posts/pending.blade.php -->

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Pending Posts for Approval') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    @if(session('success'))
                        <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
                            {{ session('success') }}
                        </div>
                    @endif

                    @forelse($pendingPosts as $post)
                        <div class="mb-6 p-4 border rounded">
                            <h3 class="text-lg font-bold">{{ $post->title }}</h3>
                            <p class="text-sm text-gray-600">بواسطة: {{ $post->user->name }} في {{ $post->created_at->format('M d, Y') }}</p>
                            <p class="mt-2">{{ Str::limit($post->content, 200) }}</p>

                            <div class="mt-4">
                                <form action="{{ route('moderator.posts.approve', $post) }}" method="POST" class="inline-block">
                                    @csrf
                                    <button type="submit" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">موافقة</button>
                                </form>

                                <form action="{{ route('moderator.posts.reject', $post) }}" method="POST" class="inline-block ml-2">
                                    @csrf
                                    <button type="submit" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">رفض</button>
                                </form>
                            </div>
                        </div>
                    @empty
                        <p>لا توجد منشورات معلقة للموافقة.</p>
                    @endforelse

                    <div class="mt-4">
                        {{ $pendingPosts->links() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

الخلاصة #

لقد قمت بنجاح بتطبيق سير عمل موافقة أساسي ولكنه قوي في تطبيق Laravel الخاص بك. يمكن توسيع هذا النظام بشكل أكبر بميزات مثل الموافقات متعددة المراحل، وسجلات التدقيق، وتعليقات المستخدمين على العناصر المعلقة، أو أنواع مختلفة من المحتوى التي تتطلب عمليات موافقة مختلفة. من خلال الاستفادة من ميزات Laravel المضمنة مثل Eloquent و Policies و Notifications، يمكنك بناء أنظمة معقدة وآمنة بكفاءة.

FAQs

ما هو سير عمل الموافقة في Laravel؟
سير عمل الموافقة في Laravel هو نظام مصمم لإدارة والتحكم في دورة حياة المحتوى أو البيانات، ويتطلب خطوات تخويل محددة (مثل من قبل مسؤول أو مشرف) قبل أن يصبح عامًا أو نشطًا أو نهائيًا. يتضمن عادةً تغييرات الحالة والأذونات والإشعارات.
كيف يمكنني تطبيق الموافقات متعددة المراحل؟
للموافقات متعددة المراحل، يمكنك توسيع عمود `status` ليشمل حالات إضافية (مثل `pending_review_1`، `pending_review_2`، `final_approved`). كل مرحلة سيكون لها طريقة سياسة خاصة بها وربما الموافقون مختلفون. ستقوم بتتبع المرحلة الحالية والانتقال إلى المرحلة التالية عند الموافقة، أو العودة إلى مرحلة سابقة عند الرفض.
ما هي فوائد استخدام سياسات Laravel لأنظمة الموافقة؟
تعمل سياسات Laravel على مركزة منطق التخويل، مما يجعل التعليمات البرمجية الخاصة بك أنظف وأسهل في الصيانة. إنها توفر طريقة واضحة وموجهة للكائنات لتحديد من يمكنه التفاعل مع نماذج محددة، مما يضمن أن المستخدمين المصرح لهم فقط يمكنهم تنفيذ إجراءات الموافقة أو الرفض، مما يعزز بشكل كبير الأمان وتنظيم التعليمات البرمجية.

Want more content like this?

Explore more tutorials in the لارافيل section.

Explore لارافيل

You might also like

لارافيل
6 دقيقة قراءة

المساهمة في مشاريع Laravel مفتوحة المصدر: كيفية الإبلاغ عن الأخطاء عبر طلبات السحب (Pull Requests)

يُعرف إطار عمل Laravel ببنيته الأنيقة وميزاته القوية، ويزدهر بفضل مجتمعه النشط والمفتوح المصدر. مؤخرًا، أعلن تايلور أوتويل، مؤسس Laravel، عن تحول كبير في كيفية...

Sep 06, 2026
اقرأ