404 Not Found

404 Not Found


nginx

دورة حياة الطلب ونواة HTTP في Laravel

دورة حياة الطلب هي "مخطط المحرك" في Laravel—بفهمها ستنتقل من "معرفة كيفية استخدام الإطار" إلى "فهم الإطار."

1. ما ستتعلمه


2. قصة حقيقية لمطوّر خبير

(1) المشكلة: تصحيح الصندوق الأسود يضيع ساعات

واجهت Alice خطأً غريباً في ShopMetrics: كل شيء يعمل محلياً بشكل ممتاز، لكن بعد النشر، كانت إحدى الواجهات (Facade) تُرجع null. لم تكن تعرف أي فئة تستدعيها الواجهة داخلياً، ولم تكن متأكدة أين ربطتها حاوية الخدمات. استغرق منها البحث في الكود أربع ساعات لتكتشف أن طريقة register() في أحد ServiceProvider تحتوي على شرط شرطي، وأن الربط لم يتم تسجيله في فرع الإنتاج. كما تعلّم Bob بالطريقة الصعبة—لم يكن يفهم ترتيب تنفيذ الوسائط (middleware)، مما أدى إلى تشغيل وسيط المصادقة بعد وسيط CORS، مما تسبب في خطأ 401 أثناء التحقق المسبق من الطلبات.

(2) الحل بفهم دورة الحياة

بمجرد فهمك لدورة حياة الطلب، يمكنك تحديد المرحلة التي يحدث فيها أي مشكلة بدقة—سواء كان عدم تطابق المسار، أو فشل فحص الوسائط، أو رابط مفقود في الحاوية، أو مزود فشل في التحميل.

PHP
// معرفة دورة الحياة تساعدك في تصحيح الأخطاء هكذا:
// 1. التحقق من وجود الربط
app()->bound('payment.gateway');
// 2. التحقق من أي مزود سجّله
app()->getBindings()['payment.gateway']['concrete'];
// 3. تتبع ترتيب تنفيذ الوسائط
app()->make(\Illuminate\Foundation\Http\Kernel::class)->getMiddlewarePriority();

(3) الفائدة

باستخدام تقنيات تصحيح الحاوية، حددت Alice سبب إرجاع الواجهة لـ null في 5 دقائق فقط، وحلّ Bob مشكلة CORS فوراً بعد تعديل أولوية الوسائط. المطورون الذين يفهمون دورة الحياة يمكنهم مضاعفة كفاءة تصحيح الأخطاء عشر مرات.


3. دورة حياة الطلب

(1) العملية الكاملة

100%
flowchart TD
    A["public/index.php<br/>(نقطة الدخول)"] --> B["HTTP Kernel<br/>(bootstrap/app.php)"]
    B --> C["مزودو الخدمات<br/>(Register & Boot)"]
    C --> D["خط أنابيب الوسائط<br/>(عام + مسار)"]
    D --> E{"تم مطابقة المسار؟"}
    E -->|نعم| F["المتحكم/الإجراء<br/>(المنطق البرمجي)"]
    E -->|لا| G["Fallback/404"]
    F --> H["كائن الاستجابة"]
    G --> H
    H --> I["إرسال إلى العميل"]
    I --> J["إنهاء<br/>(خطافات ما بعد الاستجابة)"]

(2) شرح تفصيلي لكل مرحلة

المرحلة الملف/الفئة المسؤوليات
نقطة الدخول public/index.php تحميل Composer autoload، إنشاء نسخة التطبيق
النواة bootstrap/app.php تهيئة الوسائط، معالجة الاستثناءات، والتوجيه
تسجيل المزودين config/app.providers تسجيل ربط الحاوية
بدء المزودين دالة المزود boot() تنفيذ منطق البدء
الوسائط عام ← مسار تصفية/تعديل الطلبات والاستجابات
توزيع المسارات Router مطابقة URL ← تنفيذ المتحكم
الإنهاء الوسائط القابلة للإنهاء تنفيذ عمليات التنظيف بعد الاستجابة

(1) ▶ مثال:عرض مراحل دورة حياة الطلب

BASH
# التحقق من مزودي الخدمات المسجلين
php artisan about --only=providers
# القائمة: AppServiceProvider، AuthServiceProvider، إلخ

