Транзакции базы данных: Сохранение заказа
Изучение концепции транзакций БД (ACID). Применение DB::transaction для атомарного сохранения заказа и позиций корзины. Очистка сессии.
Добро пожаловать в шестнадцатый урок нашего курса! До этого момента мы научились формировать корзину пользователя, сохраняя выбранные товары в сессии, а также подготовили форму для ввода данных доставки. Теперь перед нами стоит критически важная задача: перенести данные из временного хранилища (сессии) в постоянное (базу данных). Этот процесс таит в себе серьезную опасность. Представьте ситуацию: пользователь оформляет заказ на 5 разных товаров. Наш код успешно создает запись в таблице orders, затем начинает в цикле добавлять товары в таблицу order_items. Первый товар добавлен успешно, второй тоже, но на третьем происходит сбой (например, отключилось электричество на сервере или база данных на секунду стала недоступна). Что произойдет? В базе останется «оборванный» заказ. Деньги с клиента могут быть списаны, но мы не будем знать, что именно он купил, так как часть товаров не сохранилась. Это катастрофа для любого интернет-магазина. Чтобы избежать таких ситуаций, в программировании и базах данных используется механизм транзакций.
Транзакция — это последовательность операций с базой данных, которая воспринимается СУБД как единое целое. Для транзакций действует принцип ACID: Atomicity (Атомарность), Consistency (Согласованность), Isolation (Изолированность), Durability (Надежность). Для нас сейчас важнее всего Атомарность. Этот принцип звучит так: «Или всё, или ничего». Если мы оборачиваем наши действия по сохранению заказа в транзакцию, база данных гарантирует, что либо все записи (и сам заказ, и все его позиции) будут успешно сохранены, либо, если на любом этапе возникнет ошибка, база данных откатит (Rollback) все изменения к первоначальному состоянию. В Laravel для работы с транзакциями предоставляется невероятно удобный фасад DB и метод transaction(). Метод принимает замыкание (анонимную функцию), внутри которого мы пишем наш код сохранения. Если код выполняется без выброса исключений, Laravel автоматически делает commit (фиксирует изменения). Если происходит ошибка — делает rollback.
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\OrderItem;
use Illuminate\Http\Request;
public function process(Request $request) {
$cart = session('cart', []);
if (empty($cart)) {
return redirect()->route('cart.index')->with('error', 'Корзина пуста');
}
// Оборачиваем логику сохранения в транзакцию
DB::transaction(function () use ($cart) {
// 1. Считаем общую сумму
$total = array_sum(array_map(fn($item) => $item['price'] * $item['quantity'], $cart));
// 2. Создаем главную запись заказа
$order = Order::create([
'user_id' => auth()->id(),
'total_price' => $total,
'status' => 'pending'
]);
// 3. Сохраняем каждый товар заказа
foreach ($cart as $id => $item) {
OrderItem::create([
'order_id' => $order->id,
'product_id' => $id,
'quantity' => $item['quantity'],
'price' => $item['price']
]);
}
});
// 4. Очищаем корзину после успешного сохранения
session()->forget('cart');
return redirect()->route('orders.success');
}
Давайте детально разберем представленный выше код контроллера CheckoutController. Во-первых, мы извлекаем корзину из сессии: $cart = session('cart', []);. Если корзина пуста, мы немедленно прерываем выполнение и возвращаем пользователя обратно. Далее мы вызываем DB::transaction(). Обратите внимание на конструкцию use ($cart). В PHP анонимные функции имеют свою собственную область видимости. Они не видят переменные, созданные вне их. Конструкция use позволяет «пробросить» переменную $cart внутрь транзакции. Внутри функции мы сначала вычисляем общую сумму заказа, используя комбинацию функций array_sum и array_map (это современный и лаконичный подход в PHP 8). Затем мы создаем запись в модели Order. Статус по умолчанию устанавливаем как pending (в ожидании оплаты). Получив объект $order, мы используем его ID ($order->id) для связывания товаров в цикле foreach. Мы создаем записи OrderItem. Только после того, как транзакция успешно завершится (выход из замыкания), мы вызываем session()->forget('cart'), навсегда удаляя оформленные товары из сессии пользователя.
Задание
Вам необходимо реализовать процесс оформления заказа в вашем проекте, используя транзакции.
- Создайте метод process() в CheckoutController.
- Импортируйте фасад Illuminate\Support\Facades\DB.
- Получите данные корзины из сессии.
- Реализуйте DB::transaction с замыканием, передав в него корзину.
- Внутри транзакции создайте Order и в цикле OrderItem.
- После блока транзакции добавьте очистку сессии и редирект.
Зачем использовать DB::transaction при сохранении заказа?
Какой метод фасада session используется для полного удаления ключа 'cart' из сессии? Напишите только название метода.
Флеш-карточки
Что такое Атомарность (Atomicity) в базах данных?
Нажмите, чтобы увидеть ответ
Принцип 'Всё или ничего'. Транзакция выполняется полностью, либо не выполняется вообще. Частичное сохранение исключено.
Нажмите, чтобы вернуться
Что делает команда Rollback?
Нажмите, чтобы увидеть ответ
Отменяет все изменения в базе данных, сделанные в рамках текущей транзакции, возвращая БД в исходное состояние.
Нажмите, чтобы вернуться