קונטרולר של אתר הוא קובץ PHP אחד ב-application/controllers/<name>.php עם מחלקה באותו שם, שיורשת מ-wz_controller. כל מתודה ציבורית שלו היא כתובת: /<name> מריץ את index(), ו-/<name>/<method> מריץ את המתודה. הקונטרולר מחזיר מחרוזת HTML, וה-theme עוטף אותה בעמוד.
system/ בעמוד הזה הוא תיקיית הליבה הפרוסה (api/core בריפו), ו-{admin} הוא CONFIG::$admin_url. קונטרולרים של הליבה עצמה מתוארים ב-קונטרולרי מערכת.
שלושה תנאים כדי שכתובת תגיע לקונטרולר#
- קובץ ומחלקה.
application/controllers/news.phpשמגדירclass news. שם הקובץ והמחלקה זהים לחלוטין (רגישים לאותיות גדולות וקטנות ב-Linux). - שורה פעילה ב-
CRM_modules.MODULE::get_htmlמחפש אתmoduleNameב-MISC::get_modules_tbl()ודורשactive == "1". בלי שורה, או עםactive = 0, התשובה היא 404. - מתודה ציבורית.
is_callable([$mdl, $page])חייב להצליח. מתודה פרטית או שלא קיימת מובילה ל-404.
INSERT INTO CRM_modules (moduleName, active) VALUES ('news', 1);
אחרי הוספה ידנית ב-SQL נקו את המטמון של הטבלה: cache_engine::remove("modules");. הטבלה נשמרת תחת modules_lang<מזהה פלטפורמה> ל-7 ימים, והמפתח modules תופס אותה לפי קידומת. כשמוסיפים מודול מפאנל {admin}/Modules הניקוי נעשה אוטומטית.
אם קובץ הקונטרולר קיים אבל אין לו שורה פעילה, ומי שגולש הוא מנהל מחובר, הליבה מציגה במקום העמוד טופס "Click here to activate this module", שמוסיף את השורה דרך {admin}/Modules/insert. גולש רגיל באותו מצב מקבל 404.
קונטרולר מינימלי#
<?php
// application/controllers/news.php
class news extends wz_controller
{
// GET /news => index()
// GET /news/12 => index() עם $_GET["id"] = "12"
// GET /news/archive => archive()
function index()
{
PAGE::$cache_this_page = true; // מטמון עמוד מלא (ראו page-cache)
PAGE::$cache_this_page_hours = 6;
$id = (int)($_GET["id"] ?? 0);
if ($id > 0)
{
$row = DB::query("news", ["id" => $id])->get_row();
if (!$row) return "ERRORPAGE"; // 404
SEO::set_var("title", $row["title"]);
return $this->view("news/item", ["row" => $row]);
}
SEO::set_var("title", "חדשות");
$rows = DB::query("news", ["is_active" => "1"], "id DESC", "", "0,20")->get_all();
return $this->view("news/list", ["rows" => $rows]);
}
function archive()
{
return $this->view("news/archive");
}
}
תבנית ה-Smarty של הדוגמה, application/views/news/list.tpl:
<ul class="news-list">
{foreach $rows as $row}
<li><a href="/news/{$row.id}">{$row.title|escape}</a></li>
{/foreach}
</ul>
מה הקונטרולר מחזיר#
| ערך החזרה | תוצאה |
|---|---|
| מחרוזת | הפלט של העמוד; עובר ל-wrapper() ואחר כך לתוך ה-theme |
"ERRORPAGE" | MODULE::errorpage(): 404 (או 503 אם הייתה שגיאת DB), ראו 404, 503 ושגיאות |
"REQUIRE_LOGIN" | מדפיס סקריפט שמפעיל $('#page').trigger('REQUIRE_LOGIN') בצד הלקוח (דורש jQuery ואלמנט #page) |
גם view(), system_view() ו-wrapper() שירשתם מ-wz_controller הם ציבוריים, ולכן /news/wrapper ו-/news/view מורצים. עזרים פנימיים הגדירו תמיד כ-private או protected, ואל תניחו שמתודה "לא מפורסמת" לא נגישה. בדקו הרשאה בתוך כל מתודה שחושפת מידע.
המחלקה wz_controller#
הקובץ system/collections/MODULE.php מגדיר את הבסיס (bgl_controller הוא כינוי ל-wz_controller, והוא נפוץ בקוד הליבה ובקוד ישן):
| חבר | חתימה | מה עושה |
|---|---|---|
$load | bgl_loader | טוען מודלים וספריות, ראו למטה |
wrapper | function wrapper($pageContent) | נקרא על התוצאה של כל מתודה; ברירת המחדל מחזירה אותה כמות שהיא. דורסים כדי לעטוף את כל המתודות (למשל JSON) |
view | function view($tpl, $vars = []) | מרנדר את application/views/<tpl>.tpl דרך FILES::html_template ומחזיר מחרוזת |
system_view | function system_view($tpl, $vars = []) | אותו דבר מ-system/views/<tpl>.tpl (תבניות הליבה) |
| מאפיינים דינמיים | __get / __set | מאפיין שלא הוגדר נשמר במערך פנימי; כך $this->m_articles עובד אחרי load->model |
view() מקבל נתיב בלי סיומת ובלי / בהתחלה. הרינדור עצמו מתואר ב-תבניות Smarty.
$this->load: מודלים וספריות#
function index()
{
$this->load->model("m_articles"); // application/models/m_articles.php, ויוצר $this->m_articles
$this->load->library("mail_tpl"); // system/libraries/mail_tpl.php (require_once בלבד)
$rows = $this->m_articles->latest(10);
return $this->view("news/list", ["rows" => $rows]);
}
| מתודה | התנהגות |
|---|---|
model($path) | מכניס CONFIG::$models_folder . "/" . $path . ".php" (ברירת מחדל application/models), ואם הקובץ חסר: die("ERROR: model $path is not exists"). אחרי הטעינה יוצר new <basename($path)> ושם אותו ב-$this-><basename>. אפשר תת-תיקיות: model("trg/m_banner") יוצר $this->m_banner |
library($path) | אם system/libraries/<path> הוא תיקייה, כולל את כל קבצי ה-PHP שבה (FILES::include_dir). אחרת require_once של system/libraries/<path>.php. לא יוצר מופע: משתמשים בקלאס ישירות (mail_tpl::...) |
מודל הוא קלאס רגיל, בדרך כלל extends bgl_model (כינוי ל-wz_model, שיורש מ-wz_controller ומוסיף את static merge(&$settings, $m_settings) למיזוג הגדרות ברירת מחדל):
<?php
// application/models/m_articles.php
class m_articles extends bgl_model
{
function latest($count)
{
return DB::query("articles", ["is_active" => "1"], "id DESC", "", "0," . (int)$count)->get_all();
}
}
model("m_articles") מניח מחלקה בשם m_articles. אם שם הקובץ שונה משם המחלקה, new ייכשל. ושימו לב: שום מודל או ספרייה לא נטענים אוטומטית, כל library() ו-model() נקראים במפורש.נקודות סיום בלי עמוד: JSON, הפניה, קובץ#
הקונבנציה בליבה: להגדיר $_GET["pmode"] = "empg" בתוך המתודה, ואז PAGE::output() מדלג על ה-theme ועל ה-<html> ומדפיס רק את מה שהוחזר.
<?php
// application/controllers/news_api.php
class news_api extends wz_controller
{
// GET /news_api/latest
function latest()
{
$_GET["pmode"] = "empg";
header("Content-Type: application/json; charset=utf-8");
$rows = DB::query("news", ["is_active" => "1"], "id DESC", "", "0,5")->get_all();
return json_encode($rows ?: [], JSON_UNESCAPED_UNICODE);
}
// GET /news_api/go/12
function go()
{
$_GET["pmode"] = "empg";
$id = (int)($_GET["id"] ?? 0);
header("Location: /news/" . $id);
exit;
}
}
לפלט אחיד לכל המתודות, דורסים את wrapper(): analytics של הליבה מגדיר pmode=empg ועוטף כל תוצאה ב-json_encode(["data" => $content]).
אם PAGE::$cache_this_page דלוק, התשובה נשמרת כקובץ HTML בכתובת הזו. בנתיבי JSON ופעולות (שגם נקראים ב-POST) השאירו אותו כבוי. ראו מטמון עמודים מלא.
קוד שרץ בכל בקשה: application/includes#
קבצי *.php ב-application/includes נכללים (FILES::include_dir, לפי סדר שם הקובץ, פעם אחת לבקשה) בכל בקשת לקוח, אחרי שהניתוב נקבע ולפני שהקונטרולר רץ. זה המקום לרשום אירועים (PAGE::bind), להוסיף נכסים גלובליים ולהגדיר PAGE::$parse_page_func. ראו PAGE: נכסים, מטא ואירועים.
מלכודות#
/p/<slug> ושמות שמורים
הכתובת /p/<slug> נתפסת לפני כל מודול (דפי "דף חדש"), ולכן קונטרולר בשם p לא יקבל כתובות כאלה. מודול ששמו system לא נגיש כמודול (הקידומת system/ שייכת לקונטרולרי מערכת), וכתובות כמו minify/<id>.js, assets/<קובץ קיים>, tag_manager.js, pg.js ו-<שם>.scss.css נתפסות לפני כל מודול.DB::insert
DB::insert(...) לא קיים (שגיאה קטלנית). הכנסה היא DB::update("table")->set_var("col", $val)->insert(), ועדכון הוא ->update($id). שמות טבלה ב-builder ללא CRM_; ב-SQL גולמי (DB::get_all, DB::sql) חייבים את הקידומת המלאה, וכל קלט חיצוני עובר ב-DB::escape. ראו שאילתות.ראו גם#
- ניתוב (ROUTER): איך
module,pnameו-idנקבעים - תבניות Smarty: מה
view()עושה - קונטרולרי מערכת: ה-
system/prefix - ערכות נושא: מי עוטף את הפלט
- 404, 503 ושגיאות
- ROUTER ו-MODULE (רפרנס)