symfony1.4+Doctrineでbuild-formsで生成されるコードに独自WidgetやValidatorをセットする

開発が進むに連れてフォームで

  • 表示するエラーメッセージを全体的に変更したい
  • 全角英数を半角英数に変換したい
  • その他色々と機能を拡張したい

などと、全体的な挙動を変えたいことが多くて、

$ symfony doctrine:build-forms

で生成される、lib/form/doctrine/base/Base***Form.class.php 内のWidget, Validatorを独自拡張したものに変更できないかなと思う事が多く、調べてみました。*1

$ symfony doctrine:build-forms --generator-class=myDoctrineFormGenerator

と、build-formsの--generator-classにクラス名を渡してGeneratorクラスでいろいろとやれば良いのですが、
オプション付けるのを忘れたり、他の人も触ることを考えるとデフォルト値を置き換えられないかなーと思って調べました。

既存taskを継承すれば良いかなと思い継承しましたが下記のようなエラーが。

$ cat lib/task/myDoctrineBuildFormsTask.class.php 
<?php

class myDoctrineBuildFormsTask extends sfDoctrineBuildFormsTask
{
}
$ symfony doctrine:build-forms
PHP Fatal error:  Uncaught exception 'sfCommandException' with message 'The task named "doctrine:build-forms" in "myDoctrineBuildFormsTask" task is already registered by the "sfDoctrineBuildFormsTask" task.' in /usr/local/apps/example/lib/vendor/symfony/lib/command/sfCommandApplication.class.php:142

...と、taskの正しい拡張方法がわかりませんでした。



前置きが長くなりましたが無理やりオプション書き換えてみました。BKっぽいですがメモ。

config/ProjectConfiguration.class.php

command.filter_optionsイベントを捕捉して、generator-classを置き換えます。

<?php

require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->dispatcher->connect(
      'command.filter_options',
      array($this, 'commandFilterOptionsEventHook')
    );
  }

  public function commandFilterOptionsEventHook(sfEvent $event, $options)
  {
    $task = $event->getSubject();
    $parameters = $event->getParameters();
    $commandManager = $parameters['command_manager'];
    if ($task->getFullName() === 'doctrine:build-forms') {
      $commandManager->getOptionSet()
        ->getOption('generator-class')
        ->setDefault('myDoctrineFormGenerator');
    }
    return $options;
  }
}

lib/generator/myDoctrineFormGenerator.class.php

  • $extendedWidgetFormClasses
  • $extendedValidatorClasses

のkey=>valueに置き換えたいクラスの定義を書きます。

<?php

class myDoctrineFormGenerator extends sfDoctrineFormGenerator
{
  protected $extendedWidgetFormClasses = array(
    'sfWidgetFormDate' => 'myWidgetFormTextDate', // 日付のinput text入力
  );

  protected $extendedValidatorClasses = array(
    'sfValidatorInteger' => 'myValidatorInteger', // 全角英数->半角英数
    'sfValidatorDate'    => 'myValidatorDate',    // 全角英数->半角英数
  );

  public function getWidgetClassForColumn($column)
  {
    return $this->getExtendedClass(
      parent::getWidgetClassForColumn($column),
      $this->extendedWidgetFormClasses
    );
  }

  public function getValidatorClassForColumn($column)
  {
    return $this->getExtendedClass(
      parent::getValidatorClassForColumn($column),
      $this->extendedValidatorClasses
    );
  }

  protected function getExtendedClass($class, $extendedClasses)
  {
    return isset($extendedClasses[$class]) ? $extendedClasses[$class] : $class;
  }
}

として、やるとsymfony doctrine:build-formsしてやると、生成されるソース内のWidget,Validatorが置き換わります。

Person:
  columns:
    name: string(255)
    date_of_birth: date
    allowance: integer(4)
  public function setup()
  {
    $this->setWidgets(array(
      'id'            => new sfWidgetFormInputHidden(),
      'name'          => new sfWidgetFormInputText(),
      'date_of_birth' => new myWidgetFormTextDate(),
      'allowance'     => new sfWidgetFormInputText(),
    ));

    $this->setValidators(array(
      'id'            => new sfValidatorChoice(array('choices' => array($this->getObject()->get('id')), 'empty_value' => $this->getObject()->get('id'), 'required' => false)),
      'name'          => new sfValidatorString(array('max_length' => 255, 'required' => false)),
      'date_of_birth' => new myValidatorDate(array('required' => false)),
      'allowance'     => new myValidatorInteger(array('required' => false)),
    ));
  ...

ちなみに今回、例として置き換えてみたWidgetとValidatorは以下です。参考までに。

lib/widget/myWidgetFormTextDate.class.php

<?php

class myWidgetFormTextDate extends sfWidgetFormDate
{
  protected function renderDayWidget($name, $value, $options, $attributes)
  {
    $attributes['size'] = '4';
    $attributes['maxlength'] = 2;
    return $this->renderWidgetFormInput($name, $value, $attributes);
  }
  protected function renderMonthWidget($name, $value, $options, $attributes)
  {
    $attributes['size'] = '4';
    $attributes['maxlength'] = 2;
    return $this->renderWidgetFormInput($name, $value, $attributes);
  }

  protected function renderYearWidget($name, $value, $options, $attributes)
  {
    $attributes['size'] = '6';
    $attributes['maxlength'] = 4;
    return $this->renderWidgetFormInput($name, $value, $attributes);
  }

  protected function renderWidgetFormInput($name, $value, $attributes) {
    $widget = new sfWidgetFormInput(array(), $attributes);
    return $widget->render($name, $value);
  }
}

lib/validator/myValidatorDate.class.php

<?php

class myValidatorDate extends sfValidatorDate
{
  protected function doClean($value)
  {
    if (is_string($value)) {
      $value = $this->filter($value);
    } else if (is_array($value)) {
      $value = array_map(array($this, 'filter'), $value);
    }
    return parent::doClean($value);
  }

  protected function filter($value)
  {
    return mb_convert_kana($value, 'a');
  }
}

lib/validator/myValidatorInteger.class.php

<?php

class myValidatorInteger extends sfValidatorInteger
{
  protected function doClean($value)
  {
    if ($value !== null) {
      $value = mb_convert_kana($value, 'a');
    }
    return parent::doClean($value);
  }
}

*1:デフォルトでセットされるsfValidatorString,sfValidatorInteger等を差し替えたい