Оформление заказа: Работа с формами и валидация
Студент разработает страницу оформления заказа, настроит сбор контактных данных пользователя и их строгую валидацию на стороне сервера.
Процесс Checkout и Транзакции Базы Данных
Оформление заказа (Checkout) — самый критичный и сложный этап работы интернет-магазина. Здесь мы трансформируем временные данные из сессии (корзину) в постоянные записи базы данных. Процесс включает создание записи в таблице orders (общая информация о заказе) и множества записей в таблице order_items (товары в заказе).
Представьте ситуацию: скрипт успешно создал заказ в orders, затем начал добавлять товары в order_items, но на втором товаре сервер упал или база данных выдала ошибку. У нас появится "повисший" заказ с неполным списком товаров. Это катастрофа для бизнеса. Чтобы гарантировать атомарность операций (либо выполняется всё, либо ничего), мы обязаны использовать Транзакции базы данных (DB::transaction). Если внутри замыкания (closure) транзакции произойдет любое исключение (Exception), Laravel автоматически выполнит rollback (откат), и база данных вернется в исходное состояние.
// App\Http\Controllers\CheckoutController.php
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\OrderItem;
use Illuminate\Http\Request;
public function process(Request $request) {
// 1. Получаем корзину и проверяем, не пуста ли она
$cart = session('cart', []);
if (empty($cart)) return redirect()->route('cart.index');
// 2. Транзакция БД гарантирует целостность данных
DB::transaction(function () use ($cart) {
// Элегантный подсчет общей суммы (Total) из архива курса
$total = array_sum(array_map(fn($item) => $item['price'] * $item['quantity'], $cart));
// Создаем родительскую запись заказа
$order = Order::create([
'user_id' => auth()->id(), // Пользователь должен быть авторизован (middleware 'auth')
'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'] // Фиксируем цену на момент покупки!
]);
}
});
// 3. Если транзакция успешна, очищаем корзину
session()->forget('cart');
return redirect()->route('orders.success');
}
Валидация входящих данных
В приведенном выше примере мы сфокусировались на сохранении данных корзины. Однако на странице оформления заказа мы также собираем данные от пользователя: адрес доставки, телефон, комментарий. Все эти данные приходят через объект Request и требуют строжайшей валидации. Если вы попытаетесь сохранить в базу данных строку длиной 500 символов в поле, ограниченное 255 символами, MySQL выбросит ошибку. Хуже того, отсутствие валидации открывает путь к XSS-атакам и SQL-инъекциям.
Laravel предоставляет метод $request->validate(). Если валидация не проходит, Laravel автоматически перенаправляет пользователя обратно на форму и передает переменные с сообщениями об ошибках (переменная $errors в Blade), а также сохраняет введенные данные в сессии (функция old() в Blade), чтобы пользователю не пришлось заполнять форму заново.
// Пример добавления валидации перед транзакцией
public function process(Request $request) {
// Валидация адреса и телефона
$validatedData = $request->validate([
'address' => 'required|string|min:10|max:255',
'phone' => 'required|string|regex:/^\+?[0-9]{10,15}$/',
'comment' => 'nullable|string|max:500',
]);
$cart = session('cart', []);
// ... код транзакции из предыдущего примера ...
// При создании Order мы можем добавить: 'address' => $validatedData['address']
}
Зачем использовать DB::transaction при сохранении заказа и его товаров в базу данных?
Фиксация исторических данных
Обратите внимание на строку в коде: 'price' => $item['price'] при создании OrderItem. Это критическое правило проектирования e-commerce систем. Вы обязаны копировать цену из товара (или корзины) в сводную таблицу заказа на момент покупки. Почему? Представьте, что клиент купил ноутбук за $1000. Через месяц магазин устроил распродажу, и цена ноутбука в таблице products стала $800. Если в истории заказов вы будете выводить цену через связь $orderItem->product->price, клиент увидит, что он якобы купил товар за $800, и общая сумма заказа не сойдется. Заказ — это исторический документ, его данные не должны меняться при изменении цен в каталоге.