طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 132 · تقنيات تطوير الواجهات الخلفية
الدرس 19 · الوحدة 9: ربط الكود من جانب الخادم مع قواعد البيانات

الاتصال بقاعدة البيانات عبر PDO

ما تعلّمته في 121 يتصل الآن بالواجهة الخلفية. ⏱ نحو 35 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح دور PDO وتكتب DSN لـ MySQL و SQLite.
  • تجهّز قاعدة بيانات في phpMyAdmin على XAMPP.
  • تتصل بالقاعدة من ملف واحد وتقرأ الصفوف بـ fetchAll و fetch و fetchColumn.
  • تعرض بيانات القاعدة في قالب HTML، وتتعامل مع أخطاء الاتصال بأمان.

١من الملفات إلى قاعدة البيانات

في مقرر 121 كتبت استعلامات SQL على قاعدة «متجر الواحة». والآن نربطها بالواجهة الخلفية: كود PHP يرسل استعلامًا، ويستقبل الصفوف، ويبني منها الصفحة أو JSON. هذا هو العمل اليومي لمطوّر الواجهات الخلفية.

وفي PHP طريقتان للاتصال بـ MySQL: mysqli الخاصة بـ MySQL، وPDO (PHP Data Objects) التي تعمل بالأسلوب نفسه مع MySQL و SQLite و PostgreSQL وغيرها. نستخدم PDO لأنها الأحدث والأوسع، ولأن الكود نفسه يعمل في المختبر (SQLite) وعلى XAMPP (MySQL) بتغيير سطر الاتصال فقط.

// MySQL على XAMPP$pdo = new PDO('mysql:host=localhost;dbname=store;charset=utf8mb4', 'root', ''); // SQLite (ملف واحد، يستخدمه المختبر)$pdo = new PDO('sqlite:' . __DIR__ . '/store.sqlite');

النص الأول يسمى DSN: نوع القاعدة ومكانها واسمها وترميزها. و utf8mb4 ضروري للعربية والرموز التعبيرية في MySQL.

٢جهّز القاعدة على XAMPP

  1. شغّل MySQL من XAMPP Control Panel بجانب Apache.
  2. افتح http://localhost/phpmyadmin ← New وأنشئ قاعدة اسمها store بترميز utf8mb4_unicode_ci.
  3. من تبويب SQL ألصق أوامر إنشاء الجداول والبيانات من مقرر 121 ونفّذها (في MySQL استخدم VARCHAR(100) بدل TEXT للأعمدة التي عليها UNIQUE).
  4. مستخدم XAMPP الافتراضي root بلا كلمة مرور. هذا للتطوير فقط؛ على الخادم الحقيقي لكل تطبيق مستخدم بكلمة مرور قوية وصلاحيات محدودة.

٣ملف الاتصال وأول استعلام

نضع الاتصال في db.php ونضمّنه في كل صفحة تحتاج القاعدة. والإعدادان فيه مهمان: الأخطاء تصير استثناءات (الدرس 12)، والصفوف تعود مصفوفات ترابطية مفاتيحها أسماء الأعمدة:

<?phprequire __DIR__ . '/db.php'; $rows = $pdo->query('SELECT id, name, city FROM customers ORDER BY name')->fetchAll(); echo "<p>عدد العملاء: " . count($rows) . "</p><ul>";foreach ($rows as $r) {    echo "<li>{$r['id']}. {$r['name']} — {$r['city']}</li>";}echo "</ul>";
الطريقةتعيد
$pdo->query($sql)كائن «نتيجة» Statement (للاستعلامات الثابتة بلا مدخلات من المستخدم).
->fetchAll()كل الصفوف: مصفوفة من المصفوفات الترابطية.
->fetch()صفًّا واحدًا، أو false إن انتهت الصفوف.
->fetchColumn()قيمة أول عمود في الصف التالي، مثالية لـ COUNT و SUM.

٤القالب نفسه، والبيانات من القاعدة

تذكّر جدول المنتجات في الدرس 3: كانت المصفوفة مكتوبة باليد. الآن تأتي من القاعدة، والقالب لم يتغيّر حرفًا:

