第十三天标签

原文:http://www.symfony-project.com/askeet/13

回顾: 目前的askeet程序已具有网页服务,RRS和发电子邮件等功能。使用者能提问题,其问题也能被其他用户回答。但目前的askeet仍缺乏分类的机制。就拿对问题分类来说,我们可用树状结构对问题分门别类,最终生成几百个类别。这样会对查询造成极度的不便。

然而,web 2.0 引进了一种新的管理类别的方法:标签。与树状结构中的类别不同的是,标签不依赖等级来分类,而且一条目可拥有多标签。例如,猫,用树状结构分类可编排在条目 动物/哺乳动物/四脚的/猫科。用标签就简单多了,例如用 宠物+灵俐 来描述猫。其好处是所有的用户能为自己的问题分类(加标签),更易于查询。这就是大众分类法

QuestionTag类

Schema update

...
<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

Custom class

<?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;
  }
}
 
?>

Extend the model

public function setTag($v)
{
  parent::setTag($v);
 
  $this->setNormalizedTag(Tag::normalize($v));
}

Add some test data

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

Display the tags of a question

$ symfony init-module frontend tag

Extend model

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

Modify the view

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 }

Test it

http://askeet/question/what-can-i-offer-to-my-step-mother

Display a short list of popular tags for a question

Extend the model

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

Modify the view

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

Test

http://askeet/

Display the list of questions tagged with a word

The tag/show action

public function executeShow()
{
  $this->question_pager = QuestionPeer::getPopularByTag($this->getRequestParameter('tag'), $this->getRequestParameter('page'));
}

Extend the model

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

Create the template

<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))) ?>

Add the page parameter in the routing rule

tag:
  url:   /tag/:tag/:page
  param: { module: tag, action: show, page: 1 }

Test it

http://askeet/tag/activities

See you Tomorrow

 
askeet_13.txt · 上一次变更: 2007/03/21 18:00 通过 root
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki