Магазин на Yii2, часть 21. Оформление заказа, часть вторая

07.08.2019

Теги: ActiveFormWeb-разработкаYii2БазаДанныхЗаказИнтернетМагазинКаталогТоваровКорзинаПрактикаФормаФреймворк

Хорошо, форма для оформления заказа готова, правила для валидации полей заказа заданы. Осталось только сохранить в таблицу БД order введенные пользователем данные. Поскольку у нас поля created и updated должны сохранять текущую дату и время, добавим метод behaviors() для класса Order.

<?php
namespace app\models;

use Yii;
use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;
use yii\db\Expression;

class Order extends ActiveRecord {

    /*...*/

    /**
     * Метод расширяет возможности класса Order, внедряя дополительные
     * свойства и методы. Кроме того, позволяет реагировать на события,
     * создаваемые классом Order или его родителями
     */
    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::class,
                'attributes' => [
                    // при вставке новой записи присвоить атрибутам created
                    // и updated значение метки времени UNIX
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created', 'updated'],
                    // при обновлении существующей записи  присвоить атрибуту
                    // updated значение метки времени UNIX
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated'],
                ],
                // если вместо метки времени UNIX используется DATETIME
                'value' => new Expression('NOW()'),
            ],
        ];
    }

    /*...*/

}
Вообще, в методе behaviors() нет необходимости — при создании таблицы БД order мы указали, какие значения устанавливать для полей created и updated при вставке новой записи и при обновлении существующей. Но хотелось продемонстрировать такую возможность фреймворка.

Теперь займемся контроллером. При отправке данных формы оформления заказа, мы должны загрузить их в модель и проверить с помощью заданных ранее правил валидации. Если данные прошли валидацию — сохраняем их в базу данных. Если при валидации были обнаружены ошибки — сохраняем в сессию введенные пользователем данные и массив сообщений об ошибках.

<?php
namespace app\controllers;

use Yii;
use app\models\Basket;
use app\models\Order;

class OrderController extends AppController {
    public $defaultAction = 'checkout';

    public function actionCheckout() {
        $this->setMetaTags('Оформление заказа');
        $order = new Order();
        /*
         * Если пришли post-данные, загружаем их в модель...
         */
        if ($order->load(Yii::$app->request->post())) {
            // ...и проверяем эти данные
            if ( ! $order->validate()) {
                // данные не прошли валидацию, отмечаем этот факт
                Yii::$app->session->setFlash(
                    'checkout-success',
                    false
                );
                // сохраняем в сессии введенные пользователем данные
                Yii::$app->session->setFlash(
                    'checkout-data',
                    [
                        'name' => $order->name,
                        'email' => $order->email,
                        'phone' => $order->phone,
                        'address' => $order->address,
                        'comment' => $order->comment
                    ]
                );
                /*
                 * Сохраняем в сессии массив сообщений об ошибках. Массив имеет вид
                 * [
                 *     'name' => [
                 *         'Поле «Ваше имя» обязательно для заполнения',
                 *     ],
                 *     'email' => [
                 *         'Поле «Ваш email» обязательно для заполнения',
                 *         'Поле «Ваш email» должно быть адресом почты'
                 *     ]
                 * ]
                 */
                Yii::$app->session->setFlash(
                    'checkout-errors',
                    $order->getErrors()
                );
            } else {
                /*
                 * Заполняем остальные поля модели — те которые приходят
                 * не из формы, а которые надо получить из корзины. Кроме
                 * того, поля created и updated будут заполнены с помощью
                 * метода Order::behaviors().
                 */
                $basket = new Basket();
                $content = $basket->getBasket();
                $order->amount = $content['amount'];
                // сохраняем заказ в базу данных
                $order->insert();
                $order->addItems($content);
                // очищаем содержимое корзины
                $basket->clearBasket();
                // данные прошли валидацию, заказ успешно сохранен
                Yii::$app->session->setFlash(
                    'checkout-success',
                    true
                );
            }
            // выполняем редирект, чтобы избежать повторной отправки формы
            return $this->refresh();
        }
        return $this->render('checkout', ['order' => $order]);
    }
}