# التحقق من خط أنابيب الوسائط
php artisan route:list --columns=method,uri,middleware
# GET /dashboard ← auth, tenant.resolve, verified

# التحقق من الخدمات المرتبطة في الحاوية
php artisan tinker
# app()->getBindings()
# يعرض جميع روابط الحاوية المسجلة

الناتج:

TEXT
# تم تنفيذ الأمر بنجاح

4. نواة HTTP ونواة Console

يحتوي Laravel على نواتين: نواة HTTP تتعامل مع طلبات الويب، ونواة Console تتعامل مع أوامر Artisan.

(1) تهيئة النواة في Laravel 11

PHP
// bootstrap/app.php — تهيئة ملف واحد في Laravel 11
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->appendToGuestList([
            '/api/*',
        ]);
        $middleware->alias([
            'tenant.resolve' => \App\Http\Middleware\TenantResolve::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();
البُعد نواة HTTP نواة Console
نقطة الدخول public/index.php ملف artisan
الكائن المُعالَج طلب HTTP إدخال Console
الوسائط عام + مسار لا يوجد
المخرجات استجابة HTTP مخرجات Console
البيئة طلبات الويب أوامر CLI

(1) ▶ مثال:وسيط عام مخصّص

PHP
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    // إضافة وسيط عام (يعمل على كل طلب)
    $middleware->append([
        \App\Http\Middleware\SetLocale::class,
    ]);

    // إزالة وسيط عام افتراضي
    $middleware->remove([
        \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
    ]);

    // تسجيل اسم مستعار لوسيط المسار
    $middleware->alias([
        'tenant' => \App\Http\Middleware\TenantResolve::class,
        'role' => \App\Http\Middleware\CheckRole::class,
    ]);
})

الناتج:

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

5. حاوية الخدمات

حاوية الخدمات هي قلب Laravel—تُدير حقن التبعيات ودورة حياة الفئات.

(1) الربط

PHP
// app/Providers/AppServiceProvider.php
public function register(): void
{
    // ربط الواجهة بالتنفيذ
    $this->app->bind(
        PaymentGatewayInterface::class,
        StripeGateway::class,
    );

    // ربط بإغلاق (تحكم كامل)
    $this->app->bind('analytics.service', function ($app) {
        return new AnalyticsService(
            $app->make(CacheManager::class),
            $app['config']->get('analytics.ttl'),
        );
    });

    // Singleton — نفس النسخة في كل مرة
    $this->app->singleton(ShopMetricsConfig::class, function ($app) {
        return new ShopMetricsConfig(
            $app['config']->get('shopmetrics'),
        );
    });
}
طريقة الربط لكل استدعاء الغرض
bind() إنشاء نسخة جديدة خدمة عديمة الحالة
singleton() إعادة استخدام النسخة كائنات ذات حالة/مكلفة
scoped() إنشاء نسخة جديدة لكل طلب singleton على مستوى الطلب
instance() استخدام نسخة موجودة كائنات تم إنشاؤها مسبقاً

(2) الحل

PHP
// حل تلقائي عبر تلميح النوع
class OrderController extends Controller
{
    public function __construct(
        private PaymentGatewayInterface $gateway, // يُحل تلقائياً
    ) {}
}

// حل يدوي
$gateway = app(PaymentGatewayInterface::class);
$gateway = app()->make(PaymentGatewayInterface::class);
$analytics = resolve('analytics.service');

(1) ▶ مثال:ربط حاوية خدمات ShopMetrics

PHP
// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(
        \App\Contracts\ReportGeneratorInterface::class,
        \App\Services\PdfReportGenerator::class,
    );

    $this->app->singleton(
        \App\Services\TenantManager::class,
        fn ($app) => new TenantManager(
            $app->make(\App\Models\Tenant::class),
        ),
    );

    $this->app->when(ShopController::class)
        ->needs(\App\Contracts\FileStorageInterface::class)
        ->give(\App\Services\S3StorageService::class);
}

الناتج:

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

6. مبدأ الواجهات (Facades)

الواجهات هي "الوكلاء الثابتون" في Laravel—تستخدم صياغة استدعاء ثابتة موجزة وتعتمد على حاوية الخدمات لحل الكائنات الفعلية في الخلفية.

(1) كيف تعمل الواجهة

