2012-03-07 63 views
1

我正在編寫一個有點學習的CMS項目,我碰到了一堵阻止我完成下一步的磚牆。我知道我應該把KISS(保持簡單,愚蠢)考慮進去,但我認爲這很好,能夠將頁面分層次分組。如何實現分層cms站點?

問題是我想要頁面[root]->fruits->tropical->bananas只能從這個URL訪問:http://localhost/cms/fruits/tropical/bananas/。到目前爲止我想到的是,cms表有一個指向其父項的父字段。問題是:如何解析uri地址,並儘可能選擇儘可能少的查詢來從數據庫中選擇一行?

Table structure: 
Id 
Slug 
... 
... 
... 
ParentId 

所有的幫助和建議是友好的接受。

+4

http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ – 2012-03-07 22:29:26

+1

http://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hierarchical-數據在關係數據庫 – frail 2012-03-14 12:23:25

回答

4

這裏是我用來測試這個表格的結構 -

CREATE TABLE `test`.`pages` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `slug` varchar(45) NOT NULL, 
    `title` varchar(45) NOT NULL, 
    `content` text NOT NULL, 
    `parent_id` int(10) unsigned DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `UQ_page_parent_id_slug` (`parent_id`,`slug`), 
    CONSTRAINT `FK_page_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `pages` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

請注意(parent_id,slug)上的唯一鍵。這是從以下查詢中獲得最佳性能的關鍵。我用50K行測試這一點,它仍然在不到1毫秒的五段塞路徑返回 - /cms/slug-1/slug-2/slug-3/slug-4/slug-5/

這裏是我想出了建立一個合適的查詢的PHP代碼 -

<?php 

// I will assume the rest of the url has already been stripped away 
$url = '/fruits/tropical/bananas/'; 

// lets just make sure we don't have any leading or trailing/
$url = trim($url, '/'); 

// now let's split the remaining string based on the/
$aUrl = explode('/', $url); 

/** 
* Now let's build the query to retrieve this 
*/ 

// this array stores the values to be bound to the query at the end 
$aParams = array(); 

$field_list = 'SELECT p1.* '; 
$tables = 'FROM pages p1 '; 
$where = "WHERE p1.parent_id IS NULL AND p1.slug = ? "; 

// this array stores the values to be bound to the query at the end 
$aParams[] = $aUrl[0]; 

// if we have more than one element in our array we need to add to the query 
$count = count($aUrl); 

for ($i = 1; $i < $count; $i++) { 

    // add another table to our query 
    $table_alias = 'p' . ($i + 1); 
    $prev_table_alias = 'p' . $i; 
    $tables .= "INNER JOIN pages $table_alias ON {$prev_table_alias}.id = {$table_alias}.parent_id "; 

    // add to where clause 
    $where .= "AND {$table_alias}.slug = ? "; 
    $aParams[] = $aUrl[$i]; 

    // overwrite the content of $field_list each time so we 
    // only retrieve the data for the actual page requested 
    $field_list = "SELECT {$table_alias}.* "; 

} 

$sql = $field_list . $tables . $where; 

$result = $this->db->query($sql, $aParams); 
+0

這並不像我的方法那麼快。但是,你能解釋一下什麼是INNER JOIN?我通常使用LEFT JOIN以遞歸方式查詢相鄰列表模型。 – Bytemain 2012-03-17 07:57:23

+0

這實際上比您的方法快得多,因爲它只會訪問路徑中包含的頁面,而您的方法將訪問表中的每個頁面。事實上,除非我完全誤解了他的問題,否則你的方法不會做OP所要求的。 [如果你不知道什麼是INNER JOIN,我建議你閱讀這個頁面(http://dev.mysql.com/doc/refman/5.0/en/join.html)。 – nnichols 2012-03-17 10:42:39

+0

我沒有贊成你得到一個指向無感站點的指針。你可以自己描述一個INNER JOIN嗎?或者你需要一個授權答案嗎?我可以輕鬆地自己擋住眼睛?你也使用INNER JOIN,你有一些基準嗎? – Bytemain 2012-03-17 11:05:21

0

您已將此問題標記爲CodeIgniter,因此這是一個特定的答案。

您可以使用它的routeing功能強制URL,但以所需的方式處理請求。

什麼,你本來就是這樣的:

$route['cms/fruit/(:any)'] = 'fruit/$1'; 
$route['cms/fruit/(:any)/(:any)'] = 'fruit/$1/$2'; 

第一行會轉發與CMS /水果開始水果控制器的所有URL和整個水果型傳輸作爲第一個變量(也許是水果名字作爲第二個變量)。第二行是後退,以防它不注意水果名稱。

將此結合到配置中的基本路徑中,並且您也可以自動在URL中設置'cms',如果它始終應該在URL中。

0

你對這個開發使用codeigniter嗎?以下答案基於來自codeigniter的Web應用程序框架。所以這裏去,


的問題是,我想[root]->fruits->tropical->bananas頁面 是隻能訪問此網址: http://localhost/cms/fruits/tropical/bananas/

那麼在你的控制器中創建一個函數名爲fruits和兩個參數?例如

class Cms extends CI_Controller { 
    ... 
    ... 
    ... 
    public function __construct() { 
    $this->load->model('cms_model'); 
    } 

    public function fruits($tropical, $bananas) { 

    $string = $this->cms_model->getPage($tropical, $bananas); 

    // load the view you want. 
    $this->load->view(''); 
    } 
    ... 
    ... 
    ... 

} 

我想出了到現在爲止是CMS表有一個指向它的父父場 。問題是:如何解析uri地址 並儘可能選擇儘可能少的查詢從DB中選擇一行?

Table cms: 
Id 
Slug 
ParentId 

Table cms_parent: 
Id 

讓我們從上面所示,CMS表和cms父表的兩個示例表描述。你沒有在你的問題中指定你的查詢想要的結果還是查詢結果的返回結果。所以下面是我的猜測基於你的問題描述,即通過使用公共密鑰連接它們來查詢兩個表,然後應用條件。

// select * from cms t1 join cms_parent t2 on t1.ParentId = t2.Id where t1.Id = '' and t2.ParentId = 'level1'; 
public function getPage($level0, $level1) { 
    $this->db->select('*'); 
    $this->db->from('cms'); 
    $this->db->join('cms_parent', 'cms.ParentId = cms_parent.Id'); 
    $this->db->where('cms.Id', $level0); 
    $this->db->where('cms.ParentId', $level1); 

    $query = $this->db->get(); 

    // return one row from database. 
    return $query->row(); 
} 
+0

你不完全得到它...存儲在數據庫中的每一頁也可以是一個家長n更多的頁面,並有n級父母 – JanL 2012-03-14 20:17:28

+0

當你說我沒有得到它完全,**你指的是哪一部分?你能具體嗎?根據我的理解,你認爲我不明白如何在數據庫中對頁面進行建模(N級頁面),從而從數據庫中檢索頁面? – Jasonw 2012-03-15 01:10:52

3

如果頁面只有一個鏈接的存在是爲了解決問題將是存儲的完整URL的散列在它自己的索引字段最簡單的方法:

SELECT * FROM table WHERE page = MD5('http://localhost/cms/fruits/tropical/bananas/')

雖然如果你打算沿着等級路線走,你可能會發現以下有用的東西: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

+0

有趣的是,這些人是如何爭論的,而你的三句話答案是最好的。散列是快速和獨特的。只要頁面移動就更新它。所有這些其他答案都是滑稽的。 – bkconrad 2012-03-18 08:16:18

+2

@bkconrad - 我同意這個哈希解決方案是快速的(不是唯一的,但在這種情況下可能已經足夠了),但它不能返回路徑中的所有節點。在層次結構中移動時,您也有必要重新分派分支中的每個項目。您還必須存儲冗餘數據。 – nnichols 2012-03-18 11:19:30