12日目: アドミンジェネレータ
11日目にJobeetに追加したもので、フロントエンドアプリケーションは職を探す人にも、提供する人にもとても便利なものになりました。ここで少しバックエンドアプリケーションについて話します。今日は、symfonyのアドミンジェネレータを使って、1時間でJobeetの完全なバックエンドインターフェイスを開発しましょう。
バックエンドの作成
一番最初のステップは backend アプリケーションを作ることです。記憶力が良ければ、generate:app
タスクでこれを行う方法を覚えていることでしょう:
$ php symfony generate:app backend
prod
環境では http://jobeet.localhost/backend.php/
、dev
環境では http://jobeet.localhost/backend_dev.php/
を通してバックエンドアプリケーションを利用できます。
フロントエンドアプリケーションを作ったとき、運用環境のフロントコントローラの名前は
index.php
でした。ディレクトリごとにindex.php
は1つしか置けないので、symfony は一番最初のフロントコントローラにindex.php
を作り、その後はアプリケーションの名前を使います。
propel:data-load
'doctrine:data-load</doctrine> タスクでデータフィクスチャをリロードしようとしても、動かないでしょう。
JobeetJob::save()メソッドは
frontendアプリケーションの
app.yml設定ファイルにアクセスすることが必要とするからです。今は2つのアプリケーションがあるので、symfony は最初に見つかるアプリケーションである
backend` アプリケーションを使います。
しかし8日目でみたように、異なるレベルで設定を変更できます。apps/frontend/config/app.yml
ファイルの内容を config/app.yml
に移動させることで、すべてのアプリケーションの間で設定は共有され問題は解決されます。アドミンジェネレータでモデルクラスを広範囲で使うので、今変更しましょう。また、バックエンドアプリケーションの app.yml
で定義される変数が必要になります。
propel:data-load
'doctrine:data-load</doctrine> タスクは
--application` オプションもとります。ですので、あるアプリケーションから特定の設定が必要な場合、次のように実行します:$ php symfony <propel>propel</propel><doctrine>doctrine</doctrine>:data-load --application=frontend
バックエンドモジュール
フロントエンドアプリケーションでは、モデルクラスにもとづいた基本的な CRUD モジュールをブートストラップするために、propel:generate-module
doctrine:generate-module
タスクを使いました。バックエンドでは、モデルクラスの完全なバックエンドインターフェイスを生成する doctrine:generate-admin
タスクを使います:
$ php symfony <propel>propel</propel><doctrine>doctrine</doctrine>:generate-admin backend JobeetJob --module=job
$ php symfony <propel>propel</propel><doctrine>doctrine</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: sf<propel>Propel</propel><doctrine>Doctrine</doctrine>RouteCollection
options:
model: JobeetJob
module: job
prefix_path: job
column: id
with_wildcard_routes: true
アドミンインターフェイスの主な目的ははモデルオブジェクトのライフサイクルの管理なので、アドミンジェネレータによって使われるルートクラスが sfDoctrineRouteCollection
であるのは驚くことではありません。
ルートは以前には見かけなかったオプションも定義します:
prefix_path
: 生成されるルートの前に置かれるパスを定義する (たとえば、編集ページは/job/1/edit
のようになる)。column
: オブジェクトを参照するリンク用の URL で使うテーブルのカラムを定義するwith_wildcard_routes
: アドミンインターフェイスは典型的な CRUD オペレーション以上の機能を持つため、このオプションでルートの編集なしにさらなるオブジェクトやコレクションアクションを定義することを許可します。
いつものことですが、新しいタスクを使う前にヘルプを読むのはよい考えです。
$ php symfony help <propel>propel</propel><doctrine>doctrine</doctrine>:generate-admin
このコマンドによって他のソフトウェアの典型的な使い方の例と同じようにタスクのすべての引数とオプションが表示されます。
バックエンドの外観
生成されたモジュールはすぐに利用できます:
http://jobeet.localhost/backend_dev.php/job
http://jobeet.localhost/backend_dev.php/category
アドミンモジュールは以前に生成したシンプルなモジュールよりも多くの機能を持ちます。PHP を一行も書くことなく、それぞれのモジュールはこれらのすばらしい機能を提供します:
- オブジェクトのリストはページネートされる
- リストをソート可能
- リストをフィルタリングできる
- オブジェクトを作成、編集、削除できる
- 選択されたオブジェクトを一括で削除できる
- フォームのバリデーションができる
- ユーザーはフラッシュメッセージによってすぐに結果を得る
- さらに多くの機能…
アドミンジェネレータはパッケージを設定し、簡単にバックエンドインターフェイスを作るために必要なすべての機能を提供します。
2つの生成されたモジュールを見ると、ウェブデザインされていないことに気付くでしょうが、symfony 組み込みのアドミンジェネレータにはデフォルトで基本的なグラフィックインターフェイスがあります。現在、sfDoctrinePlugin からのアセットが web/ フォルダーに置かれていません。plugin:publish-assets
によって web/ フォルダー下に配置します。
$ php symfony plugin:publish-assets
ユーザーエクスペリエンスを少し改善するために、デフォルトのバックエンドをカスタマイズする必要があります。異なるモジュールの間を移動することを楽にするためにシンプルなメニューを追加します。
デフォルトのレイアウトファイルの内容を次のコードに置き換えます:
// 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="/images/logo.jpg" 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="/images/jobeet-mini.png" /> powered by <a href="http://www.symfony-project.org/"> <img src="/images/symfony.gif" alt="symfony framework" /></a> </div> </div> </body> </html>
レイアウトは admin.css
スタイルシートを使います。これは4日目の間に他のスタイルシートと一緒にインストールしたので、このファイルは web/css/
のなかに存在しなければなりません。
最後に、routing.yml
のデフォルト homepage を変更します:
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()) )) { // ...
アドミンジェネレータの動作は既知の動作と良く似ています。実際、これはモデルとフォームクラスに関してすでに学んだこととよく似ています。モデルスキーマの定義に基づいて、symfony はモデルとフォームクラスを生成します。アドミンジェネレータの場合、生成されたモジュールを module フォルダ内の config/generator.yml
ファイルを編集することで設定できます。
---
# apps/backend/modules/job/config/generator.yml
generator:
class: sf<propel>Propel</propel><doctrine>Doctrine</doctrine>Generator
param:
model_class: JobeetJob
theme: admin
non_verbose_templates: true
with_show: false
singular:
plural:
route_prefix: jobeet_job
with_<propel>propel</propel><doctrine>doctrine</doctrine>_route: true
config:
actions:
fields:
list:
filter:
form:
edit:
new:
generator.yml
ファイルを更新するたびに、symfony はキャッシュを再生成します。後で見るように、アドミンジェネレータに生成されたモジュールをカスタマイズするのは簡単で、早く、楽しいことです。
キャッシュファイルの自動再生成は開発環境でのみ行われます。運用環境では、
cache:clear
タスクを使ってキャッシュを手動でクリアする必要があります。
--
with_show パラメータに効果はありません。このパラメータは propeldoctrine:generate-module タスクを用いた 「通常の」モジュールのときにのみ効果があります。
バックエンドのコンフィギュレーション
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
セクションでカスタマイズできます:
fields
セクションはすべてのビューのフィールドコンフィギュレーションをオーバーライドします。つまり、 is_activated
フィールドのラベルが list
、edit
と new
ビューに合わせて変化するということです。
アドミンジェネレータのコンフィギュレーションはコンフィギュレーションカスケードの原則に基づきます。たとえば、ラベル用の list
ビューのみを変更したい場合、list
セクションの下の fields
オプションを定義します:
メインの fields
セクションの下で設定されるコンフィギュレーションはビュー固有のコンフィギュレーションによってオーバーライドされます。オーバーライドのルールは次のとおりです:
new
とedit
はfields
を継承するform
を 継承するlist
はfields
を継承するfilter
はfields
を継承する
フォームセクション (
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 は異なるレイアウトで表示されます。デフォルトのレイアウトはテーブルレイアウト です。このことはそれぞれのカラムの値がそれぞれのテーブルカラムのなかにあることを意味します。しかし job
モジュールに関しては、他の組み込みのレイアウトであるスタックレイアウトを使うほうが良いでしょう。:
---
# 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%%):
スタックレイアウトでは、それぞれのオブジェクトはparams
オプションによって定義された通りに1つの文で表示されます。
display
オプションはユーザーがソートできるようにするためにまだ必要です。
「バーチャル」カラム
この設定によって、%%category_id%%
セグメントはカテゴリの主キーによって置き換えられます。しかし、カテゴリの名前を表示するほうがより分かりやすくなるでしょう。
%%
の表記を使うときは、変数はデータベーススキーマの実際にカラムに一致する必要はありません。アドミンジェネレータはモデルクラスに関連する getter が見つかることだけを必要とします。
カテゴリの名前を表示するために、JobeetJob
モデルクラスの getCategoryName()
メソッドを定義し %%category_id%%
を %%category_name%%
で置換します。
しかし JobeetJob
クラスにはすでに関連カテゴリオブジェクトを返す getJobeetCategory()
メソッドがあります。%%jobeet_category%%
を使う場合、JobeetCategory
クラスにはオブジェクトを文字列に変換する __toString()
マジックメソッドがあるので、%%jobeet_category%% は動作します。
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');
$jobs = JobeetJobPeer::retrieveByPks($ids);
foreach ($jobs as $job)
$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/JobeetJob.php // lib/model/doctrine/JobeetJob.class.php class JobeetJob extends BaseJobeetJob { public function extend($force = false) { if (!$force && !$this->expiresSoon()) { return false; }
$this->setExpiresAt(time() + 86400 * sfConfig::get('app_active_days')); $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'); } // ... }
actions
オブジェクトのリストもしくは単独のオブジェクトにアクションをリンクする方法を見てきました。新しいオブジェクトを作るように、actions
オプションはオブジェクトをまったく取らないアクションを定義します。デフォルトの new
アクションを削除して60日以上投稿者によってアクティベートされなかったすべての求人を削除する新しいアクションを追加しましょう:
これまで、定義したすべてのアクションは ~
をもちます。これは symfony がアクションを自動的に設定することを意味します。それぞれのアクションはパラメータの配列を定義することでカスタマイズできます。label
オプションは symfony によって生成されたデフォルトのラベルをオーバーライドします。
デフォルトでは、リンクをクリックするときに実行されるアクションの名前は list
をプレフィックスとします。
job
モジュールの listDeleteNeverActivated
アクションを作りましょう:
// apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeListDeleteNeverActivated(sfWebRequest $request) {
$nb = JobeetJobPeer::cleanup(60); $nb = Doctrine_Core::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');
}
// ...
}
昨日定義した JobeetJobPeer::cleanup()
JobeetJobTable::cleanup()
メソッドを再利用しました。これも MVC パターンによって提供される再利用性のすばらしい例です。
d
action
パラメータを渡すことで実行されるアクションも変更できます:
peermethod
tablemethod
Web デバッグツールバーによって表示されるように、求人リストのページを表示するために必要なデータベースへのリクエスト回数は14回です。
その数字をクリックすると、ほとんどのリクエストがそれぞれの求人のカテゴリ名を取得するためだということが分かるでしょう:
リクエストの数を減らすために、peermethod
tablemethod
オプションを使用して求人情報を得るのに使われるデフォルトメソッドを変更できます。
---
# apps/backend/modules/job/config/generator.yml
config:
list:
peer_method: doSelectJoinJobeetCategory
doSelectJoinJobeetCategory()
メソッドは job
と category
テーブルのあいだに JOIN を追加し、それぞれの求人に関連するカテゴリオブジェクトを自動的に作ります。 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
) が定義され、それぞれはフォームフィールドのサブセットを含みます。
Admin
グループのカラムはまだブラウザには表示されません。これらは求人フォームの定義に設定されていないからです。admin アプリケーション用にカスタムの求人フォームクラスを定義するときのいくつかのセクションに現れます。
アドミンジェネレータは多対多のリレーションシップ用の組み込み機能をサポートします。カテゴリフォームにおいて、名前の入力ボックス、スラッグの入力ボックス、関連するアフィリエイト用のドロップダウンボックスがあります。このページでこのリレーションを編集するのは意味がないので、削除しましょう:
// lib/form/JobeetCategoryForm.class.php // lib/form/doctrine/JobeetCategoryForm.class.php class JobeetCategoryForm extends BaseJobeetCategoryForm { public function configure() { unset($this['jobeet_category_affiliate_list']); 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()
メソッドを通してアクセスできます。
チルダ (
~
) をフィールドの名前のプレフィックスにすることで、表示をコンポーネントに委譲することもできます。
class
管理者によって使われるフォームとして、ユーザーの求人フォームよりも多くの情報を表示してきました。しかし今では、JobeetJobForm
クラスでこれらを削除したので、これらのなかにはフォームに表示されないものがあります。
フロントエンドとバックエンドで異なるフォームを用意するには、2つのフォームクラスを作る必要があります。JobeetJobForm
クラスを継承する BackendJobeetJobForm
クラスを作りましょう。隠したフィールドが同じでないようにするため、BackendJobeetJobForm でオーバーライドするメソッドのunset() 文を移動させるように少しリファクタリングする必要があります:
// lib/form/JobeetJobForm.class.php // 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/BackendJobeetJobForm.class.php // lib/form/doctrine/BackendJobeetJobForm.class.php class BackendJobeetJobForm extends JobeetJobForm { protected function removeFields() { unset( $this['created_at'], $this['updated_at'], $this['token'] ); } }
アドミンジェネレータによって使われるデフォルトのフォームクラスは class
オプションを設定することでオーバーライドできます:
---
# apps/backend/modules/job/config/generator.yml
config:
form:
class: BackendJobeetJobForm
クラスを新たに作ったので、キャッシュをクリアすることをお忘れなく。
edit
フォームは小さな問題をかかえています。現在のアップロードされたロゴはどこにも表示されないので、現在のものを削除できません。sfWidgetFormInputFileEditable
ウィジェットはシンプルなファイル入力ウィジェットに編集機能を追加します:
// lib/form/BackendJobeetJobForm.class.php // 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
: ウィジェットをレンダリングするために使うテンプレート
生成されたテンプレートは多くの
class
とid
属性を定義するので、アドミンジェネレータの見た目はとても簡単に調整できます。たとえば、ロゴフィールドはsf_admin_form_field_logo
クラスを使ってカスタマイズできます。それぞれのフィールドはsf_admin_text
もしくはsf_admin_boolean
のようなフィールドタイプによって決まるクラスも持ちます。
edit_mode
オプションは sfPropelRecord::isNew()
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つのことしか行わないので、たくさんのコードをコピー&ペーストしなくてもふるまいを変更するのは簡単です。
テンプレートのカスタマイズ
アドミンジェネレータによって HTML コードに追加される class
と id
属性を用いて生成されたテンプレートをカスタマイズする方法を見てきました。
クラスと同じように、オリジナルのテンプレートをオーバーライドすることもできます。テンプレートはプレーンな PHP ファイルで PHP クラスではないので、モジュールのなかで同じ名前のテンプレートを作ることでテンプレートをオーバーライドできます (たとえば job
アドミンモジュールでは apps/backend/modules/job/templates/
ディレクトリ):
テンプレート | 説明 |
---|---|
_assets.php |
テンプレート用の CSS と JS を表示する |
_filters.php |
フィルタボックスを表示する |
_filters_field.php |
単独のフィルタフィールドを表示する |
_flashes.php |
フラッシュメッセージを表示する |
_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 |
行単位のスタックレイアウトを表示する |
_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 の最終的なコンフィギュレーションは次のとおりです:
#%%is_activated%% %%jobeet_category%% - %%company%% %%is_activated%% %%JobeetCategory%% - %%company%% (%%email%%) 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 } peer_method: doSelectJoinJobeetCategory 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: sf<propel>Propel</propel><doctrine>Doctrine</doctrine>Generator
param:
model_class: JobeetCategory
theme: admin
non_verbose_templates: true
with_show: false
singular:
plural:
route_prefix: jobeet_category
with_<propel>propel</propel><doctrine>doctrine</doctrine>_route: true
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 用のすばらしいバックエンドインターフェイスを短時間で開発しました。
何かが YAML ファイルで設定可能であるとき、プレーンな PHP コードを使うことができるのはご存じのとおりです。アドミンジェネレータの場合、
apps/backend/modules/job/lib/jobGeneratorConfiguration.class.php
ファイルを編集できます。これによって YAML ファイルと同じオプションが PHP で提供されます。メソッドの名前を学ぶには、生成された基底クラスのcache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php
を見てください。
また明日
丁度一時間で、Jobeet プロジェクト用に十分な機能をもつバックエンドインターフェイスを開発しました。全体で、50行に満たない PHP コードしか書きませんでした。こんなに多くの機能がある割りにまずますです!
明日は、ユーザー名とパスワードでバックエンドアプリケーションをセキュアにする方法を見ることになります。symfony のユーザークラスに関して話す機会でもあります。
ORM
インデックス
Document Index
関連ページリスト
Related Pages
- 1日目: プロジェクトを始める
- 2日目: プロジェクト
- 3日目: ~データモデル~
- 4日目: Controller と View
- 5日目: ルーティング
- 6日目: モデルの詳細
- 7日目: カテゴリページで遊ぶ
- 8日目: ユニットテスト
- 9日: 機能テスト
- 10日目: フォーム
- 11日目: フォームをテストする
- 12日目: アドミンジェネレータ
- 13日目: ユーザー
- 14日目: フィード
- 15日目: Web サービス
- 16日目: ~メーラー~
- 17日目: 検索
- 18日目: ~AJAX~
- 19日目: 国際化とローカライゼーション
- 20日目: プラグイン
- 21日目: キャッシュ
- 22日目: デプロイ
- 23日目: 別の視点から symfony を見る
- Appendix A - License
- 謝辞
日本語ドキュメント
Japanese Documents
- 2011/01/18 Chapter 17 - Extending Symfony
- 2011/01/18 The generator.yml Configuration File
- 2011/01/18 Les tâches
- 2011/01/18 Emails
- 2010/11/26 blogチュートリアル(8) ビューの作成
リリース情報
Release Information
- 2.0 : 2.0.15(2011/05/30)
Symfony2日本語ドキュメント - 1.4 : 1.4.18(2012/05/30)
Changelog