Skip to content
Muhammet Şafak edited this page Jun 10, 2026 · 1 revision

FAQ

Quick answers to common pitfalls and clarifications. If your question is not here, please open an issue — documentation fixes are reviewed eagerly.

My cookie isn't being set — why?

Almost always because send() ran after output. Like native setcookie(), send() writes an HTTP header, which only works before any body has been emitted (no echo, no HTML, no whitespace/BOM outside the PHP tags). Call send() at the end of request handling, before you render.

$cookie->set('a', '1');
$cookie->send(); // BEFORE any output
echo $html;      // output starts here

Do not rely on the destructor: it often runs during shutdown, after the body has flushed, and the default writer suppresses the "headers already sent" warning — so a too-late write fails silently. See Sending & Lifecycle.

My values disappear between requests — why?

The usual cause is a per-key TTL that has elapsed. The $ttl argument of set()/setArray()/push() is seconds from now; once it passes, the entry is removed on the next read and never re-sent. Check:

  • You did not pass a short $ttl you have since forgotten about.
  • The per-key TTL is not longer than the transport ttl option — if it is, the browser drops the whole cookie when the transport TTL elapses.
  • You actually called send() after the mutation.

See TTL & Expiry.

I changed the salt and all my cookies vanished — is that a bug?

No, that is by design. The salt is the HMAC key. When it changes, every previously issued cookie fails the signature check and is silently replaced with an empty (freshly signed) cookie. Users lose their stored values. Rotate the salt only when you intend exactly that — it doubles as a global "invalidate every cookie" switch. See the Security Model.

Can the client read my cookie values?

Yes. The payload is signed, not encrypted — it is base64 of a plain PHP serialization, which anyone can decode. The signature proves the data was issued by you and detects tampering; it does not hide the values. Do not store secrets, password hashes, or sensitive PII. Store an opaque reference and keep the real data server-side. See the Security Model and the Remember Me recipe.

Why can't I store an array or an object?

Only string, bool, int, float and numeric strings are accepted; anything else throws CookieInvalidArgumentException. To store structured data, encode it into a string yourself and decode on read:

$cookie->set('prefs', json_encode(['theme' => 'dark', 'lang' => 'en']));
// ...later...
$prefs = json_decode((string) $cookie->get('prefs', '{}'), true);

Keep the total payload small (see the next entry).

Is there a size limit?

Yes. Browsers cap a single cookie at roughly 4 KB (4096 bytes) for the whole name=value pair, and the value here also includes the base64 overhead plus a 64-character HMAC signature. Because every value you set shares one cookie, a large value or many values can blow past the limit, at which point the browser silently drops the cookie. Keep the combined payload well under 4 KB; store bulk data server-side.

Why is null rejected when get() returns null?

null is not a storable value type, so set('k', null) throws. But get() returns null as its default when a key is absent. To remove a value, call remove('k') rather than setting it to null. To tell "absent" from a stored false/0/'', use has(). See Basic Usage.

What's the difference between flush() and destroy()?

  • flush() clears the values but keeps the cookie: the next send() writes an empty, still-signed cookie that stays in the browser.
  • destroy() writes a deletion cookie immediately, telling the browser to remove it, and makes the next send() a no-op.

Use flush() to empty the contents, destroy() to delete the cookie (e.g. logout). See Reading & Removing.

Why does a negative TTL not expire my value?

A negative $ttl is normalized with abs(), so -100 behaves like 100 seconds from now — the value stays valid. This prevents an accidental sign flip from silently dropping data. To actually delete a value, call remove(), not set() with a negative TTL. See TTL & Expiry.

Why is Cookie final?

Cookie is final and its properties are private to keep the security-sensitive surface (signing, verification, the deserialization guard) closed to accidental override. A subclass that touched encoding, the salt, or the verification flow could quietly defeat the tamper protection. Extend by composition: depend on CookieInterface and wrap a Cookie instance in your own class, as the Flash Messages and Testing pages do.

Does it touch the superglobals?

It reads $_COOKIE by default (you can inject $source instead) and never writes to it. Writes go through the injectable $writer, which defaults to native setcookie(). This makes the class fully testable without superglobal or header juggling — see Testing.

Which PHP versions are supported?

PHP 7.4 through 8.4. The 2.0 line requires 7.4+ (1.x advertised 7.2 but already relied on the PHP 7.3 setcookie() options-array signature). See Migration (v1 → v2).

Where do I report a security issue?

See SECURITY.md in the InitPHP org profile. Please do not file public issues for vulnerabilities.

See also

Clone this wiki locally