本文執行個體講述了symfony表單與頁面實現技巧。分享給大家供大家參考。具體如下:
symfony開發很簡潔,但是功能的數量仍然很缺乏。現在是時候進行一些askeet網站與使用者之間的互動了。而HTML互動的根本--除了起連結--就是表單了。
這裡我們的目標是允許使用者登陸,並在首頁的問題列表中進行翻閱。這對於開發而言是很快的,並且可以讓我們回憶起前面的內容。
登陸表單
在測試資料中存在使用者,但是程式卻沒有辦法來進行驗證。下面我們要在程式的每一個頁面添加一個登陸表單。開啟全域的布局檔案askeet/apps/frontend/templates/layout.php,並且在到about的串連之前添加下面的程式碼:
複製代碼 代碼如下:
<?php echo link_to('sign in', 'user/login') ?>
當前的布局將這些連結放在web調試工具列之後。要看到這些連結,點擊'Sf'表徵圖摺疊起調試工具列就可以看到了。
現在需要建立user模組。而question模組是在第二天產生的,這一次我們只是叫symfony來建立模組架構,而我們將會自己來編寫這些代碼。
複製代碼 代碼如下:$ symfony init-module frontend user
這個架構套件含一個預設的index動作與一個indexSuccess.php模板。刪除他們,因為我們並不需要他們。
建立user/login動作
複製代碼 代碼如下:在user/actions/action.class.php檔案中,添加下面的登陸動作:
public function executeLogin()
{
$this->getRequest()->setAttribute('referer', $this->getRequest()->getReferer());
return sfView::SUCCESS;
}
這個動作將referer儲存在請求屬性中。然後這個屬性可為模組所用存放在一個隱藏地區中,從而這個表單的目的動作可以在成功登陸後重新導向到原始的referer。
語句return sfView::SUCCESS將動作執行結果傳遞到loginSuccess.php模組。這條語句是在一個不包含返回語句的動作中實現的,這也就是一個動作的預設模組被稱之為actionnameSuccess.php的原因。
在動作上開始更多的工作之前,我們先來看一下模組。
建立loginSuccess.php模組
web上的許多人機互動使用表單,而Symfony通過提供一個form協助器集合來組織表單的建立與管理。
在askeet/apps/frontend/modules/user/templates/目錄下,建立下面的loginSuccess.php模組:
複製代碼 代碼如下:<?php echo form_tag('user/login') ?>
nickname:
<?php echo input_tag('nickname', $sf_params->get('nickname')) ?>
password:
<?php echo input_password_tag('password') ?>
<?php echo input_hidden_tag('referer', $sf_request->getAttribute('referer')) ?>
<?php echo submit_tag('sign in') ?>
這個模組是我們第一次使用表單協助器。這些Symfony函數可以協助我們自動化編寫表單標籤。form_tag()開啟一從此標籤,使用POST作為預設的動作,並且指向作為參數傳遞的動作。input_tag()協助器產生一個標籤,並且依據所傳遞的第一個參數自動添加一個id屬性;而預設值則是由第二個參數得到。我們可以在Symfony一書的相關章節尋找到更多的關於表單協助器與他們所產生的HTML代碼的內容。
這裡的實質是當表單提交時則會調用這個動作。所以我們返回來看一下這個動作。
處理表單提交
用下面的代碼來替換我們剛才所編寫的登陸動作:
複製代碼 代碼如下:public function executeLogin()
{
if ($this->getRequest()->getMethod() != sfRequest::POST)
{
// display the form
$this->getRequest()->setAttribute('referer', $this->getRequest()->getReferer());
}
else
{
// handle the form submission
$nickname = $this->getRequestParameter('nickname');
$c = new Criteria();
$c->add(UserPeer::NICKNAME, $nickname);
$user = UserPeer::doSelectOne($c);
// nickname exists?
if ($user)
{
// password is OK?
if (true)
{
$this->getUser()->setAuthenticated(true);
$this->getUser()->addCredential('subscriber');
$this->getUser()->setAttribute('subscriber_id', $user->getId(), 'subscriber');
$this->getUser()->setAttribute('nickname', $user->getNickname(), 'subscriber');
// redirect to last page
return $this->redirect($this->getRequestParameter('referer', '@homepage'));
}
}
}
}
登陸動作可以同時用來顯示登陸表單並且進行處理。相應的,他必須知道所調用的環境。如果這個動作並沒有在POST模式下調用(因為是由一個連結來請求的):而這正是我們在前面所討論的情況。如果是在POST模式下請求的,那麼則會由表單調用這個動作並進行相應的處理。
這個動作會由請求參數得到nickname域的值,並且查詢User表來查看在資料庫是否存在此使用者。
將來一個密碼控制將會為使用者指派憑證。但是現在,這個動作所做的只是在一個會話屬性中儲存使用者的id與nickname屬性。最後,這個動作重新導向到表單中隱藏中的原始referer域,這是作為一個請求參數傳遞的。如果這個域是空的,則會使用預設值。
這裡我們需要注意這個例子中兩種類型的屬性集合之間的區別:request attributes($this->getRequest()->setAttribute())是為模板所儲存的,而且只要答案發送到referer則會被忘記。session attributes($this->getUser()->setAttribute())是在整個使用者會話生命期被儲存的,而且在將來其他的動作也可以訪問他們。如果我們希望瞭解更多的關於屬性的內容,我們可以查看Symfony一書的參數儲存器一節。
分配許可權
使用者可以登陸進askeet網站是一件好事,但是使用者並不僅是因為好玩而登陸。發表一個新問題,對某一個問題表示興趣,評價一個評論都需要登陸。而其他的動作將會向非登陸使用者開放。
要將一個使用者佈建為經過驗證的,我們需要調用sfUser對象的->setAuthenticated()方法。這個對象同時提供了一個認證機制(->addCredential()),來通過配置限制訪問。Symfony一書的使用者認證一節對此進行了詳細的解釋。
這就是下面兩行的目的:
複製代碼 代碼如下:$this->getContext()->getUser()->setAuthenticated(true);
$this->getContext()->getUser()->addCredential('subscriber');
當nickname被識別後,不僅使用者資料被存放在會話屬性中,而且這個使用者也會被分配網站限制部分的存取權限。在明天我們將會看到如何限制驗證使用者的程式訪問。
添加user/logout動作
關於->setAttribute()方法還有最後一個竅門:最後一個參數(上面例子中的subscriber)定義了屬性存放的名字空間。一個名字空間不僅允許一個在另一個名字空間存在的名字指定給一個屬性,而且可以使用一個命令快速移除所有這些屬性:
複製代碼 代碼如下:public function executeLogout()
{
$this->getUser()->setAuthenticated(false);
$this->getUser()->clearCredentials();
$this->getUser()->getAttributeHolder()->removeNamespace('subscriber');
$this->redirect('@homepage');
}
使用名字空間可以省去我們一個一個移除這些屬性的麻煩:這隻是一行語句。
更新布局
當前這個布局即使使用者已經登陸仍然顯示一個'login'連結。讓我們來修正這一點。在askeet/apps/frontend/templates/layout.php檔案中,修改我們在今天的指南開始時所修改的代碼:
複製代碼 代碼如下:<?php if ($sf_user->isAuthenticated()): ?>
<?php echo link_to('sign out', 'user/logout') ?>
<?php echo link_to($sf_user->getAttribute('nickname', '', 'subscriber').' profile', 'user/profile') ?>
<?php else: ?>
<?php echo link_to('sign in/register', 'user/login') ?>
<?php endif ?>
現在是時候進行測試了,我們可以顯示程式的任何一頁,點擊'login'連結,輸入一個可用的暱稱('anonymous'為例)並且進行驗證。如果視窗頂部的'login'變為'sign out',則我們所做的一切都是正確的。最後,試著登出來查看'login'連結是否再次出現。
問題組織
隨著數以千計的Symfony愛好者訪問askeet網站,在首頁上顯示的問題就會逐漸層多。為了避免變慢的請求速度,問題列的隨意翻閱就成為必須解決的問題。
Symfony為這一目的提供了一個對象:sfPropelPager。他會封裝到資料的請求,從而只會查詢當前頁面所顯示的記錄。例如,如果一個頁面初始化時每頁只顯示10個問題,則到資料的請求只會限制為10個結果,並且會設定位移來在頁面中進行匹配。
修改question/list動作
在前面的練習中,我們看到了問題模組的顯示動作:
複製代碼 代碼如下:public function executeList ()
{
$this->questions = QuestionPeer::doSelect(new Criteria());
}
我們將會修改這個動作來向模板傳遞一個sfPropelPager而不是傳遞一個數組。同時,我們會依據感興趣的數量來對問題進行排序:
複製代碼 代碼如下:public function executeList ()
{
$pager = new sfPropelPager('Question', 2);
$c = new Criteria();
$c->addDescendingOrderByColumn(QuestionPeer::INTERESTED_USERS);
$pager->setCriteria($c);
$pager->setPage($this->getRequestParameter('page', 1));
$pager->setPeerMethod('doSelectJoinUser');
$pager->init();
$this->question_pager = $pager;
}
sfPropelPager對象的初始化指明了他包含哪個對象類,以及在一個頁面中可以放置的對象的最大數目(在這個例子中為2)。->setPage()方法使用一個請求參數來設定當前頁面。例如,如果這個頁面參數的值為2,sfPropelPager將會返回3到5的結果。頁面請求參數的值變為1,則頁面預設會返回1到2的結果。我們可以在Symfony一書的頁面章節中瞭解到關於sfPropelPager對象及其方法的更多資訊。
使用一個預設參數
將常量放在我們所使用的設定檔中是一個好主意。例如,每頁的結果(在這個例子為2)可以由一個在我們自訂的程式配置中的參數來代替。用下面的代碼來改變上面的sfPropelPager行:
複製代碼 代碼如下:..
$pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));
這裡的pager關鍵字是作為名字空間使用的,這也就是為什麼在參數名字中出現的原因。我們可以在Symfony一書的配置一節中查看到更多的關於自訂配置與命名自訂參數規則的更多的內容。
修改listSuccess.php模板
在listSuccess.php模板中,將下面的程式碼:
複製代碼 代碼如下:<?php foreach($questions as $question): ?>
替換為
複製代碼 代碼如下:<?php foreach($question_pager->getResults() as $question): ?>
從而頁面顯示儲存在頁面中的結果清單。
添加頁面瀏覽
在這個模板中還需要做另外一件事:頁面瀏覽。現在,模板所做的只是顯示前兩個問題,但是我們應添加到下一個頁面的功能,以及回到前一個頁面的功能。要完成添加這些功能,我們需要在模板後面添加下面的代碼:
複製代碼 代碼如下:
<?php if ($question_pager->haveToPaginate()): ?>
<?php echo link_to('«', 'question/list?page=1') ?>
<?php echo link_to('<', 'question/list?page='.$question_pager->getPreviousPage()) ?>
<?php foreach ($question_pager->getLinks() as $page): ?>
<?php echo link_to_unless($page == $question_pager->getPage(), $page, 'question/list?page='.$page) ?>
<?php echo ($page != $question_pager->getCurrentMaxLink()) ? '-' : '' ?>
<?php endforeach; ?>
<?php echo link_to('>', 'question/list?page='.$question_pager->getNextPage()) ?>
<?php echo link_to('»', 'question/list?page='.$question_pager->getLastPage()) ?>
<?php endif; ?>
這段代碼利用了sfPropelPager對象的各種方法,以及->haveToPaginate(),這個函數只有在請求的結果數目超過了頁面尺寸時才會返回真;而->getPreviousPage(),->getNextPage(),->getLastPage()都具有明顯示的意義;->getLinks()函數提供了一個頁面號的數組;而->getCurrentMaxLink()函數返回最後的頁面號。
這個例子同時顯示了一個Symfony連結協助器:link_to_unless()會在作為第一個參數的測試為假的情況下輸出一個常規link_to(),否則會輸出一個非連結的文本,並使用簡單的封裝。
我們測試這個頁面了嗎?我們應進行測試。直到我們用我們自己的眼睛來驗證,這個修改才算結束。要進行測試,開啟在第三天所建立的測試資料檔案,並且為要顯示的頁面瀏覽添加一些問題。重新運行匯入資料批次檔,然後再一次請求首頁。
為子頁添加路由規則
預設情況下,頁面規則如下:
http://askeet/frontend_dev.php/question/list/page/XX
現在我們利用路由規則使用這些頁面更易於理解:
http://askeet/frontend_dev.php/index/XX
開啟apps/frontend/config/routing.yml檔案並且在頂部添加下面內容:
複製代碼 代碼如下:popular_questions:
url: /index/:page
param: { module: question, action: list }
並且為登陸頁面添加另外的路由規則:
複製代碼 代碼如下:login:
url: /login
param: { module: user, action: login }
重構
模型
question/list動作執行與模型相關的代碼,這也就是我們為什麼要將這些代碼移動到模組中的原因。用下面的代碼來代替question/list動作:
複製代碼 代碼如下:public function executeList ()
{
$this->question_pager = QuestionPeer::getHomepagePager($this->getRequestParameter('page', 1));
}
並且在lib/model中的QuestionPeer.php類中添加下面的方法:
複製代碼 代碼如下:public static function getHomepagePager($page)
{
$pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));
$c = new Criteria();
$c->addDescendingOrderByColumn(self::INTERESTED_USERS);
$pager->setCriteria($c);
$pager->setPage($page);
$pager->setPeerMethod('doSelectJoinUser');
$pager->init();
return $pager;
}
同樣的想法也適用於我們昨天編寫的question/show動作:Propel對象由其剝離的標題取回問題的用法應屬於這個模組。所以用下面的代碼來變更question/show動作代碼:
複製代碼 代碼如下:public function executeShow()
{
$this->question = QuestionPeer::getQuestionFromTitle($this->getRequestParameter('stripped_title'));
$this->forward404Unless($this->question);
}
在QuestionPeer.php檔案中添加下面的代碼:
複製代碼 代碼如下:public static function getQuestionFromTitle($title)
{
$c = new Criteria();
$c->add(QuestionPeer::STRIPPED_TITLE, $title);
return self::doSelectOne($c);
}
模板
在question/templates/listSuccess.php中顯示的問題列表在將來的某些地方還會用到。所以我們將顯示問題列表的模板代碼放在一個_list.php片段中,並且用下面的簡單代碼來代替listSuccess.php的內容:
複製代碼 代碼如下:
popular question
<?php echo include_partial('list',array('question_pager'=>$question_pager)) ?>
希望本文所述對大家的symfony架構程式設計有所協助。