Symfony第九天--局部改进

 回顾

在我们第八天的学习中,我们很容易的为askeet添加了AJAX交互。现在程序已经相当有用了,但是还需要大量的小修改。在问题体中应允许丰富的文本,而主键不应出现在URI中。在Symfony中修正这些问题并不难,今天将是我们练习我们所学东西的一个好机会,并且可以检测我们是否已经知道如何操作MVC结构的所有层。

在question与answer上允许丰富的文本格式

Markdown

question与answer现在只允许接受普通文本。要允许基本的格式,粗体,斜体,超链接,图片,等,我们将会使用一个外部库,而不是重新发明轮子。

如果我们阅读过文本格式的Symfony文档,那么你也许知道我们是Markdown的fans。Markdown是一个文本到HTML的转换工具,以及一个文本格式化语法。例如,与Wiki或是论坛语法相比,Markdown的最大好处就是普通的markdown文本文件仍然具有可读性:

Test Markdown text
------------------

This is a **very simple** example of [Markdown][1].
The best thing about markdown is its _auto-escape_ feature for code chunks:

    <a href="http://www.symfony-project.com">link to symfony</a>

>The `<` and `>` are properly escaped as `&lt;` and `&gt;`,
>and are not interpreted by any browser

[1]: http://daringfireball.net/projects/markdown/   "Markdown"

Markdown的渲染结果如下:

Test Markdown text

This is a very simple example of Markdown. The best thing about markdown is its auto-escape feature for code chunks:

 <a href="http://www.symfony-project.com">link to symfony</a>

    The < and > are properly escaped as &lt; and &gt;, and are not interpreted by any browser

Markdown库

尽管最初是使用Perl语言编写的,但是现在Markdown已经可以作为一个PHP库来下载了。而这正是我们要使用的。下载markdown.php文件,并将其放置在askeet工程的lib目录下(askeet/lib)。就是这样:如果在我们的文件中首先进行请求,那么现在askeet程序的所有类都可以访问这个库了:

require_once('markdown.php');

我们可以在每次要显示消息体时调用Markdown转换器,但是这会给我们的服务器造成沉重的负载。我们会在问题创建时将文本体转换为HTML,并且在Question表中存储内容的HTML版本。也许我们已经熟悉这样了,所以模块扩展并不惊奇。

扩展模块

首先在schema.xml中的Question表部分添加一列:

column name="html_body" type="longvarchar" />

然后,生成模块并且更新数据库:

$ symfony propel-build-model
$ symfony propel-build-sql
$ symfony propel-insert-sql

覆盖setBody方法

当调用Question类的->setBody()方法时,html_body列必须使用文本内容的Markdown版本进行更新。打开askeet/lib/model/Question.php模块文本,并且编写下面的函数:

public function setBody($v)
{
  parent::setBody($v);
 
  require_once('markdown.php');
 
  // strip all HTML tags
  $v = htmlentities($v, ENT_QUOTES, 'UTF-8');
 
  $this->setHtmlBody(markdown($v));
}

在设置HTML内容之前使用htmlentities()函数可能保护askeet免受跨站点脚本(cross-site-scripting XSS)攻击,因为所有的<script>标记都会转义。

更新测试数据

