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

Ngày 12: Admin Generator

Tóm tắt

Với những việc chúng ta đã làm ngày hôm qua, frontend application đã đầy đủ tính năng cho việc gửi công việc mới và xem danh sách công việc. Bây giờ chúng ta sẽ nói về backend application.

Hôm nay, nhờ có chức năng admin generator của symfony, chúng ta sẽ phát triển toàn bộ backend interface cho Jobeet chỉ trong 1 giờ.

Tạo Backend

Việc đầu tiên là phải tạo backend application. Có thể bạn đã biết cách thực hiện việc này thông qua lệnh generate:app:

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

Mặc dù backend application chỉ được dùng bởi administrators, nhưng chúng ta vẫn enable tất cả các tính năng bảo mật có sẵn của symfony.

Backend application bây giờ đã có thể truy cập ở http://jobeet.localhost/backend.php đối với môi trường prod, và http://jobeet.localhost/backend_dev.php với môi trường dev.

note

Khi bạn tạo frontend application, production front controller có tên là index.php. Do bạn chỉ có thể có một file index.php trong thư mục, symfony sẽ dùng file index.php cho production front controller đầu tiên còn các production front controller khác thì có tên như tên application.

Nếu bạn thử reload data fixtures với lệnh doctrine:data-load , nó sẽ không làm việc. Đó là bởi vì phương thức JobeetJob::save() cần truy cập file cấu hình app.yml từ frontend application. Bây giờ chúng ta có 2 application, symfony sẽ sử dụng file đầu tiên nó tìm thấy, đó là file nằm trong backend.

Nhưng như đã nói trong ngày 8, các setting có thể được cấu hình ở các mức khác nhau. Bằng cách chuyển nội dung từ file apps/frontend/config/app.yml sang file config/app.yml, những thiết lập này có thể được chia sẻ giữa các application khác nhau và vấn đề được giải quyết. Chúng ta sẽ dùng các class model rất nhiều trong admin generator, vì thế chúng ta cần các biến thiết lập trong file app.yml cho backend application.

tip

Lệnh doctrine:data-load cũng có option --application. Vì thế nếu bạn cần một vài cấu hình cụ thể từ một application, bạn có thể dùng option này:

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

Module Backend

Với frontend application, lệnh doctrine:generate-module được dùng để tạo module với các thao tác cơ bản CRUD dựa trên model class. Với backend, lệnh doctrine:generate-admin cũng được dùng để tạo ra backend interface với đầy đủ các chức năng ứng với một model class:

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

Hai lệnh trên tạo ra module jobcategory ứng với các class model JobeetJobJobeetCategory.

Option --module sẽ thay thế tên của module được tạo mặc định bởi lệnh (là jobeet_job với lớp JobeetJob).

Lệnh này cũng tạo route cho mỗi module:

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

Bạn sẽ không ngạc nhiên khi thấy admin generator dùng route class sfDoctrineRouteCollection, do mục đích chính của một admin interface là quản lý vòng đời của các model objet.

Route cũng có một vài option chúng ta chưa thấy trước đây:

  • prefix_path: Defines the prefix path for the generated route (for instance, the edit page will be something like /job/1/edit).
  • column: Defines the table column to use in the URL for links that references an object.
  • with_wildcard_routes: As the admin interface will have more than the classic CRUD operations, this option allows to define more object and collection actions without editing the route.

tip

Nên đọc help trước khi sử dụng một task mới.

$ php symfony help doctrine:generate-admin

Nó sẽ cung cấp cho bạn tất cả các argument và option cùng một vài ví dụ đơn giản.

Backend Look and Feel

Bây giờ, bạn có thể sử dụng các module đã được tạo ra:

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

Admin module có nhiều tính năng hơn các module đơn giản được tạo tự động trong những ngày trước. Mặc dù không phải viết dòng code PHP nào, chúng ta cũng có đầy đủ những tính năng sau:

  • Hiển thị danh sách cách đối tượng có phân trang
  • Có thể sắp xếp danh sách
  • Có thể lọc danh sách
  • Có thể tạo, sửa, và xóa đối tượng
  • Có thể chọn nhiều đối tượng để cùng thực hiện một thao tác nào đó (batch)
  • Form nhập được validation
  • Hiển thị Flash messages tới user
  • ... và nhiều tính năng khác

Admin generator cung cấp đầy đủ các tính năng bạn cần để tạo một backend interface và đơn giản trong việc cấu hình.

Để thuận tiện cho người dùng, ta thay đổi lại layout mặc định của backend. Chúng ta sẽ thêm một menu đơn giản để dễ dàng chuyển qua lại giữa các modules khác nhau. Thay thế nội dung trong file layout.php bằng đoạn sau:

