今回は、フォームとパーツについて説明します。

フォームに関しては、symfonyのものと大差ありません。
フォームクラスを利用し、出力します。復習は以下のページを御覧下さい。

Practical symfony 10日目: フォーム

フォームフレームワークを利用することは、
べた書きでformを用意するよりも以下の点で優れています。

  • Tokenをフォーム中に含めるためCSRF対策になる。(デフォルトで働くようになっています)
  • 簡単にバリデートを行うことができる。
  • 継承などができる

特に、一番上はセキュリティに関わる項目です。

また、プラグイン開発者あれば、デザイン面に関して、
独自のものではなく、OpenPNEに馴染んだものにしたいことでしょう。
今回利用するパーツは、その為に用意されています。

説明だけでは伝わりにくいので、実際に作りながら説明していきます。

フォーム

まず、質問作成/編集用のフォームを作ります。

symfonyが生成したFormクラスを使います。
モデル作成時に、formクラスが作成されており、 OpenPNE本体の lib/form/doctrine/opVotePlugin/VoteQuestionForm.class.php を実際に使うことになります。
しかし、原則としてプラグイン開発ではOpenPNE本体を編集しないことになっています。そこで、上記の VoteQuestionForm は親クラスとして、プラグイン下に PluginVoteQuestionForm が抽象クラスとして用意されています。
そのファイルは、plugins/opVotePlugin/lib/form/doctrine/PluginVoteQuestionForm.class.php にあります。

このファイルを編集していきます。
まず、setup() メソッドをオーバーライドし、必要なフィールドの指定や追加を行います。

  public function setup()
  {
    // 親クラスのsetup()呼び出し
    parent::setup();

    // バリデータ書き換え
    $this->setValidator('title', new opValidatorString(array('max_length' => 140, 'trim' => true)));
    $this->setValidator('body', new opValidatorString(array('max_length' => 2147483647, 'trim' => true)));

    // 選択肢を指定するウィジェット追加
    $this->setWidget('option', new sfWidgetFormInput());
    $this->setValidator('option', new sfValidatorString(array('trim' => true)));
    $this->widgetSchema->setLabel('option', '選択肢');
    $this->widgetSchema->setHelp('option', '選択肢をスペース区切りで入力してください');

    // 編集時なら選択肢に現在のデータをデフォルトして挿入
    if (!$this->isNew())
    {   
      $options = $this->getObject()->getVoteQuestionOptions()->toKeyValueArray('id', 'body');
      $this->setDefault('option', implode(' ', $options));
    }   

    // 使うフィールド指定
    $this->useFields(array('title', 'body', 'option'));
  }

useFields() メソッドは symfony1.3〜の新機能です。すなわち、OpenPNE3.4.x以降での仕様が可能な新機能になります。
また、バリデータの指定では、sfValidatorString ではなく、opValidatorString を指定しています。
このクラスは、 OpenPNE独自に作られたもので、sfValidatorStringの機能に加え、全角スペースのトリムや、文字列の右端・左端だけのスペースのトリムをサポートしたものです。
さらに、選択肢を入力させるためのウィジェット・バリデータを追加します。ひとまず、現段階ではスペース区切りで選択肢を入力させます。
補足として、configure()rを使わず、setup()を使った理由は、生成された VoteQuestionPluginにはすでに空の configure() メソッドが用意されているためです。その為、configure()には何を書いても動作しないということに注意してください。

新たなウィジェットを追加したので保存に関しても変える必要があります。doSave()メソッドをオーバーライドしましょう。

  protected function doSave($con = null)
  {
    parent::doSave();

    $newOptions = $this->getValue('option');
    $newOptions = preg_split('/[\s ]+/u', $newOptions, -1, PREG_SPLIT_NO_EMPTY);
    $voteQuestion = $this->getObject();

    // 過去の選択肢の抽出
    $oldOptions = $voteQuestion->getVoteQuestionOptions();
    $oldOptions = $oldOptions->toKeyValueArray('id', 'body');

    // 削除された選択肢の抽出
    $deletedOptions = array_diff($oldOptions, $newOptions);
    foreach ($deletedOptions as $id => $body)
    { 
      // 削除
      $object = Doctrine::getTable('VoteQuestionOption')->find($id);
      $object->delete();
    }

    // 新規の選択肢
    $insertOptions = array_diff($newOptions, $oldOptions);
    foreach ($insertOptions as $body)
    { 
      // 追加
      $object = new VoteQuestionOption();
      $object->setVoteQuestion($voteQuestion);
      $object->setBody($body);
      $object->save();
    }
  }

アクション

実際に、上記で作成したフォームを動くようにしましょう。

まず、アクションを書いていきます。

plugins/opVotePlugin/lib/action/opVotePluginVoteActions.class.php を編集しましょう。

  public function executeIndex(sfWebRequest $request)
  {
    //次回やります
  }

  public function executeNew(sfWebRequest $request)
  {
    $this->form = new VoteQuestionForm();
  }

  public function executeCreate(sfWebRequest $request)
  {
    $object = new VoteQuestion();
    $object->setMember($this->getUser()->getMember());
    $this->form = new VoteQuestionForm($object);
    if ($this->form->bindAndSave($request->getParameter('vote_question')))
    {   
      $this->redirect('@vote_list');
    }
    // newのテンプレートを使う
    $this->setTemplate('new');
  }

executeCreate()中で、メンバーを取得してモデルオブジェクトに登録しています。後にもっと詳しく説明しますが、ログイン中のメンバーのモデルは、アクション内で

$this->getUser()->getMember()

で取得が可能です。

メンバーIDを取得するときは、

$this->getUser()->getMember()->getId()

とすることもできますが、長いので

$this->getUser()->getMemberId()

と指定することができます。

テンプレート

最後に、テンプレートを作成します。
現在の状態では newSuccess.php を作成すればいいということになります。
plugins/opVotePlugin/apps/pc_frontend/modules/vote/templates/newSuccess.php
を以下のようにします。

<?php op_include_form('vote_question_form', $form, array(
  'url' => url_for('@vote_create'),
  'title' => '質問作成',
)); ?>

http://#セットアップしたSNSのURL#/vote/new にアクセスしてみましょう。
以下のようになるはずです。
ss

op_include_form()は、OpenPNEのデフォルトヘルパーであるopPartsHelperが用意している関数のひとつで、OpenPNEに馴染むようなパーツを使いフォームを挿入します。

op_include_form()の各引数は以下のようになっています。

第1引数 $id
パーツのID。パーツの外枠のID属性になるほか、テンプレート拡張に用いられる。テンプレート拡張に関しては後の回で説明
第2引数 $form
出力するフォームオブジェクト
第3引数 $options
オプションの配列。

オプションにはおもに以下のパラメータを指定することができます。

title
パーツのタイトル部に表示する文字列
body
フォームの前に挿入するメッセージ
url
フォームのactionURLを指定する。指定しない場合は、現在のURLになる。
button
フォームの送信ボタンのキャプション。デフォルトは送信(Send)
method
フォームのmethod属性に指定する文字列。デフォルトはpost
isMultipart
フォームのenctype属性をmultipart/form-dataにする。

同様にフォーム以外でも、このようなパーツが用意されています。
パーツ自体のテンプレートは、OpenPNE本体の apps/*/template/_parts* がそれにあたり、 呼び出しは op_include_parts() を利用します。利用頻度の高いパーツは、その為の関数が用意されています。

パーツはpc_frontendのもので、20個ほどあります。
ここで、説明すると話がややこしくなるので、後の付録ですべてのパーツのサンプルを用意するつもりです。