כתובת כמו /about-us או /blog/my-first-post לא חייבת להתאים לשם של קונטרולר. הטבלה CRM_seoUrl מכילה כללי מיפוי: כתובת ידידותית (sysname) והנתיב הפנימי שהיא מייצגת (pageurl), כולל כללים שמבוססים על ביטוי רגולרי. ROUTER::parse_friendly_url בודק את הטבלה לפני שהוא מפרק את הכתובת לקונטרולר ולמתודה.
system/ בעמוד הזה הוא תיקיית הליבה הפרוסה (api/core בריפו), ו-{admin} הוא CONFIG::$admin_url.
מבנה הטבלה#
הטבלה נקראת seoUrl ב-builder של DB (ו-CRM_seoUrl ב-SQL גולמי):
| עמודה | משמעות |
|---|---|
id | מפתח |
sysname | הכתובת הידידותית (ללא / בהתחלה ובסוף), או ביטוי רגולרי עם delimiters כשהדגל regularexp דלוק |
pageurl | הנתיב הפנימי שאליו הכתובת מתורגמת; יכול לכלול ?key=value ו-$1, $2 בכללי regex |
regularexp | 0 להתאמה מדויקת, 1 להתאמת regex |
ord | סדר הבדיקה בין כללי ה-regex (עולה); ROUTER::get_seo_tbl() ממיין לפי ord ASC |
התאמה מדויקת#
כלל עם regularexp = 0 מתאים כש-sysname שווה לכתובת (אחרי הסרת / מההתחלה ומהסוף), או שווה לכתובת אחרי urldecode. כך כתובת בעברית מותאמת בין אם הדפדפן שלח אותה מקודדת ובין אם לא.
-- כתובת בלי הרחבה: /about-us פותחת את about/index
INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('about-us', 'about', 0, 0);
-- כתובת שמעבירה פרמטר: /products/shoes פותחת products/category?slug=shoes
INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('products/shoes', 'products/category?slug=shoes', 0, 0);
אותו דבר דרך ה-builder (אין DB::insert, ההכנסה היא DB::update(...)->insert()):
$q = DB::update("seoUrl");
$q->set_var("sysname", "about-us");
$q->set_var("pageurl", "about");
$q->set_var("regularexp", 0);
$q->insert();
cache_engine::remove("seoUrl_tbl"); // ראו "מטמון" למטה
אם pageurl כולל ?, מה שאחריו נפרס ונמזג ל-$_GET (ודורס מפתח באותו שם), והנתיב שלפניו הוא זה שמפורק לקונטרולר ולמתודה לפי הטבלה ב-ניתוב.
כללי ביטוי רגולרי#
כלל עם regularexp = 1 מורץ רק אם לא נמצאה התאמה מדויקת. ההתאמה היא preg_match($sysname, $url) על הכתובת, ואם לא הצליחה, שוב על הכתובת אחרי urldecode. ב-pageurl כותבים $1, $2: הקוד מחליף כל $ ב-\ ומריץ preg_replace.
-- /blog/my-first-post => blog/post?slug=my-first-post
INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('/^blog\\/([a-z0-9-]+)$/', 'blog/post?slug=$1', 1, 10);
-- /he/news/12 => news/index/12 (id יהיה "12")
INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('/^[a-z]{2}\\/news\\/(\\d+)$/', 'news/index/$1', 1, 20);
preg_replace מחליף רק את החלק שהותאם. ביטוי בלי ^ ובלי $ משאיר את שאר הכתובת מוצמד לנתיב הפנימי, ותקבלו נתיב שבור שנראה כמו קונטרולר אחר. פאנל הניהול מזהיר אם חסר ^, אבל לא מונע את השמירה.כל כללי ה-regex עוברים בלולאה לפי ord, עם preg_match על כל כתובת שלא נפתרה קודם (גם על נכסים שמגיעים ל-PHP). שגיאת קומפילציה ב-preg_replace מושתקת ב-@, כלומר ביטוי שבור פשוט לא נותן תוצאה. שמרו כללים ספציפיים בראש (ord נמוך), והימנעו מביטויים כמו /(.*)/.
כתובת 404 מותאמת#
שורה עם sysname = '404' מגדירה את העמוד שמוצג כשאין התאמה: ה-pageurl שלה הוא הקונטרולר והמתודה שירונדרו. ראו 404, 503 ושגיאות.
INSERT INTO CRM_seoUrl (sysname, pageurl, regularexp, ord)
VALUES ('404', 'errors/not_found', 0, 0);
ניהול מפאנל הניהול#
הפאנל ADMINMODULE_Seo (system/admin/Seo.php, הכתובת {admin}/Seo) מציג רשימה של כל השורות, ובטופס יש שלושה שדות: כתובת חדשה (sysname), כתובת מקורית (pageurl) ותיבת "ביטוי רגולרי". שמירת כלל regex עוברת בדיקה בצד השרת: חייבים delimiters (/, #, ~, @, % או !), והביטוי חייב להתקמפל ב-preg_match, אחרת השמירה נדחית עם הודעה. הטופס כולל גם תצוגה מקדימה חיה של כתובת דוגמה. שמירה ומחיקה מנקות את המטמון.
שורה לכל רשומה: FormInput_SEO#
טופס רשומה (למשל חדשות) יכול לתת למנהל שדה "Page URL" שמנהל שורה אחת ב-seoUrl לרשומה, באמצעות FormInput_SEO עם pageUrl (ראו SEO למפתחי אתר). השדה מוחק שורה ישנה אם הכתובת השתנתה ומחזיר שגיאה אם הכתובת כבר תפוסה על ידי נתיב אחר. ב-DB::delete של רשומה, מחקו גם את השורה:
$t->delete = function ($id) {
DB::delete("news", ["id" => $id]);
ROUTER::remove_by_url("news/item?id=" . $id); // מוחק שורות seoUrl שה-pageurl שלהן שווה לערך
};
מטמון#
ROUTER::get_seo_tbl() קורא את כל הטבלה ושומר אותה תחת המפתח seoUrl_tbl, 24 שעות כברירת מחדל. הפאנל וה-FormInput_SEO מנקים אותו, אבל הכנסה ישירה ב-SQL או מקוד לא מנקה: קראו ל-cache_engine::remove("seoUrl_tbl") או נקו מטמון מהפאנל (ניקוי ותחזוקה). בנוסף, עמוד מלא שכבר נשמר ב-מטמון עמודים ימשיך להיות מוגש עד שיפוג או יימחק.
קריאה מהקוד#
// נתיב פנימי => כתובת ידידותית (או הנתיב עצמו אם אין שורה)
$friendly = ROUTER::get_by_url("blog/post?slug=my-first-post");
// כתובת ידידותית => נתיב פנימי
$internal = ROUTER::get_by_friendly("about-us");
get_by_url מחפש התאמה מדויקת
הוא משווה את pageurl כמחרוזת, ולכן כללי regex (עם $1) לא יחזירו כתובת ידידותית. לכתובות כאלה בנו את הקישור בעצמכם.ראו גם#
- ניתוב (ROUTER): הסדר המלא של ההחלטות
- הפניות 301: כתובת ישנה שצריכה להפנות לחדשה
- דיבוג ניתוב: לראות איזה כלל תפס כתובת
- 404, 503 ושגיאות
- ROUTER ו-MODULE (רפרנס)