100%
flowchart LR
    A["Cache::get('key')"] --> B["واجهة Cache<br/>(استدعاء ثابت)"]
    B --> C["Facade::__callStatic()"]
    C --> D["حل من الحاوية<br/>(مدير التخزين المؤقت)"]
    D --> E["CacheManager->get('key')"]

(2) جدول أنماط الواجهات الشائعة

الواجهة الفئة الفعلية مفتاح ربط الحاوية
Cache CacheManager cache
DB DatabaseManager db
Event Dispatcher events
Log LogManager log
Mail MailManager mail
Queue QueueManager queue
Route Router router
Storage FileManager filesystem

(3) الواجهة مقابل حقن التبعيات

البُعد الواجهة حقن التبعيات
الصياغة استدعاء ثابت حقن عبر المُنشئ/الطريقة
قابلية الاختبار قابل للمحاكاة (Cache::fake()) قابل للمحاكاة (ربط يدوي)
دعم IDE يتطلب إضافات/ملفات دعم تلميحات نوع أصلية
البساطة ✅ استدعاء بسطر واحد ❌ يتطلب إعلان مُنشئ
السيناريوهات الموصى بها عمليات بسيطة/متحكمات فئات الخدمة/المُنشآت

(1) ▶ مثال:الواجهة مقابل حقن التبعيات

PHP
// استخدام الواجهة — موجز
use Illuminate\Support\Facades\Cache;

public function getShopStats(int $shopId): array
{
    return Cache::remember("shop.stats.{$shopId}", 3600, function () use ($shopId) {
        return Shop::findOrFail($shopId)->getStats();
    });
}

// استخدام حقن التبعيات — صريح، أسهل في الاختبار
public function __construct(
    private CacheManager $cache,
) {}

public function getShopStats(int $shopId): array
{
    return $this->cache->remember("shop.stats.{$shopId}", 3600, function () use ($shopId) {
        return Shop::findOrFail($shopId)->getStats();
    });
}

الناتج:

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

7. آلية بدء مزود الخدمات

(1) دورة حياة المزود

TEXT
يصل الطلب
  ← مرحلة التسجيل: تُستدعى register() لجميع المزودين (بدون بدء بعد)
  ← مرحلة البدء: تُستدعى boot() لجميع المزودين (جميع الروابط متاحة)
  ← التطبيق جاهز

(2) المزود المؤجل

PHP
// app/Providers/PaymentServiceProvider.php
class PaymentServiceProvider extends ServiceProvider
{
    protected bool $defer = true;

    public function register(): void
    {
        $this->app->singleton(StripeGateway::class, function ($app) {
            return new StripeGateway(config('services.stripe'));
        });
    }

    public function provides(): array
    {
        return [StripeGateway::class];
    }
}
نوع المزود متى يُحمّل حالات الاستخدام
مزود قياسي يُحمّل على كل طلب ميزات أساسية/شائعة
مزود مؤجل يُحمّل عند أول استخدام ميزات مكلفة/نادرة الاستخدام

(1) ▶ مثال:مزود خدمات مخصّص لـ ShopMetrics

PHP
// app/Providers/ShopMetricsServiceProvider.php
class ShopMetricsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            ReportGeneratorInterface::class,
            PdfReportGenerator::class,
        );

        $this->app->singleton(TenantManager::class);
    }

    public function boot(): void
    {
        // تسجيل اسم مستعار للوسيط
        $this->app->make(\Illuminate\Routing\Router::class)
            ->aliasMiddleware('tenant', TenantResolve::class);

        // تسجيل ملحن العرض
        View::composer('dashboard.*', NavigationComposer::class);

        // تسجيل مستمعي الأحداث
        Event::listen(OrderPlaced::class, SendOrderNotification::class);
    }
}

الناتج:

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

8. مثال شامل: تتبع طلب ShopMetrics

PHP
// ============================================
// شامل: تتبع طلب ShopMetrics
// يغطي: دورة الحياة، الحاوية، الواجهات، المزود
// ============================================

// 1. public/index.php — نقطة الدخول
// $app = require_once __DIR__.'/../bootstrap/app.php';
// $app->handleRequest();

// 2. bootstrap/app.php — تهيئة النواة
// return Application::configure(basePath: dirname(__DIR__))
//     ->withRouting(web: ..., api: ...)
//     ->withMiddleware(fn ($m) => $m->append([SetLocale::class]))
//     ->create();

