دورة حياة الطلب ونواة HTTP في Laravel
دورة حياة الطلب هي "مخطط المحرك" في Laravel—بفهمها ستنتقل من "معرفة كيفية استخدام الإطار" إلى "فهم الإطار."
1. ما ستتعلمه
- دورة حياة الطلب: public/index.php ← Kernel ← Pipeline ← Response
- بنية النواة المزدوجة مع HTTP Kernel و Console Kernel
- ربط وحل حاوية الخدمات: bind/singleton/make
- الواجهات (Facades): المبادئ وآلية الوكيل الثابت
- ترتيب بدء المزودين والمزودون المؤجلون
2. قصة حقيقية لمطوّر خبير
(1) المشكلة: تصحيح الصندوق الأسود يضيع ساعات
واجهت Alice خطأً غريباً في ShopMetrics: كل شيء يعمل محلياً بشكل ممتاز، لكن بعد النشر، كانت إحدى الواجهات (Facade) تُرجع null. لم تكن تعرف أي فئة تستدعيها الواجهة داخلياً، ولم تكن متأكدة أين ربطتها حاوية الخدمات. استغرق منها البحث في الكود أربع ساعات لتكتشف أن طريقة register() في أحد ServiceProvider تحتوي على شرط شرطي، وأن الربط لم يتم تسجيله في فرع الإنتاج. كما تعلّم Bob بالطريقة الصعبة—لم يكن يفهم ترتيب تنفيذ الوسائط (middleware)، مما أدى إلى تشغيل وسيط المصادقة بعد وسيط CORS، مما تسبب في خطأ 401 أثناء التحقق المسبق من الطلبات.
(2) الحل بفهم دورة الحياة
بمجرد فهمك لدورة حياة الطلب، يمكنك تحديد المرحلة التي يحدث فيها أي مشكلة بدقة—سواء كان عدم تطابق المسار، أو فشل فحص الوسائط، أو رابط مفقود في الحاوية، أو مزود فشل في التحميل.
// معرفة دورة الحياة تساعدك في تصحيح الأخطاء هكذا:
// 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) العملية الكاملة
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) ▶ مثال:عرض مراحل دورة حياة الطلب
# التحقق من مزودي الخدمات المسجلين
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()
# يعرض جميع روابط الحاوية المسجلة
الناتج:
# تم تنفيذ الأمر بنجاح
4. نواة HTTP ونواة Console
يحتوي Laravel على نواتين: نواة HTTP تتعامل مع طلبات الويب، ونواة Console تتعامل مع أوامر Artisan.
(1) تهيئة النواة في Laravel 11
// 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) ▶ مثال:وسيط عام مخصّص
// 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,
]);
})
الناتج:
// تم التنفيذ بنجاح
5. حاوية الخدمات
حاوية الخدمات هي قلب Laravel—تُدير حقن التبعيات ودورة حياة الفئات.
(1) الربط
// 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) الحل
// حل تلقائي عبر تلميح النوع
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
// 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);
}
الناتج:
// تم التنفيذ بنجاح
6. مبدأ الواجهات (Facades)
الواجهات هي "الوكلاء الثابتون" في Laravel—تستخدم صياغة استدعاء ثابتة موجزة وتعتمد على حاوية الخدمات لحل الكائنات الفعلية في الخلفية.
(1) كيف تعمل الواجهة
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) ▶ مثال:الواجهة مقابل حقن التبعيات
// استخدام الواجهة — موجز
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();
});
}
الناتج:
// تم التنفيذ بنجاح
7. آلية بدء مزود الخدمات
(1) دورة حياة المزود
يصل الطلب
← مرحلة التسجيل: تُستدعى register() لجميع المزودين (بدون بدء بعد)
← مرحلة البدء: تُستدعى boot() لجميع المزودين (جميع الروابط متاحة)
← التطبيق جاهز
(2) المزود المؤجل
// 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
// 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);
}
}
الناتج:
// تم التنفيذ بنجاح
8. مثال شامل: تتبع طلب ShopMetrics
// ============================================
// شامل: تتبع طلب 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 يبدأ الحاوية."getFacadeAccessor() لفئة الواجهة، ثم ابحث عن الربط المقابل في الحاوية. أو استخدم app('cache') لاسترداد النسخة مباشرة.Kernel.php؟bootstrap/app.php، باستخدام API متسلسل لتهيئة الوسائط والاستثناءات والمسارات. الوظائف كما هي؛ فقط التهيئة أكثر مركزية.📖 ملخص
- دورة حياة الطلب: index.php ← Kernel ← Providers ← Middleware ← Route ← Controller ← Response
- نواة HTTP تتعامل مع طلبات الويب، ونواة Console تتعامل مع أوامر Artisan
- حاوية الخدمات تُدير حقن التبعيات:
bind()يُنشئ نسخة جديدة، بينماsingleton()يعيد استخدام النسخة - الواجهة هي وكيل ثابت؛ تستخدم الحاوية لحل الكائن الفعلي في الخلفية وتدعم اختبار المحاكاة
- عملية المزود تتكون من مرحلتين: register (ربط) و boot (بدء)
- المزود المؤجل: التحميل المؤجل يقلل من نفقات بدء الخدمات غير الضرورية
📝 تمارين
-
سؤال أساسي (⭐): استخدم
php artisan aboutلعرض قائمة مزودي خدمات ShopMetrics، وحدد أيها افتراضي من الإطار وأيها مخصّص، وارسم مخطط تدفق مبسط لدورة حياة الطلب. -
تمرين متقدم (⭐⭐): أنشئ
ShopMetricsServiceProvider، واستخدمbind()لربطReportGeneratorInterfaceبـPdfReportGenerator، ثم استخدمه في المتحكم عبر حقن التبعيات. -
تحدي (⭐⭐⭐): نفّذ مزوداً مؤجلاً للتحميل الكسول لبوابة دفع Stripe. استخدم الربط المُعلن في
provides()وتحقق عبرapp()->resolved()أنه يُحمّل فعلاً عند أول استخدام فقط.



