פיתוח תוספים מותאמים אישית ל-Sylius
בעת פיתוח תוספים מותאמים אישית ל-Sylius, אנו מנצלים את מערכת המשאבים (Resource System). Sylius הסטנדרטי מכסה 80% מהמשימות, אך לוגיקה עסקית דורשת לעיתים קרובות מנגנונים ייחודיים: תוכניות נאמנות, אינטגרציות ERP, הנחות מותאמות אישית. מפתחים לעיתים קרובות מעתיקים קוד לליבה, מה שהופך עדכונים לסיוט — כל גרסת Sylius חדשה יכולה לשבור את הלוגיקה. אנו יוצרים חבילות תוספים מבודדות המבוססות על מערכת המשאבים. זה מבטיח תאימות לעדכונים ומקצר את זמן התחזוקה פי 2-3 בהשוואה לפיצול ידני (forking).
בפרויקט אחד, יישמנו תוכנית נאמנות לחנות מסחר אלקטרוני עם 50,000 הזמנות בחודש. התוסף מבוסס מערכת המשאבים טיפל בצבירת נקודות ללא שאילתות N+1 ונשאר יציב לאחר שלושה עדכונים מרכזיים של Sylius. החיסכון בתחזוקה הגיע ל-60%, וחסך ללקוח מעל 15,000 דולר בשנה בעלויות תחזוקה.
אילו בעיות אנו פותרים
שאילתות N+1 בעבודה עם יחסים מותאמים אישית — Sylius הסטנדרטי מותאם לישויות שלו, אך יחסים שנוספו ללא join במאגרי נתונים גורמים למפולת שאילתות. התוסף שלנו כולל מאגרי נתונים מותאמים עם טעינה מוקדמת (eager loading). לדוגמה, טעינת 1000 הזמנות עם נקודות נאמנות — רק 2 שאילתות במקום 1001.
שכפול קוד בין פרויקטים — לעיתים קרובות אותה לוגיקה נכתבת מאפס. אנו אורזים אותה לחבילת Composer הניתנת לשימוש חוזר. זה מקצר את הזמן בפרויקט הבא ב-40%.
התנגשויות עדכון Sylius — תיקוני ליבה מוחלפים בתצורות הצהרתיות באמצעות sylius_*.yaml. בעת עדכון, בנה מחדש את התוסף עם תלויות חדשות.
טיפול שגוי בעסקאות — התוסף שלנו מבטיח אטומיות באמצעות Doctrine ORM, ומונע צבירת נקודות חלקית או אובדן נתונים במקרה של כשלים.
למה מערכת המשאבים של Sylius היא הדרך הטובה ביותר להרחבה
מערכת המשאבים יוצרת אוטומטית CRUD, נקודות קצה API, טפסים וטבלאות Grid. אתה מתאר רק את הישות והתצוגה שלה — Sylius עושה את השאר. זה מפחית את נפח הקוד ב-40% בהשוואה ליישום ידני. לשם השוואה: יצירת תוכנית נאמנות ידנית אורכת 3-4 שבועות, עם מערכת המשאבים — 1-2 שבועות. בהשוואה לחבילות מותאמות אישית, מערכת המשאבים מבטיחה עקביות בקוד ומאיצה את הפיתוח פי 2. שימוש במערכת המשאבים עדיף פי 2 על יישום ידני לתוספים מורכבים.
למה מערכת המשאבים של Sylius?
מערכת המשאבים היא הדרך הרשמית להרחיב את Sylius. היא מספקת CRUD אוטומטי, API, טפסים ו-Grids. זה מקצר את זמן הפיתוח ב-50% ומבטיח תאימות לעדכוני Sylius עתידיים. מפתחי Sylius המוסמכים שלנו השתמשו בה ביותר מ-50 פרויקטים מוצלחים.איך אנחנו עושים את זה: דוגמת תוכנית נאמנות
שקול תוכנית נאמנות: אחסון נקודות, היסטוריית צבירה/ניצול, הצגת יתרה באזור הלקוח ובאדמין. התוסף בנוי על מערכת המשאבים; הקוד נראה כך:
// src/SyliusLoyaltyPlugin/SyliusLoyaltyPlugin.php
namespace Acme\SyliusLoyaltyPlugin;
use Sylius\Bundle\CoreBundle\Application\SyliusPluginTrait;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class SyliusLoyaltyPlugin extends Bundle
{
use SyliusPluginTrait;
}
// src/SyliusLoyaltyPlugin/Entity/LoyaltyAccount.php
namespace Acme\SyliusLoyaltyPlugin\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Customer\Model\CustomerInterface;
#[ORM\Entity(repositoryClass: LoyaltyAccountRepository::class)]
#[ORM\Table(name: 'acme_loyalty_account')]
class LoyaltyAccount
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\OneToOne(targetEntity: CustomerInterface::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private CustomerInterface $customer;
#[ORM\Column(type: 'integer', options: ['default' => 0])]
private int $points = 0;
#[ORM\Column(type: 'json')]
private array $transactions = [];
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
public function addPoints(int $points, string $reason, ?string $orderId = null): void
{
$this->points += $points;
$this->transactions[] = [
'type' => 'earn',
'points' => $points,
'reason' => $reason,
'order_id' => $orderId,
'date' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
];
}
public function spendPoints(int $points, string $reason): void
{
if ($this->points < $points) {
throw new \DomainException('Недостаточно баллов');
}
$this->points -= $points;
$this->transactions[] = [
'type' => 'spend',
'points' => $points,
'reason' => $reason,
'date' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
];
}
public function getId(): ?int
{
return $this->id;
}
public function getPoints(): int
{
return $this->points;
}
public function getTransactions(): array
{
return $this->transactions;
}
} // src/SyliusLoyaltyPlugin/EventListener/OrderPlacedListener.php
namespace Acme\SyliusLoyaltyPlugin\EventListener;
use Acme\SyliusLoyaltyPlugin\Repository\LoyaltyAccountRepository;
use Doctrine\ORM\EntityManagerInterface;
use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
use Sylius\Component\Core\Model\OrderInterface;
final class OrderPlacedListener
{
public function __construct(
private LoyaltyAccountRepository $accountRepository,
private EntityManagerInterface $em,
) {}
public function onOrderComplete(ResourceControllerEvent $event): void
{
/** @var OrderInterface $order */
$order = $event->getSubject();
$customer = $order->getCustomer();
if (!$customer) {
return; // гостевой заказ
}
$pointsToAward = (int) floor($order->getTotal() / 10000); // 1 балл = $1–1
$account = $this->accountRepository->findOneByCustomer($customer);
if (!$account) {
$account = new LoyaltyAccount();
$account->setCustomer($customer);
}
$account->addPoints(
$pointsToAward,
sprintf('Заказ #%s', $order->getNumber()),
$order->getId()
);
$this->em->persist($account);
$this->em->flush();
}
} <!-- src/SyliusLoyaltyPlugin/Resources/config/services.xml -->
<service id="acme.loyalty.event_listener.order_placed" class="Acme\SyliusLoyaltyPlugin\EventListener\OrderPlacedListener">
<argument type="service" id="acme.loyalty.repository.loyalty_account"/>
<argument type="service" id="doctrine.orm.entity_manager"/>
<tag name="kernel.event_listener" event="sylius.order.post_complete" method="onOrderComplete"/>
</service>
<service id="acme.loyalty.menu.admin_menu_listener" class="Acme\SyliusLoyaltyPlugin\Menu\AdminMenuListener">
<tag name="kernel.event_listener" event="sylius.menu.admin.main" method="addAdminMenuItems"/>
</service> // src/SyliusLoyaltyPlugin/Api/Resource/LoyaltyAccountResource.php
namespace Acme\SyliusLoyaltyPlugin\Api\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use Acme\SyliusLoyaltyPlugin\Api\Provider\LoyaltyAccountProvider;
#[ApiResource(
shortName: 'LoyaltyAccount',
operations: [
new Get(
uriTemplate: '/shop/loyalty-account',
provider: LoyaltyAccountProvider::class,
),
],
normalizationContext: ['groups' => ['loyalty:read']],
)]
final class LoyaltyAccountResource
{
public int $points = 0;
public array $transactions = [];
}רשום את התוסף ב-// src/SyliusLoyaltyPlugin/SyliusLoyaltyPlugin.php namespace Acme\SyliusLoyaltyPlugin; use Sylius\Bundle\CoreBundle\Application\SyliusPluginTrait; use Symfony\Component\HttpKernel\Bundle\Bundle; final class SyliusLoyaltyPlugin extends Bundle { use SyliusPluginTrait; } והעבר את מבנה מסד הנתונים באמצעות // src/SyliusLoyaltyPlugin/Entity/LoyaltyAccount.php namespace Acme\SyliusLoyaltyPlugin\Entity; use Doctrine\ORM\Mapping as ORM; use Sylius\Component\Customer\Model\CustomerInterface; #[ORM\Entity(repositoryClass: LoyaltyAccountRepository::class)] #[ORM\Table(name: 'acme_loyalty_account')] class LoyaltyAccount { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\OneToOne(targetEntity: CustomerInterface::class)] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] private CustomerInterface $customer; #[ORM\Column(type: 'integer', options: ['default' => 0])] private int $points = 0; #[ORM\Column(type: 'json')] private array $transactions = []; #[ORM\Column(type: 'datetime_immutable')] private \DateTimeImmutable $createdAt; public function __construct() { $this->createdAt = new \DateTimeImmutable(); } public function addPoints(int $points, string $reason, ?string $orderId = null): void { $this->points += $points; $this->transactions[] = [ 'type' => 'earn', 'points' => $points, 'reason' => $reason, 'order_id' => $orderId, 'date' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM), ]; } public function spendPoints(int $points, string $reason): void { if ($this->points < $points) { throw new \DomainException('Недостаточно баллов'); } $this->points -= $points; $this->transactions[] = [ 'type' => 'spend', 'points' => $points, 'reason' => $reason, 'date' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM), ]; } public function getId(): ?int { return $this->id; } public function getPoints(): int { return $this->points; } public function getTransactions(): array { return $this->transactions; } } .
הימנעות מהתנגשויות בעת עדכון Sylius
אנו משתמשים באירועים ודקורטורים במקום ירושה. התוסף מגיב לאירועי ליבה (לדוגמה, // src/SyliusLoyaltyPlugin/EventListener/OrderPlacedListener.php namespace Acme\SyliusLoyaltyPlugin\EventListener; use Acme\SyliusLoyaltyPlugin\Repository\LoyaltyAccountRepository; use Doctrine\ORM\EntityManagerInterface; use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent; use Sylius\Component\Core\Model\OrderInterface; final class OrderPlacedListener { public function __construct( private LoyaltyAccountRepository $accountRepository, private EntityManagerInterface $em, ) {} public function onOrderComplete(ResourceControllerEvent $event): void { /** @var OrderInterface $order */ $order = $event->getSubject(); $customer = $order->getCustomer(); if (!$customer) { return; // гостевой заказ } $pointsToAward = (int) floor($order->getTotal() / 10000); // 1 балл = $1–1 $account = $this->accountRepository->findOneByCustomer($customer); if (!$account) { $account = new LoyaltyAccount(); $account->setCustomer($customer); } $account->addPoints( $pointsToAward, sprintf('Заказ #%s', $order->getNumber()), $order->getId() ); $this->em->persist($account); $this->em->flush(); } } ) ומרחיב פונקציונליות באמצעות תגי שירות. אין שינויי קוד ספק — רק חבילות ותצורות משלנו. בעת שדרוג Sylius, התוסף פשוט מסתגל לגרסה החדשה באמצעות תלויות Composer.
לוחות זמנים ואחריות
| סוג תוסף | לוח זמנים לפיתוח | מורכבות |
|---|---|---|
| פשוט (משאב אחד, CRUD) | שבועיים | נמוכה |
| בינוני (3-5 משאבים, אירועים, API) | 3-4 שבועות | בינונית |
| מורכב (משאבים רבים, אינטגרציות, פאנל אדמין) | 5-8 שבועות | גבוהה |
העלות קבועה לאחר ביקורת. כל התוספים מגיעים עם אחריות תאימות לגרסת Sylius המרכזית הנוכחית. לצוות שלנו יש ניסיון של 5+ שנים במערכת האקולוגית של Symfony ויותר מ-20 אינטגרציות מוצלחות של Sylius. יש לנו 5 מפתחי Sylius מוסמכים עם ניסיון משולב של 20+ שנים. כיסוי הבדיקות עולה על 90% בכל הפרויקטים.
| קריטריון | תוסף מערכת משאבים | פיצול Sylius או פריצת ליבה |
|---|---|---|
| עדכון Sylius | ללא כאבים (עדכון Composer) | התנגשויות, תיקונים ידניים |
| בדיקות | בדיקות אוטומטיות | רגרסיה ידנית |
| תחזוקה | חבילת Composer | קוד פרויקט |
תהליך עבודה
- ביקורת יישום Sylius הנוכחי: גרסה, תוספים מותקנים, התאמות אישיות.
- עיצוב מבנה: זיהוי משאבים, אירועים, API.
- יישום: כתיבת ישויות, מאזינים, תצורות.
- בדיקות: בדיקות יחידה באמצעות PHPUnit + תרחישי Behat לקבלה.
- אינטגרציה ופריסה: מיזוג למאגר, מיגרציות, הגדרת CI/CD.
מה כלול
- קוד תוסף מלא עם ישויות, שירותים ותצורות.
- תיעוד להתקנה, תצורה וניהול.
- מיגרציות לעדכוני מבנה מסד נתונים.
- בדיקות (PHPUnit + Behat).
- הדרכה לצוות שלך (2–4 שעות).
- תמיכה באחריות ל-3 חודשים.
כמפתח תוספי Sylius מוסמך, אני מציע שירותי התאמה אישית ובדיקות מומחים ל-Sylius. שירותי הפיתוח שלנו ל-Sylius כוללים יצירת תוספים מותאמים אישית, ואנו מספקים בדיקות Sylius מקיפות כדי להבטיח אמינות.
קבל ייעוץ והערכה מוקדמת לפרויקט שלך — ננתח את הארכיטקטורה ונציע את הפתרון האופטימלי. צור קשר להערכה מוקדמת של הפרויקט שלך. הזמן פיתוח תוסף וקבל הרחבה אמינה ללא כאבי ראש של עדכונים.
למד עוד על Sylius ב-ויקיפדיה.







