原文:http://www.symfony-project.com/askeet/13
回顾: 目前的askeet程序已具有网页服务,RRS和发电子邮件等功能。使用者能提问题,其问题也能被其他用户回答。但目前的askeet仍缺乏分类的机制。就拿对问题分类来说,我们可用树状结构对问题分门别类,最终生成几百个类别。这样会对查询造成极度的不便。
然而,web 2.0 引进了一种新的管理类别的方法:标签。与树状结构中的类别不同的是,标签不依赖等级来分类,而且一条目可拥有多标签。例如,猫,用树状结构分类可编排在条目 动物/哺乳动物/四脚的/猫科。用标签就简单多了,例如用 宠物+灵俐 来描述猫。其好处是所有的用户能为自己的问题分类(加标签),更易于查询。这就是大众分类法
... <table name="ask_question_tag" phpName="QuestionTag"> <column name="question_id" type="integer" primaryKey="true" /> <foreign-key foreignTable="ask_question"> <reference local="question_id" foreign="id" /> </foreign-key> <column name="user_id" type="integer" primaryKey="true" /> <foreign-key foreignTable="ask_user"> <reference local="user_id" foreign="id" /> </foreign-key> <column name="created_at" type="timestamp" /> <column name="tag" type="varchar" size="100" /> <column name="normalized_tag" type="varchar" size="100" primaryKey="true" /> <index name="normalized_tag_index"> <index-column name="normalized_tag" /> </index> </table>
$ symfony propel-build-model
<?php class Tag { public static function normalize($tag) { $n_tag = strtolower($tag); // remove all unwanted chars $n_tag = preg_replace('/[^a-zA-Z0-9]/', '', $n_tag); return trim($n_tag); } public static function splitPhrase($phrase) { $tags = array(); $phrase = trim($phrase); $words = preg_split('/(")/', $phrase, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $delim = 0; foreach ($words as $key => $word) { if ($word == '"') { $delim++; continue; } if (($delim % 2 == 1) && $words[$key - 1] == '"') { $tags[] = trim($word); } else { $tags = array_merge($tags, preg_split('/\s+/', trim($word), -1, PREG_SPLIT_NO_EMPTY)); } } return $tags; } } ?>
public function setTag($v) { parent::setTag($v); $this->setNormalizedTag(Tag::normalize($v)); }
QuestionTag:
t1: { question_id: q1, user_id: fabien, tag: relatives }
t2: { question_id: q1, user_id: fabien, tag: girl }
t4: { question_id: q1, user_id: francois, tag: activities }
t6: { question_id: q2, user_id: francois, tag: 'real life' }
t5: { question_id: q2, user_id: fabien, tag: relatives }
t5: { question_id: q2, user_id: fabien, tag: present }
t6: { question_id: q2, user_id: francois, tag: 'real life' }
t7: { question_id: q3, user_id: francois, tag: blog }
t8: { question_id: q3, user_id: francois, tag: activities }
$ php batch/load_data.php
$ symfony init-module frontend tag
public function getTags() { $c = new Criteria(); $c->clearSelectColumns(); $c->addSelectColumn(QuestionTagPeer::NORMALIZED_TAG); $c->add(QuestionTagPeer::QUESTION_ID, $this->getId()); $c->setDistinct(); $c->addAscendingOrderByColumn(QuestionTagPeer::NORMALIZED_TAG); $tags = array(); $rs = QuestionTagPeer::doSelectRS($c); while ($rs->next()) { $tags[] = $rs->getString(1); } return $tags; }
showSuccess:
components:
sidebar: [sidebar, question]
public function executeQuestion()
{
$this->question = QuestionPeer::getQuestionFromTitle($this->getRequestParameter('stripped_title'));
}
<?php include_partial('sidebar/default') ?> <h2>question tags</h2> <ul id="question_tags"> <?php include_partial('tag/question_tags', array('question' => $question, 'tags' => $question->getTags())) ?> </ul>
<?php foreach($tags as $tag): ?> <li><?php echo link_to($tag, '@tag?tag='.$tag, 'rel=tag') ?></li> <?php endforeach; ?>
tag:
url: /tag/:tag
param: { module: tag, action: show }
SELECT normalized_tag AS tag, COUNT(normalized_tag) AS count FROM question_tag WHERE question_id = $id GROUP BY normalized_tag ORDER BY count DESC LIMIT $max
public function getPopularTags($max = 5) { $tags = array(); $con = Propel::getConnection(); $query = ' SELECT %s AS tag, COUNT(%s) AS count FROM %s WHERE %s = ? GROUP BY %s ORDER BY count DESC '; $query = sprintf($query, QuestionTagPeer::NORMALIZED_TAG, QuestionTagPeer::NORMALIZED_TAG, QuestionTagPeer::TABLE_NAME, QuestionTagPeer::QUESTION_ID, QuestionTagPeer::NORMALIZED_TAG ); $stmt = $con->prepareStatement($query); $stmt->setInt(1, $this->getId()); $stmt->setLimit($max); $rs = $stmt->executeQuery(); while ($rs->next()) { $tags[$rs->getString('tag')] = $rs->getInt('count'); } return $tags; }
<?php use_helpers('Text', 'Date', 'Global', 'Question') ?> <?php foreach($question_pager->getResults() as $question): ?> <div class="question"> <div class="interested_block" id="block_<?php echo $question->getId() ?>"> <?php include_partial('question/interested_user', array('question' => $question)) ?> </div> <h2><?php echo link_to($question->getTitle(), '@question?stripped_title='.$question->getStrippedTitle()) ?></h2> <div class="question_body"> <div>asked by <?php echo link_to($question->getUser(), '@user_profile?nickname='.$question->getUser()->getNickname()) ?> on <?php echo format_date($question->getCreatedAt(), 'f') ?></div> <?php echo truncate_text(strip_tags($question->getHtmlBody()), 200) ?> </div> tags: <?php echo tags_for_question($question) ?> </div> <?php endforeach; ?> <div id="question_pager"> <?php echo pager_navigation($question_pager, $rule) ?> </div>
function tags_for_question($question, $max = 5) { $tags = array(); foreach ($question->getPopularTags($max) as $tag => $count) { $tags[] = link_to($tag, '@tag?tag='.$tag); } return implode(' + ', $tags); }
public function executeShow() { $this->question_pager = QuestionPeer::getPopularByTag($this->getRequestParameter('tag'), $this->getRequestParameter('page')); }
public static function getPopularByTag($tag, $page) { $c = new Criteria(); $c->add(QuestionTagPeer::NORMALIZED_TAG, $tag); $c->addDescendingOrderByColumn(QuestionPeer::INTERESTED_USERS); $c->addJoin(QuestionTagPeer::QUESTION_ID, QuestionPeer::ID, Criteria::LEFT_JOIN); $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max')); $pager->setCriteria($c); $pager->setPage($page); $pager->init(); return $pager; }
<h1>popular questions for tag "<?php echo $sf_params->get('tag') ?>"</h1> <?php include_partial('question/list', array('question_pager' => $question_pager, 'rule' => '@tag?tag=.'$sf_params->get(tag))) ?>
tag:
url: /tag/:tag/:page
param: { module: tag, action: show, page: 1 }