学習日記59日目

スタートアップ研修記はこちらです。

どうも、enomotoです。
今日から一旦進めるのを休んで復習していきたいと思います。

symfony メモ

パーミッションの変更

symfony fix-perms

review askeet Day 1

symfonyインストールは終了しているので省略。プロジェクトのセットアップから行います。

フォルダの作成

今回は/Users/shota/education/enomoto/askeet2/に作ります。

apacheの設定

順番は逆ですが、ブラウザ上でも確認しながらやりたいのでApacheの設定から始めます。
場所: /opt/local/apache2/conf/httpd.conf

NameVirtualHost 127.0.0.1

<VirtualHost 127.0.0.1>
  ServerName askeet.localhost
  DocumentRoot "/Users/shota/education/askeet/web"
  DirectoryIndex index.php
  Alias /sf /opt/local/lib/php/data/symfony/web/sf
  
  <Directory "/Users/shota/education/askeet/web">
    AllowOverride All
    Allow From All
  </Directory>
  <Directory "/opt/local/lib/php/data/symfony/web/sf">
    AllowOverride All
    Allow From All
  </Directory>
</VirtualHost>

<VirtualHost 127.0.0.1>
  ServerName review.askeet.localhost
  DocumentRoot "/Users/shota/education/enomoto/askeet2/web"
  DirectoryIndex index.php
  Alias /sf /opt/local/lib/php/data/symfony/web/sf

  <Directory "/Users/shota/education/enomoto/askeet2/web">
    AllowOverride All
    Allow From All
  </Directory>
  <Directory "/opt/local/lib/php/data/symfony/web/sf">
    AllowOverride All
    Allow From All
  </Directory>
</VirtualHost>
/etc/hostsの書換

忘れていたので早速修正。

127.0.0.1	askeet.localhost
127.0.0.1	review.askeet.localhost
ブラウザで確認。


よし、問題なく設定ができたぞ。

プロジェクトの作成

問題なくApacheの設定ができたみたいなのでsymfonyのプロジェクトの作成をする

symfony init-project askeet

これで基本となるものが入るわけですね。

.htaccessのせいか、表示が変わったぞ。
index.phpがないよという404メッセージになった。

フロントエンドアプリケーションを入れる。

index.phpがないなら入れればいい。
ということでfrontendを入れることに。

symfony init-app frontend


よし、できた。
ついでにfrontend_dev.php(開発環境)も確認。

これも、問題なし。

subversion(svn)の設定

まずはcacheとlogの中身を空にします。
rm -rf cache/*
rm -rf log/*
作業フォルダaskeet2をaddします。
svn add askeet2
次に、cacheとlogはsvnのコミット対象外にします。
svn propedit svn:ignore cache --editor-cmd emacs
svn propedit svn:ignore log --editor-cmd emacs
cacheとlogのフォルダのパーミッションを777にする。
chmod 777 log
chmod 777 cache

全部終わったら1日目終了

コミットします。

svn up // まずは最新にして
svn st // 対象を確認
svn ci -m "[review askeet Day 1] 終了" // ""内はコメント

review askeet Day 2

DBでまず、何が必要かを考える。
askeetというのはデモサイトを見た限り、日本で言うところのOKwaveやY!知恵袋みたいなもの。
ということで必要なものを考えると

  • question (質問)
  • answer (答え)
  • user (ユーザー)
  • relevancy (妥当性)

となる…みたい。

schema.ymlを書く。

ということでデータベース設計を書きたいと思います。
前回、askeet通りにしたらえらいことになったのでschema.ymlを書きます。
場所: askeet2/config/schema.yml

propel:
  _attributes:      { noXsd: false, defaultIdMethod: none, package: lib.model }

  ask_question:
    _attributes:    { phpName: Question, idMethod: native }
    id:             { type: integer, required: true, primaryKey: true, autoIncrement: true }
    user_id:        { type: integer, foreignTable: ask_user, foreignReference: id }
    title:          { type: longvarchar }
    body:           { type: longvarchar }
    created_at:     ~
    updated_at:     ~

  ask_answer:
    _attributes:    { phpName: Answer, idMethod: native }
    id:             { type: integer, required: true, primaryKey: true, autoIncrement: true }
    question_id:    { type: integer, foreignTable: ask_question, foreignReference: id }
    user_id:        { type: integer, foreignTable: ask_user, foreignReference: id }
    body:           { type: longvarchar }
    created_at:     ~

  ask_user:
    _attributes:    { phpName: User, idMethod: native }
    id:             { type: integer, required: true, primaryKey: true, autoIncrement: true }
    nickname:       { type: varchar(50), required: true, index: true }
    first_name:     varchar(100)
    last_name:      varchar(100)
    created_at:     ~

  ask_interest:
    _attributes:    { phpName: Interest, idMethod: native }
    question_id:    { type: integer, foreignTable: ask_question, foreignReference: id, primaryKey: true }
    user_id:        { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }

  ask_relevancy:
    _attributes:    { phpName: Relevancy, idMethod: native }
    answer_id:      { type: integer, foreignTable: ask_answer, foreignReference: id, primaryKey: true }
    user_id:        { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
    score:          { type: integer }
    created_at:     ~

書いているうちにうっすらどういうことか分かってきた感じがします。

MySQLとPropelの設定

InnoDBに対応するように書き換える。ついでにaskeet2というデータベースを使うように設定。

場所: askeet2/config/propel.ini

propel.database.url        = mysql://root@localhost/askeet2
propel.mysql.tableType     = InnoDB
MySQLにaskeet2というデータベースを作る。
mysqladmin5 -uroot -p create askeet2
databases.ymlを設定する。

前回はここではまったので忘れないように。
場所: askeet/config/databases.yml

all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://root@localhost/askeet2
Propelモデルを作る
symfony propel-build-model
SQL文を作る
symfony propel-build-sql
MySQLSQL文を流す
symfony propel-insert-sql

これは便利だなーと思ったところ。

Questionアプリを作る

symfony propel-generate-crud frontend question Question

これはPropelモデルを元に勝手に作ってくれる。
できたら

symfony cc

キャッシュを削除する。

確認してみよう。


おーできてる><
何度、見てもいいですね。
ということで2日目も復習終了。

review askeet Day 3

frontendのレイアウトを変更する。

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>

<?php include_http_metas() ?>
<?php include_metas() ?>

<?php include_title() ?>

<link rel="shortcut icon" href="/favicon.ico" />

</head>
<body>

<div id="header">
  <ul>
    <li><?php echo link_to('about','@homepage') ?></li>
  </ul>
  <h1><?php echo link_to(image_tag('askeet_logo.gif','alt=askeet'),'@homepage') ?></h1>
</div>

<div id="content">
  <div id="content_main">
    <?php echo $sf_data->getRaw('sf_content') ?>
    <div class="verticalalign"></div>
  </div>

  <div id="content_bar">
    <!-- Nothing for the moment -->
    <div class="verticalalign"></div>
  </div>
</div>

</body>
</html>
書き換えたらCSSを拾いに行く。

http://svn.askeet.com/tags/release_day_3/web/css/
ここからダウンロードする。askeet2/web/cssに移動して
main.cssを一度削除してからダウンロードする。

cd web/css
rm main.css
wget http://svn.askeet.com/tags/release_day_3/web/css/main.css
wget http://svn.askeet.com/tags/release_day_3/web/css/layout.css
layout.cssも表示できるようにする。

現状ではmain.cssしか当てられていないのでおかしい表示になっている。

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

  stylesheets:    [main, layout]

これでlayout.cssも読み込めるように。

これで問題なし><
画像がないのは今回はそのまま放置。

トップページの変更

現在はsymfonyのCongratulations画面なので
questionをトップページにします。
場所: askeet2/apps/frontend/config/routing.yml

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

テストデータを用意する。

そのままテストデータを使ってもおもしろくないので多少弄る。
場所: askeet2/data/fixtures/test_data.yml

User:
  anonymous:
    nickname:   anonymous
    first_name: Anonymous
    last_name:  Coward

  crazyup:
    nickname:   crazyup
    first_name: Shota
    last_name:  Enomoto

  dinotaro:
    nickname:   dino
    first_name: Taro
    last_name:  Dino

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: crazyup
    body:  |
      We shall meet in front of the Dunkin'Donuts before dinner, 
      and I haven't the slightest idea of what I can do with her. 
      She's not interested in programming, space opera movies nor insects.
      She's kinda cute, so I really need to find something 
      that will keep her to my side for another evening.

  q2:
    title: What can I offer to my step mother?
    user_id: anonymous
    body:  |
      My stepmother has everything a stepmother is usually offered
      (watch, vacuum cleaner, earrings, del.icio.us account). 
      Her birthday comes next week, I am broke, and I know that 
      if I don't offer her something sweet, my girlfriend 
      won't look at me in the eyes for another month.

  q3:
    title: How can I generate traffic to my blog?
    user_id: dinotaro
    body:  |
      I have a very swell blog that talks 
      about my class and mates and pets and favorite movies.

Interest:
  i1: { user_id: crazyup, question_id: q1 }
  i2: { user_id: dinotaro, question_id: q1 }
  i3: { user_id: dinotaro, question_id: q2 }
  i4: { user_id: crazyup, question_id: q2 }

テストデータを入れるためのバッチを作る

場所: askeet2/batch/load_data.php

<?php

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'frontend');
define('SF_ENVIRONMENT', 'dev');
define('SF_DEBUG',       true);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.
             SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

// データベースマネージャーを初期化する
$databaseManager = new sfDatabaseManager();
$databaseManager->initialize();

// fixturesにあるすべてのファイルを読み込みDBにデータを追加
$data = new sfPropelData();
$data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');

あとはこいつをコマンドラインから叩けばテストデータがDBへ。

php batch/load_data.php


入ってる><

テーブルからdivに変更

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

<?php use_helper('Text') ?>

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

<?php foreach($questions as $question): ?>

  <div class="question">
    <div class="interested_block">
      <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
   <?php echo count($question->getInterests()) ?>
      </div>
    </div>

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

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

  </div>
<?php endforeach; ?>

link_toはaタグとかを書く必要が無くてすごく便利。
truncate_textはgetBodyを200文字に切り詰めて表示していると。

おーそれっぽい><

使わないコードやファイルを削除する。

あとあと実装するので邪魔なのは削除しておく。
まずはquestionのアクションのお掃除
場所: askeet2/apps/frontend/modules/question/actions/actions.class.php

<?php

/**
 * question actions.
 *
 * @package    askeet
 * @subpackage question
 * @author     Your name here
 * @version    SVN: $Id: actions.class.php 3335 2007-01-23 16:19:56Z fabien $
 */
