Создание каталога

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Создание каталога

Сообщение evrej »

Делаю каталог товаров такого типа:

- Бытовая техника catalog/bytovay-tehnika
    - Техника для кухни catalog/bytovay-tehnika/kuhnja
    - Техника для гостиной catalog/bytovay-tehnika/gostinaja
- Спортивные товары catalog/sport-tovary
    - Велосипеды catalog/sport-tovary/velosipedy
        - Электровелосипеды catalog/sport-tovary/velosipedy/elektro
        - Скоростные велосипеды catalog/sport-tovary/velosipedy/skorostnye
    - Лыжи catalog/sport-tovary/lyji
- Открытки catalog/otkrytki

Всего два вида страниц:
views/catalog/catalog.php - вывод простым списком подкатегорий.
views/catalog/goods.php - вывод товаров в конечной подкатегории.

Не могу понять как сделать контроллер. Например, что бы узнать catalog/sport-tovary/velosipedy/elektro что это конечная подкатегория или нет надо подключиться к базе, а в контроллере обращаться к базе считается плохим тоном.
Или надо сделать класс который обрабатывает URL, далее подключать класс который обрабатывает конечную подкатегорию и подключать соответствующий вывод.
Подскажите, пожалуйста, примерно куда копать.
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

Я предполагаю что надо создать класс со своими правилами, типа этого

Код: Выделить всё

namespace app\components;

use yii\web\UrlRule;

class CarUrlRule extends UrlRule
{
    public $connectionID = 'db';

    public function init()
    {
        if ($this->name === null) {
            $this->name = __CLASS__;
        }
    }

    public function createUrl($manager, $route, $params)
    {
        if ($route === 'car/index') {
            if (isset($params['manufacturer'], $params['model'])) {
                return $params['manufacturer'] . '/' . $params['model'];
            } elseif (isset($params['manufacturer'])) {
                return $params['manufacturer'];
            }
        }
        return false;  // это правило не подходит
    }

    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();
        if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
            // check $matches[1] and $matches[3] to see
            // if they match a manufacturer and a model in the database
            // If so, set $params['manufacturer'] and/or $params['model']
            // and return ['car/index', $params]
        }
        return false;  // это правило не подходит
    }
} 
В какую папку надо положить такой файл?
Аватара пользователя
Insolita
Сообщения: 788
Зарегистрирован: 2011.06.06, 01:39
Контактная информация:

Re: Создание каталога

Сообщение Insolita »

ну если

Код: Выделить всё

namespace app\components; 
то в папку components

а вообще - куда угодно и удобно - если шаблон advanced то в common - главное правильно namespace и пути к классу в конфиге прописать
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

Insolita писал(а):ну если

Код: Выделить всё

namespace app\components;
то в папку components

а вообще - куда угодно и удобно - если шаблон advanced то в common - главное правильно namespace и пути к классу в конфиге прописать
Insolita, спасибо. У меня basic. Создал папку components и добавил туда этот класс. В параметрах прописал:

Код: Выделить всё

'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
                '' => 'site/index',
                'mail' => 'site/mail',
                'about' => 'site/about',
                'contact' => 'site/contact',
                'login' => 'site/login',
                'site/<action>' => '404',
                'catalog' => 'catalog/index',
                [
                    'class' => 'app\components\CatalogUrlRule',
                ],
            ],
        ], 
Ошибок не вылетает :) Буду дальше писать правила для разбора URL.
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

Код: Выделить всё

<?php

namespace app\components;

use yii\web\UrlRule;

class CatalogUrlRule extends UrlRule
{
    public $connectionID = 'db';

    public function init()
    {
        if ($this->name === null) {
            $this->name = __CLASS__;
        }
    }