// 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/jobeet.gif" alt="Jobeet Job Board" />
          </a>
        </h1>
      </div>
 
      <div id="menu">
        <ul>
          <li><?php echo link_to('Jobs', '@jobeet_job') ?></li>
          <li><?php echo link_to('Categories', '@jobeet_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>

Giống như frontend, chúng tôi cũng chuẩn bị một file stylesheet đơn giản cho backend. File admin.css có thể download từ subversion tag của ngày hôm nay.

The admin generator look and feel

Cuối cùng, đổi lại trang chủ mặc định trong file routing.yml:

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

Symfony Cache

Nếu bạn tò mò mở những file đã được tự động tạo trong thư mục apps/backend/modules/ bạn sẽ thấy ngạc nhiên! Thư mục templates hoàn toàn trống, và file actions.class.php cũng không có gì:

// 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
{
}

Làm thế nào để nó có thể hoạt động được? Nếu bạn để ý kĩ, bạn sẽ thấy rằng lớp jobActions thừa kế từ autoJobActions. Lớp autoJobActions được tự động tạo bởi symfony nếu nó không tồn tại. Nó có thể được tìm thấy trong thư mục cache/backend/dev/modules/autoJob/, và chứa các module "thực sự":

// 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())
    ))
    {
 
// ...

Các module được tạo ra có thể được cấu hình bằng cách chỉnh sửa file config/generator.yml trong mỗi module:

# 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_propel_route:     1
 
    config:
      actions: ~
      fields:  ~
      list:    ~
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~

Mỗi khi bạn sửa file generator.yml, symfony sẽ tạo lại cache. Như chúng ta sẽ thấy trong hôm nay, chỉnh sửa admin generated modules là công việc dễ dàng, nhanh chóng và thú vị.

note

Việc tự động tạo lại các file cache chỉ được thực hiện trong môi trường development. Trong môi trường production, bạn cần tự xoá cache thông qua lệnh cache:clear.

Cấu hình Backend

Một module trong admin có thể được cấu hình thông qua file generator.yml. Nội dung cấu hình được tổ chức trong 7 mục:

  • actions: Default configuration for the actions found on the list and on the forms
  • fields: Default configuration for the fields
  • list: Configuration for the list
  • filter: Configuration for the filters
  • form: Configuration for the new/edit form
  • edit: Specific configuration for the edit page
  • new: Specific configuration for the new page

Hãy bắt đầu công việc cấu hình!

Chỉnh sửa tiêu đề

Tiêu đề của các mục list, edit, và new của module category có thể được chỉnh sửa thông qua option title:

config:
  actions: ~
  fields:  ~
  list:
    title: Category Management
  filter:  ~
  form:    ~
  edit:
    title: Editing Category "%%name%%" (#%%id%%)
  new:
    title: New Category

title của mục edit chứa vài giá trị động: tất cả các chuỗi nằm giữa cụm %% được thay thế bởi giá trị của các cột tương ứng.

Titles

Cấu hình cho module job hoàn toàn tương tự:

config:
  actions: ~
  fields:  ~
  list:
    title: Job Management
  filter:  ~
  form:    ~
  edit:
    title: Editing Job "%%company%% is looking for a %%position%% (#%%id%%)"
  new:
    title: Job Creation

Cấu hình các Field

Một field có thể là một cột trong model class, cũng có thể là một giá trị do ta tạo ra.

Cấu hình cho các field nằm trong mục fields:

config:
  fields:
    is_activated: { label: Activated?, help: Whether the user has activated the job, or not }
    is_public:    { label: Public? }

Mục fields sẽ cấu hình cho các field ở tất cả các module, cấu hình trên sẽ thay đổi label của field is_activated ở các trang list, edit, và new.

Cấu hình trong admin generator dựa trên nguyên lý xếp tầng. Ví dụ, nếu bạn chỉ muốn thay đổi tiêu đề trong trang list, hãy tạo nó ở option fields dưới mục list:

config:
  list:
    fields:
      is_public:    { label: "Public? (label for the list)" }

Bất kì cấu hình nào nằm dưới mục fields chính đều có thể được thay đổi bởi cấu hình ở từng trang cụ thể. Luật cấu hình như sau:

  • newedit thừa kế từ form, form lại được thừa kế từ fields
  • list thừa kế từ fields
  • filter thừa kế từ fields

note

Trong các mục về form (form, edit, và new), các option labelhelp sẽ thay thế nội dung mặc định trong các lớp form.

Cấu hình trang List

display

Mặc định, trang list hiển thị tất cả các cột trong model. Option display sẽ thay đổi hiển thị mặc định đó bằng cách chỉ rõ cột nào được hiển thị:

config:
  list:
    title:   Category Management
    display: [=name, slug]

Kí tự = đặt trước cột name sẽ tạo link cho chuỗi này.

Table list

Làm tương tự với module job:

config:
  list:
    title:   Job Management
    display: [company, position, location, url, is_activated, email]

layout

Trang list có thể được hiển thị trong các layout khác nhau. Mặc định là layout tabular, các cột sẽ được hiển thị trong một bảng. Nhưng với module job, ta nên sử dụng layout stacked, là layout khác có sẵn trong hệ thống:

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%%)

Trong layout stacked, mỗi đối tượng được mô tả trong một chuỗi, tạo thành từ giá trị các cột xác định trong option params.

note

Option display vẫn cần thiết để người dùng có thể sắp xếp danh sách theo thứ tự tăng/giảm của từng cột.

Cột ảo

Với cấu hình trên, đoạn %%category_id%% sẽ được thay bằng khóa chính của category. Nhưng sẽ hữu ích hơn nếu hiển thị tên của category.

Khi bạn sử dụng kí hiệu %%, biến trong đó không nhất thiết phải tương ứng với một cột trong database schema. Khi đó, admin generator sẽ tìm phương thức getter tương ứng trong model class.

Để hiển thị category name, chúng ta có thể tạo phương thức getCategoryName() trong class model JobeetJob và thay thế %%category_id%% bằng %%category_name%%.

Nhưng lớp JobeetJob đã có phương thức getJobeetCategory() trả về đối tượng category liên quan. Và nếu bạn sử dụng %%jobeet_category%%, nó cũng hiển thị tên category vì lớp JobeetCategory có một magic method __toString() trả về tên của đối tượng.

%%is_activated%% <small>%%jobeet_category%%</small> - %%company%%
 (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%)

Stacked layout

sort

Là administrator, bạn sẽ muốn biết các công việc đưa lên gần đây nhất. Bạn có thể cấu hình để sắp xếp theo một cột nào đó bằng cách thêm option sort:

config:
  list:
    sort: [expires_at, desc]

max_per_page

Mặc định, danh sách sẽ được phân trang: 20 công việc mỗi trang. Bạn có thể thay đổi nó với option max_per_page:

config:
  list:
    max_per_page: 10

Max per page

batch_actions

Ở trang list, ta có thể thực hiện một hành động với vài đối tượng một lúc (batch action). Batch action này là không cần thiết trong module category, vì thế ta bỏ nó đi:

config:
  list:
    batch_actions: {}

Remove the batch actions

Option batch_actions xác định danh dách các batch action. Mảng rỗng có nghĩa là ta bỏ mọi chức năng.

Mặc định, mỗi module có một batch action delete tạo sẵn bởi framework, nhưng với module job, ta cần thêm một batch action để gia hạn cho những công việc được chọn thêm 30 ngày:

config:
  list:
    batch_actions:
      _delete:    ~
      extend:     ~

Tất cả các action bắt đầu bằng _ là các action có sẵn trong framework. Nếu bạn refresh lại trình duyệt và chọn batch action extend, symfony sẽ hiện ra thông báo lỗi rằng bạn chưa có phương thức 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);
      $job->save();
    }
 
    $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.');
 
    $this->redirect('@jobeet_job');
  }
}

