А требований, собственно, не так уж и много:
- поддержка вложенности страниц,
- возможность управления и редактирования страниц из админки,
- быстрая, без многочисленных запросов к базе данных, проверка существования страницы по запрашиваемому адресу, а также быстрая генерация URL страницы.
Поскольку описанные требования, предъявляются к функционалу, необходимому для большинства создающихся сайтов, имеет смысл оформить его в виде модуля, и в будущем просто копировать последний от проекта к проекту.
Вся наша система будет вращается вокруг простого массива, назовём его картой путей. Каждый элемент массива характеризует отдельную страницу. В качестве индексов массива используются первичные ключи (далее ID) страниц в базе, а в качестве значений — пути до соотвествующих страниц.
Таким образом, задача заключается в написании кода, который должен:
- в процессе разбора URL производить поиск страницы (её ID) по запрашиваемому пути, и при положительном исходе выдавать страницу пользователю.
- при создании URL проверять существование элемента с индексом, равным ID страницы, на которую создаётся ссылка, и если такой элемент существует, возвращать путь до этой страницы.
- разумеется всё должно кешироваться, а кеш при изменениях в иерархии страниц обновляться.
Итак, приступим. Начнём с создания таблицы для хранения страниц. SQL-запрос для этого выглядит следующим образом:
CREATE TABLE IF NOT EXISTS `pages` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `root` int(10) unsigned NOT NULL, `lft` int(10) unsigned NOT NULL, `rgt` int(10) unsigned NOT NULL, `level` int(10) unsigned NOT NULL, `parent_id` int(10) unsigned NOT NULL, `slug` varchar(127) NOT NULL, `layout` varchar(15) DEFAULT NULL, `is_published` tinyint(1) unsigned NOT NULL DEFAULT '0', `page_title` varchar(255) NOT NULL, `content` text NOT NULL, `meta_title` varchar(255) NOT NULL, `meta_description` varchar(255) NOT NULL, `meta_keywords` varchar(255) NOT NULL, PRIMARY KEY (`id`) );
Как видно из запроса, для построения иерархической структуры используется метод хранения деревьев «вложенные множества», поэтому при дописании административной части модуля, будет иметь смысл использовать расширение Nested Set Behavior.
Далее, с помощью Gii, генерируем каркас модуля (назовём его pages), а также модель для работы с только что созданной таблицей (её назовём Page).
Поправим код созданного модуля. Добавим атрибут $cacheId, в котором будет храниться идентификатор для кешированной карты путей.
При инициализации модуля должна происходить проверка, существует ли в кеше карта путей, и если она там отсутствует, должна генерироваться актуальная на момент вызова карта. Для этого дописываем функцию init().
Также добавляем три метода: генерирующий, обновляющий и возвращающий карту путей. Итого, код модуля принимает следующий вид:
class PagesModule extends CWebModule { /** * @var string идентификатор, по которому доступна закешированная карта путей */ public $cacheId = 'pagesPathsMap'; public function init() { if (Yii::app()->cache->get($this->cacheId) === FALSE) $this->updatePathsMap(); $this->setImport(array( 'pages.models.*', 'pages.components.*', )); } /** * Возвращает карту путей из кеша. * @return mixed */ public function getPathsMap() { return Yii::app()->cache->get($this->cacheId); } /** * Сохраняет в кеш актуальную на момент вызова карту путей. * @return void */ public function updatePathsMap() { Yii::app()->cache->set($this->cacheId, $this->generatePathsMap()); } /** * Генерация карты страниц. * @return array ID узла => путь до узла */ public function generatePathsMap() { $nodes = Yii::app()->db->createCommand() ->select('id, level, slug') ->from('pages') ->order('root, lft') ->queryAll(); $pathsMap = array(); $depths = array(); foreach ($nodes as $node) { if ($node['level'] > 1) $path = $depths[$node['level'] - 1]; else $path = ''; $path .= $node['slug']; $depths[$node['level']] = $path . '/'; $pathsMap[$node['id']] = $path; } return $pathsMap; } }
На этом с классом модуля мы закончили, не забудьте дать знать о нём приложению, дописав идентификатор модуля в свойство modules массива конфигурации.
Теперь создадим класс правила PagesUrlRule, унаследованный от CBaseUrlRule. В нём достаточно объявить всего два метода: для создания и для разбора URL. Код метода для создания URL выглядит следующим образом:
public function createUrl($manager, $route, $params, $ampersand) { $pathsMap = Yii::app()->getModule('pages')->getPathsMap(); if ($route === 'pages/default/view' && isset($params['id'], $pathsMap[$params['id']])) return $pathsMap[$params['id']] . $manager->urlSuffix; else return false; }
В методе производится проверка существования страницы в карте путей, и при нахождении возвращается путь к ней (не забываем про URL-суффикс! — люблю чтобы адреса оканчивались слешем).
Код метода для разбора URL (здесь наоборот, производится поиск ID страницы по пути к ней):
public function parseUrl($manager, $request, $pathInfo, $rawPathInfo) { $pathsMap = Yii::app()->getModule('pages')->getPathsMap(); $id = array_search($pathInfo, $pathsMap); if ($id === false) return false; $_GET['id'] = $id; return 'pages/default/view'; }
Не забудьте добавить запись со ссылкой на класс правила в конфигурационный массив. Ну и раз уж мы возвращаем здесь ссылку на контроллер default, не лишним будет привести его код.
class DefaultController extends Controller { public function actionView($id) { $page = $this->loadModel($id); $this->render('view', array( 'page' => $page, )); } public function loadModel($id) { $model = Page::model()->published()->findByPk($id); if ($model === null) throw new CHttpException(404, 'Запрашиваемая страница не существует.'); return $model; } }
Собственно, всё. Функционал для разбора, создания URL, и вывода страниц посетителю готов. А реализацию функционала управления страницами (он вполне стандартен), если есть желание, можете посмотреть в готовом проекте, который можно загрузить отсюда.
ссылка на оригинал статьи http://habrahabr.ru/post/155927/
Добавить комментарий