Вы когда-нибудь пытались сделать интернет-магазин на коленке за выходные? Я да. И знаете что? Получилось. Но не сразу. В этой статье я расскажу, как я использовал Yii2 для создания полноценного магазина, и поделюсь граблями, на которые наступил. Если вы тоже хотите собрать свой онлайн-бутик, но боитесь сложностей — читайте дальше. Будет просто и с примерами.

Почему Yii2, а не Laravel или Symfony

Когда я выбирал фреймворк, у меня было три кандидата: Laravel, Symfony и Yii2. Laravel — красивый, но для магазина показался тяжеловатым. Symfony — мощный, но слишком много конфигурации. Yii2 же — как швейцарский нож: легкий, быстрый, и в нем уже есть Gii — генератор кода. Это спасло мне кучу времени. Не надо писать CRUD вручную. Нажал пару кнопок — и модель готова.

Вот таблица для сравнения:

ПараметрYii2LaravelSymfony
Скорость стартаВысокая (Gii, готовые модули)Средняя (нужно настроить многое)Низкая (много boilerplate)
ПроизводительностьОтличная (ленивая загрузка)ХорошаяОтличная
ГибкостьСредняя (но для магазина хватает)ВысокаяОчень высокая
СообществоМаленькое, но активноеОгромноеБольшое

Вывод: для небольшого или среднего магазина Yii2 — идеальный вариант. Он не перегружен, но дает все необходимое.

Структура проекта: с чего начать

Первым делом я установил Yii2 через Composer. Команда простая:

composer create-project --prefer-dist yiisoft/yii2-app-basic shop

Потом настроил базу данных. Я использовал MySQL, но можно и PostgreSQL. В конфиге config/db.php прописал подключение:

<?php
return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=localhost;dbname=shop',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
];

Дальше — модели и контроллеры. С помощью Gii я сгенерировал модели для товаров, категорий и заказов. Это заняло 10 минут. Потом добавил связи: товар belongsTo категории, заказ hasMany товаров.

Правило: Не пиши код руками, если его можно сгенерировать. Gii — твой друг. Но не забывай потом править сгенерированный код под свои нужды.

Корзина: как не запутаться в сессиях

Корзина — сердце магазина. Я решил хранить ее в сессии, чтобы не грузить базу лишний раз. Создал компонент Cart:

<?php
namespace app\components;

use Yii;
use app\models\Product;

class Cart
{
    public function add($productId, $quantity = 1)
    {
        $session = Yii::$app->session;
        $cart = $session->get('cart', []);
        if (isset($cart[$productId])) {
            $cart[$productId] += $quantity;
        } else {
            $cart[$productId] = $quantity;
        }
        $session->set('cart', $cart);
    }

    public function getItems()
    {
        $session = Yii::$app->session;
        $cart = $session->get('cart', []);
        $items = [];
        foreach ($cart as $id => $qty) {
            $product = Product::findOne($id);
            if ($product) {
                $items[] = [
                    'product' => $product,
                    'quantity' => $qty,
                ];
            }
        }
        return $items;
    }

    public function clear()
    {
        Yii::$app->session->remove('cart');
    }
}

В контроллере вызывал Yii::$app->cart->add($id). Просто и понятно. Но есть нюанс: сессии не работают, если не включены куки. Проверьте настройки в config/web.php.

Ajax-обновление корзины: делаем без перезагрузки

Чтобы корзина обновлялась динамически, я добавил немного JavaScript. Вот как это выглядит на фронте:

$(document).on('click', '.add-to-cart', function() {
    var productId = $(this).data('id');
    $.ajax({
        url: '/cart/add',
        method: 'POST',
        data: {id: productId},
        success: function(response) {
            $('#cart-count').text(response.count);
            alert('Товар добавлен в корзину');
        }
    });
});

А в контроллере Yii2 обрабатываем запрос:

public function actionAdd()
{
    $id = Yii::$app->request->post('id');
    Yii::$app->cart->add($id);
    return json_encode(['count' => count(Yii::$app->cart->getItems())]);
}

Только не забудьте подключить jQuery и настроить CSRF-защиту. В Yii2 это делается через мета-тег в шаблоне:

<head>
    <meta name="csrf-token" content="<?= Yii::$app->request->csrfToken ?>">
</head>

Оформление заказа: валидация и сохранение

Когда пользователь кликает «Оформить заказ», я создаю модель Order и сохраняю данные. Вот упрощенный код:

<?php
namespace app\models;

use yii\db\ActiveRecord;

class Order extends ActiveRecord
{
    public static function tableName()
    {
        return 'orders';
    }

    public function rules()
    {
        return [
            [['customer_name', 'customer_email', 'address'], 'required'],
            ['customer_email', 'email'],
            ['status', 'default', 'value' => 'new'],
        ];
    }

    public function getItems()
    {
        return $this->hasMany(OrderItem::class, ['order_id' => 'id']);
    }
}

В контроллере оформления:

public function actionCheckout()
{
    $order = new Order();
    if ($order->load(Yii::$app->request->post()) && $order->validate()) {
        $order->save();
        // Сохраняем товары из корзины в order_items
        foreach (Yii::$app->cart->getItems() as $item) {
            $orderItem = new OrderItem();
            $orderItem->order_id = $order->id;
            $orderItem->product_id = $item['product']->id;
            $orderItem->quantity = $item['quantity'];
            $orderItem->price = $item['product']->price;
            $orderItem->save();
        }
        Yii::$app->cart->clear();
        Yii::$app->session->setFlash('success', 'Заказ оформлен!');
        return $this->redirect(['site/index']);
    }
    return $this->render('checkout', ['model' => $order]);
}
Лайфхак: Используйте транзакции при сохранении заказа. Если что-то пойдет не так, данные не сломаются. Yii2 поддерживает транзакции через Yii::$app->db->transaction.

Что в итоге

Мы разобрали базовый интернет-магазин на Yii2: установку, структуру, корзину, Ajax и оформление заказа. Этого достаточно, чтобы запустить MVP. Дальше можно добавить админку, платежные системы, email-уведомления. Yii2 дает гибкость, но не заставляет писать лишнего. Если вы новичок — не бойтесь. Просто делайте шаг за шагом. И помните: код должен быть читаемым, а не гениальным.

Студия WNDER