class questionActions extends sfActions
{

  public function executeList()
  {
    $this->questions = QuestionPeer::doSelect(new Criteria());
  }

  public function executeShow()
  {
    $this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('id'));
    $this->forward404Unless($this->question);
  }

}

あとはeditSuccess.phpを削除

svn rm apps/frontend/modules/question/templates/editSuccess.php

review askeet Day 4

詳細画面をtableからdiv化する。

このテーブルでできたサイトを直す。

場所: askeet2/apps/frontend/modules/question/templates/showSuccess.php

<?php use_helper('Date') ?>
 
<div class="interested_block">
  <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
    <?php echo count($question->getInterests()) ?>
  </div>
</div>
 
<h2><?php echo $question->getTitle() ?></h2>
 
<div class="question_body">
  <?php echo $question->getBody() ?>
</div>
 
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>

  <div class="answer">

    posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?> 
    on <?php echo format_date($answer->getCreatedAt(), 'p') ?>

    <div>
      <?php echo $answer->getBody() ?>
    </div>

  </div>

<?php endforeach; ?>
</div>


うん、変わった。

テストデータの追加

答えと妥当さのデータをテストデータに入れる。
場所: askeet2/data/fixtures/test_data.yml

User:
  anonymous:
    nickname:   anonymous
    first_name: Anonymous
    last_name:  Coward

  crazyup:
    nickname:   crazyup
    first_name: Shota
    last_name:  Enomoto

  dinotaro:
    nickname:   dino
    first_name: Taro
    last_name:  Dino

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: crazyup
    body:  |
      We shall meet in front of the Dunkin'Donuts before dinner, 
      and I haven't the slightest idea of what I can do with her. 
      She's not interested in programming, space opera movies nor insects.
      She's kinda cute, so I really need to find something 
      that will keep her to my side for another evening.

  q2:
    title: What can I offer to my step mother?
    user_id: anonymous
    body:  |
      My stepmother has everything a stepmother is usually offered
      (watch, vacuum cleaner, earrings, del.icio.us account). 
      Her birthday comes next week, I am broke, and I know that 
      if I don't offer her something sweet, my girlfriend 
      won't look at me in the eyes for another month.

  q3:
    title: How can I generate traffic to my blog?
    user_id: dinotaro
    body:  |
      I have a very swell blog that talks 
      about my class and mates and pets and favorite movies.

Interest:
  i1: { user_id: crazyup, question_id: q1 }
  i2: { user_id: dinotaro, question_id: q1 }
  i3: { user_id: dinotaro, question_id: q2 }
  i4: { user_id: crazyup, question_id: q2 }

Answer:
  a1_q1:
    question_id: q1
    user_id:     dinotaro
    body:        |
      You can try to read her poetry. Chicks love that kind of things.

  a2_q1:
    question_id: q1
    user_id:     crazyup
    body:        |
      Don't bring her to a donuts shop. Ever. Girls don't like to be
      seen eating with their fingers - although it's nice. 

  a3_q2:
    question_id: q2
    user_id:     crazyup
    body:        |
      The answer is in the question: buy her a step, so she can 
      get some exercise and be grateful for the weight she will
      lose.

  a4_q3:
    question_id: q3
    user_id:     crazyup
    body:        |
      Build it with symfony - and people will love it.

で、

php batch/load_data.php

確認する。

データが表示されている。

モデルの変更

まずはいちいちgetUserの中のgetFirstNameとか呼び出さなくても
getUserすれば苗字+名前が出るようにする。
現在、askeet2/lib/model/User.phpは空っぽなので
そこに__toStringで書く。

  public function __toString()
  {
    return $this->getFirstName().' '.$this->getLastName();
  }

あとはテンプレートを直すだけ。
さっそく、askeet2/apps/frontend/modules/question/templates/showSuccess.phpを直す。

    posted by <?php echo $answer->getUser() ?> 

同じことは繰り返さない

listSuccess.phpとshowSuccess.phpには同じコードがある。
同じことは繰り返さないということに反するのでフラグメントにする。
場所: askeet2/apps/frontend/modules/question/templates/_interested_user.php

  <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
    <?php echo count($question->getInterests()) ?>
  </div>

↑が書かれた部分を↓のコードに置き換える。
場所: askeet2/apps/frontend/modules/question/templates/listSuccess.php
場所: askeet2/apps/frontend/modules/question/templates/showSuccess.php

   <?php include_partial('interested_user',array('question' => $question)) ?>

にすると共通化ができる。これで同じところを何度も直す必要がなくなる。

オブジェクトモデルに項目を追加

場所: askeet2/config/schema.yml

  ask_question:
    _attributes:      { phpName: Question, idMethod: native }
    id:               { type: integer, required: true, primaryKey: true, autoIncrement: true }
    user_id:          { type: integer, foreignTable: ask_user, foreignReference: id }
    title:            { type: longvarchar }
    body:             { type: longvarchar }
    interested_users: { type: integer, default: 0 }
    created_at:       ~
    updated_at:       ~
モデルの再設定
symfony propel-build-model

モデルを再構築したらSQLを作ってMySQLに流す。
その後にテストデータを再びMySQLに流す。

symfony propel-build-sql
symfony propel-insert-sql
php batch/load_data.php

関心の更新をできるようにする

InnoDBなのでトランザクションを使えるように書きます。
場所: askeet2/lib/model/Interest.php

<?php

