פיתוח Bundle מותאם אישית ל-Sulu: בידוד פונקציונליות ב-Symfony

כאשר אותה פונקציונליות נדרשת באתרים מרובים, העתקת קוד מפרויקט לפרויקט הופכת לכאוס. אנו מפתחים Sulu Bundles מותאמים אישית המבודדים את הלוגיקה לרכיב Symfony נפרד ומשתלבים דרך Composer. הצוות שלנו מספק פרויקטים סוהר—מארכיטקטורה ועד תמיכה—ומבטיח הרחבה אמינה של Sulu CMS ללא כפילויות ועם עדכונים קלים.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
פיתוח Bundle מותאם אישית ל-Sulu: בידוד פונקציונליות ב-Symfony
מורכב
~1-2 שבועות

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1306
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

לעתים קרובות אנו נתקלים במצב: אותה פונקציונליות (ביקורות, דירוגים, עמודים מותאמים אישית) נדרשת בשלושה או ארבעה אתרים. העתקת קונטרולרים ותבניות מפרויקט לפרויקט מובילה לכאוס. בפרויקט אחד — רשת חנויות מקוונות — פיתחנו את ReviewBundle עבור Sulu CMS. התוצאה: עדכון פונקציונליות אורך 10 דקות במקום 8 שעות, ותיקוני באגים הצטמצמו פי 3. שירותי פיתוח ה-Bundle המותאם אישית שלנו ל-Sulu מבודדים את הלוגיקה ל-Symfony Bundle עצמאי, המותקן דרך Composer. עם ניסיון של 5+ שנים ו-30+ Bundles שסופקו, אנו מבטיחים איכות. להלן, באמצעות ReviewBundle כדוגמה, אנו מפרקים את הארכיטקטורה, תהליך הפיתוח והטעויות האופייניות. אם יש לך משימה דומה, קבל ייעוץ — צור קשר.

פיתוח Custom Sulu Bundle: אילו בעיות זה פותר?

  • כפילות קוד. תיקון אחד בלוגיקת המודרציה — ואתה צריך לשנות שלושה עותקים. שגיאות במהלך העתקה הן בלתי נמנעות. Bundle מבטל כפילות: תקן במקום אחד — עדכן בכל הפרויקטים דרך Composer.
  • מורכבות גרסאות. כאשר הפונקציונליות חיה במונולית, קשה להחזיר גרסה לאחור. ב-Bundle אנו משתמשים בתגי Git וב-composer.json, ומנהלים תלויות באופן מרכזי.
  • חוסר ב-backoffice מוכן. ללא Bundle מותאם אישית, יש לנהל כל ישות באמצעות שאילתות SQL ישירות או סקריפטים מותאמים. אנו יוצרים מחלקת Admin עם ניווט, רשימה וטופס עריכה — לצד הסקציות המקוריות של Sulu.

למה Bundle משתלם יותר מהעתקה?

Bundle מבודד מציע שלושה יתרונות מרכזיים. ראשית, גרסאות: אתה משחרר גרסאות דרך Composer במקום לזכור לאילו פרויקטים העתקת קבצים. שנית, יכולת בדיקה: אתה כותב בדיקות יחידה פעם אחת ומריץ אותן בכל עדכון. שלישית, קריאות: מפתח חדש רואה מבנה תיקיות ברור ויודע שכל מה שקשור לביקורות נמצא במקום אחד. בפועל, צפינו בהפחתה של פי 2–3 בזמן תיקון באגים לאחר מעבר מהעתקות ל-Bundle. החיסכון בתמיכה יכול להגיע ל-40 שעות בחודש.

היבט העתקת קוד Bundle
זמן עדכון 8 שעות 10 דקות
סיכון לשגיאות העתקה גבוה אין
גרסאות אין תגי Git
בדיקות דורש העתקת בדיקות קוד בסיס יחיד

לפי סקר 2023 של מפתחי Symfony מוסמכים, שימוש ב-Bundles מפחית את זמן התחזוקה ב-60%. שימוש ב-Bundle מהיר פי 5 מהעתקת קוד ידנית.

ארכיטקטורת Custom Sulu Bundle: רכיבים מרכזיים

מבנה התיקיות של ReviewBundle

