Магазин на Yii2, часть 17. Корзина покупателя, часть первая

13.07.2019

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

Ну вот, добрались и до корзины. Перед тем, как писать код — несколько слов о том, что будем делать. Для начала создадим форму для добавления товара в корзину из списка товаров категории, бренда, результатов поиска. Контроллер будет принимать POST-данные и обращаться к модели, чтобы добавить товар в корзину. Саму корзину будем хранить в сессии в виде массива.

Итак, создаем класс контроллера BasketController:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {
    public function actionIndex() {
        $basket = (new Basket())->getBasket();
        return $this->render('index', ['basket' => $basket]);
    }

    public function actionAdd() {

        $basket = new Basket();

        /*
         * Данные должны приходить методом POST; если это не
         * так — просто показываем корзину
         */
        if (!Yii::$app->request->isPost) {
            return $this->redirect(['basket/index']);
        }

        $data = Yii::$app->request->post();
        if (!isset($data['id'])) {
            return $this->redirect(['basket/index']);
        }
        if (!isset($data['count'])) {
            $data['count'] = 1;
        }

        // добавляем товар в корзину и перенаправляем покупателя
        // на страницу корзины
        $basket->addToBasket($data['id'], $data['count']);

        return $this->redirect(['basket/index']);
    }
}

Создаем класс модели Basket:

<?php
namespace app\models;

use yii\base\Model;
use Yii;

class Basket extends Model {
    /**
     * Метод добавляет товар в корзину
     */
    public function addToBasket($id, $count = 1) {
        $count = (int)$count;
        if ($count < 1) {
            return;
        }
        $id = abs((int)$id);
        $product = Product::findOne($id);
        if (empty($product)) {
            return;
        }
        if ($count > 10) {
            $count = 10;
        }
        $session = Yii::$app->session;
        $session->open();
        if (!$session->has('basket')) {
            $session->set('basket', []);
            $basket = [];
        } else {
            $basket = $session->get('basket');
        }
        if (isset($basket['products'][$product->id])) { // такой товар уже есть?
            $count = $basket['products'][$product->id]['count'] + $count;
            if ($count > 100) {
                $count = 100;
            }
            $basket['products'][$product->id]['count'] = $count;
        } else { // такого товара еще нет
            $basket['products'][$product->id]['name'] = $product->name;
            $basket['products'][$product->id]['price'] = $product->price;
            $basket['products'][$product->id]['count'] = $count;
        }
        $amount = 0.0;
        foreach ($basket['products'] as $item) {
            $amount = $amount + $item['price'] * $item['count'];
        }
        $basket['amount'] = $amount;
        $session->set('basket', $basket);
    }

    /**
     * Метод удаляет товар из корзины
     */
    public function removeFromBasket($id) {
        $id = abs((int)$id);
        $session = Yii::$app->session;
        $session->open();
        if (!$session->has('basket')) {
            return;
        }
        $basket = $session->get('basket');
        if (!isset($basket['products'][$id])) {
            return;
        }
        unset($basket['products'][$id]);
        if (count($basket['products']) == 0) {
            $session->set('basket', []);
            return;
        }
        $amount = 0.0;
        foreach ($basket['products'] as $item) {
            $amount = $amount + $item['price'] * $item['count'];
        }
        $basket['amount'] = $amount;

        $session->set('basket', $basket);
    }

    /**
     * Метод возвращает содержимое корзины
     */
    public function getBasket() {
        $session = Yii::$app->session;
        $session->open();
        if (!$session->has('basket')) {
            $session->set('basket', []);
            return [];
        } else {
            return $session->get('basket');
        }
    }

    /**
     * Метод удаляет все товары из корзины
     */
    public function clearBasket() {
        $session = Yii::$app->session;
        $session->open();
        $session->set('basket', []);
    }

}

Создаем view-шаблон для страницы корзины:

<?php
/*
 * Страница корзины покупателя, файл views/basket/index.php
 */

use app\components\TreeWidget;
use app\components\BrandsWidget;
use yii\helpers\Html;
use yii\helpers\Url;
?>

<section>
    <div class="container">
        <div class="row">
            <div class="col-sm-3">
                <h2>Каталог</h2>
                <div class="category-products">
                    <?= TreeWidget::widget(); ?>
                </div>

                <h2>Бренды</h2>
                <div class="brand-products">
                    <?= BrandsWidget::widget(); ?>
                </div>
            </div>

            <div class="col-sm-9">
                <h1>Корзина</h1>
                <?php if (!empty($basket)): ?>
                    <table class="table table-bordered">
                        <tr>
                            <th>Наименование</th>
                            <th>Количество</th>
                            <th>Цена, руб.</th>
                            <th>Сумма, руб.</th>
                        </tr>
                        <?php foreach ($basket['products'] as $item): ?>
                            <tr>
                                <td><?= $item['name']; ?></td>
                                <td class="text-right"><?= $item['count']; ?></td>
                                <td class="text-right"><?= $item['price']; ?></td>
                                <td class="text-right"><?= $item['price'] * $item['count']; ?></td>
                            </tr>
                        <?php endforeach; ?>
                        <tr>
                            <td colspan="3" class="text-right">Итого</td>
                            <td class="text-right"><?= $basket['amount']; ?></td>
                        </tr>
                    </table>
                <?php else: ?>
                    <p>Ваша корзина пуста</p>
                <?php endif; ?>
            </div>
        </div>
    </div>
</section>

Формат корзины, которую мы храним в сессии (123 и 456 — идентификаторы товаров):

Array (
    [products] => Array(
            [123] => Array (
                    [name] => Мужская рубашка
                    [price] => 1000
                    [count] => 2
                )
            [456] => Array(
                    [name] => Мужская футблока
                    [price] => 1200
                    [count] => 1
                )
        )
    [amount] => 3200
)

Кроме всего прочего, изменяем файлы view-шаблонов, где показывается список товаров — добавляем форму. Например, вот как будет выглядеть файл views/catalog/category.php:

<?php
/*
 * Страница раздела каталога, файл views/catalog/category.php
 */

use app\components\TreeWidget;
use app\components\BrandsWidget;
use app\components\ChainWidget;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\widgets\LinkPager;
?>

<section>
    <div class="container">
        <div class="row">
            <div class="col-sm-3">
                <div class="left-sidebar">
                    <h2>Каталог</h2>
                    <div class="category-products">
                        <?= TreeWidget::widget(); ?>
                    </div>

                    <h2>Бренды</h2>
                    <div class="brand-products">
                        <?= BrandsWidget::widget(); ?>
                    </div>
                </div>
            </div>

            <div class="col-sm-9">
                <?= ChainWidget::widget(['itemCurrent' => $category['id'], 'showCurrent' => false]); ?>
                <?php if (!empty($products)): ?>
                    <h2><?= Html::encode($category['name']); ?></h2>
                    <div class="row">
                        <?php foreach ($products as $product): ?>
                            <div class="col-sm-4">
                                <div class="product-wrapper text-center">
                                    <?=
                                    Html::img(
                                        '@web/images/products/medium/'.$product['image'],
                                        ['alt' => $product['name'], 'class' => 'img-responsive']
                                    );
                                    ?>
                                    <h2><?= $product['price']; ?> руб.</h2>
                                    <p>
                                        <a href="<?= Url::to(['catalog/product', 'id' => $product['id']]); ?>">
                                            <?= Html::encode($product['name']); ?>
                                        </a>
                                    </p>
                                    <form method="post"
                                          action="<?= Url::to(['basket/add']); ?>">
                                        <input type="hidden" name="id"
                                               value="<?= $product['id']; ?>">
                                        <?=
                                        Html::hiddenInput(
                                            Yii::$app->request->csrfParam,
                                            Yii::$app->request->csrfToken
                                        );
                                        ?>
                                        <button type="submit" class="btn btn-warning">
                                            <i class="fa fa-shopping-cart"></i>
                                            Добавить в корзину
                                        </button>
                                    </form>
                                    <?php
                                    if ($product['new']) { // новинка?
                                        echo Html::img(
                                            '@web/images/home/new.png',
                                            ['alt' => 'Новинка', 'class' => 'new']
                                        );
                                    }
                                    if ($product['sale']) { // распродажа?
                                        echo Html::img(
                                            '@web/images/home/sale.png',
                                            ['alt' => 'Распродажа', 'class' => 'sale']
                                        );
                                    }
                                    ?>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>

                    <?= LinkPager::widget(['pagination' => $pages]); /* постраничная навигация */ ?>
                <?php else: ?>
                    <p>Нет товаров в этой категории.</p>
                <?php endif; ?>
            </div>
        </div>
    </div>
</section>

Обратите внимание на скрытое поле со значением CSRF токена. Это нужно, чтобы не получить ошибку:

Bad Request (#400). Не удалось проверить переданные данные.

На этом все, корзина для списка товаров уже работает.

Поиск: Session • Web-разработка • Yii2 • Интернет магазин • Каталог товаров • Практика • Форма • Фреймворк • Корзина • Сессия

Каталог оборудования
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.