takt

PHP

The PHP core vskstudio/takt-core-php (Packagist, v0.5.0) is framework-agnostic. It covers two server-side needs: rendering the snippet for the browser runtime into your HTML, and sending server-to-server (S2S) events straight from PHP. The Laravel and Symfony bridges build on it.

composer require vskstudio/takt-core-php

PHP 8.1+. SnippetRenderer needs no HTTP dependency: that line is enough to render the snippet.

The S2S client Takt, on the other hand, discovers its transport via PSR-18 / PSR-17 (php-http/discovery), and the package only declares the interfaces — you bring the implementation:

composer require vskstudio/takt-core-php guzzlehttp/guzzle
# or: composer require vskstudio/takt-core-php symfony/http-client nyholm/psr7

With no PSR-18 / PSR-17 implementation installed, new Takt(...) throws at construction time (discovery happens in the constructor, outside the fire-and-forget net).

SnippetRenderer — the browser runtime

SnippetRenderer produces the <script> block to drop into your <head>. It is configured with a readonly Options value object.

use Vskstudio\Takt\SnippetRenderer;
use Vskstudio\Takt\Options;
use Vskstudio\Takt\Mode;

$renderer = new SnippetRenderer(new Options(
    domain: 'example.com',
    outbound: true,
    files: true,
    tagged: true,
    notFound: true,
    fileExtensions: ['pdf', 'zip'],
));

echo $renderer->render(); // place inside <head>
FieldDefaultMeaning
domain(required)Site domain
endpointhttps://taktlytics.com/api/eventIngest endpoint — defaults to the hosted Takt origin so a bare setup works; pass /api/event for a same-origin proxy
scriptOriginnullFirst-party origin to serve the runtime + derive the endpoint (dodge ad-blockers; endpoint wins)
outboundfalseTrack outbound links (outbound token)
filesfalseTrack file downloads (downloads token)
fileExtensions[]Restrict downloads to these extensions (data-downloads-ext); empty keeps the default list
taggedfalseTrack elements marked data-takt-event (tagged token)
notFoundfalseTrack 404 pageviews (404 token)
excludeLocalhosttrueDrop localhost events
noncenullCSP nonce for the <script> tag
sampleRatenullKeep only this fraction (0–1) of hits (data-sample-rate)
trackQuerynullKeep the query string + hash (data-track-query); off strips them
queryParams[]Allowlist of query params to keep (data-query-params) — no effect when trackQuery is on, which keeps everything
respectDntnullfalse stops honoring Do-Not-Track (data-respect-dnt)
enablednullfalse = kill-switch, no-op snippet (data-enabled)
scrubUrlnullRaw JS URL-rewrite function — requires Mode::Sdk
exclude[]Path prefixes never tracked (segment-bounded) — requires Mode::Sdk
modeMode::InlineRuntime sourcing (below)

enum Mode:

  • Inline — embeds the vendored takt.auto.js bundle inline in the tag (zero extra requests, CSP-friendly with a nonce).
  • Cdn — emits a <script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js"> loader.
  • Assetdefer src="/takt/takt.auto.js" pointing at a self-hosted copy, or {scriptOrigin}/takt/takt.auto.js when scriptOrigin is set.
  • Sdk — emits a <script type="module">import{init}…;init({…})</script> that boots the full SDK. The only mode able to express scrubUrl; the module is loaded from {scriptOrigin}/takt/takt.esm.js when scriptOrigin is set, otherwise from jsDelivr. The package only vendors takt.auto.js: takt.esm.js is not shipped, it is on you to serve it at that URL, otherwise the module 404s and nothing is measured.

Autocapture is opt-in: outbound, files, tagged and notFound each add a token to a single data-auto attribute the bundled takt.auto.js reads; fileExtensions narrows which downloads count.

Advanced options

Each advanced option defaults to null (“unset” — the tracker’s own default applies); only a non-default value is rendered. sampleRate, trackQuery, queryParams, respectDnt and enabled mirror to data-* attributes in Inline, Cdn and Asset modes (snippet parity, see Configuration); in Mode::Sdk no data-* attribute is emitted, everything goes into the object passed to init(). scrubUrl is a raw JS function injected verbatim into the page: it can only be expressed in Mode::Sdk (passing it in any other mode throws) and stays dev-controlled — never build it from user input. exclude likewise lives only in the full SDK (the ≤ 1 kB minimal snippet omits it): it requires Mode::Sdk and throws in the other modes rather than silently dropping paths you believed were excluded.

new Options(
    domain: 'example.com',
    mode: Mode::Sdk,
    sampleRate: 0.5,
    queryParams: ['utm_source'],
    exclude: ['/app', '/account'],
    scrubUrl: '(u) => u.split("?")[0]',
);
SPA navigation tracking is always on (no spa toggle). Do-Not-Track is honored by default; only pass respectDnt: false (or data-respect-dnt="false") if you know exactly why.

Options::fromArray(array $a): self builds the options from an array and additionally accepts snake_case aliases (exclude_localhost, not_found, file_extensions, sample_rate, track_query, query_params, respect_dnt, scrub_url) — that is what makes the Laravel and Symfony bridges config-file driven. Two constants save you from retyping the URLs: Options::HOSTED_ORIGIN (https://taktlytics.com) and Options::HOSTED_ENDPOINT (https://taktlytics.com/api/event). If you assemble an inline tag yourself, SnippetRenderer::neutralizeScriptClose(string $js): string is public: it escapes the script closing tags contained in a bundle.

Takt — the server-to-server client

Takt posts events to POST /api/event with an API key carrying the events:write permission (takt_ik_ prefix), bound to the site whose domain matches domain exactly — otherwise ingest answers 401. Ideal for actions that don’t happen in the browser (payment webhook, queued job…).

The S2S client’s endpoint is a base origin, never a path: /api/event is appended on every send. So don’t copy the SnippetRenderer default from the table above, which is a full URL — it would yield https://taktlytics.com/api/event/api/event. Pass Options::HOSTED_ORIGIN, or the origin of your first-party proxy if you serve one.

use Vskstudio\Takt\Options;
use Vskstudio\Takt\Revenue;
use Vskstudio\Takt\Takt;

// Forward the real visitor's IP + User-Agent for attribution
// (adapt how you read the request to your framework):
$takt = (new Takt(
    endpoint: Options::HOSTED_ORIGIN, // https://taktlytics.com
    domain: 'example.com',
    apiKey: $_ENV['TAKT_API_KEY'],
))->withVisitor($_SERVER['REMOTE_ADDR'] ?? null, $_SERVER['HTTP_USER_AGENT'] ?? null);

$takt->event('Signup', ['plan' => 'pro'], null, 'https://example.com/signup');

$takt->event(
    'Purchase',
    ['plan' => 'pro'],
    new Revenue(amount: '29.00', currency: 'EUR'),
    'https://example.com/thanks',
);

$takt->pageview('https://example.com/thanks');
  • __construct(string $endpoint, string $domain, ?string $apiKey = null, ?ClientInterface $httpClient = null, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null) — the last three parameters inject the PSR transport (required for testing); left null, they are discovered.
  • withVisitor(?string $ip, ?string $userAgent): self — bind the real visitor before sending (returns an instance to send from)
  • event(string $name, array $props = [], ?Revenue $revenue = null, ?string $url = null, ?string $referrer = null): void$props is an array<string,scalar> whose every value is cast to a string (true goes out as '1')
  • pageview(?string $url = null, ?string $referrer = null): void
  • strict(): self — returns a clone that throws on a transport error or on any response other than 202 (handy in tests)
  • Fire-and-forget: in default mode, transport errors and error responses alike are swallowed (analytics must never break the app). A 202 means “accepted”, not “recorded”: a User-Agent classified as a bot, or an opt-out (DNT / GPC), is dropped server-side — still with a 202.
The page URL is required on every send: pass $url as an absolute http(s) URL. Left out, it goes out empty, ingest answers 400 and the event is not recorded — in default mode the rejection is swallowed, the caller sees nothing.
Visitor attribution is derived server-side from the IP and User-Agent. Without withVisitor(), S2S events attribute to the application server, not the real visitor — forward the IP / User-Agent from the current request.

Revenue is a readonly value object (amount, currency): the amount is a decimal string, the currency a 3-letter uppercase code (mirroring the JS SDK). Both are validated at construction — '29,00' or 'eur' throw an InvalidArgumentException.