またどこかのCTOになった人のブログ

またどこかのCTOになった人が書いてます。最近はCakePHP3とConoHaのネタが多いです。

CakePHP3で画像データをDBに保存してみた。

その昔、こんな記事を書きました。
tsuralabo.hatenablog.com


画像データのDB登録記事がないことに1年近く経って気がつきました。
これはいけません。
ということで何事もなかったかの如く、記事を書くことにします。


DBへの画像登録というのは、いわゆるバイナリ登録です。
BLOBデータのアレですね。
昔から「BLOB」と聞くとスライム的な何かを想像してしまうのですが、それはどうでもいいです。
きっと過去にプレイしたRPGの何かなんじゃないかと思いますが、それもよくわかりません。


さて。
画像をDB登録する際はフォームヘルパーのFile属性に対して画像を指定して、それをアップロードするのが多いんじゃないかと。
それ以外もあるのかもしれませんが、自分が見たことないだけかもしれません。
とりあえず、View側の画像アップロード部分はこんな感じで作ります。

  <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
    <span>画像</span>
    <div class="input-group">
      <span class="input-group-addon">
        <i class="glyphicon glyphicon-paperclip"></i>
      </span>
      <?php echo $this->Form->file('sample_image_id', [
         'class' => 'form-control', 
         'type' => 'file', 
         'div' => false,
         'label'=> false
      ]); ?>
    </div>
  </div>


Bootstrap3使ってます。ちょっと怪しい書き方ですが、その辺は生暖かくお願いします。
さて、ここに指定された画像データがsubmitされた時、以下のようにごにょっとすると登録できます。

<?php
  /**
   * 画像登録処理(アップロードされた画像をそのまま登録)
   * @param type $imageFileModel    image_filesテーブルのモデル
   * @param type $imageFile              登録する画像本体
   */
  public function saveImageFile($imageFileModel, $imageFile) {
    
    // 画像情報を取得
    $imginfo = getimagesize($imageFile['tmp_name']);

    // ファイル拡張子に応じて処理
    if ($imginfo[2] == IMAGETYPE_JPEG) {
      // JPEG画像データを生成
      $createImage = imagecreatefromjpeg($imageFile['tmp_name']);
    } else if ($imginfo[2] == IMAGETYPE_PNG) {
      // PNG画像データを生成
      $createImage = imagecreatefrompng($imageFile['tmp_name']);
    }

    // 画像データをバッファへバイナリ出力
    ob_start();
    imagepng($createImage);
    $imageBinary = ob_get_clean();

    // モデル登録用パラメータ生成
    $imgFileParam = [
      'content_type' => $imageFile['type'],
      'image_data' => $imageBinary
    ];

    // 新規登録用エンティティ生成
    $entity = $imageFileModel->newEntity($imgFileParam);

    // 画像保存処理実行
    $result = $imageFileModel->save($entity);

    // 画像メモリを解放
    ImageDestroy($createImage);

    // データ登録時のIDを返す
    return $result->id;
  }


自分のよくやるパターンだと、上記のfunctionをコンポーネントに入れてます。
で、コントローラの方でそのコンポーネント経由でfunction呼び出してます。
「tmp_nameってなんだよ」ってのはview側からsubmitすればわかります。
「pr($this->request->getData());」とかやってみると出てくると思います。

トランザクションに関しては呼び元のコントローラの方でやっているのですが、環境や仕様に合わせて適宜修正して下さい。
一番最後にIDを返していますが、画像登録用テーブルと実テーブルが別ですのでIDを取得して実テーブルにパラメータセットしているためこんな感じになっています。ここも適宜修正して頂けますと。

getimagesize部分は対象画像のデータが色々取れますので、幅とか高さとかその辺の制限があればここで取得を。
でも登録処理する前にバリデーションは別でやりたいんだよ! という方は、上記functionの前にバリデーションを。
箇条書きっぽくなりましたが、以下を参考にして下さい。

<?php
  // $params['sample_image_id']でFile属性のデータが渡ってくることにします
  // 拡張子判定の場合
  $extension = substr($params['sample_image_id'], strrpos($params['sample_image_id'], '.') + 1);
  
  // $extensionに拡張子が入るので判定。
  // 判定用のリストを作っておくのもいいかもしれません
  private $extensionList = ['jpg', 'png'];

  if (!in_array($extension, $this->extensionList)) {
      $errorMessage[] = '画像の拡張子が不正です。jpgファイル、またはpngファイルを指定して下さい。';
  }


  // サイズ判定の場合
  $fileSize = $params['sample_image_id']['size'];

  // そもそもアップロードされてるかどうか判定
  if ($params['sample_image_id']['error'] == 0) {
    // アップロードされた
  } else {
    // アップロードされてない
    // $params['sample_image_id']['error']が「4」とかだと思います 
  }


こんな感じで画像データをDBにバイナリ登録したり、バリデーション実行したりするといいんじゃないかと思います。
でも、多分もっといい書き方あると思います。

まず動くことが大事。

動いてから色々ごにょごにょするといいかなと。