昨日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.