إطار Laravel

معالجة الأخطاء والتسجيل في Laravel

15 دقيقة الدرس 15 من 45

مقدمة في معالجة الأخطاء

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

ملاحظة: يميز Laravel بين الاستثناءات (الأخطاء التي يمكن اصطيادها ومعالجتها) والأخطاء الفادحة (التي تنهي التطبيق). معالج الاستثناءات هو نقطتك المركزية لإدارة أخطاء التطبيق.

معالج الاستثناءات

تتم معالجة جميع الاستثناءات بواسطة صنف App\Exceptions\Handler:

<?php namespace App\Exceptions; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Throwable; class Handler extends ExceptionHandler { /** * قائمة أنواع الاستثناءات التي لا يتم الإبلاغ عنها. */ protected $dontReport = [ // الاستثناءات التي لا يجب تسجيلها ]; /** * قائمة المدخلات التي يجب عدم حفظها في الجلسة عند أخطاء التحقق. */ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * تسجيل callbacks معالجة الاستثناءات. */ public function register(): void { $this->reportable(function (Throwable $e) { // منطق الإبلاغ المخصص }); } }

رمي الاستثناءات

يوفر Laravel طرقاً متعددة لرمي الاستثناءات:

<?php use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Symfony\Component\HttpKernel\Exception\HttpException; // رمي استثناء أساسي throw new \Exception('حدث خطأ ما'); // الرمي مع رمز حالة HTTP باستخدام abort() abort(404); // غير موجود abort(403, 'إجراء غير مصرح به'); abort(500, 'حدث خطأ في الخادم'); // abort_if() - إيقاف مشروط abort_if(! Auth::check(), 403, 'يجب أن تكون مسجل الدخول'); abort_if($user->banned, 403, 'تم حظر حسابك'); // abort_unless() - عكس abort_if abort_unless(Auth::user()->is_admin, 403); // رمي استثناء HTTP يدوياً throw new HttpException(403, 'تم رفض الوصول'); // استثناء التحقق (يُرمى تلقائياً بواسطة التحقق) // يرجع 422 Unprocessable Entity مع رسائل الخطأ

استثناءات HTTP

يوفر Laravel اختصارات لاستجابات أخطاء HTTP الشائعة:

<?php // 400 طلب سيئ abort(400, 'تنسيق طلب غير صالح'); // 401 غير مصرح abort(401, 'المصادقة مطلوبة'); // 403 محظور abort(403, 'ليس لديك صلاحية'); // 404 غير موجود abort(404, 'المورد غير موجود'); // 419 انتهت صلاحية الصفحة (عدم تطابق رمز CSRF) abort(419); // 422 كيان غير قابل للمعالجة (أخطاء التحقق) abort(422, 'فشل التحقق'); // 429 طلبات كثيرة جداً (تحديد المعدل) abort(429, 'تجاوز حد المعدل'); // 500 خطأ داخلي في الخادم abort(500, 'حدث خطأ في الخادم'); // 503 الخدمة غير متاحة (وضع الصيانة) abort(503, 'التطبيق قيد الصيانة');
نصيحة: استخدم abort() للأخطاء المتوقعة (المستخدم غير موجود، رفض الصلاحية) وارمي استثناءات عادية للأخطاء غير المتوقعة (فشل اتصال قاعدة البيانات، خطأ API خارجي).

الاستثناءات المخصصة

أنشئ أصناف استثناءات مخصصة لسيناريوهات أخطاء محددة:

# إنشاء استثناء مخصص php artisan make:exception PaymentFailedException
<?php namespace App\Exceptions; use Exception; class PaymentFailedException extends Exception { /** * معرّف معاملة الدفع. */ protected $transactionId; /** * إنشاء نسخة استثناء جديدة. */ public function __construct($message = 'فشلت معالجة الدفع', $transactionId = null) { parent::__construct($message); $this->transactionId = $transactionId; } /** * الحصول على معرّف المعاملة. */ public function getTransactionId() { return $this->transactionId; } /** * الإبلاغ عن الاستثناء (التسجيل). */ public function report() { Log::error('فشل الدفع', [ 'transaction_id' => $this->transactionId, 'message' => $this->getMessage(), ]); } /** * عرض الاستثناء كاستجابة HTTP. */ public function render($request) { if ($request->expectsJson()) { return response()->json([ 'error' => 'فشل الدفع', 'message' => $this->getMessage(), 'transaction_id' => $this->transactionId, ], 422); } return redirect()->back() ->with('error', $this->getMessage()) ->withInput(); } } // الاستخدام throw new PaymentFailedException('تم رفض بطاقة الائتمان', $transactionId);

اصطياد ومعالجة الاستثناءات

استخدم كتل try-catch لمعالجة الاستثناءات بشكل رشيق:

<?php use App\Exceptions\PaymentFailedException; use Illuminate\Database\QueryException; use Exception; public function processPayment($orderId) { try { // محاولة معالجة الدفع $payment = PaymentGateway::charge($orderId); return response()->json([ 'success' => true, 'payment_id' => $payment->id, ]); } catch (PaymentFailedException $e) { // معالجة أخطاء الدفع المحددة Log::error('فشل الدفع: ' . $e->getMessage()); return response()->json([ 'error' => 'فشل الدفع', 'message' => $e->getMessage(), ], 422); } catch (QueryException $e) { // معالجة أخطاء قاعدة البيانات Log::error('خطأ في قاعدة البيانات: ' . $e->getMessage()); return response()->json([ 'error' => 'حدث خطأ في قاعدة البيانات', ], 500); } catch (Exception $e) { // معالجة جميع الاستثناءات الأخرى Log::error('خطأ غير متوقع: ' . $e->getMessage()); return response()->json([ 'error' => 'حدث خطأ غير متوقع', ], 500); } finally { // يتم التنفيذ دائماً (كود التنظيف) Cache::forget('processing_payment_' . $orderId); } }

صفحات الأخطاء المخصصة

أنشئ عروضاً مخصصة لرموز أخطاء HTTP في resources/views/errors/:

<!-- resources/views/errors/404.blade.php --> @extends('layouts.app') @section('content') <div class="error-page"> <h1>404 - الصفحة غير موجودة</h1> <p>الصفحة التي تبحث عنها غير موجودة.</p> <a href="{{ route('home') }}">الذهاب إلى الصفحة الرئيسية</a> </div> @endsection
<!-- resources/views/errors/403.blade.php --> @extends('layouts.app') @section('content') <div class="error-page"> <h1>403 - تم رفض الوصول</h1> <p>{{ $exception->getMessage() ?: 'ليس لديك صلاحية للوصول إلى هذا المورد.' }}</p> <a href="{{ route('home') }}">العودة</a> </div> @endsection

التسجيل في Laravel

يستخدم Laravel مكتبة Monolog القوية للتسجيل. التكوين في config/logging.php:

<?php return [ 'default' => env('LOG_CHANNEL', 'stack'), 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single', 'slack'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => env('LOG_LEVEL', 'critical'), ], 'stderr' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ 'stream' => 'php://stderr', ], ], ], ];

كتابة رسائل السجل

استخدم واجهة Log لكتابة رسائل بمستويات خطورة مختلفة:

<?php use Illuminate\Support\Facades\Log; // طارئ: النظام غير قابل للاستخدام Log::emergency('النظام متوقف'); // تنبيه: يجب اتخاذ إجراء فوري Log::alert('خادم قاعدة البيانات غير قابل للوصول'); // حرج: حالات حرجة Log::critical('مكون التطبيق غير متاح'); // خطأ: حالات الخطأ Log::error('فشلت معالجة الدفع', [ 'user_id' => $userId, 'amount' => $amount, ]); // تحذير: حالات التحذير Log::warning('الاقتراب من حد معدل API'); // ملاحظة: حالة عادية ولكن مهمة Log::notice('سجل المستخدم الدخول من عنوان IP جديد'); // معلومات: رسائل إعلامية Log::info('تم تقديم الطلب بنجاح', [ 'order_id' => $order->id, ]); // تصحيح: رسائل مستوى التصحيح Log::debug('فشل التخزين المؤقت للمفتاح: ' . $cacheKey); // التسجيل في قناة محددة Log::channel('slack')->critical('بوابة الدفع معطلة'); // التسجيل في قنوات متعددة Log::stack(['single', 'slack'])->error('حدث خطأ حرج');
ملاحظة: مستويات السجل تتبع مواصفة RFC 5424. استخدم المستويات المناسبة: emergency/alert/critical للمشاكل العاجلة، error للأخطاء، warning للتحذيرات، info للمعلومات العامة، debug للتطوير.

التسجيل السياقي

أضف سياقاً إلى رسائل السجل لتصحيح أفضل:

<?php // التسجيل مع مصفوفة سياق Log::info('إجراء المستخدم', [ 'user_id' => auth()->id(), 'action' => 'شراء', 'product_id' => $product->id, 'ip_address' => request()->ip(), 'timestamp' => now(), ]); // تسجيل الاستثناء مع تتبع المكدس try { // بعض الكود الذي قد يفشل } catch (Exception $e) { Log::error('حدث استثناء', [ 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), ]); } // التسجيل مع سياق الطلب Log::info('طلب API', [ 'method' => request()->method(), 'url' => request()->fullUrl(), 'params' => request()->all(), 'headers' => request()->headers->all(), ]);

التسجيل المشروط

سجل الرسائل فقط في بيئات أو حالات محددة:

<?php // التسجيل فقط في الإنتاج if (app()->environment('production')) { Log::error('حدث خطأ في الإنتاج'); } // التسجيل فقط عند التصحيح if (config('app.debug')) { Log::debug('معلومات التصحيح', ['data' => $data]); } // تسجيل الاستعلامات البطيئة if ($executionTime > 1000) { Log::warning('تم اكتشاف استعلام بطيء', [ 'query' => $query, 'time' => $executionTime . 'ms', ]); } // تسجيل محاولات تسجيل الدخول الفاشلة if ($loginFailed) { Log::notice('محاولة تسجيل دخول فاشلة', [ 'email' => $request->email, 'ip' => $request->ip(), ]); }

قنوات السجل المخصصة

أنشئ قنوات سجل مخصصة لأغراض محددة:

<?php // config/logging.php 'channels' => [ 'payments' => [ 'driver' => 'daily', 'path' => storage_path('logs/payments.log'), 'level' => 'info', 'days' => 30, ], 'security' => [ 'driver' => 'daily', 'path' => storage_path('logs/security.log'), 'level' => 'warning', 'days' => 90, ], 'api' => [ 'driver' => 'daily', 'path' => storage_path('logs/api.log'), 'level' => 'debug', 'days' => 7, ], ]; // الاستخدام Log::channel('payments')->info('تمت معالجة الدفع', [ 'transaction_id' => $transaction->id, 'amount' => $amount, ]); Log::channel('security')->warning('تم اكتشاف نشاط مشبوه', [ 'user_id' => $userId, 'ip' => $ip, ]); Log::channel('api')->debug('استدعاء API خارجي', [ 'endpoint' => $endpoint, 'response_time' => $responseTime, ]);

أدوات التصحيح

يوفر Laravel دوال تصحيح مفيدة:

<?php // dd() - إلقاء والموت (يوقف التنفيذ) dd($user); // يعرض مخرجات منسقة ويتوقف // dump() - الإلقاء بدون التوقف dump($user); // يعرض المخرجات ويستمر // ddd() - إلقاء، موت، وتصحيح (Laravel 9.3+) ddd($user); // dd() محسّن مع مزيد من التفاصيل // ray() - التصحيح مع Ray (يتطلب حزمة ray) ray($user); // يرسل إلى تطبيق Ray المكتبي // الإلقاء في السجل بدلاً من المتصفح Log::debug('بيانات المستخدم', ['user' => $user]); // إلقاء استعلامات SQL DB::enableQueryLog(); // تنفيذ الاستعلامات $queries = DB::getQueryLog(); dd($queries); // التصحيح في Blade @dump($variable) @dd($variable)

الإبلاغ عن الاستثناءات مع الخدمات الخارجية

التكامل مع خدمات تتبع الأخطاء لمراقبة الإنتاج:

# تثبيت Sentry (خدمة تتبع أخطاء شائعة) composer require sentry/sentry-laravel # نشر التكوين php artisan vendor:publish --provider="Sentry\Laravel\ServiceProvider" # التكوين في .env SENTRY_LARAVEL_DSN=https://your-dsn@sentry.io/project-id
<?php // app/Exceptions/Handler.php public function register(): void { $this->reportable(function (Throwable $e) { if (app()->bound('sentry')) { app('sentry')->captureException($e); } }); }
تحذير أمني: لا تسجل أبداً معلومات حساسة مثل كلمات المرور، أرقام بطاقات الائتمان، أو مفاتيح API. استخدم خاصية $dontFlash في معالج الاستثناءات لمنع تسجيل البيانات الحساسة.

تمرين 1: صنف استثناء مخصص

أنشئ استثناءً مخصصاً لحالات المخزون غير الكافي.

  1. شغل php artisan make:exception InsufficientInventoryException
  2. أضف خصائص: productId، requestedQuantity، availableQuantity
  3. نفذ constructor مخصص يقبل هذه الخصائص
  4. نفذ دالة report() للتسجيل في قناة "inventory" المخصصة
  5. نفذ دالة render() ترجع JSON للـ API أو إعادة توجيه للويب
  6. ارمي الاستثناء في OrderController عندما يكون المخزون غير كافٍ
  7. اختبر مع منتجات منخفضة المخزون

تمرين 2: تسجيل الأخطاء المركزي

قم بإعداد تسجيل أخطاء شامل مع قنوات مخصصة.

  1. أنشئ قنوات سجل مخصصة: "errors"، "security"، "performance"
  2. كوّن كل قناة بتدوير يومي والاحتفاظ
  3. أنشئ middleware لتسجيل جميع استجابات HTTP 4xx و 5xx
  4. سجل فشل المصادقة في قناة security
  5. سجل استعلامات قاعدة البيانات البطيئة (>1000ms) في قناة performance
  6. أضف معلومات سياقية (المستخدم، IP، URL) لجميع السجلات
  7. اختبر بإطلاق أخطاء متنوعة وفحص ملفات السجل

تمرين 3: صفحة 404 مخصصة مع اقتراحات

أنشئ صفحة خطأ 404 محسّنة مع اقتراحات مفيدة.

  1. أنشئ resources/views/errors/404.blade.php
  2. اعرض عنوان URL المطلوب الذي لم يُعثر عليه
  3. أظهر 5 صفحات شعبية (الأكثر زيارة) كاقتراحات
  4. أضف مربع بحث لمساعدة المستخدمين في العثور على ما يحتاجون
  5. سجل أخطاء 404 لتتبع الروابط المكسورة
  6. قم بتضمين التنقل بالفتات للعودة إلى الصفحة الرئيسية
  7. اختبر بزيارة عناوين URL غير موجودة

أفضل الممارسات

  • استخدم مستويات السجل المناسبة (لا تسجل كل شيء كخطأ)
  • أضف سياقاً لرسائل السجل لتسهيل التصحيح
  • لا تسجل أبداً بيانات حساسة (كلمات المرور، الرموز، بطاقات الائتمان)
  • استخدم التدوير اليومي للسجل لمنع ملفات السجل الضخمة
  • قم بإعداد سياسات الاحتفاظ بالسجل لحذف السجلات القديمة تلقائياً
  • أنشئ استثناءات مخصصة للأخطاء الخاصة بالمجال
  • وفر رسائل خطأ سهلة الاستخدام (إخفاء التفاصيل التقنية)
  • استخدم خدمات تتبع الأخطاء (Sentry، Bugsnag) في الإنتاج
  • راقب السجلات بانتظام وقم بإعداد تنبيهات للأخطاء الحرجة
  • اختبر مسارات معالجة الأخطاء (وليس فقط المسارات السعيدة)

الملخص

في هذا الدرس، تعلمت:

  • كيف يعمل نظام معالجة الاستثناءات في Laravel
  • رمي الاستثناءات باستخدام abort() والأصناف المخصصة
  • إنشاء واستخدام الاستثناءات المخصصة
  • اصطياد ومعالجة الاستثناءات بشكل رشيق
  • إنشاء صفحات أخطاء مخصصة لرموز حالة HTTP
  • نظام التسجيل في Laravel وتكامل Monolog
  • كتابة رسائل السجل بمستويات خطورة مختلفة
  • إضافة سياق للسجلات لتصحيح أفضل
  • إنشاء قنوات سجل مخصصة لأغراض محددة
  • استخدام أدوات التصحيح (dd، dump، ray)
  • التكامل مع خدمات تتبع الأخطاء الخارجية
  • أفضل الممارسات لمعالجة الأخطاء والتسجيل
تهانينا! لقد أكملت أساسيات إطار Laravel. استمر في الممارسة ببناء مشاريع حقيقية واستكشاف مواضيع متقدمة مثل الطوابير، البث، والاختبار.