Магазин на Yii2, часть 14. Показываем страницу 404 Not Found

03.07.2019

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

После установки Yii2 уже имеется файл view-шаблона views/site/error.php. Чтобы получить свою страницу ошибки, можно просто отредактировать этот файл. В нём доступны три переменные: $name, $message, $exception. Признак ошибки 404 — это значение свойства statusCode объекта $exception. Таким образом можно написать свою проверку этого значения и для разных ошибок показывать пользователю разный ответ.

Почему для показа сообщения об ошибках используется шаблон views/site/error.php? Это задается в настройках приложения, в файле config/web.php:

$config = [
    /* ... */
    'components' => [
        /* ... */
        'errorHandler' => [
            'errorAction' => 'site/error'
        ],
        /* ... */
    ],
    /* ... */
];

Для обработки ошибок будет использован метод actionError() класса SiteController:

class SiteController extends Controller {
    /* ... */
    public function actionError() {
        $exception = Yii::$app->errorHandler->exception;
        if ($exception !== null) {
            return $this->render('error', ['exception' => $exception]);
        }
    }
    /* ... */
}

Но если мы заглянем в исходный код controllers/SiteController.php, то обнаружим, что такого метода там нет. А вместо него есть метод actions():

class SiteController extends Controller {
    /* ... */
    public function actions()
    {
        return [
            'error' => [
                // объявляем действие error и задаем имя класса
                'class' => 'yii\web\ErrorAction',
            ],
        ];
    }
    /* ... */
}
Все действия контроллера можно разделить на встроенные и отдельные. Встроенные действия определены как методы контроллера, например actionError(). Отдельные действия указываются в карте действий контроллера, в методе actions(). Сам же функционал таких действий описан в отдельных классах.

Мы договорились ранее использовать класс SiteController только как образец. Давайте зададим свой обработчик ошибки в файле конфигурации config/web.php:

$config = [
    /* ... */
    'components' => [
        /* ... */
        'errorHandler' => [
            'errorAction' => 'app/error'
        ],
        /* ... */
    ],
    /* ... */
];

Добавим метод actions() в класс AppController:

class AppController extends Controller {
    /* ... */
    public function actions() {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
        ];
    }
    /* ... */
}

Создадим view-шаблон views/app/error.php:

<?php
/* @var $this yii\web\View */
/* @var $name string */
/* @var $message string */
/* @var $exception Exception */

use yii\helpers\Html;

$this->title = $name;
?>
<div class="container">
    <h1><?= Html::encode($this->title) ?></h1>
    <div class="alert alert-danger">
        <?= nl2br(Html::encode($message)) ?>
    </div>
</div>

И внесем изменения в класс CatalogController, чтобы он выбрасывал исключение, когда в базе данных не удается найти категорию, бренд или товар каталога:

<?php
namespace app\controllers;

use app\models\Category;
use app\models\Brand;
use app\models\Product;
use yii\web\HttpException;
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 === null) {
            // данные есть в кеше, но такой категории не существует
            throw new HttpException(
                404,
                'Запрошенная страница не найдена'
            );
        }
        if ($data === false) {
            // данных нет в кеше, получаем их заново
            $temp = new Category();
            // данные о категории
            $category = $temp->getCategory($id);
            if (!empty($category)) { // такая категория существует
                // товары категории
                list($products, $pages) = $temp->getCategoryProducts($id);
                // сохраняем полученные данные в кеше
                $data = [$products, $pages, $category];
                Yii::$app->cache->set('category-' . $id . '-page-' . $page, $data);
            } else { // такая категория не существует
                Yii::$app->cache->set('category-' . $id . '-page-' . $page, null);
                throw new HttpException(
                    404,
                    'Запрошенная страница не найдена'
                );
            }
        }
        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;
        $page = (int)$page;
        // пробуем извлечь данные из кеша
        $data = Yii::$app->cache->get('brand-'.$id.'-page-'.$page);
        if ($data === null) {
            // данные есть в кеше, но такого бренда не существует
            throw new HttpException(
                404,
                'Запрошенная страница не найдена'
            );
        }
        if ($data === false) {
            // данных нет в кеше, получаем их заново
            $temp = new Brand();
            // данные о бренде
            $brand = $temp->getBrand($id);
            if (!empty($brand)) { // такой бренд существует
                // товары бренда
                list($products, $pages) = $temp->getBrandProducts($id);
                // сохраняем полученные данные в кеше
                $data = [$products, $pages, $brand];
                Yii::$app->cache->set('brand-'.$id.'-page-'.$page, $data);
            } else { // такой бренд не существует
                Yii::$app->cache->set('brand-'.$id.'-page-'.$page, null);
                throw new HttpException(
                    404,
                    'Запрошенная страница не найдена'
                );
            }
        }
        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')
        );
    }

    /**
     * Страница товара с идентификатором $id
     */
    public function actionProduct($id) {
        $id = (int)$id;
        // пробуем извлечь данные из кеша
        $data = Yii::$app->cache->get('product-'.$id);
        if ($data === null) {
            // данные есть в кеше, но такого товара не существует
            throw new HttpException(
                404,
                'Запрошенная страница не найдена'
            );
        }
        if ($data === false) {
            // данных нет в кеше, получаем их заново
            $product = (new Product())->getProduct($id);
            if (!empty($product)) { // такой товар существует
                $brand = (new Brand())->getBrand($product['brand_id']);
                $data = [$product, $brand];
                // сохраняем полученные данные в кеше
                Yii::$app->cache->set('product-' . $id, $data);
            } else { // такого товара не существует
                Yii::$app->cache->set('product-' . $id, null);
                throw new HttpException(
                    404,
                    'Запрошенная страница не найдена'
                );
            }
        }
        list($product, $brand) = $data;
        // устанавливаем мета-теги
        $this->setMetaTags(
            $product['name'] . ' | ' . Yii::$app->params['shopName'],
            $product['keywords'],
            $product['description']
        );
        // получаем популярные товары, похожие на текущий
        $similar = Yii::$app->cache->get('similar-'.$product['id']);
        if ($similar === false) {
            // товары из той же категории того же бренда
            $similar = Product::find()
                ->where([
                    'hit' => 1,
                    'category_id' => $product['category_id'],
                    'brand_id' => $product['brand_id']
                ])
                ->andWhere(['NOT IN', 'id', $product['id']])
                ->limit(3)
                ->asArray()
                ->all();
            Yii::$app->cache->set('similar-'.$product['id'], $similar);
        }
        return $this->render(
            'product',
            compact('product', 'brand', 'similar')
        );
    }
}

Поиск: Web-разработка • Yii2 • Интернет магазин • Исключение • Каталог товаров • Практика • Фреймворк • 404 • Not Found • Exception • Ошибка • Error

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