Магазин на Laravel 7, часть 5. Создаем корзину покупателя, добавление товара в корзину

03.10.2020

Теги: LaravelMySQLPHPWeb-разработкаИнтернетМагазинКаталогТоваровКорзинаПрактикаФреймворкШаблонСайта

Добавим еще один контроллер BasketController, который будет отвечать за корзину покупателя. Корзины будем хранить в таблице baskets базы данных. И нам еще потребуется таблица для связи многие-ко-многим — для товаров и корзин. В одной корзине может быть несколько товаров, один товар может быть в нескольких корзинах.

Контроллер BasketController

Создаем контроллер BasketController:

> php artisan make:controller BasketController
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BasketController extends Controller {
    public function index() {
        return view('basket.index');
    }

    public function checkout() {
        return view('basket.checkout');
    }
}

Сразу создадим два шаблона:

@extends('layout.site')

@section('content')
    <h1>Ваша корзина</h1>
    <p>Здесь будет содержимое корзины</p>
@endsection
@extends('layout.site')

@section('content')
    <h1>Оформление заказа</h1>
    <p>Здесь будет форма оформления</p>
@endsection

И добавим два маршртута:

Route::get('/basket/index', 'BasketController@index')->name('basket.index');
Route::get('/basket/checkout', 'BasketController@checkout')->name('basket.checkout');

Модель Basket

Создаем модель Basket вместе с миграцией:

> php artisan make:model -m Basket
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBasketsTable extends Migration {
    public function up() {
        Schema::create('baskets', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    public function down() {
        Schema::dropIfExists('baskets');
    }
}

Создаем таблицу для связи baskets и products:

> php artisan make:migration create_basket_product_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBasketProductTable extends Migration {
    public function up() {
        Schema::create('basket_product', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('basket_id');
            $table->unsignedBigInteger('product_id');
            $table->unsignedTinyInteger('quantity');

            $table->foreign('basket_id')
                ->references('id')
                ->on('baskets')
                ->cascadeOnDelete();
            $table->foreign('product_id')
                ->references('id')
                ->on('products')
                ->cascadeOnDelete();
        });
    }

    public function down() {
        Schema::dropIfExists('basket_product');
    }
}

Задаем связь между таблицами:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Basket extends Model {
    /**
     * Связь «многие ко многим» таблицы `baskets` с таблицей `products`
     */
    public function products() {
        return $this->belongsToMany(Product::class)->withPivot('quantity');
    }
}
namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model {
    /**
     * Связь «многие ко многим» таблицы `products` с таблицей `baskets`
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function baskets() {
        return $this->belongsToMany(Basket::class)->withPivot('quantity');
    }
}

Применяем наши миграции:

> php artisan migrate

Добавление в корзину

Во-первых, нам нужен новый маршрут в файле web.php:

Route::post('/basket/add/{id}', 'BasketController@add')
    ->where('id', '[0-9]+')
    ->name('basket.add');

Во-вторых, добавляем форму на страницу товара:

<div class="col-md-6">
    <p>Цена: {{ number_format($product->price, 2, '.', '') }}</p>
    <!-- Форма для добавления товара в корзину -->
    <form action="{{ route('basket.add', ['id' => $product->id]) }}"
          method="post" class="form-inline">
        @csrf
        <label for="input-quantity">Количество</label>
        <input type="text" name="quantity" id="input-quantity" value="1"
               class="form-control mx-2 w-25">
        <button type="submit" class="btn btn-success">Добавить в корзину</button>
    </form>
</div>

После этого добавляем новый метод add() в контроллер:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BasketController extends Controller {
    public function index() {
        return view('basket.index');
    }

    public function checkout() {
        return view('basket.checkout');
    }

    /**
     * Добавляет товар с идентификатором $id в корзину
     */
    public function add(Request $request, $id) {
        $basket_id = $request->cookie('basket_id');
        $quantity = $request->input('quantity') ?? 1;
        if (empty($basket_id)) {
            // если корзина еще не существует — создаем объект
            $basket = Basket::create();
            // получаем идентификатор, чтобы записать в cookie
            $basket_id = $basket->id;
        } else {
            // корзина уже существует, получаем объект корзины
            $basket = Basket::findOrFail($basket_id);
            // обновляем поле `updated_at` таблицы `baskets`
            $basket->touch();
        }
        if ($basket->products->contains($id)) {
            // если такой товар есть в корзине — изменяем кол-во
            $pivotRow = $basket->products()->where('product_id', $id)->first()->pivot;
            $quantity = $pivotRow->quantity + $quantity;
            $pivotRow->update(['quantity' => $quantity]);
        } else {
            // если такого товара нет в корзине — добавляем его
            $basket->products()->attach($id, ['quantity' => $quantity]);
        }
        // выполняем редирект обратно на страницу, где была нажата кнопка «В корзину»
        return back()->withCookie(cookie('basket_id', $basket_id, 525600));
    }
}

Обновить значение pivot-поля quantity в промежуточной таблице basket_product можно с помощью метода

$basket->products()->updateExistingPivot(
    $product_id,
    ['quantity', $quantity]
);

Для удобства временно отключим шифрование cookie, чтобы иметь возможность видеть значение, которые мы сохраняем в basket_id:

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware {
    /**
     * The names of the cookies that should not be encrypted.
     *
     * @var array
     */
    protected $except = [
        'basket_id'
    ];
}

Но не забываем вернуть все обратно, после того, как закончим работать над корзиной покупателя.

Содержимое корзины

Надо доработать метод index() контроллера и внести изменения в шаблон, который отвечает за показ корзины:

namespace App\Http\Controllers;

use App\Basket;
use Illuminate\Http\Request;

class BasketController extends Controller {
    /**
     * Показывает корзину покупателя
     */
    public function index(Request $request) {
        $basket_id = $request->cookie('basket_id');
        if (!empty($basket_id)) {
            $products = Basket::findOrFail($basket_id)->products;
            return view('basket.index', compact('products'));
        } else {
            abort(404);
        }
    }
    /* ... */
}
@extends('layout.site')

@section('content')
    <h1>Ваша корзина</h1>
    @if (count($products))
        @php
            $basketCost = 0;
        @endphp
        <table class="table table-bordered">
            <tr>
                <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>
                        <i class="fas fa-minus-square"></i>
                        <span class="mx-1">{{ $itemQuantity }}</span>
                        <i class="fas fa-plus-square"></i>
                    </td>
                    <td>{{ number_format($itemCost, 2, '.', '') }}</td>
                </tr>
            @endforeach
            <tr>
                <th colspan="4" class="text-right">Итого</th>
                <th>{{ number_format($basketCost, 2, '.', '') }}</th>
            </tr>
        </table>
    @else
        <p>Ваша корзина пуста</p>
    @endif
@endsection

Поиск: Laravel • MySQL • PHP • Web-разработка • Интернет магазин • Каталог товаров • Корзина • Практика • Фреймворк • Шаблон сайта

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.