במדריך הזה תבנו עמוד אחד מתחילתו ועד סופו: קונטרולר ב-application/controllers, תבנית ב-application/views, כותרת SEO, שורת רישום בטבלת CRM_modules, וגלישה אל /hello ואל /hello/card/7. בסוף יש רשימת תקלות נפוצות, ובראשן ה-404 המפורסם של "יצרתי קובץ ושום דבר לא קורה".
הדרישה היחידה היא אתר שעובד (התקנת אתר חדש) וגישה לפאנל הניהול. מי שרוצה להבין לעומק מה קורה בכל שלב מוזמן לקרוא את מחזור חיי בקשה; כאן רק מה שצריך כדי להתקדם.
הליבה יושבת ב-api/core/ בריפו ונפרסת ל-system/core/ באתר. {admin} בכתובות הניהול הוא הערך של CONFIG::$admin_url.
איך עמוד נולד: שלוש חוליות#
כתובת רגילה באתר נקראת כך: הקטע הראשון הוא שם המודול, השני הוא שם המתודה והשלישי הוא ה-id.
| כתובת | מודול | מתודה | $_GET["id"] |
|---|---|---|---|
/hello | hello | index | לא קיים |
/hello/card | hello | card | לא קיים |
/hello/card/7 | hello | card | 7 |
/hello/7 | hello | index | 7 |
כדי שכתובת כזו תחזיר עמוד צריכים להתקיים שלושה דברים בבת אחת. הקובץ application/controllers/hello.php עם מחלקה בשם hello, שורה פעילה בטבלת CRM_modules עם moduleName = 'hello', ומתודה ציבורית בשם המבוקש. חסר אחד מהם, והתשובה היא 404.
קובץ קונטרולר שמונח בתיקייה עדיין לא נגיש. הליבה בודקת קודם ברשימת המודולים (CRM_modules) שהמודול רשום ופעיל, ורק אחר כך טוענת את הקובץ. כך אפשר להשבית עמוד בלי למחוק קוד. קונטרולרי מערכת של הליבה (קונטרולרי מערכת) הם היחידים שלא צריכים שורה כזו.
שלב 1: הקונטרולר#
צרו את הקובץ application/controllers/hello.php. שם הקובץ, שם המחלקה ושם המודול זהים, באותיות קטנות.
<?php
class hello extends wz_controller
{
// /hello
function index()
{
SEO::set("title", "שלום מ-WIZZO CMS");
SEO::set("description", "העמוד הראשון שלי על WIZZO CMS: קונטרולר, תבנית ו-SEO.");
return $this->view("hello/index", [
"name" => "עולם",
"items" => ["קונטרולר", "תבנית", "שורת מודול"],
]);
}
// /hello/card/7
function card()
{
$id = (int)($_GET["id"] ?? 0);
$cards = [
7 => ["title" => "כרטיס מספר שבע", "body" => "כאן יבוא התוכן מהמסד."],
8 => ["title" => "כרטיס מספר שמונה", "body" => "עוד תוכן לדוגמה."],
];
// לא קיים אצלנו: 404 אמיתי, לא עמוד ריק
if (!isset($cards[$id])) return "ERRORPAGE";
SEO::set("title", $cards[$id]["title"]);
return $this->view("hello/card", ["card" => $cards[$id]]);
}
}
כמה דברים שכדאי להבין מהקוד:
wz_controllerהיא מחלקת הבסיס של כל קונטרולר באתר (bgl_controllerהוא כינוי ישן שלה). היא נותנת לכם את$this->view(), את$this->load(טעינת מודלים וספריות) ועוד.- מה שהמתודה מחזירה הוא תוכן העמוד. הליבה לוקחת את המחרוזת, מעבירה אותה דרך
wrapper()ומשבצת אותה בתוך ה-theme. אל תעשוechoואל תעשוdie, אלא אם אתם מחזירים JSON (ראו בהמשך). - המחרוזת המיוחדת
"ERRORPAGE"אומרת לליבה "אין כאן עמוד": היא מנתבת מחדש לעמוד ה-404 של האתר (פרטים ב-404, 503 ושגיאות). SEO::set("title", ...)ממלא את תגיות הראש. קוראים לה לפני החזרת התוכן, והליבה מדפיסה את התגיות בעצמה. כל ה-API בעמוד תגיות SEO ו-Schema.- קוראים
$_GET["id"]תמיד בהמרה. הערך מגיע מהכתובת, ולכן(int)הוא המינימום. כתובת כמו/hello/card/7/abcמגיעה אלidכמחרוזת7/abc(הקטע השלישי אוסף את כל מה שנשאר), והמרה למספר מחזירה7.
הליבה קוראת לכל מתודה שאפשר לקרוא לה (is_callable), כולל מתודות עזר שכתבתם למחלקה. מתודה שלא אמורה להיות נגישה מהרשת צריכה להיות private או protected. אותו הדבר נכון גם למתודות שהקונטרולר יורש (view, system_view, wrapper): בכתובת /hello/view הליבה תקרא ל-view() בלי פרמטרים.
בחירת שם למודול#
השם הוא גם קטע בכתובת וגם שם מחלקת PHP, ולכן:
- אותיות לטיניות, ספרות וקו תחתון בלבד. שם עם מקף (
my-page) הוא לא שם מחלקה חוקי; לכתובת עם מקף משתמשים ב-כתובות ידידותיות. - אותיות קטנות. הבדיקה מול
CRM_modulesרגישה לאותיות, ולכן/Helloלא יפתח את המודולhello. - לא שם של מחלקה קיימת בליבה. שמות מחלקות ב-PHP אינם רגישים לאותיות, ולכן קונטרולר בשם
pageאוseoמתנגש במחלקותPAGEו-SEOונופל בשגיאה קטלנית. הרשימה המלאה ב-ה-Collections.
שלב 2: התבנית#
התבניות של האתר יושבות ב-application/views. הקריאה $this->view("hello/index", [...]) טוענת את application/views/hello/index.tpl: בלי הסיומת .tpl ובלי לוכסן בהתחלה. המשתנים במערך הופכים למשתני Smarty. צרו את application/views/hello/index.tpl:
<section class="hello">
<h1>שלום {$name|escape}</h1>
<ul>
{foreach $items as $item}
<li>{$item|escape}</li>
{/foreach}
</ul>
<p><a href="/hello/card/7">לכרטיס מספר שבע</a></p>
</section>
ואת application/views/hello/card.tpl:
<article class="hello-card">
<h1>{$card.title|escape}</h1>
<p>{$card.body|escape}</p>
<p><a href="/hello">חזרה</a></p>
</article>
הסינטקס המלא (תנאים, לולאות, include, פונקציות של המערכת) ב-תבניות Smarty.
Smarty באתר מוגדר בלי קידוד HTML אוטומטי. כל משתנה שמקורו בטופס, בכתובת או במסד נתונים עובר דרך |escape, אחרת הוא פתח ל-XSS. פירוט ב-CSRF, XSS ו-SQLi.
שלב 3: לרשום את המודול ב-CRM_modules#
הקובץ מוכן, אבל הכתובת /hello עדיין מחזירה 404, כי המודול לא רשום. יש שתי דרכים לרשום אותו.
דרך א: פאנל הניהול#
- היכנסו ל-
/{admin}/Modules(הפאנל צריך להיות רשום ומורשה למשתמש שלכם, ראו הרשאות וקבוצות). - לחצו על כפתור ההוספה, מלאו
Controller name=hello, סמנו פעיל ושמרו.
השמירה בפאנל מנקה בעצמה את מטמון רשימת המודולים, ולכן אין צורך בשום פעולה נוספת.
דרך ב: SQL#
INSERT INTO CRM_modules (moduleName, active) VALUES ('hello', 1);
הטבלה כוללת עוד עמודות (modulesDir, graphicsDir, tablesPre, adminDir, ord) שכולן אופציונליות והליבה אינה קוראת אותן בטעינת עמוד. moduleName הוא מפתח ייחודי, כך ששורה כפולה תיכשל. לשימוש חוזר בסקריפט התקנה כדאי INSERT IGNORE.
אחרי כתיבה ישירה למסד חייבים לנקות את המטמון (השלב הבא), כי רשימת המודולים נשמרת בקובץ מטמון.
אם אתם מחוברים לפאנל הניהול באותו דפדפן ופותחים כתובת של קונטרולר שקיים כקובץ אבל לא רשום, הליבה מציגה במקום 404 טופס קטן עם הכיתוב "Click here" שמציע להפעיל את המודול. הטופס שולח רק את moduleName ו-modulesDir, ללא השדה active. אחרי השימוש בו פתחו את המודול ב-/{admin}/Modules וודאו שתיבת פעיל מסומנת. הדרך המומלצת היא להוסיף את השורה ישירות בפאנל או ב-SQL.
שלב 4: ניקוי מטמון המודולים#
רשימת המודולים נשמרת לשבוע בקובץ cache/<host>/modules_lang<id>.chc.gz, ולכן שורה שנוספה ב-SQL לא נראית עד שמנקים. הדרכים:
- בפאנל
/{admin}/clear_cache(ניקוי מטמון נתונים). - מהקוד:
cache_engine::remove("modules"); - מחיקת הקבצים
modules_lang*.chc.gzמתיקיית המטמון של האתר.
פרטים על כל השכבות ב-איך המטמון עובד וב-ניקוי ותחזוקה.
שלב 5: גלישה#
פתחו בדפדפן:
https://<האתר שלכם>/hello: רשימת הפריטים וכותרת הדף "שלום מ-WIZZO CMS". הכותרת מופיעה בלשונית ובקוד המקור.https://<האתר שלכם>/hello/card/7: הכרטיס.https://<האתר שלכם>/hello/card/99: עמוד ה-404 של האתר, כי המתודה החזירה"ERRORPAGE".
העיצוב שסביב התוכן (כותרת עליונה, תחתית, CSS) מגיע מה-theme של האתר, ולא מהקונטרולר. איך בונים ובוחרים theme ב-ערכות נושא.
שלב 6: כתובת ידידותית (אופציונלי)#
אם תרצו שהכרטיס יענה בכתובת כמו /cards/seven במקום /hello/card/7, מוסיפים שורה לטבלת CRM_seoUrl. העמודה sysname היא הכתובת שהגולש רואה, ו-pageurl הוא הנתיב הפנימי שאליו הליבה מנתבת:
INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('cards/seven', 'hello/card/7', 0, 0);
הטבלה נשמרת במטמון (seoUrl_tbl); שמירה דרך פאנל ה-SEO מנקה אותו, ואחרי SQL ישיר מנקים בעצמכם עם cache_engine::remove("seoUrl_tbl");. כתובות עם ביטוי רגולרי, סדר הבדיקה ושאר התכונות ב-כתובות ידידותיות.
מה הלאה: JSON במקום עמוד#
כדי שמתודה תחזיר JSON ולא עמוד עם theme, מכריזים על מצב pmode=empg בקונסטרקטור או במתודה, שולחים כותרת מתאימה ומסיימים:
<?php
class hello_api extends wz_controller
{
function __construct()
{
parent::__construct();
$_GET["pmode"] = "empg"; // בלי theme, בלי head ו-body
}
function ping()
{
header("Content-Type: application/json; charset=utf-8");
echo json_encode(["ok" => true, "time" => date("c")]);
die;
}
}
גם קונטרולר כזה צריך שורה ב-CRM_modules (המודול hello_api, הכתובת /hello_api/ping). הסיבה ל-die מפורטת ב-מחזור חיי בקשה.
כשזה לא עובד#
| התסמין | הסיבה הסבירה | מה עושים |
|---|---|---|
| 404 | אין שורה ב-CRM_modules, או active שונה מ-1 | SELECT * FROM CRM_modules WHERE moduleName = 'hello'; הוסיפו או תקנו את השורה |
| 404 גם אחרי שהוספתם שורה ב-SQL | מטמון רשימת המודולים (7 ימים) | נקו עם cache_engine::remove("modules") או מהפאנל clear_cache |
| 404 והשורה בסדר | שם המחלקה בקובץ שונה משם המודול, או שם הקובץ באותיות אחרות (בשרת לינוקס Hello.php אינו hello.php) | התאימו את שלושת השמות; המחלקה חייבת להיות מוגדרת בקובץ עצמו |
| 404 רק בכתובת עם קטע שני | המתודה לא קיימת, או לא ציבורית | /hello/card מחפש מתודה ציבורית בשם card |
404 על /Hello | רישיות: שם המודול רגיש לאותיות | השתמשו באותיות קטנות בכל מקום |
| שגיאת Smarty על תבנית שלא נמצאה | הנתיב ב-$this->view() שגוי | הנתיב יחסי ל-application/views, בלי .tpl ובלי / בהתחלה |
| עמוד לבן או 500 | שגיאת PHP בקובץ | קראו את לוג השגיאות (ראו אבחון ולוגים) |
| השינוי בקוד לא נראה | הדפדפן, או מטמון עמוד מלא אם הקונטרולר הפעיל אותו | נקו כמתואר ב-מטמון עמודים מלא |
| לא ברור איזה ניתוב נבחר | חוק ניתוב מוקדם יותר תפס את הכתובת | התחברו לפאנל וגלשו ל-/hello?__router_trace=1 (ראו דיבוג ניתוב) |
כשמנהל מחובר, הוספת ?__router_trace=1 לכל כתובת מציגה בתחתית העמוד את כל שלבי ההחלטה של ה-ROUTER: איזה כלל נתפס, מה נקבע ב-$_GET, והאם המודול נמצא ברשימה. ברוב מקרי ה-404 התשובה כתובה שם.
מה למדנו#
- כתובת רגילה היא
/<מודול>/<מתודה>/<id>, והמודול הוא קובץ ב-application/controllers. - קונטרולר רגיל חייב שורה פעילה ב-
CRM_modules, ואחרי כתיבה ישירה למסד צריך לנקות את המטמון. - המתודה מחזירה את תוכן העמוד;
"ERRORPAGE"הוא 404. SEO::setקובעת כותרת ותיאור, ו-$this->viewמרנדרת תבנית.
הצעד הבא: קונטרולרים של אתר לפרטי המחלקה ולשיטות העבודה, ניתוב להבנת כל חוקי הכתובות, ו-מבנה התיקיות של אתר לראות איפה כל דבר יושב.