In this article I want to consider writing basic functionality for working with static pages. The task seems rather trivial, but if we need nesting of pages, it must be admitted that it becomes more complicated. In this article I want to offer a simple solution for such a task, which, I think, can cover most of the requirements for static pages put forward by small websites.
And, actually, there are not so many requirements:
- support for nesting pages
- ability to manage and edit pages from admin panel,
- fast, without numerous requests to the database, checking the existence of the page at the requested address, as well as quick generation of the URL of the page.
Since the described requirements are imposed on the functionality necessary for most of the created sites, it makes sense to arrange it as a module, and in the future just copy the latter from project to project.
Our entire system will revolve around a simple array, called its
map of paths . Each element of the array characterizes a separate page. The primary keys (further ID) of the pages in the database are used as array indices, and the paths to the corresponding pages are used as values.
')
Thus, the task is to write code that should:
- in the process of parsing the URL to search for the page (its ID) on the requested path, and with a positive outcome, give the page to the user.
- when creating a URL, check for the existence of an element with an index equal to the ID of the page being linked to, and if such an element exists, return the path to this page.
- Of course, everything should be cached, and the cache, when changes in the hierarchy of pages are updated.
So let's get started. Let's start by creating a table for storing pages. The SQL query for this is as follows:
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`), KEY `root` (`root`), KEY `lft` (`lft`), KEY `rgt` (`rgt`), KEY `level` (`level`) );
As can be seen from the request, the method of storing “nested sets” trees is used to build the hierarchical structure; therefore, when adding the administrative part of the module, it will make sense to use the
Nested Set Behavior extension.
Next, using Gii, we generate the module framework (let's call it pages), as well as the model for working with the newly created table (we will call it Page).
Let's correct the code of the created module. Add the $ cacheId attribute to store the identifier for the cached path map.
During module initialization, a check should be made to see if there is a path map in the cache, and if it is absent there, a map that is up to date should be generated. To do this, we add the function init ().
We also add three methods: generating, updating and returning a map of paths. Total, the module code takes the following form:
class PagesModule extends CWebModule { public $cacheId = 'pagesPathsMap'; public function init() { if (Yii::app()->cache->get($this->cacheId) === false) $this->updatePathsMap(); $this->setImport(array( 'pages.models.*', 'pages.components.*', )); } public function getPathsMap() { $pathsMap = Yii::app()->cache->get($this->cacheId); return $pathsMap === false ? $this->generatePathsMap() : $pathsMap; } public function updatePathsMap() { Yii::app()->cache->set($this->cacheId, $this->generatePathsMap()); } 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; } }
We have finished with this module class; don't forget to let the application know about it by adding the module identifier to the modules property of the configuration array.
Now create the PagesUrlRule rule class inherited from CBaseUrlRule. It is enough to declare only two methods: to create and to parse the URL. The code for creating the URL looks like this:
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; }
The method checks the existence of the page in the map of paths, and when it finds it returns the path to it (do not forget about the URL suffix! - I like the addresses to end with a slash).
Method code to parse the URL (here, on the contrary, the page ID is searched along the path to it)
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'; }
Do not forget to add an entry with reference to the rule class in the configuration array. Well, since we are returning here a link to the default controller, it is useful to provide its code.
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; } }
Actually, everything. The functionality for parsing, creating a URL, and displaying pages to a visitor is ready. And the implementation of the page management functionality (it is quite standard), if you wish, you can look at the finished project, which can be downloaded
from here .
UPD1. Fixed work with the cache, added indexes to the table, updated link to the file with the finished project.