404 Not Found

404 Not Found


nginx

متحكمات Laravel والتعامل مع الطلبات

المتحكم هو "قائد الأعمال" في Laravel — يستقبل الطلبات وينسق النماذج والعروض ويعيد الاستجابات؛ جميع المنطق التجاري يُدار من هنا.

1. ما ستتعلمه


2. قصة حقيقية لمطور واجهة خلفية

(1) المشكلة: متحكم "إلهي" من 2000 سطر

في البداية، حشر Bob كل منطق ShopMetrics في متحكم ShopController واحد — إدارة المنتجات ومعالجة الطلبات ومصادقة المستخدمين وتوليد التقارير كانت كلها مجمعة. عندما تضخم الكود إلى 2000 سطر، كان تغيير ميزة واحدة قد يعطل أخرى. عندما تولى Charlie الأمر، استغرق ثلاثة أيام فقط لفك هيكل الكود، واضطرت Alice للانتظار أسبوعين لطلب ميزة بسيطة.

(2) حل متحكم الموارد

يحلل متحكم موارد Laravel عمليات CRUD إلى سبع دوال منفصلة، حيث تؤدي كل دالة مهمة واحدة. وعند دمجه مع ربط المسارات، يتم تعيين عناوين URL والدوال تلقائيًا.

BASH
# أمر واحد يولّد متحكم CRUD كامل
php artisan make:controller ShopController --resource
# ينشئ: index()، create()، store()، show()، edit()، update()، destroy()

(3) العائد

بعد أن أعاد Bob هيكلة الكود باستخدام متحكم الموارد، اقتصرت كل دالة على 30 سطرًا أو أقل؛ ميزة Alice الصغيرة أُنجزت في يومين بدلاً من أسبوعين؛ ولم يعد Charlie يقلق من تعطيل الوظائف الحالية عند تولي ميزات جديدة.


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

(1) الإنشاء والهيكل

BASH
php artisan make:controller HomeController
# ينشئ: app/Http/Controllers/HomeController.php
PHP
// app/Http/Controllers/HomeController.php
class HomeController extends Controller
{
    public function index(): View
    {
        return view('home.index');
    }

    public function about(): View
    {
        return view('home.about');
    }
}

(2) متحكم الإجراء الواحد __invoke

عندما يحتاج المتحكم إلى دالة واحدة فقط، استخدم __invoke بدلاً من دالة مسماة.

BASH
php artisan make:controller GenerateReportController --invokable
PHP
// app/Http/Controllers/GenerateReportController.php
class GenerateReportController extends Controller
{
    public function __invoke(Request $request): RedirectResponse
    {
        $report = ReportGenerator::create($request->all());
        return redirect()->route('reports.show', $report->id);
    }
}

// تسجيل المسار
Route::post('/reports/generate', GenerateReportController::class);
البُعد متحكم قياسي متحكم إجراء واحد
عدد الدوال متعددة 1 __invoke
تسجيل المسار [Ctrl::class, 'method'] Ctrl::class
حالات الاستخدام عمليات مرتبطة عمليات مسؤولية واحدة
مثال ShopController GenerateReportController

(1) ▶ مثال: متحكم إجراء واحد لـ ShopMetrics

PHP
// app/Http/Controllers/ExportOrdersController.php
class ExportOrdersController extends Controller
{
    public function __invoke(Request $request): StreamedResponse
    {
        $shop = Shop::findOrFail($request->shop_id);
        $csv = OrderExporter::toCsv($shop->orders);

        return response()->streamDownload(
            callback: fn () => print($csv),
            name: "orders-{$shop->slug}.csv",
            headers: ['Content-Type' => 'text/csv'],
        );
    }
}

// routes/web.php
Route::post('/shops/{shop}/export', ExportOrdersController::class)
    ->name('shops.export');

الناتج:

TEXT
// تم التنفيذ بنجاح

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

(1) إنشاء متحكم موارد

BASH
php artisan make:controller ShopController --resource

يولّد تلقائيًا 7 دوال CRUD:

فعل HTTP URI الدالة الغرض
GET /shops index قائمة
GET /shops/create create نموذج إنشاء
POST /shops store حفظ سجل جديد
GET /shops/{shop} show تفاصيل
GET /shops/{shop}/edit edit نموذج تعديل
PUT/PATCH /shops/{shop} update تحديث
DELETE /shops/{shop} destroy حذف

