More with symfony 1.3 & 1.4-高級路由 (第二部分)

終於又翻玩了一篇,果然翻譯也是需要鍛鍊的,好比,這篇還沒校對,歡迎指正

高級路由 (第二部分)

作者:Ryan Weaver 翻譯:豆派

路由集合(Route Collections)

要完成Sympal Builder應用,我們需要創建一個管理平臺,使得每個 Client 能管理他們的頁面。 要做到這一點,我們需要一系列的action讓我們能夠創建,更新和刪除 Page 對象。 由於這些類型的模塊(module)非常通用,symfony可以自動生存這些模塊。從命令行執行下面的任務(task)在backend應用中生成一個 pageAdmin 模塊:

$ php symfony doctrine:generate-module backend pageAdmin Page --with-doctrine-route --with-show

上面的命令產生一個包含action和templates的管理模塊,它能完成所有對Page對象的修改需求。有很多自定義選項可以修改自動生存的CRUD,但這超出了本章要討論的範圍。

雖然上述命令爲我們準備了需要的module,但是我們還是需要爲每個action創建路由。 通過附加給命令的--with-doctrine-route 選項,每個action都會有生存一個相應的對象路由。這樣減少了在每個action中的代碼。比如在 edit action 中只有簡單的一行:

public function executeEdit(sfWebRequest $request)
{
  $this->form = new PageForm($this->getRoute()->getObject());
}

總共,我們需要給index , new , create , edit , updatedelete actions都分配一個路由。一般創建這些具有RESTful 風格的路由需要在routing.yml做非常多的配置。

pageAdmin:
  url:         /pages
  class:       sfDoctrineRoute
  options:     { model: Page, type: list }
  params:      { module: page, action: index }
  requirements:
    sf_method: [get]
pageAdmin_new:
  url:        /pages/new
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: new }
  requirements:
    sf_method: [get]
pageAdmin_create:
  url:        /pages
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: create }
  requirements:
    sf_method: [post]
pageAdmin_edit:
  url:        /pages/:id/edit
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: edit }
  requirements:
    sf_method: [get]
pageAdmin_update:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: update }
  requirements:
    sf_method: [put]
pageAdmin_delete:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: delete }
  requirements:
    sf_method: [delete]
pageAdmin_show:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: show }
  requirements:
    sf_method: [get]

想要查看這些路由,可以使用 app:routes 命令, 它能展示一個特定應用程序的所有路由的摘要。

$ php symfony app:routes backend

>> app       Current routes for application "backend"
Name             Method Pattern
pageAdmin        GET    /pages
pageAdmin_new    GET    /pages/new
pageAdmin_create POST   /pages
pageAdmin_edit   GET    /pages/:id/edit
pageAdmin_update PUT    /pages/:id
pageAdmin_delete DELETE /pages/:id
pageAdmin_show   GET    /pages/:id

使用路由集合代替路由

幸運的是,symfony提供了一個更加方便的方法來指定包含傳統的CRUD功能的所有路由。在routing.yml中用一個簡單的路由替換上面所有的內容。

pageAdmin:
  class:   sfDoctrineRouteCollection
  options:
    model:        Page
    prefix_path:  /pages
    module:       pageAdmin

我們再次運行app:routes命令來查看所有的路由, 正如你所看到的,前面的七個路由仍然存在。

$ php symfony app:routes backend

>> app       Current routes for application "backend"
Name             Method Pattern
pageAdmin        GET    /pages.:sf_format
pageAdmin_new    GET    /pages/new.:sf_format
pageAdmin_create POST   /pages.:sf_format
pageAdmin_edit   GET    /pages/:id/edit.:sf_format
pageAdmin_update PUT    /pages/:id.:sf_format
pageAdmin_delete DELETE /pages/:id.:sf_format
pageAdmin_show   GET    /pages/:id.:sf_format

路由集合是一種特殊類型的路由對象,它內部包含了許多個路由。 比如, sfDoctrineRouteCollection 路由自動生成做CRUD操作時七個最常用的路由。  sfDoctrineRouteCollection 幕後所做的無非就是創建我們先前在routing.yml 配置的七個路由。路由集合主要是爲創建一個通過路由組合而存在的快捷方式。

創建一個自定義的路由集合

在這點上,每個Client 能夠通過URL/pages在他的Page對象上做正常CRUD 修改。 不幸的是,現在每個Client 能夠看到和修改所有的Page 對象-包括屬於和不屬於他的。比如,如,http://pete.sympalbuilder.com/backend.php/pages 會展現所有在fixtures.yml定義的Page的列表- Pete's Pet Shop的 location 頁面和City Pub的 menu 頁面。

要解決這個問題,我們需要重用在frontend創建的acClientObjectRoute。 sfDoctrineRouteCollection 類將生成一組sfDoctrineRoute 對象。但在這個應用中,我們需要生成一組 acClientObjectRoute 對象。

要做到這一點,我們將需要使用一個自定義的路由集合類。創建一個文件 acClientObjectRouteCollection.class.php ,並把它放到 lib/routing 目錄。它的內容是難以置信的簡單:

// lib/routing/acClientObjectRouteCollection.class.php
class acClientObjectRouteCollection extends sfObjectRouteCollection
{
  protected
    $routeClass = 'acClientObjectRoute';
}

$routeClass 屬性定義了創建每個基本路由時使用的類型。現在每個基本路由都是使用acClientObjectRoute了 ,工作實際已經完成了。比如 , http://pete.sympalbuilder.com/backend.php/pages 現在指揮展現一個頁面:Pete's Pet Shop的 location 頁面。 感謝自定義路由類型,index action才能根據請求中的子域名展現只與正確的Client相關的Page對象。 只用了幾行代碼,我們就已經創建了整個可以被多個用戶安全的使用的後臺模塊。

遺漏的部分:創建新頁面

現在,當創建或編輯一個Page對象時會顯示一個Client 選擇框。替換允許用戶選擇Client(存在安全隱患)的功能,讓我們根據請求中的子域名自動設置Client,

首先, 更新在 lib/form/PageForm.class.php 文件中的PageForm對象

public function configure()
{
  $this->useFields(array(
    'title',
    'content',
  ));
}

現在這個選擇框已經從Page的表單中消失了。但是,當創建了一個新的Page對象後,其client_id 從沒被設置過。爲了解決這個問題,手動在new和create action中設置關聯的 Client 信息。

public function executeNew(sfWebRequest $request)
{
  $page = new Page();
  $page->Client = $this->getRoute()->getClient();
  $this->form = new PageForm($page);
}

這裏引入了的新方法getClient() ,現在在acClientObjectRoute 類型中還沒有 。 讓我們通過一點小小的修改來把它加到類型中:

// lib/routing/acClientObjectRoute.class.php
class acClientObjectRoute extends sfDoctrineRoute
{
  // ...
 
  protected $client = null;
 
  public function matchesUrl($url, $context = array())
  {
    // ...
 
    $this->client = $client;
 
    return array_merge(array('client_id' => $client->id), $parameters);
  }
 
  public function getClient()
  {
    return $this->client;
  }
}

通過添加 $clien類屬性,並在 matchesUrl() 方法中初始化,我們可以通過路由簡單的獲得這個Client對象。 現在新Page對象的client_id 列會根據現在host中的子域名被自動正確的初始化。

自定義一個對象路由集合

通過使用路由框架,我們現在已經輕鬆的解決了創建Sympal Builder應用時提出的問題。隨着應用的增長,  開發者能夠在管理後臺給其他模塊重用自定義的路由(例如, 每個 Client 可以管理自己的圖片相冊).

創建自定義路由集合的另一個常見原因是添加額外的,常用的路由。比如,假設一個項目使用了很多model,每個model都有一個 is_active 列。 在管理方面,需要有一個簡單的方法來切換任何特定對象的is_active值。首先,修改 acClientObjectRouteCollection 指示它添加一個新的路由到集合中:

// lib/routing/acClientObjectRouteCollection.class.php
protected function generateRoutes()
{
  parent::generateRoutes();
 
  if (isset($this->options['with_is_active']) && $this->options['with_is_active'])
  {
    $routeName = $this->options['name'].'_toggleActive';
 
    $this->routes[$routeName] = $this->getRouteForToggleActive();
  }
}

