Cookie 存储
默认情况下,Phasis 的 fetch() 会忽略响应中的 Set-Cookie,并且请求不会发送 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 都可以使用。传入 null 会清空存储。
每个请求跳转时,Phasis 都会调用一次 get(url)(因此重定向到其他主机时会获取新的 Cookie);响应中的每个 Set-Cookie 标头也会调用一次 set(url, line)。Cookie 策略——域名匹配、路径范围、过期时间、SameSite、Secure——由存储负责,而不是由 Phasis 负责。
内存中的存储(测试)
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 会妥善处理策略。
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 对象绑定:
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());
};
$engine->eval($script);
$engine->setCookieJar($engine->eval('$jar'));
接收方无论是 PHP 对象还是 JsObject,都会调用相同的 get/set 方法。
每个请求的作用域
存储属于当前运行环境——它会在每个 fetch() 之间持续存在,直到通过 setCookieJar(null) 清除。若要将存储限定在单个请求流程中,请显式处理:
$engine->setCookieJar($jar);
$engine->eval('await runLoginFlow()');
$engine->setCookieJar(null);
从 PHP 检查
$engine->getCookieJar() 会返回传入的内容——适合在测试中进行断言。
$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 传输 — Cookie 存储在 Phasis 的重定向循环内部运行,并叠加在已安装的传输之上。
- Web API (WPT) — Fetch Pack 中覆盖 Cookie 行为的测试。