أساسيات PHP

رفع الملفات

13 دقيقة الدرس 19 من 45

رفع الملفات

تسهل PHP التعامل مع رفع الملفات من المستخدمين. هذا ضروري للتطبيقات التي تحتاج إلى صور الملف الشخصي ورفع المستندات وملفات الوسائط والمزيد.

تنبيه أمني: رفع الملفات يمثل خطرًا أمنيًا كبيرًا إذا لم يتم التعامل معه بشكل صحيح. تحقق دائمًا من نوع الملف وحجمه ومحتواه. لا تثق أبدًا في إدخال المستخدم!

المتغير العمومي الفائق $_FILES

عند رفع الملفات، يتم تخزينها في مصفوفة المتغير العمومي الفائق $_FILES بالبنية التالية:

<?php // بنية $_FILES لإدخال ملف اسمه "photo" $_FILES['photo']['name'] // اسم الملف الأصلي $_FILES['photo']['type'] // نوع MIME (مثل، "image/jpeg") $_FILES['photo']['size'] // حجم الملف بالبايتات $_FILES['photo']['tmp_name'] // الموقع المؤقت على الخادم $_FILES['photo']['error'] // رمز الخطأ (0 = لا يوجد خطأ) ?>

نموذج رفع أساسي

النماذج التي ترفع الملفات يجب أن تتضمن سمة enctype:

<!DOCTYPE html> <html> <head> <title>رفع ملف</title> </head> <body> <h1>رفع ملف</h1> <!-- مهم: enctype="multipart/form-data" مطلوب --> <form method="post" action="upload.php" enctype="multipart/form-data"> <label for="file">اختر ملف:</label> <input type="file" id="file" name="photo"> <button type="submit">رفع</button> </form> </body> </html>
حاسم: قم دائمًا بتضمين enctype="multipart/form-data" في النماذج مع رفع الملفات، وإلا لن يتم إرسال الملفات.

معالج رفع ملف أساسي

<?php // upload.php if ($_SERVER['REQUEST_METHOD'] === 'POST') { // التحقق من رفع الملف if (isset($_FILES['photo']) && $_FILES['photo']['error'] === 0) { // تفاصيل الملف $filename = $_FILES['photo']['name']; $temp_path = $_FILES['photo']['tmp_name']; $file_size = $_FILES['photo']['size']; $file_type = $_FILES['photo']['type']; // تعيين دليل الرفع $upload_dir = 'uploads/'; $destination = $upload_dir . $filename; // نقل الملف من الموقع المؤقت إلى الوجهة if (move_uploaded_file($temp_path, $destination)) { echo "تم رفع الملف بنجاح: $filename"; } else { echo "خطأ في رفع الملف."; } } else { echo "لم يتم رفع ملف أو حدث خطأ."; } } ?>

رموز أخطاء رفع الملفات

<?php // فحص أخطاء الرفع $error = $_FILES['photo']['error']; switch ($error) { case UPLOAD_ERR_OK: echo "لا يوجد خطأ"; break; case UPLOAD_ERR_INI_SIZE: echo "الملف يتجاوز upload_max_filesize في php.ini"; break; case UPLOAD_ERR_FORM_SIZE: echo "الملف يتجاوز MAX_FILE_SIZE في نموذج HTML"; break; case UPLOAD_ERR_PARTIAL: echo "تم رفع الملف جزئيًا فقط"; break; case UPLOAD_ERR_NO_FILE: echo "لم يتم رفع أي ملف"; break; case UPLOAD_ERR_NO_TMP_DIR: echo "مجلد مؤقت مفقود"; break; case UPLOAD_ERR_CANT_WRITE: echo "فشل في كتابة الملف على القرص"; break; default: echo "خطأ غير معروف"; } ?>

رفع ملف آمن مع التحقق

