لغات أخرى:
- English documentation
- Русская документация
- Documentation française
- Deutsche Dokumentation
- Documentazione italiana
- 日本語ドキュメント
- Documentación en español
- 한국어 문서
- 简体中文文档
- 繁體中文文件
- Dokumentasi Bahasa Indonesia
- Documentação em Português (BR)
- हिंदी दस्तावेज़
- Türkçe Dokümantasyon
- Tài liệu tiếng Việt
يمكنك تنزيلها كأرشيف، أو استنساخها من هذا الموقع، أو تثبيتها عبر composer (رابط إلى packagist.org):
composer require krugozor/database
krugozor/database هي مكتبة فئات PHP >= 8.0 للعمل البسيط والمريح والسريع والآمن مع قاعدة بيانات MySQL، باستخدام امتداد PHP mysqli.
العيوب الرئيسية لجميع المكتبات للعمل مع قاعدة بيانات MySQL في PHP هي:
- الإطناب
- لمنع حقن SQL، لدى المطورين طريقتان:
- استخدام العبارات المُجهزة.
- تجاوز المعاملات التي تدخل نص استعلام SQL يدويًا. تمرير معاملات السلسلة عبر mysqli_real_escape_string، وتحويل المعاملات الرقمية المتوقعة إلى الأنواع المقابلة —
intوfloat.
- كلا النهجين لهما عيوب هائلة:
- العبارات المُجهزة مطنبة بشكل فظيع. استخدام تجريد PDO أو امتداد mysqli "خارج الصندوق"، دون تجميع جميع الطرق للحصول على البيانات من DBMS ببساطة مستحيل — للحصول على قيمة من جدول، من الضروري كتابة 5 أسطر على الأقل من الكود! وهكذا لكل استعلام!
- تجاوز المعاملات التي تدخل نص استعلام SQL يدويًا — لا يُناقش حتى. المبرمج الجيد هو مبرمج كسول. يجب أن يكون كل شيء آليًا إلى أقصى حد.
- لمنع حقن SQL، لدى المطورين طريقتان:
- استحالة الحصول على استعلام SQL للتصحيح
- لفهم سبب عدم عمل استعلام SQL في البرنامج، من الضروري تصحيحه — العثور على خطأ منطقي أو نحوي. للعثور على الخطأ، من الضروري "رؤية" استعلام SQL نفسه، الذي "اشتكت" منه قاعدة البيانات، مع المعاملات المستبدلة في نصه. أي، امتلاك SQL مُشكّل بالكامل. إذا كان المطور يستخدم PDO مع العبارات المُجهزة، فهذا... مستحيل! لا توجد آليات مريحة للغاية لذلك مُوفرة في المكتبات الأصلية. لا يبقى سوى التواء أو النظر في سجل قاعدة البيانات.
- يلغي الإطناب — بدلاً من 3 أسطر أو أكثر من الكود لتنفيذ استعلام واحد عند استخدام المكتبة "الأصلية"، تكتب واحدة فقط.
- يتجاوز جميع المعاملات التي تدخل نص الاستعلام، وفقًا لنوع العنصر النائب المحدد — حماية موثوقة من حقن SQL.
- لا يستبدل وظيفة محول mysqli "الأصلي"، بل يكمله فقط.
- قابل للتوسيع. في الأساس، توفر المكتبة فقط محللًا وتنفيذ استعلامات SQL مع حماية مضمونة من حقن SQL. يمكنك الوراثة من أي فئة في المكتبة واستخدام كل من آليات المكتبة وآليات
mysqliوmysqli_resultلإنشاء الطرق اللازمة.
معظم المجمعات لمحركات قواعد البيانات المختلفة هي كومة من التعليمات البرمجية عديمة الفائدة ذات بنية فظيعة. مؤلفوها، غير مدركين لأنفسهم للغرض العملي من مجمعاتهم، يحولونها إلى بناة استعلامات، ومكتبات ActiveRecord، وحلول ORM أخرى.
مكتبة krugozor/database ليست أيًا من هذه. إنها مجرد أداة مريحة للعمل مع SQL العادي في إطار DBMS MySQL — لا أكثر!
Placeholders (العناصر النائبة) هي علامات خاصة مكتوبة في سلسلة استعلام SQL بدلاً من القيم الصريحة (معاملات الاستعلام). يتم تمرير القيم نفسها "لاحقًا"، كوسيطات لاحقة للطريقة الرئيسية التي تنفذ استعلام SQL:
$result = $db->query(
"SELECT * FROM `users` WHERE `name` = '?s' AND `age` = ?i",
"دارتانيان", 41
);معاملات استعلام SQL التي مرت عبر نظام placeholders تتم معالجتها بواسطة آليات تجاوز خاصة، اعتمادًا على نوع العنصر النائب. أي، الآن لم تعد بحاجة إلى وضع المتغيرات في دوال التجاوز مثل mysqli_real_escape_string() أو تحويلها إلى نوع رقمي، كما كان يُفعل من قبل:
<?php
// في السابق، قبل كل استعلام إلى DBMS كنا نفعل
// تقريبًا هذا (والكثيرون لا يزالون لا يفعلون "هذا"):
$id = (int) $_POST['id'];
$value = mysqli_real_escape_string($mysql, $_POST['value']);
$result = mysqli_query($mysql, "SELECT * FROM `t` WHERE `f1` = '$value' AND `f2` = $id");الآن أصبحت كتابة الاستعلامات سهلة وسريعة، والأهم من ذلك، تمنع مكتبة krugozor/database تمامًا جميع عمليات حقن SQL المحتملة.
أنواع العناصر النائبة وأغراضها موصوفة أدناه. قبل التعرف على أنواع العناصر النائبة، من الضروري فهم كيفية عمل آلية المكتبة.
PHP هي لغة ضعيفة الكتابة وخلال تطوير هذه المكتبة نشأت معضلة أيديولوجية. تخيل أن لدينا جدولًا بالبنية التالية:
`name` varchar not null
`flag` tinyint not nullويجب على المكتبة (لسبب ما، ربما لا يعتمد على المطور) تنفيذ الاستعلام التالي:
$db->query(
"INSERT INTO `t` SET `name` = '?s', `flag` = ?i",
null, false
);في هذا المثال، هناك محاولة لكتابة القيمة null في حقل نص not null name،
والنوع المنطقي false في الحقل الرقمي flag. ماذا نفعل في هذه الحالة؟
- من المسؤول عن التحقق من صحة معاملات الاستعلام - كود العميل أم المكتبة؟
- هل من الضروري في هذه الحالة مقاطعة تنفيذ البرنامج أم ربما يجب تطبيق بعض المعالجات لكتابة البيانات في قاعدة البيانات؟
- هل يمكننا تفسير القيمة
falseلعمودtinyintكقيمة0وnullكسلسلة فارغة لعمودname؟ - كيف يمكننا تبسيط أو توحيد هذه المشكلة في كودنا؟
بالنظر إلى الأسئلة المطروحة، تم اتخاذ قرار بتنفيذ وضعين للتشغيل في هذه المكتبة.
- Mysql::MODE_STRICT — وضع المطابقة الصارمة بين نوع العنصر النائب ونوع الوسيطة.
في وضع
Mysql::MODE_STRICTيجب أن يتطابق نوع الوسيطة مع نوع العنصر النائب. على سبيل المثال، محاولة تمرير القيمة55.5أو'55.5'كوسيطة للعنصر النائب من النوع الصحيح?iستؤدي إلى استثناء:
// تعيين الوضع الصارم
$db->setTypeMode(Mysql::MODE_STRICT);
// لن يتم تنفيذ هذا التعبير، سيتم طرح استثناء:
// محاولة تحديد قيمة من النوع "double" للعنصر النائب من النوع "integer" في قالب الاستعلام "SELECT ?i"
$db->query('SELECT ?i', 55.5);- Mysql::MODE_TRANSFORM — وضع تحويل الوسيطة إلى نوع العنصر النائب في حالة عدم تطابق نوع العنصر النائب ونوع الوسيطة. وضع
Mysql::MODE_TRANSFORMمعين افتراضيًا وهو وضع "متسامح" — في حالة عدم تطابق نوع العنصر النائب ونوع الوسيطة لا يولد استثناءً، بل يحاول تحويل الوسيطة إلى نوع العنصر النائب المطلوب عبر لغة PHP نفسها. بالمناسبة، أنا، كمؤلف المكتبة، أستخدم هذا الوضع دائمًا، الوضع الصارم (Mysql::MODE_STRICT) لم أستخدمه أبدًا في العمل الحقيقي، لكن ربما تحتاج إليه.
التحويلات التالية مسموح بها في وضع Mysql::MODE_TRANSFORM:
- يتم التحويل إلى نوع
int(العنصر النائب?i)- الأرقام العشرية، الممثلة في كل من النوع
stringوالنوعdouble - يتم تحويل
boolTRUE إلىint(1)، ويتم تحويل FALSE إلىint(0) - يتم تحويل
nullإلىint(0)
- الأرقام العشرية، الممثلة في كل من النوع
- يتم التحويل إلى نوع
double(العنصر النائب?d)- الأعداد الصحيحة، الممثلة في كل من النوع
stringوالنوعint - يتم تحويل
boolTRUE إلىfloat(1)، ويتم تحويل FALSE إلىfloat(0) - يتم تحويل
nullإلىfloat(0)
- الأعداد الصحيحة، الممثلة في كل من النوع
- يتم التحويل إلى نوع
string(العنصر النائب?s)- يتم تحويل
boolTRUE إلىstring(1) "1"، ويتم تحويل FALSE إلىstring(1) "0". يختلف هذا السلوك عن تحويل النوعboolإلىintفي PHP، لأنه غالبًا، في الممارسة العملية، يتم كتابة النوع المنطقي في MySQL على وجه التحديد كرقم. - يتم تحويل القيمة من النوع
numericإلى سلسلة وفقًا لقواعد تحويل PHP - يتم تحويل
nullإلىstring(0) ""
- يتم تحويل
- يتم التحويل إلى نوع
null(العنصر النائب?n)- أي وسيطة.
- غير مسموح بالتحويلات للمصفوفات والكائنات والموارد.
$db->query(
'SELECT * FROM `users` WHERE `id` = ?i', 123
);استعلام SQL بعد تحويل القالب:
SELECT * FROM `users` WHERE `id` = 123تحذير! إذا كنت تعمل مع أرقام تتجاوز PHP_INT_MAX، فإن:
- قم بتشغيلها حصريًا كسلاسل في برامجك.
- لا تستخدم هذا العنصر النائب، استخدم عنصر سلسلة النائب
?s(انظر أدناه). المشكلة هي أن الأرقام التي تتجاوزPHP_INT_MAX، يفسرها PHP كأرقام عشرية. سيحاول محلل المكتبة تحويل المعامل إلى النوعint، مما يؤدي إلى «ستكون النتيجة غير محددة، لأن float لا يحتوي على دقة كافية لإرجاع النتيجة الصحيحة. في هذه الحالة لن يتم إصدار تحذير أو حتى إشعار!» — php.net.
$db->query(
'SELECT * FROM `prices` WHERE `cost` IN (?d, ?d)',
12.56, '12.33'
);استعلام SQL بعد تحويل القالب:
SELECT * FROM `prices` WHERE `cost` IN (12.56, 12.33)تحذير! إذا كنت تستخدم المكتبة للعمل مع نوع البيانات double، فقم بتعيين اللغة المناسبة بحيث يكون الفاصل بين الجزء الصحيح والكسري هو نفسه على مستوى PHP ومستوى DBMS.
يتم تجاوز قيم الوسيطات باستخدام طريقة mysqli::real_escape_string():
$db->query(
'SELECT "?s"',
"أنتم جميعًا حمقى، وأنا دارتانيان!"
);استعلام SQL بعد تحويل القالب:
SELECT "أنتم جميعًا حمقى، وأنا دارتانيان!"يتم تجاوز قيم الوسيطات باستخدام طريقة mysqli::real_escape_string() + تجاوز الأحرف الخاصة المستخدمة في عامل LIKE (% و _):
$db->query('SELECT "?S"', '% _');استعلام SQL بعد تحويل القالب:
SELECT "\% \_"يتم تجاهل قيم أي وسيطات، ويتم استبدال العناصر النائبة بالسلسلة NULL في استعلام SQL:
$db->query('SELECT ?n', 123);استعلام SQL بعد تحويل القالب:
SELECT NULLحيث الرمز * هو أحد العناصر النائبة:
i(عنصر نائب للأعداد الصحيحة)d(عنصر نائب للأرقام العشرية)s(عنصر نائب لنوع السلسلة)
قواعد التحويل والتجاوز هي نفسها للأنواع القياسية الفردية الموصوفة أعلاه. مثال:
$db->query(
'INSERT INTO `test` SET ?Ai',
['first' => '123', 'second' => 456]
);استعلام SQL بعد تحويل القالب:
INSERT INTO `test` SET `first` = "123", `second` = "456"حيث * هو أحد الأنواع:
i(عنصر نائب للأعداد الصحيحة)d(عنصر نائب للأرقام العشرية)s(عنصر نائب لنوع السلسلة)
قواعد التحويل والتجاوز هي نفسها للأنواع القياسية الفردية الموصوفة أعلاه. مثال:
$db->query(
'SELECT * FROM `test` WHERE `id` IN (?ai)',
[123, 456]
);استعلام SQL بعد تحويل القالب:
SELECT * FROM `test` WHERE `id` IN ("123", "456")?A[?n, ?s, ?i, ...] — عنصر نائب لمجموعة ترابطية مع تحديد صريح للنوع وعدد الوسيطات، ينتج تسلسل أزواج مفتاح = قيمة
مثال:
$db->query(
'INSERT INTO `users` SET ?A[?i, "?s"]',
['age' => 41, 'name' => "دارتانيان"]
);استعلام SQL بعد تحويل القالب:
INSERT INTO `users` SET `age` = 41,`name` = "دارتانيان"مثال:
$db->query(
'SELECT * FROM `users` WHERE `name` IN (?a["?s", "?s"])',
["الماركيز داركيان", "دارتانيان"]
);استعلام SQL بعد تحويل القالب:
SELECT * FROM `users` WHERE `name` IN ("الماركيز داركيان", "دارتانيان")هذا العنصر النائب مخصص للحالات التي يتم فيها تمرير اسم الجدول أو الحقل في الاستعلام عبر معامل. يتم تأطير أسماء الحقول والجداول برمز "backtick":
$db->query(
'SELECT ?f FROM ?f',
'name',
'database.table_name'
);استعلام SQL بعد تحويل القالب:
SELECT `name` FROM `database`.`table_name`تتطلب المكتبة من المبرمج الامتثال لصياغة SQL. هذا يعني أن الاستعلام التالي لن يعمل:
$db->query(
'SELECT CONCAT("Hello, ", ?s, "!")',
'world'
);— يجب أن يكون العنصر النائب ?s بين علامات اقتباس مفردة أو مزدوجة:
$db->query(
'SELECT concat("Hello, ", "?s", "!")',
'world'
);استعلام SQL بعد تحويل القالب:
SELECT concat("Hello, ", "world", "!")بالنسبة لأولئك المعتادين على العمل مع PDO، قد يبدو هذا غريبًا، ولكن تنفيذ آلية تحدد ما إذا كانت قيمة العنصر النائب في حالة واحدة تحتاج إلى علامات اقتباس أم لا هي مهمة غير تافهة للغاية تتطلب كتابة محلل كامل.
انظر في الملف ../console/tests.php