画像投稿に対応した質問を作れるようにする

質問に対して、画像を投稿できるようになると、質問がより伝わりやすく
なるかと思います。
この回では、質問に対して画像を添付できるような機能を実装します。

マイグレーション

画像投稿に対応させるとすれば、ファイルを管理する必要があります。
OpenPNEにはfileテーブルが存在し、アップロードしたファイルはそこに
保存されます。

質問にファイルを関連付けるには
vote_questionテーブルにfile_idフィールドを追加します。

VoteQuestion:
  actAs: [Timestampable]
  columns:
    id: { type: integer(4), primary: true, autoincrement: true }
    member_id: { type: integer(4), notnull: true }
    title: { type: string(140), notnull: true, notnull: true}
    body: { type: string }
    public_flag: { type: integer(1), notnull: true, default: 1 } 
    file_id: { type: integer(4) }
  relations:
    Member: { onDelete: cascade }
    File: { onDelete: set null }

file_idフィールドを追加し、リレーションによってFileと関連付けます。

この変更に対するマイグレーションスクリプは以下のようになります。

plugins/opVotePlugin/data/migrations/0.9.1/002_add_file_id_column.php

<?php

class opVotePlugin2_AddFileIdColumn extends Doctrine_Migration_Base
{
  public function migrate($direction)
  {
    $this->column($direction, 'vote_question', 'file_id', 'integer', 4); 
  }
}

カラム追加

plugins/opVotePlugin/data/migrations/0.9.1/003_create_index.php

<?php

class opVotePlugin3_CreateIndex extends Doctrine_Migration_Base
{
  public function migrate($direction)
  {
    $this->foreignKey($direction, 'vote_question', 'vote_question_file_id_file_id', array(
      'name' => 'vote_question_file_id_file_id',
      'local' => 'file_id',
      'foreign' => 'id',
      'foreignTable' => 'file',
      'onUpdate' => '', 
      'onDelete' => 'SET NULL',
    )); 
    $this->index($direction, 'vote_question', 'file_id_idx', array(
      'fields' => array('file_id')
    )); 
  }
}

外部キー追加

revision更新

plugins/opVotePlugin/data/fixtures/000_revision.yml

SnsConfig:
  opvoteplugin_current_revision:
    name:  "opVotePlugin_revision"
    value: 3

マイグレートを実施します。

$ ./symfony openpne:migrate --no-update-plugin --target=opVotePlugin
$ ./symfony cc

画像を投稿できるようにする

フォームから画像を投稿できるようにするために、VoteQuestionFormをさらに拡張していきます。

plugins/opVotePlugin/lib/form/doctrine/PluginVoteQuestionForm.class.php
のsetup()メソッドをに変更を加えます。

$this->useFields() で指定する前に以下を追加します。

    // 画像投稿
    $options = array(
      'file_src'     => '', 
      'is_image'     => true,
      'with_delete'  => true,
      'delete_label' => sfContext::getInstance()->getI18N()->__('Remove the current photo'),
      'edit_mode'    => false,
    );  
    // 画像編集モード
    if (!$this->isNew() && $this->getObject()->getFileId())
    {   
      sfContext::getInstance()->getConfiguration()->loadHelpers('Partial');
      $options['edit_mode'] = true;
      $options['template'] = get_partial('default/formEditImage', array('image' => $this->getObject()));
      $this->setValidator('image_delete', new sfValidatorBoolean(array('required' => false)));
    }   
    $this->setWidget('image', new sfWidgetFormInputFileEditable($options, array('size' => 40)));
    $this->setValidator('image', new opValidatorImageFile(array('required' => false)));

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

sfWidgetFormInputFileEditableは、画像の編集などに使うフィールドです。編集時は、削除を行えるようにしています。

doSave()には、画像を保存するアクションを追加します。

plugins/opVotePlugin/lib/form/doctrine/PluginVoteQuestionForm.class.php

    // 画像の保存
    if ($this->getValue('image'))
    {   
      if ($this->getObject()->getFile())
      {   
        $this->getObject()->getFile()->delete();
      }   

      $file = new File();
      $file->setFromValidatedFile($this->getValue('image'));
      $file->setName('vote_'.$this->getObject()->getId().'_'.$file->getName());
      $this->getObject()->setFile($file);
      $this->getObject()->save();
    }   

    // 画像の削除
    if ($this->getObject()->getFileId() && $this->getValue('image_delete'))
    {   
      $this->getObject()->getFile()->delete();
    }   

formの属性であるenctype=”multipart/form-data”を追加する必要があるので、
テンプレートを編集します。

plugins/opVotePlugin/apps/pc_frontend/modules/vote/templates/newSuccess.php

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

isMultipartをtrueにます。

同様に、編集画面でもこの作業をおこないます。

plugins/opVotePlugin/apps/pc_frontend/modules/vote/templates/editSuccess.php

?php op_include_form('vote_question_from', $form, array(
  'title' => '質問編集',
  'url' => url_for('@vote_update?id='.$form->getObject()->getId()),
  'isMultipart' => true,
)); ?>

画像の投稿が可能になっているはずです。
ss1
ss2

画像を表示する

DB上にある画像の表示に関わる処理は、symfonyプラグインとして同梱されている、sfImageHandlerPluginが仕組みを提供しています。このプラグインはDBに保存されている、web/cache/* にサイズ変更などの処理をおこなったキャッシュを作成する処理することができます。
実際にこれらの機能を使うには、テンプレートでこのプラグインが用意している、image_tag_sf_image() 関数を使うだけです。

質問表示ページに画像を表示してましょう。

plugins/opVotePlugin/apps/pc_frontend/modules/vote/templates/showSuccess.php

<div>質問者: <?php echo link_to($question->getMember()->getName(), '@obj_member_profile?id='.$question->getMemberId()); ?></div>

<?php // ここから追加!! ?>
<?php if ($question->getFile()): ?>
<div><?php echo image_tag_sf_image($question->getFile()->getName(), array('size' => '180x180')) ?></div>
<?php endif; ?>
<?php // 追加ここまで ?>

<div><?php echo nl2br($question->getBody()); ?></div>

image_tag_sf_image() 関数の引数は2つです。第1引数はファイル名です。このファイル名はDBのFileテーブルのnameフィールドに保存されているものを指定します。第2引数は、配列でオプションを指定します。

size
サイズ指定です。これを指定しないと縮小されずに表示されてしまいます。サイズは “幅x高さ” という形式で指定し、そのサイズを最大として縮小されます。OpenPNE初期としては、’76×76′, ‘120×120’, ‘180×180’, ‘240×320’, ‘600×600’ が許可されており、symfonyの設定値として “sf_image_handler_allowed_size” に配列により、さらに許可するサイズを指定することにより、前に挙げたサイズ以外も指定することができるようになります。
alt
画像の解説等を指定します。imgタグのalt属性に使われます。
no_image
セットされていると、画像が存在しない時は”NO IMAGE”の画像を表示します。
f
画像の形式を指定します。”jpg”, “png”, “gif” を指定することが可能です。通常は自動的に選択されます。

image_tag_sf_image() 内部では、sf_image_path()が呼び出され、画像のURLを生成します。大きなサイズへの画像へのリンクなどの為に画像URLを利用する場合はこちらを利用してください。

sf_image_path() は3つの引数を持っています。第1,2引数引数は同じですが、第3引数を持っています。第3引数に true を指定すると、絶対パスになります。(通常は相対パスです。)