Организация ЧПУ для многоуровневого каталога на сайте?
Занялся вопросом изучения организации ЧПУ на сайте. В принципе все понятно и все ясно. Пишем директивы в .htaccess для того, чтобы все запросы переадресовывались на index.php (точка входа) и уже в index.php обрабатываем поступивший URL (если описывать очень просто).
URl вида /news/2 спомощью функций простейшей функции php разбиваем на массив строк: «news» и «2», с которыми и можем дальше работать. Казалось бы нет ничего проще. Наша программа по первой строке «news» определяет что это новость, а по второй строке «2» определяет идентификатор новости. Вызывается контроллер, отвечающий за вывод новостей, который в свою очередь выдергивает из БД из таблицы новостей нужную нам новость с id=2. Казалось бы все просто. Но не тут то было. Я никак не могу понять и не смог найти в просторах инета решения как быть в следующей ситуации: допустим на сайте есть многоуровневый каталог товаров. Т.к. каталог многоуровневый, то уровней может быть сколько угодно.
/catalog/menu1/menu1/menu2/product или /catalog/menu1/menu2/menu3/menu4/ menu5/product и т. д.
Получив такой URL, мы разбиваем его на массив строк. По первому элементу массива «catalog» мы понимаем, что это каталог и что нужно вызывать контроллер для каталога. Но есть одно «но»…
В случае с новостями мы четко знали что первый элемент «news» — это указание на раздел «новости» (контроллер «новости»), а второй элемент «2» — это указание на идентификатор конкретной новости из таблицы новостей. И структура урла для новостей строго фиксированная. Ну может не совсем фиксированная, может быть еще такой вариант /news (без id новости), но тут нет проблем, т.к. контроллер видит, что в урле есть первый элемент «news» и нет второго (идентификатора новости) и поэтому он понимает что нужно вывести шаблон не вывода конкретной новости, а шаблон вывода списка всех новостей, ну и соответственно выдернуть из базы все новости и вывести их в этот шаблон.
Но как быть с урлом для каталога, когда уровней может быть сколько угодно и фактически контроллер catalog не знает что находится на конце этого урла, т.е. это уже идентификатор конкретного товара (и нужно выводить товар) или же это очередное подменю и нужно выводить список товаров для этого подменю (или список еще одного уровня подменю)?
Например /catalog/la-la-la/tra-ta-ta/bum-bum/za-za – тут наш скрипт понимает, что это каталог, т.к. первый уровень урла на это указывает. Потом пошли разделы каталога la-la-la, tra-ta-ta и т.д. В конце стоит «za-za». И вот тут вопрос: za-za – это что? Это идентификатор очередного подменю или это уже идентификатор продукта. Что делать контроллеру, в какую таблицу лезть, за какой информацией?
Вот этот вопрос я для себя никак не могу прояснить. Может кто-нибудь подскажет какие бывают методики для работы с ЧПУ (желательно в системе MVC), решение этой проблемы.
Например, еще слышал такой подход: урл также разбивается на элементы и каждый элемент указывает на конкретную папку на сервере. Т.е. /catalog/la-la-la/tra-ta-ta/bum-bum/za-za – по сути является путем к директории где лежит контроллер. Но, я так понимаю, что в случае с многоуровневой структурой каталога (или другого какого раздела сайта) данный подход не очень корректен, т.к. не будешь же создавать на сервере папку для каждого подменю.
Была другая мысль: для каждой записи в БД хранить его урл. Например, для продукта «za-za» в таблице продуктов хранить его урл /catalog/la-la-la/tra-ta-ta/bum-bum/za-za. Казалось бы неплохое решение.
Мы в index.php берем наш урл, сравниваем его с урлами товаров из таблицы товаров и если находим продукт с таким урлом, то делаем вывод, что урл ведет к продукту и контроллер catalog выводит на экран шаблон для вывода продукта и в него выводит найденный продукт. Если же контроллер не находит в таблице товаров продукт с таким урлом, то ищет его в таблице разделов каталога и если находит его там, то делает вывод, что это не товар а раздел каталога и также выводит соответствующий шаблон и инфу в него.
Но такой подход тоже не выдерживает никакой критики, т.к. пусть невозможен один и тот же идентификатор у двух товаров или двух подменю, но ведь гипотетически возможна ситуация когда идентификатор какого-либо товара такой же как идентификатор какого либо меню. Или ситуация когда нужно отредактировать один из низжих разделов подменю (например меняем его идентификатор) и получается что у всех дочерних подменю и товаров в урлах в базе данных тоже нужно будет вычленять эти идентификаторы и менять на новые, короче говоря, не дело!
Предпочитаю страницы не-списки делать вида /{slug}.html
/catalog/la-la-la/tra-ta-ta/bum-bum/za-za.html
za-za — уникальный id товара. .html говорит о том, что это не категория, а сам товар.
/la-la-la/tra-ta-ta/bum-bum/ — можно игнорировать, но тогда у одного товара может быть несколько ссылок.
Ок, ну можно взять готовые компоненты, микрофреймворки (Silex, Fat Free, много чего еще есть), не обязательно свою систему маршрутизации писать.
А вообще проще всего сделать мэпинг — регэксп — действие в контроллере.
Я не силен в сео, и не знаю насколько принципиально использовать неограниченную вложенность разделов.
Например, для страницы, имеющей родителей делаю примерно вот так: /nazvanie-razdela-4c/stranica-97p
где: nazvanie-razdela — чпу раздела 4 — уникальный ID раздела в таблице c — идентификатор экшена для отображения раздела (задается в правилах маршрутизации) stranica — чпу страницы 97 — уникальный ID страницы в таблице p — идентификатор экшена для отображения страницы (задается в правилах маршрутизации)
Далее, если раздел с чпу nazvanie-razdela вложен в другой раздел, то адрес будет примерно таким: /roditelskiy-razdel-1c/nazvanie-razdela-4c
Т.е. я отображаю только только текущую страницу и одного ее предка.
Я делал так: www.site.com/catalogue/my_tovar.html
Где my_tovar – уникальный идентификатор товара, который задается при добавлении товара и заносится в базу.
Таким образом мы однозначно попадаем на страницу описания товара.
Сложнее вывести дерево меню слева, но зная id по идентификатору my_tovar, мы ищем полный путь (что вы называется tra-la-la/lala/) и показываем слева дерево меню с открытыми категориями, где искомая категория нашего товара подсвечена class='active'. Для меня именно эта операция была самой непростой, так как не хотелось делать рекурсию при построении сложного дерева.