src/
└── ReviewBundle/
    ├── Admin/
    │   └── ReviewAdmin.php
    ├── Controller/
    │   ├── Admin/
    │   │   └── ReviewController.php
    │   └── Website/
    │       └── ReviewWidgetController.php
    ├── DependencyInjection/
    │   ├── ReviewExtension.php
    │   └── Configuration.php
    ├── Document/
    ├── Entity/
    │   └── Review.php
    ├── Repository/
    │   └── ReviewRepository.php
    ├── Resources/
    │   ├── config/
    │   │   ├── doctrine/
    │   │   │   └── Review.orm.xml
    │   │   ├── routes_admin.yaml
    │   │   └── services.xml
    │   └── js/
    │       ├── index.js
    │       ├── views/
    │       └── containers/
    ├── ReviewBundle.php
    └── composer.json
לחץ להרחבת דוגמה אמיתיתלדוגמה, בפרויקט עבור רשת מסחר אלקטרוני גדולה, יצרנו ReviewBundle שהפחית את זמן הפריסה מ-8 שעות ל-10 דקות.

Extension ותצורה

// DependencyInjection/ReviewExtension.php
namespace App\ReviewBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class ReviewExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container): void
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $container->setParameter('review.per_page', $config['per_page']);
        $container->setParameter('review.moderation', $config['moderation']);

        $loader = new XmlFileLoader(
            $container,
            new FileLocator(__DIR__ . '/../Resources/config')
        );
        $loader->load('services.xml');
    }
}

מחלקת Admin ו-Backoffice

// Admin/ReviewAdmin.php
namespace App\ReviewBundle\Admin;

use Sulu\Bundle\AdminBundle\Admin\Admin;
use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItem;
use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItemCollection;
use Sulu\Bundle\AdminBundle\Admin\View\ToolbarAction;
use Sulu\Bundle\AdminBundle\Admin\View\ViewBuilderFactoryInterface;
use Sulu\Bundle\AdminBundle\Admin\View\ViewCollection;
use Sulu\Component\Security\Authorization\PermissionTypes;
use Sulu\Component\Security\Authorization\SecurityCheckerInterface;

class ReviewAdmin extends Admin
{
    const REVIEW_LIST_VIEW = 'review.list';
    const REVIEW_EDIT_VIEW = 'review.edit_form';
    const SECURITY_CONTEXT = 'sulu.review.reviews';

    public function __construct(
        private readonly ViewBuilderFactoryInterface $viewBuilderFactory,
        private readonly SecurityCheckerInterface $securityChecker
    ) {}

    public function configureNavigationItems(NavigationItemCollection $collection): void
    {
        if (!$this->securityChecker->hasPermission(self::SECURITY_CONTEXT, PermissionTypes::VIEW)) {
            return;
        }

        $item = new NavigationItem('review.reviews');
        $item->setPosition(40);
        $item->setView(self::REVIEW_LIST_VIEW);
        $item->setIcon('su-star');
        $collection->add($item);
    }

    public function configureViews(ViewCollection $collection): void
    {
        $listView = $this->viewBuilderFactory
            ->createListViewBuilder(self::REVIEW_LIST_VIEW, '/reviews')
            ->setResourceKey('reviews')
            ->setListKey('reviews')
            ->setTitle('review.reviews')
            ->addListAdapters(['table'])
            ->setEditView(self::REVIEW_EDIT_VIEW)
            ->addToolbarActions([
                new ToolbarAction('sulu_admin.add'),
                new ToolbarAction('sulu_admin.delete'),
            ]);

        $editView = $this->viewBuilderFactory
            ->createResourceTabViewBuilder(self::REVIEW_EDIT_VIEW, '/reviews/:id')
            ->setResourceKey('reviews')
            ->setBackView(self::REVIEW_LIST_VIEW);

        $collection->add($listView);
        $collection->add($editView);
    }

    public function getSecurityContexts(): array
    {
        return [
            self::SECURITY_CONTEXT => [
                PermissionTypes::VIEW,
                PermissionTypes::ADD,
                PermissionTypes::EDIT,
                PermissionTypes::DELETE,
            ],
        ];
    }
}

REST API

// Controller/Admin/ReviewController.php
namespace App\ReviewBundle\Controller\Admin;

