DB

רפרנס מלא של המחלקה DB ושל מחלקות ה-builder שלה (DB_QUERY_OBJECT, DB_UPDATE_CLASS): כל מתודה ציבורית עם חתימה, פרמטרים, ערך החזרה והתנהגות בפועל.

⏱ 27 דק' קריאה 4929 מילים ערוך דף זה ב-GitHub

הדף הזה הוא רפרנס לכל המתודות הציבוריות של 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().

מה חשוב ב-PHP 8.3

הליבה לא קוראת ל-PDO::setAttribute(PDO::ATTR_ERRMODE, ...) בשום מקום, וברירת המחדל של PDO מאז PHP 8.0 היא חריגות. לכן שאילתה שנכשלה (טבלה חסרה, תחביר שגוי) זורקת Exception מתוך DB::sql(), וההודעה כוללת את ה-SQL. ההערות בקוד על "silent mode" ו"תוצאה ריקה" מתארות התנהגות ישנה. קוד שבו כישלון מותר חייב try/catch.

מאפיינים סטטיים#

מאפייןברירת מחדלמשמעות
DB::$condbfalseאובייקט החיבור (\PDO, או מערך פרטים ב-ClickHouse)
DB::$encoding"utf8mb4"נשלח כ-set names בחיבור PDO
DB::$throw_on_errorfalseמשפיע רק על ההודעה של החריגה שנזרקת בכשל PDO (ראו DB::sql())
DB::$error_count0כמה שאילתות נכשלו בנתיב ה"שקט" בבקשה הנוכחית
DB::$last_error""הודעת השגיאה האחרונה באותו נתיב, וגם הודעת כשל חיבור
DB::$print_sqlfalsetrue מדפיס כל שאילתה ל-HTML בתוך <p>
DB::$is_sql_debugfalsetrue מוסיף /* REQUEST_URI */ לתחילת כל שאילתה
DB::$log_sqlfalseנדלק אוטומטית כשהפרמטר log_sql ב-CRM_params שווה "1"; כותב ל-sqllog.txt
DB::$table_exist_array, DB::$table_columns_arrayfalse, []זיכרון בתוך הבקשה של is_table_exists ו-is_column_exists
DB::$capture_cfg, $capture_buffer, $capture_registered, $capture_droppedמצב ה-Performance Advisor (ראו הסעיף על capture)
DB::$zend_dbfalseהאדפטר של DB::zend()
DB::$laminas_dbfalseמוצהר ולא בשימוש. אין בקוד שום שימוש ב-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").

רק PDO באמת עובד
"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)
פרמטרטיפוסמשמעות
$sqlstringשאילתה שלמה, עם שם טבלה מלא (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)
פרמטרמשמעות
$resstatement מ-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)
פרמטרמשמעות
$resstatement או מחרוזת 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 . "'");
מה escape לא עושה

היא מטפלת בערכי מחרוזת בלבד. היא לא מצטטת מזהים (שמות טבלה ועמודה, backtick), לא מוסיפה גרשיים, ולא מנטרלת את % ו-_ בתוך LIKE. ערך מספרי עטפו ב-(int). שם עמודה או כיוון מיון מקלט חיצוני: רק מול רשימת ערכים מותרים. ראו CSRF, XSS ו-SQLi.

שאילתות עם builder#

DB::query()#

public static function query($table = "", $whereCond = array(), $orderBy = "", $lang = "", $limit = null)
פרמטרטיפוסמשמעות
$tablestringשם הטבלה בלי CRM_. אם קיימת <table>_content, היא מצורפת אוטומטית (ראו טבלאות רב-לשוניות)
$whereCondarrayעמודה => ערך, מחובר ב-AND. ראו חוקי הערכים ב-DB_QUERY_OBJECT
$orderBystringSQL גולמי אחרי ORDER BY
$langint|stringlangId לטבלת התוכן. ברירת מחדל: CONFIG::$lang. מודבק בלי גרשיים
$limitstring|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");    // תנאי גולמי
מחרוזת לא מספרית היא SQL
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]);
מחיקה מבוססת על ה-JOIN

בטבלה רב-לשונית, ה-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 ימים. אם הטבלה לא קיימת, מוחזרת (ונשמרת) רשימה ריקה.

המטמון חי 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$orderBySQL גולמי אחרי ORDER BY
group_by""SQL גולמי אחרי GROUP BY
limit$limitמודבק אחרי LIMIT (מושמט כשהערך null או ריק)
langCONFIG::$langlangId לטבלת התוכן. מוגדר רק כשקיימת טבלת תוכן
contentTable"<table>_content" או ""שם טבלת התוכן. איפוס ל-"" מבטל את ה-JOIN; ערך אחר מציב טבלת צד
queryKey"parentId"עמודת הקישור בטבלת התוכן
tConfConnector"id"העמודה בטבלה הראשית שאליה מתחברים
rowsהשורה הנוכחית אחרי get_row()
query""ה-SQL שנבנה (אחרי BuildQuery()), לדיבוג
resQueryה-statement של ההרצה האחרונה
tableNameמוצהר ואינו בשימוש

ערכים ב-whereCond#

הערךה-SQL שנוצר
nullcol 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
מה לא עובר escape
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שם הטבלה בלי קידומת
editLangCONFIG::$langlangId שייכתב או יעודכן בטבלת התוכן
arrSet[]עמודה => ["value" => ..., "tbType" => "conf" או "content"]
new_idnullאחרי insert(): ה-id החדש; אחרי update($id) על רשומה אחת: אותו id
copyToAllLangsfalseב-insert(): ניסיון ליצור שורת תוכן לכל שפה (ראו אזהרה)
withContentValuesfalseנדלק אוטומטית כש-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 גולמי)
nullNULL
"FUNC(expr)"expr בלי העטיפה: הדרך לכתוב ביטוי SQL, למשל "FUNC(views + 1)"
כל השאר' + DB::escape($val) + '
ערכי &quot;קסם&quot;

מחרוזת שהמשתמש הקליד והיא בדיוק 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()
שבורה ב-PDO

היא קוראת ל-$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);

ראו גם#

מצאתם טעות או חוסר? תקנו את הדף או פתחו Issue בריפו. התיעוד נכתב מתוך הקוד של ליבה 5.0.115.