Почему корзина пропадает при входе в аккаунт — и как это починить в Sylius

Веб разработка
27 марта 2026
Почему корзина пропадает при входе в аккаунт — и как это починить в Sylius

Ситуация знакомая многим, кто работает с Sylius: покупатель набирает товары в корзину, переходит к оформлению, логинится — и корзина пустая. Товары исчезли. Покупатель уходит, поддержка получает тикет, бизнес теряет деньги.

Мы в NJ Soft сталкивались с этой проблемой на нескольких проектах и каждый раз разбирались заново, потому что причины бывают разные. В этой статье собрали всё в одном месте: как Sylius работает с корзиной, почему она сбрасывается и что с этим делать.

Как Sylius хранит корзину

Для начала разберёмся, как устроена корзина «под капотом».

Когда неавторизованный пользователь добавляет товар в корзину, Sylius создаёт объект Order со статусом state=cart и привязывает его к сессии через SessionCartContext. Это и есть гостевая корзина — она живёт до тех пор, пока жива сессия.

После логина корзина должна «перепривязаться» к объекту Customer. За это отвечает цепочка компонентов:

  • SessionCartContext — достаёт корзину из сессии (для гостей).
  • CustomerCartContext — достаёт корзину из базы данных (для авторизованных пользователей).
  • CompositeCartContext — обёртка, которая перебирает все контексты по приоритету и возвращает первую найденную корзину.
  • CartBlenderInterface — сервис, который сливает гостевую корзину с корзиной покупателя.

Как должно работать слияние

Штатный сценарий выглядит так:

  1. Пользователь логинится.
  2. Symfony диспатчит событие sylius.customer.logged_in.
  3. Слушатель вызывает CartBlender::blend().
  4. CartBlender берёт гостевую корзину из сессии и корзину покупателя из БД.
  5. Товары из гостевой корзины добавляются к корзине покупателя (стратегия по умолчанию).
  6. Сессионный cart_id обнуляется, покупатель получает итоговую корзину.

Звучит просто. На практике — ломается в четырёх местах.

Типичные причины сброса корзины

1. Инвалидация сессии при логине

Это самая частая причина. Symfony по умолчанию регенерирует Session ID при аутентификации — это настройка session.storage.migrate_on_login, и она включена из соображений безопасности (защита от session fixation).

Проблема в том, что если CartContext не успевает восстановить cart_id из нового сессионного контекста, корзина «теряется». Технически заказ всё ещё в базе, но связь с сессией разорвана, и Sylius создаёт новую пустую корзину.

Решение: явно сохранять cart_id в сессии до регенерации. Можно подписаться на событие security.interactive_login и сохранить идентификатор корзины заранее:

// src/EventListener/CartSessionListener.php

use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class CartSessionListener
{
    public function __construct(
        private RequestStack $requestStack,
    ) {}

    public function onInteractiveLogin(InteractiveLoginEvent $event): void
    {
        $session = $this->requestStack->getSession();
        $cartId = $session->get('_sylius.cart.FASHION_WEB'); // ключ зависит от канала

        if ($cartId) {
            // Сохраняем в атрибутах запроса, чтобы восстановить после регенерации
            $event->getRequest()->attributes->set('_cart_id_backup', $cartId);
        }
    }
}

2. Отключённый или неправильно настроенный CartBlender

Если в проекте используется кастомный аутентификатор Symfony (а в большинстве реальных проектов это так), событие sylius.customer.logged_in может просто не диспатчиться. Sylius ожидает, что аутентификация пройдёт через его стандартный flow, но кастомный security.yaml легко может перехватить процесс раньше.

Решение: явно диспатчить событие в onAuthenticationSuccess() вашего аутентификатора. Вот как это выглядит:

// src/Security/ShopUserAuthenticator.php

use Sylius\Bundle\CustomerBundle\Event\CustomerEvents as SyliusCustomerEvents;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;

class ShopUserAuthenticator extends AbstractLoginFormAuthenticator
{
    public function __construct(
        // ... остальные зависимости
        private EventDispatcherInterface $eventDispatcher,
    ) {}

    public function onAuthenticationSuccess(
        Request $request,
        TokenInterface $token,
        string $firewallName
    ): Response {
        // Обязательно: диспатчим событие для CartBlender
        $this->eventDispatcher->dispatch(
            new GenericEvent($token->getUser()->getCustomer()),
            SyliusCustomerEvents::POST_LOGGED_IN
        );

        return parent::onAuthenticationSuccess($request, $token, $firewallName);
    }
}