(2) تسجيل المسار

PHP
// سطر واحد يسجل جميع المسارات السبعة
Route::resource('shops', ShopController::class);

// تقييد بدوال محددة فقط
Route::resource('shops', ShopController::class)->only([
    'index', 'show', 'store', 'update', 'destroy',
]);

// استبعاد دوال محددة
Route::resource('shops', ShopController::class)->except([
    'create', 'edit',
]);

(1) ▶ مثال: متحكم موارد متجر ShopMetrics

PHP
// app/Http/Controllers/ShopController.php
class ShopController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('tenant.resolve')->except('index', 'show');
    }

    public function index(): View
    {
        $shops = Shop::with('tenant')->paginate(15);
        return view('shops.index', compact('shops'));
    }

    public function create(): View
    {
        return view('shops.create');
    }

    public function store(StoreShopRequest $request): RedirectResponse
    {
        $shop = Shop::create($request->validated());
        return redirect()->route('shops.show', $shop)
            ->with('success', 'تم إنشاء المتجر بنجاح.');
    }

    public function show(Shop $shop): View
    {
        $shop->load('products', 'orders');
        return view('shops.show', compact('shop'));
    }

    public function edit(Shop $shop): View
    {
        return view('shops.edit', compact('shop'));
    }

    public function update(UpdateShopRequest $request, Shop $shop): RedirectResponse
    {
        $shop->update($request->validated());
        return redirect()->route('shops.show', $shop)
            ->with('success', 'تم تحديث المتجر بنجاح.');
    }

    public function destroy(Shop $shop): RedirectResponse
    {
        $shop->delete();
        return redirect()->route('shops.index')
            ->with('success', 'تم حذف المتجر بنجاح.');
    }
}

الناتج:

TEXT
// تم التنفيذ بنجاح

5. متحكم موارد API

(1) إنشاء متحكم API

BASH
php artisan make:controller Api/ShopController --api

--api مكافئ لـ --resource --except=create,edit، لأن API لا يتطلب صفحة نموذج.

الدالة موارد الويب موارد API
index
create
store
show
edit
update
destroy

(1) ▶ مثال: متحكم API لـ ShopMetrics

PHP
// app/Http/Controllers/Api/ShopController.php
class ShopController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:sanctum');
    }

    public function index(Request $request): JsonResponse
    {
        $shops = Shop::query()
            ->when($request->search, fn($q, $search) => $q->where('name', 'like', "%{$search}%"))
            ->paginate($request->per_page ?? 15);

        return ShopResource::collection($shops);
    }

    public function store(StoreShopRequest $request): JsonResponse
    {
        $shop = Shop::create($request->validated());
        return new ShopResource($shop);
    }

    public function show(Shop $shop): JsonResponse
    {
        return new ShopResource($shop->load('products', 'orders'));
    }

    public function update(UpdateShopRequest $request, Shop $shop): JsonResponse
    {
        $shop->update($request->validated());
        return new ShopResource($shop);
    }

    public function destroy(Shop $shop): Response
    {
        $shop->delete();
        return response()->noContent();
    }
}

الناتج:

TEXT
// تم التنفيذ بنجاح

6. حقن التبعية

(1) حقن المنشئ

PHP
class OrderController extends Controller
{
    public function __construct(
        private OrderService $orderService,
        private PaymentGateway $payment,
    ) {}

    public function store(StoreOrderRequest $request): RedirectResponse
    {
        $order = $this->orderService->create($request->validated());
        $this->payment->charge($order);
        return redirect()->route('orders.show', $order);
    }
}

(2) حقن على مستوى الدالة

PHP
class ReportController extends Controller
{
    public function show(Request $request, Report $report): View
    {
        // $request يُحقن بواسطة الحاوية
        // $report يُحل عبر ربط نموذج المسار
        return view('reports.show', compact('report'));
    }
}

(3) ربط نموذج المسار

PHP
// ربط ضمني — تلميح النوع في دالة المتحكم
Route::get('/shops/{shop}', [ShopController::class, 'show']);

public function show(Shop $shop): View
{
    // $shop يُسترجع تلقائيًا من DB بـ {shop}
    // مكافئ لـ: Shop::findOrFail($shop)
    return view('shops.show', compact('shop'));
}

