اضبط PDO كما يُضبَط في الإنتاج، وتخلّص بنيويًا من حقن SQL، واجعل عمليات الكتابة لديك موثوقة بالمعاملات.
افتح هذا الدرس في Kodokonإن PDO هي الواجهة الموحّدة للوصول إلى قواعد البيانات: فسواء كانت MySQL أو PostgreSQL أو SQLite، تُدار جميعها بالـ API نفسها. وثلاثة إعدادات تفصل الاتصال الساذج عن اتصال الإنتاج: ERRMODE_EXCEPTION كي يُطلق كل خطأ SQL استثناءً بدلًا من الفشل بصمت، وFETCH_ASSOC افتراضيًا للحصول على نتائج نظيفة، وEMULATE_PREPARES مضبوطًا على false لعبارات مُحضَّرة يُنفّذها الخادم فعليًا. وأضِف charset=utf8mb4 مباشرةً في الـ DSN - فهو المكان الوحيد الموثوق للتصريح به.
<?php
declare(strict_types=1);
$dsn = 'mysql:host=localhost;dbname=shop'
. ';charset=utf8mb4';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, 'app_user', 'secret', $options);العبارة المُحضَّرة هي دفاعك البنيوي الوحيد ضد حقن SQL. والمبدأ: تُرسَل بنية الاستعلام أولًا إلى الخادم، بما فيها العناصر النائبة :email أو ?؛ ثم تتبعها البيانات بشكل منفصل، ولا تُفسَّر أبدًا على أنها SQL. ويبقى ربط متغير بالاستعلام بالتسلسل (concatenation)، ولو كان "مُهرَّبًا"، خطأً مهنيًا. والعناصر النائبة المُسمّاة تجعل الكود مقروءًا؛ أما الموضعية فتكفي للاستعلامات القصيرة.
<?php
declare(strict_types=1);
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION,
);
$pdo->exec(
'CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL
)'
);
$insert = $pdo->prepare(
'INSERT INTO users (email) VALUES (:email)'
);
$insert->execute(['email' => 'lea@example.com']);
$query = $pdo->prepare(
'SELECT id, email FROM users
WHERE email = :email'
);
$query->execute(['email' => 'lea@example.com']);
var_dump($query->fetch(PDO::FETCH_ASSOC));تضمن المعاملة الذرّية: فإما أن تنجح جميع عمليات الكتابة، أو لا ينجح أيّ منها. والنمط المعياري: beginTransaction()، ثم العمل داخل try، ثم commit() في نهاية الكتلة، وrollBack() في catch - ثم تعيد إطلاق الاستثناء، لأن إخفاء الفشل أسوأ من الفشل نفسه. وأبقِ المعاملات قصيرة: فكل قفل يُحتجز أثناء نداء شبكي أو عملية طويلة يُضعِف التزامن (concurrency) عبر التطبيق بأكمله.
<?php
declare(strict_types=1);
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION,
);
$pdo->exec(
'CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
)'
);
$pdo->exec(
'INSERT INTO accounts (balance)
VALUES (100), (20)'
);
$pdo->beginTransaction();
try {
$debit = $pdo->prepare(
'UPDATE accounts
SET balance = balance - :n
WHERE id = :id'
);
$debit->execute(['n' => 50, 'id' => 1]);
$credit = $pdo->prepare(
'UPDATE accounts
SET balance = balance + :n
WHERE id = :id'
);
$credit->execute(['n' => 50, 'id' => 2]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
echo 'Transfer done';:param في عبارة مُحضَّرة؟catch؟commit() لحفظ ما نجحrollBack()، ثم إعادة إطلاق الاستثناء