    public function createUrl($manager, $route, $params)
    {
        if ($route === 'catalog/index') {
            if (isset($params['manufacturer'], $params['model'])) {
                return $params['manufacturer'] . '/' . $params['model'];
            } elseif (isset($params['manufacturer'])) {
                return $params['manufacturer'];
            }
        }
        return false;  // это правило не подходит
    }

    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();
        if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
            // check $matches[1] and $matches[3] to see
            // if they match a manufacturer and a model in the database
            // If so, set $params['manufacturer'] and/or $params['model']
            return ['catalog/index', $params['manufacturer']]; // выдает ошибку что второй аргумент не массив
        }
        return false;  // это правило не подходит
    }
} 
Напишите, пожалуйста, пример разбора простой строки, например, catalog/avto/chehly.
Как составлять return ['catalog/index', $params['manufacturer']];
Аватара пользователя
Insolita
Сообщения: 788
Зарегистрирован: 2011.06.06, 01:39
Контактная информация:

Re: Создание каталога

Сообщение Insolita »

вам то зачем $params['manufacturer'] у вас другие параметры должны быть.. там конкретный пример для вывода машины по модели и производителю авто
если более абстрактно

Код: Выделить всё

 public function createUrl($manager, $route, $params)
    {
        if ($route === 'catalog/index') {
            // тут для создания правильной чпу ссылки правила должны быть 
            //то есть когда вы в коде генерируете ссылку типа Url::toRoute(['controller/action','paramId1'=>'paramdata1','param2'=>'paramdata2'])
           // вот при создании - в эту функцию $route будет = 'controller/action',          
           //а в $params - массив ['paramId1'=>'paramdata1','param2'=>'paramdata2']
            
        }
        return false;  // это правило не подходит
    }

    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();
        if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
           // а вот тут обрабатывается урл который получен из браузера, с помощью регулярного выражения (вы его можете изменить как надо)
            // и в результате обработки урла вы должны получить роут и список параметров которые будут переданы в ваш контроллер
            return ['controller/action','paramId1'=>'paramdata1','param2'=>'paramdata2']];
        }
        return false;  // это правило не подходит
    }
 
а у вас получается каталог неограниченной вложенности дерева? может оказаться в перспективе типа ? Как само дерево каталога строится - через NestedSet ?

Код: Выделить всё

- Спортивные товары catalog/sport-tovary
    - Велосипеды catalog/sport-tovary/velosipedy
        - Детские catalog/sport-tovary/velosipedy/detskie
            - Электровелосипеды catalog/sport-tovary/velosipedy/detskie/elektro
            - Скоростные велосипеды catalog/sport-tovary/velosipedy/detskie/skorostnye
        - Взрослые catalog/sport-tovary/velosipedy/vzroslie
            - Электровелосипеды catalog/sport-tovary/velosipedy/vzroslie/elektro
            - Скоростные велосипеды catalog/sport-tovary/velosipedy/vzroslie/skorostnye
в вашем случае например при генерации ссылки указывать только id каталога или id товара и нужный вариант отображения
допустим у вас есть контроллер Catalog и акшн

Код: Выделить всё

public function actionCategory($id, $type){
// Генерируем список вида в зависимости от параметра $type
}
 
В менюхах и т.п. делаем ссылки вида Yii::$app->urlManager->createUrl(['catalog/category','id'=>$id, $type='vid1']);Yii::$app->urlManager->createUrl(['catalog/category','id'=>$id,'type'=>'vid2']); (или через хелпер Url::to - не важно...)

в нашем классе

Код: Выделить всё

//в $params у нас попадает id и type а в $route  'catalog/category' 
 public function createUrl($manager, $route, $params)
    {
        if ($route === 'catalog/category') {
                $id=$params['id']; $type=$params['type']
                //Дальше делаем запрос к базе,  вытягиваем хвост родительских категорий, чпу-название конкретной категории
                return 'parent1/parent2/category'
            
        } 
        return false;  // это правило не подходит
    }

    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();
        //Составляем регулярное выражение -зависит от максимальной вложенности
        $pattern='%catalog\/([A-Za-z_\-0-9]+)(\/([A-Za-z_\-0-9]+))(\/([A-Za-z_\-0-9]+))?%'; 
        if (preg_match($pattern, $pathInfo, $matches)) {
           // в $matches массив результатов обрабатываем - нам нужно получить самый последний - значимый роут, 
           // Делаем запрос в таблицу категорий получаем $id по чпу-названию и определяем - конечная она 
          //или нет - в зависимости от этого    ставим $type ('vid1' или 'vid2')
            return [ 'catalog/category' ,'id'=>$id','type'=>$type]];
        }
        return false;  // это правило не подходит
    }
Для составления паттерна правильного - в помощь http://www.phpliveregex.com/ я чисто навскидку наклепала
Ну а какие делать запросы и как подгружать список всех дочерних катгегрий - это уже от структуры вашей базы зависит

можно и вообще без параметра type обойтись - чисто на основании данных из базы о конечности категории выводить... можно в контроллере не $id а непосредственно $slug последней категории передавать и с учетом этого в базу запросы делать - не по id - тут уж на вкус и цвет фломастеры разные...
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

Insolita, спасибо, Вы сильно подтолкнули в нужном направлении.
Insolita писал(а):а у вас получается каталог неограниченной вложенности дерева? может оказаться в перспективе типа ? Как само дерево каталога строится - через NestedSet ?
Вложенность 4 максимум.

В БД сделал две таблицы:
catalog - тут все url каталога (url разделов и товаров)
goods - цена товара
Изображение
Изображение

CatalogUrlRule.php

Код: Выделить всё

<?php

namespace app\components;

use yii\web\UrlRule;
use app\models\Catalog;

class CatalogUrlRule extends UrlRule
{
    public $connectionID = 'db';

    public function init()
    {
        if ($this->name === null) {
            $this->name = __CLASS__;
        }
    }
    
    public function createUrl($manager, $route, $params)
    {
        if ($route === 'catalog/category') {
            $id = $params['id'];
            
            // Если id = 0, то это главный раздел каталога
            if($id == 0) {
                return 'catalog/';
            }
            else {
                
                $count = 1; // Делаем вложенность максимум 5, если вдруг пойдет цикл
                while($count < 5) {
                    
                    $catalog = Catalog::find()
                        ->where(['id' => $id])
                        ->one();
                    
                    $id = $catalog->parent;
                    
                    if($count == 1) {
                        $url = $catalog->url.$url;
                    }
                    else {
                        $url = $catalog->url.'/'.$url;
                    }
                    
                    if($catalog->parent == 0){
                        break;
                    }
                    
                    $count++;
                }
                
                
                
                return 'catalog/'.$url;
            }
        }
        return false;
    }
    
    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();
        
        $pattern = '%catalog\/([A-Za-z_\-0-9]+){0,1}(?:\/([A-Za-z_\-0-9]+)){0,1}(?:\/([A-Za-z_\-0-9]+)){0,1}(?:\/([A-Za-z_\-0-9]+)){0,1}(?:\/([A-Za-z_\-0-9]+))*%';
        if (preg_match($pattern, $pathInfo, $matches)) {
            
            $match_count = count($matches);
            
            $match_count--;
            
            if($match_count > 0) {
                
                // Проверяем категория с название соответствует ли родителю
                $category_now = Catalog::find()
                    ->where(['url' => $matches[$match_count]])
                    ->one();
                
                // Определяем тип страницы
                if($category_now->type === 0) {
                    $type = 'category';
                }
                elseif ($category_now->type === 1) {
                    $type = 'catalog';
                }
                elseif ($category_now->type === 2) {
                    $type = 'goods';
                }
                ;
                // Если родитель категории равен 0, нечего не проверяем
                // а если не равен проверяем соответствие с родителем
                if($category_now->parent === 0) {
                    return [ 'catalog/category' , ['id' => $category_now->id, 'type' => $type] ];
                }
                else {
                    
                    $parent_id_url = $match_count - 1; // порядковый номер в массиве, название родителя
                    
                    $category_parent = Catalog::find()
                        ->where(['id' => $category_now->parent])
                        ->one();
                    
                    if($category_parent->url == $matches[$parent_id_url]) {
                        // Родитель соответствует URL
                        return [ 'catalog/category' , ['id' => $category_now->id, 'type' => $type] ];
                    }
                    else {
                        // Родитель не соответствует
                        return false;
                    }
                    
                }
                
            }
            else {
                return [ 'catalog/category' , ['id' => 0, 'type' => 'category'] ];
            }
        }
        return false;
    }
}
CatalogController.php

Код: Выделить всё

<?php

namespace app\controllers;

use yii\web\Controller;
use app\models\Catalog;
use app\models\Goods;

class CatalogController extends Controller
{
    public function actionCategory($id, $type)
    {
        
        // Определяем какой тип вывода
        if ($type == 'category') {
            
            $catalog = Catalog::find()->where(['parent' => $id])->all();
            
            return $this->render('category', [
                'catalog' => $catalog,
            ]);
            
        }
        elseif ($type == 'catalog') {
            
            $catalog = Catalog::find()->where(['parent' => $id])->all();
            
            return $this->render('catalog', [
                'catalog' => $catalog,
            ]);
        }
        elseif ($type == 'goods') {
            
            $goods = Catalog::find()->where(['id' => $id])->one();
            
            return $this->render('goods', [
                'goods' => $goods,
            ]);
            
        }
        
        
    }
}
Результат можно посмотреть тут http://sfys.ru/catalog/ Логин/пасс: sfys/sfys
Далее буду "хлебные крошки делать"
Кто разбирается, выскажите свое мнение, плиз.
Аватара пользователя
Insolita
Сообщения: 788
Зарегистрирован: 2011.06.06, 01:39
Контактная информация:

Re: Создание каталога

Сообщение Insolita »

ох. В первую очередь курить на тему хранения деревьев в БД хотябы с http://habrahabr.ru/post/193166/ начать ... сначала нормально базу каталога спроектировать
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

Insolita писал(а):ох. В первую очередь курить на тему хранения деревьев в БД хотябы с http://habrahabr.ru/post/193166/ начать ... сначала нормально базу каталога спроектировать
Главный принцип состоит в том что бы связи хранить в отдельной таблице, это я понял. А какое главное преимущество в таком способе?
Есть предположение что это будет быстрее.
Аватара пользователя
Insolita
Сообщения: 788
Зарегистрирован: 2011.06.06, 01:39
Контактная информация:

Re: Создание каталога

Сообщение Insolita »

упс не ту ссылку ткнула http://habrahabr.ru/post/46659/ тут обзор основных способов
http://habrahabr.ru/post/47280/ и тут сравнение производительности...
ну и подробнее гугль в помощь...
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

А если не смотреть на структуру базы.
Сам понимаю, что рекурсия запросов в БД при составлении URLа, а что еще не правильно сделано?
Аватара пользователя
Insolita
Сообщения: 788
Зарегистрирован: 2011.06.06, 01:39
Контактная информация:

Re: Создание каталога

Сообщение Insolita »

составление урла желательно вынести в метод модели(так же это и для хлебных крошек пригодится), если сам каталог относительно небольшой и изменятся надо полагать будет гораздо реже чем запрашиваться - соответственно закешировать,

на структуру базы не смотреть очень трудно - как минимум надо добавить ''level'' уровня чтоб можно было вытаскивать одним запросом с джойном самой себя... это ведь при каждой генерации урла и при каждом тыке на ссылку запросы будут
типы предпочтительно задать константами в модели Catalog

Код: Выделить всё

const TYPE_CATALOG='catalog';
const TYPE_CATEGORY='category';
const TYPE_GOOD='goods';
 

Код: Выделить всё


public function actionCategory($id, $type)
    {
        if(!in_array($type,array(Catalog::TYPE_CATALOG,Catalog::TYPE_CATEGORY,Catalog::TYPE_GOOD))){
            throw new NotFoundHttpException('Нет такой страницы');
        }
            //..........        
    }
 

Код: Выделить всё

 
                $category_now = Catalog::find()
                    ->where(['url' => $matches[$match_count]])
                    ->one();
                if($category_now){
                   // Определяем тип страницы
                   и т.д.
                }
                
               