<?php // secure_upload.php $errors = []; $success = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_FILES['photo']) && $_FILES['photo']['error'] === 0) { $file = $_FILES['photo']; // الحصول على معلومات الملف $filename = $file['name']; $temp_path = $file['tmp_name']; $file_size = $file['size']; $file_type = $file['type']; // الحصول على امتداد الملف $file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); // 1. التحقق من امتداد الملف $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif']; if (!in_array($file_ext, $allowed_extensions)) { $errors[] = "نوع ملف غير صحيح. المسموح: " . implode('، ', $allowed_extensions); } // 2. التحقق من نوع MIME $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif']; if (!in_array($file_type, $allowed_mime_types)) { $errors[] = "نوع MIME غير صحيح"; } // 3. التحقق من حجم الملف (الحد الأقصى 5 ميجابايت) $max_size = 5 * 1024 * 1024; // 5 ميجابايت بالبايتات if ($file_size > $max_size) { $errors[] = "الملف كبير جدًا. الحد الأقصى: 5 ميجابايت"; } // 4. التحقق من أنه صورة فعلية $image_info = getimagesize($temp_path); if ($image_info === false) { $errors[] = "الملف ليس صورة صالحة"; } // إذا لم توجد أخطاء، المتابعة مع الرفع if (empty($errors)) { // إنشاء اسم ملف فريد لمنع الكتابة فوق $new_filename = uniqid('img_', true) . '.' . $file_ext; // تعيين دليل الرفع $upload_dir = 'uploads/'; // إنشاء الدليل إذا لم يكن موجودًا if (!is_dir($upload_dir)) { mkdir($upload_dir, 0755, true); } $destination = $upload_dir . $new_filename; // نقل الملف المرفوع if (move_uploaded_file($temp_path, $destination)) { $success = true; $uploaded_file = $new_filename; } else { $errors[] = "فشل في نقل الملف المرفوع"; } } } else { $error_code = $_FILES['photo']['error']; if ($error_code === UPLOAD_ERR_NO_FILE) { $errors[] = "يرجى اختيار ملف"; } else { $errors[] = "رمز خطأ الرفع: $error_code"; } } } ?> <!DOCTYPE html> <html> <head> <title>رفع ملف آمن</title> <style> .error { color: red; } .success { color: green; } img { max-width: 300px; margin-top: 10px; } </style> </head> <body> <h1>رفع صورة</h1> <?php if (!empty($errors)): ?> <div class="error"> <h3>أخطاء:</h3> <ul> <?php foreach ($errors as $error): ?> <li><?php echo $error; ?></li> <?php endforeach; ?> </ul> </div> <?php endif; ?> <?php if ($success): ?> <div class="success"> <h3>تم الرفع بنجاح!</h3> <p>اسم الملف: <?php echo htmlspecialchars($uploaded_file); ?></p> <img src="uploads/<?php echo htmlspecialchars($uploaded_file); ?>" alt="صورة مرفوعة"> </div> <?php endif; ?> <form method="post" action="" enctype="multipart/form-data"> <label for="photo">اختر صورة (JPG، PNG، GIF - الحد الأقصى 5 ميجابايت):</label> <input type="file" id="photo" name="photo" accept="image/*"> <button type="submit">رفع</button> </form> </body> </html>

رفع ملفات متعددة

