أساسيات PHP
PDO: بديل للوصول إلى قاعدة البيانات
ما هو PDO؟
PDO (PHP Data Objects) هي طبقة تجريد لقاعدة البيانات توفر واجهة موحدة للوصول إلى أنظمة قواعد البيانات المختلفة. بخلاف MySQLi الذي يعمل فقط مع MySQL، يدعم PDO أكثر من 12 برنامج تشغيل قاعدة بيانات بما في ذلك MySQL وPostgreSQL وSQLite وOracle وMicrosoft SQL Server.
PDO مقابل MySQLi: مقارنة سريعة
| الميزة | MySQLi | PDO |
|---|---|---|
| دعم قواعد البيانات | MySQL فقط | أكثر من 12 قاعدة بيانات |
| نمط الواجهة | إجرائي + OOP | OOP فقط |
| ربط المعلمات | علامات ? فقط | ? و :named |
| جلب النتائج | طرق متعددة | أوضاع جلب مرنة |
| منحنى التعلم | أسهل للمبتدئين | ميزات أكثر، أصعب قليلاً |
متى تستخدم PDO
- مرونة قاعدة البيانات: قد تبدل قواعد البيانات في المستقبل
- المعلمات المسماة: تفضل
:nameعلى علامات? - تعيين الكائنات: تريد جلب النتائج مباشرة إلى فئات مخصصة
- المعاملات: تحتاج إلى معالجة متقدمة للمعاملات
- النقل: قد يعمل كودك على أنظمة قواعد بيانات مختلفة
الاتصال بقاعدة البيانات باستخدام PDO
<?php
$host = 'localhost';
$dbname = 'my_database';
$username = 'root';
$password = '';
try {
// إنشاء مثيل PDO
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$username,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
echo "تم الاتصال بنجاح";
} catch (PDOException $e) {
die("فشل الاتصال: " . $e->getMessage());
}
?>
شرح خيارات اتصال PDO
PDO::ATTR_ERRMODE- اضبط علىERRMODE_EXCEPTIONلرمي استثناءات عند الأخطاءPDO::ATTR_DEFAULT_FETCH_MODE- اضبط وضع الجلب الافتراضي (مصفوفة ترابطية موصى بها)PDO::ATTR_EMULATE_PREPARES- عطّل العبارات المحضرة المحاكاة للعبارات المحضرة الحقيقية
فئة تكوين PDO
<?php
class Database {
private $host = 'localhost';
private $dbname = 'my_database';
private $username = 'root';
private $password = '';
private $pdo;
public function __construct() {
$dsn = "mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => false
];
try {
$this->pdo = new PDO($dsn, $this->username, $this->password, $options);
} catch (PDOException $e) {
throw new PDOException($e->getMessage(), (int)$e->getCode());
}
}
public function getConnection() {
return $this->pdo;
}
}
// الاستخدام
$db = new Database();
$pdo = $db->getConnection();
?>
العبارات المحضرة مع علامات الاستفهام
<?php
// تحضير العبارة
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND email = ?");
// التنفيذ مع مصفوفة المعلمات
$stmt->execute(['johndoe', 'john@example.com']);
// جلب النتائج
$user = $stmt->fetch();
if ($user) {
echo "تم العثور على المستخدم: " . $user['username'];
} else {
echo "لم يتم العثور على المستخدم";
}
?>
المعلمات المسماة (ميزة حصرية لـ PDO)
تجعل المعلمات المسماة كودك أكثر وضوحًا وقابلية للصيانة:
<?php
// تحضير العبارة مع المعلمات المسماة
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND email = :email");
// التنفيذ مع مصفوفة ترابطية
$stmt->execute([
':username' => 'johndoe',
':email' => 'john@example.com'
]);
$user = $stmt->fetch();
?>
نصيحة: المعلمات المسماة مفيدة بشكل خاص عندما يكون لديك العديد من المعلمات أو عندما تظهر نفس المعلمة عدة مرات في الاستعلام.
INSERT مع PDO
<?php
// مع المعلمات المسماة
$stmt = $pdo->prepare("
INSERT INTO users (username, email, password, full_name)
VALUES (:username, :email, :password, :full_name)
");
$stmt->execute([
':username' => 'johndoe',
':email' => 'john@example.com',
':password' => password_hash('password123', PASSWORD_DEFAULT),
':full_name' => 'John Doe'
]);
// الحصول على آخر معرف إدراج
$user_id = $pdo->lastInsertId();
echo "معرف المستخدم الجديد: $user_id";
?>
UPDATE مع PDO
<?php
$stmt = $pdo->prepare("
UPDATE users
SET email = :email, full_name = :full_name
WHERE id = :id
");
$stmt->execute([
':email' => 'newemail@example.com',
':full_name' => 'John Smith',
':id' => 42
]);
// الحصول على عدد الصفوف المتأثرة
$affected = $stmt->rowCount();
echo "الصفوف المحدثة: $affected";
?>
DELETE مع PDO
<?php
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute([':id' => 42]);
echo "الصفوف المحذوفة: " . $stmt->rowCount();
?>
أوضاع الجلب في PDO
يقدم PDO طرقًا متعددة لجلب النتائج:
<?php
$stmt = $pdo->query("SELECT * FROM users");
// 1. جلب كمصفوفة ترابطية (افتراضي إذا تم تعيينه في المنشئ)
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// الاستخدام: $row['username']
// 2. جلب كمصفوفة رقمية
$row = $stmt->fetch(PDO::FETCH_NUM);
// الاستخدام: $row[0], $row[1]
// 3. جلب كمصفوفة ترابطية ورقمية معًا
$row = $stmt->fetch(PDO::FETCH_BOTH);
// الاستخدام: $row['username'] أو $row[0]
// 4. جلب ككائن (stdClass)
$row = $stmt->fetch(PDO::FETCH_OBJ);
// الاستخدام: $row->username
// 5. جلب جميع الصفوف دفعة واحدة
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 6. جلب عمود واحد
$stmt = $pdo->query("SELECT username FROM users");
$usernames = $stmt->fetchAll(PDO::FETCH_COLUMN);
// يُرجع: ['user1', 'user2', 'user3']
// 7. جلب أزواج المفتاح والقيمة
$stmt = $pdo->query("SELECT id, username FROM users");
$users = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// يُرجع: [1 => 'user1', 2 => 'user2']
?>
الجلب إلى فئات مخصصة
يمكن لـ PDO تعيين صفوف قاعدة البيانات مباشرة إلى فئاتك المخصصة:
<?php
class User {
public $id;
public $username;
public $email;
public $full_name;
public function greet() {
return "مرحبًا، اسمي {$this->full_name}";
}
}
// جلب إلى فئة مخصصة
$stmt = $pdo->query("SELECT * FROM users WHERE id = 1");
$user = $stmt->fetchObject('User');
echo $user->greet(); // "مرحبًا، اسمي John Doe"
// جلب الكل إلى فئة مخصصة
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll(PDO::FETCH_CLASS, 'User');
foreach ($users as $user) {
echo $user->greet() . "<br>";
}
?>
المعاملات في PDO
تضمن المعاملات نجاح أو فشل سلسلة من عمليات قاعدة البيانات معًا:
<?php
try {
// بدء المعاملة
$pdo->beginTransaction();
// عمليات متعددة
$stmt1 = $pdo->prepare("INSERT INTO users (username, email) VALUES (?, ?)");
$stmt1->execute(['user1', 'user1@example.com']);
$stmt2 = $pdo->prepare("INSERT INTO profiles (user_id, bio) VALUES (?, ?)");
$stmt2->execute([$pdo->lastInsertId(), 'سيرتي الذاتية']);
$stmt3 = $pdo->prepare("UPDATE stats SET user_count = user_count + 1");
$stmt3->execute();
// تأكيد إذا نجحت جميعها
$pdo->commit();
echo "تمت جميع العمليات بنجاح";
} catch (Exception $e) {
// التراجع إذا فشل أي منها
$pdo->rollBack();
echo "فشلت المعاملة: " . $e->getMessage();
}
?>
ملاحظة: تعمل المعاملات فقط مع محركات التخزين التي تدعمها (مثل InnoDB في MySQL، وليس MyISAM).
فئة CRUD كاملة مع PDO
<?php
class UserRepository {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
// CREATE
public function create($data) {
$stmt = $this->pdo->prepare("
INSERT INTO users (username, email, password, full_name)
VALUES (:username, :email, :password, :full_name)
");
$stmt->execute([
':username' => $data['username'],
':email' => $data['email'],
':password' => password_hash($data['password'], PASSWORD_DEFAULT),
':full_name' => $data['full_name']
]);
return $this->pdo->lastInsertId();
}
// READ - الحصول على واحد
public function findById($id) {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute([':id' => $id]);
return $stmt->fetch();
}
// READ - الحصول على الكل
public function findAll() {
$stmt = $this->pdo->query("SELECT * FROM users ORDER BY created_at DESC");
return $stmt->fetchAll();
}
// UPDATE
public function update($id, $data) {
$stmt = $this->pdo->prepare("
UPDATE users
SET username = :username, email = :email, full_name = :full_name
WHERE id = :id
");
return $stmt->execute([
':username' => $data['username'],
':email' => $data['email'],
':full_name' => $data['full_name'],
':id' => $id
]);
}
// DELETE
public function delete($id) {
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = :id");
return $stmt->execute([':id' => $id]);
}
// AUTHENTICATE
public function authenticate($username, $password) {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
unset($user['password']);
return $user;
}
return false;
}
}
?>
MySQLi مقابل PDO: أيهما يجب أن تستخدم؟
اختر MySQLi إذا:
- كنت تستخدم MySQL فقط ولن تغير قواعد البيانات
- تفضل نمط البرمجة الإجرائية
- تحتاج أداء أفضل قليلاً لعمليات MySQL المحددة
اختر PDO إذا:
- تريد قابلية نقل قاعدة البيانات
- تفضل البرمجة الموجهة للكائنات
- تريد معلمات مسماة لوضوح أفضل
- تحتاج إلى الجلب مباشرة إلى فئات مخصصة
- تعمل على تطبيق أكبر بمتطلبات معقدة
تمرين: تحويل كود MySQLi إلى PDO
- خذ تطبيق مدير المهام من الدرس السابق
- حوّل جميع أكواد MySQLi إلى PDO
- استخدم المعلمات المسماة بدلاً من علامات الاستفهام
- أنشئ فئة
TaskRepositoryمع جميع طرق CRUD - نفّذ معاملات للعمليات التي تتضمن جداول متعددة
- أضف فئة
Taskمخصصة واستخدمfetchObject() - اختبر جميع الوظائف للتأكد من أنها تعمل بشكل مماثل
- قارن وضوح الكود بين إصدارات MySQLi و PDO
أفضل ممارسات PDO
- استخدم دائمًا العبارات المحضرة: لا تدمج أبدًا مدخلات المستخدم في الاستعلامات
- اضبط وضع الخطأ على الاستثناءات: فعّل
PDO::ERRMODE_EXCEPTION - عطّل التحضير المحاكى: اضبط
PDO::ATTR_EMULATE_PREPARESعلى false - استخدم المعلمات المسماة: أكثر وضوحًا من علامات الاستفهام
- اضبط وضع الجلب الافتراضي: استخدم
PDO::FETCH_ASSOCكافتراضي - استخدم المعاملات: للعمليات التي يجب أن تنجح أو تفشل معًا
- لا تستخدم الاتصالات الدائمة: ما لم يكن لديك حاجة محددة
- تعامل مع الأخطاء برشاقة: اصطد الاستثناءات وسجّل الأخطاء
الملخص
- PDO هي طبقة تجريد قاعدة بيانات تدعم أنظمة قواعد بيانات متعددة
- بخلاف MySQLi، يعمل PDO مع MySQL وPostgreSQL وSQLite والمزيد
- يدعم PDO كلاً من علامات الاستفهام (?) والمعلمات المسماة (:name)
- يقدم PDO أوضاع جلب مرنة بما في ذلك الجلب إلى فئات مخصصة
- المعاملات في PDO تضمن العمليات الذرية (تنجح جميعها أو تفشل جميعها)
- كل من MySQLi و PDO آمنان عند استخدامهما مع العبارات المحضرة
- اختر حسب احتياجاتك: MySQLi لـ MySQL فقط، PDO للمرونة
- استخدم دائمًا العبارات المحضرة بغض النظر عن الامتداد الذي تختاره