أساسيات PHP
الاستثناءات و Try-Catch
الاستثناءات و Try-Catch
توفر الاستثناءات طريقة قوية لمعالجة الأخطاء في PHP. تسمح لك بفصل كود معالجة الأخطاء عن الكود العادي، مما يجعل تطبيقاتك أكثر قابلية للصيانة وقوة.
ما هي الاستثناءات؟
الاستثناءات هي كائنات تمثل أخطاء أو حالات استثنائية. عندما يحدث خطأ، يتم "رمي" استثناء ويمكن "التقاطه" بواسطة معالجات الاستثناءات.
<?php
// البنية الأساسية للاستثناء
try {
// كود قد يرمي استثناء
throw new Exception("حدث خطأ ما!");
} catch (Exception $e) {
// معالجة الاستثناء
echo "تم التقاط استثناء: " . $e->getMessage();
}
// الناتج: تم التقاط استثناء: حدث خطأ ما!
?>
ملاحظة: على عكس الأخطاء، يمكن التقاط الاستثناءات ومعالجتها بشكل سلس، مما يسمح لتطبيقك بالتعافي أو الفشل بشكل منظم.
كتل Try-Catch-Finally
توفر بنية try-catch-finally تحكماً كاملاً في معالجة الاستثناءات:
<?php
function readConfigFile($filename) {
$file = null;
try {
// محاولة فتح الملف
if (!file_exists($filename)) {
throw new Exception("ملف الإعدادات غير موجود: $filename");
}
$file = fopen($filename, 'r');
if ($file === false) {
throw new Exception("تعذر فتح الملف: $filename");
}
$content = fread($file, filesize($filename));
return $content;
} catch (Exception $e) {
// معالجة الاستثناء
error_log($e->getMessage());
return false;
} finally {
// يتم تنفيذه دائماً، حتى لو حدث استثناء
if ($file !== null) {
fclose($file);
}
echo "اكتمل التنظيف";
}
}
$config = readConfigFile('config.ini');
?>
نصيحة: كتلة finally مثالية لعمليات التنظيف مثل إغلاق الملفات أو اتصالات قاعدة البيانات، حيث يتم تنفيذها بغض النظر عما إذا تم رمي استثناء أم لا.
خصائص وطرق الاستثناء
كائنات الاستثناء لديها خصائص وطرق مفيدة لتصحيح الأخطاء:
<?php
try {
throw new Exception("استثناء تجريبي", 100);
} catch (Exception $e) {
echo "الرسالة: " . $e->getMessage() . "\n"; // رسالة الخطأ
echo "الكود: " . $e->getCode() . "\n"; // كود الخطأ
echo "الملف: " . $e->getFile() . "\n"; // الملف حيث تم الرمي
echo "السطر: " . $e->getLine() . "\n"; // رقم السطر
echo "التتبع: " . $e->getTraceAsString() . "\n"; // تتبع المكدس
echo "النص: " . $e->__toString() . "\n"; // معلومات الاستثناء الكاملة
}
// الناتج:
// الرسالة: استثناء تجريبي
// الكود: 100
// الملف: /path/to/file.php
// السطر: 3
// التتبع: #0 {main}
// النص: Exception: استثناء تجريبي في /path/to/file.php:3
?>
فئات الاستثناءات المخصصة
يمكنك إنشاء فئات استثناءات مخصصة لأنواع أخطاء معينة:
<?php
// استثناء مخصص أساسي
class DatabaseException extends Exception {}
// استثناءات قاعدة بيانات محددة
class ConnectionException extends DatabaseException {}
class QueryException extends DatabaseException {}
// الاستخدام
class Database {
private $connection;
public function connect($host, $username, $password) {
$this->connection = @mysqli_connect($host, $username, $password);
if (!$this->connection) {
throw new ConnectionException(
"فشل الاتصال: " . mysqli_connect_error(),
mysqli_connect_errno()
);
}
}
public function query($sql) {
if (!$this->connection) {
throw new ConnectionException("لا يوجد اتصال بقاعدة البيانات");
}
$result = mysqli_query($this->connection, $sql);
if ($result === false) {
throw new QueryException(
"فشل الاستعلام: " . mysqli_error($this->connection)
);
}
return $result;
}
}
// معالجة الاستثناءات المخصصة
try {
$db = new Database();
$db->connect('localhost', 'user', 'wrong_password');
$result = $db->query('SELECT * FROM users');
} catch (ConnectionException $e) {
echo "خطأ في الاتصال: " . $e->getMessage();
} catch (QueryException $e) {
echo "خطأ في الاستعلام: " . $e->getMessage();
} catch (DatabaseException $e) {
echo "خطأ في قاعدة البيانات: " . $e->getMessage();
}
?>
ملاحظة: التقط الاستثناءات الأكثر تحديداً قبل العامة. تطابق PHP كتل catch بالترتيب من الأعلى إلى الأسفل.
كتل Catch متعددة
يمكنك التقاط أنواع مختلفة من الاستثناءات بمعالجات مختلفة:
<?php
class ValidationException extends Exception {}
class FileException extends Exception {}
function processUpload($file) {
try {
// التحقق من الملف
if (empty($file['name'])) {
throw new ValidationException("لم يتم رفع ملف");
}
if ($file['size'] > 1000000) {
throw new ValidationException("الملف كبير جداً");
}
// معالجة الملف
if (!move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name'])) {
throw new FileException("فشل نقل الملف المرفوع");
}
return true;
} catch (ValidationException $e) {
// معالجة أخطاء التحقق - إظهار للمستخدم
echo "<div class='error'>" . $e->getMessage() . "</div>";
return false;
} catch (FileException $e) {
// معالجة أخطاء الملف - تسجيل وإظهار رسالة عامة
error_log($e->getMessage());
echo "<div class='error'>فشل الرفع. يرجى المحاولة مرة أخرى.</div>";
return false;
}
}
?>
إعادة رمي الاستثناءات
يمكنك التقاط استثناء، تنفيذ إجراء ما، ثم إعادة رميه:
<?php
function processPayment($amount) {
try {
if ($amount <= 0) {
throw new Exception("مبلغ غير صحيح");
}
// منطق معالجة الدفع هنا
// إذا فشل الدفع:
throw new Exception("خطأ في بوابة الدفع");
} catch (Exception $e) {
// تسجيل الخطأ
error_log("فشل الدفع: " . $e->getMessage());
// إعادة الرمي للمعالجة على مستوى أعلى
throw new Exception("فشلت معالجة الدفع", 0, $e);
}
}
try {
processPayment(-50);
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage();
// الحصول على الاستثناء السابق
if ($e->getPrevious()) {
echo "\nالخطأ الأصلي: " . $e->getPrevious()->getMessage();
}
}
?>
نصيحة: إعادة رمي الاستثناءات مفيدة عندما تريد تسجيل الأخطاء على مستويات متعددة أو إضافة سياق لرسائل الأخطاء.
التقاط أنواع استثناءات متعددة (PHP 7.1+)
يمكنك التقاط أنواع استثناءات متعددة في كتلة catch واحدة:
<?php
class NetworkException extends Exception {}
class TimeoutException extends Exception {}
class PermissionException extends Exception {}
try {
// عملية قد ترمي استثناءات مختلفة
throw new NetworkException("فُقد الاتصال");
} catch (NetworkException | TimeoutException $e) {
// معالجة أخطاء الشبكة والمهلة بنفس الطريقة
echo "مشكلة في الاتصال: " . $e->getMessage();
// منطق إعادة المحاولة هنا
} catch (PermissionException $e) {
// معالجة أخطاء الصلاحيات بشكل مختلف
echo "تم رفض الوصول: " . $e->getMessage();
}
?>
أفضل ممارسات الاستثناءات
<?php
// 1. استخدم أنواع استثناءات محددة
class UserNotFoundException extends Exception {}
class InvalidEmailException extends Exception {}
// 2. قدم رسائل واضحة
throw new InvalidEmailException(
"البريد الإلكتروني '$email' غير صحيح. التنسيق المتوقع: user@domain.com"
);
// 3. قم بتضمين أكواد أخطاء للتصنيف
throw new Exception("خطأ في قاعدة البيانات", 1001);
// 4. لا تلتقط الاستثناءات التي لا يمكنك معالجتها
try {
$result = riskyOperation();
} catch (SpecificException $e) {
// التقط فقط إذا كان بإمكانك فعل شيء مفيد
handleError($e);
}
// اترك الاستثناءات الأخرى تنتشر للأعلى
// 5. سجل الاستثناءات دائماً في الإنتاج
try {
criticalOperation();
} catch (Exception $e) {
error_log($e->getMessage() . "\n" . $e->getTraceAsString());
throw $e; // إعادة الرمي بعد التسجيل
}
?>
تحويل الأخطاء إلى استثناءات
يمكنك تحويل أخطاء PHP إلى استثناءات لمعالجة متسقة للأخطاء:
<?php
// معالج أخطاء يحول الأخطاء إلى استثناءات
function errorToException($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler('errorToException');
try {
// هذا سيولد عادة تحذير
$result = 10 / 0;
} catch (ErrorException $e) {
echo "تم التقاط خطأ كاستثناء: " . $e->getMessage();
}
restore_error_handler(); // استعادة معالج الأخطاء الافتراضي
?>
معالج الاستثناءات العام
عيّن معالج استثناءات عام للاستثناءات غير الملتقطة:
<?php
function globalExceptionHandler($exception) {
// تسجيل الاستثناء
error_log("استثناء غير ملتقط: " . $exception->getMessage());
error_log($exception->getTraceAsString());
// إظهار صفحة خطأ ودية للمستخدم
if (php_sapi_name() !== 'cli') {
http_response_code(500);
echo "<h1>حدث خطأ ما</h1>";
echo "<p>نأسف للإزعاج. يرجى المحاولة لاحقاً.</p>";
} else {
echo "خطأ: " . $exception->getMessage() . "\n";
}
}
set_exception_handler('globalExceptionHandler');
// أي استثناء غير ملتقط سيتم معالجته بواسطة globalExceptionHandler
throw new Exception("سيتم التقاط هذا بواسطة المعالج العام");
?>
تحذير: لا تعرض أبداً معلومات استثناء مفصلة (تتبعات المكدس، مسارات الملفات) للمستخدمين في الإنتاج. سجل التفاصيل دائماً واعرض رسائل خطأ عامة.
مثال عملي على معالجة الاستثناءات
<?php
class UserService {
private $db;
public function registerUser($email, $password) {
try {
// التحقق من المدخلات
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new ValidationException("تنسيق بريد إلكتروني غير صحيح");
}
if (strlen($password) < 8) {
throw new ValidationException("كلمة المرور يجب أن تكون 8 أحرف على الأقل");
}
// التحقق من وجود المستخدم
$existing = $this->db->query("SELECT id FROM users WHERE email = ?", [$email]);
if ($existing) {
throw new ValidationException("البريد الإلكتروني مسجل بالفعل");
}
// إنشاء المستخدم
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$this->db->query(
"INSERT INTO users (email, password) VALUES (?, ?)",
[$email, $hashedPassword]
);
return true;
} catch (ValidationException $e) {
// إظهار أخطاء التحقق للمستخدم
throw $e;
} catch (DatabaseException $e) {
// تسجيل أخطاء قاعدة البيانات، إظهار رسالة عامة
error_log("فشل التسجيل: " . $e->getMessage());
throw new Exception("فشل التسجيل. يرجى المحاولة مرة أخرى.");
} catch (Exception $e) {
// التقاط شامل للأخطاء غير المتوقعة
error_log("خطأ غير متوقع في registerUser: " . $e->getMessage());
throw new Exception("حدث خطأ غير متوقع");
}
}
}
// الاستخدام
try {
$userService = new UserService();
$userService->registerUser('user@example.com', 'password123');
echo "تم التسجيل بنجاح!";
} catch (Exception $e) {
echo "خطأ: " . $e->getMessage();
}
?>
تمرين:
- أنشئ فئة رفع ملفات ترمي استثناءات مخصصة لظروف خطأ مختلفة
- اكتب دالة تقرأ JSON من ملف وترمي استثناءات لملف غير موجود، JSON غير صحيح، إلخ
- نفّذ فئة حاسبة مع كتل try-catch تعالج القسمة على صفر
- أنشئ معالج استثناءات عام يسجل الأخطاء ويعرض رسائل مناسبة بناءً على البيئة (تطوير مقابل إنتاج)
أنماط الاستثناءات الشائعة
<?php
// النمط 1: الفشل السريع بالاستثناءات
function processOrder($orderId) {
if (empty($orderId)) {
throw new InvalidArgumentException("معرّف الطلب مطلوب");
}
// متابعة المعالجة...
}
// النمط 2: سلسلة الاستثناءات للسياق
try {
connectToAPI();
} catch (Exception $e) {
throw new ApiException("فشل الاتصال بواجهة برمجة تطبيقات الدفع", 0, $e);
}
// النمط 3: تنظيف الموارد مع finally
function downloadFile($url) {
$handle = null;
try {
$handle = fopen($url, 'r');
return stream_get_contents($handle);
} finally {
if ($handle) {
fclose($handle);
}
}
}
?>