昨日Jobeetに追加した機能のおかげで フロントエンドアプリケーションは求職者と仕事の提供者が十分に利用できます。 バックエンドアプリケーションを少し話すことにします。
今日は、symfonyのadminジェネレーターのおかげで、Jobeet用の完全なバックエンドインターフェイスを1時間で開発します。
バックエンドの作成
一番最初のステップはbackendアプリケーションを作ることです。
記憶力がよければ、generate:appタスクでこれを行う方法を覚えていることでしょう:
$ php symfony generate:app --escaping-strategy=on --csrf-secret=UniqueSecret1 backend
バックエンドアプリケーションを使うのがJobeetの管理者だけであっても、symfonyのすべての組み込み機能を有効にしました。
tip
パスワードでドル記号($)のような特殊記号を使いたい場合、コマンドラインで適切にエスケープする必要があります:
$ php symfony generate:app --csrf-secret=Unique\$ecret backend
prod環境ではhttp://jobeet.localhost/backend.php/、dev環境ではhttp://jobeet.localhost/backend_dev.php/を通してバックエンドアプリケーションを利用できます。
note
フロントエンドアプリケーションを作成したとき、運用環境のフロントコントローラーの名前はindex.phpでした。
ディレクトリごとに1つのindex.phpファイルだけ用意できるので、symfonyは初期の運用環境のフロントコントローラーであるindex.phpファイルを作り名前をアプリケーションからつけます。
doctrine:data-loadタスクでデータフィクスチャをリロードしようとしても、動作しません。
JobeetJob::save()メソッドがfrontendアプリケーションからapp.yml設定ファイルにアクセスする権限が必要だからです。
2つのアプリケーションを用意したので、symfonyは最初に見つかるアプリケーション、今はbackendアプリケーションを使います。
しかし8日目でみたように、コンフィギュレーションは異なるレベルで設定できます。
apps/frontend/config/app.ymlファイルの内容をconfig/app.ymlに移動させることで、すべてのアプリケーションの間で設定は共有され問題は修正されます。
adminジェネレーターでモデルクラスを広範囲で使うので、今変更をします。
バックエンドアプリケーションのapp.ymlで定義される変数が必要になります。
tip
doctrine:data-loadタスクは--applicationオプションも受け取ります。
ですので、あるアプリケーションもしくは別のものから特定の設定が必要な場合、これを行う方法は次のとおりです:
$ php symfony doctrine:data-load --application=frontend
バックエンドモジュール
フロントエンドアプリケーションに関して、モデルクラスに基づいた基本的なCRUDモジュールをブートストラップするためにdoctrine:generate-moduleタスクが使われてきました。
バックエンドに関して、モデルクラス用のバックエンドインターフェイスを生成するdoctrine:generate-adminタスクが使われます:
$ php symfony doctrine:generate-admin backend JobeetJob --module=job $ php symfony doctrine:generate-admin backend JobeetCategory --module=category
これら2つのコマンドはJobeetJobとJobeetCategoryモデルクラスに対してそれぞれjobとcategoryモジュールを作成します。
--moduleオプションを追加するとタスクによってデフォルトで生成されるmoduleの名前がオーバーライドされます(そうでなければJobeetJobクラスに対してjobeet_jobになります)。
背景では、タスクはそれぞれのモジュール用のカスタムのルートも作成しました:
# apps/backend/config/routing.yml
jobeet_job:
class: ~sfDoctrineRouteCollection~
options:
model: JobeetJob
module: job
prefix_path: job
column: id
with_wildcard_routes: true
インターフェイスのメインゴールはモデルオブジェクトのライフサイクルの管理なので、adminジェネレーターによって使われるルートクラスがsfDoctrineRouteCollectionであるのは驚くことではありません。
ルートは以前見なかったオプションも定義します:
prefix_path: 生成されるルートのプレフィックスのパスを定義する(たとえば、編集ページは/job/1/editのようになる)。column: オブジェクトを参照するリンク用のURLで使用するテーブルのカラムを定義するwith_wildcard_routes: adminインターフェイスは古典的なCRUDオペレーション以上の機能を持ち、このオプションによってルートを編集せずに、オブジェクトとコレクションアクションを定義できます。
tip
As always, 新しいタスクを使う前にヘルプを読むのはよい考えです。
$ php symfony help doctrine:generate-admin
このコマンドによって古典的な使い方の例と同じようにタスクのすべての引数とオプションが表示されます。
バックエンドの外見
生成されたモジュールは直ちに利用できます:
http://jobeet.localhost/backend_dev.php/job http://jobeet.localhost/backend_dev.php/category
adminモジュールは以前に生成したシンプルなモジュールよりも多くの機能を持ちます。 PHPを一行も書くことなく、それぞれのモジュールはこれらのすばらしい機能を提供します:
- オブジェクトのリストはページ分割される
- リストはソート可能である
- リストはフィルタリング可能である
- オブジェクトは作成、編集と削除が可能である
- 選択されたオブジェクトはバッチで削除可能である
- フォームのバリデーションが有効である
- flashメッセージによってユーザーに即座のフィードバックが行われる
- さらに多くの機能がある
adminジェネレーターはパッケージを設定するシンプルなバックエンドインターフェイスを作成するために必要なすべての機能を提供します。
ユーザーエクスペリエンスを少し改善するために、デフォルトのバックエンドをカスタマイズする必要があります。 異なるモジュールの間を渡り歩くのを楽にするためにシンプルなメニューを追加します。
デフォルトの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スタイルシートを使います。
4日目の間にこれは他のスタイルシートと一緒にインストールしたので、このファイルはweb/css/の中に存在しなければなりません。

