review askeet Day 5

ログインフォームを作る

まずは、フォームへのリンクを作る。

場所: askeet2/apps/frontend/templates/layout.php

    <li><?php echo link_to('サインイン','user/login') ?></li>

投稿は日本語が受け付けられませんが、表示類は極力日本語にしてみます。

Userモジュールを作る
symfony init-module frontend user

これでaskeet2/apps/frontend/modules/userができます。
モジュールスケルトンというらしいです。
要らないモノもできちゃうので削除します。

svn rm apps/frontend/modules/user/templates/indexSuccess.php

次に、アクションを書きます。
場所: askeet2/apps/frontend/modules/user/actions/actions.class.php

<?php

/**
 * user actions.
 *
 * @package    askeet
 * @subpackage user
 * @author     Your name here
 * @version    SVN: $Id: actions.class.php 2692 2006-11-15 21:03:55Z fabien $
 */
class userActions extends sfActions
{

  public function executeLogin()
  {
    $this->getRequest()->setAttribute('referer',$this->getRequest()->getReferer());

    return sfView::SUCCESS;
  }
}

ついでにexecuteIndexは削除しておく。

ログインフォームのテンプレートを作る。

場所: askeet2/apps/frontend/modules/user/templates/loginSuccess.php

<?php echo form_tag('user/login') ?>

  <fieldset>
  
  <div class="form-row">
     <label for="nickname">ニックネーム:</label>
     <?php echo input_tag('nickname',$sf_params->get('nickname')) ?>
  </div>

  <div class="form-row">
     <label for="password">パスワード:</label>
     <?php echo input_password_tag('password') ?>
  </div>

  </fieldset>

  <?php echo input_hidden_tag('referer',$sf_request->getAttribute('referer')) ?>
  <?php echo submit_tag('サインイン') ?>

</form>
アクションを投稿できるように書き直す。

場所: askeet2/apps/frontend/modules/user/actions/actions.class.php

  public function executeLogin()
  {

    if($this->getRequest()->getMethod() != sfRequest::POST)
      {
        // フォームを表示する
        $this->getRequest()->setAttribute('referer',$this->getRequest()->getReferer());
      }
    else
      {
        // フォーム投稿を処理する
        $nickname = $this->getRequestParameter('nickname');

        $c = new Criteria();
        $c->add(UserPeer::NICKNAME,$nickname);
        $user = UserPeer::doSelectOne($c);

        // nicknameが存在するか?
        if($user)
          {
            // passwordが合っているか?
            if(true)
              {
                $this->getUser()->setAuthenticated(true);
                $this->getUser()->addCredential('subscriber');

                $this->getUser()->setAttribute('subscriber_id',$user->getId(),'subscriber');
                $this->getUser()->setAttribute('nickname',$user->getNickname(),'subscriber');

                // 最後のページにリダイレクトする
                return $this->redirect($this->getRequestParameter('referer','@homepage'));
              }
          }
      }
  }
ここで押さえておきたい。
種類 違い
request属性 $this->getRequest()->setAttribute() データを所持するのはリファラーに送信するまで
session属性 $this->getUser()->setAttribute() セッションとして所持される(他のアクションが使える)

参考:http://symfony.xrea.jp/1.2/book/02-Exploring-Symfony-s-Code.html#parameter.holders

ログアウトを作る

ログインを作ったらログアウトも作らなきゃということで作ります。
場所: askeet2/apps/frontend/modules/user/actions/actions.class.php

  public function executeLogout()
  {
    $this->getUser()->setAuthenticated(false);
    $this->getUser()->clearCredentials();

    // subscriberを削除
    $this->getUser()->getAttributeHolder()->removeNamespace('subscriber');

    // トップページにリダイレクト
    $this->redirect('@homepage');
  }
ログイン後とログイン前のメニュー部分を変える

現状だとログイン後もログインメニューが出てしまっているので
ログイン後はログアウトとか出るようにする。
場所: askeet2/apps/frontend/modules/user/templates/layout.php

<div id="header">
  <ul>
<?php if($sf_user->isAuthenticated()): ?>
    <li><?php echo link_to('サインアウト','user/logout') ?></li>
    <li><?php echo link_to($sf_user->getAttribute('nickname','','subscriber').'のプロフィール','user/profile') ?></li>
<?php else: ?>
    <li><?php echo link_to('サインイン/ユーザー登録','user/login') ?></li>
<?php endif ?>
    <li><?php echo link_to('トップページ','@homepage') ?></li>
  </ul>
  <h1><?php echo link_to(image_tag('askeet_logo.gif','alt=askeet'),'@homepage') ?></h1>
</div>

▼ログイン前▼

▼ログイン後▼

$sf_user->isAuthenticatedにデータを抱えているときはログイン状態ということですね。
なるほど。

ページャーを作る

表示件数が多すぎると見づらいのでページャを付けようって話ですね。

まずはアクションを書く。

場所: askeet2/apps/frontend/modules/question/actions/actions.class.php

  public function executeList()
  {
    // ページャー
    $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));

    $c = new Criteria();
    $c->addDescendingOrderByColumn(QuestionPeer::INTERESTED_USERS);

    $pager->setCriteria($c);
    $pager->setPage($this->getRequestParameter('page', 1));
    $pager->setPeerMethod('doSelectJoinUser');
    $pager->init();

    $this->question_pager = $pager;
  }
