symfony回顾
在第3天,我们看到了一个MVC架构的所有层,并修改它们用来在页面上正确地表示question的列表。应用变得好看了些但是还是有点缺乏内容。第四天的目标是表示一个question的answer,给question的详细页面一个更友好的URL,加入一个客户类(custom class),路由政策,以及重构。你可能觉得现在就重写前几天的代码有点太早了,但是相信完成了今天的教程之后你的想法会有所改变。
要阅读这个教程,你应该熟悉以下的章节 MVC在symfony中的实现 .还有如果你懂一点 敏捷开发 也会有所帮助。
表示一个quesiton的answer
首先,让我们继续调整第2天Question CRUD生成的模板。quesiton/show 动作专注于表示一个quesiton的详细信息,并假设收到的参数是你传给它的id。我们可以试试看,调用下面的URL(你可能要把最后的 2 改成你的数据表里的某个id)
http://askeet/frontend_dev.php/question/show/id/2
你可能已经开到过这个页面了。这就是我们将要修改的页面,我们要加上一个question的answer.
动作速览
首先,让我们看看show动作,它在下面这个文件。
askeet/apps/frontend/modules/question/actions/actions.class.php
如果你熟悉Rropel,你知道这是一个简单的对Question数据表的请求。它的目的是用id参数作为唯一主键取得一条唯一的记录。在上面这个例子里,id参数的值是2,所以QuestionPeer类的->retrieveByPk()方法会返回一个Question类的对象,它的主键为2。如果你不熟悉Propel,那么请先看看 某些文档,然后再回来继续。
这个请求的结果被通过$question变量交给 showSuccess.php模板,
sfAction对象的->getRequestParameter('id‘)方法取得请求中的叫做'id'的参数,而不论它是通过GET还是POST模式传来的。例如,如果你请求
http://askeet/frontend_dev.php/question/show/id/1/myparam/myvalue
那么show动作将用 $this->getRequestParameter('myparam').来取得myvalue.
注意:forward404Unless()方法如果在数据库找不到要找到question它就会发送给浏览器一个404页面。总是处理执行中的边界值和错误是一个好习惯,symfony给出了一个简单的方法来让这件事情更容易。
修改showSuccess.php模板
生成的showSuccess.php模板不能满足我们的要求,所以我们要全面重写它。打开 frontend/modules/question/templates/showSuccess.php 用以下的内容替换掉。
你看到interested_block div在昨天已经加到listSuccess.php模板了。它表示对一个question感兴趣的用户数。还有,现在它非常象一个list,除了在title没有link_to.它只是对初始代码的一次改写,以表示一个question的需要表示的信息。
新的部分是answers DIV,它表示对这个question的所有answer(用 $question->getAnswers() Propel方法),除了body还表示每个answer的Relevancy(关联),作者的名字,创建日期。
format_date()是另一个需要初始声明的模板助手的例子,你可以在 symfony宝典的 助手的初始化 章节找到更多关于助手的语法。(这些助手帮助我们能更快地完成单调的日期格式化表示的任务)
注意:Propel为关联表创建的方法的名字是在表名后加一个's',虽然->getRelevancys() 这样的方法名有点不好看,但是可以帮你节约号几行SQL代码。
加入一些新的测试数据
现在是时候在 data/fixtures/test_data.yml 给answer和relevancy数据表加些数据了。(你完全可以加些自己的数据)
用下面的命令行重新载入你的数据,
$ php batch/load_data.php
如果以前修改成功,下面的URL将将表示你的第一个question
http://askeet/frontend_dev.php/question/show/id/XX
注意:把XX改成你的第一个question的id
现在 question表示得更好看了,后面还跟着它的answer.不错吧。
修改模块 第1部分
几乎可以肯定表示一个作者的全名在这个应用的其他地方也会用到。你也可以把全名考虑成一个User对象的属性,这意味着User模块应该有一个方法可以用来取得全名,而不是在动作中每次重写。打开 askeet/lib/model/User.php 加入以下的方法
为什么这个方法叫 __toString() 而不叫 getFullName()或其它类似的名字呢?因为__toString() 方法在PHP5中是一个对象描述为字符串时的缺省方法。这意味着你可以把askeet/apps/frontend/modules/question/templates/showSuccess.php 文件里的下面的代码改写成 更简单的形式
代码简洁吧.
不要重复你自己
敏捷开发的一个好的原则就是避免重复代码,被称为 Don't Repeat Yourself (D.R.Y)。这是因为重复的代码在检查,修改,测试和更新的时候都要比一段封装后的代码多花更多的时间。这还让应用的维护变得复杂。如果你去看看昨天的教程,你会注意到昨天写的listSuccess.php模板和ShowSuccess.php有一些重复的代码。
所以我们重构的第一个任务就是去掉这两个模板里的重复代码,把他们放到一个片段(fragment),或成为可重用代码块里。在askeet/apps/frontend/modules/question/template/ 里创建一个_interested_user.php文件,代码如下。
然后,把两个模板(listSuccess.php 和 showSuccess.php)里的代码置换成
一个片段没有任何对现在对象的本地访问,这个片段用了一个 $question变量,所以它必须在对 include_partial 的调用里定义。片段文件的前缀_可以帮助我们把它和template/目录下的真正的模板文件区分开来。如果你想学到更多关于片段的知识,可以读一读 symfony宝典的 显示 章节
修改模块 第2部分
新的片段里的$question->getInterests()调用请求数据库并返回一个Interest类的对象的数组。对于只需要感兴趣的用户的数字来说这是一个消耗太大的请求,它可能给数据库带来不必要的负载。记住,这个调用还在listSuccess.php里调用,是在一个循环里。看来最好优化一下它。
一个好的方法是给Question数据表加入一个叫做interested_users的列,然后在每次有关于这个question的interest创建的时候去更新这个列。
重要:我们将要更改一个模块,但是没有明显的方法去测试它,因为现在没有办法可以去通过askeet给Interest数据表加记录。如果你没法测试,你就不应该做任何修改。幸运的是,我们有一个办法可以测试这个修改,我们会在本章的后面发现它。
给User对象模块加入一个字段
大胆地修改the askeet/config/schema.xml
数据模块,给ask_question数据表加一个字段。
然后重建模块
$ symfony propel-build-model
对,我们重建模块而不用担心对现有模块的扩展,因为对User类的扩展是在 askeet/lib/model/User.php里的,它继承自Propel 生成的类askeet/lib/model/om/BaseUser.php。这就是你为什么不应该编辑 askeet/lib/model/om/ 目录下的代码:它们在每次调用build-model的时候都被重建。Symfony帮助我们更容易地在Web项目的早期修改模块。
你好需要更新真实的数据库。为了避免写SQL语句,你要重建你的SQL schema并重新载入测试数据
$ symfony propel-build-sql
$ mysql -u youruser -p askeet < data/sql/lib.model.schema.sql
$ php batch/load_data.php
注意:TIMTOWTDI(另一种方法,There is more then one way to do it):除了重建数据库,你可以手工给MySQL数据表加入新的列。
$ mysql -u youruser -p askeet -e "alter table ask_question add interested_users int default '0'"
修改Interest对象的保存方法
更新这个新字段的动作必须在每次一个user对一个question感兴趣的时候都做一次,也就是说,每次一条新的纪录加入到Interest数据表的时候都要做一次。你可以通过在MySQL里做一个触发器来实现它,但是这是一个数据库依存的解决方案,你就不能很容易地迁移到另一个数据库了。
最好的解决方案是重载Interest类的save()方法,这个方法每次Interest的对象被创建的时候都会被调用。所以,打开 askeet/lib/model/Interest.php ,写如下的代码