Các khóa chính được chọn được chứa trong request parameter ids. Với mỗi công việc được chọn, phương thức JobeetJob::extend() được gọi với một tham số để bypass một vài kiểm tra trong phương thức. Chúng ta cần sửa lại phương thức extend() với tham số mới:

// 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;
  }
 
  // ...
}

Sau khi tất cả các công việc đã được gia hạn, user được chuyển tới trang chủ của module job.

Custom batch actions

object_actions

Trong trang list, có thêm một cột chứa các action bạn có thể thực hiện với từng đối tượng riêng biệt. Với module category, cột này là không cần thiết, do đó chúng ra có thể bỏ chúng đi:

config:
  list:
    object_actions: {}

Với module job, chúng ta cần những action này và cần thêm một action mới extend tương tự như batch action chúng ta đã làm:

config:
  list:
    object_actions:
      extend:     ~
      _edit:      ~
      _delete:    ~

Tương tự như batch action, các action _delete_edit được tạo sẵn bởi framework. Chúng ta cần viết action listExtend() để link extend có thể làm việc:

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

Custom object action

actions

Chúng ta đã biết cách tạo link cho một action đối với một danh sách các đối tượng được lựa chọn, hoặc một đối tượng riêng lẻ. Option actions xác định action không tác động đến đối tượng có sẵn nào, như: tạo một đối tượng mới. Hãy bỏ action new có sẵn và thêm một action mới xóa tất cả các công việc không còn được activate bởi người post quá 60 ngày:

