סניפט טיפוסי הוא העתק-הדבק לא מותאם
דמיינו שקטלוג המוצרים שלכם ב-MODX נטען תוך 5 שניות, וכל לחיצה על קטגוריה מרגישה כמו נצח. הסיבה: סניפטים לא מותאמים שמבצעים עשרות שאילתות מסד נתונים. תרחיש טיפוסי: מפתח העתיק את הקוד הראשון שמצא, בלי לחשוב על קשנון (Caching). התוצאה: שאילתות N+1, ירידה בביצועים וחוויית משתמש ירודה. איך לתקן? להשתמש בתבניות נכונות.
פיתוח סניפטים ל-MODX מתחיל לעיתים קרובות בהעתקת הדוגמה הראשונה מהתיעוד. התוצאה: שאילתות N+1, ללא קשנון, ופגיעויות שהופכות משימה פשוטה לצוואר בקבוק באתר כולו. אנו משתמשים בתבניות ארכיטקטוניות מוכחות: שאילתה אחת, קשנון והקלדה קפדנית של פרמטרים. ניסיון של למעלה מ-5 שנים ויותר מ-50 פרויקטים על MODX מאפשרים לנו ליצור סניפטים שעומדים באלפי בקשות ללא אובדן ביצועים.
איך להימנע משאילתות N+1 ב-MODX?
בעיית N+1 מתרחשת כאשר אתם שולפים אוסף של משאבים ולאחר מכן עבור כל אחד מהם טוענים ערכי TV דרך getTVValue בלולאה. עם 100 משאבים, מדובר ב-101 שאילתות SQL. הפתרון: להשתמש ב-JOIN או ב-pdoFetch. ה-API המקורי של MODX מאפשר JOIN ידני, אבל pdoTools עושה זאת אוטומטית ומהירה יותר. מידע נוסף על הבעיה בבעיית שאילתות N+1.
למה קשנון סניפטים חשוב?
קשנון מפחית את העומס על מסד הנתונים ומאיץ דפים. ללא קשנון, כל קריאה לסניפט משמעותה שאילתת SQL. בפרויקט עם קטלוג של 5000 פריטים, הסניפט המקורי לקח 4 שניות. לאחר הוספת קשנון, הזמן ירד ל-0.2 שניות לבקשות הבאות. יש לפסול את הקשנון כאשר הנתונים משתנים או להגדיר TTL, למשל 30 דקות.
מבנה סניפט עם שאילתה בטוחה
שאילתה מקורית עם JOIN
<?php // Сниппет ProductList // Вызов: [[!ProductList? &category=`5` &limit=`12` &sort=`price`]] // Получить параметры (с default значениями) $categoryId = (int)($scriptProperties['category'] ?? 0); $limit = (int)($scriptProperties['limit'] ?? 10); $offset = (int)($scriptProperties['offset'] ?? 0); $sortField = $scriptProperties['sort'] ?? 'menuindex'; $sortDir = $scriptProperties['sortdir'] ?? 'ASC'; $tpl = $scriptProperties['tpl'] ?? 'productCard'; // Запрос к ресурсам через MODX API $c = $modx->newQuery('modResource'); $c->where([ 'parent' => $categoryId, 'published' => 1, 'deleted' => 0, 'class_key' => 'modDocument', ]); // Получить TV значения через Join $c->innerJoin('modTemplateVarResource', 'TVPrice', [ 'TVPrice.tmplvarid' => $modx->getObject('modTemplateVar', ['name' => 'price'])->id, 'TVPrice.contentid = modResource.id', ]); $c->select('modResource.*, TVPrice.value AS price'); $c->sortby($sortField, $sortDir); $c->limit($limit, $offset); $resources = $modx->getCollection('modResource', $c); if (empty($resources)) return ''; $output = ''; foreach ($resources as $resource) { $data = array_merge($resource->toArray(), [ 'price' => $resource->get('price'), 'link' => $modx->makeUrl($resource->id, '', '', 'full'), 'image' => $resource->getTVValue('product_image'), ]); // Чанк для вывода карточки $output .= $modx->getChunk($tpl, $data); } return $output; סניפט עם pdoTools
<?php // Сниппет ProductSearch с pdoTools
if (!$modx->loadClass('pdoFetch', MODX_CORE_PATH . 'components/pdotools/model/pdotools/', false, true)) {
return 'pdoTools не установлен';
}
$pdoFetch = new pdoFetch($modx, $scriptProperties);
$pdoFetch->addWhere([
'modResource.parent' => (int)($scriptProperties['category'] ?? 0),
'modResource.published' => 1,
]);
// TV join
$pdoFetch->addTVs('price,product_image,short_description');
$result = $pdoFetch->run();
return $result; קשנון תוצאות
<?php
// Кэшировать результат на 30 минут
$cacheKey = 'products_' . md5(json_encode($scriptProperties));
$cacheOptions = [
xPDO::OPT_CACHE_KEY => 'default',
xPDO::OPT_CACHE_EXPIRES => 1800
];
$cached = $modx->cacheManager->get($cacheKey, $cacheOptions);
if ($cached !== null) return $cached;
// ... запрос ...
$output = generateOutput($resources);
$modx->cacheManager->set($cacheKey, $output, 1800, $cacheOptions);
return $output; אינטגרציה עם API חיצוני
<?php
// Сниппет WeatherWidget — погода из OpenWeatherMap
$city = $scriptProperties['city'] ?? 'Moscow';
$apiKey = $modx->getOption('weather_api_key');
$tpl = $scriptProperties['tpl'] ?? 'weatherWidget';
$cacheKey = 'weather_' . $city;
$cached = $modx->cacheManager->get($cacheKey, [xPDO::OPT_CACHE_EXPIRES => 1800]);
if ($cached !== null) {
return $modx->getChunk($tpl, $cached);
}
$url = "https://api.openweathermap.org/data/2.5/weather?q={$city}&appid={$apiKey}&units=metric&lang=ru";
$response = file_get_contents($url);
if (!$response) return '';
$data = json_decode($response, true);
if (!$data || $data['cod'] !== 200) return '';
$weather = [
'city' => $data['name'],
'temp' => round($data['main']['temp']),
'feels_like' => round($data['main']['feels_like']),
'description' => $data['weather'][0]['description'],
'icon' => "https://openweathermap.org/img/wn/{$data['weather'][0]['icon']}@2x.png",
'humidity' => $data['main']['humidity'],
];
$modx->cacheManager->set($cacheKey, $weather, 1800);
return $modx->getChunk($tpl, $weather);
אילו פרמטרים להעביר לסניפט?
[[!ProductList? &category=`[[*id]]` &limit=`12` &tpl=`productCardTpl` &sort=`price` &sortdir=`ASC` ]] <?php // Сниппет ProductList // Вызов: [[!ProductList? &category=`5` &limit=`12` &sort=`price`]] // Получить параметры (с default значениями) $categoryId = (int)($scriptProperties['category'] ?? 0); $limit = (int)($scriptProperties['limit'] ?? 10); $offset = (int)($scriptProperties['offset'] ?? 0); $sortField = $scriptProperties['sort'] ?? 'menuindex'; $sortDir = $scriptProperties['sortdir'] ?? 'ASC'; $tpl = $scriptProperties['tpl'] ?? 'productCard'; // Запрос к ресурсам через MODX API $c = $modx->newQuery('modResource'); $c->where([ 'parent' => $categoryId, 'published' => 1, 'deleted' => 0, 'class_key' => 'modDocument', ]); // Получить TV значения через Join $c->innerJoin('modTemplateVarResource', 'TVPrice', [ 'TVPrice.tmplvarid' => $modx->getObject('modTemplateVar', ['name' => 'price'])->id, 'TVPrice.contentid = modResource.id', ]); $c->select('modResource.*, TVPrice.value AS price'); $c->sortby($sortField, $sortDir); $c->limit($limit, $offset); $resources = $modx->getCollection('modResource', $c); if (empty($resources)) return ''; $output = ''; foreach ($resources as $resource) { $data = array_merge($resource->toArray(), [ 'price' => $resource->get('price'), 'link' => $modx->makeUrl($resource->id, '', '', 'full'), 'image' => $resource->getTVValue('product_image'), ]); // Чанк для вывода карточки $output .= $modx->getChunk($tpl, $data); } return $output; לפני השם — קריאה לא מאוחסנת (תוכן דינמי). ללא <?php // Сниппет ProductSearch с pdoTools if (!$modx->loadClass('pdoFetch', MODX_CORE_PATH . 'components/pdotools/model/pdotools/', false, true)) { return 'pdoTools не установлен'; } $pdoFetch = new pdoFetch($modx, $scriptProperties); $pdoFetch->addWhere([ 'modResource.parent' => (int)($scriptProperties['category'] ?? 0), 'modResource.published' => 1, ]); // TV join $pdoFetch->addTVs('price,product_image,short_description'); $result = $pdoFetch->run(); return $result; — מאוחסן (בלוק סטטי, זהה לכולם).
טבלת השוואת גישות
| היבט | MODX מקורי | pdoTools | עם קשנון |
|---|---|---|---|
| מספר שאילתות SQL | N+1 (אוסף + TV) | 1 (JOIN יחיד) | פעם אחת, ולאחר מכן מהקשנון |
| קלות כתיבה | דורש JOIN ידני | טעינת TV אוטומטית | 3 שורות נוספות |
| ביצועים על 1000 משאבים | ~2-3 שניות | ~0.3-0.5 שניות | ~0.01 שניות לאחר הבקשה הראשונה |
| תמיכה בסינון | הוספת where ידנית | addWhere מובנה | לא מושפע |
טבלת פרמטרים טיפוסית לסניפט
| פרמטר | סוג | תיאור | ברירת מחדל |
|---|---|---|---|
| category | int | מזהה המשאב האב | 0 |
| limit | int | מספר הרשומות להצגה | 10 |
| sort | string | שדה המיון | menuindex |
| sortdir | string | כיוון המיון (ASC/DESC) | ASC |
| tpl | string | שם הצ'אנק לפלט | productCard |
טעויות נפוצות בפיתוח סניפטים
- שאילתות N+1: קבלת TV דרך getTVValue בלולאה. פתרון: JOIN או pdoFetch.
- חוסר בסינון קלט: פרמטרים ללא המרת סוג (category כמחרוזת במקום int).
- התעלמות מקשנון: כל קריאה לסניפט טוענת את מסד הנתונים, גם אם הנתונים לא השתנו.
- מזהי משאבים מקודדים: השתמשו ב-placeholders ובפרמטרים.
רשימת בדיקה: איך להימנע מטעויות
- [ ] השתמשו בהצהרות מוכנות (Prepared Statements)
- [ ] המירו את כל פרמטרי הקלט לסוג הנדרש
- [ ] הוסיפו קשנון עם מפתח ייחודי
- [ ] השתמשו ב-pdoFetch לשאילתות מורכבות
- [ ] תעדו פרמטרים וצ'אנקים
מה כלול בעבודה
- קוד מקור של הסניפט עם הערות.
- תיעוד: תיאורי פרמטרים, דוגמאות קריאה, תיאור לוגיקה.
- הגדרת קשנון: TTL ובחירת מפתח.
- הוראות התקנה: הוספת הסניפט, יצירת צ'אנקים, בדיקות.
- אחריות תמיכה: נעזור בכל שאלה תוך חודש מהמסירה.
תהליך פיתוח סניפטים במפתח מלא
- ניתוח: לימוד הדרישות, אילו נתונים להציג, מאיפה, תנאי סינון.
- עיצוב: קביעת מבנה השאילתה, בחירת שיטה (מקורי או pdoTools), תכנון קשנון.
- יישום: כתיבת קוד PHP לסניפט ותבניות צ'אנק.
- בדיקות: בדיקת מקרי קצה (קטגוריות ריקות, 0 רשומות), מדידת זמן ביצוע.
- פריסה: העלאה לשרת הייצור, הגדרת קשנון, מתן תיעוד קריאה.
לוח זמנים ועלות
לוח זמנים: מיום עבודה אחד לסניפט פשוט ועד 5 ימים לסניפט מורכב עם אינטגרציית API. אנו מספקים הערכה מדויקת לאחר בחינת הפרויקט. העלות מחושבת באופן אישי.
צרו קשר כדי לדון בפרויקט שלכם. הזמינו פיתוח סניפטים עכשיו — קבלו קוד מהיר ובטוח. ניסיון של למעלה מ-5 שנים עם MODX. פנו אלינו, ונעריך את המשימה ונציע את הפתרון האופטימלי.







