المحطّة ٣: إنشاء القاعدة وطبقة الوصول (PDO)
نفّذ مخطّطك: قاعدةٌ تعمل، وطبقة PDO تقرأ منها بأمان. ⏱ نحو 24 دقيقة.
- تنشئ قاعدة بياناتٍ من مخطّطك وتصل بها عبر
PDO. - تضع الاتصال في طبقةٍ واحدةٍ
db.phpقابلةٍ للتبديل بينSQLiteوMySQL. - تقرأ الصفوف بـ
queryوfetchAllوتعرضها. - تربط الجدولين بـ
JOINوتمرّر القيَم بأمانٍ عبر المعاملات المُعدّة.
١من التصميم إلى قاعدةٍ تعمل
معنا الآن مخطّطٌ من المحطّة السابقة. نحوّله إلى قاعدةٍ حقيقيّة ونصل بها من PHP. نضع الاتصال في ملفٍّ واحدٍ db.php نضمّنه في كل صفحةٍ تحتاج القاعدة — فلو تغيّر الاتصال (من SQLite في المختبر إلى MySQL على XAMPP) عدّلنا مكانًا واحدًا. هذا ما أتقنته في مقرّر 132، ونبني عليه هنا.
<?php// db.php: الاتصال بقاعدة البيانات في مكانٍ واحد (نضمّنه في كل صفحة)$pdo = new PDO('sqlite:' . __DIR__ . '/tasks.sqlite');// على XAMPP بدّل السطر أعلاه بهذا:// $pdo = new PDO('mysql:host=localhost;dbname=tasks;charset=utf8mb4', 'root', ''); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // الأخطاء استثناءات$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // الصفوف مصفوفات ترابطية
الإعدادان سطرٌ واحدٌ لكنهما يوفّران ساعاتٍ من التصحيح: الأوّل يحوّل أخطاء القاعدة إلى استثناءاتٍ تراها فورًا، والثاني يجعل كل صفٍّ مصفوفةً مفاتيحها أسماء الأعمدة.
٢أول قراءة: كل المهامّ
في المختبر القاعدة مهيّأةٌ ببيانات تجربة (مستخدمان ومهامّهما). نرسل استعلامًا بسيطًا ونعرض الصفوف. جرّب تشغيله:
<?phprequire __DIR__ . '/db.php'; $rows = $pdo->query('SELECT id, title, done FROM tasks ORDER BY id')->fetchAll(); echo "<p>عدد المهامّ: " . count($rows) . "</p><ul>";foreach ($rows as $t) { $mark = $t['done'] ? '✓' : '•'; echo "<li>{$mark} {$t['title']}</li>";}echo "</ul>";
<?php// db.php: الاتصال بقاعدة البيانات في مكانٍ واحد (نضمّنه في كل صفحة)$pdo = new PDO('sqlite:' . __DIR__ . '/tasks.sqlite');// على XAMPP بدّل السطر أعلاه بهذا:// $pdo = new PDO('mysql:host=localhost;dbname=tasks;charset=utf8mb4', 'root', ''); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // الأخطاء استثناءات$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // الصفوف مصفوفات ترابطية
لاحظ: query() تصلح للاستعلامات الثابتة التي لا تحوي مدخلاتٍ من المستخدم. حين يدخل المستخدم قيمةً (بحثٌ، معرّف…) ننتقل إلى الاستعلامات المُعدّة (prepared) لمنع الحقن — وهذا موضوع محطّة التأمين.
٣عدّ المهامّ المنجَزة لمستخدمٍ محدّد
مثالٌ على ربط الجدولين: نحسب كم مهمّةً أنجزها المستخدم «sarah». نمرّر اسم المستخدم كـ معامل مُعدّ (آمن) ونربط الجدولين:
<?phprequire __DIR__ . '/db.php'; $st = $pdo->prepare( 'SELECT COUNT(*) AS c FROM tasks t JOIN users u ON u.id = t.user_id WHERE u.username = ? AND t.done = 1');$st->execute(['sarah']);$n = $st->fetch()['c']; echo "أنجزت sarah {$n} مهمّة.";
<?php// db.php: الاتصال بقاعدة البيانات في مكانٍ واحد (نضمّنه في كل صفحة)$pdo = new PDO('sqlite:' . __DIR__ . '/tasks.sqlite');// على XAMPP بدّل السطر أعلاه بهذا:// $pdo = new PDO('mysql:host=localhost;dbname=tasks;charset=utf8mb4', 'root', ''); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // الأخطاء استثناءات$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // الصفوف مصفوفات ترابطية
توقّع وتحقّق
لماذا نضع الاتصال في ملفٍّ واحدٍ db.php؟
ما الذي يحوّل أخطاء القاعدة إلى استثناءاتٍ تراها فورًا؟
أيّهما يصلح لقيمةٍ أدخلها المستخدم؟
ماذا يفعل JOIN في استعلام الإحصاء؟
الخلاصة
- طبقة
db.phpتجمع الاتصال في مكانٍ واحد (مبدأDRY). ERRMODE_EXCEPTIONيُظهر أخطاء القاعدة فورًا؛FETCH_ASSOCيجعل الصفّ مصفوفةً بأسماء الأعمدة.query()للاستعلامات الثابتة؛prepare()+execute()لأيّ قيمةٍ من المستخدم.JOINيربط المهمّة بصاحبها عبرuser_id— أساس عزل بيانات كل مستخدم.- الكود نفسه يعمل على
MySQLبتغيير سطر الاتصال فقط.
تحدٍّ برمجي: صفحة «كل المهامّ»
برمجياكتب صفحة tasks.php تتّصل بالقاعدة عبر db.php، ثم تعرض عدد المهامّ الكلّيّ وقائمةً بعناوينها كلّها.
- ضمّن
db.php. - اقرأ كل المهامّ مرتّبةً بالمعرّف.
- اطبع سطرًا:
عدد المهامّ: 5. - اطبع قائمة
<ul>فيها عنوان كل مهمّة.
👀 اعرض الاستجابة المطلوبة
GET /tasks.php
- الكود يعمل بلا أخطاء أو تحذيرات
- يعرض العدد الصحيح
(5) - يعرض عناوين المهامّ
- يستخدم قائمة ul
💡 تلميح 1
$pdo->query(...)->fetchAll().💡 تلميح 2
count($rows)، والعناوين بحلقة foreach.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
<?phprequire __DIR__ . '/db.php'; $rows = $pdo->query('SELECT title FROM tasks ORDER BY id')->fetchAll(); echo "عدد المهامّ: " . count($rows);echo "<ul>";foreach ($rows as $t) { echo "<li>{$t['title']}</li>";}echo "</ul>";
<?php// db.php: الاتصال بقاعدة البيانات في مكانٍ واحد (نضمّنه في كل صفحة)$pdo = new PDO('sqlite:' . __DIR__ . '/tasks.sqlite');// على XAMPP بدّل السطر أعلاه بهذا:// $pdo = new PDO('mysql:host=localhost;dbname=tasks;charset=utf8mb4', 'root', ''); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // الأخطاء استثناءات$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // الصفوف مصفوفات ترابطية