PCがあれば何でもできる!

へっぽこアラサープログラマーが、覚えたての知識を得意げにお届けします

【CakePHP2.x】data URI Schemeの画像をダウンロードさせる

data URI Schemeはバイナリを文字列でやり取りできるので、色々と便利ですよね!

今回、DBに保存しているdata URIの画像を、ダウンロードできるようにして欲しいという依頼があり、対応してみました。

aタグのdownload属性は、IE11やSafariでは使えない

HTML5で追加されたdownload属性。

<a href="data:image/jpeg;base64,/..." download="file_name.jpg">ダウンロード</a>

こんな感じで、download属性にファイル名を書けば、そのままダウンロードできる仕組み。

すごく簡単で喜んでいたのですが、なんとIE11やSafariでは使えないことが発覚。そんな〜 orz

CakePHP側で対応する

通常サーバー側でファイルをダウンロードさせたい場合、レスポンスヘッダーをいじらなければいけないのですが、CakePHPだとこのように書けます。

<?php
App::uses('AppController', 'Controller');
class SampleController extends AppController {
  
  /* 〜 省略 〜 */

  public function download_image() {
    $dataUriImage = 'data:image/jpeg;base64,/...';

    // data URIのヘッダー部とMIMEの画像種別を取得
    //   - $match[0]: data:image/jpeg;base64
    //   - $match[1]: jpeg
    if (!preg_match('{^data:image/(.+);base64,}i', $dataUriImage, $match)){
      throw new NotFoundException('画像が見つかりませんでした');
    }

    // data URIのデータ部を取得し、base64からデコード
    $image = preg_replace('{^'.$match[0].'}', '', $dataUriImage);
    $image = base64_decode($image);

    // 本文に画像データを出力
    $this->response->body($image);

    // コンテンツタイプ指定(未指定だとSafariでDL時にhtmlの拡張子がついてしまう)
    $this->response->type('image/'.$match[1]);

    // ダウンロードファイル名を設定(拡張子はMIMEから取得)
    // $this->response->download('画像名.' . $match[1]); // これだと日本語がIEで化ける
    // $this->response->download(rawurlencode('画像名.' . $match[1])); // これだと今度はFireFox, Safariでデコードされずに化ける
    header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode('画像名.') . $match[1]); // これで全ブラウザOK

    return $this->response;
  }
}

response->bodyとdownloadを設定すれば良いだけです。簡単ですね!

ファイル名の拡張子はとりあえずMIMEの後方から取っているので、通常のjpeg, gif, pngあたりなら大丈夫ですが、きちんとやるなら変換表を作った方が良いかと思います。

もし、data URIではなく、ローカルに保存されているファイルであれば、response->fileを設定すればいいみたいです。
リクエストとレスポンスオブジェクト — CakePHP Cookbook 2.x ドキュメント

2016/03/18 追記

  • SafariでDL時にhtmlの拡張子がついてしまうため、$this->response->typeでコンテンツタイプを指定するようにしました。
  • ブラウザによって画像名に日本語が混じっていると文字化けするため、ファイル名に文字コードの指定と、URLエンコードを噛ませるようにしました。