最終的に、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を継承することに気がつきます。
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ジェネレーターの動作は既知のふるまいを思い出させてくれます。
実際、これはモデルとフォームクラスに関してすでに学んだこととよく似ています。
モデルスキーマの定義に基づいて、symfonyはモデルとフォームクラスを生成します。
adminジェネレーターに関して、モジュールで見つかる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はキャッシュを再生成します。
今日見たように、adminジェネレーターで生成したモジュールのカスタマイズは簡単で、速く、面白いです。
note
キャッシュファイルの自動再生成は開発環境のみで行われます。
運用環境では、cache:clearタスクを使ってキャッシュを手動でクリアする必要があります。
バックエンドのコンフィギュレーション
adminモジュールはgenerator.ymlファイルのconfigキーを編集することでカスタマイズできます。
コンフィギュレーションは7つのセクションにわかれます:
actions: リストとフォームで見つかる アクション用のデフォルトコンフィギュレーションfields: フィールド用のデフォルトコンフィギュレーションlist: リスト用のコンフィギュレーションfilter: フィルター用のコンフィギュレーションform: 新規/編集フォーム用のコンフィギュレーションedit: 編集ページ固有のコンフィギュレーションnew: 新しいページ固有のコンフィギュレーション
カスタマイズを始めましょう。
タイトルのコンフィギュレーション
categoryモジュールのlist、editとnewセクションタイトルは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
editセクション用のtitleは動的な値を含みます:
%%で囲まれるすべての文字列は対応するオブジェクトのカラムの値で置き換えられます。

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
フィールドのコンフィギュレーション
異なるビュー(list、newとedit)はフィールドで構成されます。
フィールドはモデルクラスのカラムもしくは後で見るバーチャルカラムになります。
デフォルトのフィールドコンフィギュレーションは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セクションはすべてのビュー用のフィールドコンフィギュレーションをオーバーライドします。
このことはis_activatedフィールド用のラベルがlist、editとnewビュー用に変更されることを意味します。
adminジェネレーターのコンフィギュレーションはコンフィギュレーションカスケードの原則に基づきます。
たとえば、ラベル用のlistビューのみを変更したい場合、listセクションの下のfieldsオプションを定義します:
# apps/backend/modules/job/config/generator.yml
config:
list:
fields:
is_public: { label: "Public? (label for the list)" }
メインのfieldsセクションの下でセットされるコンフィギュレーションはビュー固有のコンフィギュレーションによってオーバーライドされます。
オーバーライドのルールは次の通りです:
newとeditはformを継承し、formはfieldsを継承するlistはfieldsを継承するfilterはfieldsを継承する
note
フォームセクション(form、editとnew)に関しては、labelとhelpオプションはフォームクラスで定義されるものをオーバーライドします。
listビューのコンフィギュレーション
display
デフォルトでは、listビューのカラムはすべてモデルのカラムです。
スキーマファイルの順序で並べられています。表示され並べ替えられたカラムを定義することでdisplayオプションはデフォルトをオーバーライドします:
# apps/backend/modules/category/config/generator.yml
config:
list:
title: Category Management
display: [=name, slug]
nameカラムの前の=記号は文字列をリンクに変換するための慣習です。

