Skip to content

Latest commit

 

History

History
339 lines (259 loc) · 18.4 KB

File metadata and controls

339 lines (259 loc) · 18.4 KB

لغات أخرى:


الحصول على المكتبة

يمكنك تنزيلها كأرشيف، أو استنساخها من هذا الموقع، أو تثبيتها عبر composer (رابط إلى packagist.org):

composer require krugozor/database

ما هي krugozor/database؟

krugozor/database هي مكتبة فئات PHP >= 8.0 للعمل البسيط والمريح والسريع والآمن مع قاعدة بيانات MySQL، باستخدام امتداد PHP mysqli.

لماذا نحتاج إلى فئة مخصصة لـ MySQL إذا كان PHP يحتوي بالفعل على تجريد PDO وامتداد mysqli؟

العيوب الرئيسية لجميع المكتبات للعمل مع قاعدة بيانات MySQL في PHP هي:

  • الإطناب
    • لمنع حقن SQL، لدى المطورين طريقتان:
      • استخدام العبارات المُجهزة.
      • تجاوز المعاملات التي تدخل نص استعلام SQL يدويًا. تمرير معاملات السلسلة عبر mysqli_real_escape_string، وتحويل المعاملات الرقمية المتوقعة إلى الأنواع المقابلة — int و float.
    • كلا النهجين لهما عيوب هائلة:
      • العبارات المُجهزة مطنبة بشكل فظيع. استخدام تجريد PDO أو امتداد mysqli "خارج الصندوق"، دون تجميع جميع الطرق للحصول على البيانات من DBMS ببساطة مستحيل — للحصول على قيمة من جدول، من الضروري كتابة 5 أسطر على الأقل من الكود! وهكذا لكل استعلام!
      • تجاوز المعاملات التي تدخل نص استعلام SQL يدويًا — لا يُناقش حتى. المبرمج الجيد هو مبرمج كسول. يجب أن يكون كل شيء آليًا إلى أقصى حد.
  • استحالة الحصول على استعلام SQL للتصحيح
    • لفهم سبب عدم عمل استعلام SQL في البرنامج، من الضروري تصحيحه — العثور على خطأ منطقي أو نحوي. للعثور على الخطأ، من الضروري "رؤية" استعلام SQL نفسه، الذي "اشتكت" منه قاعدة البيانات، مع المعاملات المستبدلة في نصه. أي، امتلاك SQL مُشكّل بالكامل. إذا كان المطور يستخدم PDO مع العبارات المُجهزة، فهذا... مستحيل! لا توجد آليات مريحة للغاية لذلك مُوفرة في المكتبات الأصلية. لا يبقى سوى التواء أو النظر في سجل قاعدة البيانات.

الحل: krugozor/database — فئة للعمل مع MySQL

  1. يلغي الإطناب — بدلاً من 3 أسطر أو أكثر من الكود لتنفيذ استعلام واحد عند استخدام المكتبة "الأصلية"، تكتب واحدة فقط.
  2. يتجاوز جميع المعاملات التي تدخل نص الاستعلام، وفقًا لنوع العنصر النائب المحدد — حماية موثوقة من حقن SQL.
  3. لا يستبدل وظيفة محول mysqli "الأصلي"، بل يكمله فقط.
  4. قابل للتوسيع. في الأساس، توفر المكتبة فقط محللًا وتنفيذ استعلامات SQL مع حماية مضمونة من حقن SQL. يمكنك الوراثة من أي فئة في المكتبة واستخدام كل من آليات المكتبة وآليات mysqli و mysqli_result لإنشاء الطرق اللازمة.

ما الذي ليست عليه مكتبة krugozor/database؟

معظم المجمعات لمحركات قواعد البيانات المختلفة هي كومة من التعليمات البرمجية عديمة الفائدة ذات بنية فظيعة. مؤلفوها، غير مدركين لأنفسهم للغرض العملي من مجمعاتهم، يحولونها إلى بناة استعلامات، ومكتبات ActiveRecord، وحلول ORM أخرى.

مكتبة krugozor/database ليست أيًا من هذه. إنها مجرد أداة مريحة للعمل مع SQL العادي في إطار DBMS MySQL — لا أكثر!

