הדף הזה הוא רפרנס לכל המתודות הציבוריות של DB (api/core/collections/DB.php) ושל שלוש המחלקות שהקובץ מגדיר לצידה: DB_QUERY_OBJECT (בונה SELECT), DB_UPDATE_CLASS (בונה INSERT ו-UPDATE) ו-zend_query (קוד מת). הוא עונה על "מה בדיוק מקבלת ומחזירה המתודה הזו". להסבר מושגי, ראו שכבת ה-DB, שאילתות ו-בוני שאילתות.
DB היא מחלקה סטטית שנטענת אוטומטית עם כל ה-collections, כך שזמינה בכל מקום בלי require. החיבור נפתח בעצלות, בפעם הראשונה ש-DB::sql(), DB::num_rows() או DB::escape() צריכים אותו, ונשמר ב-DB::$condb למשך הבקשה.
כללי קידומת: איפה כותבים CRM_ ואיפה לא#
| קבוצה | מתודות | שם הטבלה |
|---|---|---|
| builders ובדיקות סכימה | DB::query(), DB::update(), DB::delete(), DB::get_val(), DB::is_table_exists(), DB::is_column_exists() | בלי קידומת: "news". הקוד מוסיף CONFIG::$db_fullprefix ("CRM_") בעצמו |
| SQL גולמי | DB::sql(), DB::get_row("SELECT ..."), DB::get_all("SELECT ...") | השם המלא: CRM_news (או CONFIG::$db_fullprefix . "news") |
DB::query("CRM_news") יפנה לטבלה CRM_CRM_news. ובכל SQL גולמי, כל ערך שמקורו בקלט חיצוני עובר דרך DB::escape().
הליבה לא קוראת ל-PDO::setAttribute(PDO::ATTR_ERRMODE, ...) בשום מקום, וברירת המחדל של PDO מאז PHP 8.0 היא חריגות. לכן שאילתה שנכשלה (טבלה חסרה, תחביר שגוי) זורקת Exception מתוך DB::sql(), וההודעה כוללת את ה-SQL. ההערות בקוד על "silent mode" ו"תוצאה ריקה" מתארות התנהגות ישנה. קוד שבו כישלון מותר חייב try/catch.
מאפיינים סטטיים#
| מאפיין | ברירת מחדל | משמעות |
|---|---|---|
DB::$condb | false | אובייקט החיבור (\PDO, או מערך פרטים ב-ClickHouse) |
DB::$encoding | "utf8mb4" | נשלח כ-set names בחיבור PDO |
DB::$throw_on_error | false | משפיע רק על ההודעה של החריגה שנזרקת בכשל PDO (ראו DB::sql()) |
DB::$error_count | 0 | כמה שאילתות נכשלו בנתיב ה"שקט" בבקשה הנוכחית |
DB::$last_error | "" | הודעת השגיאה האחרונה באותו נתיב, וגם הודעת כשל חיבור |
DB::$print_sql | false | true מדפיס כל שאילתה ל-HTML בתוך <p> |
DB::$is_sql_debug | false | true מוסיף /* REQUEST_URI */ לתחילת כל שאילתה |
DB::$log_sql | false | נדלק אוטומטית כשהפרמטר log_sql ב-CRM_params שווה "1"; כותב ל-sqllog.txt |
DB::$table_exist_array, DB::$table_columns_array | false, [] | זיכרון בתוך הבקשה של is_table_exists ו-is_column_exists |
DB::$capture_cfg, $capture_buffer, $capture_registered, $capture_dropped | מצב ה-Performance Advisor (ראו הסעיף על capture) | |
DB::$zend_db | false | האדפטר של DB::zend() |
DB::$laminas_db | false | מוצהר ולא בשימוש. אין בקוד שום שימוש ב-Laminas |
מפתחות הקונפיגורציה שהחיבור קורא (מוגדרים ב-CONFIG_USER, ראו CONFIG_USER): CONFIG::$db_type, $db_host, $db_dbname, $db_user, $db_password, $db_port (אופציונלי), $db_protocol (ClickHouse בלבד), $db_prefix ו-$db_fullprefix. הערכים התקפים של db_type הם "PDO", "mysql", "pg" ו-"clickhouse"; כל ערך אחר גורם ל-die("DB ERROR").
"mysql" משתמש בפונקציות mysql_* שהוסרו ב-PHP 7, ולכן אינו ניתן להרצה ב-8.3. "pg" ו-"clickhouse" נתמכים חלקית בלבד (למשל DB_UPDATE_CLASS::insert() מסתיים ב-die("DB ERROR") ב-ClickHouse, ו-DB_QUERY_OBJECT::free_results() גם). בפועל כל האתרים רצים על PDO מול MySQL או MariaDB.חיבור#
DB::create_connection()#
public static function create_connection()
פותחת את החיבור אם עדיין לא נפתח (אחרת חוזרת מיד). ב-PDO: new \PDO("mysql:host=...;dbname=...;port=..."), ואז set names utf8mb4 ו-SET SESSION sql_mode = ''. כשל בחיבור מגדיל את DB::$error_count, שומר את ההודעה ב-DB::$last_error וקורא ל-MISC::service_unavailable(), שמחזיר HTTP 503 עם Retry-After ועוצר (ב-CLI כותב ל-STDERR ויוצא עם קוד 1). אין צורך לקרוא לה ידנית: כל מתודה שזקוקה לחיבור קוראת לה.
sql_mode ריק
כל חיבור מאפס את sql_mode, כלומר MySQL רץ בלי STRICT. ערכים ארוכים מדי נחתכים בשקט, מספר לא חוקי הופך ל-0 ותאריך לא חוקי ל-0000-00-00. ה-DB לא ידחה קלט שגוי: ולידציה נעשית ב-PHP לפני הכתיבה.DB::close_connection()#
public static function close_connection()
סוגרת את החיבור (ב-PDO מאפסת את DB::$condb ל-null). core.php קורא לה בסוף כל בקשה. ב-db_type לא מוכר: die("DB ERROR").
DB::get_connection()#
public static function get_connection()
מחזירה את אובייקט החיבור (\PDO), אחרי שפתחה אותו אם צריך. שימושית לטרנזקציות ול-lastInsertId():
$pdo = DB::get_connection();
$pdo->beginTransaction();
try {
DB::sql("UPDATE CRM_accounts SET balance = balance - 10 WHERE id = 1");
DB::sql("UPDATE CRM_accounts SET balance = balance + 10 WHERE id = 2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
אין בשכבה תמיכה מובנית בטרנזקציות: כל statement רץ בנפרד ב-autocommit, וגם insert() עם טבלת תוכן הוא שני INSERT-ים לא אטומיים. אם צריך אטומיות, השתמשו באובייקט ה-PDO כמו למעלה או ב-DB::sql("START TRANSACTION") ו-DB::sql("COMMIT") (כך עושה תור המיילים ב-Tools.php). שימו לב ש-DDL ב-MySQL עושה commit מרומז.
הרצת SQL גולמי#
DB::sql()#
public static function sql($sql)
| פרמטר | טיפוס | משמעות |
|---|---|---|
$sql | string | שאילתה שלמה, עם שם טבלה מלא (CRM_...) |
מחזירה: את ה-statement (PDOStatement ב-PDO; משאב ב-pg; מערך ב-ClickHouse). שאילתה שנכשלה ב-PDO זורקת חריגה (ראו למעלה); רק אם execute() החזיר false בלי חריגה, sql() מגדילה את DB::$error_count, שומרת את DB::$last_error, כותבת ל-error_log ומחזירה את ה-statement הכושל.
מה היא עושה לפני ההרצה: מוסיפה /* REQUEST_URI */ אם DB::$is_sql_debug, מדפיסה אם DB::$print_sql, כותבת ל-sqllog.txt אם DB::$log_sql, ומכניסה את השאילתה לאגירת ה-Performance Advisor אם חלון איסוף פעיל. ה-SQL עובר prepare() ו-execute() בלי bind של פרמטרים: אין prepared statements אמיתיים ואחריות ה-escape על הקורא.
הודעת החריגה (בחריגת PDO) היא <הודעת MySQL> | <ה-SQL כשורה אחת>. עם DB::$throw_on_error = true זה כל מה שבהודעה; עם false (ברירת המחדל) מתווסף גם stack trace מלא. בשני המקרים נזרקת חריגה, ולכן אל תסתמכו על הדגל כדי "להשתיק" שגיאות.
בנתיב ה"שקט" (כשל בלי חריגה), אם ADMIN::is_admin(), הודעת השגיאה וה-SQL מודפסים לתוך ה-HTML של העמוד.
try {
DB::sql("CREATE TABLE IF NOT EXISTS CRM_my_log (id INT AUTO_INCREMENT PRIMARY KEY, msg TEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
cache_engine::remove("table_list"); // כדי ש-is_table_exists יראה את הטבלה החדשה
} catch (Exception $e) {
error_log("my_log create failed: " . $e->getMessage());
}
DB::get_row()#
public static function get_row($res, $return_key = false)
| פרמטר | משמעות |
|---|---|
$res | statement מ-DB::sql(), או מחרוזת SQL (אז היא מורצת קודם) |
$return_key | שם עמודה. אם הוגדר והתקבלה שורה, מוחזר רק הערך של העמודה הזו |
מחזירה: שורה כמערך אסוציאטיבי, ערך בודד (עם $return_key), או false כשאין עוד שורות.
$row = DB::get_row("SELECT * FROM CRM_news WHERE id = " . (int)$id);
$count = DB::get_row("SELECT COUNT(*) c FROM CRM_news", "c");
$res = DB::sql("SELECT id, title FROM CRM_news WHERE is_active = 1");
while ($r = DB::get_row($res)) { echo $r["title"]; }
כשמעבירים מחרוזת, כל קריאה מריצה את השאילתה מחדש ומחזירה את השורה הראשונה. while (DB::get_row("SELECT ...")) הוא לולאה אינסופית. לאיטרציה העבירו את ה-statement כמו בדוגמה האחרונה, או השתמשו ב-DB::query(...)->get_row().
DB::get_all()#
public static function get_all($res, $key = NULL, $val = NULL)
| פרמטר | משמעות |
|---|---|
$res | statement או מחרוזת SQL |
$key | שם עמודה. אם הוגדר, המערך הוחזר ממופתח לפי ערך העמודה הזו |
$val | שם עמודה. יחד עם $key, הערכים הם $row[$val] במקום השורה כולה |
מחזירה: מערך שורות (מערך ריק כשאין תוצאות). שורות עם אותו $key דורסות זו את זו.
$byId = DB::get_all("SELECT * FROM CRM_news WHERE is_active = 1", "id"); // [12 => [...], 15 => [...]]
$titles = DB::get_all("SELECT id, title FROM CRM_news", "id", "title"); // [12 => "...", 15 => "..."]
$params = DB::get_all("SELECT sysName, value FROM CRM_params", "sysName", "value");
DB::num_rows()#
public static function num_rows($res)
מחזירה: int, מספר השורות. ב-PDO זה $res->rowCount(), שבחיבור MySQL הרגיל עובד גם על SELECT. $res חייב להיות statement מ-DB::sql(): מחרוזת מתקבלת רק ב-ClickHouse, וב-PDO היא תגרום ל-Error. לספירה יעילה מעדיפים SELECT COUNT(*) c ... עם DB::get_row($sql, "c").
DB::escape()#
public static function escape($str)
מחזירה: את המחרוזת מוכנה להדבקה בתוך גרשיים בשאילתה. ב-PDO היא קוראת ל-PDO::quote() ומסירה את הגרשיים החיצוניים, כלומר לא מוסיפה גרשיים: אתם עוטפים אותה ב-'...'. קלט ריק ("" או NULL) מוחזר כמו שהוא. ב-ClickHouse זה addslashes.
$name = DB::escape($_POST["name"]);
$row = DB::get_row("SELECT * FROM CRM_customers WHERE name = '" . $name . "'");
היא מטפלת בערכי מחרוזת בלבד. היא לא מצטטת מזהים (שמות טבלה ועמודה, backtick), לא מוסיפה גרשיים, ולא מנטרלת את % ו-_ בתוך LIKE. ערך מספרי עטפו ב-(int). שם עמודה או כיוון מיון מקלט חיצוני: רק מול רשימת ערכים מותרים. ראו CSRF, XSS ו-SQLi.
שאילתות עם builder#
DB::query()#
public static function query($table = "", $whereCond = array(), $orderBy = "", $lang = "", $limit = null)
| פרמטר | טיפוס | משמעות |
|---|---|---|
$table | string | שם הטבלה בלי CRM_. אם קיימת <table>_content, היא מצורפת אוטומטית (ראו טבלאות רב-לשוניות) |
$whereCond | array | עמודה => ערך, מחובר ב-AND. ראו חוקי הערכים ב-DB_QUERY_OBJECT |
$orderBy | string | SQL גולמי אחרי ORDER BY |
$lang | int|string | langId לטבלת התוכן. ברירת מחדל: CONFIG::$lang. מודבק בלי גרשיים |
$limit | string|int|null | "offset,count" או מספר. מודבק כמו שהוא אחרי LIMIT |
מחזירה: DB_QUERY_OBJECT. השאילתה עצלה: היא נבנית ורצה רק ב-get_row(), get_all() או num_rows(), ולכן אפשר לשנות את המאפיינים של האובייקט (fields, filter, group_by ועוד) לפני כן. הבנאי מקבל את המחלקה הקוראת (get_called_class()), כך שמחלקת בת של DB שדורסת sql() או is_table_exists() מקבלת גם את ה-builders.
$news = DB::query("news", ["is_active" => 1], "date_created DESC", "", "0,10")->get_all("id");
$one = DB::query("news", ["id" => 15])->get_row(); // שורה או false
DB::get_val()#
public static function get_val($tblName, $filter = "", $rowName = "")
| פרמטר | משמעות |
|---|---|
$tblName | שם טבלה בלי קידומת |
$filter | מספר (גם מחרוזת מספרית) הופך ל-id = N; מערך הוא $whereCond; מחרוזת לא מספרית ולא ריקה היא תנאי SQL גולמי שמדביקים ל-filter. ריק או טיפוס אחר: false |
$rowName | אם הוגדר, מוחזר ערך העמודה הזו בלבד (והיא גם נקבעת כ-fields) |
מחזירה: שורה כמערך, או ערך עמודה, או false כשאין שורה מתאימה (וגם כש-$filter לא תקין). תמיד LIMIT 0,1.
$user = DB::get_val("adminPanel_admins", ["uname" => $uname]); // שורה או false
$name = DB::get_val("storage", (int)$sid, "name"); // ערך בודד לפי id
$first = DB::get_val("news", "is_active = 1 ORDER BY id DESC"); // תנאי גולמי
DB::get_val("tbl", $_GET["id"]) עם ערך כמו abc מדביק אותו כ-SQL גולמי ללא escape. תמיד (int)$_GET["id"], או מערך ["col" => $value] (ערכי מערך כן עוברים escape).DB::update()#
public static function update($table = "", $vars = array())
מחזירה: DB_UPDATE_CLASS עבור הטבלה (בלי קידומת). מערך $vars מועבר ל-set() עבור כל מפתח, לעמודות טבלת ה-conf. אחרי בניית ה-builder קוראים ל-->insert() או ל-->update($id). שימו לב: DB::insert() לא קיים; הכנסה היא DB::update("tbl")->set(...)->insert().
DB::delete()#
public static function delete($table = "", $whereCond = array(), $orderBy = "", $lang = "", $limit = null)
| פרמטר | משמעות |
|---|---|
$table | שם טבלה בלי קידומת |
$whereCond | מספר (id) או מערך לא ריק. מערך ריק, מחרוזת לא מספרית וכל ערך אחר: מחזירה false ולא נמחק דבר |
$orderBy, $lang, $limit | מועברים ל-DB::query(). $limit מגביל כמה שורות יימחקו |
מחזירה: false כשהתנאי לא תקין, אחרת כלום (null). היא מושכת את ה-id-ים שעונים לתנאי ואז מריצה DELETE FROM <table> WHERE id IN (...), ואם יש טבלת תוכן גם DELETE FROM <table>_content WHERE parentId IN (...) (כל השפות).
DB::delete("news", 15); // מוחק את news ו-news_content של id=15
DB::delete("storage", ["object_type" => "gallery", "object_id" => (int)$pid]);
בטבלה רב-לשונית, ה-SELECT שמוצא את ה-id-ים הוא join פנימי לשפה הנוכחית (CONFIG::$lang, או $lang). שורה שאין לה שורת תוכן באותה שפה לא תימצא ולא תימחק. התנאי עוקב אחרי כללי DB_QUERY_OBJECT (מפתחות לא עוברים escape). המחיקה אינה הפיכה ואינה עוברת cache: אם הנתון נשמר ב-cache_engine, הסירו אותו בעצמכם.
DB::zend()#
public static function zend()
מחזירה: אדפטר Zend_Db_Adapter_Pdo_Mysql (Zend Framework 1, vendored ב-addons/zend_db) שעוטף את אותו חיבור PDO. נטען פעם אחת. מתאים ל-JOIN ו-Zend_Db_Expr מורכבים שה-builder של WIZZO לא תומך בהם; בליבה משמש רק libraries/m_wzcontact.php.
$select = DB::zend()->select()
->from(["cnt" => "CRM_wzcontact"], ["*", "last" => new Zend_Db_Expr("MAX(msg.date_created)")])
->joinLeft(["msg" => "CRM_wzcontact_messages"], "msg.contact_id = cnt.id", [])
->group("cnt.id");
$rows = DB::get_all($select->assemble());
בדיקות סכימה#
DB::is_table_exists()#
public static function is_table_exists($table)
מחזירה: bool. השם בלי קידומת (המתודה משווה ל-CONFIG::$db_prefix . "_" . $table). רשימת הטבלאות נטענת מ-SHOW TABLES ונשמרת ב-cache_engine תחת המפתח table_list, ל-7 ימים, בלי הפרדה לפי דומיין.
DB::is_column_exists()#
public static function is_column_exists($table, $column)
מחזירה: bool. מריצה SHOW COLUMNS ושומרת את רשימת העמודות תחת table_columns_<table> ל-7 ימים. אם הטבלה לא קיימת, מוחזרת (ונשמרת) רשימה ריקה.
אחרי יצירת טבלה או עמודה, ה-builders (שבודקים is_table_exists($table . "_content") בכל DB::query()) לא "יראו" אותה עד שהמטמון יפוג. אחרי מיגרציה:
cache_engine::remove("table_list");
cache_engine::remove("table_columns_news");
שגיאות ודיבוג#
DB::had_error()#
public static function had_error()
מחזירה: true אם DB::$error_count > 0 בבקשה הנוכחית. ה-counter עולה רק בנתיב ה"שקט" (execute() שהחזיר false בלי חריגה) ובכשל חיבור, כך שבהגדרת ברירת המחדל של PHP 8 (חריגות) הוא כמעט תמיד 0. cache_engine::get() משתמש באותו counter כדי לא לשמור ל-cache תוצאה שנבנתה בזמן כשל.
DB::log_sql_to_file()#
public static function log_sql_to_file($sql)
מוסיפה שורת JSON ל-CONFIG::$base_path . '/sqllog.txt' (שורש האתר) עם url, date, sql (שורה אחת), trace מלא ו-source (admin או site). כשהקובץ עובר 5MB נשמר רק השליש האחרון. נקראת אוטומטית מ-sql() כש-DB::$log_sql דלוק (פרמטר המערכת log_sql = 1), ואין סיבה לקרוא לה ידנית. פירוט כלי הדיבוג: דיבוג SQL.
sqllog.txt נמצא בשורש האתר ועלול להיות נגיש ב-HTTP, והוא מכיל את כל ה-SQL כולל ערכים. הדליקו את הלוג רק לזמן דיבוג, וחסמו את הגישה אליו בשרת.Performance Advisor (capture)#
ארבע מתודות משרתות את ה-Performance Advisor, שאוסף שאילתות בחלון זמן מוגבל. הן חלק מהמנגנון הפנימי ובדרך כלל לא קוראים להן מקוד אתר. האיסוף מופעל על ידי קובץ סמן CONFIG::$cache_folder . "/perf_advisor/capture_on.json" (עם session_id, until ו-max_bytes אופציונלי). כשאין סמן, כל המנגנון עולה file_exists() אחד לבקשה.
| מתודה | חתימה | מה עושה |
|---|---|---|
DB::capture_cfg() | public static function capture_cfg() | בודקת את הסמן פעם אחת בבקשה. מחזירה false (כבוי או סמן שפג) או מערך הגדרות (session_id, until, max_bytes, file) |
DB::capture_kill() | public static function capture_kill() | מכבה את האיסוף לבקשה הנוכחית ומרוקנת את ה-buffer (האנלייזר קורא לה לפני השאילתות של עצמו) |
DB::capture_flush() | public static function capture_flush() | hook של register_shutdown_function: כותבת את כל שאילתות הבקשה כשורת JSON אחת ל-capture_<session>.jsonl (append עם LOCK_EX, ובכפוף לתקרת גודל) |
DB::capture_hit() | public static function capture_hit($url) | נקראת מ-cache_engine::cache_php_check() לפני הגשת עמוד מה-cache. רושמת HIT בדגימה של 1:10 |
לכל בקשה נאגרות עד 2000 שאילתות (כל שאילתה נחתכת ל-2000 תווים, עם שלוש מסגרות file:line של הקוד הקורא ומשך ב-ms). שאילתה נרשמת לפני שהיא רצה, כך ששאילתה שתקעה את הבקשה עדיין תירשם (עם ms ריק).
DB_QUERY_OBJECT: בונה ה-SELECT#
נוצר דרך DB::query(). כל המאפיינים ציבוריים ואפשר לשנות אותם לפני ההרצה.
מאפיינים#
| מאפיין | ברירת מחדל | משמעות |
|---|---|---|
table | $table | שם הטבלה בלי קידומת (alias tConf בשאילתה) |
whereCond | [] | עמודה => ערך, מחובר ב-AND |
filter | "" | תנאי SQL גולמי נוסף, נעטף ב-AND ( ... ) |
fields | "*" | רשימת העמודות. מחרוזת SQL גולמית |
order_by | $orderBy | SQL גולמי אחרי ORDER BY |
group_by | "" | SQL גולמי אחרי GROUP BY |
limit | $limit | מודבק אחרי LIMIT (מושמט כשהערך null או ריק) |
lang | CONFIG::$lang | langId לטבלת התוכן. מוגדר רק כשקיימת טבלת תוכן |
contentTable | "<table>_content" או "" | שם טבלת התוכן. איפוס ל-"" מבטל את ה-JOIN; ערך אחר מציב טבלת צד |
queryKey | "parentId" | עמודת הקישור בטבלת התוכן |
tConfConnector | "id" | העמודה בטבלה הראשית שאליה מתחברים |
rows | השורה הנוכחית אחרי get_row() | |
query | "" | ה-SQL שנבנה (אחרי BuildQuery()), לדיבוג |
resQuery | ה-statement של ההרצה האחרונה | |
tableName | מוצהר ואינו בשימוש |
ערכים ב-whereCond#
| הערך | ה-SQL שנוצר |
|---|---|
null | col IS NULL |
מספר, או מחרוזת מספרית (MISC::is_int, שהוא is_numeric) | col = 5 (בלי גרשיים) |
מחרוזת שמתחילה ב-backtick, למשל "`tData.parentId`" | col = + הערך כפי שהוא (השוואת עמודות, ללא escape) |
| כל השאר | col = 'ערך אחרי DB::escape' |
המפתח id מקבל אוטומטית קידומת tConf.; כל מפתח אחר מודבק כמו שהוא, כך שאפשר לכתוב "tConf.ord" או "tData.title" במפורש. אין אופרטורים (<, LIKE, IN): אותם כותבים ב-filter.
ה-SQL שנבנה בטבלה רב-לשונית:
SELECT * FROM `CRM_news_content` tData, `CRM_news` tConf
WHERE true AND tData.parentId = tConf.id AND tData.langId = 1
AND (is_active = 1 AND date_created < NOW()) -- filter
AND tConf.id = 15 -- whereCond
GROUP BY ... ORDER BY ... LIMIT 0,10
fields, filter, order_by, group_by, limit, lang, שמות המפתחות של whereCond וערך שמתחיל ב-backtick מודבקים כמו שהם. רק ערכי whereCond הרגילים עוברים DB::escape. אל תעבירו אליהם קלט מהמשתמש ($_GET["sort"], $_POST["lang"]) בלי רשימת ערכים מותרים או (int).אם גם טבלת ה-conf וגם טבלת התוכן כוללות עמודה בשם זהה (למשל ord), whereCond["ord"] ייכשל ב-"Column 'ord' is ambiguous". כתבו "tConf.ord". ב-SELECT * עמודה כפולה בשורה המוחזרת נדרסת, ולכן כשחשוב שה-id יהיה של טבלת ה-conf, ציינו fields מפורש.
DB_QUERY_OBJECT::__construct()#
function __construct($table = "", $whereCond = array(), $orderBy = "", $lang = "", $limit = null, $dbClass = 'DB')
בדרך כלל לא קוראים לו ישירות, אלא דרך DB::query(). הוא בודק is_table_exists($table . "_content"); אם קיימת, קובע contentTable ו-lang. $whereCond ממוזג (array_merge) לתוך whereCond. $dbClass הוא המחלקה שאליה מופנות כל קריאות ה-DB (sql, get_row, escape ...).
DB_QUERY_OBJECT::BuildQuery()#
function BuildQuery()
בונה את $this->query מהמאפיינים הנוכחיים. נקראת אוטומטית מ-get_row() (בקריאה ראשונה), get_all() ו-num_rows(); קוראים לה ידנית רק כדי לבדוק את ה-SQL בלי להריץ אותו ($qu->BuildQuery(); echo $qu->query;). אם table ריק, לא נבנית שאילתה.
DB_QUERY_OBJECT::get_row()#
function get_row()
מחזירה: את השורה הבאה (גם נשמרת ב-$this->rows), או false בסוף. בקריאה הראשונה היא בונה ומריצה את השאילתה ושומרת את ה-statement; כל קריאה אחריה מתקדמת שורה אחת. בסוף היא קוראת ל-free_results(). לכן היא גם מתאימה לשורה בודדת וגם ללולאה:
$row = DB::query("news", ["id" => 15])->get_row(); // false אם אין
$qu = DB::query("subscribers", ["is_active" => 1], "id ASC");
while ($qu->get_row())
{
send_newsletter($qu->rows["email"]); // חוסך זיכרון בטבלאות גדולות
}
DB_QUERY_OBJECT::get_all()#
function get_all($key = false, $value = false)
| פרמטר | משמעות |
|---|---|
$key | עמודה שלפיה ממפתחים את התוצאה. אותה הצגה כמו ב-DB::get_all() |
$value | יחד עם $key: מפה key => value |
מחזירה: מערך שורות. מריצה את השאילתה מחדש בכל קריאה (מתעלמת מ-get_row קודם). שימו לב: התנאי הוא על אמת/שקר, לכן $key = 0 או "" נחשבים "לא הוגדר".
$all = DB::query("news", ["is_active" => 1])->get_all();
$byId = DB::query("news", ["is_active" => 1])->get_all("id");
$titles = DB::query("news", ["is_active" => 1], "ord ASC")->get_all("id", "title");
DB_QUERY_OBJECT::num_rows()#
function num_rows()
מחזירה: int. בונה ומריצה את השאילתה מחדש (עם ה-fields הנוכחי, כלומר מביאה את כל השורות) ואז סופרת. לספירה בלבד עדיף $qu->fields = "COUNT(*) AS c" ו-get_row()["c"].
DB_QUERY_OBJECT::affected_rows()#
function affected_rows()
מחזירה: $this->resQuery->rowCount(), ולכן תקפה רק אחרי get_row() או get_all() (לפני כן resQuery ריק ותיזרק שגיאה). ב-ClickHouse מחזירה תמיד 1.
DB_QUERY_OBJECT::free_results()#
function free_results()
משחררת את תוצאת השאילתה. ב-PDO אינה עושה דבר; נקראת אוטומטית בסוף get_row().
DB_UPDATE_CLASS: בונה ה-INSERT וה-UPDATE#
נוצר דרך DB::update("news"). אוסף עמודות וערכים עם set() ואז כותב עם insert() או update($id).
מאפיינים#
| מאפיין | ברירת מחדל | משמעות |
|---|---|---|
table | $table | שם הטבלה בלי קידומת |
editLang | CONFIG::$lang | langId שייכתב או יעודכן בטבלת התוכן |
arrSet | [] | עמודה => ["value" => ..., "tbType" => "conf" או "content"] |
new_id | null | אחרי insert(): ה-id החדש; אחרי update($id) על רשומה אחת: אותו id |
copyToAllLangs | false | ב-insert(): ניסיון ליצור שורת תוכן לכל שפה (ראו אזהרה) |
withContentValues | false | נדלק אוטומטית כש-set() נקרא עם "content" |
contentTableName | "<table>_content" | טבלת התוכן |
queryKey | "parentId" | עמודת הקישור בטבלת התוכן |
log | "" | כל ה-SQL שהורץ, מופרד ב-<p>. שימושי לדיבוג; בפעולות bulk גדל ללא גבול |
result | ה-statement האחרון של update(). ל-rowCount() השתמשו בו | |
query | "" | לא בשימוש (ראו affected_rows()) |
DB_UPDATE_CLASS::__construct()#
function __construct($table = "", $vars = [], $dbClass = 'DB')
בדרך כלל נקרא דרך DB::update(). $vars הוא מערך עמודה => ערך שמועבר ל-set() עבור כל מפתח. $dbClass מחליף את מחלקת ה-DB שאליה הבנאי מפנה את כל הקריאות.
DB_UPDATE_CLASS::set_var() ו-set()#
function set_var($colName, $val, $tbType = "conf")
function set($colName, $val, $tbType = "conf") // כינוי זהה ל-set_var
| פרמטר | משמעות |
|---|---|
$colName | שם עמודה. לא עובר escape ולא עטוף ב-backticks: לעולם לא מקלט משתמש |
$val | הערך. bool הופך ל-"1" או "0". שאר הכללים בטבלת ההמרה למטה |
$tbType | "conf" (ברירת מחדל, הטבלה הראשית) או "content" (טבלת התוכן) |
מחזירה: $this, ולכן אפשר לשרשר set(). זו המתודה היחידה בשרשרת שמחזירה את האובייקט: insert() ו-update() מחזירות bool.
"content" בלי טבלת תוכן
ב-update(), עמודה מסוג "content" כשהטבלה <table>_content לא קיימת תיכשל ב-SQL (content.col בלי join). ב-insert() היא תיזרק בשקט והערך לא יישמר. ודאו שהטבלה קיימת (וזכרו את מטמון table_list בן 7 הימים).טבלת ההמרה של ערכים (str_or_int_val, חלה על insert ו-update):
| ערך PHP | מה נכתב ב-SQL |
|---|---|
מספר "נקי": 5, "5", 3.5, -2 (is_numeric ו-(string)($val + 0) === (string)$val) | הערך כמו שהוא, בלי גרשיים |
מספר "לא נקי": "007", "1e3", "5.0", " 5" | 'ערך אחרי escape' (בגרשיים) |
המחרוזות "NULL", "NOW()", "CURRENT_TIMESTAMP" | כמו שהן (SQL גולמי) |
null | NULL |
"FUNC(expr)" | expr בלי העטיפה: הדרך לכתוב ביטוי SQL, למשל "FUNC(views + 1)" |
| כל השאר | ' + DB::escape($val) + ' |
מחרוזת שהמשתמש הקליד והיא בדיוק NULL או NOW(), או שמתחילה ב-FUNC( ונגמרת ב-), תפורש כ-SQL גולמי. בשדות טקסט חופשי שהקלט שלהם לא נשלט, נרמלו או דחו ערכים כאלה.
DB_UPDATE_CLASS::insert()#
function insert($mainLang = true)
| פרמטר | משמעות |
|---|---|
$mainLang | פרמטר פנימי של הרקורסיה של copyToAllLangs. אל תעבירו אותו |
מחזירה: true, או false אם sql() החזירה ערך שקר. ב-PDO כשל בדרך כלל נזרק כחריגה לפני כן. ה-id החדש ב-$this->new_id (מ-lastInsertId()).
מה היא עושה: INSERT לטבלה הראשית; אם קיימים ערכי "content" וטבלת התוכן קיימת, INSERT שני אליה עם parentId = new_id ו-langId = editLang. בלי אף set() היא מכניסה שורה עם id = DEFAULT. ב-pg מוסיפה RETURNING id. ב-ClickHouse מסתיימת ב-die("DB ERROR").
$ins = DB::update("news");
$ins->set("is_active", true) // conf, נכתב כ-1
->set("date_created", "NOW()") // SQL גולמי
->set("category_id", (int)$catId)
->set("title", $_POST["title"], "content") // news_content.title, langId = CONFIG::$lang
->set("body", $_POST["body"], "content");
if ($ins->insert())
{
$newId = $ins->new_id;
}
copyToAllLangs
כשהדגל דלוק, הקוד עובר על כל השורות של הטבלה CRM_langs וקורא ל-insert(false) עבור כל שפה, אבל עושה זאת לפני שה-INSERT הראשי רץ ו-new_id נקבע. בקריאות הפנימיות parentId נשאר ריק, ולכן שורות התוכן של השפות האחרות נשמרות יתומות. בנוסף הוא קורא לטבלה CRM_langs, בעוד שהאדמין בונה את רשימת השפות מ-CRM_platforms. אל תסתמכו על הדגל; צרו את שורות התוכן של השפות האחרות בעצמכם עם editLang (ראו update() למטה).DB_UPDATE_CLASS::update()#
function update($whereQuery)
| פרמטר | משמעות |
|---|---|
$whereQuery | מספר (id בודד), או אובייקט DB_QUERY_OBJECT שמגדיר את הרשומות. לא מקבלת מערך ולא מחרוזת: ערך מסוג אחר גורם לשגיאה |
מחזירה: true, או false אם sql() החזירה ערך שקר (ב-PDO כשל נזרק לרוב כחריגה). לכל id מורצת הצהרת UPDATE נפרדת. כשאף רשומה לא תואמת, לא מורץ דבר והיא מחזירה true. new_id נקבע רק כשנמצא id אחד בדיוק.
אם יש ערכי "content" וטבלת התוכן קיימת, לפני ה-UPDATE נבדק אם קיימת שורת תוכן ל-(id, editLang), ואם לא, היא נוצרת. כך שינוי editLang ושמירה יוצרים תרגום חדש. מבנה ה-UPDATE (שנרשם ב-$up->log):
UPDATE `CRM_news` conf , `CRM_news_content` content
SET conf.is_active = 1, content.title = 'x', conf.id = conf.id
WHERE conf.id = 15 AND conf.id = content.parentId AND content.langId = '1'
// עדכון id בודד, עם ביטוי SQL
$up = DB::update("news");
$up->set("views", "FUNC(views + 1)");
$up->update($newId);
// עדכון קבוצתי: כל הרשומות שעונות לשאילתה
$up = DB::update("news", ["is_active" => 0]);
$up->update(DB::query("news", ["category_id" => 7]));
// תרגום לשפה אחרת (יוצר שורת content ל-langId=2 אם חסרה)
$up = DB::update("news");
$up->editLang = 2;
$up->set("title", "English title", "content");
$up->update($newId);
לא נוקה אף cache (אם הטבלה נשמרת ב-cache_engine, קראו ל-remove בעצמכם), ו-sql_mode ריק מאפשר חיתוך ערכים בשקט. אין טרנזקציה: כמה id-ים הם כמה UPDATE-ים נפרדים.
DB_UPDATE_CLASS::ChangeContentTable()#
function ChangeContentTable($tblName, $queryKey)
מחליפה את טבלת הצד של ה-builder: contentTableName = $tblName, queryKey = $queryKey. אם שם הטבלה לא מסתיים ב-_content, לא נכתב ולא מסונן langId והקישור הוא queryKey = conf.id, כלומר טבלת צד שאינה רב-לשונית. אותה הפעולה ב-DB_QUERY_OBJECT נעשית בהצבת המאפיינים contentTable ו-queryKey.
$up = DB::update("news");
$up->ChangeContentTable("news_details", "news_id");
$up->set("extra", "...", "content"); // נכתב ל-news_details.extra
$up->update($id);
DB_UPDATE_CLASS::affected_rows()#
function affected_rows()
היא קוראת ל-$this->query->rowCount(), אבל $this->query הוא תמיד מחרוזת ריקה, ולכן ב-PDO היא זורקת Error. אל תשתמשו בה. את ה-statement של הפעולה האחרונה אפשר לקבל מ-$up->result (שנקבע ב-update()), למשל $up->result->rowCount().
DB_UPDATE_CLASS::str_or_int_val()#
function str_or_int_val($val)
מחזירה: את הערך כ-literal של SQL לפי טבלת ההמרה שלמעלה. ציבורית בגלל שלא הוגדרה אחרת, ונקראת פנימית מ-insert() ו-update(); לא צריך לקרוא לה. היא קוראת ל-escape() של $dbClass.
zend_query#
class zend_query extends DB_QUERY_OBJECT
function __construct($tbl)
function build()
function get_row()
function get_all($key = false, $value = false)
הבנאי קורא ל-CONFIG::$zend_db, ומאפיין זה לא מאותחל בשום מקום (האדפטר נשמר ב-DB::$zend_db), ואין בליבה אף קריאה למחלקה. אל תשתמשו בה; ל-JOIN מורכב השתמשו ב-DB::zend().
מבחר דוגמאות#
// שליפה ממופתחת, SQL גולמי
$panels = DB::get_all("SELECT * FROM " . CONFIG::$db_fullprefix . "adminPanel_panels WHERE is_active = 1", "id");
// builder עם filter, GROUP BY ובחירת עמודות
$qu = DB::query("orders", ["status" => "paid"]);
$qu->fields = "tConf.customer_id, COUNT(*) AS cnt, SUM(tConf.total) AS total";
$qu->filter = "tConf.date_created >= '" . DB::escape($from) . "'";
$qu->group_by = "tConf.customer_id";
$qu->order_by = "total DESC";
$qu->limit = 20;
$rows = $qu->get_all("customer_id");
// בדיקת סכימה לפני שימוש בעמודה אופציונלית
$ins = DB::update("storage");
if (DB::is_column_exists("storage", "file_hash")) $ins->set("file_hash", $hash);
ראו גם#
- שכבת ה-DB ו-שאילתות: המודל והדפוסים.
- בוני שאילתות: הסבר שימוש מלא ב-
DB::query()וב-DB::update(). - טבלאות רב-לשוניות: טבלאות
_contentו-langId. - דיבוג SQL:
sqllog,print_sql,is_sql_debug. - cache_engine: המטמון שמאחורי
is_table_existsו-is_column_exists. - CSRF, XSS ו-SQLi: איך משתמשים נכון ב-
DB::escape.