Корзина Sale в Битрикс D7: загрузка, строки позиций и ORM
Введение и модуль sale
Торговое ядро «1С‑Битрикс» уже много версий живёт поверх объектного слоя D7. Большинство задач корзины решаются классами в пространстве Bitrix\Sale, без смешения с устаревающими API на C. Перед любым примером нужно включить модуль: \Bitrix\Main\Loader::includeModule('sale') и при работе с валютой — при необходимости 'currency'/'catalog'.
Загрузка Basket
Для активного интернет-посетителя корзину обычно создают из пары значений «F_USER» и символьного кода сайта. Код сайта вытягивают из текущего контекста приложения — имейте в виду ограничения зоны: в некоторых backend-вывозках Context::getCurrent()->getSite() недоступен так же предсказуемо, как на витрине.
use Bitrix\Main\Context;
use Bitrix\Sale;
$siteSlug = Context::getCurrent()->getSite();
$guestCartOwnerId = Sale\Fuser::getId();
$guestCartBasket = Sale\Basket::loadItemsForFUser($guestCartOwnerId, $siteSlug);
$orderPk = 2048;
$basketOwnedByOrder = Sale\Order::load($orderPk)->getBasket();
// Если заказ уже в переменной:
// $basketOwnedByOrder = Sale\Basket::loadItemsForOrder($issuedOrderEntity);Агрегаты и массовые операции
Суммы и вес не обязательно собирать вручную: ->getPrice() уже учитывает скидки, ->getBasePrice() показывает расчёт до их применения, ->getWeight() агрегирует строки.
Строку с известным внутренним идентификатором убирайте методом удаления записи перед сохранением корзины:
$guestCartBasket->getItemById($basketPrimaryKey)->delete(); затем $guestCartBasket->save();
Срез доступных для оформления позиций: ->getOrderableItems(). Для писем и отладочного текстового дампа есть getListOfFormatText(), для быстрых пересчётов количества — getQuantityList() в связке с array_sum или count.
Добавление или изменение SKU из каталога
Схема близка к логике старого CSaleBasket::Add: ищем существующую строку источника 'catalog' и идентификатора товара через getExistsItem. Если нашли — увеличиваем число штук, иначе создаём createItem и задаём базовые поля: количество, валюта, сайт и класс-провайдер для учёта остатков.
use Bitrix\Currency\CurrencyManager;
use Bitrix\Main\Context;
$skuElementKey = 405; // элемент каталога
$unitsPacked = 3;
if ($existingLine = $guestCartBasket->getExistsItem('catalog', $skuElementKey)) {
$existingLine->setField(
'QUANTITY',
$existingLine->getQuantity() + $unitsPacked
);
} else {
$draftLine = $guestCartBasket->createItem('catalog', $skuElementKey);
$draftLine->setFields([
'QUANTITY' => $unitsPacked,
'CURRENCY' => CurrencyManager::getBaseCurrency(),
'LID' => Context::getCurrent()->getSite(),
'PRODUCT_PROVIDER_CLASS' => 'CCatalogProductProvider',
]);
/*
Своя ставка без пересчёта прайса:
$draftLine->setFields([
'QUANTITY' => $unitsPacked,
'CURRENCY' => CurrencyManager::getBaseCurrency(),
'LID' => Context::getCurrent()->getSite(),
'PRICE' => $negotiatedAmount,
'CUSTOM_PRICE' => 'Y',
]);
*/
}
$guestCartBasket->save();Отдельно существует «коробочное» добавление после Loader::includeModule('catalog'): \Bitrix\Catalog\Product\Basket::addProduct(['PRODUCT_ID' => …, 'QUANTITY' => …, 'PROPS' => …]). Результат — объект с успехами/ошибками (isSuccess(), getErrorMessages()): проверки остатка и аккуратное слияние дубликатов встроены, зато жёстко зафиксированная вручную цена через этот путь не задаётся.
Строка корзины и свойства
Каждый элемент — экземпляр Sale\BasketItem; полный массив выдаёт ->getBasketItems(), а сам объект Basket поддерживает итерацию и доступ как к коллекции — можно писать foreach ($guestCartBasket as $lineEntity).
$lineCollection = $guestCartBasket->getBasketItems();
$singleLine = $lineCollection[0];
$singleLine->getId();
$singleLine->getProductId();
$singleLine->getPrice();
$singleLine->getQuantity();
$singleLine->getFinalPrice();
$singleLine->getWeight();
$singleLine->getField('NAME');
$singleLine->canBuy();
$singleLine->isDelay();
foreach ($guestCartBasket as $lineEntity) {
echo $lineEntity->getField('NAME') . ' — ' . $lineEntity->getQuantity() . "\n";
}
$propBag = $singleLine->getPropertyCollection();
$rowValues = $propBag->getPropertyValues();
$propBag->setProperty([
[
'NAME' => 'Габарит',
'CODE' => 'PACKAGE_SIZE',
'VALUE' => '600×400',
'SORT' => 120,
],
]);
$propBag->save();
$parentCart = $singleLine->getCollection();Чтобы удалить одно свойство, обходят коллекцию $propBag, сравнивают код и вызывают ->delete() у найденного элемента, после чего снова сохраняют контейнер.
Прямые запросы: Internals\BasketTable
Для сводной аналитики без поднятия бизнес-объекта удобен ORM-класс Sale\Internals\BasketTable; список значений свойств по строкам читают из Sale\Internals\BasketPropertyTable, фильтруя полем BASKET_ID.
use Bitrix\Sale;
$listHandle = Sale\Internals\BasketTable::getList([
'filter' => [
'FUSER_ID' => Sale\Fuser::getId(),
'ORDER_ID' => null,
'LID' => SITE_ID,
'CAN_BUY' => 'Y',
],
]);
while ($dbRow = $listHandle->fetch()) {
// обработать $dbRow
}
$rollupRow = Sale\Internals\BasketTable::getList([
'filter' => [
'FUSER_ID' => Sale\Fuser::getId(),
'ORDER_ID' => null,
'LID' => SITE_ID,
'CAN_BUY' => 'Y',
],
'select' => ['CNT_LINES', 'MONEY_TOTAL'],
'runtime' => [
new \Bitrix\Main\Entity\ExpressionField('CNT_LINES', 'COUNT(*)'),
new \Bitrix\Main\Entity\ExpressionField(
'MONEY_TOTAL',
'SUM(PRICE * QUANTITY)'
),
],
])->fetch();Вывод для разработчика
- Держите единственный объект
Basketна сценарий и завершайте изменения общимsave(). - Не смешивайте объектный слой со старым
CSale*, если хотите сохранять инварианты скидок и свойств строк. - ORM нужен для массовых отчётов, UI и бизнес-правил — там, где нужны события и провайдеры, всё равно возвращайтесь к объектам Sale.
Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии