Posts Tagged ‘route’

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

December 31st, 2009

高级路由 (第二部分)

作者: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结构能够从业务逻辑中抽象出来,并完全独立存在在路由框架中。最终的结果是更可控制,更灵活和更易于管理的代码。

More with symfony 1.3 & 1.4-day02-高级路由 (第一部分)

December 29th, 2009

高级路由(第一部分)

作者: Ryan Weaver 翻译:逗派

在symfony内核中,路由框架是链接每个url到特定symfony项目路径的映射,反之亦然。它可以很容易地创造出优雅的url,同时应用程序逻辑保持完全独立。随着最近symfony版本的更新,现在的路由框架也进步了很多。

本章将说明如何创建一个简单的Web应用程序,其中每个客户端使用一个单独的子域名(比如 client1.mydomain.comclient2.mydomain.com). 通过扩展symfony路由框架,这会变得很容易。

本章节需要你的项目使用Doctrine作为ORM框架。

项目设置: 给许多用户使用的CMS系统

在这个项目中,一个虚构的公司 – Sympal Builder – 想创建一个CMS(内容管理系统),使他们的客户可以通过sympalbuilder.com子域名建立网站 。具体来说,客户XXX可以通过xxx.sympalbuilder.com查看他的网站,通过xxx.sympalbuilder.com/backend.php使用管理界面。

Sympal 的名字借用Jonathan Wage的Sympal, 一个使用symfony开发的内容管理框架(CMF)

这个项目有两个基本需求:

  • 用户应该能够创建网页,并可以指定这些网页的标题,内容,以及url网址。
  • 整个应用建立在一个symfony项目中,并且能通过子域名检测出客户和加载正确的数据来处理所有客户网站的前台和后台。

创建此应用程序,服务器将需要设置所有的*. sympalbuilder.com路由到相同的文件根目录-该symfony项目的web目录。

Schema和数据

该项目的数据库包括ClientPage对象,每个Client代表一个子域名的网站,每个网站有许多Page对象组成。

# config/doctrine/schema.yml
Client:
  columns:
    name:       string(255)
    subdomain:  string(50)
  indexes:
    subdomain_index:
      fields:   [subdomain]
      type:     unique

Page:
  columns:
    title:      string(255)
    slug:       string(255)
    content:    clob
    client_id:  integer
  relations:
    Client:
      alias:        Client
      foreignAlias: Pages
      onDelete:     CASCADE
  indexes:
    slug_index:
      fields:   [slug, client_id]
      type:     unique

虽然每个表的索引不是必需的,但是应用程序中很多查询需要经常使用这些列,所以加入索引是一个不错的主意。

为了使项目能运行,请将下面的测试数据放到 data/fixtures/fixtures.yml 文件:

# data/fixtures/fixtures.yml
Client:
  client_pete:
    name:      Pete's Pet Shop
    subdomain: pete
  client_pub:
    name:      City Pub and Grill
    subdomain: citypub

Page:
  page_pete_location_hours:
    title:     Location and Hours | Pete's Pet Shop
    content:   We're open Mon - Sat, 8 am - 7pm
    slug:      location
    Client:    client_pete
  page_pub_menu:
    title:     City Pub And Grill | Menu
    content:   Our menu consists of fish, Steak, salads, and more.
    slug:      menu
    Client:    client_pub

测试数据最初引进两个网站,每个网站有一个页面。每个网页的完整url由Client对象中的 subdomain字段和Page对象的 slug字段组成。



http://pete.sympalbuilder.com/location

http://citypub.sympalbuilder.com/menu

路由

每个Sympal Builder网站上的网页直接对应一个 Page模型对象, Page对象定义了网页输出的标题和内容。为了把url链接到特定的Page对象,需要创建一个使用slug字段的sfDoctrineRoute对象。下面的代码使用url匹配的slug字段从数据库中自动查询出一个Page对象:

# apps/frontend/config/routing.yml
page_show:
  url:        /:slug
  class:      sfDoctrineRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show

上面的路由将自动匹配 http://pete.sympalbuilder.com/location页到正确的 Page 对象中. 不幸的是,上面的路由也会匹配 http://pete.sympalbuilder.com/menu, 这意味着餐馆的菜单也会在Pete的网站上显示。在这一点上,路由是不知道客户端子域名的重要性。

为了使应用能够使用,路由需要更加的智能。它应该同时基于 slug client_id来匹配正确的Page, 这个可以通过把子域名(比如pete.sympalbuilder.com)匹配到Client对象上的subdomain来决定。为实现这一目标,我们将充分利用路由框架来创建自定义的路由类。

但是,首先,我们需要了解一些路由系统是如何工作的背景。

路由是如何工作的

