CakePHP 4 で日本語ファイル名でも文字化けせずにファイルをダウンロードさせる方法

はじめに

CakePHP 4 にはファイルをダウンロードするための機能があります。しかしそのまま使うと、ファイル名に日本語を使った時にIEMicrosoft Edge文字化けを起こします。

今日は CakePHP 4 でファイルをダウンロードする方法を、ファイル名に日本語を使用する方法と併せてご紹介します。

今回使用したのは CakePHP 4.0.7 です。

目次
  1. 元ファイルがある or バイナリの場合
  2. テキストの場合
  3. おわりに

1. 元ファイルがある or バイナリの場合

ダウンロードさせたいファイルがサーバ上にある場合、もしくはバイナリデータの場合は下記のように実装できます。

下記サンプルは、既存ファイルを読み込んで別名でダウンロードさせるプログラムです。/data/sample.png というファイルを読み込んでいます。

/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

use Cake\Http\CallbackStream; // ← 追加

class SampleController extends AppController
{
    public function index()
    {
        $file = ROOT . DS . 'data' . DS . 'sample.png';
        $stream = new CallbackStream(function () use ($file) {
            return file_get_contents($file);
        });

        // ファイルの拡張子
        $ext = substr($file, mb_strrpos($file, '.') + 1);

        // ファイル名
        $filename = rawurlencode("サンプル.{$ext}");

        // コンテンツタイプ
        $type = $this->response->getMimeType($ext) ?: 'file';

        return $this->response->withType($type)
          ->withHeader('Content-Disposition', "attachment;filename*=UTF-8''{$filename}")
          ->withBody($stream);
    }
}

プログラム内で生成したデータをダウンロードさせたい場合には、CallbackStream のコールバック関数内でデータを出力することでその内容をファイルとして出力することができます。

下記公式ドキュメントでは imagepng() を使った例が紹介されています。
(リンク先のやや下にあります。ページ内を「CallbackStream」で検索すると早いかもしれません)

また当ブログでは過去に PhpSpreadsheet でのサンプルを紹介しています。

2. テキストの場合

ファイルではなく、プログラムで生成したテキストをダウンロードする場合はwithStringBody() を使って簡潔に書くことができます。

/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

class SampleController extends AppController
{
    public function index()
    {
        // 出力する文章
        $body = "あいうえお。\nかきくけこ。\nさしすせそ";

        // ファイル名
        $filename = rawurlencode("サンプル.txt");

        return $this->response->withType('txt')
          ->withHeader('Content-Disposition', "attachment;filename*=UTF-8''{$filename}")
          ->withStringBody($body);
    }
}

例えば CSV のときには、ファイル名の拡張子と withType の引数を csv に変更することで対応可能です。

withType は /vendor/cakephp/cakephp/src/Http/Response.php の $_mimeTypes で確認できます。

3. おわりに

ファイルダウンロードの実装方法は色々とあります。例えば、今回はご紹介しませんが、withFile() を使う場合には、IE と Microsoft Edge の時にファイル名を rawurlencode() すれば文字化けを回避できます。

また必要に応じて Component に機能を作っておくと便利だと思います。