استقبال البيانات وتحليلها
من مربعات الاختيار إلى ملفات Excel: البيانات كما تصل فعلًا. ⏱ نحو 35 دقيقة.
- تستقبل الحقول متعددة القيم بأسماء تنتهي بـ
[]وتصفّيها. - تستقبل الملفات المرفوعة عبر $
_FILESوتتحقق من نوعها وحجمها. - تحلل بيانات
CSVإلى صفوف ترابطية وتستخرج منها إحصاءات. - تطبّق قواعد أمان رفع الملفات.
١حقول تحمل أكثر من قيمة
بعض الحقول يختار منها المستخدم أكثر من قيمة: مربعات اختيار Checkboxes لمهاراته، أو قائمة متعددة. إن سمّيت الحقل باسم ينتهي بـ [] فسيجمع PHP القيم في مصفوفة:
<?phpif ($_SERVER['REQUEST_METHOD'] === 'POST') { $skills = $_POST['skills'] ?? []; // مصفوفة، أو غير موجودة إن لم يختر شيئًا $allowed = ['HTML', 'CSS', 'PHP', 'SQL']; $skills = array_values(array_intersect($skills, $allowed)); // تجاهل أي قيمة غريبة echo "<p>اخترت " . count($skills) . ": " . implode('، ', $skills) . "</p>"; exit;}?><form method="post"> <label><input type="checkbox" name="skills[]" value="HTML" checked> HTML</label> <label><input type="checkbox" name="skills[]" value="CSS"> CSS</label> <label><input type="checkbox" name="skills[]" value="PHP" checked> PHP</label> <label><input type="checkbox" name="skills[]" value="SQL"> SQL</label> <button>حفظ</button></form>
الطلب الثاني يرسل skills[] ثلاث مرات، إحداها قيمة غريبة أضافها مهاجم بطلب يدوي. array_intersect أبقت المسموح فقط.
مربع الاختيار غير المؤشَّر لا يُرسل أصلًا. فإن لم يختر المستخدم أي شيء لا يوجد المفتاح skills، ولهذا كتبنا ?? [].
٢رفع الملفات
لرفع ملف يحتاج النموذج شرطين: الطريقة post، والسمة enctype="multipart/form-data"؛ فيُرسل الطلب بصيغة خاصة تقسم الجسم إلى أجزاء، كل حقل وكل ملف في جزء. ويضع PHP الملف المرفوع في مجلد مؤقت، ويصف لك المصفوفة $_FILES:
| المفتاح | معناه |
|---|---|
$_FILES['sheet']['name'] | اسم الملف في جهاز المستخدم (لا تثق به). |
['tmp_name'] | مسار النسخة المؤقتة على الخادم. |
['size'] | الحجم بالبايت. |
['error'] | UPLOAD_ERR_OK (صفر) إن نجح الرفع. |
وهذا تطبيق كامل: يرفع المشرف ملف درجات CSV، فيحلّله الخادم ويعرض متوسط كل مقرر:
<?phpif ($_SERVER['REQUEST_METHOD'] !== 'POST') { ?> <form method="post" enctype="multipart/form-data"> <p>ملف الدرجات (CSV): <input type="file" name="sheet" accept=".csv"></p> <button>حلّل</button> </form><?php exit; } $f = $_FILES['sheet'] ?? null;if (!$f || $f['error'] !== UPLOAD_ERR_OK) { http_response_code(400); exit('لم يصل ملف');}if (strtolower(pathinfo($f['name'], PATHINFO_EXTENSION)) !== 'csv' || $f['size'] > 100_000) { http_response_code(422); exit('نقبل ملفات CSV حتى 100 كيلوبايت فقط');} $rows = array_map('str_getcsv', file($f['tmp_name'], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));$head = array_shift($rows); // السطر الأول: أسماء الأعمدة $by = [];foreach ($rows as $r) { $row = array_combine($head, $r); $by[$row['course']][] = (float) $row['grade'];}echo "<p>الملف: " . htmlspecialchars($f['name']) . " · " . count($rows) . " صفوف</p><ul>";foreach ($by as $course => $grades) { $avg = round(array_sum($grades) / count($grades), 1); echo "<li>$course: " . count($grades) . " متدربين، المتوسط $avg</li>";}echo "</ul>";
افتح الطلب الثاني لترى شكل multipart: حدود (boundary) تفصل الأجزاء. والطلب الثالث ملف صورة فرُفض بـ 422.
٣تحليل CSV خطوة بخطوة
CSV (قيم مفصولة بفواصل) أبسط صيغة لتبادل الجداول، ويصدّرها Excel ونظام رايات وغيرهما. خطوات تحليلها:
<?php$text = "name,course,grade\nسعود,PHP,92\nنورة,PHP,88\nخالد,SQL,75"; $lines = explode("\n", trim($text));$rows = array_map('str_getcsv', $lines); // كل سطر ← مصفوفة قيم$head = array_shift($rows); // انزع سطر العناوين foreach ($rows as $r) { $row = array_combine($head, $r); // ['name' => 'سعود', 'course' => 'PHP', …] echo "{$row['name']} ← {$row['grade']}\n";} $grades = array_column(array_map(fn($r) => array_combine($head, $r), $rows), 'grade');echo "الأعلى: " . max($grades);
| الدالة | عملها |
|---|---|
str_getcsv($line) | تقسم سطر CSV إلى قيم، وتفهم القيم بين علامتي تنصيص التي فيها فواصل. |
array_shift($a) | تنزع العنصر الأول وتعيده (سطر العناوين). |
array_combine($keys, $values) | تصنع مصفوفة ترابطية من مصفوفتين. |
array_column($rows, 'grade') | تستخرج عمودًا واحدًا من صفوف ترابطية. |
٤أمان الملفات المرفوعة
رفع الملفات من أخطر الوظائف: لو قبلت ملف shell.php ووضعته في مجلد مكشوف، يستطيع المهاجم تشغيله على خادمك!
- تحقق من الامتداد بقائمة مسموحة (
csv،jpg،pdf…) لا بقائمة ممنوعة. - حدّد الحجم الأقصى (وفي
php.ini: upload_max_filesizeوpost_max_size). - لا تستخدم اسم الملف الأصلي للحفظ: أنشئ اسمًا عشوائيًّا مثل
bin2hex(random_bytes(8)) . '.csv'. - احفظ بـ
move_uploaded_file()في مجلد خارجpublic، أو مجلد يمنع تشغيلPHP.
<?php$ext = strtolower(pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION));if (!in_array($ext, ['jpg', 'jpeg', 'png'], true)) exit('نوع غير مسموح'); $name = bin2hex(random_bytes(8)) . '.' . $ext;move_uploaded_file($_FILES['photo']['tmp_name'], __DIR__ . '/../uploads/' . $name);
توقّع وتحقّق
ليصل PHP عدة قيم لحقل واحد يكون اسمه:
السمة اللازمة في النموذج لرفع الملفات:
method="post".أي دالة تحوّل سطر CSV إلى مصفوفة قيم؟
أأمن طريقة لتسمية الملف المرفوع عند حفظه:
الخلاصة
name="x[]"← $_POST['x']مصفوفة؛ وصفّها بقائمة مسموحة.- الرفع:
post + enctype="multipart/form-data"ثم $_FILES['f']['tmp_name']. CSV: str_getcsvلكل سطر، وarray_shiftللعناوين، وarray_combineللصف الترابطي.- امتداد من قائمة مسموحة، وحد للحجم، واسم عشوائي، و
move_uploaded_fileخارجpublic.
تحدٍّ برمجي: تقرير الحضور من CSV
متقدمالمشرف يلصق بيانات الحضور (CSV) في حقل نصي ويرسلها POST. اكتب attendance.php يحللها.
- الحقل اسمه
data، وأول سطر فيه العناوين:name,presentوالقيمة present إما 1 أو 0. - اطبع لكل متدرب نسبة حضوره (عدد الأسطر التي present فيها 1 ÷ عدد أسطره) بالشكل:
<li>سعود: 67%</li>مقرّبة لعدد صحيح، بترتيب أول ظهور. - ومن نسبته أقل من 75% يضاف بعد النسبة:
⚠ حرمان(مثال:<li>سعود: 67% ⚠ حرمان</li>). - إن لم يُرسل data أو كان فارغًا: 422 والنص
لا توجد بيانات.
👀 اعرض الاستجابة المطلوبة
POST /attendance.php(data=name,present سعود,1 نورة,1 سعود,0 نورة,1 سعود,1 نورة,1 خالد,0)POST /attendance.php(data=name,present ريم,1 ريم,1 ريم,1 ريم,0)POST /attendance.php
- الكود يعمل بلا أخطاء أو تحذيرات
- سعود 67% ⚠ حرمان، ونورة 100%، وخالد 0% ⚠ حرمان، بالترتيب
- ريم: 75% بلا حرمان (الحد 75 ليس أقل منه)
- بلا بيانات ← 422
- تستخدم str_getcsv
💡 تلميح 1
💡 تلميح 2
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
<?php$data = trim($_POST['data'] ?? '');if ($data === '') { http_response_code(422); exit('لا توجد بيانات');}$rows = array_map('str_getcsv', explode("\n", $data));$head = array_map('trim', array_shift($rows)); $stats = [];foreach ($rows as $r) { $row = array_combine($head, array_map('trim', $r)); $n = $row['name']; $stats[$n]['total'] = ($stats[$n]['total'] ?? 0) + 1; $stats[$n]['present'] = ($stats[$n]['present'] ?? 0) + (int) $row['present'];} echo "<ul>";foreach ($stats as $name => $s) { $pct = round($s['present'] / $s['total'] * 100); echo "<li>$name: $pct%" . ($pct < 75 ? " ⚠ حرمان" : "") . "</li>";}echo "</ul>";