復習
昨日のチュートリアルにおいて、フォークソノミー機能の一部を構築しました。QuestionTag
クラスとモデルへの他の拡張機能は質問リストと詳細な質問内容で質問タグを表示することを手助けしてくれました。加えて、任意のタグのための人気のある質問のリストも開発されました。
タグに関して行うことが2つあり、これらは'web 2.0'のように聞こえるでしょう: AJAXフォームで新しいタグを追加する機能と、askeetのグローバルなタグバブルです。symfonyのアジャイルな開発方法を経験する準備はよろしいですか?
質問にタグを追加する
フォーム
登録ユーザーーに質問にタグを追加する能力を付与だけでなく、ユーザーーが入力する最初の文字列と一致する前に、他の質問で与えられたタグの1つを提示したいです。これはオートコンプリート(自動入力補完)と呼ばれています。google suggestで遊んだことがあるのでしたら、おわかり頂けるでしょう。
昨日、私たちは質問の詳細が表示されたときにサイドバーに挿入されたフラグメントを作成しました。最後にこのフォームを追加するためにaskeet/apps/frontend/modules/sidebar/templates/_question.php
ファイルを編集します:
... <?php if ($sf_user->isAuthenticated()): ?> <div>Add your own: <?php echo form_remote_tag(array( 'url' => '@tag_add', 'update' => 'question_tags', )) ?> <?php echo input_hidden_tag('question_id', $question->getId()) ?> <?php echo input_auto_complete_tag('tag', '', 'tag/autocomplete', 'autocomplete=off', 'use_style=true') ?> <?php echo submit_tag('Tag') ?> </form> </div> <?php endif; ?>
もちろん、ユーザーにタグをリンクしなければならないので、新しいタグの追加は認証ユーザーに制限されます。form_remote_tag()
ヘルパーについて一分ほど話すことにします。しかし、最初は、オートコンプリートのinput
タグを見てください。これはマッチするオプションの配列を取得するアクション(ここでは、tag/autocomplete
)を指定します。
オートコンプリート
アクションが返すリストはユーザーによって入力されたタグリストです。このタグリストは重複することなく、アルファベット順に並べられ、tag
フォールドのエントリとマッチします。次のようなSQLクエリが返されます:
SELECT DISTINCT tag AS tag FROM question_tag WHERE user_id = $id AND tag LIKE $entry ORDER BY tag
このアクションをmpdules/tag/acitons/action.class.php
ファイルに追加します:
public function executeAutocomplete() { $this->tags = QuestionTagPeer::getTagsForUserLike($this->getUser()->getSubscriberId(), $this->getRequestParameter('tag'), 10); }
通常は、データベースのクエリの中心はモデルにあります。QuestionTagPeer
クラスに次のメソッドを追加します:
public static function getTagsForUserLike($user_id, $tag, $max = 10) { $tags = array(); $con = Propel::getConnection(); $query = ' SELECT DISTINCT %s AS tag FROM %s WHERE %s = ? AND %s LIKE ? ORDER BY %s '; $query = sprintf($query, QuestionTagPeer::TAG, QuestionTagPeer::TABLE_NAME, QuestionTagPeer::USER_ID, QuestionTagPeer::TAG, QuestionTagPeer::TAG ); $stmt = $con->prepareStatement($query); $stmt->setInt(1, $user_id); $stmt->setString(2, $tag.'%'); $stmt->setLimit($max); $rs = $stmt->executeQuery(); while ($rs->next()) { $tags[] = $rs->getString('tag'); } return $tags; }
アクションがタグリストを決定するので、必要なのはautocompleteSuccess.php
テンプレートでこれらを形作ることだけです:
<ul> <?php foreach ($tags as $tag): ?> <li><?php echo $tag ?></li> <?php endforeach; ?> </ul>
新しいrouting.yml
ルートを追加します(_question.php
パーシャルのinput_auto_complet_tag()
呼び出しでmodule/action
の代わりにこれを使います):
tag_autocomplete: url: /tag_autocomplete param: { module: tag, action: autocomplete }
そしてview.yml
を設定します:
autocompleteSuccess: has_layout: off components: []
前に進んで、試せます。既存のアカウントで登録した後に(たとえばfabpot/symofny)、質問を表示し、サイドバーの新しいフィールドに注目してください。このユーザーによってすでに付与されたタグの最初の文字列を入力してください(たとえば、relatives)そして、適切なエントリを提示するフィールドの下で表示されるdivを見てください。
リモートフォーム
フォームが投稿されたとき、全ページをリフレッシュする必要はありません。タグのリストとタグを追加するフォームだけをリフレッシュする必要があります。それがform_remote_tag()
ヘルパーの目的です。フォームが投稿されたとき(tag/add
)に呼び出されるアクションおよびこのアクション('question_tags'によって指定される要素)の結果によって更新されるページの領域を指定します。これは8日目に質問を追加するAJAXフォームで説明しました。
tag
アクションでexecuteAdd()
メソッドを作りましょう:
public function executeAdd() { $this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('question_id')); $this->forward404Unless($this->question); $userId = $this->getUser()->getSubscriberId(); $phrase = $this->getRequestParameter('tag'); $this->question->addTagsForUser($phrase, $userId); $this->tags = $this->question->getTags(); }
そしてQuestion
クラスにaddTagsForUser
です:
public function addTagsForUser($phrase, $userId) { // フレーズを個別のタグに分割する $tags = Tag::splitPhrase($phrase); // タグを追加する foreach ($tags as $tag) { $questionTag = new QuestionTag(); $questionTag->setQuestionId($this->getId()); $questionTag->setUserId($userId); $questionTag->setTag($tag); $questionTag->save(); } }
addSuccess.php
テンプレートはupdate
の領域を置き換えるコードを決定します。通常はAJAXアクションを伴い、シンプルなinclude_partial()
を含みます:
<?php include_partial('tag/question_tags', array('question' => $question, 'tags' => $tags)) ?>
新しいrouting.yml
ルートを追加します:
tag_add: url: /tag_add param: { module: tag, action: add }
view.yml
を設定します:
addSuccess: has_layout: off components: []
テストする
試してください: サイトにログインする、質問の詳細を表示する、新しいタグを入力し投稿する。全体のリストを更新し、アルファベット順で新しいタグの挿入されます。
タグバブルを表示する
フォークソノミーは人気によってタグを評価します。しかしたくさんのタグはタグのリストを見ることを難しくします。もっとも満足する解決方法は、視覚的に言えば、人気に応じて、タグの言葉のサイズを増やすことです。もっとも人気のあるタグ -多くのユーザーによって付与されます- が即座に表示されるようにします。タグバブルとは何かということを理解するにはdel.icio.usの人気タグページを確認してください。
Webサイトへの80%の訪問者は20%以下の内容にしか興味を示しません。それは毎日多くのWebサイトで確認されるルールで、おそらくaskeetでも違いはないでしょう。そこでaskeetがタグのリストを提示するとしたら、同じように人気によって並べ替える必要があります。もっと不人気なタグ('grandma' 'chocolate')の小さな不統一を制限するため、およびもっとも人気のあるタグ('php' 'real life' 'usefule')の認知度を向上させるためです。
QuestionTagPeerクラスを拡張する
人気タグのリストの提供クラスは'QuesitonTagPeer'以外にはありえません。新しいメソッドで拡張すると、SQLクエリの代わりを書くことを経験します:
public static function getPopularTags($max = 5) { $tags = array(); $con = Propel::getConnection(); $query = ' SELECT '.QuestionTagPeer::NORMALIZED_TAG.' AS tag, COUNT('.QuestionTagPeer::NORMALIZED_TAG.') AS count FROM '.QuestionTagPeer::TABLE_NAME.' GROUP BY '.QuestionTagPeer::NORMALIZED_TAG.' ORDER BY count DESC'; $stmt = $con->prepareStatement($query); $stmt->setLimit($max); $rs = $stmt->executeQuery(); $max_popularity = 0; while ($rs->next()) { if (!$max_popularity) { $max_popularity = $rs->getInt('count'); } $tags[$rs->getString('tag')] = floor(($rs->getInt('count') / $max_popularity * 3) + 1); } ksort($tags); return $tags; }
人気度の数値を4の度合いまで制限します。そうしないとタグクラウドが読めなくなるからです。メソッドの結果はタグの名前と人気度の連想配列です。表示する準備ができました。
タグバブルを表示する
tag
モジュールでpopular
アクションを作ります:
public function executePopular() { $this->tags = QuestionTagPeer::getPopularTags(sfConfig::get('app_tag_cloud_max')); }
アクションと同じぐらいシンプルなpopularSuccess.php
テンプレートです:
<h1>popular tags</h1> <ul id="tag_cloud"> <?php foreach($tags as $tag => $count): ?> <li class="tag_popularity_<?php echo $count ?>"><?php echo link_to($tag, '@tag?tag='.$tag, 'rel=tag') ?></li> <?php endforeach; ?> </ul>
新しいアクションのためにrouting.yml
設定ファイルでルーティングルールを追加することをお忘れなく:
popular_tags: url: /popular_tags param: { module: tag, action: popular }
アプリケーションのapp.yml
にapp_tag_cloud_max
パラメータを追加します:
all: tag: cloud_max: 40
すべての準備が整いました: リクエストしてタグクラウドを表示してください。
http://askeet/popular_tags
タグリストの項目を表す
しかし、クラウドはどこにあるのでしょうか?Web標準で推奨されるように、本当の造形はスタイルシートによって行われます。main.css
スタイルシート(askeet/web/css
に設置)に次の宣言を追加します。
ul#tag_cloud { list-style: none; } ul#tag_cloud li { list-style: none; display: inline; } ul#tag_cloud li.tag_popularity_1 { font-size: 60%; } ul#tag_cloud li.tag_popularity_2 { font-size: 100%; } ul#tag_cloud li.tag_popularity_3 { font-size: 130%; } ul#tag_cloud li.tag_popularity_4 { font-size: 160%; }
人気タグのページをリフレッシュして、ほら!
それではまた明日
symfonyのサイトにタクソノミーを追加するのは大したことではありません。複雑なリクエスト、オートコンプリート、フォーム投稿後のローカルページのリフレッシュには数行のコードだけが必要です。
しかし、アプリケーションを簡単に開発できることで開発のよい原則を忘れてしまうことはありませんし、行った変更は常にテストすべきです。素早く開発してこまめにリファクタリングするための最良のツールはユニットテストです。ユニットテストは最新のコンピュータプログラミングにおけるもっとも偉大な進歩です。それらの内容は明日説明します。
それまでは、askeetのメーリングリストで21日目の提案を投稿できます。これまでのアプリケーションのソースコード全体をダウンロードしたければ、/tags/release_day_14
とタグづけされたaskeetのSVNリポジトリにアクセスしてください。
This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.