<!-- نموذج HTML لملفات متعددة --> <form method="post" action="upload_multiple.php" enctype="multipart/form-data"> <label>اختر صورًا متعددة:</label> <input type="file" name="photos[]" multiple accept="image/*"> <button type="submit">رفع الكل</button> </form>
<?php // upload_multiple.php if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_FILES['photos'])) { $upload_dir = 'uploads/'; $uploaded_files = []; $errors = []; // عد عدد الملفات $file_count = count($_FILES['photos']['name']); // المرور عبر كل ملف for ($i = 0; $i < $file_count; $i++) { // التحقق من أن الملف ليس به خطأ if ($_FILES['photos']['error'][$i] === 0) { $filename = $_FILES['photos']['name'][$i]; $temp_path = $_FILES['photos']['tmp_name'][$i]; $file_size = $_FILES['photos']['size'][$i]; $file_ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $allowed = ['jpg', 'jpeg', 'png', 'gif']; // التحقق if (!in_array($file_ext, $allowed)) { $errors[] = "$filename: نوع ملف غير صحيح"; continue; } if ($file_size > 5 * 1024 * 1024) { $errors[] = "$filename: الملف كبير جدًا"; continue; } // إنشاء اسم ملف فريد $new_filename = uniqid('img_', true) . '.' . $file_ext; $destination = $upload_dir . $new_filename; // رفع الملف if (move_uploaded_file($temp_path, $destination)) { $uploaded_files[] = $new_filename; } else { $errors[] = "$filename: فشل الرفع"; } } } echo "<h3>تم رفع " . count($uploaded_files) . " ملفات</h3>"; foreach ($uploaded_files as $file) { echo "<p>$file</p>"; } if (!empty($errors)) { echo "<h3>أخطاء:</h3>"; foreach ($errors as $error) { echo "<p style='color:red;'>$error</p>"; } } } } ?>

دوال مساعدة لرفع الملفات

<?php // دالة مساعدة لتنسيق حجم الملف function formatFileSize($bytes) { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2) . ' جيجابايت'; } elseif ($bytes >= 1048576) { return number_format($bytes / 1048576, 2) . ' ميجابايت'; } elseif ($bytes >= 1024) { return number_format($bytes / 1024, 2) . ' كيلوبايت'; } else { return $bytes . ' بايت'; } } // دالة مساعدة لإنشاء اسم ملف آمن function generateSafeFilename($original_name) { $ext = strtolower(pathinfo($original_name, PATHINFO_EXTENSION)); return uniqid('file_', true) . '.' . $ext; } // دالة مساعدة للتحقق من الصورة function validateImage($file) { $errors = []; // التحقق من وجود الملف if (!isset($file) || $file['error'] !== 0) { $errors[] = "خطأ في رفع الملف"; return $errors; } // التحقق من الامتداد $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp']; if (!in_array($ext, $allowed)) { $errors[] = "امتداد ملف غير صحيح"; } // التحقق من نوع MIME $allowed_mime = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!in_array($file['type'], $allowed_mime)) { $errors[] = "نوع MIME غير صحيح"; } // التحقق من الحجم (5 ميجابايت) if ($file['size'] > 5 * 1024 * 1024) { $errors[] = "الملف كبير جدًا (الحد الأقصى 5 ميجابايت)"; } // التحقق من أنها صورة حقيقية if (getimagesize($file['tmp_name']) === false) { $errors[] = "الملف ليس صورة صالحة"; } return $errors; } // الاستخدام if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors = validateImage($_FILES['photo']); if (empty($errors)) { $new_filename = generateSafeFilename($_FILES['photo']['name']); $destination = 'uploads/' . $new_filename; if (move_uploaded_file($_FILES['photo']['tmp_name'], $destination)) { echo "تم الرفع: $new_filename (" . formatFileSize($_FILES['photo']['size']) . ")"; } } else { foreach ($errors as $error) { echo "<p style='color:red;'>$error</p>"; } } } ?>

معالجة الصور (تغيير الحجم)

<?php // دالة لتغيير حجم الصورة المرفوعة function resizeImage($source, $destination, $max_width, $max_height) { // الحصول على الأبعاد الأصلية list($orig_width, $orig_height, $type) = getimagesize($source); // حساب الأبعاد الجديدة $ratio = min($max_width / $orig_width, $max_height / $orig_height); $new_width = intval($orig_width * $ratio); $new_height = intval($orig_height * $ratio); // إنشاء صورة من المصدر بناءً على النوع switch ($type) { case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($source); break; case IMAGETYPE_PNG: $image = imagecreatefrompng($source); break; case IMAGETYPE_GIF: $image = imagecreatefromgif($source); break; default: return false; } // إنشاء صورة جديدة $new_image = imagecreatetruecolor($new_width, $new_height); // الحفاظ على الشفافية لـ PNG/GIF if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF) { imagealphablending($new_image, false); imagesavealpha($new_image, true); $transparent = imagecolorallocatealpha($new_image, 0, 0, 0, 127); imagefill($new_image, 0, 0, $transparent); } // تغيير الحجم imagecopyresampled($new_image, $image, 0, 0, 0, 0, $new_width, $new_height, $orig_width, $orig_height); // حفظ الصورة المُعدلة الحجم switch ($type) { case IMAGETYPE_JPEG: imagejpeg($new_image, $destination, 90); break; case IMAGETYPE_PNG: imagepng($new_image, $destination, 9); break; case IMAGETYPE_GIF: imagegif($new_image, $destination); break; } // تحرير الذاكرة imagedestroy($image); imagedestroy($new_image); return true; } // الاستخدام بعد الرفع if (move_uploaded_file($_FILES['photo']['tmp_name'], $destination)) { // إنشاء صورة مصغرة $thumbnail = 'uploads/thumb_' . $new_filename; resizeImage($destination, $thumbnail, 300, 300); echo "تم رفع الصورة وإنشاء صورة مصغرة"; } ?>

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

قائمة مراجعة أمان رفع الملفات:
  • تحقق دائمًا من امتداد الملف ونوع MIME
  • استخدم getimagesize() للتحقق من الصور الحقيقية
  • حدد حدودًا لحجم الملف الأقصى
  • أنشئ أسماء ملفات فريدة (استخدم uniqid())
  • احفظ الرفوعات خارج جذر الويب عندما يكون ذلك ممكنًا
  • لا تنفذ أبدًا الملفات المرفوعة
  • عيّن أذونات الملفات الصحيحة (0644 للملفات)
  • تحقق من محتوى الملف، وليس الامتداد فقط
  • استخدم move_uploaded_file() (يتحقق من الملف)
  • افحص الملفات بمضاد فيروسات إذا أمكن
الثغرات الشائعة:
  • الثقة في اسم الملف المقدم من المستخدم (أعد تسمية الملفات!)
  • عدم التحقق من نوع MIME (يمكن تزويره)
  • السماح بأنواع ملفات قابلة للتنفيذ (.php، .exe)
  • تخزين الرفوعات في أدلة يمكن الوصول إليها عبر الويب بدون حماية
  • عدم تحديد حجم الملف (هجوم DoS)

تمرين تطبيقي

المهمة: أنشئ نظام رفع صورة الملف الشخصي مع:

  1. نموذج رفع يقبل الصور فقط (JPG، PNG، GIF)
  2. التحقق من نوع الملف ونوع MIME والحجم (الحد الأقصى 2 ميجابايت)
  3. إنشاء اسم ملف فريد مع طابع زمني
  4. إنشاء نسختين: أصلية وصورة مصغرة (150×150)
  5. عرض كلتا النسختين بعد الرفع
  6. تخزين الملفات في دليل "uploads/"
  7. إظهار حجم الملف بصيغة قابلة للقراءة

إضافي: أضف القدرة على حذف الرفوعات السابقة وتطبيق واجهة رفع السحب والإفلات.

الملخص

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

  • بنية المتغير العمومي الفائق $_FILES
  • إنشاء نماذج رفع مع enctype الصحيح
  • معالجة رفع الملفات الأساسية باستخدام move_uploaded_file()
  • رموز أخطاء الرفع والمعالجة
  • التحقق الآمن من الملفات (الامتداد، نوع MIME، الحجم، المحتوى)
  • إنشاء أسماء ملفات فريدة
  • التعامل مع رفع ملفات متعددة
  • دوال مساعدة لعمليات الملفات
  • تغيير حجم الصور وإنشاء صور مصغرة
  • أفضل ممارسات الأمان الحاسمة

بعد ذلك، سنستكشف الجلسات والكوكيز في PHP!