ORMフォームで複合ウィジェットを使う

ここで説明するやり方はちょっと強引なやり方かと思います。
symfony的にはカスタムウィジェットを作成して使うのが綺麗なやり方だと思います。


以前のエントリで、複合ウィジェットをフォームで扱う方法を紹介しました。

先日某掲示板のsymfonyのスレッドに、ちょっとした質問(http://pc11.2ch.net/test/read.cgi/php/1201177567/896)があったので、これに対応する方法を書いてみます。


通常のフォームの場合は自力で値の取得やデータベースへの保存を行うのでよいのですが、ORMフォーム(DoctrineフォームやPropelフォーム)の場合は、データベースへの値の保存回りの処理もフレームワークがやってくれるので、そこもカスタマイズする必要があります。

今回使用するモデル

住所と電話番号を格納する「Address」というテーブルに、「tel」という電話番号用のフィールドがあるとします。例えば「03-1234-5678」といった値がこのフィールドに保存されます。スキーマは次の通りです。(Doctrine)

Address:
  actAs:
    Timestampable: ~
  columns:
    tel:              string(20)

このスキーマに対して、モデルやフォームを生成します。

php symfony doctrine:build --all

# symfony 1.3(1.4)では、生成コマンドが少し変わったようです。(doctrine:build-allではなくなった)

フォームのコード

複合ウィジェットを使う要領は以前のエントリで紹介したとおりですが、symfonyの内部処理をカスタマイズするために、少し追加が必要です。
/lib/form/doctrine/AddressForm.class.phpを、次のようにします。

<?php
class AddressForm extends BaseAddressForm
{
    public function configure()
    {
        $this->useFields(array());

        //  電話番号用の個別フィールドを準備する。
        $tel_widgets = new sfWidgetFormSchema(array(
        'tel1'=>new sfWidgetFormInputText(array(),array('size'=>'6','maxlength'=>'5')),
        'tel2'=>new sfWidgetFormInputText(array(),array('size'=>'5','maxlength'=>'4')),
        'tel3'=>new sfWidgetFormInputText(array(),array('size'=>'5','maxlength'=>'4')),
        ));
        $this->setWidget('vtel', $tel_widgets);

        //  電話番号用のバリデーターを準備する。
        $tel_validators = new sfValidatorSchema(array(
        'tel1'=>new sfValidatorAnd(array(
            new sfValidatorString(array('max_length'=>5, 'required'=>false)),
            new sfValidatorRegex(array('pattern'=>'/^[0-9]*$/'))
            ), array('required'=>false)
            ),
        'tel2'=>new sfValidatorAnd(array(
            new sfValidatorString(array('max_length'=>4, 'required'=>false)),
            new sfValidatorRegex(array('pattern'=>'/^[0-9]*$/'))
            )
            ),
        'tel3'=>new sfValidatorAnd(array(
            new sfValidatorString(array('max_length'=>4, 'required'=>false)),
            new sfValidatorRegex(array('pattern'=>'/^[0-9]*$/'))
            )
            ),
        ));
        $this->setValidator('vtel', $tel_validators);

        //  既存レコードの編集か?
        if(!$this->isNew()){
            //  電話番号フィールドのデフォルト値を設定する。
            $tel_numbers = explode('-', $this->getObject()->getTel());
            $defaults = $this->getDefaults();
            if(count($tel_numbers)<3){
                $defaults['vtel']['tel1'] = '';
                $defaults['vtel']['tel2'] = $tel_numbers[0];
                $defaults['vtel']['tel3'] = $tel_numbers[1];
            }else{
                $defaults['vtel']['tel1'] = $tel_numbers[0];
                $defaults['vtel']['tel2'] = $tel_numbers[1];
                $defaults['vtel']['tel3'] = $tel_numbers[2];
            }
            $this->setDefaults($defaults);
        }
    }

    /*
     * sfFormDoctrine::processValues()のオーバーライド
     */
    public function processValues($values){

        //  電話番号をまとめる。
        $values['tel'] = ((is_null($values['vtel']['tel1']))?'':$values['vtel']['tel1'].'-').$values['vtel']['tel2'].'-'.$values['vtel']['tel3'];

        //  親クラスのメソッドを呼び出してreturn。
        return parent::processValues($values);
    }
}

ビューのコードも、対応するように修正していますが、次のコードで動作確認できます。

<?php echo $form['vtel']->render() ?>


必要な処理は、次の2つです。

  • フォームに関連付けられたモデルオブジェクトの保存処理をフレームワークに任せるために、複合ウィジェットの値をモデルのフィールドに対応するようにセットし直しています。(processValues内)
  • フォームを編集モードで呼び出した場合に、フォームに関連付けられたモデルオブジェクトから複合ウィジェット用のデフォルト値を読み込んでいます。(configure内)

また、複合ウィジェットをまとめるフィールドの名前を「vtel」(virtual telの意)としています。これを単に「tel」とすると、モデルのフィールドと対応づけられて処理されてしまい、上手くいきません。