【问题标题】:How to implement hierarchical cms sites?如何实现分层cms网站?
【发布时间】:2012-03-25 11:34:45
【问题描述】:

我正在编写一个小的学习 CMS 项目,但我遇到了阻碍我完成下一步的障碍。我知道我应该考虑 KISS(保持简单,愚蠢),但我认为能够对页面进行分层分组会很好。

问题是我希望页面[root]->fruits->tropical->bananas 只能从这个网址访问:http://localhost/cms/fruits/tropical/bananas/。到目前为止,我想出的是 cms 表有一个指向其父级的父级字段。问题是:如何以尽可能少的查询/有效地解析 uri 地址并从 DB 中选择一行?

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

欢迎所有帮助和建议。

【问题讨论】:

标签: php mysql database codeigniter content-management-system


【解决方案1】:

我特别喜欢 Bill Karwin (https://stackoverflow.com/users/20860/bill-karwin) 关于层次结构的演讲:

http://en.slideshare.net/billkarwin/models-for-hierarchical-data

Recursively grab all data based on a parent id

你应该看看他用闭包表的解决方案。我发现它特别有用。这可能是您需要的。

【讨论】:

    【解决方案2】:

    这是我用于测试的表结构 -

    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 行对此进行了测试,但对于 5 个 slug 路径,它仍然在不到 1ms 的时间内返回 - /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);
    

    【讨论】:

    • 这不如我的方法快。但是你能解释一下什么是 INNER JOIN 吗?我通常使用 LEFT JOIN 递归地查询相邻的列表模型。
    • 这实际上比您的方法快得多,因为它只会访问路径中包含的页面,而您的方法将访问表中的每个页面。实际上,除非我完全误解了他的问题,否则您的方法不会满足 OP 的要求。 I suggest you read this page if you do not know what an INNER JOIN is.
    • 我没有赞成你获得指向一个毫无意义的网站的指针。您可以自己描述 INNER JOIN 还是需要权威答案?我可以轻松地 Goggle 自己吗?你也使用 INNER JOIN,你有一些基准吗?
    • 我在回答中包含了一个基本基准。在层次结构中有 50k 页的情况下,检索 5 个 slug 路径所需的时间不到 1 毫秒。 INNER JOIN 需要左右表中的条目,而 LEFT JOIN 只需要左表中的数据。 MySQL 参考手册没有任何意义。
    • 谢谢。我的方法也显示了画树。您的方法只是访问相邻的列表模型。
    【解决方案3】:

    如果页面只存在于一个 url 上,解决问题的最简单方法是将完整 url 的哈希存储在它自己的索引字段中:

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

    虽然如果你打算沿着分层路线走,你可能会发现以下有用: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

    【讨论】:

    • 有趣的是,这些人是如何争论的,而你的 3 句话答案是其中最好的。散列是快速且独特的。如果页面移动,只需更新它。所有这些其他答案都很滑稽。
    • @bkconrad - 我同意这个散列解决方案很快(不是唯一的,但在这种情况下可能足够),但它不能返回路径中的所有节点。在层次结构中移动时,您还必须重新散列分支中的每个项目。您还必须存储冗余数据。
    【解决方案4】:

    您是否使用 codeigniter 进行此开发?下面的这个答案是基于 来自 codeigniter 的 Web 应用程序框架。就这样吧,


    问题是我希望页面[root]-&gt;fruits-&gt;tropical-&gt;bananas 只能从此 url 访问: 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地址 并以尽可能少的查询/尽可能高效地从数据库中选择一行?

    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();
    }
    

    【讨论】:

    • 你没有完全明白......存储在数据库中的每个页面也可以是n个更多页面的父级,并且有n个父级
    • 当你说我没有完全理解时,你指的是哪个部分?你能具体点吗?根据我从您的评论中了解到的情况,您认为我不明白的是如何在数据库中对页面(N 级页面)进行建模,从而从数据库中检索页面?
    【解决方案5】:

    您已将此问题标记为 CodeIgniter,因此这是特定于该问题的答案。

    您可以使用它的 routeing 功能来强制 URL,但以所需的方式处理请求。

    你会得到类似的东西:

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

    第一行会将所有以 cms/fruit 开头的 URL 转发给水果控制器,并将水果类型作为第一个变量传递(也许水果名称也作为第二个变量)。第二行是一个后备,以防它没有处理水果名称。

    将此与配置中的基本路径结合起来,您也可以在 URL 中自动设置“cms”,如果它应该始终在 URL 中。

    【讨论】:

      猜你喜欢
      • 2011-04-25
      • 2011-03-20
      • 1970-01-01
      • 1970-01-01
      • 2011-05-17
      • 2010-09-28
      • 2011-12-16
      • 2023-03-25
      • 1970-01-01
      相关资源
      最近更新 更多