Работа со статическими страницами в Yii

от автора

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

А требований, собственно, не так уж и много:

  • поддержка вложенности страниц,
  • возможность управления и редактирования страниц из админки,
  • быстрая, без многочисленных запросов к базе данных, проверка существования страницы по запрашиваемому адресу, а также быстрая генерация 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *