
# 쿠키 저장소

기본적으로 Phasis의 `fetch()`은 응답의 `Set-Cookie`을 무시하고 요청에 `Cookie` 헤더를 보내지 않습니다. 서버 측 임베딩에 적합한 기본값입니다. 일반적으로 한 사용자의 응답이 다른 사용자의 요청에 영향을 주지 않도록 해야 합니다.

세션 상태에 따라 달라지는 흐름을 실행하거나, 로그인 뒤에서 데이터를 가져오거나, 테스트에서 브라우저 동작을 재현하는 등 쿠키가 필요하다면 `$engine->setCookieJar($jar)`으로 저장소를 연결하세요.

## 인터페이스

저장소는 다음 두 메서드에 응답하는 객체라면 무엇이든 됩니다:

```
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`을 전달하면 저장소가 비워집니다.

Phasis은 요청이 이동하는 각 단계마다 `get(url)`을 호출합니다(따라서 다른 호스트로 리디렉션되면 새 쿠키를 가져옵니다). 응답의 각 `Set-Cookie` 헤더마다 `set(url, line)`를 호출합니다. 도메인 일치, 경로 범위, 만료, `SameSite`, `Secure` 같은 쿠키 *정책*은 저장소가 담당하며 Phasis의 책임은 아닙니다.

## 메모리 내 저장소(테스트)

```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 측 저장소

저장소가 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` 메서드가 동일하게 호출됩니다.

## 요청별 범위

저장소는 영역에 속하며, `setCookieJar(null)`로 비울 때까지 모든 `fetch()`에 걸쳐 유지됩니다. 저장소를 단일 요청 흐름에만 적용하려면 명시적으로 설정하세요:

```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/ko/docs/interop/fetch-transport) — 쿠키 저장소는 Phasis의 리디렉션 루프 **내부에서** 작동하며 설치된 전송 계층 위에 적용됩니다.
- [웹 API(WPT)](https://phasis.inline0.dev/ko/docs/compatibility/web-apis) — 쿠키 동작을 다루는 Fetch Pack 테스트.
