【PHP】PHPフレームワーク「Flow」を試してみる～ファイルダウンロード編～

# 初めに
前回は[こちら](https://qiita.com/nnhkrnk/items/f06f40bf8e1159eff80d)の記事でファイルのアップロードについてをまとめました。
今回はFlowを用いてファイルダウンロードの実装方法についてまとめます。

# 事前知識
この記事はFlowにおける基本的なDB操作ができることを前提に記載しています。
以下の記事を読んでから読むことをおすすめします。
[【PHP】マイナーフレームワーク「Flow」を試してみる～API作成編～](https://qiita.com/nnhkrnk/items/2b9f647d6130a098345f)
[【PHP】マイナーフレームワーク「Flow」を試してみる～DB接続＆データ登録編～](https://qiita.com/nnhkrnk/items/739ac12036c858525e23)

また、この記事は以下の記事の続きです。
[【PHP】マイナーフレームワーク「Flow」を試してみる～ファイルアップロード編～](https://qiita.com/nnhkrnk/items/f06f40bf8e1159eff80d)


# ダウンロード試してみた
さっそくダウンロードを試してみましょう。
[前回の記事](https://qiita.com/nnhkrnk/items/f06f40bf8e1159eff80d)でアップロードしたファイルをダウンロードするAPIを作成します。

プロジェクト構成は以下です。

```
Project/
  └ Packages/
      ├ Application/
      |    └ Neos.Welcome/
      |         └ Classes/
      |              ├ Controller/
      |              |    └ ImageController.php（★）
      |              |
      |              ├ Domain/
      |              |    ├ Model
      |              |    |    └ Image.php（前回から変更なし）
      |              |    |
      |              |    └ Repository/
      |              |         └ ImageRepository.php（★）
      |              |
      |              └ View/
      |                   └ DownloadView.php（★）
      |
      ├ Framework/
      └ Libraries/
```

## 1. Viewクラスの作成
初めにViewクラスを作成します。
今回作成するAPIはブラウザ（Google Chrome）経由で叩かれる想定としました。
ブラウザがファイルのダウンロードを行うためには、APIのレスポンスのヘッダーに特定の値を設定する必要があります。
詳しくは以下の記事をご覧ください。

https://qiita.com/nnhkrnk/items/e13290554dab8e3e6588

Flowの標準で用意されているViewクラスでは、ダウンロードの判定になりません。
そのため、ダウンロード専用のViewクラスを作成する必要があります。

```php: DownloadView.php
<?php
namespace Neos\Welcome\View;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\View\AbstractView;


/**
 * A JSON view
 *
 * @api
 */
class DownloadView extends AbstractView
{

    /**
     * @var string
     */
    protected $fileName;

    public function render()
    {
        $contentType = $this->controllerContext->getResponse()->getContentType();
        $this->controllerContext->getResponse()->setContentType($contentType);
        $this->controllerContext->getResponse()->setHttpHeader('Content-disposition', 'attachment; filename=' . $this->fileName);
        // $this->controllerContext->getResponse()->setHttpHeader('Access-Control-Allow-Origin', '*');
    }

    public function setFileName($fileName)
    {
        $this->fileName = $fileName;
    }

}
```
```$fileName```はダウンロードするファイルの名前です。
拡張子がないとダウンロードができないため、Viewクラスを利用する側で設定を行います（今回の場合、Controllerクラスで設定します）。

:::note info
CORS対策で```Access-Control-Allow-Origin```が必要な場合があります。今回は同一オリジンからのダウンロードのためコメントアウトしています。
:::

## 2. Repositoryクラスの修正
ImageRepositoryクラスにデータ取得用のメソッドを作成しました。
アップロードの際に一緒に登録した```fileKey```のカラムを用いて対象のファイルを取得します。


```php: ImageRepository.php
<?php
namespace Neos\Welcome\Domain\Repository;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\Repository;

/**
 * @Flow\Scope("singleton")
 */
class ImageRepository extends Repository
{
    /**
     * Find an image by its key
     *
     * @param string $imageKey
     * @return \Neos\Welcome\Domain\Model\Image|null
     */
    public function findByFileKey(string $fileKey): ?\Neos\Welcome\Domain\Model\Image
    {
        $query = $this->createQuery();
        $query->matching($query->equals('fileKey', $fileKey));
        return $query->execute()->getFirst();
    }
}
```


## 3. Controllerクラスの作成
ImageCOntrollerにファイル取得用のメソッドを作成しました。
Viewは先ほど作成した```DownloadView```を指定しています。

```php:ImageController.php
<?php
namespace Neos\Welcome\Controller;


use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Controller\ActionController;
use Neos\Welcome\Domain\Model\Image;
use \Neos\Flow\ResourceManagement\PersistentResource;

class ImageController extends ActionController
{

    /**
     * @Flow\Inject
     * @var \Neos\Flow\ResourceManagement\ResourceManager
     */
    protected $resourceManager;

    /**
     * @Flow\Inject
     * @var \Neos\Welcome\Domain\Repository\ImageRepository
     */
    protected $imageRepository;
    
    /**
     * @Flow\Inject
     * @var \Neos\Welcome\View\DownloadView
     */
    protected $view;

    /**
     * Gets an image
     * 
     * @param string $id
     * @return void
     */
    public function getImageAction(string $fileKey) {
        $image = $this->imageRepository->findByFileKey($fileKey);
        $file = $this->resourceManager->getStreamByResource($image->getOriginalResource());
        $this->view->setFileName($image->getOriginalResource()->getFilename());

        $this->response->setContent($file);
    }
}
```

## 4. 動作確認
動作確認してみます。
ブラウザに以下のアドレスを入力してAPIを呼び出します。
```http://localhost:8081/Neos.Welcome/Image/getImage?fileKey=key```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/94962e0c-ed67-7b03-7357-5b4c9f7e16a3.png)

無事にダウンロードできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430841/293fa59a-ed2e-5ab2-5f3a-0f53959bedc0.png)


# 終わりに
今回はファイルダウンロードの方法についてまとめました。
思いのほか簡単に実装でき、Flowの便利さを感じました。
次回は静的ファイルについてまとめる予定です。
ここまで読んでいただきありがとうございました！

# 参考

https://flowframework.readthedocs.io/en/9.0/TheDefinitiveGuide/PartIII/ResourceManagement.html