Оформление заказа и транзакции БД
Студент напишет логику сохранения заказа в базу данных, используя транзакции для обеспечения атомарности и целостности данных.
Процесс оформления заказа (Checkout)
Оформление заказа — самый ответственный этап работы e-commerce платформы. На этом этапе корзина, временно хранящаяся в сессии, должна превратиться в постоянные записи в базе данных. Согласно нашей архитектуре, мы используем две таблицы: `orders` (хранит общую информацию: ID пользователя, итоговую сумму, статус заказа 'pending') и `order_items` (хранит детали: какой товар, в каком количестве и по какой цене был куплен). Связь между ними — 'один ко многим' (One-to-Many). Один заказ содержит множество позиций. При нажатии кнопки 'Оформить заказ' контроллер CheckoutController должен выполнить следующие действия: 1. Проверить корзину на пустоту. 2. Создать запись в таблице orders. 3. Пройти циклом по корзине и создать записи в order_items, привязав их к ID созданного заказа. 4. Очистить корзину в сессии. 5. Перенаправить пользователя на оплату.
Проблема частичного сохранения и Транзакции
Представьте ситуацию: вы создали запись в таблице `orders`, начали в цикле добавлять `order_items`. Добавили первый товар, а на втором товаре сервер базы данных 'моргнул' или отвалился жесткий диск. Произошла ошибка. В результате в БД остался пустой заказ и только половина товаров. Пользователь оплатит неверную сумму, а менеджер отправит не тот комплект. Это катастрофа для бизнеса. Для решения этой проблемы используются Транзакции Базы Данных. Транзакция гарантирует свойство Атомарности (Atomicity) из принципов ACID: 'Всё или ничего'. Мы оборачиваем весь блок кода с запросами к БД в конструкцию DB::transaction(Closure). Если внутри замыкания происходит любая ошибка (выбрасывается Exception), Laravel автоматически делает 'rollback' — отменяет все изменения, сделанные в рамках этой транзакции. База данных остается в исходном, чистом состоянии. Если всё проходит успешно, выполняется 'commit', и данные фиксируются навсегда.
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\OrderItem;
public function process() {
$cart = session('cart', []);
if (empty($cart)) return redirect()->route('cart.index');
DB::transaction(function () use ($cart) {
// Расчет итоговой суммы
$total = array_sum(array_map(fn($item) => $item['price'] * $item['quantity'], $cart));
// Создание заказа
$order = Order::create([
'user_id' => auth()->id(),
'total_price' => $total,
'status' => 'pending'
]);
// Создание позиций заказа
foreach ($cart as $id => $item) {
OrderItem::create([
'order_id' => $order->id,
'product_id' => $id,
'quantity' => $item['quantity'],
'price' => $item['price']
]);
}
});
// Очистка корзины после успешной транзакции
session()->forget('cart');
// В следующем уроке здесь будет редирект на Stripe
return redirect()->route('orders.success');
}
Анализ кода транзакции
Внимательно изучите код выше, взятый из архивных материалов курса. Мы используем анонимную функцию (Closure) внутри DB::transaction. Обратите внимание на конструкцию `use ($cart)`. В PHP анонимные функции по умолчанию не имеют доступа к переменным из родительской области видимости. Мы обязаны явно 'пробросить' переменную $cart внутрь функции. Внутри мы создаем заказ через Eloquent метод create(). Благодаря тому, что мы используем auth()->id(), мы связываем заказ с текущим авторизованным пользователем (именно поэтому маршрут checkout защищен middleware 'auth' из 11 урока). Сразу после создания заказа мы получаем доступ к его идентификатору ($order->id), который используем как внешний ключ (foreign key) при создании записей в цикле foreach для модели OrderItem. Очистка сессии session()->forget('cart') вызывается СТРОГО после успешного завершения транзакции.
Зачем использовать DB::transaction при сохранении заказа?
Флеш-карточки
Что такое ACID в контексте баз данных?
Нажмите, чтобы увидеть ответ
Набор требований к транзакционной системе: Atomicity (Атомарность), Consistency (Согласованность), Isolation (Изолированность), Durability (Долговечность).
Нажмите, чтобы вернуться
Что делает функция array_map() в PHP?
Нажмите, чтобы увидеть ответ
Применяет callback-функцию ко всем элементам указанных массивов. В нашем коде она используется для умножения цены на количество для каждого товара в корзине.
Нажмите, чтобы вернуться
Задание
Практическая реализация CheckoutController.
- Создайте модели и миграции для Order и OrderItem (если еще не созданы). Убедитесь, что настроены связи hasMany и belongsTo.
- В CheckoutController скопируйте логику метода process() из примеров выше.
- Импортируйте фасад DB в начале файла: use Illuminate\Support\Facades\DB;
- Проверьте работу, добавив товары в корзину и перейдя по POST-маршруту /checkout.
- Откройте базу данных (например, через phpMyAdmin или DBeaver) и убедитесь, что записи появились в обеих таблицах с правильными связями по order_id.