В этой статье я хочу рассмотреть написание базового функционала для работы со статическими страницами. Задача кажется довольно банальной, но если нам требуется вложенность страниц, она, надо признать, усложняется. В этой статье я хочу предложить простое решение для такой задачи, которое, как мне кажется, может покрыть большинство требований к статическим страницам, выдвигаемых небольшими веб-сайтами.
А требований, собственно, не так уж и много:
- поддержка вложенности страниц,
- возможность управления и редактирования страниц из админки,
- быстрая, без многочисленных запросов к базе данных, проверка существования страницы по запрашиваемому адресу, а также быстрая генерация 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, и вывода страниц посетителю готов. А реализацию функционала управления страницами (он вполне стандартен), если есть желание, можете посмотреть в готовом проекте, который можно загрузить отсюда.
Автор: mayton
Обновите, пожалуйста, ссылку на архив