在symfony中一个路由就是指一个 sfRoute 对象,它有两个重要的工作:

  • 生成一个url:例如,如果您传递一个slug参数给page_show方法,它应该能够产生一个真正的url(例如,/location)。
  • 匹配传入的url:对于每个传入的请求URL,每个路由必须能够确定url是否“匹配”自己的路由要求。

每个路由的信息一般在每个应用程序的config目录下的app/yourappname/config/routing.yml文件设置。回想一下,每个路由就是“一个sfRoute类型的对象”。那么,这些简单YAML的记录是如何转化成为sfRoute对象的呢?

路由缓存配置处理器

尽管大多数路由在YAML文件上定义,在请求的时候,文件中的每个记录都会通过一个叫缓存配置处理器的特殊类型来转化成实际的对象。最终的结果是每一个应用程序中的路由都对应一个PHP代码。这一过程的细节超出了本章的范围,让我们直接看一下结果吧,编译后的page_show路由代码看起来像:

new sfDoctrineRoute('/:slug', array (
  'module' => 'page',
  'action' => 'show',
), array (
  'slug' => '[^/\\.]+',
), array (
  'model' => 'Page',
  'type' => 'object',
));

每个路由类的名称定义在routing.yml文件中的class字段。如果没有class字段,默认会使用sfRoute类。 另一个常用的路由类型是sfRequestRoute,它允许开发人员创建REST风格的路由。完整的路由类列表和可用选项可以通过《symfony参考指南》查询。

匹配传入的请求到特定的路由

路由框架的主要工作之一是将每个传入的URL匹配到正确的路由对象。sfPatternRouting类代表着核心路由引擎,由它负责上面的匹配任务。尽管它的重要性,开发人员还是很少会直接与sfPatternRouting打交道。

为了匹配正确的路由, sfPatternRouting遍历每个sfRoute,“询问”它是否匹配传入的请求。 在内部,这意味着sfPatternRouting调每个路由对象的sfRoute::matchesUrl()方法。 如果路由不匹配传入的网址,这个方法只返回false。

但是,如果路线匹配传入的URL,sfRoute::matchesUrl()不只是简单的返回trueHowever。 相反,路由对象返回一个参数数组并将其合并到request对象。例如,网址http://pete.sympalbuilder.com/location会匹配page_show路由,它地matchesUrl()方法将返回下面的数组:

array('slug' => 'location')

然后,此信息合并到request对象,这就是为什么我们可以从actions或其他地方获得路由中的变量(比如,slug)

$this->slug = $request->getParameter('slug');

正如您可能已经猜到的,覆盖sfRoute::matchesUrl()是扩展和定制的路由做几乎任何事情的很棒的方式。

创建一个自定义的Route类

为了扩展page_show路由以石塔能够基于Client对的subdomain匹配url, 我们将创建一个新的自定义route类。创建一个名叫 acClientObjectRoute.class.php文件,并把它放到项目的  lib/routing 文件夹 (你需要创建这个文件夹):

// lib/routing/acClientObjectRoute.class.php
class acClientObjectRoute extends sfDoctrineRoute
{
  public function matchesUrl($url, $context = array())
  {
    if (false === $parameters = parent::matchesUrl($url, $context))
    {
      return false;
    }

    return $parameters;
  }
}

唯一的其他步骤是指示page_show 路由使用这个路由类。在routing.yml中,更新这个路由的class 字段:

# apps/fo/config/routing.yml
page_show:
  url:        /:slug
  class:      acClientObjectRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show

到目前为止, acClientObjectRoute 还没有添加额外的功能,但是所有的细节都已经准备到位了。 matchesUrl()方法由两个具体的工作。

在自定义route中添加逻辑

为了给自定义的路由所需的功能,使用下面的代码替换acClientObjectRoute.class.php文件中的内容:

class acClientObjectRoute extends sfDoctrineRoute
{
  protected $baseHost = '.sympalbuilder.com';

  public function matchesUrl($url, $context = array())
  {
    if (false === $parameters = parent::matchesUrl($url, $context))
    {
      return false;
    }

    // return false if the baseHost isn't found
    if (strpos($context['host'], $this->baseHost) === false)
    {
      return false;
    }

    $subdomain = str_replace($this->baseHost, '', $context['host']);

    $client = Doctrine_Core::getTable('Client')
      ->findOneBySubdomain($subdomain)
    ;

    if (!$client)
    {
      return false;
    }

    return array_merge(array('client_id' => $client->id), $parameters);
  }
}

开始的parent::matchesUrl() 方法调用很重要因为它运行了正常的路由匹配过程。在这个例子中,因为URL /location 匹配 page_show 路由, parent::matchesUrl() 将返回一个包含匹配的 slug 参数的数组。

array('slug' => 'location')