list:
  list:
    actions:
      deleteNeverActivated: { label: Delete never activated jobs }

Mỗi action có thể được cấu hình bằng cách xác định một mảng các tham số. Action listDeleteNeverActivated rất đơn giản:

// 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');
  }
 
  // ...
}

Chúng ta đã dùng lại phương thức JobeetJobTable::cleanup() tạo ngày hôm qua. Một lần nữa ta lại thấy lợi ích của việc sử dụng lại trong MVC pattern.

note

bạn cũng có thể thay đổi action để thực thi bằng các cung cấp giá trị cho tham số action:

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

Actions

table_method

Số câu truy vấn cần thiết để hiển thị trang list công việc là 13, được chỉ ra trên thanh web debug toolbar.

Number of requests before

Nếu bạn click vào số này, bạn sẽ thấy phần lớn các câu truy vấn là lấy category name cho mỗi công việc.

Để giảm số câu truy vấn, chúng ta có thể thay đối phương thức mặc định để lấy danh sách các công việc bằng cách sử dụng option table_method:

config:
  list:
    table_method: retrieveBackendJobList

Bây giờ, bạn cần tạo phương thức retrieveBackendJobList trong lớp JobeetJobTablelib/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;
  }
 
  // ...

Phương thức retrieveBackendJobList() thêm join giữa bảng job và bảng category và tự động tạo đối tượng category tương ứng với mỗi công việc.

Số câu truy vấn giờ giảm xuống còn 3:

Number of requests after

Cấu hình Form

Cấu hình form nằm trong 3 mục: form, edit, và new. Các mục này có cấu hình như nhau và mục form chỉ tồn tại như một fallback cho các mục editnew.

display

Giống như trang list, bạn có thể thay đổi thứ tự hiển thị các fields với option display. Nhưng do form hiển thị được tạo bởi một class, nên việc bỏ một field có thể dẫn đến lỗi khi validation.

Option display cho form có thể dùng để nhóm các field lại trong một nhóm:

config:
  form:
    display:
      Content: [category_id, type, company, logo, url, position, location, description, how_to_apply, is_public, email]
      Admin:   [_token, is_activated, expires_at]

Cấu hình trên tạo ra 2 nhóm (ContentAdmin), mỗi nhóm chứa một số field.

Fields grouping

Admin generator cũng hỗ trợ sẵn quan hệ nhiều-nhiều. Ở form category, bạn có một input cho trường name, một cho trường slug, và một drop-down box cho các affiliate liên quan. Chúng ta không sửa giá trị này ở trang này nên ta cần bỏ đi:

// lib/model/doctrine/JobeetCategoryForm.class.php
class JobeetCategoryForm extends BaseJobeetCategoryForm
{
  public function configure()
  {
    unset($this['jobeet_category_affiliate_list']);
  }
}

Cột ảo

Field _token bắt đầu bằng kí tự (_). Điều đó có nghĩa là việc render ra field này được tạo bởi một partial do ta tự tạo _token.php:

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

Trong partial, bạn có thể truy cập form hiện tại ($form) và các đối tượng liên quan thông qua phương thức getObject().

note

Bạn cũng có thể sử dụng kết quả của một component bằng kí tự (~) đặt trước tên field

class

Do form được sử dụng bởi administrators, chúng ta cần hiển thị nhiều thông tin hơn khi được dùng bởi user. Nhưng hiện tại, một vài thông tin không được hiển thị do chúng ta đã bỏ chúng đi trong lớp JobeetJobForm.

Để form khác nhau giữa frontend và backend, chúng ta cần tạo 2 lớp form khác nhau. Hãy tạo lớp BackendJobeetJobForm thừa kế từ lớp JobeetJobForm. Do chúng ta không có những field ẩn đi như ở frontend, nên chúng ta cần refactor lớp JobeetJobForm bằng cách chuyển các đoạn unset() vào một phương thức để có thể overridden trong lớp BackendJobeetJobForm:

// lib/form/doctrine/JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm
{
  public function configure()
  {
    $this->removeFields();
 
    $this->validatorSchema['email'] = new sfValidatorEmail();
 
    // ...
  }
 
  protected function removeFields()
  {
    unset(
      $this['created_at'], $this['updated_at'],
      $this['expires_at'], $this['token'],
      $this['is_activated']
    );
  }
}
 
// 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']
    );
  }
}