<?phprequire __DIR__ . '/db.php';$products = $pdo->query('SELECT name, category, price, stock FROM products ORDER BY category, price')->fetchAll();$total = $pdo->query('SELECT SUM(stock) FROM products')->fetchColumn();?><table border="1" cellpadding="5">  <tr><th>المنتج</th><th>التصنيف</th><th>السعر</th><th>الحالة</th></tr>  <?php foreach ($products as $p): ?>  <tr>    <td><?= htmlspecialchars($p['name']) ?></td>    <td><?= $p['category'] ?></td>    <td><?= $p['price'] ?> ريال</td>    <td><?= $p['stock'] > 0 ? 'متوفر' : 'نفد' ?></td>  </tr>  <?php endforeach; ?></table><p>القطع في المخزون: <?= $total ?></p>

جرّب في المختبر: عدّل الاستعلام ليعرض المتوفر فقط بـ WHERE stock > 0، أو صنفًا واحدًا. كل ما تعلّمته في 121 يعمل هنا.

٥صف واحد، وقيمة واحدة، والتجميع

<?phprequire __DIR__ . '/db.php'; // صف واحد$top = $pdo->query('SELECT name, price FROM products ORDER BY price DESC LIMIT 1')->fetch();echo "الأغلى: {$top['name']} ({$top['price']})\n"; // قيمة واحدةecho "عدد الطلبات: " . $pdo->query('SELECT COUNT(*) FROM orders')->fetchColumn() . "\n"; // تجميع من جدولين$sql = 'SELECT c.city, COUNT(*) AS n        FROM orders o JOIN customers c ON c.id = o.customer_id        GROUP BY c.city ORDER BY n DESC';foreach ($pdo->query($sql) as $r) {       // يمكن المرور على النتيجة مباشرة    echo "{$r['city']}: {$r['n']}\n";}

٦حين يفشل الاتصال

كلمة مرور خاطئة، أو خادم MySQL متوقف، أو جدول غير موجود: كلها تصير PDOException. ورسالتها قد تحوي اسم المستخدم ومسار الملف، فلا تعرضها للزائر:

<?phptry {    $pdo = new PDO('mysql:host=localhost;dbname=store;charset=utf8mb4', 'root', '');} catch (PDOException $e) {    error_log('DB: ' . $e->getMessage());     // التفاصيل للمطوّر في السجل    http_response_code(503);                   // Service Unavailable    exit('الخدمة غير متاحة مؤقتًا');           // رسالة عامة للزائر}

توقّع وتحقّق

ما ميزة PDO على mysqli؟

MySQL و SQLite و PostgreSQL…

لعدد الصفوف من COUNT(*) نستخدم:

قيمة واحدة.

FETCH_ASSOC تجعل الصف:

$r['name']

ERRMODE_EXCEPTION تعني:

نلتقطها بـ try/catch.

الخلاصة

تحدٍّ برمجي: لوحة إحصاءات المتجر

متوسط

اكتب stats.php التي تقرأ من قاعدة «متجر الواحة» وتعرض لوحة إحصاءات. db.php جاهز.

المطلوب:
  • اطبع <p>المنتجات: 12</p> بعدد المنتجات من القاعدة (لا تكتبه بيدك).
  • اطبع <p>النافد: …</p> بعدد المنتجات التي stock فيها 0.
  • ثم قائمة <ul> بعدد المنتجات في كل تصنيف، مرتبة أبجديًّا بالتصنيف: <li>التصنيف: العدد</li>
  • استخدم db.php و $pdo.
👀 اعرض الاستجابة المطلوبة
✓ التحقق الآلي: يرسل المختبر هذه الطلبات إلى خادمك بالترتيب ثم يفحص الاستجابات
  1. GET /stats.php
  • الكود يعمل بلا أخطاء أو تحذيرات
  • عدد المنتجات من القاعدة
  • النافد والقائمة كما في المطلوب تمامًا
  • تستخدم GROUP BY
  • لا تكتب العدد 12 بيدك

💡 تلميح 1
$pdo->query('SELECT COUNT(*) FROM products')->fetchColumn()
💡 تلميح 2
للتصنيفات: SELECT category, COUNT(*) AS n FROM products GROUP BY category ORDER BY category

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

<?phprequire __DIR__ . '/db.php'; $count = $pdo->query('SELECT COUNT(*) FROM products')->fetchColumn();$out = $pdo->query('SELECT COUNT(*) FROM products WHERE stock = 0')->fetchColumn();echo "<p>المنتجات: $count</p>";echo "<p>النافد: $out</p>"; echo "<ul>";foreach ($pdo->query('SELECT category, COUNT(*) AS n FROM products GROUP BY category ORDER BY category') as $r) {    echo "<li>{$r['category']}: {$r['n']}</li>";}echo "</ul>";
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس