
# Cookie管理

デフォルトでは、Phasisの`fetch()`はレスポンスの`Set-Cookie`を無視し、リクエストに`Cookie`ヘッダーを付けません。サーバー側に埋め込む場合には適切な初期設定です。通常、あるユーザーのレスポンスが別のユーザーのリクエストに影響することは望ましくありません。

セッション状態に依存する操作、ログイン後のスクレイピング、テストでのブラウザー動作の再現などでCookieを使う場合は、`$engine->setCookieJar($jar)`でjarを設定してください。

## インターフェース

jarは、次の2つのメソッドに応答する任意のオブジェクトです。

```
get(url: string): string         // returns a "Cookie:" header value, e.g. "sid=abc; pref=dark"
set(url: string, header: string): void   // receives one Set-Cookie line
```

PHPオブジェクトと、対応するメンバーを持つJS側の`JsObject`sのどちらも使えます。`null`を渡すとjarがクリアされます。

Phasisはリクエストの各ホップで`get(url)`を呼び出すため、別ホストへのリダイレクトでは新しいCookieが取得されます。また、レスポンス内の各`Set-Cookie`ヘッダーに対して`set(url, line)`を呼び出します。ドメイン照合、パスの範囲、有効期限、`SameSite`、`Secure`といったCookieの*ポリシー*はjarの責任であり、Phasisの責任ではありません。

## メモリ内jar（テスト用）

```php
final class MemoryJar
{
    /** @var array<string, array<string, string>> host → name → "name=value" */
    private array $store = [];

    public function get(string $url): string
    {
        $host = parse_url($url, PHP_URL_HOST) ?? '';
        return implode('; ', $this->store[$host] ?? []);
    }

    public function set(string $url, string $header): void
    {
        $host = parse_url($url, PHP_URL_HOST) ?? '';
        [$nv] = explode(';', $header, 2);
        [$name, $value] = explode('=', trim($nv), 2) + ['', ''];
        if ($name !== '') {
            $this->store[$host][$name] = "$name=$value";
        }
    }
}

$engine->setCookieJar(new MemoryJar());
```

正常系のテスト用フィクスチャには十分ですが、`Domain=`、`Path=`、`Expires=`、および`Secure`/`HttpOnly`/`SameSite`属性は無視します。信頼境界を越える用途には使わないでください。

## Symfony BrowserKit

SymfonyのHTTPライブラリをすでにインストールしている場合は、`BrowserKit\CookieJar`がポリシーを適切に処理します。

```php
use Symfony\Component\BrowserKit\CookieJar as BrowserCookieJar;
use Symfony\Component\BrowserKit\Cookie;

final class SymfonyJarAdapter
{
    public function __construct(private BrowserCookieJar $jar) {}

    public function get(string $url): string
    {
        $cookies = $this->jar->allValues($url);
        return implode('; ', array_map(
            fn ($n, $v) => "$n=$v",
            array_keys($cookies),
            array_values($cookies),
        ));
    }

    public function set(string $url, string $header): void
    {
        $cookie = Cookie::fromString($header, $url);
        $this->jar->set($cookie);
    }
}

$engine->setCookieJar(new SymfonyJarAdapter(new BrowserCookieJar()));
```

## JS側のjar

jarがJS側にあり、たとえばJavaScriptから内容を確認したり初期データを設定したりする場合は、JSオブジェクトとしてバインドします。

```js
const jar = new Map();
$jar.get = (url) => Array.from(jar.entries()).map(([k, v]) => `${k}=${v}`).join('; ');
$jar.set = (url, header) => {
  const [nv] = header.split(';');
  const [name, value] = nv.split('=');
  jar.set(name.trim(), value.trim());
};
```

```php
$engine->eval($script);
$engine->setCookieJar($engine->eval('$jar'));
```

受け手がPHPオブジェクトでも`JsObject`でも、同じ`get`/`set`メソッドが呼び出されます。

## リクエストごとのスコープ

jarはrealm上にあり、`setCookieJar(null)`でクリアするまで、すべての`fetch()`を通じて保持されます。jarを単一のリクエストフローだけで使うには、明示的に設定してください。

```php
$engine->setCookieJar($jar);
$engine->eval('await runLoginFlow()');
$engine->setCookieJar(null);
```

## PHPからの確認

`$engine->getCookieJar()`は渡された値をそのまま返すため、テストのアサーションに便利です。

```php
$jar = new MemoryJar();
$engine->setCookieJar($jar);
$engine->eval('await fetch("https://api.example.com/login", { method: "POST" })');

assertNotEmpty($engine->getCookieJar()->get('https://api.example.com/'));
```

## 関連項目

- [Fetchトランスポート](https://phasis.inline0.dev/ja/docs/interop/fetch-transport) — Cookie jarはPhasisのリダイレクトループ内で動作し、インストール済みのトランスポートに重ねて使われます。
- [Web API（WPT）](https://phasis.inline0.dev/ja/docs/compatibility/web-apis) — Cookie動作を対象とするFetch Packテスト。
