Jobeetの2週目はsymfonyのテストフレームワークの導入で好調な滑り出しを始めました。 今日はフォームフレームワークに焦点を合わせます。
フォームフレームワーク
ほとんどのWebサイトにはフォームがあります。 シンプルな問い合わせから、たくさんのフィールドがある複雑なものまで、さまざまなフォームがあります。 フォームを作る作業は、Web開発者にとってもっとも複雑で退屈な作業の1つです。 HTMLフォームを書き、それぞれのフィールド用のバリデーションルールを実装し、値を処理してデータベースに保存し、エラーメッセージを表示し、エラーの場合はフィールドを再設定することなどが必要です。
もちろん、何度も車輪の再発明をする代わりに、symfonyはフォームの管理を簡単にするフレームワークを提供します。 フォームフレームワークは3つの部分で構成されます:
- バリデーション: バリデーションサブフレームワークは入力(整数、文字列、Eメールアドレス・・・) をバリデートするクラス群を提供します。
- ウィジェット: ウィジェットサブフレームワークはHTMLフィールド(入力、テキストエリア、選択・・・) を出力するクラス群を提供します。
- フォーム: フォームクラス群はウィジェットとバリデーターで構成されるフォームを表し、フォームを管理しやすくするメソッドを提供します。それぞれのフォームフィールドに、個別のバリデーターとウィジェットが設定されます。
フォーム
symfonyのフォームは1つ以上のフィールドから構成されるクラスです。
それぞれのフィールドは名前、バリデーターとウィジェットを持ちます。
次のクラスでは、シンプルなContactForm
を定義しています:
class ContactForm extends sfForm { public function configure() { $this->setWidgets(array( 'email' => new sfWidgetFormInput(), 'message' => new sfWidgetFormTextarea(), )); $this->setValidators(array( 'email' => new sfValidatorEmail(), 'message' => new sfValidatorString(array('max_length' => 255)), )); } }
configure()
メソッド内で、setValidators()
とsetWidgets()
メソッドを使ってフォームフィールドを定義します。
ウィジェットとバリデーターのクラス名はきわめて明確です:
email
フィールドはHTMLの<input>
タグとしてレンダリングされ(sfWidgetFormInput
)、Eメールアドレスとしてバリデートされます(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
タスクを起動したとき、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/
サブディレクトリに生成されたファイルを眺めると、組み込みのウィジェットとバリデーターのすばらしい使い方の例がたくさん見つかります。
求人フォームをカスタマイズする
求人フォームはフォームのカスタマイズを学ぶための完璧な例です。 カスタマイズする方法を順に見てみましょう。
最初に、これからの変更をブラウザーですぐにチェックできるように、レイアウトの"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
すると、フィールドのウィジェットとバリデーターの両方が削除されます。
フォームの設定は、データベーススキーマから自動生成された内容よりも正確でなければならないことがあります。
たとえば、email
カラムはスキーマでは単なるvarchar
ですが、実際はEメールとしてバリデートする必要があります。
デフォルトの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::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::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::getTable('JobeetJob')->getTypes(), 'expanded' => true, )); $this->validatorSchema['type'] = new sfValidatorChoice(array( 'choices' => array_keys(Doctrine::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 include_stylesheets_for_form($form) ?> <?php include_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>
include_javascripts_for_form()
とinclude_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($this->generateUrl('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::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 floor((strtotime($this->getExpiresAt()) - time()) / 86400); }
管理バーには、求人のステータスに合わせて異なるアクションが表示されます:
note
次のセクションの後で"activated"バーを見ることができます。
求人の有効化と公開
前のセクションで、求人を公開するリンクがありました。
このリンクが新しいpublish
アクションを指し示すように変更する必要があります。
新しいルートを作る代わりに、既存のjob
ルートを次のように設定します:
# apps/frontend/config/routing|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($this->generateUrl('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.