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 ,寫如下的代碼