Caution: You are browsing the legacy symfony 1.x part of this website.

第十二天:管理程序生成器

经过昨天的修改,Jobeet前台(frontend)程序已经可以满足招聘和求职者的需要。接下来 我们讨论Jobeet后台(backend)程序。

今天,我们的目标是用一小时时间,给jobeet开发一个完整的后台界面。这都要感谢symfony 的admin generator(管理程序生成器,下面简称AG)提供的强大功能。

创建后台

我们首先要创建后台(backend)程序,你应该还记得如何使用generate:app来 创建新程序:

$ php symfony generate:app --escaping-strategy=on --csrf-secret=UniqueSecret1 backend

需要说明的是,虽然后台程序只供jobeet管理员使用,但是我们仍然使用了symfony所有 内建安全特性。

tip

If you want to use special characters in the password, like a dollar sign ($) for instance, you need to properly escape them on the command line:

$ php symfony generate:app --csrf-secret=Unique\$ecret backend

现在后台程序已经可以使用了,http://jobeet.localhost/backend.php用于生产(prod)环境, http://jobeet.localhost/backend_dev.php用于开发(dev)环境。

note

当生成前台程序时,给生产环境生成的前端控制器为index.php。因为在同一目录中只能有一个 index.php文件,所以symfony会将index.php赋予最先生成的程序,之后创建的程序前端 控制器则以程序名命名。

现在,如果你尝试用doctrine:data-load重新导入测试数据的话,它不会正常工作。因为 JobeetJob::save()方法需要从前台程序访问app.yml配置文件。而现在我们有两个程序, symfony会使用它最先找到程序——后台程序。

我们可以通过另一种方式让JobeetJob::save()使用正确的app.yml配置,通过第8天的学习我们知道, yml配置是可以分为不同的级别。如果将apps/frontend/config/app.yml移动到config/app.yml, 那么app.yml便成为程序级的配置,可以被所有程序共享,从而解决这个问题。因为AG中将广泛地 用到model类,因此我们也需要app.yml中的变量。

tip

doctrine:data-load命令拥有--application选项,可以为data-load指定目标程序:

$ php symfony doctrine:data-load --application=frontend

后台模块

前台程序中创建模块时,我们使用doctrine:generate-module命令,生成一个基于模型类的 基本的CRUD模块。在后台程序中创建模块,我们使用doctrine:generate-admin命令,这个命令 可以为一个model类生成完整的后台界面:

$ php symfony doctrine:generate-admin backend JobeetJob --module=job
$ php symfony doctrine:generate-admin backend JobeetCategory --module=category

上面两个命令为JobeetJob类和JobeetCategory类分别创建jobcategory两个模块。

这里的--module选项作用是,使用指定的模块名称替换自动生成的默认名称(如果没有指定, JobeetJob类将默认生成名称为jobeet_job模块,指定后为job)。

这个命令同时会为每个模块生成一个定制的路由规则:

# apps/backend/config/routing.yml
jobeet_job:
  class: sfDoctrineRouteCollection
  options:
    model:                JobeetJob
    module:               job
    prefix_path:          job
    column:               id
    with_wildcard_routes: true

我们发现路由规则中class项使用sfDoctrineRouteCollection类,这并不不奇怪,因为管理界面 主要目的是管理模型对象。