/**
 * Subclass for representing a row from the 'ask_interest' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Interest extends BaseInterest
{

  public function save($con = null)
  {
    $con= Propel::getConnection();
    try
      {
        $con->begin();

        $ret = parent::save($con);

        // interested_usersを更新
        $question = $this->getQuestion();
        $interested_users = $question->getInterestedUsers();
        $question->setInterestedUsers($interested_users + 1);
        $question->save($con);

        $con->commit();

        return $ret;
      }
    catch (Exception $e)
      {
        throw $e;
      }
  }

}
interested_userパーシャルの更新

場所: askeet2/apps/frontend/modules/question/templates/_interested_user.php

  <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
    <?php echo $question->getInterestedUsers() ?>
  </div>

修正したら、テストデータを更新する。

php batch/load_data.php

回答の投票システム

まずはschema.ymlに追加。
場所: askeet2/config/schema.yml

  ask_answer:
    _attributes:      { phpName: Answer, idMethod: native }
    id:               { type: integer, required: true, primaryKey: true, autoIncrement: true }
    question_id:      { type: integer, foreignTable: ask_question, foreignReference: id }
    user_id:          { type: integer, foreignTable: ask_user, foreignReference: id }
    body:             { type: longvarchar }
    relevancy_up:     { type: integer, default: 0 }
    relevancy_down:   { type: integer, default: 0 }
    created_at:       ~
できたら、モデルを再設定してDB更新
symfony propel-build-model
symfony propel-build-sql
symfony propel-insert-sql
Revancyクラスのsaveを書き換える

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

<?php

/**
 * Subclass for representing a row from the 'ask_relevancy' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Relevancy extends BaseRelevancy
{

  public function save($con = null)
  {
    $con = Propel::getConnection();
    try
      {
        $con->begin();

        $ret = parent::save();

        // relevancy(妥当性)を更新する
        $answer = $this->getAnswer();
        if($this->getScore() == 1)
          {
            $answer->setRelevancyUp($answer->getRelevancyUp() + 1);
          }
        else
          {
            $answer->setRelevancyDown($answer->getRelevancyDown() + 1);
          }
        $answer->save($con);

        $con->commit();

        return $ret;
      }
    catch(Exception $e)
      {
        $con->rollback();
        throw $e;
      }
  }
}
Answerクラスにメソッドを追加

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

<?php

/**
 * Subclass for representing a row from the 'ask_answer' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Answer extends BaseAnswer
{

  public function getRelevancyUpPercent()
  {
    $total = $this->getRelevancyUp() + $this->getRelevancyDown();

    return $total ? sprintf('$.0f',$this->getRelevancyUp() * 100 / $total) : 0;

  }

  public function getRelevancyDownPercent()
  {
    $total = $this->getRelevancyUp() + $this->getRelevancyDown();

    return $total ? sprintf('%.0f',$this->getRelevancyDown() * 100 / $total) : 0;
  }

}
テンプレートの変更

場所: askeet2/apps/frontend/modules/question/templates/showSuccess.php

<?php use_helper('Date') ?>
 
<div class="interested_block">
   <?php include_partial('interested_user',array('question' => $question)) ?>
</div>
 
<h2><?php echo $question->getTitle() ?></h2>
 
<div class="question_body">
  <?php echo $question->getBody() ?>
</div>
 
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>

  <div class="answer">

    <?php echo $answer->getRelevancyUpPercent() ?>% UP 
    <?php echo $answer->getRelevancyDownPercent() ?>% DOWN

    posted by <?php echo $answer->getUser() ?> 
    on <?php echo format_date($answer->getCreatedAt(), 'p') ?>

    <div>
      <?php echo $answer->getBody() ?>
    </div>

  </div>

<?php endforeach; ?>
</div>
テストデータに追加する。

場所: askeet2/data/fixtures/test_data.yml

User:
  anonymous:
    nickname:   anonymous
    first_name: Anonymous
    last_name:  Coward

  crazyup:
    nickname:   crazyup
    first_name: Shota
    last_name:  Enomoto

  dinotaro:
    nickname:   dino
    first_name: Taro
    last_name:  Dino

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: crazyup
    body:  |
      We shall meet in front of the Dunkin'Donuts before dinner, 
      and I haven't the slightest idea of what I can do with her. 
      She's not interested in programming, space opera movies nor insects.
      She's kinda cute, so I really need to find something 
      that will keep her to my side for another evening.

  q2:
    title: What can I offer to my step mother?
    user_id: anonymous
    body:  |
      My stepmother has everything a stepmother is usually offered
      (watch, vacuum cleaner, earrings, del.icio.us account). 
      Her birthday comes next week, I am broke, and I know that 
      if I don't offer her something sweet, my girlfriend 
      won't look at me in the eyes for another month.

  q3:
    title: How can I generate traffic to my blog?
    user_id: dinotaro
    body:  |
      I have a very swell blog that talks 
      about my class and mates and pets and favorite movies.

Interest:
  i1: { user_id: crazyup, question_id: q1 }
  i2: { user_id: dinotaro, question_id: q1 }
  i3: { user_id: dinotaro, question_id: q2 }
  i4: { user_id: crazyup, question_id: q2 }

Answer:
  a1_q1:
    question_id: q1
    user_id:     dinotaro
    body:        |
      You can try to read her poetry. Chicks love that kind of things.

  a2_q1:
    question_id: q1
    user_id:     crazyup
    body:        |
      Don't bring her to a donuts shop. Ever. Girls don't like to be
      seen eating with their fingers - although it's nice. 

  a3_q2:
    question_id: q2
    user_id:     crazyup
    body:        |
      The answer is in the question: buy her a step, so she can 
      get some exercise and be grateful for the weight she will
      lose.

  a4_q3:
    question_id: q3
    user_id:     crazyup
    body:        |
      Build it with symfony - and people will love it.

Relevancy:
  rel1:
    answer_id: a1_q1
    user_id:   crazyup
    score:     1

  rel2:
    answer_id: a1_q1
    user_id:   dinotaro
    score:     -1

書いたらバッチを走らせる。

php batch/load_data.php

ルーティング

http://review.askeet.localhost/question/show/id/1

http://review.askeet.localhost/question/what-shall-i-do-tonight-with-my-girlfriend
↑のようにするためにはQuestionテーブルにwhat-shall-i-do-tonight-with-my-girlfriendを保存すればよい。

と、言うことでschema.ymlを書こう。

場所: askeet2/config/schema.yml

  ask_question:
    _attributes:      { phpName: Question, idMethod: native }
    id:               { type: integer, required: true, primaryKey: true, autoIncrement: true }
    user_id:          { type: integer, foreignTable: ask_user, foreignReference: id }
    title:            { type: longvarchar }
    body:             { type: longvarchar }
    interested_users: { type: integer, default: 0 }
    stripped_title:   varchar(255)
    _uniques:
      unique_stripped_title: [stripped_title]
    created_at:       ~
    updated_at:       ~
モデルを書いたら再設定とデータベースを更新。
symfony propel-build-model
symfony propel-build-sql
symfony propel-insert-sql
タイトルから生成する。

場所: askeet2/lib/myTools.class.php

<?php

class myTools
{

  public static function stripText($text)
  {

    $text = strtolower($text);

    //単語以外をはぎ取る
    $text = preg_replace('/\W/',' ',$text);
    //空白文字をハイフンに置き換える
    $text = preg_replace('/\ +/','-',$text);
    //ハイフンをトリムする
    $text = preg_replace('/\-$/','',$text);
    $text = preg_replace('/^\-/','',$text);

    return $text;

  }

}
Questionクラスを書く

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

<?php

/**
 * Subclass for representing a row from the 'ask_question' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Question extends BaseQuestion
{

  public function setTitle($v)
  {

    parent::setTitle($v);
    $this->setStrippedTitle(myTools::stripText($v));

  }

}
テストデータのリロード

キャッシュも捨てておきます。

symfony cc
php batch/load_data.php
テンプレートの書き換え

リンク部分を書き換える
場所: askeet2/apps/frontend/modules/question/templates/listSuccess.php

    <h2><?php echo link_to($question->getTitle(),'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>
Questionのshowアクションを直す。

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

  public function executeShow()
  {
    $c = new Criteria();
    $c->add(QuestionPeer::STRIPPED_TITLE,$this->getRequestParameter('stripped_title'));
    $this->question = QuestionPeer::doSelectOne($c);

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


ただ、今の状態だと
http://review.askeet.localhost/question/show/stripped_title/what-shall-i-do-tonight-with-my-girlfriend
となる。
/show/stripped_title/は要らない。

だったら、ルーティングルールを変えればいい。

ということで変更。追加するところは一番最初。
場所: askeet2/apps/frontend/config/routing.yml

question:
  url:   /question/:stripped_title
  param: { module: question, action: show }


できた><