2015年3月10日火曜日

PhalconとPostgresqlとバイナリーデータ

Phalconを使いPostgresqlのbytea(バイナリー)型へ画像の登録をしてみた。 ちょっと気を付けなければならないと思いメモする事に。
最初にポイントだけ書くとModelを使用した場合に登録、更新時のデータ型と取得した時のデータ型が異なって いるので気を付けなければいけない。また、基本的にはその部分について書いているので他の点についてはだいぶ 簡略化して記述している。

併せて、これを実行した時の各種バージョン
Postgresql 8.4.X
PHP 5.5.15
Phalcon 1.3.2

今回は以下のようなsqlで作成されたbytea型を持ったテーブルから画像データを操作するイメージで 考える。
create table t_image (
  id bigserial not null
  , file_name varchar(255)
  , binarydata bytea
  , constraint t_image_PKC primary key (id)
) ;

上記のテーブルを前提とした場合 model は以下のようになる。 Image.php
class Image extends \Phalcon\MVC\Model
{
  public $id;
  public $file_name;
  public $binarydata;

  /**
   * ソース指定
   *
   * @see Phalcon\Mvc\Model::getSource
   */
  public function getSource()
  {
    return "t_image";
  }
}

画像の登録

Phalconで送信されたファイルを受け取る方法はPhalconのドキュメントを参照する。 参照したデータからバイナリデータを読み出し16進数文字列に変換して登録する
$image = new \App\Mvc\Model\Image();
if ($this->request->hasFiles() == true) {
  // test.png というファイルが送信されると仮定した実装
  foreach ($this->request->getUploadedFiles() as $_file){
    if($_file->getName() == "test.png"){
      $file = $_file;
    }
  }
}

// ファイルがあれば登録を行う
if($file){
  $image->file_name = $file->getName();
  $image->binarydata = bin2hex(file_get_contents($file->getTempName()));
  $image->create();
}

画像の表示

Phalconのモデルを使用した場合にはbytea型にはリソース型が設定される 以下実行例
$image = \App\Mvc\Model\Image::findFirst(array(
    "conditions" => "id = ?1",
    "bind"         => array(1 => 1),
  )
);
if($image && $image->binarydata){
  var_dump($image->binarydata);
}

-- 標準出力 -------------------
resource(21) of type (stream) 
出力する際にはリソースの中身は16進数文字列なのでバイナリ文字列に 変換してから出力する
if($image && $image->binarydata){
  header('Content-Type: image/png');
  echo hex2bin(stream_get_contents($image->binarydata));
}

更新時の注意点

画像の表示で触れたModelを使用した場合にはbytea型にはリソース型が設定される事により bytea型はリソース型から一度文字列データへ変換しないとデータが失われてしまう。
$image = \App\Mvc\Model\Image::findfirst(array(
    "conditions" => "id = ?1",
    "bind"         => array(1 => 1),
  )
);

// ファイル名だけ変更
$image->file_name = "othername.png";

if($image->binarydata){
  // リソースから文字列データを抜出し再設定。
  // リソース型を自動で登録するような事はしてくれていない
  $image->binarydata = stream_get_contents($image->binarydata);
}
$image->update();
勢いで書いてしまっているのでだいぶ端折っているがポイントは網羅できていると思う。 Model::findfirst()で取得してModel::update()やsave()で更新させるのは Phalconでは結構重要な思想のはずだが、データ型が違うという事を意識しなければならないのは ちょっと厳しい。。。もしくはもっと簡単に行う方法があるのかもしれない。