読みやすくするためにjobモジュールに同じことをやってみましょう:
# apps/backend/modules/job/config/generator.yml
config:
list:
title: Job Management
display: [company, position, location, url, is_activated, email]
layout
listは異なるレイアウトで表示されます。
デフォルトでは、レイアウトは~tabular|テーブルレイアウト~です。
このことはそれぞれのカラムの値が独自のテーブルカラムの中にあることを意味します。
しかしjobモジュールに関しては、stackedレイアウトを使うほうがよいです。
これは他の組み込みのレイアウトです:
# 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レイアウトにおいて、それぞれのオブジェクトは単独の文字列で表され、paramsオプションによって定義されます。
note
displayオプションはこれはユーザーによってソートできるカラムを定義するのでまだ必要です。
"バーチャル"カラム
この設定によって、%%category_id%%セグメントはカテゴリの主キーによって置き換えられます。
しかし、カテゴリの名前を表示するほうがより意味があります。
%%の表記を使うときは、変数はデータベーススキーマの実際にカラムに対応する必要はありません。
adminジェネレーターはモデルクラスの関連ゲッターを見つけることだけが必要です。
カテゴリの名前を表示するには、JobeetJobモデルクラスのgetCategoryName()メソッドを定義し%%category_id%%を%%category_name%%に置き換えます。
しかしJobeetJobクラスはすでに関連カテゴリオブジェクトを返すgetJobeetCategory()メソッドを持ちます。
%%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%%)

sort
管理者として、投稿された最新の求人を見ることにご興味があるでしょう。
sortオプションを追加することでデフォルトのsortカラムを設定できます:
# apps/backend/modules/job/config/generator.yml
config:
list:
sort: [expires_at, desc]
max_per_page
デフォルトでは、リストはパジネートされそれぞれのページは20のアイテムを含みます。
これはmax_per_pageオプションで変更できます:
# apps/backend/modules/job/config/generator.yml
config:
list:
max_per_page: 10

batch_actions
リストにおいて、アクションはいくつかのオブジェクトで実行できます。
categoryモジュールに対してこれらのバッチは必要ありません。
ですのでこれらを削除しましょう:
# apps/backend/modules/category/config/generator.yml
config:
list:
batch_actions: {}

batch_actionsオプションはバッチアクションのリストを定義します。
空の配列によって機能の削除ができます。
デフォルトでは、それぞれのモジュールはフレームワークによって定義されたdeleteバッチアクションを持ちますが、jobモジュールに関しては、選択された求人の有効期間を30日延長することが必要な場合を考えてみましょう:
# apps/backend/modules/job/config/generator.yml
config:
list:
batch_actions:
_delete: ~
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'); } }
選択された主キーは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; } // ... }
すべての求人期間が延長された後で、ユーザーはjobモジュールのホームページにリダイレクトされます。

object_actions
listにおいて、単独のオブジェクトで実行できるアクション用の追加カラムがあります。
categoryモジュールに関しては、編集するカテゴリの名前の上にリンクがありリストから直接削除できる必要がないので、これらを削除しましょう:
# 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アクションはフレームワークによって定義されます。
extendリンクを動作させるためにlistExtend()アクションを定義する必要があります:
// 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'); } // ... }

actions
オブジェクトのリストもしくは単独のオブジェクトにアクションをリンクする方法をすでに見てきました。
新しいオブジェクトを作るように、actionsオプションはオブジェクトを全く受け取らないアクションを定義します。
デフォルトのnewアクションを削除して60日以上投稿者によってアクティベートされなかったすべての求人を削除する新しいアクションを追加しましょう:
# apps/backend/modules/job/config/generator.yml
config:
list:
actions:
deleteNeverActivated: { label: Delete never activated jobs }
これまで、定義したすべてのアクションは~を持ちました。
これはsymfonyがアクションを自動的に設定することを意味します。
それぞれのアクションはパラメーターの配列を定義することでカスタマイズできます。
labelオプションはsymfonyによって生成されたデフォルトのラベルをオーバーライドします。
デフォルトでは、リンクをクリックするときに実行されるアクションはlistをプレフィックスとするアクションの名前です。
jobモジュールのlistDeleteNeverActivatedアクションを作りましょう:
// 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 }

table_method
求人リストのページを表示するために必要なデータベースへのリクエスト回数は14回で、Webデバッグツールバーによって表示されます。
その数字をクリックすると、それぞれの求人に対してカテゴリの名前を読み取るための最新のリクエスト回数がわかります:

リクエストの回数を減らすには、
table_methodオプションを指定することで求人を取得するために使われるデフォルトのメソッドを変更できます:
# apps/backend/modules/job/config/generator.yml
config:
list:
table_method: retrieveBackendJobList
lib/model/doctrine/JobeetJobTable.class.phpに設置されるJobeetJobTableのretrieveBackendJobListメソッドを作らなければなりません。
// 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; } // ...
retrieveBackendJobList()メソッドはjobとcategoryテーブルの間のJOINを追加しそれぞれの求人に関連するカテゴリオブジェクトを自動的に作成します。
リクエストの回数は4回に減ります:

