Магазин на Laravel 7, часть 6. Изменение количества товара, удаление товара из корзины
04.10.2020
Теги: Laravel • MySQL • PHP • Web-разработка • БазаДанных • ИнтернетМагазин • КаталогТоваров • Корзина • Практика • Фреймворк • ШаблонСайта
Обновление корзины
Для каждого товара в корзине есть две кнопки — «Плюс» и «Минус», которые увеличивают или уменьшают количество. Давайте добавим два маршрута, создадим две формы в шаблоне и реализуем два метода в контроллере — plus()
и minus()
.
Route::post('/basket/plus/{id}', 'BasketController@plus') ->where('id', '[0-9]+') ->name('basket.plus'); Route::post('/basket/minus/{id}', 'BasketController@minus') ->where('id', '[0-9]+') ->name('basket.minus');
<td> <form action="{{ route('basket.minus', ['id' => $product->id]) }}" method="post" class="d-inline"> @csrf <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="fas fa-minus-square"></i> </button> </form> <span class="mx-1">{{ $itemQuantity }}</span> <form action="{{ route('basket.plus', ['id' => $product->id]) }}" method="post" class="d-inline"> @csrf <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="fas fa-plus-square"></i> </button> </form> </td>
class BasketController extends Controller { /** * Увеличивает кол-во товара $id в корзине на единицу */ public function plus(Request $request, $id) { $basket_id = $request->cookie('basket_id'); if (empty($basket_id)) { abort(404); } $this->change($basket_id, $id, 1); // выполняем редирект обратно на страницу корзины return redirect() ->route('basket.index') ->withCookie(cookie('basket_id', $basket_id, 525600)); } /** * Уменьшает кол-во товара $id в корзине на единицу */ public function minus(Request $request, $id) { $basket_id = $request->cookie('basket_id'); if (empty($basket_id)) { abort(404); } $this->change($basket_id, $id, -1); // выполняем редирект обратно на страницу корзины return redirect() ->route('basket.index') ->withCookie(cookie('basket_id', $basket_id, 525600)); } /** * Изменяет кол-во товара $product_id на величину $count */ private function change($basket_id, $product_id, $count = 0) { if ($count == 0) { return; } $basket = Basket::findOrFail($basket_id); // если товар есть в корзине — изменяем кол-во if ($basket->products->contains($product_id)) { $pivotRow = $basket->products()->where('product_id', $product_id)->first()->pivot; $quantity = $pivotRow->quantity + $count; if ($quantity > 0) { // обновляем кол-во товара $product_id в корзине $pivotRow->update(['quantity' => $quantity]); // обновляем поле `updated_at` таблицы `baskets` $basket->touch(); } else { // кол-во равно нулю — удаляем товар из корзины $pivotRow->delete(); } } } }
Временна́я зона
Немного неудобно, что в базе данных поля created_at
и updated_at
сохраняются в UTC. Это можно изменить в настройках приложения, в файле config/app.php
. Или в методе boot()
сервис-провайдера App\Providers\AppServiceProvide
.
return [ /* ... */ 'timezone' => 'Europe/Moscow', /* ... */ ];
class AppServiceProvider extends ServiceProvider { /* ... */ public function boot(){ date_default_timezone_set('Europe/Moscow'); } /* ... */ }
Но мы этого делать не будем. В базе данных дату и время лучше хранить именно в UTC. А вот пользователю можно показывать дату и время с учетом временной зоны. Для этого надо либо спросить у пользователя временную зону (если он зарегистрирован) и сохранить в базу данных. Либо определить зону без участия пользователя, по ip-адресу. И при показе даты и времени на сайте, преобразовывать дату-время из БД с помощью аксессоров и мутаторов. Как это сделать с помощью пакета laravel-geoip
— хорошо описано здесь.
Задействуем модель
У меня опять получился большой и запутанный контроллер, а модель не используется вовсе. Давайте это исправим и реализуем методы модели, которые позволят добавлять и удалять товар из корзины.
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Basket extends Model { /** * Связь «многие ко многим» таблицы `baskets` с таблицей `products` */ public function products() { return $this->belongsToMany(Product::class)->withPivot('quantity'); } /** * Увеличивает кол-во товара $id в корзине на величину $count */ public function increase($id, $count = 1) { $this->change($id, $count); } /** * Уменьшает кол-во товара $id в корзине на величину $count */ public function decrease($id, $count = 1) { $this->change($id, -1 * $count); } /** * Изменяет количество товара $id в корзине на величину $count; * если товара еще нет в корзине — добавляет этот товар; $count * может быть как положительным, так и отрицательным числом */ private function change($id, $count = 0) { if ($count == 0) { return; } // если товар есть в корзине — изменяем кол-во if ($this->products->contains($id)) { // получаем объект строки таблицы `basket_product` $pivotRow = $this->products()->where('product_id', $id)->first()->pivot; $quantity = $pivotRow->quantity + $count; if ($quantity > 0) { // обновляем количество товара $id в корзине $pivotRow->update(['quantity' => $quantity]); } else { // кол-во равно нулю — удаляем товар из корзины $pivotRow->delete(); } } elseif ($count > 0) { // иначе — добавляем этот товар $this->products()->attach($id, ['quantity' => $count]); } // обновляем поле `updated_at` таблицы `baskets` $this->touch(); } /** * Удаляет товар с идентификатором $id из корзины покупателя */ public function remove($id) { // удаляем товар из корзины (разрушаем связь) $this->products()->detach($id); // обновляем поле `updated_at` таблицы `baskets` $this->touch(); } }
Модель переделали, теперь надо удалить все лишнее из контроллера:
<?php namespace App\Http\Controllers; use App\Basket; use Illuminate\Http\Request; use Illuminate\Database\Eloquent\ModelNotFoundException; class BasketController extends Controller { private $basket; public function __construct() { $this->getBasket(); } /** * Показывает корзину покупателя */ public function index() { $products = $this->basket->products; return view('basket.index', compact('products')); } /** * Форма оформления заказа */ public function checkout() { return view('basket.checkout'); } /** * Добавляет товар с идентификатором $id в корзину */ public function add(Request $request, $id) { $quantity = $request->input('quantity') ?? 1; $this->basket->increase($id, $quantity); // выполняем редирект обратно на ту страницу, // где была нажата кнопка «В корзину» return back(); } /** * Увеличивает кол-во товара $id в корзине на единицу */ public function plus($id) { $this->basket->increase($id); // выполняем редирект обратно на страницу корзины return redirect()->route('basket.index'); } /** * Уменьшает кол-во товара $id в корзине на единицу */ public function minus($id) { $this->basket->decrease($id); // выполняем редирект обратно на страницу корзины return redirect()->route('basket.index'); } /** * Возвращает объект корзины; если не найден — создает новый */ private function getBasket() { $basket_id = request()->cookie('basket_id'); if (!empty($basket_id)) { try { $this->basket = Basket::findOrFail($basket_id); } catch (ModelNotFoundException $e) { $this->basket = Basket::create(); } } else { $this->basket = Basket::create(); } Cookie::queue('basket_id', $this->basket->id, 525600); } }
Удаление из корзины
Теперь в модели есть метод remove()
, который позволяет удалить товар из корзины. Добавим два новых маршрута, несколько форм в шаблон страницы корзины и реализуем два метода в контроллере — remove()
и clear()
.
Route::post('/basket/remove/{id}', 'BasketController@remove') ->where('id', '[0-9]+') ->name('basket.remove'); Route::post('/basket/clear', 'BasketController@clear')->name('basket.clear');
@extends('layout.site') @section('content') <h1>Ваша корзина</h1> @if (count($products)) @php $basketCost = 0; @endphp <form action="{{ route('basket.clear') }}" method="post" class="text-right"> @csrf <button type="submit" class="btn btn-outline-danger mb-4 mt-0"> Очистить корзину </button> </form> <table class="table table-bordered"> <tr> <th>№</th> <th>Наименование</th> <th>Цена</th> <th>Кол-во</th> <th>Стоимость</th> <th></th> </tr> @foreach($products as $product) @php $itemPrice = $product->price; $itemQuantity = $product->pivot->quantity; $itemCost = $itemPrice * $itemQuantity; $basketCost = $basketCost + $itemCost; @endphp <tr> <td>{{ $loop->iteration }}</td> <td> <a href="{{ route('catalog.product', [$product->slug]) }}"> {{ $product->name }} </a> </td> <td>{{ number_format($itemPrice, 2, '.', '') }}</td> <td> <form action="{{ route('basket.minus', ['id' => $product->id]) }}" method="post" class="d-inline"> @csrf <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="fas fa-minus-square"></i> </button> </form> <span class="mx-1">{{ $itemQuantity }}</span> <form action="{{ route('basket.plus', ['id' => $product->id]) }}" method="post" class="d-inline"> @csrf <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="fas fa-plus-square"></i> </button> </form> </td> <td>{{ number_format($itemCost, 2, '.', '') }}</td> <td> <form action="{{ route('basket.remove', ['id' => $product->id]) }}" method="post"> @csrf <button type="submit" class="m-0 p-0 border-0 bg-transparent"> <i class="fas fa-trash-alt text-danger"></i> </button> </form> </td> </tr> @endforeach <tr> <th colspan="4" class="text-right">Итого</th> <th>{{ number_format($basketCost, 2, '.', '') }}</th> <th></th> </tr> </table> @else <p>Ваша корзина пуста</p> @endif @endsection
class BasketController extends Controller { /** * Удаляет товар с идентификаторм $id из корзины */ public function remove($id) { $this->basket->remove($id); // выполняем редирект обратно на страницу корзины return redirect()->route('basket.index'); } /** * Полностью очищает содержимое корзины покупателя */ public function clear() { $this->basket->delete(); // выполняем редирект обратно на страницу корзины return redirect()->route('basket.index'); } }
Исправляем шаблоны
В layout-шаблоне надо добавить ссылку на страницу корзины. И нужна кнопка «Добавить в корзину» для списка товаров.
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <!-- Бренд и кнопка «Гамбургер» --> <a class="navbar-brand" href="{{ route('index') }}">Магазин</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-example" aria-controls="navbar-larashop" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <!-- Основная часть меню (может содержать ссылки, формы и прочее) --> <div class="collapse navbar-collapse" id="navbar-larashop"> <!-- Этот блок расположен слева --> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="{{ route('catalog.index') }}">Каталог</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Доставка</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Контакты</a> </li> </ul> <!-- Этот блок расположен посередине --> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="search" placeholder="Поиск по каталогу" aria-label="Search"> <button class="btn btn-outline-info my-2 my-sm-0" type="submit">Искать</button> </form> <!-- Этот блок расположен справа --> <ul class="navbar-nav ml-auto"> <li class="nav-item"> <a class="nav-link" href="{{ route('basket.index') }}">Корзина</a> </li> </ul> </div> </nav>
<div class="col-md-6 mb-4"> <div class="card list-item"> <div class="card-header"> <h3 class="mb-0">{{ $product->name }}</h3> </div> <div class="card-body p-0"> <img src="https://via.placeholder.com/400x120" alt="" class="img-fluid"> </div> <div class="card-footer"> <!-- Форма для добавления товара в корзину --> <form action="{{ route('basket.add', ['id' => $product->id]) }}" method="post" class="d-inline"> @csrf <button type="submit" class="btn btn-success">Добавить в корзину</button> </form> <a href="{{ route('catalog.product', ['slug' => $product->slug]) }}" class="btn btn-dark float-right">Перейти к товару</a> </div> </div> </div>
BasketController
, это аукнулось позже, когда потребовалось получать этот объект не только в контроллере. Так что позже метод getBasket()
пришлось перенести в модель Basket
и сделать его статическим. Подробнее можно посмотреть здесь.
- Магазин на Laravel 7, часть 10. Форма оформления, сохранение заказа в базу данных
- Магазин на Laravel 7, часть 24. Фильтр товаров категории по цене, новинкам и лидерам продаж
- Магазин на Laravel 7, часть 23. Главная страница сайта, новинки, лидеры продаж и распродажа
- Магазин на Laravel 7, часть 22. Рефакторинг кода, работа над каталогом товаров и корзиной
- Магазин на Laravel 7, часть 21. Добавляем профили и используем их при оформлении заказа
- Магазин на Laravel 7, часть 20. Показ отдельной страницы и верхнее меню всех страниц
- Магазин на Laravel 7, часть 18. Панель управления, пользователи и CRUD страниц сайта
Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Интернет магазин • Каталог товаров • Корзина • Практика • Фреймворк • Шаблон сайта