作者:Ryan Weaver 翻译:豆派
要完成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
, update
和 delete
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参考指南 》中查到。
每个对象路由集合接收三个不同的选项,它们决定在集合中需要生成的正确的路由。不做深入的探讨,下面配置的集合会生成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_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结构能够从业务逻辑中抽象出来,并完全独立存在在路由框架中。最终的结果是更可控制,更灵活和更易于管理的代码。