CakePHP3:ブラウザにファイルをダウンロードさせる

mp3のようなファイルを配信する場合、次のように書きます:

$this->autoRender = false;
$download_file   = $file_dir . DS . $file_id . ".mp3";
$response = $this->response->withFile($download_file);
return $response;

このままでは、mp3がその場で再生され、ダウンロードできません。 当初は、ダウンロードするリンクにdownload属性を付けていたのですがIE11ではサポートされていません:

<a href="/Audios/download.php?id=100" download>クリックしてダウンロード</a>

解決策は至って簡単でした。withFileの第2引数に['download' => true, 'name' => 'ファイル名']と指定してあげるだけです:

$this->autoRender = false;
$download_file   = $file_dir . DS . $file_id . ".mp3";
$response = $this->response->withFile($download_file, ['download' => true, 'name' => 'ファイル名');
return $response;

とするだけです。これで、IE11を含めて、正しくダウンロードしてくれます。

念のため、どのような仕組みかソースを見てみました。ファイルはvendor/cakephp/cakephp/src/Http/Response.phpです。

       if ($options['download']) {
            $agent = env('HTTP_USER_AGENT');

            if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
                $contentType = 'application/octet-stream';
            } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
                $contentType = 'application/force-download';
            }

            if (isset($contentType)) {
                $new = $new->withType($contentType);
            }
            $name = $options['name'] ?: $file->name;
            $new = $new->withDownload($name)
                ->withHeader('Content-Transfer-Encoding', 'binary');
        }

なお、aタグにdownload属性をつけるのを怠ると、Safariでは再生とダウンロードが両方走ります。