Transitionコンポーネント - さらに込み入った使い方

というわけでTransitionコンポーネントを便利に使わせてもらっているのですが、このコンポーネントはその性格上以下のような制約があります。

- アクションごとにバリデーションを行う場合はそれごとにモデルが必要になる

画面ごとにきっちりとモデルが割り当てれればいいのですが、1つのモデルのフィールドが複数の画面にちらばるとか、全画面の項目を1つのモデルに割り当てたいとかいろいろな事情があると思います。
実際自分もそうだったので、このTransitionコンポーネントとMultiValidatableというビヘイビアを組み合わせて使ってます。

(Bakery) MultivalidatableBehavior: Using many validation rulesets per model

全ての画面の項目を1つのモデルに押し込んで、画面毎のバリデーションルールを作って、画面毎にしていするっていう形です。

class Wizard extends AppModel {
  public $name = 'Wizard';
  public $useTable = false;
  public $actsAs = array('Multivalidatable');

  public $validate = array();
  public $validationSets = array(
    'step1' => array(
      'field1_1' => array(...),
           :
    ),
    'step2' => array(
      'field2_1' => array(...),
            :
    ),
    'step3' => array(
      'field3_1' => array(...),
             :
    ),
  );

  public function beforeValidate($option) {
    foreach ($this->validationSets as $key => $rules) {
      $this->validate[] = $rules;
    }
  }
} 

このようにしておけばコントローラでは以下のように指定できます。

class FooController extends AppController {
  public $uses = array('Foo', 'Wizard');
  public $components = array(
    'Transition' => array(
      'models' => 'Wizard',
    ),
  );

  public function step1()
  {
    // Wizardモデルのstep1のルールでバリデーションして問題がなければ step2 へリダイレクト
    $this->Wizard->setValidation('step1');
    $this->Transition->checkData('step2');
  }

  public function step2()
  {
    // step1 から遷移したことをチェックし、
    // Wizardモデルのstep2のルールでバリデーションして問題がなければ step3 へリダイレクト
    $this->Wizard->setValidation('step2');
    $this->Transition->automate('step3', 'Wizard', 'step1');
  }

  public function step3()
  {
    // step2 から遷移したことをチェックし、
    // Wizardモデルのstep3のルールでバリデーションして問題がなければ confirm へリダイレクト
    $this->Wizard->setValidation('step3');
    this->Transition->automate('confirm', 'Wizard', 'step2');
  }

  public function confirm()
  {
    // ポストで何らかのデータを受け取ったらセッション情報を展開
    if (!empty($this->data)) {
      $this->data = $this->Transaction->mergedData();
    }
    // step3 から遷移したことをチェックし、
    // Wizardの「全部のルールで」バリデーションして問題がなければ、add へリダイレクト
    this->Transition->automate('add', 'Wizard', 'step3');
    // 各画面で入力されたデータをマージする
    $this->set('post', $this->data);
  }

  public function add()
  {
    // step1/step2/step3 から遷移したことをチェック
    $this->Transition->checkPrev(array('step1', 'step2', 'step3', 'confirm'));
    $data = $this->Transition->mergedData();
    if ($this->Foo->saveAll($data)) {
      $this->Transition->clearData();
    } else {
      $this->redirect(array('action' => 'confirm'));
    }
  }
}

これで開発中に項目がいろんな画面に移動してもWizardモデルのバリデーションをいじればいいだけになるので、手間はあまりかかりません。また念のためにconfirmアクションでも全項目のバリデーションを書けるようにしたのでちょっと安心感が増します。(まぁ本当に念のためなんですけどね)

そして実テーブル側でもさらに念を入れてバリデーションをしたいとかある場合はWizardモデルを継承して必要なバリデーションルールを使うとかすれば、あっちこっちを修正することにもならないかと思います。