我们会在问题的测试数据中添加一些Markdown格式(askeet/data/fixtures/test_data.yml),来检测转换可以正常工作:

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: fabien
    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](http://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.

我们现在可以重新装入数据库:

$ php batch/load_data.php

修改模板

question模块的showSuccess.php模板可以进行简单的修改:

...
<div class="question_body">
  <?php echo $question->getHtmlBody() ?>
</div>
...

list模板片段(_list.php)也会显示内容,但是却是截短的形式:

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

现在可以测试所有的修改了:显示我们修改的三个页面,并且观察由测试数据所显示的格式化文本:

http://askeet/question/list
http://askeet/recent
http://askeet/question/show/stripped_title/what-shall-i-do-tonight-with-my-girlfriend

同样的修改也适用于Answer内容:在模块中创建一个html_body列,覆盖->setBody()方法,question/show所显示的another时使用的是->getHtmlBody()方法,而不是->getBody()方法。因这些代码与上面的完全相同,所以在这里我们不再进行描述,但是我们可以在SVN代码中看到修改。

隐藏所有的id

在Symfony中另一个好的习惯就是尽量避免将主键作为参数传递。这是因为我们的主键是自动增加的,这样就为破坏都提供了太多的关于数据库记录的信息。另外,这样显示的URI并没有任何意义,而且也不适用于搜索引擎。

例如,以用户配置页面为例。现在,他使用用户id作为参数。但是如果我们确保nickname是唯一的,那么他也可以是请求的参数。让我们进行修改。

更改动作

编辑user/show动作:

public function executeShow()
{
  $this->subscriber = UserPeer::retrieveByNickname($this->getRequestParameter('nickname'));
  $this->forward404Unless($this->subscriber);
 
  $this->interests = $this->subscriber->getInterestsJoinQuestion();
  $this->answers   = $this->subscriber->getAnswersJoinQuestion();
  $this->questions = $this->subscriber->getQuestions();
}

更改模块

在askeet/lib/model/目录下的UserPeer中添加下面的方法:

public static function retrieveByNickname($nickname)
{
  $c = new Criteria();
  $c->add(self::NICKNAME, $nickname);
 
  return self::doSelectOne($c);
}

更改模板

现在到用户配置的显示链接必须关注用户的nickname而不是其id。

在question/showSuccess.php,question/_list.php模板中,将下面的代码:

<?php echo link_to($question->getUser(), 'user/show?id='.$question->getUserId()) ?>

替换为:

<?php echo link_to($question->getUser(), 'user/show?nickname='.$question->getUser()->getNickname()) ?>

对answer/_answer.php模板也要做同样的修改。

添加路由规则

在这个动作的转发配置中添加一条新的规则,从而url模式会显示一个nickname请求参数:

user_profile:
  url:   /user/:nickname
  param: { module: user, action: show }

在执行Symfony的clear-cache命令之后,我们要做的最后一件事就是要测试我们的修改。

转发(routing)

除了今天的内容之外,我们到现在所编写的许多动作都使用默认的转发规则,从而模块的名字与动作会显示在浏览器的地址栏中。我们已经了解了如何进行修正,所以让我们为所有的动作定义URL模式。编辑askeet/apps/frontend/config/routing.yml:

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

popular_questions:
  url:   /index/:page
  param: { module: question, action: list, page: 1 }

recent_questions:
  url:   /recent/:page
  param: { module: question, action: recent, page: 1 }

add_question:
  url:   /add_question
  param: { module: question, action: add }

# answer
recent_answers:
  url:   /recent/answers/:page
  param: { module: answer, action: recent, page: 1 }

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

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

user_profile:
  url:   /user/:nickname
  param: { module: user, action: show }

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

default_symfony:
  url:   /symfony/:action/*
  param: { module: default }

default_index:
  url:   /:module
  param: { action: index }

default:
  url:   /:module/:action/*

如果我们在生产环境中进行浏览,那么强烈建议在测试这些配置修改之前清除缓存。

Symfony转发的一个好习惯就是在一个link_to()帮助器中使用规则名,而不是module/action。不仅因为其速度快(转发引擎并不需要分析转发配置来查找合适的规则),还因为他允许我们在规则名之后修改动作。Symfony一书的转发一章进行详细的解释。

<?php link_to('@user_profile?id='.$user->getId()) ?>
// is better than
<?php link_to('user/show?id='.$user->getId()) ?>

Askeet遵循Symfony的好习惯,所以我们今天所下载的代码在链接帮助器中只有规则名。将所有模板中的action/module替换为@rule以及自定义帮助器并不是一件有趣的事情,所以关于转发的最后一条建议就是:当我们创建动作时编写转发规则,并且从开始就在链接帮助器中使用规则名。

明天见
posted @ 2008-03-16 10:03  jlins  阅读(381)  评论(0编辑  收藏  举报