换句话说,所有路由匹配过程中的困难都已经为我们完成了。这使得方法剩余部分只要关注于匹配正确的 Client 子域名。

public function matchesUrl($url, $context = array())
{
  // ...

  $subdomain = str_replace($this->baseHost, '', $context['host']);

  $client = Doctrine_Core::getTable('Client')
    ->findOneBySubdomain($subdomain)
  ;

  if (!$client)
  {
    return false;
  }

  return array_merge(array('client_id' => $client->id), $parameters);
}

通过一个简单的字符串替换,我们可以隔离得到host中的子域名,然后查询数据库判断是否有 Client 对象包含该子域名。如果没有  Client 对象匹配该子域名,我们返回 false 表示传入的请求没有匹配路由。但是,如果有 Client 对象包含了正确的子域名,我们将一个额外的参数  client_id 合并到返回的数组中。

这个 传给matchesUrl()的$context数组预置了很多关于当前请求的很多有用信息,包括 host, is_secure 布尔值request_uri, HTTP请求方法等。

但是,自定义的路由真正完成的是什么呢? acClientObjectRoute 类现在做了下面两点:

  • 传入的 $url 现在只有当其 host 包含了属于一个 Client 对象的子域名时才会被匹配.
  • 如果路由匹配成功,会返回一个额外的Client对象中获得的client_id 参数并最终合并到reqeust参数中。

使用自定义路由

现在正确的client_id 参数通过acClientObjectRoute返回了, 我们可疑通过reqeust对象获得它了。For 比如,在 page/show action中可以使用client_id 来获得正确的 Page 对象:

public function executeShow(sfWebRequest $request)
{
  $this->page = Doctrine_Core::getTable('Page')->findOneBySlugAndClientId(
    $request->getParameter('slug'),
    $request->getParameter('client_id')
  );

  $this->forward404Unless($this->page);
}

这个 findOneBySlugAndClientId() 方法是在Doctrine1.2中新的 魔幻查找器 ,可以根据多个字段查询对象。

这已经很好了,但是路由框架提供你一个更加完美的解决方案。首先, 添加下面的方法到 acClientObjectRoute 类中:

protected function getRealVariables()
{
  return array_merge(array('client_id'), parent::getRealVariables());
}

根据这最后的代码,action中现在可疑完全依靠route就能获得正确的Page对象, page/show action可以精减到一行代码:

public function executeShow(sfWebRequest $request)
{
  $this->page = $this->getRoute()->getObject();
}

没有任何额外的工作,上面的代码将同时根据slugclient_id字段查询得到Page对象。 此外,像所有的对象路由一样,如果没有找到相应的对象,这个action会自动跳转到404页面。

但它是如何工作的呢?对象路由,比如 sfDoctrineRouteacClientObjectRoute 就是继承它的类, 会根据路由中的url关键字自动查询关联对象。比如这个 page_show 路由,在url中包含了 :slug 变量, 就会使用 slug字段来查询获得Page对象

然而在这个应用中,page_show路由也必需同时使用client_id 字段来查询Page对象。要做到这一点,我们需要重写 sfObjectRoute::getRealVariables()方法, 这个方法在内部调用来决定需要使用哪些列来查询对象。通过添加client_id字段到数组, 这个 acClientObjectRoute 会同时根据 slugclient_id 列来做查询。

对象路由自动忽略哪些没有真实列对应的变量。比如,如果url关键字中包含 :page变量,但是在相关的表中没有  page 列,这个变量就会被忽略。

目前为止,自定义路由类只做了一点修改就完成了所有所需的功能。在下一章中,我们会重用这个路由来创建一个特定客户的管理功能。

生成正确的路由

剩下的一个小问题是如何生成路由。假设使用下面的代码创建一个网页的链接:

<?php echo link_to('Locations', 'page_show', $page) ?>
Generated url: /location?client_id=1

正如你所看到的, 这个client_id 会自动添加到url。 这是因为路由尝试使用所有可用变量来生成的网址。 因为路由同时知道slugclient_id参数,当生成路由时会同时使用两者。

要解决这个问题,添加下面的方法到acClientObjectRoute 类中:

protected function doConvertObjectToArray($object)
{
  $parameters = parent::doConvertObjectToArray($object);

  unset($parameters['client_id']);

  return $parameters;
}

当生成对象路由时,它会通过调用doConvertObjectToArray()来试图获得所有需要的信息。默认情况下,  client_id$parameters 数组中返回。 但是通过unset, 我们防止它被包含在生成的url中。记住我们能删除它是因为子域名自身包含了Client 对象信息

你可以重写整个doConvertObjectToArray() 过程,只要在model类中添加toParams()就可以了,这个方法应该返回一个你想在生成路由时需要使用的参数数组

未完待续…