واجهة mysqli هي تحسين (وتعني إضافة MySQL محسَّنة "MySQL Improvement extension") لواجهة MySQL التي أُهملت في الإصدار 5.5 وحُذِفت في الإصدار 7.0. طُوِّرت إضافة mysqli
المعروفة أيضًا باسم إضافة MySQL المحسَّنة للاستفادة من إيجابيات الميزات الجديدة الموجودة في إصدارات أنظمة MySQL بدءًا من الإصدار 4.1.3 وما بعد. ضُمِّنت إضافة mysqli
في الإصدار PHP 5 وما بعده.
إغلاق الاتصال
يُنصَح بإغلاق الاتصال بعد الانتهاء من الاستعلام من قاعدة البيانات لتحرير الموارد.
النمط الكائني التوجه
$conn->close();
النمط الإجرائي
mysqli_close($conn);
ملاحظة: سيُغلق الاتصال مع الخادم حالما ينتهي تنفيذ السكربت ما لم يُغلق مبكرًا باستدعاء دالة إغلاق الاتصال بشكلٍ صريح.
حالة استخدام: يجب بكل تأكيد إغلاق الاتصال إذا احتوى السكربت على كمية معقولة من المعالجة بعد جلب النتيجة واستعاد مجموعة النتائج كاملةً، إذا لم نقم بذلك فهناك احتمال أن يصل خادم MySQL إلى حد الاتصال عندما يكون خادم الويب قيد الاستخدام المكثف.
اتصال MySQLi
النمط الكائني التوجه
الاتصال بالخادم:
$conn = new mysqli("localhost","my_user","my_password");
ضبط قاعدة البيانات الافتراضية:
$conn->select_db("my_db");
الاتصال بقاعدة البيانات:
$conn = new mysqli("localhost","my_user","my_password","my_db");
النمط الإجرائي
الاتصال بالخادم:
$conn = mysqli_connect("localhost","my_user","my_password");
ضبط قاعدة البيانات الافتراضية:
mysqli_select_db($conn, "my_db");
الاتصال بقاعدة البيانات:
$conn = mysqli_connect("localhost","my_user","my_password","my_db");
التحقق من الاتصال بقاعدة البيانات
النمط الكائني التوجه
if ($conn->connect_errno > 0) { trigger_error($db->connect_error); } // else: successfully connected
النمط الإجرائي
if (!$conn) { trigger_error(mysqli_connect_error()); } // else: successfully connected
تمرير حلقة على نتائج MySQLi
تجعل PHP من السهل الحصول على البيانات من النتائج وتكرارها باستخدام تعليمة while
، وترجع false
عندما تفشل في الحصول على السطر التالي وتنتهي الحلقة. تعمل هذه الأمثلة مع:
- mysqli_fetch_assoc - مصفوفة ترابطية مع أسماء الأعمدة كمفاتيح.
-
mysqli_fetch_object - كائن
stdClass
مع أسماء الأعمدة كمتغيرات. - mysqli_fetch_array - مصفوفة ترابطية وعددية (يمكنك استخدام وسيط لتحصل على إحداها)
- mysqli_fetch_row - مصفوفة عددية.
النمط الكائني التوجه
while($row = $result->fetch_assoc()) { var_dump($row); }
النمط الإجرائي
while($row = mysqli_fetch_assoc($result)) { var_dump($row); }
يمكننا استخدام الشيفرة التالية للحصول على معلومات دقيقة من النتائج:
while ($row = $result->fetch_assoc()) { echo 'Name and surname: '.$row['name'].' '.$row['surname'].'<br>'; // طباعة معلومات من العمود 'age' echo 'Age: '.$row['age'].'<br>'; }
تعليمات التحضير في MySQLi
يمكنك قراءة المزيد حول منع حقن SQL مع الاستعلامات التي تحوي معاملات لمعرفة سبب مساعدة تعليمات التحضير لك في تأمين تعليمات SQL ضد هجمات حقن SQL.
المتغير $conn
هو كائن MySQLi في الشيفرات التالية وفي المثالين نفرض أنّ $sql
:
$sql = "SELECT column_1 FROM table WHERE column_2 = ? AND column_3 > ?";
تمثّل ?
القيم التي سنوفرها لاحقًا، لاحظ أننا لا نحتاج علامات الاقتباس للنصوص البديلة بغض النظر عن النوع، يمكننا أيضًا أن نوفر النصوص البديلة فقط في جزء البيانات من الاستعلام أي SET
وVALUES
وWHERE
، ولا يمكن استخدام النصوص البديلة في الأجزاء SELECT
أو FROM
.
النمط الكائني التوجه
if ($stmt = $conn->prepare($sql)) { $stmt->bind_param("si", $column_2_value, $column_3_value); $stmt->execute(); $stmt->bind_result($column_1); $stmt->fetch(); // يمكننا الآن استخدام المتغير $column_1 مثل أي متغير PHP آخر $stmt->close(); }
النمط الإجرائي
if ($stmt = mysqli_prepare($conn, $sql)) { mysqli_stmt_bind_param($stmt, "si", $column_2_value, $column_3_value); mysqli_stmt_execute($stmt); // جلب البيانات هنا mysqli_stmt_close($stmt); }
يُحدَّد المعامل الأول للتابع $stmt->bind_param
أو المعامل الثاني للتابع mysqli_stmt_bind_param
وفقًا لنمط البيانات للمعامل المقابل في استعلام SQL:
المعامل | نوع البيانات للمعامل المقيّد |
i | integer عدد صحيح |
d | double عدد عشري |
s | string سلسلة نصية |
b | blob كائن بيانات ثنائية |
يجب أن تكون قائمة المعاملات بنفس الترتيب الموجود في الاستعلام، تعني si
في هذا المثال أنّ المعامل الأول (column_2 = ?)
هو سلسلة نصية والمعامل الثاني (column_3 > ?)
عدد صحيح.
سلاسل الهروب النصية
سلاسل الهروب النصية (Escaping Strings) طريقة قديمة (وأقل أمنًا) لتأمين البيانات لإدراجها في استعلام، تعمل باستخدام دالة MySQL mysql_real_escape_string()
لمعالجة وتعقيم البيانات (أي أنّ PHP لا تقوم بعملية الهروب). توفر واجهة برمجة التطبيقات MySQLi الوصول المباشر لهذه الدالة.
$escaped = $conn->real_escape_string($_GET['var']); //أو $escaped = mysqli_real_escape_string($conn, $_GET['var']);
عند هذه النقطة يصبح لديك سلسلة نصية تعدها MySQL آمنة للاستخدام في استعلام مباشر.
$sql = 'SELECT * FROM users WHERE username = "' . $escaped . '"'; $result = $conn->query($sql);
إذًا لم لا تُعدّ هذه الطريقة آمنة مثل تعليمات التحضير؟ لأنه يوجد عدة طرق لخداع MySQL لإنتاج سلسلة نصية تُعد آمنة. إليك المثال التالي:
$id = mysqli_real_escape_string("1 OR 1=1"); $sql = 'SELECT * FROM table WHERE id = ' . $id;
لا يمثّل التعبير 1 OR 1=1
بيانات ستهرّبها MySQL، لكنه لا يزال يمثّل حقن SQL، يوجد أمثلة أخرى تمثّل حالات تُعاد فيها بيانات غير آمنة، المشكلة هي أنّ دالة الهروب في MySQL صُممت لجعل البيانات تتوافق مع صيغة SQL وليس للتأكد من أنّ MySQL لا تتمكن من خلط بيانات المستخدم من أجل تعليمات SQL.
تنقيح أخطاء SQL في MySQLi
الاستعلام في الشيفرة التالية سيفشل (استخدمنا المتغير $conn للاتصال بالخادم كما وضحنا سابقًا):
$result = $conn->query('SELECT * FROM non_existent_table');
نتيجة $result
هي false
وهذا لا يساعدنا على معرفة الخطأ، لحسن الحظ يمكن لمتغير الاتصال $conn
أن يخبرنا عن الفشل:
trigger_error($conn->error);
أو بالنمط الإجرائي:
trigger_error(mysqli_error($conn));
عندها ستحصل على خطأ مشابه لما يلي:
Table 'my_db.non_existent_table' doesn't exist
استعلام MySQLi
تأخذ الدالة query
سلسلة SQL صحيحة وتنفّذها مباشرةً على الاتصال بقاعدة البيانات $conn
.
النمط الكائني التوجه
$result = $conn->query("SELECT * FROM `people`");
النمط الإجرائي
$result = mysqli_query($conn, "SELECT * FROM `people`");
سيظهر تحذير، المشكلة الشائعة هنا هي أنّك تنفّذ الشيفرة ببساطة وتتوقعها أن تعمل (تعيد مثلًا كائنًا من الصنف mysqli_stmt)، بما أنّ هذه الدالة تأخذ سلسلة نصية فقط فأنت تبني الاستعلام أولًا وإذا وُجدَت أي أخطاء في SQL سيفشل مصرِّف MySQL وعندها تعيد الدالة القيمة false
.
// سيفشل الاستعلام التالي $result = $conn->query('SELECT * FROM non_existent_table'); $row = $result->fetch_assoc();
ستنتج الشيفرة السابقة خطأ E_FATAL
لأنّ نتيجة $result
هي false
وليس كائنًا.
PHP Fatal error: Call to a member function fetch_assoc() on a non-object
الخطأ الإجرائي مشابه لكنه ليس خطأً فادحًا لأننا نخترق توقعات الدالة فقط.
// نفس الاستعلام السابق $row = mysqli_fetch_assoc($result);
ستحصل على الرسالة التالية من PHP:
mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given
يمكنك تجنب هذا باستخدام شيفرة الاختبار التالية:
if($result) $row = mysqli_fetch_assoc($result);
كيفية الحصول على البيانات من تعليمات التحضير
تعليمات التحضير
اطّلع على تعليمات التحضير في mysqli لمعرفة كيفية تحضير وتنفيذ استعلام.
ربط النتائج
- النمط الكائني التوجه
$stmt->bind_result($forename);
- النمط الإجرائي
mysqli_stmt_bind_result($stmt, $forename);
مشكلة استخدام الدالة bind_result
أنّها تتطلب تعليمة لتحديد الأعمدة التي ستُستخدم، أي أنّه كي تعمل الشيفرة السابقة يجب أن يبدو الاستعلام بالشكل SELECT forename FROM users
، لتضمين المزيد من الأعمدة يمكنك إضافتها كمعاملات للدالة bind_result
(وتأكد من إضافتهم إلى استعلام SQL).
نُسند في كلتا الحالتين العمود forename
للمتغير $forename
، تأخذ هذه الدالة عدد وسائط بعدد الأعمدة التي تريد إسنادها، يتم هذا الإسناد مرة واحدة بما أنّ الدالة تُربَط بالمرجع، لذا يمكننا تمرير حلقة بالشكل التالي:
- النمط الكائني التوجه
while ($stmt->fetch()) echo "$forename<br />";
- النمط الإجرائي
while (mysqli_stmt_fetch($stmt)) echo "$forename<br />";
العيب في هذا أنّك تحتاج لإسناد الكثير من المتغيرات في وقت واحد وهذا يجعل تتبع الاستعلامات الكبيرة أمرًا صعبًا، إذا كان لديك محرك MySQL أساسي (mysqlnd) مثبّت فإنّ كل ما تحتاجه هو استخدام get_result.
- النمط الكائني التوجه
$result = $stmt->get_result();
- النمط الإجرائي
$result = mysqli_stmt_get_result($stmt);
يعدّ هذا سهلًا لأننا نحصل على كائن من الصنف mysqli_result? وهو نفس الكائن الذي تعيده mysqli_query
أي أنّه يمكنك استخدام حلقة لعرض النتيجة.
ماذا لو لم نستطع تثبيت mysqlnd
؟
يمكنك الاطلاع على جواب مناسب من هنا.
يمكن أن تؤدي هذه الدالة مهمة get_result
بدون أن تكون مثبّتة على الخادم، فهي ببساطة تكرر النتائج وتبني مصفوفة ترابطية.
function get_result(\mysqli_stmt $statement) { $result = array(); $statement->store_result(); for ($i = 0; $i < $statement->num_rows; $i++) { $metadata = $statement->result_metadata(); $params = array(); while ($field = $metadata->fetch_field()) { $params[] = &$result[$i][$field->name]; } call_user_func_array(array($statement, 'bind_result'), $params); $statement->fetch(); } return $result; }
يمكننا بعدها استخدام الدالة للحصول على النتائج كما لو أننا نستخدم mysqli_fetch_assoc()
:
<?php $query = $mysqli->prepare("SELECT * FROM users WHERE forename LIKE ?"); $condition = "J%"; $query->bind_param("s", $condition); $query->execute(); $result = get_result($query); while ($row = array_shift($result)) { echo $row["id"] . ' - ' . $row["forename"] . ' ' . $row["surname"] . '<br>'; }
سيظهر لنا نفس الخرج في حالة استخدام محرك mysqlnd باستثناء أنّه لا يحتاج للتثبيت، وهذا الحل مناسب جداً إذا كنت لا تستطيع تثبيت المحرك.
إضافة معرف في MySQLi
استعادة آخر معرّف مولَّد من استعلام INSERT
على جدول فيه عمود AUTO_INCREMENT
.
- النمط الكائني التوجه
$id = $conn->insert_id;
- النمط الإجرائي
$id = mysqli_insert_id($conn);
ترجع الشيفرة السابقة القيمة صفر إذا لم يكن هناك استعلام سابق على الاتصال أو إذا لم يحدّث الاستعلام قيمة AUTO_INCREMENT
.
إضافة معرّف عند تحديث الأسطر
لا تُرجع تعليمة UPDATE
معرّف السطر المُضاف في الحالة العادية، إذ يُرجَع المعرّف AUTO_INCREMENT
عند حفظ سطر جديد فقط (أو إضافته)، يمكن استخدام الصياغة INSERT ... ON DUPLICATE KEY UPDATE
للتحديث مما يجعل التحديثات تطرأ على المعرِّف الجديد.
مثال:
CREATE TABLE iodku ( id INT AUTO_INCREMENT NOT NULL, name VARCHAR(99) NOT NULL, misc INT NOT NULL, PRIMARY KEY(id), UNIQUE(name) ) ENGINE=InnoDB; INSERT INTO iodku (name, misc) VALUES ('Leslie', 123), ('Sally', 456); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0
id | name | misc |
1 | Leslie | 123 |
2 | Sally | 456 |
في حال حدّثَ الجدول IODKU
واستعاد LAST_INSERT_ID()
المعرِّف المرتبط:
$sql = "INSERT INTO iodku (name, misc) VALUES ('Sally', 3333) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), misc = VALUES(misc)"; $conn->query($sql); $id = $conn->insert_id;
يجب أن تحدّث الشيفرة السابقة السطر الثاني من الجدول وتعيد القيمة الحالية للمعرِّف (2).
الحالة التي يحدث فيها إضافة سطر وتعيد LAST_INSERT_ID()
المعرِّف الجديد:
$sql = "INSERT INTO iodku (name, misc) VALUES ('Dana', 789) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), misc = VALUES(misc); $conn->query($sql); $id = $conn->insert_id;
يجب أن تضيف الشيفرة السابقة سطرًا جديدًا إلى الجدول وتعيد القيمة الأخيرة للمعرِّف (3). ينتج عن الاستعلام:
SELECT * FROM iodku;
الجدول التالي:
id | name | misc |
1 | Leslie | 123 |
2 | Sally | 3333 |
3 | Dana | 789 |
درس سريع لمكتبة SQLite3
إليك مثال كامل عن جميع واجهات برمجة التطبيقات الشائعة الاستخدام المرتبطة بمكتبة SQLite3
، بهدف جعلك تعمل بسرعة كبيرة ويمكنك أيضاً الحصول على ملف PHP قابل للتنفيذ لهذا الدرس.
إنشاء/فتح قاعدة بيانات
ننشئ قاعدة بيانات أولاً، ننشئها فقط إذا لم يكن الملف موجوداً ونفتحها للقراءة/الكتابة، امتداد الملف يعود لك لكن الشائع هو استخدام الامتداد .sqlite
الأكثر إيضاحًا.
$db = new SQLite3('analytics.sqlite', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
إنشاء جدول
$db->query('CREATE TABLE IF NOT EXISTS "visits" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" INTEGER, "url" VARCHAR, "time" DATETIME )');
إضافة عينة بيانات
يُنصح بإحاطة الاستعلامات المرتبطة ضمن عملية (transaction) (مع الكلمات المفتاحية BEGIN
وCOMMIT
) حتى لو لم تكن مهتمًا بالترابط، إذا لم تفعل ذلك فإنّ SQLite
ستحيط كل استعلام مفرد بعملية بشكلٍ تلقائي مما يؤدي إلى البطء الشديد. قد تُفاجئ بسبب بطء تعليمات INSERT
إذا كنت جديدًا على استخدام SQLite
.
$db->exec('BEGIN'); $db->query('INSERT INTO "visits" ("user_id", "url", "time") VALUES (42, "/test", "2017-01-14 10:11:23")'); $db->query('INSERT INTO "visits" ("user_id", "url", "time") VALUES (42, "/test2", "2017-01-14 10:11:44")'); $db->exec('COMMIT');
يمكنك القيام بإضافة بيانات قد لا تكون آمنة مع تعليمة تحضير، باستخدام المعاملات ذات الأسماء (named parameters):
$statement = $db->prepare('INSERT INTO "visits" ("user_id", "url", "time") VALUES (:uid, :url, :time)'); $statement->bindValue(':uid', 1337); $statement->bindValue(':url', '/test'); $statement->bindValue(':time', date('Y-m-d H:i:s')); $statement->execute(); you can reuse the statement with different values
جلب البيانات
لنجلب زيارات اليوم للمستخدم رقم 42، سنستخدم تعليمة التحضير من جديد لكن هذه المرة مع معاملات ذات أسماء وهذا أكثر إيجازًا:
$statement = $db->prepare('SELECT * FROM "visits" WHERE "user_id" = ? AND "time" >= ?'); $statement->bindValue(1, 42); $statement->bindValue(2, '2017-01-14'); $result = $statement->execute(); echo "Get the 1st row as an associative array:\n"; print_r($result->fetchArray(SQLITE3_ASSOC)); echo "\n"; echo "Get the next row as a numeric array:\n"; print_r($result->fetchArray(SQLITE3_NUM)); echo "\n";
ملاحظة: تُرجع الدالة fetchArray()
القيمة false
إذا لم يكن هناك المزيد من الأسطر، يمكنك الاستفادة من هذا في حلقة while
.
حرّر الذاكرة - لا يحدث هذا تلقائيًا أثناء تشغيل السكربت.
$result->finalize();
الاختزالات
إليك اختزال مفيد لجلب سطر واحد كمصفوفة ترابطية، يعني المعامل الثاني أننا نريد كل الأعمدة المُختارة. انتبه إلى أنّ هذا الاختزال لا يدعم ربط المعاملات لكن يمكنك بدلًا من ذلك الهروب من السلاسل النصية، ضع القيم دائمًا بين علامات اقتباس مفردة إذ تستخدم علامات الاقتباس المزدوجة لأسماء الجداول والأعمدة (بشكل مشابه لعلامات الاقتباس المائلة في MySQL).
$query = 'SELECT * FROM "visits" WHERE "url" = \'' . SQLite3::escapeString('/test') . '\' ORDER BY "id" DESC LIMIT 1'; $lastVisit = $db->querySingle($query, true); echo "Last visit of '/test':\n"; print_r($lastVisit); echo "\n";
اختزال آخر مفيد لاستعادة قيمة واحدة فقط.
$userCount = $db->querySingle('SELECT COUNT(DISTINCT "user_id") FROM "visits"'); echo "User count: $userCount\n"; echo "\n";
التنظيف
في النهاية، أغلق قاعدة البيانات على الرغم من أنّ هذا يتم تلقائيًا عندما ينتهي السكربت.
$db->close();
الاستعلام من قاعدة بيانات
<?php // إنشاء كائن SQLite3 جديد من ملف قاعدة البيانات على الخادم $database = new SQLite3('mysqlitedb.db'); // الاستعلام من قاعدة البيانات باستخدام SQL $results = $database->query('SELECT bar FROM foo'); // التكرار على كل النتائج وإظهارهم على الصفحة while ($row = $results->fetchArray()) { var_dump($row); } ?>
استعادة نتيجة واحدة فقط
بالإضافة إلى استخدام تعليمات LIMIT
في SQL، يمكنك استخدام الدالة querySingle
في SQLite3
لاستعادة سطر واحد أو العمود الأول.
<?php $database = new SQLite3('mysqlitedb.db'); //(1) $database->querySingle('SELECT column1Name FROM table WHERE column2Name=1'); // (2) $database->querySingle('SELECT column1Name, column2Name FROM user WHERE column3Name=1', true);
في الموضع (1) بدون ضبط المعامل الثاني الاختياري للقيمة true
سيُرجع الاستعلام العمود الأول من السطر الأول للنتائج فقط ويكون من نفس نوع columnName
.
في الموضع (2) مع المعامل الاختياري entire_row
سيرجع هذا الاستعلام مصفوفة من كامل السطر الأول من نتائج الاستعلام.
ترجمة -وبتصرف- للفصول من كتاب PHP Notes for Professionals book
اقرأ أيضًا
- المقال التالي: استخدام MongoDB و Redis في PHP
- المقال السابق: المقابس (sockets) في PHP
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.