تابع query() در PHP
تابع query() در اکوسیستم PHP معمولاً به متدهای کتابخانههای اتصال به پایگاه داده اشاره دارد؛ ازجمله mysqli::query و PDO::query. این متدها برای اجرای دستورات SQL استفاده میشوند اما رفتار، مقدار بازگشتی و نکات امنیتی متفاوتی دارند. در این مقاله بهصورت عملی، تفاوتها، مثالها، بهترین روشها و نکات خطایابی را بررسی میکنیم.
تفاوتهای اساسی: mysqli::query vs PDO::query
هرچند هر دو متد برای اجرای کوئریها استفاده میشوند، تفاوتهای مهمی وجود دارد:
- mysqli::query در رابط MySQLi بازمیگرداند: mysqli_result برای SELECT و bool برای دستورات غیر SELECT.
- PDO::query یک PDOStatement برمیگرداند یا در صورت شکست false میدهد. PDO مستقل از درایور است و امکان استفاده از ویژگیهای پیشرفتهتر مثل prepared statements و مدیریت استثناء را دارد.
- برای ورودیهای کاربر هرگز مستقیماً از query() با رشتههای ترکیبشده استفاده نکنید؛ از prepared statements استفاده کنید تا از SQL Injection جلوگیری شود.
مثال پایه با MySQLi (روش پروسیژدال و شیءگرا)
// Object-oriented
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$result = $mysqli->query("SELECT id, name FROM users");
while ($row = $result->fetch_assoc()) {
echo $row['id'] . ': ' . $row['name'];
}
$result->free();
$mysqli->close();
توضیح: این کد با رابط شیءگرا یک کوئری SELECT اجرا میکند، نتایج را ردیفبهردیف میخواند و در پایان منابع را آزاد میکند. نکته: اگر کوئری شکست بخورد، $result مقدار false خواهد داشت؛ بهتر است قبل از خواندن نتایج خطا را بررسی کنیم.
مثال پایه با PDO
$pdo = new PDO('mysql:host=localhost;dbname=db;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->query("SELECT id, name FROM users");
foreach ($stmt as $row) {
echo $row['id'] . ': ' . $row['name'];
}
توضیح: با تنظیم PDO::ATTR_ERRMODE به PDO::ERRMODE_EXCEPTION خطاها بهصورت استثناء تولید میشوند که مدیریت آنها با try/catch سادهتر است. PDO::query یک شیء PDOStatement برمیگرداند که قابل پیمایش است.
چرا نباید query() را برای ورودیهای کاربر استفاده کنیم؟
اجرای مستقیم رشتههای SQL که با دادهٔ کاربر ترکیب شدهاند خطر SQL Injection ایجاد میکند. بهجای آن از prepared statements یا حداقل توابع escape استفاده کنید.
نمونهٔ بهبود یافته با آمادهسازی (PDO)
// امن: prepared statement در PDO
$pdo = new PDO('mysql:host=localhost;dbname=db;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE email = :email");
$stmt->execute([':email' => $userInputEmail]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
توضیح: بهجای ترکیب مستقیم مقدار ایمیل در کوئری، از prepare و حتی جایگرین نامگذاریشده استفاده شده است. این روش باعث میشود پارامترها بهصورت امن به پایگاهداده ارسال شوند و از تزریق جلوگیری گردد.
نمونهٔ بهبود یافته با MySQLi (شیءگرا)
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$stmt = $mysqli->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->bind_param('ss', $name, $email);
$name = 'Ali';
$email = 'ali@example.com';
$stmt->execute();
$stmt->close();
$mysqli->close();
توضیح: در MySQLi از bind_param برای بایند کردن متغیرها به پارامترهای سوالدار استفاده میشود. این کار نیز از SQL Injection جلوگیری میکند و برای عملیات INSERT/UPDATE مناسب است.
مقادیر بازگشتی و مدیریت خطا
| کتابخانه | مقدار بازگشتی | نکات |
|---|---|---|
| mysqli::query | mysqli_result | bool | برای SELECT شیء نتیجه، برای INSERT/UPDATE/DELETE مقدار بولین یا مقدار true/false |
| PDO::query | PDOStatement | false | در حالت ERRMODE_EXCEPTION خطا با استثناء گزارش میشود |
چند نکتهٔ عملی و بهترین شیوهها
- برای کوئریهایی که شامل ورودی کاربر هستند، از prepared statements استفاده کنید؛ حتی اگر ورودی بهظاهر بیخطر باشد.
- برای عملیات دستهای از transactions استفاده کنید تا از دست رفتن داده جلوگیری شود.
- در محیط تولید از کاراکترست UTF-8 (utf8mb4) استفاده کنید تا با ایموجی و کاراکترهای خاص مشکلی نداشته باشید.
- منابع (نتایج، prepared statements، ارتباط) را پس از اتمام آزاد یا بسته کنید تا نشت منابع رخ ندهد.
- برای کوئریهای پیچیده یا تکراری نگهداری شده یا لایهٔ مدل (ORM) را مدنظر داشته باشید تا نگهداری آسانتر شود.
مثال: استفاده از تراکنش و query()
$pdo->beginTransaction();
try {
$pdo->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$pdo->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
توضیح: این مثال تراکنشی نشان میدهد که برای عملیات چندمرحلهای باید از beginTransaction/commit/rollBack استفاده کرد تا در صورت خطا وضعیت پایگاهداده به حالت قبل برگردد. در اینجا از exec استفاده شده که برای دستورات غیر SELECT مناسب است؛ میتوان مشابه آن را با query() نیز برای SELECT بهکار برد.
خطایابی و پیامهای خطا
در MySQLi میتوانید با $mysqli->error یا $mysqli->errno خطا را بررسی کنید. در PDO اگر ERRMODE را روی EXCEPTION قرار دهید، خطاها با استثناء نمایش داده میشوند که به مدیریت بهتر منجر میشود.
جمعبندی و توصیههای نهایی
- استفاده از query() برای کوئریهای ثابت و فقط خواندنی مناسب است، اما برای ورودی کاربر از prepared statements استفاده کنید.
- PDO گزینهٔ منعطفتری است که قابلیت استفاده از چندین درایور و مدیریت استثناء را فراهم میکند.
- همیشه نتیجهٔ query() را قبل از استفاده بررسی کنید و منابع را پس از استفاده آزاد کنید.
با رعایت این نکات میتوانید کوئریهای امن، خواناتر و قابل نگهداریتری بنویسید و خطاها و آسیبپذیریهای رایج را کاهش دهید.
آیا این مطلب برای شما مفید بود ؟



