في هذا الدرس 6تصفّح المواضيع
صفِّ سجلات قاعدة البيانات بدمج شروط WHERE المنطقية والرقمية مع المعاملات المحضرة المربوطة بأمان.
- يجب تمثيل كل قيمة ديناميكية داخل شرط
WHEREبمعامل محضر (:statusو:minPrice) بدلاً من الدمج النصي المباشر. - عند الجمع بين
ANDوORفي استعلام SQL، أحِط مجموعةORبقوسين لضمان وضوح أولوية التنفيذ. - استدعِ
fetchAll(PDO::FETCH_ASSOC)بعد$stmt->execute()لجلب كافة الصفوف المطابقة كمصفوفة ترابطية.
يُربط المعاملان :lvl و:max بأمان داخل execute() لإرجاع الدورات التي تحقق الشرطين معاً.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE courses (title TEXT, level TEXT, price INT)');
$pdo->exec("INSERT INTO courses VALUES ('PHP Core', 'beginner', 0), ('PDO Pro', 'advanced', 90)");
$stmt = $pdo->prepare('SELECT title FROM courses WHERE level = :lvl AND price <= :max');
$stmt->execute(['lvl' => 'beginner', 'max' => 50]);
$rows = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo "Matched: " . implode(', ', $rows) . "\n";
عند استخدام البحث الجزئي LIKE مع الاستعلامات المحضرة، أضف علامات النسبة المئوية % داخل قيمة المتغير في PHP، ولا تضع علامات تنصيص حول المعامل داخل جملة SQL.
- اكتب في جملة الاستعلام
WHERE title LIKE :search، ومرّر القيمة في PHP على الصورة["search" => "%$keyword%"]. - كتابة
LIKE "%:search%"داخل نص SQL خطأ شائع؛ لأن PDO يعتبرها نصاً حرفياً ثابتاً ولن يربط المعامل:search. - لمنع المستخدم من إدخال رموز البحث
%أو_كحروف عادية، يمكنك تحييدها مسبقاً عبرaddcslashes($keyword, "%_").
تستخدم جملة SQL المعامل المجرد LIKE :q، بينما يضيف كود PHP علامتي النسبة المئوية في القيمة الممررة "%$term%".
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE articles (id INTEGER PRIMARY KEY, title TEXT)');
$pdo->exec("INSERT INTO articles (title) VALUES ('Mastering PHP Streams'), ('Intro to CSS Grid')");
$term = 'PHP';
$stmt = $pdo->prepare('SELECT title FROM articles WHERE title LIKE :q');
$stmt->execute(['q' => "%$term%"]);
echo "Found: " . $stmt->fetchColumn() . "\n";
لا يمكن لمعامل محضر واحد (WHERE id IN (:ids)) أن يربط مصفوفة كاملة؛ بل يجب توليد سلسلة من علامات ? تطابق عدد عناصر المصفوفة ديناميكياً.
- استخدم التعبير
implode(", ", array_fill(0, count($ids), "?"))لبناء نص المعاملات الموضعية بأمان مثل"?, ?, ?". - افحص أولاً أن مصفوفة
$idsليست فارغة، لأن كتابةWHERE id IN ()فارغة تسبب خطأ نحوياً في SQL. - مرّر المصفوفة المفهرسة
array_values($ids)مباشرة إلى$stmt->execute()لربط كل علامة?بقيمتها بالترتيب.
تولّد array_fill(0, 3, "?") ثلاث علامات استفهام ليقوم الشرط IN (?, ?, ?) بربط المعرفات الثلاثة بأمان.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE items (id INT PRIMARY KEY, name TEXT)');
$pdo->exec("INSERT INTO items VALUES (1, 'Pen'), (2, 'Book'), (3, 'Bag'), (4, 'Lamp')");
$ids = [1, 3, 4];
$in = implode(', ', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT name FROM items WHERE id IN ($in)");
$stmt->execute(array_values($ids));
echo implode(', ', $stmt->fetchAll(PDO::FETCH_COLUMN)) . "\n";
لا تربط المعاملات المحضرة إلا قيم البيانات فقط، ولا يمكنها ربط أسماء الأعمدة أو اتجاه الترتيب (ASC وDESC) في جملة ORDER BY.
- إذا كتبت
ORDER BY :sort، فستعتبر قاعدة البيانات المعامل نصاً ثابتاً ولن تقوم بترتيب الصفوف فعلياً. - تحقق دائماً من عمود الترتيب المطلوب عبر قائمة بيضاء صارمة بالدالة
in_array($sort, $allowedColumns, true)مع تحديد عمود افتراضي آمن. - اضبط اتجاه الترتيب بصرامة عبر تعبير مثل
$dir = strtoupper($inputDir) === "DESC" ? "DESC" : "ASC";.
لا يُدرج اسم العمود في ORDER BY إلا بعد تأكيد وجوده ضمن القائمة البيضاء المعتمدة عبر in_array($userSort, $allowed, true).
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE products (id INT, price INT)');
$pdo->exec('INSERT INTO products VALUES (1, 300), (2, 120), (3, 450)');
$userSort = 'price';
$allowed = ['id', 'price'];
$col = in_array($userSort, $allowed, true) ? $userSort : 'id';
$dir = 'ASC';
$prices = $pdo->query("SELECT price FROM products ORDER BY $col $dir")->fetchAll(PDO::FETCH_COLUMN);
echo implode(' -> ', $prices) . "\n";
قسّم نتائج الجداول الكبيرة إلى صفحات باستخدام LIMIT :limit OFFSET :offset مع ربط القيمتين كأعداد صحيحة عبر PDO::PARAM_INT.
- احسب إزاحة البداية من رقم الصفحة الحالي بالمعادلة:
$offset = ($page - 1) * $perPageمع ضمان أن الصفحة لا تقل عن1. - اربط المعاملين
:limitو:offsetعبر$stmt->bindValue(":limit", $perPage, PDO::PARAM_INT)حتى لا يحيطهما PDO بعلامات تنصيص. - اربط دائماً تقسيم الصفحات بترتيب حاسم مثل
ORDER BY id DESCلضمان عدم تداخل أو تكرار السجلات عند الانتقال بين الصفحات.
في الصفحة الثانية بعنصرين لكل صفحة، تكون الإزاحة (2 - 1) * 2 = 2، فيتخطى الاستعلام أول سجلين ويُرجع P3, P4.
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE posts (id INT PRIMARY KEY, title TEXT)');
$pdo->exec("INSERT INTO posts VALUES (1,'P1'),(2,'P2'),(3,'P3'),(4,'P4'),(5,'P5')");
$page = 2;
$perPage = 2;
$offset = ($page - 1) * $perPage;
$stmt = $pdo->prepare('SELECT title FROM posts ORDER BY id ASC LIMIT :lim OFFSET :off');
$stmt->bindValue(':lim', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':off', $offset, PDO::PARAM_INT);
$stmt->execute();
echo "Page 2: " . implode(', ', $stmt->fetchAll(PDO::FETCH_COLUMN)) . "\n";
اجمع بين ربط المعاملات للقيم، والقوائم البيضاء لأسماء الأعمدة، والربط الرقمي لتقسيم الصفحات لضمان سرعة وأمان كافة استعلامات PDO.
- استخدم استعلام
COUNT(*)بنفس شروطWHEREلحساب إجمالي عدد الصفحات عبر$totalPages = (int) ceil($totalRows / $perPage). - تذكر أن تمرير مصفوفة إلى
$stmt->execute([...])يربط جميع عناصرها كنصوصPDO::PARAM_STRافتراضياً؛ لذا استخدمbindValue(..., PDO::PARAM_INT)مع أرقام التقسيم الصفحي. - لا تثق أبداً بمعاملات الرابط مثل
$_GET["sort"]أو$_GET["page"]دون فحص وتطهير.
مع وجود 45 سجلاً و10 سجلات لكل صفحة، تعطي ceil(45 / 10) إجمالي 5 صفحات، وتبدأ الصفحة الثالثة عند الإزاحة 20.
| جزء استعلام SQL | الطريقة الآمنة للتعامل مع المدخلات | الخطأ الشائع الواجب تجنبه |
|---|---|---|
WHERE col = :val | Bind via execute(["val" => $v]) | Interpolating "$v" directly into SQL |
WHERE col LIKE :q | Bind ["q" => "%$term%"] in PHP | Writing LIKE "%:q%" inside SQL string |
WHERE id IN (?, ?, ?) | Generate ? via array_fill() | Binding an array to a single IN (:ids) |
ORDER BY / LIMIT | Whitelist column; bindValue(PDO::PARAM_INT) | Binding column name as ORDER BY :col |
<?php
$totalRows = 45;
$perPage = 10;
$page = max(1, 3);
$totalPages = (int) ceil($totalRows / $perPage);
$offset = ($page - 1) * $perPage;
echo "Page $page of $totalPages | LIMIT $perPage OFFSET $offset\n";
ملخص الدرس
حصاد الدرسمراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي
ماذا تعلّمت في هذا الدرس؟
اجمع بين ربط المعاملات للقيم، والقوائم البيضاء لأسماء الأعمدة، والربط الرقمي لتقسيم الصفحات لضمان سرعة وأمان كافة استعلامات PDO. استخدم استعلام COUNT(*) بنفس شروط WHERE لحساب إجمالي عدد الصفحات عبر $totalPages = (int) ceil($totalRows / $perPage). تذكر أن تمرير مصفوفة إلى $stmt->execute([...]) يربط جميع عناصرها كنصوص PDO::PARAM_STR افتراضياً؛ لذا استخدم bindValue(..., PDO::PARAM_INT) مع أرقام التقسيم الصفحي. لا تثق أبداً بمعاملات الرابط مثل $_GET["sort"] أو $_GET["page"] دون فحص وتطهير.
سجّل الدخول لحفظ تقدمك
تابع إنجاز المواضيع والمصطلحات والمهام، وكمّل من آخر خطوة على أي جهاز.
المصطلحات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 WHERE, ORDER & LIMIT الفلترة والترتيب و LIMIT
- ابنِ فلاتر بحث ديناميكية بـ LIKE وIN، وأمّن الترتيب بـ ORDER BY، ونفّذ تقسيم الصفحات Pagination عبر LIMIT وOFFSET في PDO.
- للمراجعة
- 02 Filtering Rows with WHERE and Parameters فلترة السجلات بشرط WHERE والمعاملات
- المفهوم الأساسي لقسم فلترة السجلات بشرط WHERE والمعاملات في لغة PHP الحديثة.
- للمراجعة
- 03 Safe Pattern Matching with LIKE البحث الجزئي الآمن بالمعامل LIKE
- الصياغة البرمجية والتطبيق العملي المباشر لقسم البحث الجزئي الآمن بالمعامل LIKE.
- للمراجعة
- 04 Dynamic IN Clauses with Placeholders بناء شرط IN الديناميكي بالمعاملات المحضرة
- قواعد التحقق وأفضل الممارسات القياسية الخاصة بقسم بناء شرط IN الديناميكي بالمعاملات المحضرة.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
الاختصارات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 PHP PHP
- PHP: Hypertext Preprocessor
- لغة البرمجة النصية على جانب الخادم لبناء تطبيقات الويب الديناميكية وواجهات البرمجة.
- للمراجعة
- 02 CLI CLI
- Command-Line Interface
- واجهة سطر الأوامر التي تتيح تشغيل ملفات PHP مباشرة عبر الطرفية.
- للمراجعة
- 03 FPM FPM
- FastCGI Process Manager
- مدير عمليات FastCGI عالي الأداء لمعالجة طلبات خادم الويب في PHP.
- للمراجعة
- 04 HTML HTML
- HyperText Markup Language
- لغة الترميز القياسية التي يولدها محرك PHP ويرسلها إلى متصفح العميل بعد التنفيذ.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
مهامك قبل الاختبار
خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.
-
أنشئ ملف PHP محلياً ونفّذ المثال البرمجي المشروح في قسم فلترة السجلات بشرط WHERE والمعاملات للتحقق من المخرجات.
-
بدّل بين الحالات الثلاث في المحاكي التفاعلي ضمن قسم البحث الجزئي الآمن بالمعامل LIKE وقارن مخرجات التنفيذ.
تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.
اختبر فهمك.
ووصلها للإتقان.
كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.
أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.
الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.
لمحة عن التحدي8 أسئلة تنتظرك +
كيف يجب كتابة استعلام البحث الجزئي LIKE المحضر بطريقة صحيحة وآمنة في PDO؟
توضع علامات النسبة المئوية % داخل قيمة المتغير الممرر في PHP وليس حول اسم المعامل في SQL.
لماذا يفشل الترتيب عند محاولة ربط اسم العمود كمعامل محضر مثل ORDER BY :sortColumn؟
تتعامل قاعدة البيانات مع المعامل المربوط كقيمة نصية ثابتة وليس كاسم عمود؛ لذا يجب استخدام القائمة البيضاء.
كيف تنفذ استعلام WHERE id IN (...) بأمان لمصفوفة ديناميكية في PHP مثل $ids = [4, 8, 15]؟
يحصل كل عنصر في المصفوفة على معامل موضعي ? مستقل يتم ربطه بأمان.
ما المعادلة الصحيحة لحساب إزاحة البداية (OFFSET) للصفحة رقم $page عند عرض $perPage عنصراً في كل صفحة؟
الصفحة 1 تبدأ من الإزاحة 0، والصفحة 2 تبدأ من الإزاحة 10 (عند عرض 10 عناصر).
لماذا يُفضّل ربط :limit و:offset عبر $stmt->bindValue(..., PDO::PARAM_INT) بدلاً من تمريرهما في مصفوفة execute([...])؟
يضمن الثابت PDO::PARAM_INT إرسال أرقام صحيحة بدون علامات تنصيص في جملة LIMIT.
أي فحص في PHP يتحقق بأمان من أن عمود الترتيب المطلوب $sort ضمن الأعمدة المسموح بها؟
يضمن الفحص الصارم بـ in_array(..., true) عدم تمرير أي نص خارج القائمة البيضاء المعتمدة.
لماذا يجب أن يتضمن كل استعلام مقسم لصفحات (LIMIT / OFFSET) جملة ترتيب صريحة وحاسمة ORDER BY؟
لا تضمن قواعد البيانات ثبات ترتيب الصفوف عبر الصفحات إلا بوجود ORDER BY صريح.
إذا كان عدد السجلات المطابقة 95 سجلاً وعرض الصفحة $perPage = 10، فكيف تحسب إجمالي عدد الصفحات في PHP؟
تقرب الدالة ceil(9.5) الناتج للأعلى إلى 10 صفحات لعرض آخر 5 سجلات متبقية في الصفحة العاشرة.