フォームビューのコンフィギュレーション
フォームビューは3つのセクション: form、editとnewで構成されます。
これらすべては同じ設定機能を持ちformセクションはeditとnewセクション用のフォールバックとしてのみ存在します。
display
リストに関して、displayオプションで表示されるフィールドの順序を変更できます。
しかし表示されるフォームはクラスによって定義されるので、予期しないバリデーションエラーにつながるのでフィールドを削除しようとしないでください。
フォームビュー用の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つのグループ(ContentとAdmin)が定義され、それぞれはフォームフィールドのサブセットを含みます。

note
Adminグループのカラムはまだブラウザーには表示されません。
これらは求人フォームの定義に設定されていないからです。
adminアプリケーション用にカスタムの求人フォームクラスを定義するときにこれらはわずかなセクションに現れます。
adminジェネレーターは多対多のリレーション用の組み込み機能をサポートします。 カテゴリフォームにおいて、名前の入力、スラッグの入力、関連するアフィリエイト用のドロップダウンボックスがあります。 このページでこのリレーションを編集するのは意味がないので、削除しましょう:
// lib/form/doctrine/JobeetCategoryForm.class.php class JobeetCategoryForm extends BaseJobeetCategoryForm { public function configure() { unset($this['created_at'], $this['updated_at'], $this['jobeet_affiliates_list']); } }
"バーチャル"カラム
求人フォームのdisplayオプションにおいて、_generated_tokenフィールドはアンダースコア(_)で始まります。
このフィールドのレンダリングは_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)にアクセス可能で関連オブジェクトはgetObject()メソッドを通してアクセスできます。
note
チルダ(~)をフィールドの名前のプレフィックスにすることでレンダリングをコンポーネントにデリゲートすることもできます。
class
管理者によって使われるフォームに関して、ユーザーの求人フォームよりも多くの情報を表示してきました。
しかし今では、JobeetJobFormクラスでこれらを削除したので、これらの中にはフォームに表示されないものがあります。
フロントエンドとバックエンドで異なるフォームを用意するには、2つのフォームクラスを作る必要があります。
JobeetJobFormクラスを継承するBackendJobeetJobFormクラスを作りましょう。
同じ隠しフィールドを持たないので、unset()ステートメントをBackendJobeetJobFormでオーバーライドされるメソッドに移動させるために、JobeetJobFormクラスを少しリファクタリングすることも必要です:
// 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'] ); } }
adminジェネレーターによって使われるデフォルトのフォームクラスはclassオプションを設定することでオーバーライドできます:
# apps/backend/modules/job/config/generator.yml
config:
form:
class: BackendJobeetJobForm
note
新しいクラスを作成したので、キャッシュをクリアすることを忘れないでください。
editフォームは小さな問題をかかえています。
現在のアップロードされたロゴはどこにも表示されないので現在のものを削除できません。
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: ウィジェットをレンダリングするために使うテンプレート

tip
生成されたテンプレートはたくさんのclassとid属性を定義するので、adminジェネレーターの見た目はとても簡単に調整できます。
たとえば、ロゴフィールドはsf_admin_form_field_logoクラスを使ってカスタマイズできます。
それぞれのフィールドはsf_admin_textもしくはsf_admin_booleanのようなフィールドタイプに決まるクラスも持ちます。
edit_modeオプションはsfDoctrineRecord::isNew()メソッドを使います。
これはフォームのモデルオブジェクトが新しい場合はtrueを返し、そうでなければfalseを返します。
埋め込みオブジェクトのステータスによって異なるウィジェットもしくはバリデーターを用意する必要がある場合にこれはとても役立ちます。
フィルターのコンフィギュレーション
フィルターの設定方法はフォームビューの設定方法とまったく同じです。
実際、フィルターは単なるフォームです。
フォームに関しては、クラスはdoctrine:build-allタスクで生成されました。
doctrine:build-filtersタスクでこれらを再生成することもできます。
フォームフィルタークラスはlib/filter/ディレクトリの下に設置されそれぞれのモデルクラスは関連フィルターフォームクラスを持ちます(JobeetJobFormに対してJobeetJobFormFilter)。
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]
フィルターは常にオプションなので、フィールドが表示されるように設定するために、フィルターフォームクラスをオーバーライドする必要はありません。