カスタムパラメータを使用する

sfConfig::get('app_pager_homepage_max')は
場所: askeet2/apps/frontend/config/app.ymlで設定できます。
app.ymlのpagerのhomepage_maxを呼び出している。なるほど。

# default values
all:
  pager:
    homepage_max: 2
テンプレートを修正する

あとは表示部を変えるだけですね。
場所: askeet2/apps/frontend/modules/question/templates/listSuccess.php

<?php use_helper('Text') ?>

<h1>人気の質問</h1>

<?php foreach($question_pager->getResults() as $question): ?>

  <div class="question">
    <div class="interested_block">
      <?php include_partial('interested_user',array('question' => $question)) ?>
    </div>

    <h2><?php echo link_to($question->getTitle(),'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>

    <div class="question_body">
   <?php echo truncate_text($question->getBody(),200) ?>
    </div>

  </div>
<?php endforeach; ?>

<div id="question_pager">
<?php if($question_pager->haveToPaginate()): ?>
   <?php echo link_to('&laquo;','question/list?page=1') ?>
   <?php echo link_to('&lt;','question/list?page='.$question_pager->getPreviousPage()) ?>

   <?php foreach($question_pager->getLinks() as $page): ?>
     <?php echo link_to_unless($page == $question_pager->getPage(),$page,'question/list?page='.$page) ?>
     <?php echo ($page != $question_pager->getCurrentMaxLink()) ? '-' : '' ?>
   <?php endforeach; ?>

   <?php echo link_to('&gt;','question/list?page='.$question_pager->getNextPage()) ?>
   <?php echo link_to('&raquo;','question/list?page='.$question_pager->getLastPage()) ?>

<?php endif; ?>
</div>


お、できてる><

ルーティングルール

場所: askeet2/apps/frontend/config/routing.yml

homepage:
  url:   /
  param: { module: question, action: list }

login:
  url:   /login
  param: { module: user, action: login }

これでアドレスがすっきりした。

リファスタリング

Questionのアクションに書いたページャーに関するコードは
モデルと密接な関係があるのでモデルに移動します。

呼び出す元を書き換える

場所: askeet2/apps/frontend/modules/question/actions/actions.class.php

  public function executeList()
  {
    $this->question_pager = QuestionPeer::getHomepagePager($this->getRequestParameter('page', 1));
  }
呼び出す側を書く

場所: askeet2/lib/model/QuestionPeer.php

<?php

/**
 * Subclass for performing query and update operations on the 'ask_question' table.
 *
 * 
 *
 * @package lib.model
 */ 
class QuestionPeer extends BaseQuestionPeer
{

  public static function getHomepagePager($page)
  {
    // ページャー
    $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));

    $c = new Criteria();
    $c->addDescendingOrderByColumn(self::INTERESTED_USERS);

    $pager->setCriteria($c);
    $pager->setPage($page);
    $pager->setPeerMethod('doSelectJoinUser');
    $pager->init();

    return $pager;
  }

}
showにも応用

まずは、呼び出す側を書き換える。
場所: askeet2/apps/frontend/modules/question/actions/actions.class.php

  public function executeShow()
  {
    $this->question = QuestionPeer::getQuestionFromTitle($this->getRequestParameter('stripped_title'));

    $this->forward404Unless($this->question);
  }

呼び出される側に追加する。
場所: askeet2/lib/model/QuestionPeer.php

  public static function getQuestionFromTitle($title)
  {
    $c = new Criteria();
    $c->add(QuestionPeer::STRIPPED_TITLE,$title);
    return self::doSelectOne($c);
  }
テンプレートも簡単に。

呼び出し部分はこんな感じに。
場所: askeet2/apps/question/templates/listSuccess.php

<?php use_helper('Text') ?>

<h1>人気の質問</h1>

<?php echo include_partial('list',array('question_pager' => $question_pager)) ?>

呼び出される側を書く。
場所: askeet2/apps/question/templates/_list.php

<?php foreach($question_pager->getResults() as $question): ?>

  <div class="question">
    <div class="interested_block">
      <?php include_partial('interested_user',array('question' => $question)) ?>
    </div>

    <h2><?php echo link_to($question->getTitle(),'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>

    <div class="question_body">
   <?php echo truncate_text($question->getBody(),200) ?>
    </div>

  </div>
<?php endforeach; ?>

<div id="question_pager">
<?php if($question_pager->haveToPaginate()): ?>
   <?php echo link_to('&laquo;','question/list?page=1') ?>
   <?php echo link_to('&lt;','question/list?page='.$question_pager->getPreviousPage()) ?>

   <?php foreach($question_pager->getLinks() as $page): ?>
     <?php echo link_to_unless($page == $question_pager->getPage(),$page,'question/list?page='.$page) ?>
     <?php echo ($page != $question_pager->getCurrentMaxLink()) ? '-' : '' ?>
   <?php endforeach; ?>

   <?php echo link_to('&gt;','question/list?page='.$question_pager->getNextPage()) ?>
   <?php echo link_to('&raquo;','question/list?page='.$question_pager->getLastPage()) ?>

<?php endif; ?>
</div>