// مفتاح مخصص — ربط بـ slug بدلاً من id
Route::get('/shops/{shop:slug}', [ShopController::class, 'show']);
// الآن: /shops/alice-store → Shop حيث slug = 'alice-store'
طريقة الحقن حالات الاستخدام دورة الحياة
المنشئ مطلوب لجميع دوال المتحكم الطلب بالكامل
مستوى الدالة مطلوب لدوال محددة فقط دالة واحدة
ربط نموذج المسار استرجاع النماذج تلقائيًا من URL دالة واحدة

(1) ▶ مثال: متحكم طلبات ShopMetrics مع حقن التبعية

PHP
// app/Http/Controllers/OrderController.php
class OrderController extends Controller
{
    public function __construct(
        private OrderService $orderService,
    ) {
        $this->middleware('auth');
    }

    public function index(Request $request): View
    {
        $orders = $request->user()->orders()
            ->with('shop', 'products')
            ->latest()
            ->paginate(15);

        return view('orders.index', compact('orders'));
    }

    public function show(Order $order): View
    {
        $this->authorize('view', $order);
        $order->load('items.product', 'shop', 'payment');
        return view('orders.show', compact('order'));
    }
}

الناتج:

TEXT
// تم التنفيذ بنجاح

7. برمجية المتحكم الوسيطة

(1) التعيين في المنشئ

PHP
class ShopController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('tenant.resolve')->except('index');
        $this->middleware('can:update,shop')->only('update', 'edit');
    }
}

(2) التخصيص على مستوى المسار

PHP
Route::middleware(['auth', 'admin'])->group(function () {
    Route::resource('plans', PlanController::class);
});
موقع التخصيص الدقة حالات الاستخدام
المنشئ مستوى الدالة دوال مختلفة في نفس المتحكم تتطلب برمجيات وسيطة مختلفة
تعريف المسار مجموعة مسارات مجموعة من المسارات تشترك في برمجية وسيطة
Kernel العام عام يجب تنفيذه لجميع الطلبات

(1) ▶ مثال: متحكم لوحة إدارة ShopMetrics

PHP
// app/Http/Controllers/Admin/PlanController.php
class PlanController extends Controller
{
    public function __construct()
    {
        $this->middleware(['auth', 'role:admin']);
    }

    public function index(): View
    {
        $plans = Plan::withCount('subscriptions')->get();
        return view('admin.plans.index', compact('plans'));
    }

    public function store(StorePlanRequest $request): RedirectResponse
    {
        Plan::create($request->validated());
        return redirect()->route('admin.plans.index')
            ->with('success', 'تم إنشاء الخطة.');
    }
}

الناتج:

TEXT
// تم التنفيذ بنجاح

8. سلسلة الطلب ← المتحكم ← النموذج ← العرض ← الاستجابة

100%
sequenceDiagram
    participant C as العميل
    participant R as الموجه
    participant M as البرمجية الوسيطة
    participant Ctrl as المتحكم
    participant Model as النموذج
    participant V as العرض

    C->>R: طلب HTTP
    R->>M: تشغيل خط أنابيب البرمجية الوسيطة
    M->>Ctrl: استدعاء دالة المتحكم
    Ctrl->>Model: استعلام البيانات
    Model-->>Ctrl: إرجاع النتائج
    Ctrl->>V: تمرير البيانات إلى العرض
    V-->>Ctrl: HTML المُعرض
    Ctrl-->>C: استجابة HTTP

9. مثال شامل: متحكم CRUD منتجات ShopMetrics

PHP
// ============================================
// شامل: ProductController لـ ShopMetrics
// يغطي: متحكم موارد، حقن تبعية، برمجية وسيطة، ربط نموذج
// ============================================

// app/Http/Controllers/ProductController.php
class ProductController extends Controller
{
    public function __construct(
        private ProductService $productService,
    ) {
        $this->middleware('auth');
        $this->middleware('tenant.resolve');
    }

    public function index(Request $request): View
    {
        $products = Product::query()
            ->where('tenant_id', tenant()->id)
            ->when($request->search, fn($q, $s) => $q->where('name', 'like', "%{$s}%"))
            ->when($request->category, fn($q, $c) => $q->where('category_id', $c))
            ->with('category')
            ->orderBy($request->sort ?? 'created_at', $request->direction ?? 'desc')
            ->paginate(20);

        return view('products.index', compact('products'));
    }

    public function create(): View
    {
        $categories = Category::forTenant(tenant()->id)->get();
        return view('products.create', compact('categories'));
    }

    public function store(StoreProductRequest $request): RedirectResponse
    {
        $product = $this->productService->create(
            tenant()->id,
            $request->validated(),
        );
        return redirect()->route('products.show', $product)
            ->with('success', 'تم إنشاء المنتج بنجاح.');
    }

    public function show(Product $product): View
    {
        $this->authorize('view', $product);
        $product->load('category', 'orderItems.order');
        return view('products.show', compact('product'));
    }

    public function edit(Product $product): View
    {
        $this->authorize('update', $product);
        $categories = Category::forTenant(tenant()->id)->get();
        return view('products.edit', compact('product', 'categories'));
    }

    public function update(UpdateProductRequest $request, Product $product): RedirectResponse
    {
        $this->authorize('update', $product);
        $product->update($request->validated());
        return redirect()->route('products.show', $product)
            ->with('success', 'تم تحديث المنتج بنجاح.');
    }

    public function destroy(Product $product): RedirectResponse
    {
        $this->authorize('delete', $product);
        $product->delete();
        return redirect()->route('products.index')
            ->with('success', 'تم حذف المنتج بنجاح.');
    }
}

❓ أسئلة شائعة

س ما مدى "نحافة" المتحكم؟
ج يجب أن يعمل المتحكم فقط كـ "منسق" — يستقبل الطلبات، يستدعي Service/Model، ويعيد الاستجابات. المنطق التجاري يجب وضعه في صنف Service، والوصول إلى البيانات في Model/Repository؛ يجب إبقاء المتحكمات على 10-30 سطرًا لكل دالة.
س متى يجب استخدام متحكم إجراء واحد؟
ج عندما لا تنتمي العملية إلى أي مورد CRUD، مثل "تصدير تقرير" أو "إرسال بريد إلكتروني" أو "توليد فاتورة". إذا كان المتحكم يحتوي على دالة عامة واحدة فقط، استخدم __invoke.
س ماذا أفعل إذا لم يتمكن ربط نموذج المسار من العثور على سجل؟
ج افتراضيًا، يُعاد خطأ 404. يمكنك تخصيص هذا السلوك باستخدام Route::bind() في RouteServiceProvider لتعريف منطق تحليل مخصص، أو تجاوز دالة resolveRouteBindingQuery() على النموذج.
س ما الفرق بين حقن التبعية وnew؟
ج new ينشئ كائنات يدويًا، مما يتطلب منك إدارة سلسلة التبعيات بنفسك؛ مع حقن التبعية، تحل حاوية Laravel التبعيات وتضخها تلقائيًا، مع دعم ربط الواجهات وإدارة Singleton والاختبار الوهمي.
س لماذا تعيد متحكمات API صنف Resource بدلاً من JSON مباشرة؟
ج صنف Resource يوحّد تنسيق إخراج JSON، مما يسمح لك بإخفاء الحقول الحساسة وإعادة تسمية الحقول وتداخل البيانات المرتبطة. إرجاع النموذج مباشرة سيكشف جميع الحقات وينتج تنسيقًا غير متسق.
س هل يمكنني استخدام DB::raw لكتابة SQL في المتحكم؟
ج نعم، لكن لا يُنصح بذلك. للاستعلامات المعقدة، يجب استخدام Eloquent Scopes أو دوال Query Builder. DB::raw يتجاوز طبقة أمان Eloquent، مما يجعله عرضة لحقن SQL.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): استخدم make:controller --resource لإنشاء ProductController لـ ShopMetrics، وسجل مسارات الموارد، ونفذ دالتي index وshow لإرجاع عروض بسيطة.

  2. تمرين متقدم (⭐⭐): أنشئ متحكم إجراء واحد ExportOrdersController ينفذ وظيفة تصدير CSV، مع حقن OrderService للتعامل مع تحويل البيانات، واستخدام streamDownload لإرجاع الملف.

  3. تحدٍ (⭐⭐⭐): صمم TenantProductController يستخدم ربط نموذج المسار لتحليل {tenant} و{product}، ونفذ عمليات CRUD للمنتجات مع عزل المستأجر للتأكد من أن المستخدمين يمكنهم فقط إدارة المنتجات ضمن مستأجرهم.

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%