路由配置中还定义了一些我们以前没有见过的选项:

  • prefix_path: 为生成的路由规则定义前缀路径(例如,edit页面类似于/job/1/edit
  • column: 定义用在URL中的数据表字段,用于引用一个对象。
  • with_wildcard_routes:与标准的CRUD相比,管理界面有更多的操作,这个选项可以不编辑路由的情况下, 定义更多对象和动作集。

tip

我们可以使用help参数查看命令的帮助信息。

$ php symfony help doctrine:generate-admin

它将显示所有的参数、选项和例子。

Backend Look and Feel

你马上就可以使用生成的模块:

http://jobeet.localhost/backend_dev.php/job
http://jobeet.localhost/backend_dev.php/category

与前几天生成的简单模块相比,管理模块有更多的功能。即使我们一行PHP代码也不写, 仍然可以得打功能强大的模块:

  • 对象列表是分页显示的
  • 这个列表可排序
  • 这个列表可筛选
  • 对象可以被创建, 编辑和删除
  • 表单可以验证
  • 选中的这些对象可以批量删除
  • Flash messages给用户即时的消息反馈
  • 还有很多…

admin generator提供创建后台界面需要的所有功能。

为了获得良好的用户体验,后台默认layout可以自定义。我们给后台界面添加了一个 简单的导航栏,方便在job和category之间切换。用

下面的代码替换默认~layout|Layout~.php内容:

// apps/backend/templates/layout.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Jobeet Admin Interface</title>
    <link rel="shortcut icon" href="/favicon.ico" />
    <?php use_stylesheet('admin.css') ?>
    <?php include_javascripts() ?>
    <?php include_stylesheets() ?>
  </head>
  <body>
    <div id="container">
      <div id="header">
        <h1>
          <a href="<?php echo url_for('@homepage') ?>">
            <img src="/legacy/images/logo.jpg" alt="Jobeet Job Board" />
          </a>
        </h1>
      </div>
 
      <div id="menu">
        <ul>
          <li>
            <?php echo link_to('Jobs', '@jobeet_job_job') ?>
          </li>
          <li>
            <?php echo link_to('Categories', '@jobeet_category_category') ?>
          </li>
        </ul>
      </div>
 
      <div id="content">
        <?php echo $sf_content ?>
      </div>
 
      <div id="footer">
        <img src="/legacy/images/jobeet-mini.png" />
        powered by <a href="/">
        <img src="/legacy/images/symfony.gif" alt="symfony framework" /></a>
      </div>
    </div>
  </body>
</html>

同前台一样,我们给后台准备了admin.css文件。这个文件已经在第四天被安装在web/css目录下了。

The admin generator look and feel

最后在routing.yml修改后台默认主页:

# apps/backend/config/routing.yml
homepage:
  url:   /
  param: { module: job, action: index }

symfony缓存

如果你已经迫不及待地打开了apps/backend/modules/目录中的文件,你会惊奇的 发现/templates目录中竟然没有任何模板文件, actions.class.php文件中也没有 任何动作:

// apps/backend/modules/job/actions/actions.class.php
require_once dirname(__FILE__).'/../lib/jobGeneratorConfiguration.class.php';
require_once dirname(__FILE__).'/../lib/jobGeneratorHelper.class.php';
 
class jobActions extends autoJobActions
{
}

什么都没有,那它是如何工作的?其实,如果你仔细观察就会注意到jobActions继承了 autoJobActions类(以前都是sfActions类)。autoJobActions类由symfony自动生成, 在cache/backend/dev/modules/autoJob/目录下,它包含后台使用的“真正”模块(模板和动作):

// cache/backend/dev/modules/autoJob/actions/actions.class.php
class autoJobActions extends sfActions
{
  public function preExecute()
  {
    $this->configuration = new jobGeneratorConfiguration();
 
    if (!$this->getUser()->hasCredential(
      $this->configuration->getCredentials($this->getActionName())
    ))
    {
 
// ...

我们对admin generator这种工作方式可能似曾相识,事实上,它与model和form类非常相似, symfony基于schema.yml文件内容生成model类和form类。admin generator中的模块可以在 config/generator.yml中配置,下面是默认文件:

# apps/backend/modules/job/config/generator.yml
generator:
  class: sfDoctrineGenerator
  param:
    model_class:           JobeetJob
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          jobeet_job
    with_doctrine_route:   1
 
    config:
      actions: ~
      fields:  ~
      list:    ~
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~

每次更新generator.yml,symfony都会重新生成cache。我们今天将看到,自定义管理模块 是件非常简单、快速而有意思的事情。

note

只有在开发环境里才会自动重新生成cache文件。在生成环境中,你需要通过 cache:clear手动清空。

后台配置

管理模块可以通过编辑generator.yml配置文件中的config键来配置。配置由7部分组成:

  • actions:动作默认配置,建立在列表和表单上。
  • fields: 字段默认配置。
  • list: 列表配置。
  • filter: 过滤配置。
  • form: new/edit 表单配置。
  • edit: edit页特殊配置。
  • new: new页特殊配置。

让我们开始配置吧。

Title Configuration

category模块的listeditnew的标题,都可以通过定义title选项来设置:

# apps/backend/modules/category/config/generator.yml
config:
  actions: ~
  fields:  ~
  list:
    title: Category Management
  filter:  ~
  form:    ~
  edit:
    title: Editing Category "%%name%%"
  new:
    title: New Category

edittitle选项包含变量:所有被包围在%%中的字符串都会被替换成对应的对象列。

Titles

job模块的配置也非常相似:

# apps/backend/modules/job/config/generator.yml
config:
  actions: ~
  fields:  ~
  list:
    title: Job Management
  filter:  ~
  form:    ~
  edit:
    title: Editing Job "%%company%% is looking for a %%position%%"
  new:
    title: Job Creation

字段(fields)配置

视图(list, new, 和edit)由字段(fields)组成。一个字段可以是model类的一个列, 也可以是一个自定义列,稍后我们会看到自定义列。

默认字段配置可以在fields部分定义:

# apps/backend/modules/job/config/generator.yml
config:
  fields:
    is_activated: { label: Activated?, help: Whether the user has activated the job, or not }
    is_public:    { label: Public?, help: Whether the job can also be published on affiliate websites, or not }

Fields Configuration

fields部分配置将影响所有视图中的字段,这意味着list, edit, 和 new视图中 is_activated字段的label都将改变。

admin generator配置基于配置级联原则。例如,如果你只想改变list视图的label, 可以在list部分定义一个fields选项:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    fields:
      is_public:    { label: "Public? (label for the list)" }

任何主fields部分下的配置都可以被特殊配置覆盖。覆盖规则如下:

  • newedit继承formform继承fields
  • list继承fields
  • filter继承fields

note

对于form部分(form, edit, 和new),labelhelp配置将覆盖 在form类中定义的值。

列表视图配置

display

默认情况下,列表中显示的model类的所有列。可以使用display选项自定义定义显示哪些列:

# apps/backend/modules/category/config/generator.yml
config:
  list:
    title:   Category Management
    display: [=name, slug]

上面表示只显示nameslug两列值。name列前的=表示,name将以链接的形式显示。

Table list

同样设置job模块中display部分:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    title:   Job Management
    display: [company, position, location, url, is_activated, email]

layout

列表可以通过不同的layout显示。默认地,layout的显示方式为平板式(~tabular|Tabular Layout~), 这意味着所有值都是按列显示的。但是对于job模块,最好用堆叠式(`stacked)的layout, 我们使用另一个内建的layout:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    title:   Job Management
    layout:  stacked
    display: [company, position, location, url, is_activated, email]
    params:  |
      %%is_activated%% <small>%%category_id%%</small> - %%company%%
       (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%)

在堆叠式(stacked)layout,每个对象显示为单独一行,通过params选项定义。

note

虽然已经不按列显示各项,但display选项仍然要保留,用于用户排序各项。

虚拟("virtual")列

这个配置中%%category_id%%部分显示category主键,显示分类名会更直观。

其实%%标记中的变量不必真正对应数据库的字段,只要model类中存在相应getter方法 (如getFoo())就可以正确显示。

所以,要显示分类名,我们可以在JobeetJob类中 定义getCategoryName()方法,并将%%category_id%%替换为%%category_name%%来实现。

不过,我们也可以通过JobeetJob类中getJobeetCategory()方法来实现显示分类名, 这个类返回相关的category对象。如果你用%%jobeet_category%%,它会像JobeetCategory__toString()方法一样将对象转换为字符串。

# apps/backend/modules/job/config/generator.yml
%%is_activated%% <small>%%jobeet_category%%</small> - %%company%%
 (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%)

Stacked layout

sort

作为管理员,你可能对最近发表的job感兴趣。你可以通过sort选项设置默认用来排序的字段:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    sort: [expires_at, desc]

max_per_page

默认情况下list每页最多显示20条记录,这可以通过max_per_page选项修改:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    max_per_page: 10

Max per page

批处理动作batch_actions

在list中,一个动作可以运行在几个对象上。category不需要这些批处理动作,删除它们:

# apps/backend/modules/category/config/generator.yml
config:
  list:
    batch_actions: {}

Remove the batch actions

The batch_actions option defines the list of batch actions. The empty array allows the removal of the feature.

默认情况下,框架为每个模块定义一个delete批处理动作 ,但对于job模块,我们需要 一个将一些选中的job的有效期延长30天的方法,我们需要添加extend批处理动作:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    batch_actions:
      _delete:    ~
      extend:     ~

这些以下划线(_)开头的动作都是框架内建动作。如果你现在刷新浏览器, 并选择extend批处理动作,symfony将提示你需要创建executeBatchExtend()方法:

// apps/backend/modules/job/actions/actions.class.php
class jobActions extends autoJobActions
{
  public function executeBatchExtend(sfWebRequest $request)
  {
    $ids = $request->getParameter('ids');
 
    $q = Doctrine_Query::create()
      ->from('JobeetJob j')
      ->whereIn('j.id', $ids);
 
    foreach ($q->execute() as $job)
    {
      $job->extend(true);
    }
 
    $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.');
 
    $this->redirect('@jobeet_job_job');
  }
}

这些选中的主键被存储到请求参数ids中。通过传递给JobeetJob::extend()方法一个 额外的参数,绕开方法中的一些检查。

我们需要更新extend()让它接受新参数:

// lib/model/doctrine/JobeetJob.class.php
class JobeetJob extends BaseJobeetJob
{
  public function extend($force = false)
  {
    if (!$force && !$this->expiresSoon())
    {
      return false;
    }
 
    $this->setExpiresAt(date('Y-m-d', time() + 86400 * sfConfig::get('app_active_days')));
    $this->save();
 
    return true;
  }
 
  // ...
}

执行完extend批处理动作后,页面将跳转到job模块主页。

Custom batch actions

对象动作object_actions

在列表中有一个额外的列,这个列中包含用于控制某一对象的动作(editdelete)。 对于category模块来说,我们已经有在名字加上了edit链接,并且我们不需要从列表中 直接删除某个对象,所以可以删除这些动作:

# apps/backend/modules/category/config/generator.yml
config:
  list:
    object_actions: {}

对于job模块,我们则要保留现有的动作,并添加一个新的extend动作,与我们添加的 批处理动作相似:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    object_actions:
      extend:     ~
      _edit:      ~
      _delete:    ~

同批处理动作一样,_delete_edit动作是框架内建动作。我们需要定义listExtend() 方法使extend链接工作:

// apps/backend/modules/job/actions/actions.class.php
class jobActions extends autoJobActions
{
  public function executeListExtend(sfWebRequest $request)
  {
    $job = $this->getRoute()->getObject();
    $job->extend(true);
 
    $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.');
 
    $this->redirect('@jobeet_job_job');
  }
 
  // ...
}

Custom object action

actions

我们已经看到,如何将一个动作与一个对象(object_actions选项)或多个对象 (batch_actions选项)建立联系。而actions选项则没有将动作与任何现有的对象相联系, 它更像创建了一个新对象。现在让我们移除默认的new动作,并添加一个新动作, 用来删除过期60天的所有job:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    actions:
      deleteNeverActivated: { label: Delete never activated jobs }

Until now, all actions we have defined had ~, which means that symfony configures the action automatically. Each action can be customized by defining an array of parameters. The label option overrides the default label generated by symfony.

By default, the action executed when you click on the link is the name of the action prefixed with list.

Create the listDeleteNeverActivated action in the job module:

// apps/backend/modules/job/actions/actions.class.php
class jobActions extends autoJobActions
{
  public function executeListDeleteNeverActivated(sfWebRequest $request)
  {
    $nb = Doctrine::getTable('JobeetJob')->cleanup(60);
 
    if ($nb)
    {
      $this->getUser()->setFlash('notice', sprintf('%d never activated jobs have been deleted successfully.', $nb));
    }
    else
    {
      $this->getUser()->setFlash('notice', 'No job to delete.');
    }
 
    $this->redirect('@jobeet_job_job');
  }
 
  // ...
}

我们重用了昨天定义的JobeetJobTable::cleanup()方法。这是MVC模式提供重用的一个很好例子。

note

你也可以给动作传送一个action参数:

deleteNeverActivated: { label: Delete never activated jobs, action: foo }

Actions

table_method

通过调试工具栏(web debug toolbar)我们可以看到,显示一次 招聘列表页面需要请求数据库14次。

如果你点击这个数字,可以看到大部分请求都是为每个招聘信息获取分类名。

Number of requests before

要减少请求次数,我们可以改变默认获取信息方法, 使用table_method选项:

# apps/backend/modules/job/config/generator.yml
config:
  list:
    table_method: retrieveBackendJobList

Now you must create the retrieveBackendJobList method in JobeetJobTable located in lib/model/doctrine/JobeetJobTable.class.php.

// lib/model/doctrine/JobeetJobTable.class.php
class JobeetJobTable extends Doctrine_Table
{
  public function retrieveBackendJobList(Doctrine_Query $q)
  {
    $rootAlias = $q->getRootAlias();
    $q->leftJoin($rootAlias . '.JobeetCategory c');
    return $q;
  }
 
  // ...

The retrieveBackendJobList() method adds a join between the job and the category tables and automatically creates the category object related to each job.

现在数据库请求数变为4次:

Number of requests after

表单视图配置

表单视图配置(Form Views Configuration)包括3部分:form, edit, 和 new。 它们可以进行同样的配置,form部分只作为edit部分和new部分回调存在。

display

list部分一样,你可以使用display选项改变字段的显示顺序。但是因为显示的 form是在类中定义的,不要试图删除任何一个表单元素,这将导致意外验证错误。

display选项也可以将表单字段分成不同的组:

# apps/backend/modules/job/config/generator.yml
config:
  form:
    display:
      Content: [category_id, type, company, logo, url, position, location, description, how_to_apply, is_public, email]
      Admin:   [_generated_token, is_activated, expires_at]

上面定义了2个组(ContentAdmin),每组里都包含几个字段,显示如下:

Fields grouping

note

The columns in the Admin group do not show up in the browser yet because they have been unset in the job form definition. They will appear in a few sections when we define a custom job form class for the admin application.

admin generator默认支持数据表之间的多对多关系。所有在category form中,除了有 nameslug输入框外,还有一个下拉框,里面是与category对应的所有affiliates。 在这个页面修改categoryaffiliates之间管理没有意义,删除它:

// lib/form/doctrine/JobeetCategoryForm.class.php
class JobeetCategoryForm extends BaseJobeetCategoryForm
{
  public function configure()
  {
    unset($this['created_at'], $this['updated_at'], $this['jobeet_affiliates_list']);
  }
}

"Virtual" columns

In the display options for the job form, the _generated_token field starts with an underscore (_). This means that the rendering for this field will be handled by a custom partial named _generated_token.php

使用下面内容创建局部模板:

// apps/backend/modules/job/templates/_generated_token.php
<div class="sf_admin_form_row">
  <label>Token</label>
  <?php echo $form->getObject()->getToken() ?>
</div>

在局部模板中调用了当前form对象($form),并通过getObject()方法访问关联的对象。

note

如果需要使用组件,可以用(~)开头。

class

后台的表单是给管理员使用的,我们希望它比用户job表单显示更多的内容。但是现在 并非如此,因为我们曾经在JobeetJobForm类中将一些当时不需要的表单元素删除了。

为让前台和后台使用不同的表单,我们需要再创建一个form类——BackendJobeetJobForm, 它继承JobeetJobForm类。我们重构一下JobeetJobForm类,将unset()语句放到到一个方法中, 这样我们可以通过重载BackendJobeetJobForm中的方法来重设被删除的表单元素:

// lib/form/doctrine/JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm
{
  public function configure()
  {
    $this->removeFields();
 
    $this->validatorSchema['email'] = new sfValidatorAnd(array(
      $this->validatorSchema['email'],
      new sfValidatorEmail(),
    ));
 
    // ...
  }
 
  protected function removeFields()
  {
    unset(
      $this['created_at'], $this['updated_at'],
      $this['expires_at'], $this['is_activated'],
      $this['token']
    );
  }
}
 
// lib/form/doctrine/BackendJobeetJobForm.class.php
class BackendJobeetJobForm extends JobeetJobForm
{
  public function configure()
  {
    parent::configure();
  }
 
  protected function removeFields()
  {
    unset(
      $this['created_at'], $this['updated_at'],
      $this['token']
    );
  }
}

配置文件中的class选项可以覆盖admin generator的默认from类:

# apps/backend/modules/job/config/generator.yml
config:
  form:
    class: BackendJobeetJobForm

note

当我们添加了一个新的类,不要忘记清理缓存。

目前,编辑表单还有些问题:上传的logo不显示,也无法删除。sfWidgetFormInputFileEditable 控件可以生成一个文件上传框控件:

// lib/form/doctrine/BackendJobeetJobForm.class.php
class BackendJobeetJobForm extends JobeetJobForm
{
  public function configure()
  {
    parent::configure();
 
    $this->widgetSchema['logo'] = new sfWidgetFormInputFileEditable(array(
      'label'     => 'Company logo',
      'file_src'  => '/uploads/jobs/'.$this->getObject()->getLogo(),
      'is_image'  => true,
      'edit_mode' => !$this->isNew(),
      'template'  => '<div>%file%<br />%input%<br />%delete% %delete_label%</div>',
    ));
 
    $this->validatorSchema['logo_delete'] = new sfValidatorPass();
  }
 
  // ...
}

sfWidgetFormInputFileEditable控件有以下几个选项:

  • file_src: 当前上传文件的web路径
  • is_image: 如果是true,文件将作为图片处理
  • edit_mode: 表单是不是在编辑模式下
  • with_delete:是否显示删除当前文件选框
  • template: 显示用的模板

File upload

tip

因为生成的模板定义许多了classid属性,所以admin generator的外观很容易修改。 如logo表单元素可以使用sf_admin_form_field_logo定制。每个字段都有一个依存的类, 如sf_admin_textsf_admin_boolean

edit_mode选项使用了sfDoctrineRecord::isNew()方法。

如果form中model对象是新的,将返回true,否则返回false。当需要根据对象状态 来使用不同的控件(widgets)或验证器时,这个很有用。

过滤器配置(Filters Configuration)

配置filter和配置form视图完全一样。事实上,filter就是form。form类是由doctrine:build-all生成的。 你也可以用doctrine:build-filters重新生成它们。

form过滤类位于lib/filter目录下,每个model类都有一个对应的filter类(JobeetJobFormFilter 对应JobeetJobForm)。

我们为category模块移除所有过滤字段:

# apps/backend/modules/category/config/generator.yml
config:
  filter:
    class: false

job模块中,我们删除一些过滤用的字段:

# apps/backend/modules/job/config/generator.yml
filter:
  display: [category_id, company, position, description, is_activated, is_public, email, expires_at]

因为filter总是可选的,不需要覆盖filter form类配置显示的字段。

Filters

自定义动作(Actions Customization)

当配置不够用时,你可以在action类中添加新方法,也可以覆盖已有方法:

Method Description
executeIndex() list view action
executeFilter() Updates the filters
executeNew() new view action
executeCreate() Creates a new Job
executeEdit() edit view action
executeUpdate() Updates a Job
executeDelete() Deletes a Job
executeBatch() Executes a batch action
executeBatchDelete() Executes the _delete batch action
processForm() Processes the Job form
getFilters() Returns the current filters
setFilters() Sets the filters
getPager() Returns the list pager
getPage() Gets the pager page
setPage() Sets the pager page
buildCriteria() Builds the Criteria for the list
addSortCriteria() Adds the sort Criteria for the list
getSort() Returns the current sort column
setSort() Sets the current sort column

因为这里每个方法只做一件事,所有不需要大量修改就可以改变它们的功能。

自定义模板(Templates Customization)

我们已经看到,如何自定义admin generator生成的模板,感谢classid属性。

至于这些类,你也可以覆盖原来的模板。因为模板是纯PHP文件并且不是PHP类, 一个模板可以被模块里的同名模板所覆盖(例如,apps/backend/modules/job/templates/目录对应job管理模块):

Template Description
_assets.php Renders the CSS and JS to use for templates
_filters.php Renders the filters box
_filters_field.php Renders a single filter field
_flashes.php Renders the flash messages
_form.php Displays the form
_form_actions.php Displays the form actions
_form_field.php Displays a singe form field
_form_fieldset.php Displays a form fieldset
_form_footer.php Displays the form footer
_form_header.php Displays the form header
_list.php Displays the list
_list_actions.php Displays the list actions
_list_batch_actions.php Displays the list batch actions
_list_field_boolean.php Displays a single boolean field in the list
_list_footer.php Displays the list footer
_list_header.php Displays the list header
_list_td_actions.php Displays the object actions for a row
_list_td_batch_actions.php Displays the checkbox for a row
_list_td_stacked.php Displays the stacked layout for a row
_list_td_tabular.php Displays a single field for the list
_list_th_stacked.php Displays a single column name for the header
_list_th_tabular.php Displays a single column name for the header
_pagination.php Displays the list pagination
editSuccess.php Displays the edit view
indexSuccess.php Displays the list view
newSuccess.php Displays the new view

最终配置

这是最后的配置文件内容:

# apps/backend/modules/job/config/generator.yml
generator:
  class: sfDoctrineGenerator
  param:
    model_class:           JobeetJob
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          jobeet_job
    with_doctrine_route:   1
 
    config:
      actions: ~
      fields:
        is_activated: { label: Activated?, help: Whether the user has activated the job, or not }
        is_public:    { label: Public? }
      list:
        title:         Job Management
        layout:        stacked
        display:       [company, position, location, url, is_activated, email]
        params:  |
          %%is_activated%% <small>%%JobeetCategory%%</small> - %%company%%
           (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%)
        max_per_page:  10
        sort:          [expires_at, desc]
        batch_actions:
          _delete:    ~
          extend:     ~
        object_actions:
          extend:     ~
          _edit:      ~
          _delete:    ~
        actions:
          deleteNeverActivated: { label: Delete never activated jobs }
        table_method: retrieveBackendJobList
      filter:
        display: [category_id, company, position, description, is_activated, is_public, email, expires_at]
      form:
        class:     BackendJobeetJobForm
        display:
          Content: [category_id, type, company, logo, url, position, location, description, how_to_apply, is_public, email]
          Admin:   [_generated_token, is_activated, expires_at]
      edit:
        title: Editing Job "%%company%% is looking for a %%position%%"
      new:
        title: Job Creation
 
# apps/backend/modules/category/config/generator.yml
generator:
  class: sfDoctrineGenerator
  param:
    model_class:           JobeetCategory
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          jobeet_category
    with_doctrine_route:   1
 
    config:
      actions: ~
      fields:  ~
      list:
        title:   Category Management
        display: [=name, slug]
        batch_actions: {}
        object_actions: {}
      filter:
        class: false
      form:
        actions:
          _delete: ~
          _list:   ~
          _save:   ~
      edit:
        title: Editing Category "%%name%%"
      new:
        title: New Category

通过这两个配置文件,我们在很短的时间内已经开发了很好的后台界面。

tip

You already know that when something is configurable in a YAML file, there is also the possibility to use plain PHP code. For the admin generator, you can edit the apps/backend/modules/job/lib/jobGeneratorConfiguration.class.php file. It gives you the same options as the YAML file but with a PHP interface. To learn the method names, have a look at the generated base class in cache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php.

TIP 你已经知道什么东西可以YAML文件中配置,YAML文件中允许使用纯PHP代码。对于admin generator, 你可以编辑apps/backend/modules/job/lib/jobGeneratorConfiguration.class.php文件。 它提供了与YAML文件相同选项,但是PHP接口。想了解这些方法,可以看一下生成在缓存中的base类 cache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php.

明天见

只用了1个小时时间,我们已经创建一个功能完整的后台界面。我们只写了不到50行PHP代码。 对于这么多功能来说,不算太糟糕。

明天,我们经看到如何使用用户名和密码,保证后台安全。是时候讨论symfony User类了。