Jobeet - 12日目: アドミンジェネレータ
昨日Jobeetに追加した機能は求職者や求人側のフロントエンドアプリケーションがより便利になるものでした。バックエンドアプリケーションについて少しお話しします。
今日はsymfonyの機能的なアドミンジェネレータを使うことでちょうど1時間くらいでJobeetに完全なバックエンドのインタフェースを開発するつもりです。
Backendの作成
一番最初のステップはバックエンドアプリケーションを作成することです。もし記憶力が良いなら、generate:appタスクの使い方を覚えるべきです。
$ php symfony generate:app --escaping-strategy=on --csrf-secret=UniqueSecret1 backend
たとえバックエンドアプリケーションがJobeetの管理者だけが使う場合でも、symfonyに組み込まれているセキュリティ要素は有効にしておきます。
もしパスワードに$のような特殊文字を使いたいなら、CLI上で適切なエスケープが必要になります。
$ php symfony generate:app --csrf-secret=Unique\$ecret backend
バックエンドアプリケーションはこれで利用できるようになります、本番環境は http://jobeet.localhost/backend.php/ で、開発環境は http://jobeet.localhost/backend_dev.php/ で動作します。
フロントエンドアプリケーションを生成するとき、生成物の中にindex.phpがあります。1ディレクトリに1つのindex.phpしか持てないので、symfonyは一番最初のフロントコントローラ用にindex.phpを生成し、その後はアプリケーション名を利用します。
もしpropel:data-loadタスクを使ってデータをリロードしようと試しても動作しないでしょう。その理由はJobeetJob::save()メソッドがfrontendアプリケーションのapp.yml設定ファイルを利用する必要があるからです。現在2つのアプリケーションが存在し、symfonyは最初に見つけた方を使います。それはbackend側のファイルです。
しかし8日目で見たように、設定は異なったレベルで構成できます。 apps/frontend/config/app.ymlファイルを config/app.ymlへ移動させることによって、設定は全てのアプリケーション間で共有され、問題は解決します。この変更で、アドミンジェネレータ内で広範囲のモデルクラスを使うことになります、そのためバックエンドアプリケーション内のapp.ymlファイルで変数を定義することが必要となります。
propel:data-loadタスクは--applicationオプションを引数にとれます。もしアプリケーションにおいて特定の設定が必要なら、下記コマンドが使えます。
$ php symfony propel:data-load --application=frontend
Backendモジュール
フロントエンドアプリケーション用に、モデルクラスをベースとしたCRUDモジュールを生成するのにpropel:generate-moduleタスクを利用しました。バックエンド用には、propel:generate-adminタスクを使うことでモデルクラス用の完全に動作するバックエンドのインターフェースが生成されます。
$ php symfony propel:generate-admin backend JobeetJob --module=job $ php symfony propel:generate-admin backend JobeetCategory --module=category
これら2つのコマンドはJobeetJob、JobeetCategoryモデルそれぞれに対応するjob、categoryモジュールを生成します。
任意の --module オプションでタスクがデフォルトで付ける名前を上書きできます。(指定していないとJobeetJobクラスの場合jobeet_jobという名前になります)
ひそかに、タスクは各モジュール間のカスタムルートも生成します。
# apps/backend/config/routing.yml jobeet_job: class: sfPropelRouteCollection options: model: JobeetJob module: job prefix_path: job column: id with_wildcard_routes: true
アドミンインターフェースの主な目標はモデルオブジェクトのライフサイクルの管理であるため、アドミンジェネレータによって使われるルートクラスがsfPropelRouteCollectionであることは何ら驚くべきことではありません
ルートの定義は上記のサンプルで出てこないようなオプションを定義します。
- prefix_path
- 生成されたルートのためのprefixパスを定義します(例えば、編集ページでは /job/1/edit のようになります)
- column
- オブジェクトを参照するため、URLで使っているカラムを定義します
- with_wildcard_routes
- アドミンインターフェースがより多くの回数のクラシックなCRUD操作をするのであれば、このオプションを有効にすることでルートを編集することなしでより多くのオブジェクトやコレクションを定義できます
いつものことですが、新しいタスクを使う前にヘルプを読むのは良い傾向です。
$ php symfony help propel:generate-adminそのタスクの典型的な使い方だけでなく、全ての引数、オプションの使い方を見れます。
Backendの見た目
即座に、生成したモジュールは使えるようになります。
http://jobeet.localhost/backend_dev.php/job
http://jobeet.localhost/backend_dev.php/category
アドミンモジュールは以前生成したシンプルなモジュールよりもより多くの要素を持っています。1行もPHPコードを書くことなしに各モジュールは下記の素晴らしい要素を提供します。
- オブジェクトのリストのペジネート
- ソーティング処理
- フィルタ処理
- オブジェクトの生成、編集、削除
- 選択したオブジェクトに対するバッチ処理
- フォームバリデーション
- ユーザへ直接フィードバックするFlashメッセージ
- などなど...
アドミンジェネレータはパッケージを構成するためのシンプルなバックエンドインターフェースを生成するのに必要な全ての要素を提供します。
より良いユーザ体験にさせるため、デフォルトのバックエンドをカスタマイズする必要があります。シンプルなメニューを追加して、異なったモジュール間でのナビゲーションを簡単にさせます。
デフォルトの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="/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内でsymfonyのデフォルトホームページを変更します。
# apps/backend/config/routing.yml homepage: url: / param: { module: job, action: index }
symfonyキャッシュ
好奇心が旺盛な人ならば、おそらくすでにタスクが生成したapps/backend/modulesディレクトリ下のファイルを開いているでしょう。もしそうでないなら、今開いてみましょう。ビックリしましたか?テンプレートディレクトリは空で、actions.class.phpファイルも同じようにもぬけの殻です。
<?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/ ディレクトリで見ることができ、本当のモジュールを含んでいます。
<?php // 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はモデルとフォームクラスを生成します。アドミンジェネレータでは、生成されたモジュールはモジュール内にある config/generator.yml ファイルを編集することで設定できます。
# apps/backend/modules/job/config/generator.yml generator: class: sfPropelGenerator 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: ~
generator.ymlを更新するたびに、symfonyはキャッシュを再生成します。今日見たように生成されたモジュールをカスタマイズすることは簡単で速く、そして面白いです。
キャッシュの自動再生成は開発環境のみで起こります。本番環境では、cache:clearタスクを使って手動でキャッシュ削除する必要があります
Backendの設定
アドミンモジュールは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)はフィールドから成っています。1つのフィールドはモデルクラスのカラムや後で見るようなバーチャルカラムとなります。
デフォルトのフィールド設定は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? }
fieldsセクションは全てのビューのフィールド設定を上書きします。これはis_activatedフィールドのlabelは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はformとfieldsから継承
- listはfieldsから継承
- filterはfieldsから継承
フォームセクション(form, edit, new)では、labelとhelpオプションはformクラス内の物を上書きします。
リストビュー設定
display
デフォルトでは、リストビューのカラムはschema.ymlファイル順にモデルの全カラムとなっています。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
リストは異なったレイアウトで表示できます。デフォルトではレイアウトは表形式で、各コラム値がそれぞれのテーブルカラムとなっています。しかし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オプションで定義した一連の文字列として表されます。
displayオプションはユーザによって分類されたカラムを定義することがまだ必要です。
バーチャルカラム
この設定で、%%category_id%%セグメントはcategoryプライマリキーで置き換えることが可能です。しかしカテゴリ名を表示することでもっと意味を持たせれます。
%%表記を使うときはいつでも、変数はデータベーススキーマ内に存在するカラムと一致する必要はありません。アドミンジェネレータはモデルクラスの関連するgetterを見つけるためだけに必要です。
カテゴリ名を表示するためには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オプションを追加することでデフォルトでソートするカラムを設定することができます。
# 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: ~
_から始まる全てアクションはフレームワークによって提供されるアクションです。もしブラウザをリフレッシュして、extendバッチアクションを選択したなら、symfonyはexecuteBatchExtend()メソッドを生成するように促すエラーとともに例外を投げるでしょう。
<?php // 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) { $job->extend(true); } $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.'); $this->redirect('@jobeet_job'); } }
選択されたプライマリキーはidsというリクエストパラメータに保存されています。選択された仕事ごとにJobeetJob::extend()メソッドを期限切れチェックを回避した特別な引数とともにコールさせます。
このアカウント内の新しい引数を持つようにextend()メソッドを更新します。
<?php // lib/model/JobeetJob.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->save(); return true; } // ... }
object_actions
リスト内では、シングルオブジェクトとして実行できるアクション用の追加のカラムがあります。categoryモジュールでは編集可能なカテゴリー名がリンクを持つようになっているのを削除してみましょう。また、リストから1つのディレクトリを削除するのは実際には必要ありません。
# 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()アクションを定義する必要があります。
<?php // 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アクションを生成します。
<?php // apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeListDeleteNeverActivated(sfWebRequest $request) { $nb = JobeetJobPeer::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()メソッドを再利用します。これはMVCパターンが提供する再利用性の良いサンプルです。
actionパラメータへ渡す実行するアクションを変更できます。
deleteNeverActivated: { label: Delete never activated jobs, action: foo }
peer_method
Webデバッグツールバーで確認すると、仕事のページを表示するために必要なデータベースへのリクエスト数は14です。
そのリクエスト数をクリックすれば、ほとんどのリクエストが各仕事ごとにカテゴリー名で取得しているのが見れます。
データベースへのリクエスト数を減らすために、peer_methodオプションを使って仕事を取得するのに利用するデフォルトメソッドを変更できます。
# apps/backend/modules/job/config/generator.yml config: list: peer_method: doSelectJoinJobeetCategory
doSelectJoinJobeetCategory()メソッドはjobテーブルとcategoryテーブル間にJOINを追加し、各仕事に関連するカテゴリーオブジェクトを自動で生成します。
リクエスト数は4回に減っています。
フォームビューの設定
フォームビューの設定は3つのセクションから構成されます。form、edit、newセクションです。それらは全て同じ設定の機能であるので、editやnewセクションの替わりにformセクションだけを使います。
display
listで行ったように、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グループのカラムはブラウザからまだ見えません。アドミンアプリケーションでカスタムジョブフォームクラスを定義すれば、それらは数セクション内に現れるでしょう。
アドミンジェネレータは多対多のリレーションシップのサポートは組み込まれています。カテゴリーフォームでは、名前やスラッグの入力、ドロップダウンボックスから関連する会社を選択します。ページ上で関連を編集することが意味がないのであれば削除してしまいましょう。
<?php // lib/form/JobeetCategoryForm.class.php class JobeetCategoryForm extends BaseJobeetCategoryForm { public function configure() { unset($this['jobeet_category_affiliate_list']); } }
「Virtual」クラス
ジョブフォーム用のdisplayオプション内で、アンダースコアで始まる_generated_tokenフィールドを定義しました。これはこのフィールドのレンダリングをカスタムパーシャルである _generated_token.php で処理させるということを意味します。
下記コードを使ってパーシャルを作成します。
<?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クラスを作成しましょう。同じhiddenフィールドを持たないようにするため、JobeetJobFormクラスを少しリファクタリングして、BackendJobeetJobFormクラスにunset()ステートメントのメソッドをオーバーライドする必要があります。
<?php // lib/form/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['is_activated'], $this['token'] ); } } // lib/form/BackendJobeetJobForm.class.php class BackendJobeetJobForm extends JobeetJobForm { public function configure() { parent::configure(); } protected function removeFields() { unset( $this['created_at'], $this['updated_at'], $this['token'] ); } }
アドミンジェネレータが使うデフォルトフォームはclassオプションで設定をオーバーライドできます。
# apps/backend/modules/job/config/generator.yml config: form: class: BackendJobeetJobForm
新しいクラスを追加したときはキャッシュの削除を忘れずに。
editフォームはまだ少し使いづらいです。現在アップロードされたロゴはどこにも表示されず、削除することもできません。sfWidgetFormInputFileEditableウィジェットはシンプルなファイル入力の編集機能を追加します。
<?php // lib/form/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オプションはsfPropel::isNew()メソッドを使います。
isNew()メソッドはフォームのモデルオブジェクトが新しく生成されたものであればtrueを、それ以外はfalseを返します。埋め込みオブジェクトのステータスに依存する異なったウィジェットやバリデータを持つ必要があるとき、とても役に立ちます。
フィルタの設定
フィルタの設定はフォームビューの設定とよく似ています。事実問題として、フィルタはフォームのようなものです。フォームに関して、クラスはpropel:build-allタスクによって生成されます。propel:build-filtersタスクを使うと再生成できます。
フォームフィルタクラスは lib/filter/ ディレクトリ下に位置されていて、各モデルクラスはフィルタフォームクラスと関連づけられています(JobeetJobFormFilterとJobeetJobFormのように)。
categoryモジュール用のフィルタを完全に削除してみましょう。
# apps/backend/modules/category/config/generator.yml config: filter: class: false
jobモジュール用に、いくつかのフィルタを削除してみましょう。
# apps/backend/modules/job/config/generator.yml filter: display: [category_id, company, position, description, is_activated, is_public, email, expires_at]
フィルタは常に任意設定であるため、表示されるフィールドを構成するためにフィルタフォームクラスをオーバーライドする必要はありません。
アクションの設定
設定が十分でないとき、拡張した要素を見るためにアクションクラスに新しいメソッドを追加できます。しかし生成されたアクションメソッドをオーバーライドすることもできます。
メソッド | 説明 |
---|---|
exexuteIndex() | listビューアクション |
executeFilter() | フィルタの更新 |
executeNew() | 新しいビューアクション |
executeCreate() | 新しいジョブの作成 |
executeEdit() | editビューアクション |
executeUpdate() | ジョブの更新 |
executeDelete() | ジョブの削除 |
executeBatch() | バッチアクションの実行 |
executeBatchDelete() | _deleteバッチアクションの実行 |
processForm() | ジョブフォームの処理 |
getFilters() | 現在のフィルタを返す |
setFilters() | フィルタのセット |
getPager() | リストページャを返す |
getPage() | ページャページの取得 |
setPage() | ページャページをセット |
buildCriteria() | リスト用のCriteriaをビルド |
addSortCriteria() | リスト用のソートCriteriaを追加 |
getSort() | ソートしているカラムを返す |
setSort() | ソートするカラムのセット |
生成された各メソッドがたった1つであるなら、たくさんのコードをコピー&ペーストすることなしに動作を変更するのは簡単です。
テンプレートの設定
アドミンジェネレータによってHTMLコード内に追加されたclassやid属性のおかげで生成されたテンプレートをカスタマイズする方法はすでに確認しました。
classに関しては、オリジナルテンプレートで上書きもできます。テンプレートはプレーンPHPファイルでありPHPクラスファイルではないので、モジュール内で同じ名前のテンプレートを作成することで上書きされるようになります(例えば、apps/backend/modules/job/templates/ ディレクトリはjobアドミンモジュールのためのものです)
テンプレート | 説明 |
---|---|
_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ビューの表示 |
最終的な設定
# apps/backend/modules/job/config/generator.yml generator: class: sfPropelGenerator 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: 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>%%jobeet_category%%</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 } peer_method: doSelectJoinJobeetCategory 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: sfPropelGenerator param: model_class: JobeetCategory theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: jobeet_category with_propel_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用のすばらしいバックエンドインターフェースを開発しました。
すでに知っているかもしれませんが、YAMLファイル内で何か設定するときプレーンPHPコード使うことも可能です。アドミンジェネレータ用には apps/backend/modules/job/lib/jobGeneratorConfigguration.class.php ファイルを編集することで可能です。YAMLファイルのようにいくつかのオプションが使えますがPHPインターフェースです。メソッド名を学習するには、生成されるcache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php ファイルの基本クラスを見てください。