בפרויקט Drupal עלתה המשימה להציג רשימה מותאמת אישית של מאמרים עם פגינציה וקאשינג. ה-Views הסטנדרטי לא מציע מספיק גמישות, ופריצות תבנית הופכות את הקוד לספגטי. הפתרון הוא פיתוח מודול Drupal 10 מותאם אישית מאפס, עם ארכיטקטורה מחושבת היטב. פיתוח מודול Drupal 10 מותאם אישית דורש ידע ב-Drupal hooks, services ו-plugins—זה מאפשר הרחבה גמישה של הפונקציונליות: מבלוקים ייחודיים ועד אינטגרציה עם REST APIs חיצוניים. נתקלנו במשימות כאלה עשרות פעמים ואנחנו יודעים לבנות מודול מרעיון ועד production—בלי כאבים ובלי ויכוחים מיותרים. זה חוסך בתקציב בשלב התחזוקה.
אילו בעיות טכניות אנחנו פותרים
שימוש כאוטי ב-hooks ללא שכבת services הוא הבעיה הנפוצה ביותר בביקורת מודולים של צד שלישי: שאילתות N+1 בלולאות, כפילות קוד, חוסר יכולת לשימוש חוזר בלוגיקה. הבעיה השנייה בשכיחותה היא חוסר בקאשינג או תגי קאש שגויים, שגורמים לדפים להיטען ב-5–7 שניות. הבעיה השלישית היא קוד פרוצדורלי בקבצי .module עם למעלה מאלף שורות, שהופך לבלתי ניתן לתחזוקה.
איך להימנע משאילתות N+1?
במקום לטעון כל ישות אחת-אחת בלולאה, אנחנו משתמשים ב-EntityStorageInterface::loadMultiple(): שאילתה אחת לקבלת מזהים, ושאילתה שנייה לטעינת כל הישויות. זה הופך 20 שאילתות ל-2. במודולים שלנו, אנחנו שולטים בזה בשכבת ה-services.
למה מיכל ה-DI חשוב?
הזרקת תלויות (DI) היא התקן של Drupal. לפי Drupal API, כל service מוגדר ב-services.yml. בזכות Dependency Injection, הקוד הופך לבדיק ובר-החלפה. אם צריך להחליף את הקאשינג מ-database ל-Redis, משנים שורה אחת ב-services.yml, לא את כל הקוד. DI מקצר את זמן השינוי פי 2–3, ומודולים הבנויים על services נבדקים פי 3 מהר יותר.
איך אנחנו מתכננים את ארכיטקטורת המודול
דוגמה למבנה מודול מינימלי
עבור כל פרויקט, אנחנו מגדירים מבנה מינימלי. הנה סט קבצים טיפוסי המכסה 80% מהמשימות:
web/modules/custom/my_module/
├── my_module.info.yml
├── my_module.module
├── my_module.services.yml
├── src/
│ ├── Controller/
│ │ └── ArticleController.php
│ ├── Service/
│ │ └── ArticleService.php
│ ├── Plugin/Block/
│ │ └── RecentPostsBlock.php
│ └── EventSubscriber/
│ └── RequestSubscriber.php
└── templates/
└── my-module-template.html.twig דוגמה: controller עם קאשינג
יישמנו API להצגת מאמרים עם פגינציה וקאשינג מבוסס תגיות—הלקוח ביטל את הצורך באינוולידציה ידנית של קאש.
// src/Controller/ArticleController.php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class ArticleController extends ControllerBase {
public function __construct(
private readonly EntityTypeManagerInterface $entityTypeManager,
) {}
public static function create(ContainerInterface $container): static {
return new static(
$container->get('entity_type.manager'),
);
}
public function list(): array {
$storage = $this->entityTypeManager->getStorage('node');
$ids = $storage->getQuery()
->condition('type', 'article')
->condition('status', 1)
->sort('created', 'DESC')
->range(0, 20)
->accessCheck(TRUE)
->execute();
$nodes = $storage->loadMultiple($ids);
$view_builder = $this->entityTypeManager->getViewBuilder('node');
return [
'#theme' => 'item_list',
'#items' => array_map(
fn($node) => $view_builder->view($node, 'teaser'),
$nodes
),
];
}
public function apiList(Request $request): JsonResponse {
$page = (int) $request->query->get('page', 0);
$limit = min((int) $request->query->get('limit', 10), 100);
$storage = $this->entityTypeManager->getStorage('node');
$query = $storage->getQuery()
->condition('type', 'article')
->condition('status', 1)
->sort('created', 'DESC')
->range($page * $limit, $limit)
->accessCheck(TRUE);
$ids = $query->execute();
$nodes = $storage->loadMultiple($ids);
$data = array_map(function ($node) {
return [
'id' => $node->id(),
'uuid' => $node->uuid(),
'title' => $node->getTitle(),
'created' => $node->getCreatedTime(),
'url' => $node->toUrl()->setAbsolute()->toString(),
'summary' => $node->get('body')->summary,
];
}, $nodes);
return new JsonResponse([
'data' => array_values($data),
'meta' => ['page' => $page, 'limit' => $limit],
]);
}
} השוואה: מודול פשוט מול מורכב
| פרמטר | מודול פשוט | מודול מורכב |
|---|---|---|
| ישויות | אין | ישויות מותאמות אישית + bundle |
| API | אין | נקודות קצה REST / JSON:API |
| קאשינג | בסיסי | תגיות, הקשרים, אינוולידציה דינמית |
| אירועים | אין | מנויי אירועים + מאזינים |
| ציר זמן | 2–3 ימים | 8–15 ימים |
טעויות נפוצות בפיתוח מודולים והפתרונות שלהן
| טעות | השלכות | פתרון |
|---|---|---|
| אין שכבת services | שאילתות N+1, כפילות קוד | הוצאת הלוגיקה ל-services עם DI |
| התעלמות מתגי קאש | אינוולידציה מלאה של קאש, דפים איטיים | הקצאת תגי קאש לכל ישות |
| Hooks פרוצדורליים ב-.module | קשה לתחזק ולבדוק | שימוש ב-EventSubscriber ללוגיקה |
רכיבי מודול נוספים
בלוקים עם קונפיגורציה
בלוק RecentPosts עם הגדרות: המנהל משנה את מספר הפוסטים דרך הממשק בלי לגעת בקוד. אנחנו משתמשים ב-web/modules/custom/my_module/ ├── my_module.info.yml ├── my_module.module ├── my_module.services.yml ├── src/ │ ├── Controller/ │ │ └── ArticleController.php │ ├── Service/ │ │ └── ArticleService.php │ ├── Plugin/Block/ │ │ └── RecentPostsBlock.php │ └── EventSubscriber/ │ └── RequestSubscriber.php └── templates/ └── my-module-template.html.twig כדי להזריק services לתוך ה-plugin.
מנויי אירועים ללוגיקה מותאמת אישית
הערה: כשצריך להגיב לכל בקשה (למשל, לבדוק את ה-header X-Api-Version), מנוי אירועים הוא אלגנטי יותר מאשר בדיקות בכל controller. רישום דרך התג // src/Controller/ArticleController.php namespace Drupal\my_module\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; class ArticleController extends ControllerBase { public function __construct( private readonly EntityTypeManagerInterface $entityTypeManager, ) {} public static function create(ContainerInterface $container): static { return new static( $container->get('entity_type.manager'), ); } public function list(): array { $storage = $this->entityTypeManager->getStorage('node'); $ids = $storage->getQuery() ->condition('type', 'article') ->condition('status', 1) ->sort('created', 'DESC') ->range(0, 20) ->accessCheck(TRUE) ->execute(); $nodes = $storage->loadMultiple($ids); $view_builder = $this->entityTypeManager->getViewBuilder('node'); return [ '#theme' => 'item_list', '#items' => array_map( fn($node) => $view_builder->view($node, 'teaser'), $nodes ), ]; } public function apiList(Request $request): JsonResponse { $page = (int) $request->query->get('page', 0); $limit = min((int) $request->query->get('limit', 10), 100); $storage = $this->entityTypeManager->getStorage('node'); $query = $storage->getQuery() ->condition('type', 'article') ->condition('status', 1) ->sort('created', 'DESC') ->range($page * $limit, $limit) ->accessCheck(TRUE); $ids = $query->execute(); $nodes = $storage->loadMultiple($ids); $data = array_map(function ($node) { return [ 'id' => $node->id(), 'uuid' => $node->uuid(), 'title' => $node->getTitle(), 'created' => $node->getCreatedTime(), 'url' => $node->toUrl()->setAbsolute()->toString(), 'summary' => $node->get('body')->summary, ]; }, $nodes); return new JsonResponse([ 'data' => array_values($data), 'meta' => ['page' => $page, 'limit' => $limit], ]); } } ב-services.yml.
Hooks וסכמות עדכון
אנחנו שומרים hooks פרוצדורליים רק במקומות שאין חלופה. לדוגמה, ContainerFactoryPluginInterface לחישוב אוטומטי של זמן קריאה. להתקנת מודול ומיגרציות, אנחנו משתמשים בקבצי .install עם update hooks.
תהליך העבודה וציר הזמן
- ניתוח דרישות ועיצוב ארכיטקטורה — 1–2 ימים.
- יישום המודול — 2 עד 15 ימים.
- בדיקות (יחידה + פונקציונליות) — 1–2 ימים.
- פריסה ותיעוד — יום אחד.
צירי הזמן הסופיים תלויים במורכבות: מיומיים למודול בסיסי ועד 3 שבועות לפתרון מקיף.
מה כלול בפיתוח turnkey
- עיצוב ארכיטקטורת המודול
- קידוד (services, controllers, plugins, hooks, events)
- הגדרת קאשינג עם תגיות ואינוולידציה
- בדיקות יחידה (PHPUnit) ובדיקות פונקציונליות
- תיעוד התקנה וקונפיגורציה
- העברת קוד מקור וגישה
- אחריות על קוד עד 3 חודשים וחודש תמיכה חינם
היתרונות שלנו
מעל 5 שנות ניסיון בפיתוח Drupal, יותר מ-50 פרויקטים מוצלחים. אנחנו עוקבים אחר תקני הקידוד של Drupal ומשתמשים בפרקטיקות מודרניות—מיכל DI, ארכיטקטורת אירועים, תגי קאש. שימוש במיכל ה-DI מקצר את זמן השינוי פי 2–3 בהשוואה לקוד פרוצדורלי, ומודולים הבנויים על services נבדקים פי 3 מהר יותר. אנחנו מספקים אחריות על קוד עד 3 חודשים—אם מתרחשות שגיאות או נדרשים שינויים במסגרת הפונקציונליות המוסכמת, אנחנו מתקנים בחינם. תוך חודש מהמסירה—תמיכה וייעוץ חינם.
צרו קשר להערכה של הפרויקט שלכם. הזמינו פיתוח מודול turnkey וקבלו פתרון אמין בלי תוספות נסתרות. אם אתם צריכים פיתוח כזה, קבלו ייעוץ על הפרויקט שלכם היום.