アクションのカスタマイズ
設定が十分ではないとき、これまでの機能の拡張方法で見てきたように新しいメソッドをアクションクラスに追加できます。 生成されたアクションモジュールをオーバーライドすることもできます:
| メソッド | 説明 |
|---|---|
executeIndex() |
listビューアクション |
executeFilter() |
フィルターを更新する |
executeNew() |
newビューアクション |
executeCreate() |
新しい求人を作成する |
executeEdit() |
editビューアクション |
executeUpdate() |
Jobを更新する |
executeDelete() |
Jobを削除する |
executeBatch() |
バッチアクションを実行する |
executeBatchDelete() |
_deleteバッチアクションを実行する |
processForm() |
求人フォームを更新する |
getFilters() |
現在のフィルターを返す |
setFilters() |
フィルターを設定する |
getPager() |
リストページャーを返す |
getPage() |
ページャーページを取得する |
setPage() |
ページャーページを設定する |
buildCriteria() |
リスト用のCriteriaをビルドする |
addSortCriteria() |
リスト用のソートCriteriaを追加する |
getSort() |
現在のソートカラムを返す |
setSort() |
現在のソートカラムを設定する |
それぞれの生成メソッドは1つのことしか行わないので、たくさんのコードをコピー&ペーストしなくてもふるまいを変更するのは簡単です。
テンプレートのカスタマイズ
adminジェネレーターによってHTMLコードに追加されるclassとid属性のおかげで生成されたテンプレートをカスタマイズする方法を見てきました。
クラスに関して、オリジナルのテンプレートをオーバーライドすることもできます。
テンプレートはプレーンなPHPファイルでPHPクラスではないので、モジュールで同じ名前のテンプレートを作ることでテンプレートをオーバーライドできます(たとえばadminモジュールのjobに対してapps/backend/modules/job/templates/ディレクトリ):
| テンプレート | 説明 |
|---|---|
_assets.php |
テンプレート用のCSSとJSをレンダリングする |
_filters.php |
フィルターボックスをレンダリングする |
_filters_field.php |
単独のフィルターフィールドをレンダリングする |
_flashes.php |
flashメッセージをレンダリングする |
_form.php |
フォームを表示する |
_form_actions.php |
フォームアクションを表示する |
_form_field.php |
単独のフォームフィールドを表示する |
_form_fieldset.php |
フォームのフィールドセットを表示する |
_form_footer.php |
フォームのフッターを表示する |
_form_header.php |
フォームのヘッダーを表示する |
_list.php |
リストを表示する |
_list_actions.php |
リストアクションを表示する |
_list_batch_actions.php |
リストバッチアクションを表示する |
_list_field_boolean.php |
リストの単独のブール型フィールドを表示する |
_list_footer.php |
リストフッターを表示する |
_list_header.php |
リストヘッダーを表示する |
_list_td_actions.php |
列用のオブジェクトアクションを表示する |
_list_td_batch_actions.php |
列用のチェックボックスを表示する |
_list_td_stacked.php |
列用のstackedレイアウトを表示する |
_list_td_tabular.php |
リスト用の単独のフィールド名を表示する |
_list_th_stacked.php |
ヘッダー用の単独のカラム名を表示する |
_list_th_tabular.php |
ヘッダー用の単独のカラム名を表示する |
_pagination.php |
リストのページ分割を表示する |
editSuccess.php |
editビューを表示する |
indexSuccess.php |
listビューを表示する |
newSuccess.php |
newビューを表示する |
最終的なコンフィギュレーション
Jobeetのadminの最終的なコンフィギュレーションは次のとおりです:
# 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
これら2つの設定ファイルだけで、Jobeet用のすばらしいバックエンドインターフェイスを短時間で開発しました。
tip
何かがYAMLファイルで設定可能であるとき、プレーンなPHPコードを使うことができるのはご存じのとおりです。
adminジェネレーターに関して、apps/backend/modules/job/lib/jobGeneratorConfiguration.class.phpファイルを編集できます。
これによってYAMLファイルと同じオプションがPHPで帝京されます。
メソッドの名前を学ぶには、cache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.phpの生成された基底クラスを見てください。
また明日
丁度一時間で、Jobeetプロジェクト用に十分な機能を持つバックエンドインターフェイスを開発しました。 全体で、50行に満たないPHPコードしか書きませんでした。 こんなに多くの機能があって悪くないです!
明日は、ユーザー名とパスワードでバックエンドアプリケーションをセキュアにする方法を見ることになります。 symfonyのユーザークラスに関して話す機会もあります。
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.