[原创中文翻译]symfony askeet24:第九天,本地优化。
October 4, 2007 – 6:56 pm[欢迎转载,转载请注名出处http://symfony.net.cn。本文英文版权归symfony官方网站所有]
Allow rich text formatting on questions and answers
允许问题和答案内容富文本编辑
Markdown
Markdown插件
The question and answer bodies only accept plain text for now. To allow basic formatting - bold, italic, hyperlinks, images, etc. - we will use an external library rather than reinvent the wheel.
问题和答案内容现在只能接受无格式文本。实际上应该允许使用基本格式——粗体,斜体,超级链接,图片等等。——我们使用外部库来实现。
If you have taken a look at the symfony documentation in text format, you probably know that we are big Markdown fans. Markdown is a text-to-HTML conversion tool, and a syntax for text formatting. The great advantage of Markdown over, for instance, Wiki or forum syntax, is that a plain text markdown file is still very readable:
如果你看过symfony手册中关于文本格式的内容,你会发现我们是狂热的Markdown粉丝。Markdown是text-to-HTML转换工具,有给文本格式的语法。最好的Markdown来了,例如,Wiki或者论坛语法,简单格式的markdown文件很易读:
This Markdown renders as follow:
markdonw就这样实现功能(见上图):
Test Markdown text
测试markdonw功能
This is a very simple example of Markdown. The best thing about markdown is its auto-escape feature for code chunks:
这是markdown简单的例子。最显著的特点是auto-escape代码标记:
<a href=”http://www.symfony-project.com”>link to symfony</a>
The < and > are properly escaped as & l t ;and & g t ;, and are not interpreted by any browser
< 和 > 被过滤成 & l t ;和& g t ;,任何浏览器都解释不了。
Markdown library
markdown库
Although originally written in Perl, Markdown is available as a PHP library at PHP Markdown. That’s the one we will use. Download the markdown.php file and put it in the lib folder of the askeet project (askeet/lib/). That’s all: It is now available to all the classes of the askeet applications, provided that you require it first:
虽然本来是用perl写的,在PHP markdown里markdown被当作PHP库。我们要的就是这个。下载markdown.php文件放到askeet的lib目录里(askeet/lib/)。这就完事了:这样askeet所有的程序都能调用它了,首先你需要把它require进来:
require_once('markdown.php');
We could call the Markdown converter each time we display the body of a message, but that would require too high a load on our servers. We’d rather convert the text body to an HTML body when the question is created, and store the HTML version of the body in the Question table. You are probably getting used to this, so the model extension won’t be a surprise.
当需要显示信息内容时就调用markdown进行转换,但是很耗费服务器资源。我们最好在问题文本内容创建时就做好转换工作,把转换好的HTML文本放到问题数据表里。你会很快习惯这样做的,所以model的扩展也算不上啥稀奇的。
Extend the model
扩展model
First, add a colomn to the Question table in the schema.xml:
首先,给问题表加个字段,修改改schema.xml:
<column name=“html_body” type=“longvarchar” />
Then, regenerate the model and update the database:
然后,生成模块并更新数据库:
$ symfony propel-build-model
$ symfony propel-build-sql
$ symfony propel-insert-sql
Override the setBody method
覆盖setBody方法
When the ->setBody() method of the Question class is called, the html_body column must also be updated with the Markdown conversion of the text body. Open the askeet/lib/model/Question.php model file, and create:
当Question类的->setBody()被调用时,html_body字段也使用markdown转换的文本内容更新。打开askeet/lib/model/Question.php model文件,创建:
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));
}
Applying the htmlentities() function before setting the HTML body protects askeet from cross-site-scripting (XSS) attacks since all <script> tags are escaped.
插入HTML文本前先用htmlentities()方法过滤以防跨站脚本攻击,把所有的<script>标签过滤掉。
Update the test data
更新测试数据
We will add some Markdown formatting to some of the questions of the test data (in askeet/data/fixtures/test_data.yml), to be able to check that the conversion works properly:
我们增加些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.
You can now repopulate the database:
现在载入数据:
$ php batch/load_data.php
Modify the templates
修改模板
The showSuccess.php template of the question module can be sightly modified:
问题模块的showSuccess.php模板可以修改得漂亮些:
...
<div class="question_body">
<?php echo $question->getHtmlBody() ?>
</div>
...
The list template fragment (_list.php) also shows the body, but in a truncated version:
模板的代码片断(_list.php)也显示主体文字,但是是截短版本:
<div class="question_body">
<?php echo truncate_text(strip_tags($question->getHtmlBody()), 200) ?>
</div>
Everything is now ready for the final test: display the three pages that were modified, and observe the formatted text coming from the test data:
一切都准备好了,为了最后的测试:查看三个修改后页面的显示情况,通过测试数据检查最后文本格式化结果:
http://askeet/question/list
http://askeet/recent
http://askeet/question/show/stripped_title/what-shall-i-do-tonight-with-my-girlfriend
The same goes for the Answer body: An alternate html_body column has to be created in the model, the ->setBody() method needs to be overridden, and the answers displayed in question/show have to use the ->getHtmlBody() method instead of the ->getBody(). As the code is exactly the same as above, we won’t decribe it here, but you will find it in today’s SVN code.
同样应用到答案显示上:修改过的html_body字段被创建在模型里了,->setBody()方法需要被重写,显示在question/show模板的答案必须使用->getHtmlBody()方法代替->getBody()方法。代码和上面大部分都一样,我们不多描述了,你可以在今天的SVN代码里找到。
Hide all ids
不显示id
Another good practice in symfony actions is to avoid as much as possible to pass primary keys as request parameters. This is because our primary keys are mainly auto-incremental, and this gives hackers too much information about the records of the database. Plus, the displayed URI doesn’t mean anything, and that’s bad for the search engines.
symfony的动作是尽可能避免传递重要关键信息作为请求参数。主要是因为我们id在数据库中是自动增加的,这样就给了黑客更多的关于数据库记录的信息。另外,URL不友好,影响搜索引擎收录。
Take the user profile page, for instance. For now, it uses the user id as a parameter. But if we make sure that the nickname is unique, it could as well be the parameter for the request. Let’s do it.
使用用户信息。例如,现在我们使用user id当作参数。但如果我们确定了用户名是唯一的,那就变成了请求参数。那么来完成它。
Change the action
改变动作
Edit the user/show action:
编辑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();
}
Change the model
改变模型
Add the following method to the UserPeer class in the askeet/lib/model/ directory.
在askeet/lib/model/下,给UserPeer类加几个方法。
public static function retrieveByNickname($nickname)
{
$c = new Criteria();
$c->add(self::NICKNAME, $nickname);
//
return self::doSelectOne($c);
}
Change the template
改变模板
The pages that display a link to the user profile must now mention the user’s nickname instead of his/her id.
显示到用户资料的链接现在应该用用户名代替用户的id。
In the question/showSuccess.php, question/_list.php templates, replace:
在question/showSuccess.php,question/_list.php模板,替换:
<?php echo link_to($question->getUser(), 'user/show?id='.$question->getUserId()) ?>
by:
为下面代码:
<?php echo link_to($question->getUser(), 'user/show?nickname='.$question->getUser()->getNickname()) ?>
The same kind of modification goes for the answer/_answer.php template.
同样修改一下answer/_answer.php模板。
Add the routing rule
增加路由规则
Add a new rule in the routing configuration for this action so that the url pattern shows a nickname request parameter:
给动作加新的路由规则,让URL格式显示用户名参数:
user_profile:
url: /user/:nickname
param: { module: user, action: show }
After a symfony clear-cache, the last thing to do is to test your modifications.
清除缓存后,要做的是测试你的修改结果。
Routing
路由
Apart from today’s additions, many of the actions written until now use the default routing, so the module name and the action name are often displayed in the address bar of the browser. You already learned how to fix it, so let’s define URL patterns for all the actions. Edit the askeet/apps/frontend/config/routing.yml:
从今天看来,许多动作使用的都是默认路由规则,所以模块名和动作名经常显示在浏览器地址栏里。你已经学习如何修正了,现在来定义一下所有动作涉及到的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/*
If you navigate in the production environment, you are strongly advised to clear the cache before testing this configuration modification.
如果在发布模式下,在测试前强烈建议清除缓存。
One good practice of symfony routing is to use the rule names in a link_to() helper instead of the module/action. Not only is it faster (the routing engine doesn’t need to parse the routing configuration to find the rule to apply), but it also allows you to modify the action behind a rule name later. The routing chapter of the symfony book explains that more in detail.
symfony路由的妙处是在link_to() helper中使用配置规则名来代替module/action。不仅因为速度快(路由引擎不需要分析路由配置文件来找到执行规则),而且允许你在路由规则名使用后修改动作。symfony book的路由章节解释得很详细了。
<?php link_to(‘@user_profile?id=’.$user->getId()) ?>
// is better than
<?php link_to(’user/show?id=’.$user->getId()) ?>
Askeet follows the symfony good practices, so the code that you will download at the end of this day’s tutorial contains only rule names in the link helpers. Replacing action/module by @rule in all the templates and custom helper is not very fun to do, so the last advice concerning routing is: Write the routing rules as you create actions, and use rule names in the link helpers from the beginning.
askeet使用了symfony精华,所以今天你下载的代码在link helper包含了路由规则名。在所有模板和自定义helper中把action/module用@rule换了并不是很有趣。所以最后关于路由规则的建议是:把路由规则写到你创建的action里,现在开始在link helper使用路由规则。
