Jobeet の2週目は symfony のテストフレームワークの導入で好調な滑り出しを始めました。今日はフォームフレームワークに焦点を合わせます。
フォームフレームワーク
ほとんどの Web サイトにはフォームがあります。シンプルな問い合わせから、たくさんのフィールドがある複雑なものまで、さまざまなフォームがあります。フォームを作る作業は、Web 開発者にとってもっとも複雑で退屈な作業の1つです。HTML フォームを書き、それぞれのフィールド用のバリデーションルールを実装し、値を処理してデータベースに保存し、エラーメッセージを表示し、エラーの場合はフィールドを再設定することなどが必要です。
もちろん、何度も車輪の再発明をする代わりに、symfony はフォームの管理を簡単にするフレームワークを提供します。 フォームフレームワークは3つの部分で構成されます:
- バリデーション: バリデーションサブフレームワークは入力 (整数、文字列、メールアドレス・・・) をバリデートするクラス群を提供します。
- ウィジェット: ウィジェットサブフレームワークは HTML フィールド(入力、テキストエリア、選択・・・) を出力するクラス群を提供します。
- フォーム: フォームクラス群はウィジェットとバリデータで構成されるフォームを表し、フォームを管理しやすくするメソッドを提供します。それぞれのフォームフィールドに、個別のバリデータとウィジェットが設定されます。
フォーム
symfony のフォームは1つ以上のフィールドから構成されるクラスです。それぞれのフィールドは名前、バリデータとウィジェットをもちます。次のクラスでは、シンプルな ContactForm
を定義しています:
class ContactForm extends sfForm { public function configure() { $this->setWidgets(array( 'email' => new sfWidgetFormInputText(), 'message' => new sfWidgetFormTextarea(), )); $this->setValidators(array( 'email' => new sfValidatorEmail(), 'message' => new sfValidatorString(array('max_length' => 255)), )); } }
configure()
メソッドのなかで、setValidators()
と setWidgets()
メソッドを使ってフォームフィールドを定義します。
ウィジェットとバリデータのクラス名はきわめて明確です: email
フィールドは HTML の <input>
タグとしてレンダリングされ (sfWidgetFormInputText
)、メールアドレスとしてバリデートされます (sfValidatorEmail
)。message
フィールドは <textarea>
タグとしてレンダリングされ (sfWidgetFormTextarea
)、255文字以下の文字列でなければなりません (sfValidatorString
)。required
オプションのデフォルト値は true
なので、デフォルトではすべてのフィールドは必須です。ですので、email
のバリデーションの定義は new sfValidatorEmail(array('required' => true))
と同等です。
tip
mergeForm()
メソッドを使ってフォームを別のフォームにマージしたり、embedForm()
メソッドを使って別のフォームに埋め込むことができます:
$this->mergeForm(new AnotherForm()); $this->embedForm('name', new AnotherForm());
Doctrine フォーム
たいていの場合、フォームの内容をデータベースに保存する必要があります。symfony はデータベースモデルに関するすべての内容を知っているので、この情報に基づいてフォームを自動的に生成できます。実際、3日目で doctrine:build --all --and-load
タスクを起動したとき、symfony は自動的に doctrine:build --forms
タスクを呼び出しました:
$ php symfony doctrine:build --forms
doctrine:build --forms
タスクにより、lib/form/
ディレクトリにフォームクラスが生成されます。生成されるファイルの構成は、lib/model/
のものと似ています。それぞれのモデルクラスごとに、対応するフォームクラスが生成されます (たとえば JobeetJob
に対応する JobeetJobForm
)。このクラスは基底クラスを継承し、デフォルトでは次のように空です:
// lib/form/doctrine/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function configure() { } }
tip
lib/form/doctrine/base/
サブディレクトリに生成されたファイルを眺めると、組み込みのウィジェットとバリデータのすばらしい使い方の例がたくさん見つかります。
tip
Doctrine の symfony
ビヘイビアにパラメータを渡すことで特定のモデルでのフォーム生成を無効にできます:
SomeModel: options: symfony: form: false filter: false
求人フォームをカスタマイズする
求人フォームはフォームのカスタマイズを学ぶための完璧な例です。カスタマイズする方法を順に見てみましょう。
最初に、これからの変更をブラウザですぐにチェックできるように、レイアウトの「Post a Job」リンクを変更します:
<!-- apps/frontend/templates/layout.php -->
<a href="<?php echo url_for('job_new') ?>">Post a Job</a>
Doctrine フォームでは、デフォルトではすべてのテーブルカラムに対応するフィールドが表示されます。求人フォームには、エンドユーザーが編集してはならないフィールドがあります。フォームからフィールドを削除するには、単純にフィールドを unset
するだけです:
// lib/form/doctrine/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function configure() { unset( $this['created_at'], $this['updated_at'], $this['expires_at'], $this['is_activated'] ); } }
フィールドを unset
すると、フィールドのウィジェットとバリデータの両方が削除されます。
表示したくないフィールドを解除する代わりに、useFields()
メソッドを使うことで望むフィールドのリストを明確に用意することもできます:
// lib/form/doctrine/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function configure() { $this->useFields(array('category_id', 'type', 'company', 'logo', 'url', 'position', 'location', 'description', 'how_to_apply', 'token', 'is_public', 'email')); } }
useFields()
メソッドはあなたのために2つのことを自動的に行います: 隠しフィールドが追加され、フィールドの配列はフィールドの順序を変更するために使われます。
tip
表示したいフォームフィールドのリストを明示的に示すことは、新しいフィールドを基底フォームに追加するとき、それらのフィールドが自動的にフォームに現れないことを意味します (モデルフォームで新しいカラムを関連テーブルに追加することを想像してください)。
フォームの設定は、データベーススキーマから自動生成された内容よりも正確でなければならないことがあります。たとえば、email
カラムはスキーマでは単なる varchar
ですが、実際はメールアドレスとしてバリデートする必要があります。デフォルトの sfValidatorString
を sfValidatorEmail
に変更してみましょう:
// lib/form/doctrine/JobeetJobForm.class.php public function configure() { // ... $this->validatorSchema['email'] = new sfValidatorEmail(); }
このように、デフォルトのバリデータを置き換えることは、常にベストソリューションというわけではありません。データベーススキーマから自動生成されるデフォルトのバリデーションルール (new sfValidatorString(array('max_length' => 255))
) が失われるからです。特別な sfValidatorAnd
バリデータを使って、新しいバリデータを既存のバリデータに追加するようにしましょう:
// lib/form/doctrine/JobeetJobForm.class.php public function configure() { // ... $this->validatorSchema['email'] = new sfValidatorAnd(array( $this->validatorSchema['email'], new sfValidatorEmail(), )); }
sfValidatorAnd
バリデータは有効な値に対して通るバリデータの配列を受け取ります。ここでは、現在のバリデータを参照し ($this->validatorSchema['email'])、新しいバリデータを追加するトリックを使っています。
note
値が少なくとも1つのバリデータだけを通ればよい場合は、sfValidatorOr
バリデータを使うこともできます。そしてもちろん、sfValidatorAnd
と sfValidatorOr
バリデータを組み合わせて、複雑なブール値にもとづいたバリデータを作ることができます。
スキーマで type
カラムが varchar
である場合でも、値を選択肢のリスト: フルタイム、パートタイムもしくはフリーランスに制限したい場合を考えてみましょう。
最初に、利用可能な値を JobeetJobTable
で定義しましょう:
// lib/model/doctrine/JobeetJobTable.class.php class JobeetJobTable extends Doctrine_Table { static public $types = array( 'full-time' => 'Full time', 'part-time' => 'Part time', 'freelance' => 'Freelance', ); public function getTypes() { return self::$types; } // ... }
type
フィールドのウィジェットに sfWidgetFormChoice
を使います:
$this->widgetSchema['type'] = new sfWidgetFormChoice(array( 'choices' => Doctrine_Core::getTable('JobeetJob')->getTypes(), 'expanded' => true, ));
sfWidgetFormChoice
は選択ウィジェットを表し、設定オプション (expanded
と multiple
) の値に応じて、次のような異なるウィジェットをレンダリングできます:
- ドロップダウンリスト (
<select>
):array('multiple' => false, 'expanded' => false)
- ドロップダウンボックス (
<select multiple="multiple">
):array('multiple' => true, 'expanded' => false)
- ラジオボタンのリスト:
array('multiple' => false, 'expanded' => true)
- チェックボックスのリスト:
array('multiple' => true, 'expanded' => true)
note
デフォルトでラジオボタンの1つを選択した状態にしたい場合 (たとえば full-time
)、データベーススキーマのデフォルト値を変更します。
このようにすれば、有効ではない値を誰も投稿できないと考えるかもしれません。しかし、curl もしくは Firefox の Web Developer Toolbar のようなツールを利用することで、ハッカーはウィジェットの選択肢を簡単に回避できます。そこで、値を利用可能な選択肢のみに制限するためにバリデータを変更しましょう:
$this->validatorSchema['type'] = new sfValidatorChoice(array( 'choices' => array_keys(Doctrine_Core::getTable('JobeetJob')->getTypes()), ));
logo
カラムには求人に関連するロゴのファイル名を保存するので、ウィジェットをファイル入力タグに変更する必要があります:
$this->widgetSchema['logo'] = new sfWidgetFormInputFile(array( 'label' => 'Company logo', ));
それぞれのフィールドに対して、symfony はラベル (<label>
タグに使われる) を自動的に生成します。ラベルは label
オプションで変更できます。
setLabels()
メソッドを使って、ウィジェット配列のラベルを一括で変更することもできます:
$this->widgetSchema->setLabels(array( 'category_id' => 'Category', 'is_public' => 'Public?', 'how_to_apply' => 'How to apply?', ));
デフォルトのバリデータを変更する必要もあります:
$this->validatorSchema['logo'] = new sfValidatorFile(array( 'required' => false, 'path' => sfConfig::get('sf_upload_dir').'/jobs', 'mime_types' => 'web_images', ));
sfValidatorFile
にはとてもたくさんの機能があります:
- アップロードされたファイルが Web フォーマットの画像であることをバリデートする (
mime_types
) - ファイルを一意性のある名前にリネームする
- ファイルを
path
で指定された場所に保存する - 生成されたファイル名で
logo
カラムを更新する
note
ロゴ用のディレクトリ (web/uploads/jobs/
) を作り、Web サーバーによって書き込み可能であることを確認してください。
バリデータはデータベースに相対パスを保存するので、showSuccess
テンプレートで使われているパスを次のように変更します:
// apps/frontend/modules/job/templates/showSuccess.php <img src="/uploads/jobs/<?php echo $job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" />
tip
generateLogoFilename()
メソッドがモデルクラスに存在する場合、このメソッドがバリデータによって呼び出され、デフォルトで生成される logo
のファイル名をオーバーライドします。メソッドには sfValidatedFile
オブジェクトが引数として渡されます。
フィールドごとに生成されるラベルをオーバーライドできるのと同じようにして、ヘルプメッセージも定義できます。重要性をよりわかりやすく説明するために、ヘルプメッセージを is_public
カラムに追加してみましょう:
$this->widgetSchema->setHelp('is_public', 'Whether the job can also be published on affiliate websites or not.');
ここまでで JobeetJobForm
クラスは次のようになりました:
// lib/form/doctrine/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function configure() { unset( $this['created_at'], $this['updated_at'], $this['expires_at'], $this['is_activated'] ); $this->validatorSchema['email'] = new sfValidatorEmail(); $this->widgetSchema['type'] = new sfWidgetFormChoice(array( 'choices' => Doctrine_Core::getTable('JobeetJob')->getTypes(), 'expanded' => true, )); $this->validatorSchema['type'] = new sfValidatorChoice(array( 'choices' => array_keys(Doctrine_Core::getTable('JobeetJob')->getTypes()), )); $this->widgetSchema['logo'] = new sfWidgetFormInputFile(array( 'label' => 'Company logo', )); $this->widgetSchema->setLabels(array( 'category_id' => 'Category', 'is_public' => 'Public?', 'how_to_apply' => 'How to apply?', )); $this->validatorSchema['logo'] = new sfValidatorFile(array( 'required' => false, 'path' => sfConfig::get('sf_upload_dir').'/jobs', 'mime_types' => 'web_images', )); $this->widgetSchema->setHelp('is_public', 'Whether the job can also be published on affiliate websites or not.'); } }
フォームのテンプレート
フォームのカスタマイズが完了したので、次はフォームを表示してみましょう。新しい求人を作成もしくは既存の求人を編集する場合のフォームのテンプレートは同じです。実際、newSuccess.php
と editSuccess.php
テンプレートはとても似通っています:
<!-- apps/frontend/modules/job/templates/newSuccess.php --> <?php use_stylesheet('job.css') ?> <h1>Post a Job</h1> <?php include_partial('form', array('form' => $form)) ?>
note
まだ job
スタイルシートを追加していないので、両方のテンプレートに追加しましょう (<?php use_stylesheet('job.css') ?>
)。
フォーム自身は _form
パーシャルでレンダリングされます。生成された _form
パーシャルの内容を次のコードで置き換えます:
<!-- apps/frontend/modules/job/templates/_form.php --> <?php use_stylesheets_for_form($form) ?> <?php use_javascripts_for_form($form) ?> <?php echo form_tag_for($form, '@job') ?> <table id="job_form"> <tfoot> <tr> <td colspan="2"> <input type="submit" value="Preview your job" /> </td> </tr> </tfoot> <tbody> <?php echo $form ?> </tbody> </table> </form>
use_javascripts_for_form()
と use_stylesheets_for_form()
ヘルパーは、フォームウィジェットに必要な JavaScript とスタイルシートをインクルードします。
tip
求人フォームが JavaScript もしくはスタイルシートを必要としなくても、万が一に備えてこれらのヘルパーを呼び出しておくのはよい習慣です。ウィジェットを JavaScript もしくは固有のスタイルシートを必要とするウィジェットに変更する場合、後で時間の節約になります。
form_tag_for()
ヘルパーは、渡されたフォームとルートに対応する <form>
タグを生成し、オブジェクトの新規作成かどうかによって HTTP メソッドを POST
または PUT
へ変更します。フォームがファイル入力タグをもつ場合は、ヘルパーにより multipart
属性が自動的に加えられます。
最後に、<?php echo $form ?>
によってフォームウィジェットがレンダリングされます。
フォームのアクション
フォームクラスと、フォームをレンダリングするテンプレートが用意できました。では、実際にこれらをアクションと連携させてみましょう。
求人のフォームは job
モジュールの次の5つのメソッドで管理されます:
- new: 新しい求人を作成する空白のフォームを表示する
- edit: 既存の求人を編集するフォームを表示する
- create: ユーザーが投稿した値で新しい求人を作成する
- update: ユーザーが投稿した値で既存の求人を更新する
- processForm:
create
とupdate
によって呼び出され、フォームを処理する (バリデーション、フォームの再設定、およびデータベースへの保存)
すべてのフォームには次のようなライフサイクルがあります:
5日前に job
モジュール用の Doctrine ルートコレクションを作ったので、フォーム管理メソッド用のコードを次のように簡略化できます:
// apps/frontend/modules/job/actions/actions.class.php public function executeNew(sfWebRequest $request) { $this->form = new JobeetJobForm(); } public function executeCreate(sfWebRequest $request) { $this->form = new JobeetJobForm(); $this->processForm($request, $this->form); $this->setTemplate('new'); } public function executeEdit(sfWebRequest $request) { $this->form = new JobeetJobForm($this->getRoute()->getObject()); } public function executeUpdate(sfWebRequest $request) { $this->form = new JobeetJobForm($this->getRoute()->getObject()); $this->processForm($request, $this->form); $this->setTemplate('edit'); } public function executeDelete(sfWebRequest $request) { $request->checkCSRFProtection(); $job = $this->getRoute()->getObject(); $job->delete(); $this->redirect('job/index'); } protected function processForm(sfWebRequest $request, sfForm $form) { $form->bind( $request->getParameter($form->getName()), $request->getFiles($form->getName()) ); if ($form->isValid()) { $job = $form->save(); $this->redirect('job_show', $job); } }
/job/new
ページにアクセスすると、新しいフォームインスタンスが作成され、テンプレートに渡されます (new
アクション)。
ユーザーがフォームを投稿すると (create
アクション)、ユーザーが投稿した値がフォームにバインドされ (bind()
メソッド)、バリデーションが実行されます。
フォームがバインドされると、isValid()
メソッドを利用して有効性をチェックできます: フォームが有効な場合 (true
を返す)、求人がデータベースに保存され ($form->save()
)、ユーザーは求人のプレビューページにリダイレクトされます; フォームが有効でない場合、ユーザーが投稿した値と関連するエラーメッセージが設定された newSuccess.php
テンプレートが再表示されます。
tip
setTemplate()
メソッドを呼び出すと、アクションで使うテンプレートを変更できます。投稿されたフォームが有効でない場合、create
と update
メソッドはエラーメッセージを伴うフォームを再表示するためにそれぞれ new
と edit
アクションと同じテンプレートを使います。
既存の求人の修正は、作成ととても似ています。new
と edit
アクションの唯一の違いは、修正される求人オブジェクトがフォームコンストラクタの最初の引数として渡されることです。このオブジェクトは、テンプレートのウィジェットのデフォルト値として使われます (Doctrine フォームでは、デフォルト値はオブジェクトですが、シンプルなフォームではプレーンな配列です)。
求人の作成フォームにデフォルト値を定義することもできます。フォームのデフォルト値を定義する1つのやり方は、データベーススキーマのなかでデフォルト値を宣言することです。別のやり方は、フォームコンストラクタにあらかじめ修正した Job
オブジェクトを渡すことです。
type
カラムのデフォルト値として full-time
を定義するために executeNew()
メソッドを次のように変更します:
// apps/frontend/modules/job/actions/actions.class.php public function executeNew(sfWebRequest $request) { $job = new JobeetJob(); $job->setType('full-time'); $this->form = new JobeetJobForm($job); }
note
フォームがバインドされると、デフォルトの値はユーザー投稿の値に置き換えられます。バリデーションエラーでフォームが再表示される場合、ユーザーが投稿した値がフォームの再設定に使われます。
トークンで求人フォームを保護する
そろそろ完成が近づいてきました。ただし今のところ、求人に対するトークンをユーザーが入力しなければなりません。一意性をもつトークンの取得をユーザーに依存したくないので、新しい求人が作られるときに求人トークンが自動的に生成されるようにします。
新しい求人が保存される前にトークンを生成するロジックを追加するように、JobeetJob
の save()
メソッドを次のように更新します:
// lib/model/doctrine/JobeetJob.class.php public function save(Doctrine_Connection $conn = null) { // ... if (!$this->getToken()) { $this->setToken(sha1($this->getEmail().rand(11111, 99999))); } return parent::save($conn); }
これでフォームから token
フィールドを取り除くことができます:
// lib/form/doctrine/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function configure() { unset( $this['created_at'], $this['updated_at'], $this['expires_at'], $this['is_activated'], $this['token'] ); // ... } // ... }
2日目のユーザーストーリーで示したように、求人情報は、ユーザーが関連トークンを知っている場合にのみ編集できます。しかし現時点では、きわめて簡単に URL を推測して求人を編集もしくは削除できてしまいます。求人の主キーを ID
とすると、編集フォームの URL は /job/ID/edit
のようになるからです。
デフォルトでは、sfDoctrineRouteCollection
ルートは主キーを使う URL を生成しますが、次のように column
オプションを指定することで一意性をもつ値をもつ別のカラムに変更できます:
# apps/frontend/config/routing.yml job: class: sfDoctrineRouteCollection options: { model: JobeetJob, column: token } requirements: { token: \w+ }
ユニークキーに対する symfony のデフォルトの要件が \d+
なので、任意の文字列にマッチさせるために token
パラメータの要件も変更したことにご注意ください。
job に関連するすべてのルートは、job_show_user
のものを除いて、トークンを埋め込みます。たとえば、job を編集するルートは次のパターンで構成されます:
http://jobeet.localhost/job/TOKEN/edit
showSuccess
テンプレートの「Edit」リンクも変更してください:
<!-- apps/frontend/modules/job/templates/showSuccess.php -->
<a href="<?php echo url_for('job_edit', $job) ?>">Edit</a>
プレビューページ
プレビューページは job ページの表示と同じです。ルーティングのおかげで、ユーザーが正しいトークンでアクセスした場合は、token
リクエストパラメータにアクセスできます。
ユーザーがトークンを使用する URL でアクセスした場合、管理バーがページのトップに追加されます。showSuccess
テンプレートの始めに管理バーをホストするパーシャルを追加し、末尾の edit
リンクを削除します:
<!-- apps/frontend/modules/job/templates/showSuccess.php --> <?php if ($sf_request->getParameter('token') == $job->getToken()): ?> <?php include_partial('job/admin', array('job' => $job)) ?> <?php endif ?>
_admin
パーシャルを作ります:
<!-- apps/frontend/modules/job/templates/_admin.php --> <div id="job_actions"> <h3>Admin</h3> <ul> <?php if (!$job->getIsActivated()): ?> <li><?php echo link_to('Edit', 'job_edit', $job) ?></li> <li><?php echo link_to('Publish', 'job_edit', $job) ?></li> <?php endif ?> <li><?php echo link_to('Delete', 'job_delete', $job, array('method' => 'delete', 'confirm' => 'Are you sure?')) ?></li> <?php if ($job->getIsActivated()): ?> <li<?php $job->expiresSoon() and print ' class="expires_soon"' ?>> <?php if ($job->isExpired()): ?> Expired <?php else: ?> Expires in <strong><?php echo $job->getDaysBeforeExpires() ?></strong> days <?php endif ?> <?php if ($job->expiresSoon()): ?> - <a href="">Extend</a> for another <?php echo sfConfig::get('app_active_days') ?> days <?php endif ?> </li> <?php else: ?> <li> [Bookmark this <?php echo link_to('URL', 'job_show', $job, true) ?> to manage this job in the future.] </li> <?php endif ?> </ul> </div>
たくさんのコードがありますが、たいていのコードは簡単に理解できます。
テンプレートを読みやすくするために、JobeetJob
クラスに次のような一連のショートカットメソッドを追加しました:
// lib/model/doctrine/JobeetJob.class.php public function getTypeName() { $types = Doctrine_Core::getTable('JobeetJob')->getTypes(); return $this->getType() ? $types[$this->getType()] : ''; } public function isExpired() { return $this->getDaysBeforeExpires() < 0; } public function expiresSoon() { return $this->getDaysBeforeExpires() < 5; } public function getDaysBeforeExpires() { return ceil(($this->getDateTimeObject('expires_at')->format('U') - time()) / 86400); }
管理バーには、求人のステータスに合わせて異なるアクションが表示されます:
note
次のセクションの後で「activated」バーを見ることができます。
求人の有効化と公開
前のセクションで、求人を公開するリンクがありました。このリンクが新しい publish
アクションを指し示すように変更する必要があります。新しいルートを作る代わりに、既存の job
ルートを次のように設定します:
# apps/frontend/config/routing.yml job: class: sfDoctrineRouteCollection options: model: JobeetJob column: token object_actions: { publish: put } requirements: token: \w+
object_actions
は与えられたオブジェクト用の追加アクションの配列をとります。「Publish」リンクを変更しましょう:
<!-- apps/frontend/modules/job/templates/_admin.php --> <li> <?php echo link_to('Publish', 'job_publish', $job, array('method' => 'put')) ?> </li>
最後のステップは publish
アクションを作ることです:
// apps/frontend/modules/job/actions/actions.class.php public function executePublish(sfWebRequest $request) { $request->checkCSRFProtection(); $job = $this->getRoute()->getObject(); $job->publish(); $this->getUser()->setFlash('notice', sprintf('Your job is now online for %s days.', sfConfig::get('app_active_days'))); $this->redirect('job_show_user', $job); }
賢明な読者は「Publish」リンクが HTTP PUT メソッドで投稿されることにお気づきでしょう。PUT メソッドをシミュレートするために、リンクがクリックされると自動的にフォームに変換されます。
CSRF の保護を有効にしたので、link_to()
ヘルパーによって CSRF トークンがリンクに埋め込まれます。リクエストオブジェクトの checkCSRFProtection()
メソッドを使って、投稿された CSRF トークンの有効性をチェックできます。
executePublish()
メソッドでは、次のように定義される新しい publish()
メソッドを呼び出します:
// lib/model/doctrine/JobeetJob.class.php public function publish() { $this->setIsActivated(true); $this->save(); }
これで、新しい公開機能をブラウザでテストできます。
しかし、修正するものがまだあります。有効化されていない求人情報はアクセスされてはなりません。つまり、Jobeet ホームページにこれらの求人情報は表示されてはならず、URL からアクセス可能であってはならないことです。Doctrine_Query
を有効な求人情報のみに制限する addActiveJobsQuery()
メソッドをすでに作ったので、最後にこのメソッドを編集して新しい要件を追加します:
// lib/model/doctrine/JobeetJobTable.class.php public function addActiveJobsQuery(Doctrine_Query $q = null) { // ... $q->andWhere($alias . '.is_activated = ?', 1); return $q; }
これでお終いです。ブラウザでテストしてみましょう。すべての有効ではない求人はホームページから消えました; それらの URL を知っていても、もはやアクセスできません。しかしながら、これらは求人のトークンURLを知っていればアクセスできます。この場合、求人のプレビューは管理バーつきで表示されます。
これまでのところ、これが MVC パターンとリファクタリングの大きな利点の1つです。新しい要件を追加するために1つのメソッドで1つの変更だけが必要でした。
note
getWithJobs()
メソッドを作成したとき、addActiveJobsQuery()
メソッドを使うのを忘れていました。ですので、このメソッドを編集して新しい要件を追加する必要があります:
class JobeetCategoryTable extends Doctrine_Table { public function getWithJobs() { // ... $q->andWhere('j.is_activated = ?', 1); return $q->execute(); }
また明日
今日のチュートリアルにはたくさんの新しい情報が詰め込まれていましたが、symfony のフォームフレームワークをより理解していただけることを願っています。
今日私たちが忘れたことにお気づきの方がいらっしゃることは承知しております。新しい機能に対してテストを実装しませんでした。テストを書くことはアプリケーションの開発の重要な部分なので、これは明日の最初に行います。
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.