Разработка интернет-магазина на Sylius
Sylius — PHP e-commerce фреймворк на базе Symfony. Это не CMS с прикрученной корзиной: архитектура спроектирована для сложных B2C и B2B сценариев. Компонентная структура позволяет использовать отдельные части (Inventory, Pricing, Promotions) в существующих Symfony-приложениях, не устанавливая полный стек.
Позиционирование относительно конкурентов
Sylius выигрывает когда:
- Команда работает на PHP/Symfony
- Нужна гибкость без SaaS-ограничений (commercetools, Shopify)
- Требуется мультиканальность и мультивалютность из коробки
- Важен headless-режим через API Platform (JSON-LD / HAL / JSON:API)
- Нужны кастомные рабочие процессы через Symfony State Machine
Архитектура: Resource Layer
Ключевая особенность Sylius — Resource System: все сущности (Product, Order, Customer) управляются через единый механизм конфигурации ресурсов. Это упрощает переопределение модели и добавление кастомных полей.
# config/packages/sylius_product.yaml
sylius_product:
resources:
product:
classes:
model: App\Entity\Product\Product # ваш entity, extends Sylius\Product
translation:
classes:
model: App\Entity\Product\ProductTranslation
Sylius автоматически обновляет репозитории, фабрики и формы для кастомного класса.
Каналы: мультисайтовость
Канал в Sylius — это точка продажи с отдельным каталогом, ценами, валютой и доменом:
// src/DataFixtures/ChannelFixture.php
$channel = $this->channelFactory->createNew();
$channel->setCode('WEB_RU');
$channel->setName('Интернет-магазин Россия');
$channel->setHostname('myshop.ru');
$channel->setDefaultLocale($this->localeRepository->findOneBy(['code' => 'ru_RU']));
$channel->addLocale($this->localeRepository->findOneBy(['code' => 'en_US']));
$channel->setBaseCurrency($this->currencyRepository->findOneBy(['code' => 'RUB']));
$channel->setTaxCalculationStrategy('order_items_based');
$channel->setContactEmail('[email protected]');
$channel->setSkippingShippingStepAllowed(false);
$channel->setSkippingPaymentStepAllowed(false);
$this->channelRepository->add($channel);
Один экземпляр Sylius обслуживает несколько каналов. Каждый канал видит только назначенные ему товары и имеет собственный прайслист.
Расширение модели Product
// src/Entity/Product/Product.php
namespace App\Entity\Product;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\Product as BaseProduct;
#[ORM\Entity]
#[ORM\Table(name: 'sylius_product')]
class Product extends BaseProduct
{
#[ORM\Column(type: 'string', nullable: true)]
private ?string $sku = null;
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $weight = null;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $isBulkAvailable = false;
#[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)]
private ?string $bulkMinOrderAmount = null;
public function getSku(): ?string { return $this->sku; }
public function setSku(?string $sku): void { $this->sku = $sku; }
// геттеры/сеттеры для остальных полей
}
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
Ценообразование через Price Group
Sylius поддерживает Catalog Promotions (постоянные скидки на группы товаров) и Cart Promotions (промокоды, правила):
// Catalog Promotion: скидка 20% на бренд Nike
$catalogPromotion = $this->factory->createNew();
$catalogPromotion->setCode('NIKE_20_OFF');
$catalogPromotion->setName('Nike -20%');
$catalogPromotion->addChannel($niceChannel);
$scope = $this->catalogPromotionScopeFactory->createNew();
$scope->setType(InForProductScopeVariantChecker::TYPE);
$scope->setConfiguration([
'products' => $nikeProductCodes,
]);
$catalogPromotion->addScope($scope);
$action = $this->catalogPromotionActionFactory->createNew();
$action->setType(PercentageDiscountPriceCalculator::TYPE);
$action->setConfiguration(['amount' => 0.20]);
$catalogPromotion->addAction($action);
Checkout: State Machine
Checkout в Sylius — State Machine с явными переходами. Стандартные состояния: cart → addressed → shipping_selected → payment_selected → completed.
// src/StateMachine/CustomOrderCheckoutListener.php
class CustomOrderCheckoutListener
{
public function preComplete(GenericEvent $event): void
{
/** @var OrderInterface $order */
$order = $event->getSubject();
// Проверка наличия товаров перед завершением
foreach ($order->getItems() as $item) {
$variant = $item->getVariant();
if (!$this->inventoryChecker->isReserved($variant, $item->getQuantity())) {
throw new \RuntimeException(
sprintf('Товар "%s" закончился', $variant->getName())
);
}
}
}
}
# config/services.yaml
App\StateMachine\CustomOrderCheckoutListener:
tags:
- { name: kernel.event_listener, event: sylius.order.pre_complete, method: preComplete }
API Platform: Headless режим
Sylius 2.0 интегрирован с API Platform. Каждый ресурс доступен через REST и GraphQL:
# GET /api/v2/shop/products
# GET /api/v2/shop/products/my-product-slug
# POST /api/v2/shop/orders
# PATCH /api/v2/shop/orders/TOKEN/items
Аутентификация через JWT:
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%kernel.project_dir%/config/jwt/private.pem'
public_key: '%kernel.project_dir%/config/jwt/public.pem'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600
// Frontend: получить JWT и использовать
const auth = await fetch('/api/v2/shop/customers/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const { token } = await auth.json();
// Последующие запросы
const products = await fetch('/api/v2/shop/products?channel=WEB_RU&locale=ru_RU', {
headers: { Authorization: `Bearer ${token}` },
});
Деплой
# docker-compose.yml (prod)
services:
app:
image: myshop:latest
environment:
APP_ENV: prod
DATABASE_URL: "postgresql://sylius:pass@postgres/sylius"
MAILER_DSN: "smtp://user:[email protected]:587"
depends_on: [postgres, redis]
worker:
image: myshop:latest
command: php bin/console messenger:consume async --limit=100
depends_on: [postgres, redis]
Этапы разработки
| Этап | Срок |
|---|---|
| Установка, Docker, конфигурация | 2–3 дня |
| Настройка каналов, валют, локалей | 1–2 дня |
| Кастомные Entity + миграции | 2–4 дня |
| Импорт каталога | 4–8 дней |
| Бизнес-логика (промоции, доставка, налоги) | 5–10 дней |
| Headless фронтенд (Next.js + API Platform) | 12–20 дней |
| Платёжные интеграции | 3–5 дней |
| Итого | 29–52 дня |







