PHP PDO وطرق CRUD على قواعد البيانات
يشرح هذا الدرس كيفية استخدام PDO في PHP لإجراء عمليات CRUD (إنشاء، قراءة، تحديث، حذف) آمنة وقابلة للصيانة مع MySQL. يتضمن أمثلة جاهزة، سيناريوهات أعمال حقيقية، أفضل الممارسات للأمان والأداء، وأنماط يمكن إعادة استخدامها في المشاريع.
لماذا نستخدم PDO؟
- تجريد قاعدة البيانات: PDO يدعم العديد من قواعد البيانات بنفس واجهة البرمجة.
- الاستعلامات المحضرة: تمنع حقن SQL عن طريق فصل البيانات عن الاستعلام.
- طرق جلب مرنة: استرجاع الصفوف كمصفوفات ترابطية، كائنات، أو مصفوفات رقمية.
- معالجة الأخطاء والمعاملات: الاستثناءات المتسقة تجعل التطبيقات قوية وموثوقة.
مثال على جدول قاعدة البيانات (products)
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(255) NOT NULL,
product_price INT NOT NULL,
product_status TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
الاتصال بـ PDO (قابل لإعادة الاستخدام)
يجب دائمًا وجود دالة أو كلاس لإنشاء اتصال PDO واحد قابل لإعادة الاستخدام. ضبط PDO::ATTR_ERRMODE إلى PDO::ERRMODE_EXCEPTION لتمكين التقاط الاستثناءات.
<?php
function getPDO(): PDO {
$dsn = 'mysql:host=127.0.0.1;dbname=aiwephppdobasics_example_basic_ecommerce;charset=utf8mb4';
$user = 'root';
$password = '';
try {
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
return $pdo;
} catch (PDOException $e) {
die('فشل الاتصال: ' . $e->getMessage());
}
}
?>
CREATE — إدخال منتج جديد
استخدم المعاملات المسماة أو الموضعية وقم بربط القيم. المعاملات المسماة توضح الكود أكثر في الاستعلامات الكبيرة.
<?php
$pdo = getPDO();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['pr_name'])) {
$product_name = trim($_POST['pr_name']);
$product_price = (int)($_POST['price'] ?? 0);
$product_status = 1;
$sql = "INSERT INTO products (product_name, product_price, product_status)
VALUES (:pr_name, :price, :pr_status)";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':pr_name', $product_name, PDO::PARAM_STR);
$stmt->bindParam(':price', $product_price, PDO::PARAM_INT);
$stmt->bindParam(':pr_status', $product_status, PDO::PARAM_INT);
$stmt->execute();
$id = $pdo->lastInsertId();
$select = $pdo->prepare("SELECT * FROM products WHERE product_id = :id");
$select->execute([':id' => $id]);
$newProduct = $select->fetch();
echo '<pre>'; print_r($newProduct); echo '</pre>';
}
?>
ملاحظات: تحقق من البيانات قبل الإدخال، واستخدم lastInsertId() بحذر في البيئات متعددة الخوادم.
READ — استرجاع الصفوف (واحد أو متعدد)
استعلام بسيط لعدة صفوف:
<?php
$pdo = getPDO();
$minPrice = 10;
$sql = "SELECT * FROM products WHERE product_price > :minPrice AND product_status = :status";
$stmt = $pdo->prepare($sql);
$stmt->execute([':minPrice' => $minPrice, ':status' => 1]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo '<pre>'; print_r([$stmt->rowCount(), $rows]); echo '</pre>';
?>
استرجاع صف واحد:
<?php
$id = 2;
$stmt = $pdo->prepare("SELECT * FROM products WHERE product_id = ?");
$stmt->execute([$id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product) { /* تم العثور */ } else { /* غير موجود */ }
?>
UPDATE — تحديث الصفوف
<?php
$pdo = getPDO();
$new_name = 'iPhone x1811';
$id = 2;
$update_sql = "UPDATE products SET product_name = :product_name WHERE product_id = :id";
$stmt = $pdo->prepare($update_sql);
$stmt->bindParam(':product_name', $new_name, PDO::PARAM_STR);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
echo 'عدد الصفوف المحدثة: ' . $stmt->rowCount();
?>
ملاحظات: حدّد فقط الحقول التي تم تغييرها عند تحديث عدة أعمدة.
DELETE — حذف الصفوف
<?php
$pdo = getPDO();
$idToDelete = 13;
$stmt = $pdo->prepare("DELETE FROM products WHERE product_id = ?");
$stmt->execute([$idToDelete]);
echo 'عدد الصفوف المحذوفة: ' . $stmt->rowCount();
?>
بديل الحذف الناعم: استخدم حقل product_status أو deleted_at TIMESTAMP لتتمكن من استعادة البيانات لاحقًا.
المعاملات Transactions
غلف العمليات المتعددة في transaction لضمان تنفيذ جميعها أو عدم تنفيذ أي منها.
<?php
$pdo = getPDO();
try {
$pdo->beginTransaction();
// مثال: إنشاء طلب وتخفيض المخزون
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo 'فشل العملية: ' . $e->getMessage();
}
?>
الأمان والمخاطر الشائعة
- منع حقن SQL باستخدام الاستعلامات المحضرة فقط.
- التحقق من البيانات Input validation والتأكد من مطابقة القواعد التجارية.
- استخدام حساب DB بصلاحيات محدودة.
- تسجيل الأخطاء وعدم عرض الاستثناءات للمستخدمين في بيئة الإنتاج.
- التحقق من الأعمدة عند استخدام ORDER BY مع المعاملات لتجنب الأخطاء.
نصائح للأداء
- استخدام LIMIT مع التصفح لتجنب جلب بيانات ضخمة.
- إضافة مؤشرات
INDEXللأعمدة المستخدمة بشكل متكرر. - استخدام
EXPLAINلتحديد الاستعلامات البطيئة. - يمكن استخدام التخزين المؤقت Cache لصفحات القراءة الكثيفة.
أمثلة على الواقع العملي
1) إدارة منتجات التجارة الإلكترونية
لوحة تحكم صغيرة لإضافة، تعديل، عرض، وحذف المنتجات مع استخدام المعاملات عند إضافة المنتج مع الصور والمخزون.
2) معالجة المخزون والطلبات
- بدء المعاملة
- التحقق من المخزون (
SELECT ... FOR UPDATE) - إدراج الطلب والمنتجات
- تخفيض المخزون
- تأكيد أو التراجع عند حدوث خطأ
3) سجل مراجعة المدير Admin Audit Log
تسجيل التغييرات عند تعديل المنتجات. هذه السجلات مهمة للمراجعة وتصحيح الأخطاء.
أخطاء شائعة وكيفية تجنبها
- بناء استعلامات SQL مباشرة من مدخلات المستخدم — استخدم الاستعلامات المحضرة.
- عدم استخدام المعاملات في العمليات متعددة الخطوات.
- إساءة استخدام
rowCount()مع SELECT — استخدمcount($result)أوfetchAll. - ربط LIMIT/OFFSET بطريقة خاطئة — استخدم
bindValueمعPDO::PARAM_INT.
الاختبار والتصحيح
- في التطوير: عرض الاستثناءات وتشغيل استعلامات التجربة مع قاعدة بيانات محلية.
- في الإنتاج: تسجيل الاستثناءات في ملف أو نظام تسجيل مثل Sentry وLoggly.
- اختبارات وحدة للـ DAO layer عند الإمكان.
نصائح SEO للمحررين
- استخدام عناوين H1/H2 واضحة.
- كتابة فقرات قصيرة مع أمثلة للكود.
- تضمين سيناريوهات الأعمال لتتناسب مع بحث المستخدم.
- استخدام
schema.orgللبرامج التعليمية والمقالات إذا كان CMS يدعمه.
خلاصة وقائمة مراجعة سريعة
- اتصال PDO قابل لإعادة الاستخدام مع استثناءات.
- استعلامات محضرة لكل مدخلات المستخدم.
- التحقق من البيانات وتنقيتها.
- استخدام المعاملات للعمليات متعددة الخطوات.
- الحذف الناعم عند الحاجة.
- إضافة مؤشرات للأعمدة الشائعة.
- تسجيل ومراقبة الأخطاء في الإنتاج.
- اختبارات للعمليات الحرجة (طلبات، مخزون).
أمثلة الملفات الكاملة
insert_pdo.php (نموذج + إدخال + استرجاع):
// insert_pdo.php
$pdo = getPDO();
$product_name = $_POST['pr_name'] ?? '';
$product_price = $_POST['price'] ?? 0;
$product_status = 1;
if (!empty($product_name)) {
$sql = "INSERT INTO products (product_name, product_price, product_status) VALUES (:pr_name, :price, :pr_status)";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':pr_name', $product_name, PDO::PARAM_STR);
$stmt->bindParam(':price', $product_price, PDO::PARAM_INT);
$stmt->bindParam(':pr_status', $product_status, PDO::PARAM_INT);
$stmt->execute();
$id = $pdo->lastInsertId();
$q = $pdo->prepare("SELECT * FROM products WHERE product_id = :id");
$q->bindParam(':id', $id, PDO::PARAM_INT);
$q->execute();
$fetch = $q->fetch(PDO::FETCH_ASSOC);
echo '<pre>'; print_r($fetch); echo '</pre>';
}
// HTML form
<label>اسم المنتج</label>
<input name="pr_name" type="text">
<label>السعر</label>
<input name="price" type="number">
<button type="submit">إرسال</button>
خاتمة وخطوات تالية
استخدام PDO للـ CRUD خطوة مهمة لبناء تطبيقات آمنة وقابلة للصيانة. ابدأ بفصل منطق قاعدة البيانات في دوال أو طبقة بيانات صغيرة، ثم أضف التحقق والمعاملات والتسجيل. بعد فهم PDO الخام يمكنك التفكير في ORM مثل Doctrine أو Eloquent.
تمارين مقترحة:
- بناء لوحة تحكم لإضافة، عرض (مع الصفحات)، تعديل وحذف المنتجات.
- تنفيذ عملية “وضع الطلب” باستخدام المعاملات وقفل الصفوف.
- إضافة اختبارات وحدة للـ DAO باستخدام قاعدة بيانات اختبارية.