В view-шаблоне формы оформления заказа мы проверяем — были отправлены данные формы? Если да — проверяем, прошли ли данные валидацию? Если данные прошли валидацию — показываем сообщение об успешном оформлении заказа. Если нет — выводим сообщения об ошибках, опять показываем форму и заполняем ее введенными ранее данными.

<?php
/*
 * Страница оформления заказа, файл views/order/checkout.php
 */

use app\components\TreeWidget;
use app\components\BrandsWidget;
use yii\widgets\ActiveForm;
use yii\helpers\Html;
use yii\helpers\Url;

/*
 * Если данные формы не прошли валидацию, получаем из сессии сохраненные
 * данные, чтобы заполнить ими поля формы, не заставляя пользователя
 * заполнять форму повторно
 */
$name = '';
$email = '';
$phone = '';
$address = '';
$comment = '';
if (Yii::$app->session->hasFlash('checkout-data')) {
    $data = Yii::$app->session->getFlash('checkout-data');
    $name = Html::encode($data['name']);
    $email = Html::encode($data['email']);
    $phone = Html::encode($data['phone']);
    $address = Html::encode($data['address']);
    $comment = Html::encode($data['comment']);
}
?>

<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">
                <h1>Оформление заказа</h1>
                <div id="checkout">
                    <?php
                    $success = false;
                    if (Yii::$app->session->hasFlash('checkout-success')) {
                        $success = Yii::$app->session->getFlash('checkout-success');
                    }
                    ?>

                    <?php if (!$success): ?>
                        <?php if (Yii::$app->session->hasFlash('checkout-errors')): ?>
                            <div class="alert alert-warning alert-dismissible" role="alert">
                                <button type="button" class="close"
                                        data-dismiss="alert" aria-label="Закрыть">
                                    <span aria-hidden="true">&times;</span>
                                </button>
                                <p>При заполнении формы допущены ошибки</p>
                                <?php $allErrors = Yii::$app->session->getFlash('checkout-errors'); ?>
                                <ul>
                                    <?php foreach ($allErrors as $errors): ?>
                                        <?php foreach ($errors as $error): ?>
                                            <li><?= $error; ?></li>
                                        <?php endforeach; ?>
                                    <?php endforeach; ?>
                                </ul>
                            </div>
                        <?php endif; ?>

                        <?php
                        $form = ActiveForm::begin(
                                ['id' => 'checkout-form', 'class' => 'form-horizontal']
                        );
                        echo $form->field($order, 'name')
                                  ->textInput(['value' => $name]);
                        echo $form->field($order, 'email')
                                  ->input('email', ['value' => $email]);
                        echo $form->field($order, 'phone')
                                  ->textInput(['value' => $phone]);
                        echo $form->field($order, 'address')
                                  ->textarea(['rows' => 2, 'value' => $address]);
                        echo $form->field($order, 'comment')
                                  ->textarea(['rows' => 2, 'value' => $comment]);
                        echo Html::submitButton(
                                'Оформить заказ',
                                ['class' => 'btn btn-primary']
                        );
                        ActiveForm::end();
                        ?>
                    <?php else: ?>
                        <p>Ваш заказ успешно оформлен, спасибо за покупку.</p>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>
</section>

В классе модели Order нам еще потребуется метод addItems(), который мы вызываем из контроллера.

<?php
namespace app\models;

use Yii;
use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;
use yii\db\Expression;

class Order extends ActiveRecord {

    /*...*/

    /**
     * Добавляет записи в таблицу БД `order_item`
     */
    public function addItems($basket) {
        // получаем товары в корзине
        $products = $basket['products'];
        // добавляем товары по одному
        foreach ($products as $product_id => $product) {
            $item = new OrderItem();
            $item->order_id = $this->id;
            $item->product_id = $product_id;
            $item->name = $product['name'];
            $item->price = $product['price'];
            $item->quantity = $product['count'];
            $item->cost = $product['price'] * $product['count'];
            $item->insert();
        }
    }
}

Поиск: ActiveForm • Web-разработка • Yii2 • База данных • Интернет магазин • Каталог товаров • Корзина • ActiveRecord • Заказ • Order • Форма • Basket

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