當集合對象被初始化時調用sfObjectRouteCollection::generateRoutes() 方法,該方法負責創建所有需要的路由並把它們添加到$routes類屬性中。在這個例子中,我們把實際創建路由的工作放到了新創建的 getRouteForToggleActive() 方法中:

protected function getRouteForToggleActive()
{
  $url = sprintf(
    '%s/:%s/toggleActive.:sf_format',
    $this->options['prefix_path'],
    $this->options['column']
  );
 
  $params = array(
    'module' => $this->options['module'],
    'action' => 'toggleActive',
    'sf_format' => 'html'
  );
 
  $requirements = array('sf_method' => 'put');
 
  $options = array(
    'model' => $this->options['model'],
    'type' => 'object',
    'method' => $this->options['model_methods']['object']
  );
 
  return new $this->routeClass(
    $url,
    $params,
    $requirements,
    $options
  );
}

唯一剩下的步驟是在routing.yml 中設定這個路由集合。注意generateRoutes() 在添加新的路由前需要查看 lwith_is_active 參數。 添加這樣的邏輯讓我們在以後使用acClientObjectRouteCollection但不需要 toggleActive 的情況下 有更多的控制能力:

# apps/frontend/config/routing.yml
pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    model:          Page
    prefix_path:    /pages
    module:         pageAdmin
    with_is_active: true

運行app:routes 命令,驗證新的 toggleActive路由已經存在 。唯一剩下要做的是創建一個action來完成實際的工作。因爲你可能需要在多個模塊中使用這個路由集合和相應的action。在apps/backend/lib/action(你需要創建這個目錄)目錄中新建 backendActions.class.php :

# apps/backend/lib/action/backendActions.class.php
class backendActions extends sfActions
{
  public function executeToggleActive(sfWebRequest $request)
  {
    $obj = $this->getRoute()->getObject();
 
    $obj->is_active = !$obj->is_active;
 
    $obj->save();
 
    $this->redirect($this->getModuleName().'/index');
  }
}

最後,把pageAdminActions類的基類換成 backendActions 類。

class pageAdminActions extends backendActions
{
  // ...
}

我們剛纔完成了什麼呢? 通過添加路由到路由集合和一個關聯的基類action,任何新的模塊只要使用acClientObjectRouteCollection和擴展 backendActions類就 可以自動使用這些功能。這樣,通用的功能可以很容易地在許多模塊共享。

路由集合的配置選項

對象路由包含了一系列的選項,允許它可以高度自定義。在很多情況下,開發人員可以使用這些選項配置路由集合而不需要創建一個新的自定義路由集合類。一份詳細的路由集合選項列表可以在《symfony參考指南 》中查到。

Action路由

每個對象路由集合接收三個不同的選項,它們決定在集合中需要生成的正確的路由。不做深入的探討,下面配置的集合會生成7個默認的路由以及一個額外的集合路由和對象路由:

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    actions:      [list, new, create, edit, update, delete, show]
    collection_actions:
      indexAlt:   [get]
    object_actions:
      toggle:     [put]

默認情況下,使用model的主健來生成所有的url和查詢對象。這個,當然,也可以簡單的改變。 比如,下面的代碼會使用slug列而不是主健:

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    column: slug

Model方法

默認情況下,路由會獲取集合路由中所有關聯的對象和查詢對象路由中指定的列。如果你需要重寫這些,在路由上添加 model_methods 選項。在這個例子中,fetchAll()findForRoute() 方法 需要被添加到PageTable 類中。兩個方法都會接收一個request參數數組作爲參數。

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    model_methods:
      list:       fetchAll
      object:     findForRoute

默認參數

最後,假設你需要在請求中添加特定參數,並在集合中的每個路由可用。這個可以通過default_params 選項簡單的實現:

pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    default_params:
      foo:   bar

最後的思考

傳統的路由框架功能是-匹配和生成url- 已發展成爲一個完全可定製的,能滿足項目最複雜的URL需求的能力的系統。通過控制路由對象,特定的URL結構能夠從業務邏輯中抽象出來,並完全獨立存在在路由框架中。最終的結果是更可控制,更靈活和更易於管理的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章