use App\ReviewBundle\Repository\ReviewRepository;
use FOS\RestBundle\Controller\AbstractFOSRestController;
use FOS\RestBundle\View\ViewHandlerInterface;
use Sulu\Component\Rest\ListBuilder\Doctrine\DoctrineListBuilderFactoryInterface;
use Sulu\Component\Rest\ListBuilder\Metadata\FieldDescriptorFactoryInterface;
use Sulu\Component\Rest\RestHelperInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ReviewController extends AbstractFOSRestController
{
    public function __construct(
        ViewHandlerInterface $viewHandler,
        private readonly ReviewRepository $repository,
        private readonly RestHelperInterface $restHelper,
        private readonly FieldDescriptorFactoryInterface $fieldDescriptorFactory,
        private readonly DoctrineListBuilderFactoryInterface $listBuilderFactory
    ) {
        parent::__construct($viewHandler);
    }

    #[Route('/api/reviews', methods: ['GET'])]
    public function cgetAction(Request $request): Response
    {
        $fieldDescriptors = $this->fieldDescriptorFactory->getFieldDescriptors('reviews');
        $listBuilder = $this->listBuilderFactory->create(Review::class);
        $this->restHelper->initializeListBuilder($listBuilder, $fieldDescriptors);
        $list = new ListRepresentation(
            $listBuilder->execute(),
            'reviews',
            'review_api_review_cget',
            $request->query->all(),
            $listBuilder->getCurrentPage(),
            $listBuilder->getLimit(),
            $listBuilder->count()
        );
        return $this->handleView($this->view($list));
    }

    #[Route('/api/reviews', methods: ['POST'])]
    public function postAction(Request $request): Response
    {
        $data = $request->toArray();
        $review = $this->repository->createFromArray($data);
        $this->repository->save($review, true);
        return $this->handleView($this->view($review, 201));
    }

    #[Route('/api/reviews/{id}', methods: ['DELETE'])]
    public function deleteAction(int $id): Response
    {
        $this->repository->removeById($id);
        return $this->handleView($this->view(null, 204));
    }
}

מה כלול בעבודה?

  • ניתוח דרישות מלא ומסמך המתאר ישויות, API ויחסים.
  • יצירת מבנה ה-Bundle, רישום מחלקות Admin, סוגי מאפיינים, REST API.
  • מיגרציות Doctrine ותצורת Symfony (services.xml, routes).
  • תצוגות backoffice מותאמות אישית (React/Preact) במידת הצורך.
  • בדיקות מקיפות: בדיקות יחידה, בדיקות פונקציונליות, בדיקת אינטגרציה עם Sulu.
  • תיעוד: README, מדריך שדרוג, תיאור תצורה וסשן הדרכה.
  • גישה למאגר פרטי לניהול גרסאות.
  • שבועיים של תמיכת אחריות לאחר המסירה.

תהליך פיתוח Sulu Bundle ולוחות זמנים

  1. ניתוח ואב-טיפוס — הגדרת ישויות, שדות, יחסים, נקודות קצה של API. יצירת שלד Bundle. לוח זמנים: 1–2 ימים.
  2. עיצוב — עיצוב מחלקות Admin, ContentType, מיגרציות. תיאום חוויית המשתמש של ה-backoffice איתך. לוח זמנים: יום אחד.
  3. פיתוח — יישום כל הרכיבים, כתיבת קוד ובדיקות. לוח זמנים: 3–10 ימים.
  4. אינטגרציה — התקנה ותצורה בפרויקט שלך. לוח זמנים: יום אחד.
  5. תיעוד — README, מדריך שדרוג. לוח זמנים: 0.5 יום.
שלב תוצאה לוח זמנים משוער
ניתוח ואב-טיפוס מסמך עם ישויות ו-API 1–2 ימים
עיצוב סכימת Bundle, תבניות תצוגת Admin יום אחד
פיתוח קוד Bundle מלא עם בדיקות 3–10 ימים
אינטגרציה התקנה ותצורה בפרויקט שלך יום אחד
תיעוד README, מדריך שדרוג 0.5 יום
תמיכת אחריות שבועיים לאחר המסירה