Без этого вызова CartBlender просто не узнает, что пользователь залогинился, и слияние не произойдёт.

3. Несколько CartContext-ов с неверным приоритетом

CompositeCartContext перебирает зарегистрированные контексты по приоритету. Если CustomerCartContext срабатывает раньше SessionCartContext, то после логина Sylius сразу вернёт корзину покупателя из БД (скорее всего пустую) и никогда не доберётся до гостевой корзины в сессии.

Решение: проверить приоритеты тегов sylius.context.cart в конфигурации сервисов:

php bin/console debug:container --tag=sylius.context.cart

Убедитесь, что SessionCartContext имеет более высокий приоритет (меньшее числовое значение), чем CustomerCartContext. Если в проекте есть кастомные CartContext-ы — проверьте и их тоже.

4. Race condition при AJAX-запросах

Этот случай коварнее остальных. Фронтенд отправляет AJAX-запрос на получение корзины параллельно с запросом логина. Запрос корзины приходит на сервер до того, как авторизация завершится, — и возвращает пустую корзину нового пользователя. Фронтенд получает пустой ответ и рисует пустую корзину.

Решение: блокировать запросы корзины на фронтенде до завершения авторизации. Если используете JavaScript-фреймворк — добавьте флаг isAuthenticating и не отправляйте запросы к API корзины, пока он активен. Если используете стандартные шаблоны Sylius — убедитесь, что после логина происходит полный редирект, а не частичное обновление страницы.

Диагностика

Если корзина пропадает и вы не уверены в причине, вот чек-лист для быстрой диагностики.

Проверить, какой CartContext возвращает корзину:

$cart = $container->get('sylius.context.cart')->getCart();
dump($cart->getId(), $cart->getCustomer());

Посмотреть, диспатчится ли событие:

Откройте Symfony Profiler → вкладка Events → найдите sylius.customer.logged_in. Если его нет — проблема в аутентификаторе (причина №2).

Проверить слушателей на событие:

$listeners = $dispatcher->getListeners('sylius.customer.logged_in');
dump($listeners);

Проверить «осиротевшие» корзины в БД:

SELECT id, customer_id, state, token_value
FROM sylius_order
WHERE state = 'cart'
  AND customer_id IS NULL
ORDER BY created_at DESC
LIMIT 10;

Если после логина в таблице остаются записи с customer_id = NULL и state = cart — значит слияние не отработало.

Особый случай: авторизация на шаге оформления заказа

Отдельная головная боль — когда пользователь логинится уже в процессе чекаута, например на шаге ввода адреса или выбора оплаты.

Checkout в Sylius управляется собственным стейт-машиной, и если пользователь авторизуется на шаге address или payment, order_id в URL может указывать на гостевой заказ. После слияния корзин нужно редиректить пользователя на корзину авторизованного покупателя, а не продолжать гостевой чекаут с невалидным order_id.

Решение: переопределить CheckoutResolver или добавить middleware-редирект, который после логина на чекауте перебросит пользователя на начало оформления с правильной корзиной:

// src/EventListener/PostLoginCheckoutRedirectListener.php

class PostLoginCheckoutRedirectListener
{
    public function onPostLogin(GenericEvent $event): void
    {
        $request = $this->requestStack->getCurrentRequest();

        // Если логин произошёл во время чекаута — редирект на корзину
        if (str_contains($request->getPathInfo(), '/checkout/')) {
            $this->session->set('_sylius.redirect_after_login', 
                $this->router->generate('sylius_shop_cart_summary')
            );
        }
    }
}

Итоги

Проблема сброса корзины при авторизации в Sylius — системная. Фреймворк предполагает, что разработчик будет кастомизировать аутентификацию, но механизм слияния корзин при этом остаётся хрупким местом.

Вот краткий чек-лист для проверки:

  1. Диспатч события — убедитесь, что sylius.customer.logged_in вызывается при кастомной аутентификации.
  2. Приоритеты CartContextSessionCartContext должен идти раньше CustomerCartContext.
  3. Регенерация сессииcart_id должен пережить смену Session ID.
  4. Поведение при чекауте — после логина на шаге оформления пользователь должен получить правильную корзину.

И самое важное: этот сценарий — «гость → логин → оформление заказа» — обязательно нужно покрывать автотестами. Ручное тестирование здесь недостаточно, потому что баг может проявляться только при определённом порядке запросов или при наличии существующей корзины у покупателя.

Редакция NJ Soft
Редакция NJ Soft
Редактор блога

Поделиться: