تحميل الملفات في PHP
صور الملف الشخصي، ومرفقات السيرة الذاتية، ومشاركة الصور — كل تطبيق ويب حقيقي يتعامل مع عمليات تحميل الملفات. وتجعل لغة PHP هذه العملية بسيطة، لكن أي تفصيل أمني واحد يتم تجاهله قد يؤدي إلى ظهور ثغرة خطيرة.
1. المتطلبات الأساسية
(1) إعداد نموذج HTML المطلوب
HTML
<!-- The three requirements for a file upload form -->
<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
| المتطلب | السبب |
|---|---|
method="POST" |
يجب إرسال الملفات عبر طريقة POST |
enctype="multipart/form-data" |
مطلوب! بدونه، يتم إرسال اسم الملف فقط |
type="file" |
يطلب من المتصفح عرض أداة اختيار الملفات |
🔥 خطأ شائع: نسيان
enctype="multipart/form-data" هو الخطأ الأكثر شيوعًا عند تحميل الملفات — حيث يتلقى PHP اسم الملف دون أن يتلقى محتوياته أبدًا.
(2) إنشاء مجلد التحميل
داخل myphp/، أنشئ مجلدًا باسم uploads/ وتأكد من أن PHP يمكنه الكتابة فيه:
PHP
<?php
// Automatically create the upload directory from your script
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
?>
2. نظرة متعمقة على $_FILES
PHP
<?php
// After uploading a file, $_FILES is structured like this:
print_r($_FILES['avatar']);
/*
Array (
[name] => photo.jpg // Original filename
[type] => image/jpeg // MIME type reported by the browser
[tmp_name] => C:\xampp\tmp\php123.tmp // Temporary path on the server
[error] => 0 // Error code (0 = success)
[size] => 85469 // File size in bytes
)
*/
?>
| المفتاح | المعنى | ملاحظة |
|---|---|---|
name |
اسم الملف الأصلي | غير موثوق — يمكن للمستخدمين تزويره |
type |
نوع MIME | وفقًا للمتصفح، غير موثوق |
tmp_name |
ملف مؤقت للخادم | يتم حذفه تلقائيًا عند انتهاء البرنامج النصي |
error |
رمز الخطأ | 0 = نجاح، أي قيمة أخرى = مشكلة |
size |
الحجم بالبايت | مفيد لفرض حدود الحجم |
(1) رموز الأخطاء
| الرمز | الثابت | المعنى |
|---|---|---|
| 0 | UPLOAD_ERR_OK |
✅ تم التحميل بنجاح |
| 1 | UPLOAD_ERR_INI_SIZE |
يتجاوز القيمة المحددة في ملف php.ini upload_max_filesize |
| 2 | UPLOAD_ERR_FORM_SIZE |
يتجاوز MAX_FILE_SIZE الخاص بالنموذج |
| 3 | UPLOAD_ERR_PARTIAL |
تم تحميل الملف جزئيًا فقط |
| 4 | UPLOAD_ERR_NO_FILE |
لم يتم تحديد أي ملف |
| 6 | UPLOAD_ERR_NO_TMP_DIR |
لم يتم تكوين مجلد مؤقت |
| 7 | UPLOAD_ERR_CANT_WRITE |
فشل في الكتابة على القرص |
3. move_uploaded_file() — الدالة الأساسية
يخزن PHP الملفات التي تم تحميلها في دليل مؤقت. يجب عليك نقلها صراحةً إلى موقع دائم، وإلا فستُفقد عند انتهاء البرنامج النصي:
PHP
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['avatar'];
$uploadDir = __DIR__ . '/uploads/';
if ($file['error'] !== UPLOAD_ERR_OK) {
echo "Upload failed. Error code: " . $file['error'];
} else {
$targetPath = $uploadDir . $file['name'];
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "Upload successful! File saved at: {$targetPath}";
} else {
echo "Failed to move the uploaded file";
}
}
}
?>
▶ مثال: التحميل الأساسي للملفات مع معالجة الأخطاء
PHP
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['avatar'];
$uploadDir = __DIR__ . '/uploads/';
if ($file['error'] === UPLOAD_ERR_OK) {
$targetPath = $uploadDir . $file['name'];
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "<p style='color:green'>Upload successful!</p>";
}
} elseif ($file['error'] === UPLOAD_ERR_NO_FILE) {
echo "<p style='color:red'>Please select a file</p>";
} else {
echo "<p style='color:red'>Upload failed. Error code: {$file['error']}</p>";
}
}
?>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
4. التحقق من الأمان (أمر بالغ الأهمية!)
(1) 4.1 الحد الأقصى لحجم الملف
PHP
<?php
$maxSize = 2 * 1024 * 1024; // 2MB
if ($file['size'] > $maxSize) {
die("File too large — maximum 2MB allowed");
}
?>
(2) 4.2 القائمة البيضاء للموسعات
PHP
<?php
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
die("File type not allowed. Accepted: " . implode(', ', $allowed));
}
?>
💡 نصيحة: استخدم دائمًا قائمة بيضاء (لا تسمح إلا بالأنواع المعروفة بأنها آمنة)، ولا تستخدم أبدًا قائمة سوداء (تحظر ملفات .php و.exe وما إلى ذلك). فالمهاجمون سيجدون دائمًا امتدادًا لم تتضمنه قائمتك السوداء.
(3) 4.3 إنشاء اسم ملف آمن
PHP
<?php
// ❌ Using the user-supplied filename directly is dangerous
// $targetPath = $uploadDir . $file['name'];
// ✅ Generate a unique, safe filename
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$safeName = uniqid('img_') . '.' . $ext;
$targetPath = $uploadDir . $safeName;
?>
▶ مثال: وظيفة التحميل الآمن الكاملة
PHP
<?php
function handleUpload(array $file, string $uploadDir): array {
$maxSize = 2 * 1024 * 1024; // 2MB
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
// 1. Check for upload errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Upload failed'];
}
// 2. Check file size
if ($file['size'] > $maxSize) {
return ['success' => false, 'message' => 'File must not exceed 2MB'];
}
// 3. Check extension (whitelist)
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
return ['success' => false, 'message' => 'Only image files are allowed'];
}
// 4. Verify the real file type (Magic Bytes — far more reliable than extension)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mimeType, $allowedMimes)) {
return ['success' => false, 'message' => 'File type mismatch'];
}
// 5. Generate a safe filename
$safeName = uniqid('upload_') . '.' . $ext;
$targetPath = $uploadDir . $safeName;
// 6. Move the file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return [
'success' => true,
'message' => 'Upload successful',
'path' => $targetPath,
'name' => $safeName,
];
}
return ['success' => false, 'message' => 'Failed to save the file'];
}
// Usage
$result = handleUpload($_FILES['avatar'], __DIR__ . '/uploads/');
echo $result['message'];
?>
5. تحميل ملفات متعددة
PHP
<form method="POST" enctype="multipart/form-data">
<input type="file" name="photos[]" multiple>
<button type="submit">Upload Multiple</button>
</form>
PHP
<?php
$uploadDir = __DIR__ . '/uploads/';
$results = [];
foreach ($_FILES['photos']['name'] as $i => $name) {
// Reassemble into a single-file array
$singleFile = [
'name' => $_FILES['photos']['name'][$i],
'type' => $_FILES['photos']['type'][$i],
'tmp_name' => $_FILES['photos']['tmp_name'][$i],
'error' => $_FILES['photos']['error'][$i],
'size' => $_FILES['photos']['size'][$i],
];
$results[] = handleUpload($singleFile, $uploadDir);
}
foreach ($results as $r) {
echo $r['message'] . "<br>";
}
?>
💡 نصيحة: يخزن لغة PHP الملفات المتعددة في بنية «عمودية» — حيث تُخزَّن جميع الأسماء في مصفوفة واحدة، وجميع الأحجام في مصفوفة أخرى، وهكذا. ويجب عليك إعادة تجميعها يدويًّا في مصفوفات منفصلة لكل ملف على حدة من أجل معالجتها.
6. عرض الصور التي تم تحميلها
PHP
<?php
// List images in the uploads directory
$images = glob(__DIR__ . '/uploads/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE);
?>
<h3>Uploaded Images</h3>
<div style="display:flex;flex-wrap:wrap;gap:10px">
<?php foreach ($images as $img): ?>
<img src="uploads/<?= basename($img) ?>" width="150">
<?php endforeach; ?>
</div>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
❓ أسئلة شائعة
س ما الفرق بين
move_uploaded_file وcopy؟ج
move_uploaded_file يتحقق من أن الملف قد وصل فعليًّا عبر HTTP POST (آمن). أما copy() العادي فيمكن استغلاله لنسخ ملفات عشوائية على الخادم. استخدم دائمًا move_uploaded_file() لعمليات التحميل.س تستمر الملفات الكبيرة في الفشل. ما المشكلة؟
ج تحقق من ثلاثة حدود: (1)
upload_max_filesize في ملف php.ini (القيمة الافتراضية 2M)؛ (2) post_max_size (يجب أن تكون أكبر من upload_max_filesize)؛ (3) الحقل المخفي الاختياري MAX_FILE_SIZE في النموذج. بعد تغيير ملف php.ini، أعد تشغيل Apache/Nginx.س هل
$_FILES['avatar']['type'] موثوق به؟ج لا. إنه نوع MIME الذي يبلغ عنه المتصفح، وهو أمر يسهل تزويره. استخدم
finfo_file() لفحص «بايتات السحر» الفعلية للملف من أجل الكشف الدقيق.📖 ملخص
- يجب أن يتضمن النموذج
enctype="multipart/form-data" $_FILES['field']['tmp_name']هو الملف المؤقت — انقله معmove_uploaded_file()- أربع خطوات للتحقق من الصحة: الحد الأقصى للحجم → قائمة الامتدادات المسموح بها → فحص MIME → إعادة التسمية الآمنة
finfo_file()يتحقق من النوع الحقيقي للملف عبر «البايتات السحرية» — وهي طريقة أكثر موثوقية بكثير من الامتداد- يتطلب تحميل ملفات متعددة إعادة تجميع بنية
$_FILES - قاعدة الأمان: القائمة البيضاء > القائمة السوداء، إنشاء أسماء ملفات جديدة، فرض حدود الحجم
📝 تمارين
- إنشاء ميزة لتحميل الصورة الرمزية: تحديد الحجم بـ 1 ميغابايت، والسماح بتنسيقات jpg/png/gif فقط، وعرض معاينة بعد التحميل.
- إضافة رسائل خطأ شاملة إلى ميزة التحميل (حجم الملف كبير جدًّا، نوع الملف غير صحيح، لم يتم تحديد ملف) والحفاظ على حالة النموذج في حالة الفشل.
- تنفيذ أداة تحميل جماعي: تحميل ما يصل إلى 5 صور وعرضها في شبكة من الصور المصغرة بعد الانتهاء من التحميل.