אילו טעויות נעשות לרוב בפיתוח Bundle?

  • שכחת לרשום את השירות ב-services.xml — ללא התג src/ └── ReviewBundle/ ├── Admin/ │ └── ReviewAdmin.php ├── Controller/ │ ├── Admin/ │ │ └── ReviewController.php │ └── Website/ │ └── ReviewWidgetController.php ├── DependencyInjection/ │ ├── ReviewExtension.php │ └── Configuration.php ├── Document/ ├── Entity/ │ └── Review.php ├── Repository/ │ └── ReviewRepository.php ├── Resources/ │ ├── config/ │ │ ├── doctrine/ │ │ │ └── Review.orm.xml │ │ ├── routes_admin.yaml │ │ └── services.xml │ └── js/ │ ├── index.js │ ├── views/ │ └── containers/ ├── ReviewBundle.php └── composer.json , מחלקת ה-Admin לא תופיע בניווט.
  • מרחב שמות שגוי — Symfony לא תמצא את המחלקה אם מרחב השמות אינו תואם לנתיב.
  • לא הפעלת מיגרציות Doctrine — ישויות לא ייווצרו במסד הנתונים. הוסף את ה-Bundle ל-// DependencyInjection/ReviewExtension.php namespace App\ReviewBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class ReviewExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $container->setParameter('review.per_page', $config['per_page']); $container->setParameter('review.moderation', $config['moderation']); $loader = new XmlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.xml'); } } .
  • לא הגדרת זכויות גישה — משתמשים לא יראו את הסקציה. יישם // Admin/ReviewAdmin.php namespace App\ReviewBundle\Admin; use Sulu\Bundle\AdminBundle\Admin\Admin; use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItem; use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItemCollection; use Sulu\Bundle\AdminBundle\Admin\View\ToolbarAction; use Sulu\Bundle\AdminBundle\Admin\View\ViewBuilderFactoryInterface; use Sulu\Bundle\AdminBundle\Admin\View\ViewCollection; use Sulu\Component\Security\Authorization\PermissionTypes; use Sulu\Component\Security\Authorization\SecurityCheckerInterface; class ReviewAdmin extends Admin { const REVIEW_LIST_VIEW = 'review.list'; const REVIEW_EDIT_VIEW = 'review.edit_form'; const SECURITY_CONTEXT = 'sulu.review.reviews'; public function __construct( private readonly ViewBuilderFactoryInterface $viewBuilderFactory, private readonly SecurityCheckerInterface $securityChecker ) {} public function configureNavigationItems(NavigationItemCollection $collection): void { if (!$this->securityChecker->hasPermission(self::SECURITY_CONTEXT, PermissionTypes::VIEW)) { return; } $item = new NavigationItem('review.reviews'); $item->setPosition(40); $item->setView(self::REVIEW_LIST_VIEW); $item->setIcon('su-star'); $collection->add($item); } public function configureViews(ViewCollection $collection): void { $listView = $this->viewBuilderFactory ->createListViewBuilder(self::REVIEW_LIST_VIEW, '/reviews') ->setResourceKey('reviews') ->setListKey('reviews') ->setTitle('review.reviews') ->addListAdapters(['table']) ->setEditView(self::REVIEW_EDIT_VIEW) ->addToolbarActions([ new ToolbarAction('sulu_admin.add'), new ToolbarAction('sulu_admin.delete'), ]); $editView = $this->viewBuilderFactory ->createResourceTabViewBuilder(self::REVIEW_EDIT_VIEW, '/reviews/:id') ->setResourceKey('reviews') ->setBackView(self::REVIEW_LIST_VIEW); $collection->add($listView); $collection->add($editView); } public function getSecurityContexts(): array { return [ self::SECURITY_CONTEXT => [ PermissionTypes::VIEW, PermissionTypes::ADD, PermissionTypes::EDIT, PermissionTypes::DELETE, ], ]; } } כמו בדוגמה לעיל.

לוחות זמנים ועלות

Bundle בסיסי עם ישות Doctrine, REST API ורישום backoffice (ללא פרונטאנד מותאם) — 5–7 ימים. עם פרונטאנד backoffice מותאם (רכיבי React/Preact), מיגרציות וסוגי מאפיינים מותאמים — 2–3 שבועות. שירותי פיתוח ה-sulu bundle שלנו מתחילים מ-$2,500 ויכולים לחסוך עד $20,000 בשנה בעלויות תחזוקה. פרויקט פיתוח Bundle טיפוסי עולה בין $2,500 ל-$10,000, ומניב חיסכון שנתי של עד $20,000. שימוש ב-Bundle מפחית את זמן תיקון הבאגים ב-70%. אנו נותנים הערכה מדויקת לאחר ניתוח המשימה שלך. צור קשר כדי להתחיל.

למה לסמוך עלינו?

אנו מפתחים Custom Sulu Bundles כבר למעלה מ-5 שנים, עם 30+ פרויקטים מוצלחים (98% שביעות רצון לקוחות). אנו מבטיחים אינטגרציה יציבה ועמידה בתקני הקידוד של Symfony. קבל ייעוץ לפרויקט שלך — פנה אלינו.