Laravelのresponse()->json()のソースを追ってみた

## はじめに
前回以下を行った。

https://qiita.com/kenji123/items/06f8f2a476235bdf0046

ソースでも追ってみたので、記録として残しておく。

## ソースの流れ
### response()->json()のjsonメソッド
vscode等のエディタを使用しているとリックでvendorフォルダの以下の箇所が表示される。

``` /vendor/laravel/framework/src/Illuminate/Contracts/Routing/ResponseFactory.php
     * Create a new JSON response instance.
     *
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @return \Illuminate\Http\JsonResponse
     */
    public function json($data = [], $status = 200, array $headers = [], $options = 0);
```

必要な引数が何か把握できる。

`/vendor/laravel/framework/src/Illuminate/Contracts/Routing/ResponseFactory.php` は、Laravelの ResponseFactory インターフェース。このインターフェースは、どのようなメソッドがこのクラスで提供されるべきかを定義しているが、具体的な実装（ロジック）はインターフェースなので含まれていない。

実際のメソッドの実装は、このインターフェースを実装した具体的なクラスにある。多くの場合、これは`Illuminate\Routing\ResponseFactory`クラスで実装されているらしい。
`vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php`のようなパスで見つかるはず、今回はここでソースを追うことができた。

インターフェースとは気づかず、初心者はここで追うことを諦めてしまう要因かとも思われる。


### 実際のクラス
```/vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php
/**
     * Create a new JSON response instance.
     *
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @return \Illuminate\Http\JsonResponse
     */
    public function json($data = [], $status = 200, array $headers = [], $options = 0)
    {
        return new JsonResponse($data, $status, $headers, $options);
    }

```

この json() メソッドは Illuminate\Http\JsonResponse クラスの新しいインスタンスを作成して返している。この時、渡されたデータ（`$data`）、HTTPステータスコード（`$status`）、ヘッダー（`$headers`）、およびオプション（`$options`）はそのまま JsonResponse コンストラクタに渡されている。

この JsonResponse クラスが内部でデータをJSON形式にエンコードして、適切な Content-Type: application/json ヘッダーをセットしているはず。

### JsonResponseクラス
```/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php
/**
     * Create a new JSON response instance.
     *
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @param  bool  $json
     * @return void
     */
    public function __construct($data = null, $status = 200, $headers = [], $options = 0, $json = false)
    {
        $this->encodingOptions = $options;

        parent::__construct($data, $status, $headers, $json);
    }

```
`parent::__construct($data, $status, $headers, $json)`なのでその親クラスを確認。

### parent::__construct($data, $status, $headers, $json);

```/vendor/symfony/http-foundation/JsonResponse.php
/**
     * @param bool $json If the data is already a JSON string
     */
    public function __construct(mixed $data = null, int $status = 200, array $headers = [], bool $json = false)
    {
        parent::__construct('', $status, $headers);

        if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) {
            throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data)));
        }

        $data ??= new \ArrayObject();

        $json ? $this->setJson($data) : $this->setData($data);
    }

```

この時点では$jsonはデフォルトのfalseである。
`$json ? $this->setJson($data) : $this->setData($data);`

setDataメソッドを確認する。


### setDataメソッド
```/vendor/symfony/http-foundation/JsonResponse.php
/**
     * Sets the data to be sent as JSON.
     *
     * @return $this
     *
     * @throws \InvalidArgumentException
     */
    public function setData(mixed $data = []): static    
{
        try {
            $data = json_encode($data, $this->encodingOptions);
        } catch (\Exception $e) {
            if ('Exception' === $e::class && str_starts_with($e->getMessage(), 'Failed calling ')) {
                throw $e->getPrevious() ?: $e;
            }
            throw $e;
        }

        if (\JSON_THROW_ON_ERROR & $this->encodingOptions) {
            return $this->setJson($data);
        }

        if (\JSON_ERROR_NONE !== json_last_error()) {
            throw new \InvalidArgumentException(json_last_error_msg());
        }

        return $this->setJson($data);
    }

```

ここでjson_encode関数が出てきて$dataが処理される。
次に`return $this->setJson($data);`が処理される。

### setJsonメソッド
```/vendor/symfony/http-foundation/JsonResponse.php
  /**
     * Sets a raw string containing a JSON document to be sent.
     *
     * @return $this
     */
    public function setJson(string $json): static
    {
        $this->data = $json;

        return $this->update();
    }
```

updateメソッドを確認する。

### updateメソッド
```/vendor/symfony/http-foundation/JsonResponse.php
 /**
     * Updates the content and headers according to the JSON data and callback.
     *
     * @return $this
     */
    protected function update(): static
    {
        if (null !== $this->callback) {
            // Not using application/javascript for compatibility reasons with older browsers.
            $this->headers->set('Content-Type', 'text/javascript');

            return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
        }

        // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
        // in order to not overwrite a custom definition.
        if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
            $this->headers->set('Content-Type', 'application/json');
        }

        return $this->setContent($this->data);
    }
```
この update() メソッドによって、Content-Type ヘッダが application/json にセットされている。これによって、HTTP レスポンスが JSON データであることが明示された。


### 考察
response()->json()が、データをjson_encodeし、application/jsonでheaderをセットしているという流れを確認した。


## おわりに
エディタでソースを追うことができるが、途中でインターフェイスの定義で追跡が止まってしまうことが多かった、ここが肝だと感じた。