اذهب إلى المحتوى

إضافة PHP MySQLi ونظام إدارة قواعد البيانات SQLite3


سارة محمد2

واجهة 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

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...