ما هي placeholders (العناصر النائبة)؟

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

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
    • يتم تحويل bool TRUE إلى int(1)، ويتم تحويل FALSE إلى int(0)
    • يتم تحويل null إلى int(0)
  • يتم التحويل إلى نوع double (العنصر النائب ?d)
    • الأعداد الصحيحة، الممثلة في كل من النوع string والنوع int
    • يتم تحويل bool TRUE إلى float(1)، ويتم تحويل FALSE إلى float(0)
    • يتم تحويل null إلى float(0)
  • يتم التحويل إلى نوع string (العنصر النائب ?s)
    • يتم تحويل bool TRUE إلى string(1) "1"، ويتم تحويل FALSE إلى string(1) "0". يختلف هذا السلوك عن تحويل النوع bool إلى int في PHP، لأنه غالبًا، في الممارسة العملية، يتم كتابة النوع المنطقي في MySQL على وجه التحديد كرقم.
    • يتم تحويل القيمة من النوع numeric إلى سلسلة وفقًا لقواعد تحويل PHP
    • يتم تحويل null إلى string(0) ""
  • يتم التحويل إلى نوع null (العنصر النائب ?n)
    • أي وسيطة.
  • غير مسموح بالتحويلات للمصفوفات والكائنات والموارد.

ما أنواع العناصر النائبة التي توفرها المكتبة؟

?i — عنصر نائب للأعداد الصحيحة

$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.

?d — عنصر نائب للأرقام العشرية

$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.

?s — عنصر نائب لنوع السلسلة

يتم تجاوز قيم الوسيطات باستخدام طريقة mysqli::real_escape_string():

$db->query(
    'SELECT "?s"',
    "أنتم جميعًا حمقى، وأنا دارتانيان!"
);

استعلام SQL بعد تحويل القالب:

SELECT "أنتم جميعًا حمقى، وأنا دارتانيان!"

?S — عنصر نائب لنوع السلسلة للإدراج في عامل SQL LIKE

يتم تجاوز قيم الوسيطات باستخدام طريقة mysqli::real_escape_string() + تجاوز الأحرف الخاصة المستخدمة في عامل LIKE (% و _):

$db->query('SELECT "?S"', '% _');

استعلام SQL بعد تحويل القالب:

SELECT "\% \_"

?n — عنصر نائب لنوع NULL

يتم تجاهل قيم أي وسيطات، ويتم استبدال العناصر النائبة بالسلسلة NULL في استعلام SQL:

$db->query('SELECT ?n', 123);

استعلام SQL بعد تحويل القالب:

SELECT NULL

?A* — عنصر نائب لمجموعة ترابطية من مصفوفة ترابطية، ينتج تسلسل أزواج في شكل مفتاح = قيمة

حيث الرمز * هو أحد العناصر النائبة:

  • i (عنصر نائب للأعداد الصحيحة)
  • d (عنصر نائب للأرقام العشرية)
  • s (عنصر نائب لنوع السلسلة)

قواعد التحويل والتجاوز هي نفسها للأنواع القياسية الفردية الموصوفة أعلاه. مثال:

$db->query(
    'INSERT INTO `test` SET ?Ai',
    ['first' => '123', 'second' => 456]
);

استعلام SQL بعد تحويل القالب:

INSERT INTO `test` SET `first` = "123", `second` = "456"

?a* — عنصر نائب لمجموعة من مصفوفة بسيطة (أو أيضًا ترابطية)، ينتج تسلسل قيم

حيث * هو أحد الأنواع:

  • 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` = "دارتانيان"

?a[?n, ?s, ?i, ...] — عنصر نائب لمجموعة مع تحديد صريح للنوع وعدد الوسيطات، ينتج تسلسل قيم

مثال:

$db->query(
    'SELECT * FROM `users` WHERE `name` IN (?a["?s", "?s"])',
    ["الماركيز داركيان", "دارتانيان"]
);

استعلام SQL بعد تحويل القالب:

SELECT * FROM `users` WHERE `name` IN ("الماركيز داركيان", "دارتانيان")

?f — عنصر نائب لاسم جدول أو حقل

هذا العنصر النائب مخصص للحالات التي يتم فيها تمرير اسم الجدول أو الحقل في الاستعلام عبر معامل. يتم تأطير أسماء الحقول والجداول برمز "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