HTTPのやりとりに使うデータを扱う、 "HttpFoundation"

[Symfony Component Advent Calendar 2022](https://qiita.com/advent-calendar/2022/symfony-component)の1日目の記事です。

## 最初に
[Symfony](https://symfony.com)はPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

> Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
  （SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。）

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。


## HTTPのやりとりに使うデータを扱う、 "HttpFoundation"

[HttpFoundation](https://symfony.com/doc/current/components/http_foundation.html)は、HTTPで扱うデータを**オブジェクト**として扱うことができるようになるコンポーネントです。Symfony以外のフレームワークやアプリケーションでも動作します。Symfonyではフレームワークインストール時に初期インストールされているコンポーネントです。

### インストール

```shell
composer require symfony/http-foundation
```

### リクエスト

PHPでは`$_GET`, `$_POST`, `$_COOKIE` など、標準のリクエスト用の変数が用意されています。それらをHttpFoundationではオブジェクトとして扱えるようにします。

```php

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();

$request->query->get('hoge'); // $_GET['hoge']にアクセス

$request->request->get('hoge'); // $_POST['hoge']にアクセス

$request->cookies->get('hoge'); // $_COOKIE['hoge']にアクセス

$request->files->get('hoge'); // $_FILE['hoge']にアクセス

$request->attributes->get('hoge'); // HTTPリクエストではない追加した値にアクセス

$request->server->get('hoge'); // $_SERVER['hoge']にアクセス

$request->headers->get('hoge'); // $_SERVER['hoge']にアクセス

$request->getContent(); // POSTの生データにアクセス
```

上記のように、リクエストオブジェクトの中にそれぞれ対応したプロパティがあり、それを使ってHTTPリクエストの値にアクセスします。
この中で特殊なのは`attribute`と`getContent()`です。

#### attribute

HTTPリクエストにはないけど、一緒に扱うと便利そうなデータを追加するためにあります。

```php
$request = Request::createFromGlobals();
$request->attributes->set('hoge', 'abcde');
echo($request->attributes->get('hoge')); // 'abcde'
```

また、Symfonyコントローラーで、リクエストを使うとデフォルトで以下の値が追加されています。

```php

class TestController extends AbstractController
{
    #[Route('/test/{id}, name: 'app_test')]
    public function index(Request $request)
    {
        dd($request->attributes);
    }
}
```

```
^ Symfony\Component\HttpFoundation\ParameterBag {#13 ▼
  #parameters: array:5 [▼
    "_route" => "app_test" // ルーティング名
    "_controller" => "App\Controller\TestController::index" // 呼び出したコントローラー::アクション
    "id" => "1" // ルーティングでパスに設定したパラメータ属性の値（/test/1でアクセスした場合、"1"）
    "_route_params" => array:1 [▼ //  ルーティングでパスに設定したパラメータ属性の値の配列
      "id" => "1"
    ]
  ]
}
```

#### getContent()

`getContent()` は、POSTされた生データを扱います。XMLやJSONなどの生データを使う必要があるときに便利です。さらにJSONの場合は`toArray()`を使うと配列にしたJSONデータを受け取ることができます。

```php
$rawData = $request->getContent(); // 生データ
$data = $request->toArray(); // JSONが連想配列になったデータ
```

### レスポンス

レスポンスも、ヘッダーやコンテンツなどをオブジェクトとして扱えます。

```php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;

$response = new Response(
    'コンテンツ',
    Response::HTTP_OK, // 200
    ['Content-Type' => 'text/html']
);

$response->headers->get('Content-Type'); // ヘッダーのContent-Typeにアクセス
$response->headers->set('Content-Type', 'text/html');// ヘッダーのContent-Typeを設定

$response->getContent(); // コンテンツにアクセス
$response->setContent('コンテンツ'); // コンテンツを設定

$response->getStatusCode(); // ステータスコードにアクセス
$response->setStatusCode(200); // ステータスコード設定

$response->headers->getCookie('hoge'); // クッキー hoge にアクセス
$response->headers->setCookie(Cookie::create('hoge', 'foo')); // クッキー hoge を設定

$response->send(); // レスポンスを出力
```

`send()`は内部で`echo()`　などの出力処理をしてたりするので、send()を呼ぶだけで出力されます。
また、レスポンスはそれぞれの出力形式によって便利なクラスが用意されています。

#### リダイレクト

リダイレクト用にRedirectResponseが用意されています。

```php
use Symfony\Component\HttpFoundation\RedirectResponse;

$response = new RedirectResponse('http://qiita.com');
```

#### ストリーミング

ストリーミング出力もあります。`setCallback()` 内で出力処理は書く必要があります。

```php
use Symfony\Component\HttpFoundation\StreamedResponse;

$response = new StreamedResponse();
$response->setCallback(function() {
    for (i = 0; i < 10; i ++) {
        echo("${i}<br>");
        flush();
    }
});
$response->send();
```
#### ファイル

ファイル出力の場合、環境内のファイルを出力する場合は`BinaryFileResponse`, 動的に作った値を出力する場合は`Response`を使います。

```php
use Symfony\Component\HttpFoundation\BinaryFileResponse;

$file = 'public/images/image.png';
$response = new BinaryFileResponse($file);

$response->send();
```

```php
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Response;

$csv = "a,b,c\nd,e,f\n";
$response = new Response($csv);

$disposition = HeaderUtils::makeDisposition(
    HeaderUtils::DISPOSITION_ATTACHEMENT,
    'result.csv'
);
$response->headers->set('Content-Disposition', $disposition);

$response->send();
```

#### JSON

JSONを出力する場合は `JsonResponse`を使います。APIとか作ったときに便利です。

```php
use Symfony\Component\HttpFoundation\JsonResponse;

$data = [
    'hoge' => 'foo',
    'bar' => [
        1,
        2,
    ],
];

$response = new JsonResponse($data);

$response->send();
```

## まとめ

上記のような感じで、HTTPリクエスト、レスポンスをオブジェクトで扱えるようになります。
素のPHPにこれだけインストールして使うだけでもだいぶ作りやすくなるんじゃないかなと思います。


※2日目からは、もうちょっと記述量減らしてエコでやっていくとおもいます。