// 3. AppServiceProvider — تسجيل الروابط
// public function register(): void
// {
//     $this->app->singleton(TenantManager::class);
//     $this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);
// }

// 4. المسار: GET /{tenant}/dashboard
// Route::middleware(['auth', 'tenant'])->get('/{tenant}/dashboard', [DashboardController::class, 'index']);

// 5. المتحكم — استخدام حقن التبعيات والواجهات
class DashboardController extends Controller
{
    public function __construct(
        private TenantManager $tenantManager, // يُحل عبر حقن التبعيات
    ) {}

    public function index(Request $request): View
    {
        $tenant = $this->tenantManager->current();
        $stats = Cache::remember("dashboard.{$tenant->id}", 300, function () use ($tenant) {
            return [
                'revenue' => $tenant->shops()->sum('revenue'),
                'orders' => $tenant->orders()->count(),
                'shops' => $tenant->shops()->active()->count(),
            ];
        });

        return view('dashboard.index', compact('tenant', 'stats'));
    }
}

// 6. إرسال الاستجابة ← تشغيل الوسائط القابلة للإنهاء ← اكتمال الطلب

❓ أسئلة شائعة

س أيهما أفضل، الواجهة أم الدالة المساعدة؟
ج كلاهما يستدعي نفس الخدمة في الخلفية. الواجهة يمكن محاكاتها للاختبار (Cache::fake())، بينما الدالة المساعدة أكثر إيجازاً (cache()). التوصية: استخدم الدوال المساعدة في السيناريوهات البسيطة، واستخدم الواجهة عند الحاجة للمحاكاة.
س ما الفرق بين register و boot؟
ج register يقوم فقط بربط الحاوية ولا يمكنه الاعتماد على خدمات أخرى؛ boot يعمل بعد اكتمال جميع عمليات register ويمكنه استخدام أي ربط بأمان. اتبع المبدأ: "register يربط فقط؛ boot يبدأ الحاوية."
س متى يجب استخدام singleton ومتى bind؟
ج استخدم bind للخدمات عديمة الحالة (حيث تُنشأ نسخة جديدة في كل مرة، مثل مُنشئ PDF)؛ استخدم singleton للخدمات ذات الحالة أو التي تكلف تهيئتها الكثير (مثل اتصالات قاعدة البيانات ومديري التخزين المؤقت).
س هل يؤدي استخدام المزود المؤجل إلى إبطاء الطلب الأول؟
ج هناك تكلفة طفيفة أثناء التحميل الأولي، لكنه يقلل وقت البدء لكل طلب لاحق بشكل عام. إذا كانت 90% من طلبات ميزة معينة لا تستخدمها، فالتأجيل هو الخيار الصحيح.
س كيف أجد الفئة الفعلية المرتبطة بواجهة معينة؟
ج تحقق من السلسلة التي تُرجعها طريقة getFacadeAccessor() لفئة الواجهة، ثم ابحث عن الربط المقابل في الحاوية. أو استخدم app('cache') لاسترداد النسخة مباشرة.
س هل أزال Laravel 11 ملف Kernel.php؟
ج نعم، دمج Laravel 11 نواة HTTP ونواة Console في bootstrap/app.php، باستخدام API متسلسل لتهيئة الوسائط والاستثناءات والمسارات. الوظائف كما هي؛ فقط التهيئة أكثر مركزية.

📖 ملخص


📝 تمارين

  1. سؤال أساسي (⭐): استخدم php artisan about لعرض قائمة مزودي خدمات ShopMetrics، وحدد أيها افتراضي من الإطار وأيها مخصّص، وارسم مخطط تدفق مبسط لدورة حياة الطلب.

  2. تمرين متقدم (⭐⭐): أنشئ ShopMetricsServiceProvider، واستخدم bind() لربط ReportGeneratorInterface بـ PdfReportGenerator، ثم استخدمه في المتحكم عبر حقن التبعيات.

  3. تحدي (⭐⭐⭐): نفّذ مزوداً مؤجلاً للتحميل الكسول لبوابة دفع Stripe. استخدم الربط المُعلن في provides() وتحقق عبر app()->resolved() أنه يُحمّل فعلاً عند أول استخدام فقط.

Web-Tutorial.com

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

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

100%