Магазин на Yii2, часть 12. Промежуточные итоги и рефакторинг кода
03.06.2019
Теги: Web-разработка • Yii2 • ИнтернетМагазин • КаталогТоваров • Кеширование • Практика • Фреймворк
Некий промежуточный итог — здесь все, что было сделано на текущий момент. Исправлены ошибки, допущенные ранее. Добавлены новые поля в таблицы базы данных. Переписаны некоторые фрагменты кода, которые оказались неудачными. Добавлено кеширование тяжелых фрагментов кода, связанных с выборкой данных из БД.
Контроллеры AppController, PageController и CatalogController
<?php namespace app\controllers; use yii\web\Controller; use Yii; class AppController extends Controller { /** * Метод устанавливает мета-теги для страницы сайта * @param string $title * @param string $keywords * @param string $description */ protected function setMetaTags($title = '', $keywords = '', $description = '') { $this->view->title = $title ?: Yii::$app->params['defaultTitle']; $this->view->registerMetaTag([ 'name' => 'keywords', 'content' => $keywords ?: Yii::$app->params['defaultKeywords'] ]); $this->view->registerMetaTag([ 'name' => 'description', 'content' => $description ?: Yii::$app->params['defaultDescription'] ]); } }
<?php namespace app\controllers; use app\models\Product; use Yii; class PageController extends AppController { /* * Главная страница сайта */ public function actionIndex() { // получаем лидеров продаж $hitProducts = Yii::$app->cache->get('hit-products'); if ($hitProducts === false) { $hitProducts = Product::find()->where(['hit' => 1])->limit(3)->asArray()->all(); Yii::$app->cache->set('hit-products', $hitProducts); } // получаем новые товары $newProducts = Yii::$app->cache->get('new-products'); if ($newProducts === false) { $newProducts = Product::find()->where(['new' => 1])->limit(3)->asArray()->all(); Yii::$app->cache->set('new-products', $newProducts); } // получаем товары распродажи $saleProducts = Yii::$app->cache->get('sale-products'); if ($saleProducts === false) { $saleProducts = Product::find()->where(['sale' => 1])->limit(3)->asArray()->all(); Yii::$app->cache->set('sale-products', $saleProducts); } // устанавливаем мета-теги для страницы $this->setMetaTags(); return $this->render( 'index', compact('hitProducts', 'newProducts', 'saleProducts') ); } }
<?php namespace app\controllers; use app\models\Category; use app\models\Brand; use app\models\Product; use Yii; class CatalogController extends AppController { /** * Главная страница каталога товаров */ public function actionIndex() { // получаем корневые категории $roots = Yii::$app->cache->get('root-categories'); if ($roots === false) { $roots = Category::find()->where(['parent_id' => 0])->asArray()->all(); Yii::$app->cache->set('root-categories', $roots); } // получаем популярные бренды $brands = Yii::$app->cache->get('popular-brands'); if ($brands === false) { $brands = (new Brand())->getPopularBrands(); Yii::$app->cache->set('popular-brands', $brands); } return $this->render('index', compact('roots', 'brands')); } /** * Категория каталога товаров */ public function actionCategory($id, $page = 1) { $id = (int)$id; $page = (int)$page; // пробуем извлечь данные из кеша $data = Yii::$app->cache->get('category-'.$id.'-page-'.$page); if ($data === false) { // данных нет в кеше, получаем их заново $temp = new Category(); // товары категории list($products, $pages) = $temp->getCategoryProducts($id); // данные о категории $category = $temp->getCategory($id); // сохраняем полученные данные в кеше $data = [$products, $pages, $category]; Yii::$app->cache->set('category-'.$id.'-page-'.$page, $data); } list($products, $pages, $category) = $data; // устанавливаем мета-теги для страницы $this->setMetaTags( $category['name'] . ' | ' . Yii::$app->params['shopName'], $category['keywords'], $category['description'] ); return $this->render( 'category', compact('category', 'products', 'pages') ); } /** * Список всех брендов каталога товаров */ public function actionBrands() { // пробуем извлечь данные из кеша $brands = Yii::$app->cache->get('all-brands'); if ($brands === false) { // данных нет в кеше, получаем их заново $brands = (new Brand())->getAllBrands(); // сохраняем полученные данные в кеше Yii::$app->cache->set('all-brands', $brands); } return $this->render( 'brands', compact('brands') ); } /** * Список товаров бренда с идентификатором $id */ public function actionBrand($id, $page = 1) { $id = (int)$id; // пробуем извлечь данные из кеша $data = Yii::$app->cache->get('brand-'.$id.'-page-'.$page); if ($data === false) { // данных нет в кеше, получаем их заново $temp = new Brand(); // товары бренда list($products, $pages) = $temp->getBrandProducts($id); // данные о бренде $brand = $temp->getBrand($id); // сохраняем полученные данные в кеше $data = [$products, $pages, $brand]; Yii::$app->cache->set('brand-'.$id.'-page-'.$page, $data); } list($products, $pages, $brand) = $data; // устанавливаем мета-теги $this->setMetaTags( $brand['name'] . ' | ' . Yii::$app->params['shopName'], $brand['keywords'], $brand['description'] ); return $this->render( 'brand', compact('brand', 'products', 'pages') ); } }
Модели Brand и Category
<?php namespace app\models; use yii\data\Pagination; use yii\db\ActiveRecord; use Yii; class Brand extends ActiveRecord { /** * Метод возвращает имя таблицы БД */ public static function tableName() { return 'brand'; } /** * Метод возвращает массив товаров бренда */ public function getProducts() { // связь таблицы БД `brand` с таблицей `product` return $this->hasMany(Product::class, ['brand_id' => 'id']); } /** * Возвращает информацию о бренде с идентификатором $id */ public function getBrand($id) { return self::find()->where(['id' => $id])->asArray()->one(); } /** * Возвращает массив популярных брендов и * количество товаров для каждого бренда */ public function getPopularBrands() { // получаем бренды с наибольшим кол-вом товаров $brands = self::find() ->select([ 'id' => 'brand.id', 'name' => 'brand.name', 'content' => 'brand.content', 'image' => 'brand.image', 'count' => 'COUNT(*)' ]) ->innerJoin( 'product', 'product.brand_id = brand.id' ) ->groupBy([ 'brand.id', 'brand.name', 'brand.content', 'brand.image' ]) ->orderBy(['count' => SORT_DESC]) ->limit(10) ->asArray() // для дальнейшей сортировки ->indexBy('name') ->all(); // теперь нужно отсортировать бренды по названию ksort($brands); return $brands; } /** * Возвращает массив всех брендов каталога и * количество товаров для каждого бренда */ public function getAllBrands() { return self::find() ->select([ 'id' => 'brand.id', 'name' => 'brand.name', 'content' => 'brand.content', 'image' => 'brand.image', 'count' => 'COUNT(*)' ]) ->innerJoin( 'product', 'product.brand_id = brand.id' ) ->groupBy([ 'brand.id', 'brand.name', 'brand.content', 'brand.image' ]) ->orderBy(['name' => SORT_ASC]) ->asArray() ->all(); } /** * Возвращает массив всех товаров бренда с идентификатором $id */ public function getBrandProducts($id) { // для постаничной навигации получаем только часть товаров $query = Product::find()->where(['brand_id' => $id]); $pages = new Pagination([ 'totalCount' => $query->count(), 'pageSize' => Yii::$app->params['pageSize'], 'forcePageParam' => false, 'pageSizeParam' => false ]); $products = $query ->offset($pages->offset) ->limit($pages->limit) ->asArray() ->all(); return [$products, $pages]; } }
<?php namespace app\models; use yii\data\Pagination; use yii\db\ActiveRecord; use Yii; class Category extends ActiveRecord { /** * Метод возвращает имя таблицы БД */ public static function tableName() { return 'category'; } /** * Метод описывает связь таблицы БД `category` с таблицей `product` */ public function getProducts() { // связь таблицы БД `category` с таблицей `product` return $this->hasMany(Product::class, ['category_id' => 'id']); } /** * Метод описывает связь таблицы БД `category` с таблицей `category` */ public function getParent() { return $this->hasOne(self::class, ['id' => 'parent_id']); } /** * Метод описывает связь таблицы БД `category` с таблицей `category` */ public function getChildren() { return $this->hasMany(self::class, ['parent_id' => 'id']); } /** * Возвращает информацию о категории с иденификатором $id */ public function getCategory($id) { return self::find()->where(['id' => $id])->asArray()->one(); } /** * Возвращает массив всех товаров в категории с идентификатором $id * и в ее потомках, т.е. в дочерних, дочерних-дочерних и так далее */ public function getCategoryProducts($id) { // получаем массив идентификаторов всех потомков категории $ids = $this->getAllChildIds($id); $ids[] = $id; // для постаничной навигации получаем только часть товаров $query = Product::find()->where(['in', 'category_id', $ids]); $pages = new Pagination([ 'totalCount' => $query->count(), 'pageSize' => Yii::$app->params['pageSize'], 'forcePageParam' => false, 'pageSizeParam' => false ]); $products = $query ->offset($pages->offset) ->limit($pages->limit) ->asArray() ->all(); return [$products, $pages]; } /** * Возвращает массив идентификаторов всех потомков категории $id, * т.е. дочерние, дочерние дочерних и так далее */ protected function getAllChildIds($id) { $children = []; $ids = $this->getChildIds($id); foreach ($ids as $item) { $children[] = $item; $c = $this->getAllChildIds($item); foreach ($c as $v) { $children[] = $v; } } return $children; } /** * Возвращает массив идентификаторов дочерних категорий (прямых * потомков) категории с уникальным идентификатором $id */ protected function getChildIds($id) { $children = self::find()->where(['parent_id' => $id])->asArray()->all(); $ids = []; foreach ($children as $child) { $ids[] = $child['id']; } return $ids; } }
Шаблоны в директории views/catalog
<?php /* * Главная страница сайта, файл views/page/index.php */ use app\components\TreeWidget; use app\components\BrandsWidget; use yii\helpers\Url; use yii\helpers\Html; ?> <section> <div class="container"> <!-- Слайдер из трех элементов --> <div id="slider" class="carousel slide" data-ride="carousel"> <!-- Индикатор текущего элемента --> <ol class="carousel-indicators"> <!-- Активный элемент --> <li data-target="#slider" data-slide-to="0" class="active"></li> <li data-target="#slider" data-slide-to="1"></li> <li data-target="#slider" data-slide-to="2"></li> </ol> <!-- Обертка для слайдов --> <div class="carousel-inner" role="listbox"> <!-- Активный элемент --> <div class="item active"> <img src="/images/slider/1.jpg" alt="..."> <div class="carousel-caption">Первый элемент слайдера</div> </div> <div class="item"> <img src="/images/slider/2.jpg" alt="..."> <div class="carousel-caption">Второй элемент слайдера</div> </div> <div class="item"> <img src="/images/slider/3.jpg" alt="..."> <div class="carousel-caption">Третий элемент слайдера</div> </div> </div> <!-- Элементы управления --> <a class="left carousel-control" href="#carousel-example" role="button" data-slide="prev"> <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> <span class="sr-only">Предыдущий</span> </a> <a class="right carousel-control" href="#carousel-example" role="button" data-slide="next"> <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> <span class="sr-only">Следующий</span> </a> </div> </div> </section> <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"> <?php if (!empty($hits)): ?> <h2>Лидеры продаж</h2> <div class="row"> <?php foreach ($hits as $hit): ?> <div class="col-sm-4"> <div class="product-wrapper text-center"> <?= Html::img( '@web/images/products/medium/'.$hit['image'], ['alt' => $hit['name'], 'class' => 'img-responsive'] ); ?> <h2><?= $hit['price']; ?> руб.</h2> <p> <a href="<?= Url::to(['catalog/product', 'id' => $hit['id']]); ?>"> <?= Html::encode($hit['name']); ?> </a> </p> <a href="#" class="btn btn-warning"> <i class="fa fa-shopping-cart"></i> Добавить в корзину </a> <?php if ($hit['new']) { // новинка? echo Html::img( '@web/images/home/new.png', ['alt' => 'Новинка', 'class' => 'new'] ); } if ($hit['sale']) { // распродажа? echo Html::img( '@web/images/home/sale.png', ['alt' => 'Распродажа', 'class' => 'sale'] ); } ?> </div> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> </div> </div> </section>
<?php /* * Главная страница каталога, файл views/catalog/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"> <?php if (!empty($roots)): ?> <h2>Одежда и обувь</h2> <div class="row"> <?php foreach ($roots as $root): ?> <div class="col-sm-6 col-md-4"> <div class="thumbnail"> <?= Html::img( '@web/images/roots/'.$root['image'], ['alt' => $root['name']] ); ?> <div class="caption"> <h2> <a href="<?= Url::to(['catalog/category', 'id' => $root['id']]); ?>"> <?= Html::encode($root['name']); ?> </a> </h2> <p><?= Html::encode($root['content']); ?></p> </div> </div> </div> <?php endforeach; ?> </div> <?php endif; ?> <?php if (!empty($brands)): ?> <h2>Популярные бренды</h2> <div class="row"> <?php foreach ($brands as $brand): ?> <div class="col-sm-6 col-md-4"> <div class="thumbnail"> <?= Html::img( '@web/images/brands/'.$brand['image'], ['alt' => $brand['name']] ); ?> <div class="caption"> <h2> <a href="<?= Url::to(['catalog/brand', 'id' => $brand['id']]); ?>"> <?= Html::encode($brand['name']); ?> </a> </h2> <p><?= Html::encode($brand['content']); ?></p> </div> </div> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> </div> </div> </section>
<?php /* * Страница раздела каталога, файл views/catalog/category.php */ use app\components\TreeWidget; use app\components\BrandsWidget; 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"> <?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> <a href="#" class="btn btn-warning"> <i class="fa fa-shopping-cart"></i> Добавить в корзину </a> <?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>
<?php /* * Страница списка всех брендов, файл views/catalog/brands.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"> <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 padding-right"> <h1>Все бренды</h1> <?php if (!empty($brands)): ?> <div class="row"> <?php foreach ($brands as $brand): ?> <div class="col-sm-6 col-md-4"> <div class="thumbnail"> <?= Html::img( '@web/images/brands/'.$brand['image'], ['alt' => $brand['name']] ); ?> <div class="caption"> <h2> <a href="<?= Url::to(['catalog/brand', 'id' => $brand['id']]); ?>"> <?= Html::encode($brand['name']); ?> </a> </h2> <p><?= Html::encode($brand['content']); ?></p> </div> </div> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> </div> </div> </section>
<?php /* * Страница списка товаров бренда, файл views/catalog/brand.php */ use app\components\TreeWidget; use app\components\BrandsWidget; use yii\helpers\Html; use yii\helpers\Url; use yii\widgets\LinkPager; ?> <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"> <?php if (!empty($products)): /* выводим товары бренда */ ?> <h2><?= Html::encode($brand['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> <a href="#" class="btn btn-warning"> <i class="fa fa-shopping-cart"></i> Добавить в корзину </a> <?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>
- Магазин на Yii2, часть 35. Админка: загрузка картинок для страниц и страница 404
- Магазин на Yii2, часть 34. Показываем меню страниц в публичной части
- Магазин на Yii2, часть 33. Админка: приводим в порядок CRUD-код для страниц
- Магазин на Yii2, часть 32. Админка: удаление категорий и CRUD для страниц
- Магазин на Yii2, часть 31. Админка: загрузка изображений для категорий и брендов
- Магазин на Yii2, часть 30. Админка: WYSIWYG-редактор и изображение для товара
- Магазин на Yii2, часть 29. Админка: добавляем список товаров категории
Поиск: Web-разработка • Yii2 • Интернет магазин • Каталог товаров • Кеширование • Практика • Фреймворк