Lớp form mặc định dùng bởi admin generator có thể thay đổi thông qua option class:

config:
  form:
    class: BackendJobeetJobForm

Form edit hiện vẫn còn một vấn đề nhỏ. Logo upload lên hiện chưa được hiển thị và bạn không thể xóa nó. Widger sfWidgetFormInputFileEditable thêm tính năng chỉnh sửa đối với một input file widget:

// 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>',
    ));
  }
 
  // ...
}

Widget sfWidgetFormInputFileEditable có vài option cho việc tinh chỉnh các tính năng và render:

  • file_src: đường dẫn đến file được upload
  • is_image: nếu true, ảnh sẽ được hiển thị
  • edit_mode: form ở chế độ edit hay không
  • with_delete: có hiển thị checkbox delete hay không
  • template: template dùng để render widget

File upload

tip

Giao diện của admin generator có thể chỉnh sửa dễ dàng do template được tạo ra bao gồm rất nhiều classid attributes. Ví dụ, có thể chỉnh sửa field logo thông qua class sf_admin_form_field_logo (class trong css :D). Mỗi field cũng có một class phụ thuộc vào kiểu field như sf_admin_text hay sf_admin_boolean.

Option edit_mode sử dụng phương thức sfDoctrineRecord::isNew().

Nó trả về true nếu model object của form là mới, và false trong trường hợp ngược lại. Nó giúp ích rất nhiều khi bạn càn có những widget hay validator khác nhau tùy vào trạng thái của đối tượng.

Cấu hình Filter

Cấu hình cho filter tương tự như cấu hình cho form. Đơn giản vì filter chính là các form. Và các form này là các lớp được tạo thông qua lệnh doctrine:build-all. Bạn cũng có thể tạo lại chúng với lệnh doctrine:build-filters.

Các lớp form filter nằm trong thư mục lib/filter và mỗi model class có một filter form class tương ứng (JobeetJobFormFilter ứng với JobeetJobForm).

Chúng ta không cần filter trong module category:

config:
  filter:
    class: false

Với module job, ta bỏ một số field:

filter:
  display: [category_id, company, position, description, is_activated, is_public, email, expires_at]

Do filter là không bắt buộc, nên ta không cần override lớp filter form để cấu hình hiển thị các field.

Filters

Actions Customization

Khi việc cấu hình không thỏa mãn được yêu cầu của bạn, bạn có thể thêm một phương thức mới vào action như chúng ta đã làm với tính năng extend, nhưng bạn cũng có thể override các phương thức được tạo ra:

Method Mô tả
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

Do mỗi phương thức tạo ra chỉ thực hiện một công việc, nên việc thay đổi behavior là khá dễ dàng mà không cần copy & paste quá nhiều code.

Templates Customization

Chúng ta đã biết cách chỉnh sửa templates được tạo ra nhờ có các classid attribute trong mã HTML.

Bạn cũng có thể thay thế template được tạo ra. Do template là file PHP đơn thuần chứ không chứa lớp PHP, nên bạn có thể thay thế template bằng cách tạo một template cùng tên trong module (ví dụ trong thư mục apps/backend/modules/job/templates/ của admin module job):

Template Mô tả
_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

Kết quả cuối cùng

Kết quả cuối cùng của việc cấu hình cho Jobeet admin:

# apps/backend/modules/job/config/generator.yml
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:   [_token, is_activated, expires_at]
  edit:
    title: Editing Job "%%company%% is looking for a %%position%% (#%%id%%)"
  new:
    title: Job Creation
 
# apps/backend/modules/category/config/generator.yml
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%%" (#%%id%%)
  new:
    title: New Category

Chỉ với 2 file cấu hình, chúng ta đã phát triển một backend interface đầy đủ các tính năng cho Jobeet trong vài phút.

tip

Bạn đã biết rằng khi một thứ có thể cấu hình trong file YAML, nó cũng có thể được cấu hình sử dụng code PHP. Với admin generator, bạn có thể sửa file apps/backend/modules/job/lib/jobGeneratorConfiguration.class.php. Nó cũng có các option như file YAML nhưng thông qua code PHP. Để biết tên của những phương thức này, hãy xem trong lớp cache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php.

Hẹn gặp lại ngày mai

Chỉ trong một giờ, chúng ta đã xây dựng đầ đủ các tính năng của một backend interface cho Jobeet project. Để làm tất cả những việc đó, chúng ta chỉ viết chưa đến 50 dòng code PHP. Thật tuyệt!

Ngày mai, chúng ta sẽ học cách bảo mật application backend với username và password. Đồng thời, chúng ta cũng đề cập đến lớp symfony user.