Код: Выделить всё

$match_count = count($matches)-1;
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

На данном этапе пока добавил level для генерации запросов с JOIN. И я встал в ступор.
Я так понимаю что есть несколько способов обращения в базу данных: ActiveRecord, генерация через Query Builder и прямой запрос в БД на чистом SQL.

ActiveRecord. Таким способом придется под каждую вложенность прописывать разные связи.

Query Builder. Так вроде легко прописать JOINы, но как сделать их генерацию.

Чистый SQL. Так я знаю как сделать, генерировал бы строки с JOINами (LEFT JOIN catalog c2 ON c1.parent = c2.id) и добавил в запрос. Но на чистом SQLе обращаться в базу в фреймворке считается плохим тоном.

Направьте, пожалуйста, в нужном направлении. :?
rak
Сообщения: 2181
Зарегистрирован: 2010.11.02, 23:40
Контактная информация:

Re: Создание каталога

Сообщение rak »

вообще автору стоило бы задуматься о том, что при такой структуре урлов перенос товара или категории влечет за собой 404.
а что касается деревьев - посоветовал бы использовать closure table
Аватара пользователя
evrej
Сообщения: 60
Зарегистрирован: 2014.12.19, 09:07

Re: Создание каталога

Сообщение evrej »

rak писал(а):вообще автору стоило бы задуматься о том, что при такой структуре урлов перенос товара или категории влечет за собой 404.
Да я это понимаю. Хотел сделать что бы по урлу можно было перемещаться в верх по каталогу. Типа: форд/фокус/коврики-в-салон/.
rak писал(а):а что касается деревьев - посоветовал бы использовать closure table
Т.е. сделать таблицу связей?
rak
Сообщения: 2181
Зарегистрирован: 2010.11.02, 23:40
Контактная информация:

Re: Создание каталога

Сообщение rak »

evrej писал(а): Т.е. сделать таблицу связей?
т.е. найти описание алгоритма и почитать :)
ну или поискать готовый behavior
andreyrud
Сообщения: 265
Зарегистрирован: 2011.09.26, 14:59

Re: Создание каталога

Сообщение andreyrud »

Тут есть еще один вопрос: как отображать товары?
Допустим, есть велосипед "вел1". Он одновременно и электрический и скоростной. Имеем дубль.
catalog/sport-tovary/velosipedy/elektro/вел1
catalog/sport-tovary/velosipedy/skorostnye/вел1
Аватара пользователя
Bethrezen
Сообщения: 42
Зарегистрирован: 2011.04.17, 15:06
Откуда: Tambov
Контактная информация:

Re: Создание каталога

Сообщение Bethrezen »

andreyrud писал(а):Тут есть еще один вопрос: как отображать товары?
Допустим, есть велосипед "вел1". Он одновременно и электрический и скоростной. Имеем дубль.
catalog/sport-tovary/velosipedy/elektro/вел1
catalog/sport-tovary/velosipedy/skorostnye/вел1
Определите для товара главную категорию - по ней и стройте всегда URL
DotPlant 2 - Open-source yii2 e-commerce CMS :: GitHub | Official site | Made by DevGroup
goodfriend
Сообщения: 50
Зарегистрирован: 2018.06.02, 09:58

Re: Создание каталога

Сообщение goodfriend »

А одним запросом какие из этих способов выбирают все дерево/поддерево? Знаю точно что MP ?
А Closure table? С ним кто то на yii2 работал, там несколько расширений и все 5-тилетней давности.
Аватара пользователя
maleks
Сообщения: 1992
Зарегистрирован: 2012.12.26, 12:56

Re: Создание каталога

Сообщение maleks »

goodfriend писал(а): 2019.06.29, 07:42 А одним запросом какие из этих способов выбирают все дерево/поддерево?
Да, materialized path выбирает. Nested sets тоже. Для вот этих категорий с головой хватит и первого способа, особенно когда дерево то небольшое.
Ответить