הדף הזה קובע איך כותבים קוד חדש בליבה ובאתרים שרצים עליה. הוא מבחין בין התקן, שחל על כל שורה שנכתבת מעכשיו, לבין הקוד הקיים, שהצטבר לאורך שנים ולא תמיד עומד בו. כשהם סותרים, כותבים לפי התקן ולא מעתיקים מהקוד הישן.
אין כיום כלי שאוכף את התקן: אין phpcs, אין .editorconfig ואין CI (ראו בדיקות). האכיפה היא סקירת קוד ב-Pull Request. במפת הדרכים מתוארים הכלים שיאכפו אותו אוטומטית.
שמות וקבצים#
| מה | הכלל | דוגמה |
|---|---|---|
| קבצי PHP | snake_case, שם הקובץ זהה לשם המחלקה (כולל האותיות) | core_ping.php מגדיר core_ping |
| קונטרולר של אתר או קונטרולר מערכת | מחלקה שיורשת wz_controller (או bgl_controller, שם נרדף ישן) | class core_ping extends wz_controller |
| פאנל ניהול | ADMINMODULE_<שם_הקובץ> שיורש bgl_controller | ADMINMODULE_minify ב-admin/minify.php |
| ערכת נושא | TEMPLATE_<שם_התיקייה> שיורש bgl_theme, בקובץ themes/<שם>/index.php | TEMPLATE_admin_panel |
| מודל | מחלקה שיורשת wz_model (או bgl_model), בתיקייה CONFIG::$models_folder, נטענת עם $this->load->model('path') | m_orders |
| טבלאות | CRM_ ואחריו שם באותיות קטנות עם _ | CRM_adminPanel_admins |
| פעולות (actions) בפאנל | מתודה ציבורית אחת לכל פעולה, index היא ברירת המחדל | groups_insert() ב-ADMINMODULE_minify |
כל הקבצים תחת api/core/controllers/ נגישים מהרשת בלי הרשאה אוטומטית. מי שמוסיף קובץ שם מוסיף נקודת כניסה ציבורית. פירוט במבנה הריפו ובקונטרולרי מערכת.
סגנון PHP#
- הזחה בטאבים (זה הרוב בקבצים המרכזיים,
DB.php,MISC.php,controllers/*). קובץ קיים שמוזח ברווחים: משמרים את ההזחה שלו ולא מעצבים אותו מחדש, כדי שה-diff יישאר קריא. - בלי
?>סוגר בסוף קובץ, כדי שלא ייפלט רווח או שורה ריקה לפני ה-headers. - קידוד UTF-8 בלי BOM, סיומות שורה LF (אין CRLF בקבצי הליבה).
- אין namespaces ואין autoload של Composer לקוד הליבה. הקבצים של
collections/נטענים בעזרתglobבעליית המערכת, ולכן שם המחלקה חייב להיות ייחודי גלובלית. - בלי
display_errors,error_reportingאוini_setשל שגיאות בתוך קוד: הם נקבעים ב-CONFIG_USERאו בהגדרות השרת (ראו קונפיגורציה). - בלי קוד מוער שנשאר "ליתר ביטחון". Git שומר היסטוריה.
- טקסט שמוצג למשתמש בממשק ניהול עובר דרך
LANGS::get_word('KEY')כשקיים מפתח מתאים (ראו פרמטרים ושפות). פאנלים ותיקים מכילים עברית ישירות בקוד, וזה מקובל שם, אבל מפתחות חדשים שייכים למילון.
<?php
class ADMINMODULE_orders extends bgl_controller
{
public function index()
{
$list = new panel_table("orders");
$list->query = DB::query("orders");
$list->titles = array(
array("title" => "id", "field" => "id"),
array("title" => "name", "field" => "name")
);
return $list->get_html();
}
}
כללי DB#
כל הכללים כאן נובעים מאיך ש-DB בנוי בפועל. הפירוט המלא בשכבת ה-DB ובשאילתות.
| כלל | למה |
|---|---|
אין DB::insert. הכנסה: DB::update("table")->set("col", $val)->insert(). עדכון: ->update($id). שתיהן מחזירות bool | DB::insert לא קיים וגורם ל-fatal error. קוד ישן שקורא לו הוא קוד מת |
ה-builder (DB::query, DB::update, DB::delete, DB::get_val) מקבל שמות טבלה בלי CRM_ | הקידומת מתווספת אוטומטית |
SQL גולמי (DB::get_all, DB::get_row, DB::sql) מחייב את השם המלא CRM_... | אין שם הוספה אוטומטית |
כל ערך חיצוני ב-SQL גולמי עובר DB::escape או הופך ל-(int) | אין prepared statements בשכבה |
DB::escape מחזיר מחרוזת בלי גרשיים מקיפים, ולכן חייבים לעטוף אותה בעצמכם: "... WHERE name = '" . DB::escape($v) . "'" | escape מסיר את הגרשיים ש-PDO מוסיף. ערך ריק או NULL חוזר כמות שהוא |
לא להעביר מחרוזת של משתמש כארגומנט שני של DB::get_val | מחרוזת (שאינה מספר) מתפרשת כתנאי SQL גולמי ($filter), לא כערך להשוואה |
מזהים ב-backtick, קריאות FUNC() ומפתחות order, limit, group נחשבים גולמיים | אותם אסור להרכיב מקלט משתמש בלי רשימה לבנה |
id מספרי נכנס ל-SQL כ-(int) | ההמרה סוגרת הזרקה בלי תלות ב-escape |
// נכון: builder, בלי CRM_, מערך תנאים
$ok = DB::update("orders")
->set("name", $name)
->set("status", 1)
->insert();
$row = DB::get_row("SELECT * FROM CRM_orders WHERE id = " . (int)$id);
// נכון: ערך מחרוזת ב-SQL גולמי
$rows = DB::get_all("SELECT id FROM CRM_orders WHERE email = '" . DB::escape($email) . "'");
בקשות GET ו-POST, כותרות HTTP ושדות מ-JSON הם קלט חיצוני. ב-$_GET["id"] שנדבק לשאילתה בלי המרה נמצא מקור מרכזי לפרצות בליבה. אין חריגים "כי זה פאנל ניהול".
אבטחה בקוד חדש#
- בדיקת הרשאה בכל נקודת כניסה שמשנה מצב או חושפת מידע. בפאנל:
ADMIN::is_admin()בודק שמחובר מנהל, ו-ADMIN::has_perms($perm, $page, $action)בודק הרשאה לפעולה (ראו הרשאות). קונטרולר מערכת שלא בודק כלום פתוח לכל העולם. - פלט ל-HTML עובר
MISC::special_chars($text)(עוטףhtmlspecialcharsומחליף גם'ב-'). פלט שנכנס לתוך מאפיין או ל-JS מקבל קידוד מתאים להקשר. - פעולות שמשנות מצב (מחיקה, שמירה, שינוי הגדרות) לא מתבצעות על GET. ראו CSRF, XSS ו-SQLi למצב ההגנות בפועל.
- קבצים: מעלים רק דרך
STORAGEובודקים סיומת ותוכן לפי כללי העלאת קבצים. לא סומכים על$_FILES[..]['type']. - מייל, SMS ו-push: לא קוראים ל-
MISC::emailולא לשירותי השליחה מתוך קוד שרץ בבדיקות, ב-cron לא מתוזמן או בלולאה. כל שליחה אמיתית היא החלטה מפורשת. - סודות (סיסמאות, מפתחות API, טוקנים) לא נכתבים בקוד, בקובץ בריפו או ב-commit. הם חיים ב-
CONFIG_USERשל האתר או בטבלת ההגדרות. ראו הקשחה. - תשובות לשגיאות: לא
die("...")עם טקסט חופשי שמכיל נתיבים או שגיאות SQL. מחזירים קוד סטטוס ו-JSON קצר, וכותבים פרטים ללוג (ראו קודי שגיאה). unserializeעל קלט שמגיע מבחוץ,evalו-includeשל נתיב שנבנה מפרמטר אסורים. לנתונים מבניים משתמשים ב-json_decode.
JavaScript ו-Vue#
- Vue 3 ב-Options API בלבד. לא Composition API ולא
<script setup>. - קריאות שרת דרך
this.api(), ניווט דרךthis.goto(), הודעות דרךthis.toast(). - כל פאנל Vue חייב להיות מחובר לטעינת AJAX של האדמין: פונקציות
mountAppו-unmountApp, ו-MutationObserverשמפעיל אותן כשהפאנל נכנס ויוצא מה-DOM. פירוט בפאנלי Vue. - FontAwesome באדמין הוא גרסה 5. שמות אייקונים של גרסה 6 לא יוצגו, והשדה יישאר ריק.
- לעולם לא
v-if,v-elseאוv-forעל אלמנט<i>של FontAwesome. הסקריפט של FA מחליף את ה-<i>ב-<svg>, וה-patch של Vue נשבר עד שהרכיב כולו מפסיק להתרנדר. עוטפים:
<span v-if="done" class="wz_if"><i class="fas fa-check"></i></span>
<span v-else class="wz_if"><i class="fas fa-clock"></i></span>
- ספינר לא נבנה מ-FA. משתמשים ב-
<span v-if="loading" class="wz_spin"></span>.
CSS ו-SCSS#
- עורכים
.scssבלבד. הקובץ ה-.cssנוצר בצד השרת ולא נערך ידנית (ראו צינור הנכסים). - מאפיינים פיזיים של כיוון אסורים: לא
left,right,margin-left,padding-right,text-align: left,float: right. משתמשים באלו הלוגיים:inset-inline-start/inset-inline-end,margin-inline-*,padding-inline-*,text-align: start/end. inline-endב-RTL הוא הצד השמאלי. "end" אינו "right".- הלוגיים עוקבים אחרי כיוון האלמנט עצמו: על תא עם
direction: ltr,padding-inline-endינחת בצד ימין פיזית. - באדמין הכיוון נקבע ב-class של ה-
body(body.rtl) ולא ב-[dir=rtl]. כלל שתלוי בכיוון נכתב גם כ-.rtl &.
גרסה, commit ותיעוד#
- שינוי בקוד שנשלח ללקוחות מעלה את
api/core/version.txtבאותו PR (ראו שליחת שינויים וגרסאות). - הודעת commit קצרה ומתארת מה השתנה ולמה. פורמט מלא בשליחת שינויים.
- שינוי ב-API ציבורי של מחלקה (חתימה, מפתח הגדרה, שם טבלה) מעדכן באותו PR את דף הרפרנס המתאים תחת רפרנס ליבה.
הקוד הקיים והתקן#
חלק גדול מהקוד נכתב לפני שהתקן הזה נוסח, ולכן תמצאו בו דברים שהתקן אוסר: $_GET בתוך SQL, die עם טקסט, בדיקות הרשאה חסרות, הזחה מעורבת, קוד מוער. הנחיות לעבודה בקוד כזה:
- לא מעתיקים דפוס מקוד ישן רק כי הוא קיים. מחפשים את הדוגמה בדפי התיעוד.
- לא מתקנים בהזדמנות קבצים שלא קשורים לשינוי. PR ממוקד קל לסקירה ולביטול.
- כשנוגעים בפונקציה, מתקנים בה מה שהתקן אוסר ושייך לשורות שאתם משנים (למשל המרת
$idל-(int)). - ממצא אבטחה בקוד שלא נגעתם בו: מדווחים בצנרת הפנימית, לא פותחים issue ציבורי ולא מתארים אותו בדף תיעוד.