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/
を通してバックエンドアプリケーションを利用できます。
note
フロントエンドアプリケーションを作ったとき、運用環境のフロントコントローラの名前は index.php
でした。ディレクトリごとに index.php
は1つしか置けないので、symfony は一番最初のフロントコントローラに index.php
を作り、その後はアプリケーションの名前を使います。
doctrine:data-load
タスクでデータフィクスチャをリロードしようとしても、動かないでしょう。JobeetJob::save()
メソッドは frontend
アプリケーションの app.yml
設定ファイルにアクセスすることが必要とするからです。今は2つのアプリケーションがあるので、symfony は最初に見つかるアプリケーションである backend
アプリケーションを使います。
しかし8日目でみたように、異なるレベルで設定を変更できます。apps/frontend/config/app.yml
ファイルの内容を config/app.yml
ファイルに移動させることで、すべてのアプリケーションの間で設定は共有され問題は解決されます。アドミンジェネレータでモデルクラスを広範囲で使うので、今変更しましょう。また、バックエンドアプリケーションの 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
これら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
アドミンインターフェイスの主な目的ははモデルオブジェクトのライフサイクルの管理なので、アドミンジェネレータによって使われるルートクラスが
sfDoctrineRouteCollection
であるのは驚くことではありません。
ルートは以前には見かけなかったオプションも定義します。
prefix_path
: 生成されるルートの前につけられるパスを定義する (たとえば、編集ページは/job/1/edit
のようになる)。column
: オブジェクトを参照するリンク用の URL で使うテーブルのカラムを定義するwith_wildcard_routes
: アドミンインターフェイスは典型的な CRUD オペレーション以上の機能を持つため、このオプションでルートの編集なしにさらなるオブジェクトやコレクションアクションを定義することを許可します。
tip
いつものことですが、新しいタスクを使う前にヘルプを読むのはよい考えです。
$ php symfony help 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="/legacy/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="/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
ファイルのデフォルトの homepage を変更します。
# 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()) )) { // ...
アドミンジェネレータの動作は既知の動作と良く似ています。実際、これはモデルとフォームクラスに関してすでに学んだこととよく似ています。モデルスキーマの定義に基づいて、symfony はモデルとフォームクラスを生成します。アドミンジェネレータの場合、生成されたモジュールを module フォルダ内の 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_propel_route: true config: actions: ~ fields: ~ list: ~ filter: ~ form: ~ edit: ~ new: ~
generator.yml
ファイルを更新するたびに、symfony はキャッシュを再生成します。後で見るように、アドミンジェネレータに生成されたモジュールをカスタマイズするのは簡単で、早く、楽しいことです。
note
キャッシュファイルの自動再生成は開発環境でのみ行われます。運用環境では、cache:clear
タスクを使ってキャッシュを手動でクリアする必要があります。
NOTE
with_show
パラメータに効果はありません。このパラメータは doctrine: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
セクションでカスタマイズできます。
# 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
ビューに合わせて変化するということです。
アドミンジェネレータのコンフィギュレーションはコンフィギュレーションカスケードの原則に基づきます。たとえば、ラベル用の list
ビューのみを変更したい場合、list
セクションの下の fields
オプションを定義します。
# apps/backend/modules/job/config/generator.yml config: list: fields: is_public: { label: "Public? (label for the list)" }
メインの fields
セクションの下で設定されるコンフィギュレーションはビュー固有のコンフィギュレーションによってオーバーライドされます。オーバーライドのルールは次のとおりです:
new
とedit
はfields
を継承するform
を 継承する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 は異なるレイアウトで表示されます。デフォルトのレイアウトはテーブルレイアウト です。このことはそれぞれのカラムの値がそれぞれのテーブルカラムのなかにあることを意味します。しかし 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つの文で表示されます。
note
display
オプションはユーザーがソートできるようにするためにまだ必要です。
「バーチャル」カラム
この設定によって、%%category_id%%
セグメントはカテゴリの主キーによって置き換えられます。しかし、カテゴリの名前を表示するほうがよりわかりやすくなるでしょう。
%%
の表記を使うときは、変数はデータベーススキーマの実際にカラムに一致する必要はありません。アドミンジェネレータはモデルクラスに関連する getter が見つかることだけを必要とします。
カテゴリの名前を表示するために、JobeetJob
モデルクラスの getCategoryName()
メソッドを定義し %%category_id%%
を %%category_name%%
で置換します。
しかし JobeetJob
クラスにはすでに関連カテゴリオブジェクトを返す getJobeetCategory()
メソッドがあります。%%jobeet_category%%
を使う場合、JobeetCategory
クラスにはオブジェクトを文字列に変換する __toString()
マジックメソッドがあるので、%%jobeet_category%%
は動作します。
# 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
オプションを追加することで、デフォルトのソートカラムを設定できます。
# 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'); } // ... }
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_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'); } // ... }
昨日定義した JobeetJobTable::cleanup()
メソッドを再利用しました。
これも MVC パターンによって提供される再利用性のすばらしい例です。
note
action
パラメータを渡すことで実行されるアクションも変更できます。
deleteNeverActivated: { label: Delete never activated jobs, action: foo }
tablemethod
Web デバッグツールバーによって表示されるように、求人リストのページを表示するために必要なデータベースへのリクエスト回数は14回です。
その数字をクリックすると、ほとんどのリクエストがそれぞれの求人のカテゴリ名を取得するためだということがわかるでしょう。
リクエストの数を減らすために、tablemethod
オプションを使用して求人情報を得るのに使われるデフォルトメソッドを変更できます。
# 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 アプリケーション用にカスタムの求人フォームクラスを定義するときのいくつかのセクションにあらわれます。
アドミンジェネレータは多対多のリレーションシップ用の組み込み機能をサポートします。カテゴリフォームにおいて、名前の入力ボックス、スラッグの入力ボックス、関連するアフィリエイト用のドロップダウンボックスがあります。このページでこのリレーションを編集するのは意味がないので、削除しましょう。
// 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
クラスを作りましょう。隠したフィールドが同じでないようにするため、BackendJobeetJobForm
でオーバーライドするメソッドのなかの unset()
関数を移動させるように少しリファクタリングする必要があります。
// 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 { protected function removeFields() { unset( $this['created_at'], $this['updated_at'], $this['token'] ); } }
アドミンジェネレータによって使われるデフォルトのフォームクラスは 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
属性を定義するので、アドミンジェネレータの見た目はとてもかんたんに調整できます。たとえば、ロゴフィールドは 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つのことしか行わないので、たくさんのコードをコピー&ペーストしなくてもふるまいを変更するのはかんたんです。
テンプレートのカスタマイズ
アドミンジェネレータによって 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 の最終的なコンフィギュレーションは次のとおりです。
# 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: true 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: 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 用のすばらしいバックエンドインターフェイスを短時間で開発しました。
tip
何かが 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 のユーザークラスに関して話す機会でもあります。
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.