Разработка кастомного плагина Sylius
При разработке кастомного плагина Sylius мы используем Resource System. Стандартный Sylius покрывает 80% задач, но бизнес-логика часто требует уникальных механик: программа лояльности, интеграция с ERP, кастомные скидки. Разработчики часто копируют код в ядро, что превращает обновление в кошмар — каждый релиз Sylius может сломать логику. Мы создаём изолированные плагины-базлы на основе Resource System. Это гарантирует совместимость с обновлениями и сокращает время на поддержку в 2-3 раза по сравнению с ручным форком.
В одном проекте мы внедряли лояльность для интернет-магазина с 50 000 заказов в месяц. Плагин на Resource System обрабатывал начисления баллов без N+1 запросов и работал стабильно после трёх мажорных обновлений Sylius. Экономия на поддержке составила 60%.
Какие проблемы решаем
N+1 запросы при работе с кастомными связями — стандарт Sylius оптимизирован под свои сущности, но добавленные отношения без join в репозиториях приводят к лавине запросов. Плагин включает оптимизированные репозитории с eager loading. Например, при загрузке 1000 заказов с баллами лояльности — всего 2 запроса вместо 1001.
Дублирование кода между проектами — часто одну и ту же логику пишут заново. Мы упаковываем её в Composer-пакет, который можно переиспользовать. Это сокращает время на следующий проект на 40%.
Конфликты при обновлении Sylius — патчи ядра заменяются декларативными конфигурациями через sylius_*.yaml. При обновлении достаточно пересобрать плагин с новыми зависимостями.
Некорректная работа с транзакциями — плагин гарантирует атомарность операций через Doctrine ORM, что исключает частичное начисление баллов или потерю данных при сбоях.
Почему Sylius Resource System — лучший способ расширения?
Resource System автоматически генерирует CRUD, API-эндпоинты, формы и Grid-таблицы. Вы описываете только сущность и её отображение — остальное Sylius делает за вас. Это сокращает объём кода на 40% по сравнению с ручной реализацией. Для сравнения: создание программы лояльности вручную занимает 3-4 недели, с Resource System — 1-2 недели. По сравнению с кастомными Bundle, Resource System обеспечивает единообразие кода и ускоряет разработку в 2 раза.
Как мы это делаем: пример программы лояльности
Рассмотрим программу лояльности: нужно хранить баллы, историю начислений и списаний, отображать баланс в личном кабинете и админке. Плагин строится на Resource System, а код выглядит так:
// 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 = []; } Регистрация плагина в config/bundles.php и миграция структуры БД через doctrine:migrations:diff && doctrine:migrations:migrate.
Как избежать конфликтов при обновлении Sylius?
Мы используем события и декораторы вместо наследования. Плагин реагирует на события ядра (например, sylius.order.post_complete) и расширяет функциональность через сервисные теги. Никаких изменений в vendor-коде — только собственные Bundle и конфигурации. При обновлении Sylius плагин просто адаптируется под новую версию через зависимости Composer.
Сроки и гарантии
| Тип плагина | Срок разработки | Сложность |
|---|---|---|
| Простой (1 ресурс, CRUD) | 2 недели | Низкая |
| Средний (3-5 ресурсов, события, API) | 3-4 недели | Средняя |
| Комплексный (много ресурсов, интеграции, админ-панель) | 5-8 недель | Высокая |
Стоимость фиксируется после аудита. На все плагины даём гарантию совместимости с текущей мажорной версией Sylius. Опыт команды — 5+ лет в Symfony-экосистеме, более 20 успешных интеграций с Sylius.
| Критерий | Плагин на Resource System | Fork Sylius или хак ядра |
|---|---|---|
| Обновление Sylius | Безболезненно (composer update) | Конфликты, патчи вручную |
| Тестирование | Автоматические тесты | Ручное регрессионное |
| Поддержка | Через композер-пакет | Кодовая база проекта |
Процесс работы
- Аудит текущего Sylius-приложения: версия, установленные плагины, кастомизации.
- Проектирование структуры: определение ресурсов, событий, API.
- Реализация: написание сущностей, листенеров, конфигураций.
- Тестирование: юнит-тесты на PHPUnit + Behat-сценарии для acceptance.
- Интеграция и деплой: merge в репозиторий, миграции, настройка CI/CD.
Что входит в разработку
- Полный код плагина с сущностями, сервисами, конфигурациями.
- Документация по установке, настройке и администрированию.
- Миграции для обновления структуры БД.
- Тесты (PHPUnit + Behat).
- Обучение команды заказчика (2–4 часа).
- Гарантийная поддержка 3 месяца.
Получите консультацию и предварительную оценку вашего проекта — мы проанализируем архитектуру и предложим оптимальное решение. Свяжитесь с нами для предварительной оценки вашего проекта. Закажите разработку плагина — получите надёжное расширение без головной боли с обновлениями.
Подробнее о Sylius можно прочитать в Wikipedia.







