setono / client
PHP abstraction for identifying a browser client
Fund package maintenance!
Requires
- php: >=8.1
Requires (Dev)
- ergebnis/composer-normalize: ^2.50
- infection/infection: ^0.29
- jangregor/phpstan-prophecy: ^2.3
- phpspec/prophecy-phpunit: ^2.5
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- ramsey/uuid: ^4.7.6
- rector/rector: ^2.4
- shipmonk/composer-dependency-analyser: ^1.8
- sylius-labs/coding-standard: ^4.5
- symfony/uid: ^6.4 || ^7.0
This package is auto-updated.
Last update: 2026-06-15 11:18:57 UTC
README
This library provides a small, framework-agnostic value object for identifying a browser client: a stable id
together with arbitrary, optionally expiring metadata. A companion Cookie class serializes that id — along with a
format version and "first/last seen" timestamps — to and from a cookie value, so the same visitor can be recognized
across requests.
Installation
composer require setono/client
When you don't supply an id yourself, the library generates a UUIDv7. To use that, install one of the supported UUID
libraries (if both are installed, symfony/uid is preferred):
# If you want to use symfony/uid composer require symfony/uid # If you want to use ramsey/uuid composer require ramsey/uuid
Usage
The client
use Setono\Client\Client; // A new client with a generated UUIDv7 id and empty metadata $client = new Client(); // ...or built from an existing id and metadata $client = new Client('a3f1c0de-...', ['plan' => 'pro']); $client->id; // the identifier (string, readonly) (string) $client; // the same id — Client is Stringable $client->metadata; // the Metadata object (readonly)
Metadata
Metadata is a string => mixed bag. It implements ArrayAccess, Countable and IteratorAggregate, so you can
use it like an array:
$metadata = $client->metadata; $metadata->set('plan', 'pro'); $metadata->has('plan'); // true $metadata->get('plan'); // 'pro' — throws InvalidArgumentException if the key is missing or expired $metadata->remove('plan'); // array access, counting and iteration all work $metadata['plan'] = 'pro'; isset($metadata['plan']); // true unset($metadata['plan']); count($metadata); foreach ($metadata as $key => $value) { // ... }
Expiring entries
Pass a TTL (in seconds) as the third argument to set(). Expired entries are pruned lazily: they are no longer
returned by get()/has() and are excluded from iteration and count().
// "promo" expires one hour from now $metadata->set('promo', 'BLACKFRIDAY', ttl: 3600);
Persisting and restoring
Metadata::toArray() returns everything needed to rebuild the object — including the expiry bookkeeping — so you can
store it (in a database, cache, session, ...) and reconstruct the client later with the timestamps intact:
$id = $client->id; $data = $client->metadata->toArray(); // ...later $client = new Client($id, $data);
Client and Metadata are also JsonSerializable:
json_encode($client); // {"id":"a3f1c0de-...","metadata":{"plan":"pro"}}
Storing the id in a cookie
The Cookie class converts a client id to and from a cookie value. The value also carries a format version and the
timestamps for when the client was first and last seen. The library only produces and parses the string — reading the
request cookie and writing it to the response is left to your HTTP layer.
use Setono\Client\Cookie; // firstSeenAt and lastSeenAt default to the current time $cookie = new Cookie($client->id); (string) $cookie; // "2.1700000000.1700000000.a3f1c0de-..." (version.firstSeenAt.lastSeenAt.clientId) // Parse an incoming value. A bare id (no dots) is treated as a legacy v1 cookie and upgraded. $cookie = Cookie::fromString($_COOKIE['_client'] ?? ''); $cookie->clientId; $cookie->version; $cookie->firstSeenAt; $cookie->lastSeenAt; // Cookie is immutable; "touch" the last-seen timestamp by deriving a new instance $cookie = $cookie->withLastSeenAt(time());