PHP
Le cœur PHP vskstudio/takt-core-php (Packagist, v0.6.0) est agnostique au framework. Il couvre deux besoins côté serveur : rendre le snippet du runtime navigateur dans ton HTML, et envoyer des événements serveur-à-serveur (S2S) directement depuis PHP. Les bridges Laravel et Symfony s'appuient dessus.
composer require vskstudio/takt-core-php PHP 8.1+. SnippetRenderer n’a besoin d’aucune dépendance HTTP : cette ligne suffit pour rendre le snippet.
Le client S2S Takt découvre en revanche son transport via PSR-18 / PSR-17 (php-http/discovery) et le paquet ne déclare que les interfaces. Tu apportes l’implémentation :
composer require vskstudio/takt-core-php guzzlehttp/guzzle
# ou : composer require vskstudio/takt-core-php symfony/http-client nyholm/psr7 Sans implémentation PSR-18 / PSR-17 installée, new Takt(...) lève dès la construction (la découverte a lieu dans le constructeur, hors du filet fire-and-forget).
SnippetRenderer : le runtime navigateur
SnippetRenderer produit le bloc <script> à déposer dans le <head>. Il se configure avec un objet Options en lecture seule.
use Vskstudio\Takt\SnippetRenderer;
use Vskstudio\Takt\Options;
use Vskstudio\Takt\Mode;
$renderer = new SnippetRenderer(new Options(
domain: 'exemple.fr',
outbound: true,
files: true,
tagged: true,
notFound: true,
fileExtensions: ['pdf', 'zip'],
));
echo $renderer->render(); // à placer dans <head> | Champ | Défaut | Rôle |
|---|---|---|
domain | (requis) | Domaine du site |
endpoint | https://taktlytics.com/api/event | Endpoint d’ingestion, par défaut l’origine Takt hébergée (un setup nu marche direct) ; passe /api/event pour un proxy same-origin |
scriptOrigin | null | Origine first-party pour servir le runtime + dériver l’endpoint (contourne les ad-blockers ; endpoint prime) |
outbound | false | Suit les liens sortants (token outbound) |
files | false | Suit les téléchargements (token downloads) |
fileExtensions | [] | Restreint les téléchargements à ces extensions (data-downloads-ext) ; vide = liste par défaut |
tagged | false | Suit les éléments marqués data-takt-event (token tagged) |
notFound | false | Suit les pages 404 (token 404) |
excludeLocalhost | true | Ignore les événements localhost |
nonce | null | Nonce CSP pour la balise <script> |
sampleRate | null | N’envoie que cette fraction (0 à 1) des hits (data-sample-rate) |
trackQuery | null | Conserve la query string + le hash (data-track-query) ; off = strippé |
queryParams | [] | Allowlist de paramètres de query conservés (data-query-params). Sans effet si trackQuery est actif, qui garde tout |
respectDnt | null | false cesse de respecter Do-Not-Track (data-respect-dnt) |
enabled | null | false = coupe-circuit, snippet no-op (data-enabled) |
scrubUrl | null | Fonction JS brute de réécriture d’URL (requiert Mode::Sdk) |
exclude | [] | Préfixes de chemin jamais suivis (borné au segment), requiert Mode::Sdk |
redactRoutes | [] | Motifs de routes sensibles envoyés sous la forme du motif, voir Masquage de routes (requiert Mode::Sdk) |
routeTemplates | false | Envoie chaque page sous la forme de son gabarit de route (requiert Mode::Sdk) |
routeTemplate | null | Gabarit de la route de la page rendue, lu quand routeTemplates est actif |
mode | Mode::Inline | Source du runtime (voir ci-dessous) |
enum Mode :
Inline: embarque le bundletakt.auto.jsvendoré dans la balise (zéro requête en plus, compatible CSP avec un nonce).Cdn: émet un loader<script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js">.Asset:defer src="/takt/takt.auto.js"pointant vers une copie auto-hébergée, ou{scriptOrigin}/takt/takt.auto.jssiscriptOriginest défini.Sdk: émet un<script type="module">import{init}…;init({…})</script>qui boote le SDK complet. Seul mode capable d’exprimerscrubUrl; le module est chargé depuis{scriptOrigin}/takt/takt.esm.jssiscriptOriginest défini, sinon depuis jsDelivr. Le paquet ne vendore quetakt.auto.js:takt.esm.jsn’est pas fourni, c’est à toi de le déposer à cette URL, sinon le module renvoie un 404 et rien n’est mesuré.
L’autocapture est opt-in : outbound, files, tagged et notFound ajoutent chacun un token à un unique attribut data-auto que lit le bundle takt.auto.js ; fileExtensions restreint les téléchargements comptés.
Options avancées
Chaque option avancée vaut null par défaut (« non définie » : le défaut du tracker s’applique) ; seule une valeur non-défaut est rendue. sampleRate, trackQuery, queryParams, respectDnt et enabled se reflètent en attributs data-* dans les modes Inline, Cdn et Asset (parité snippet, voir Configuration) ; en Mode::Sdk aucun attribut data-* n’est émis, tout part dans l’objet passé à init(). scrubUrl est une fonction JS brute injectée verbatim dans la page : elle ne s’exprime qu’en Mode::Sdk (la passer dans un autre mode lève une exception) et reste contrôlée par le dev. Ne la construis jamais à partir d’entrées utilisateur. exclude vit lui aussi uniquement dans le SDK complet (le snippet minimal ≤ 1 kB ne l’embarque pas) : il requiert Mode::Sdk et lève une exception dans les autres modes plutôt que de laisser filer silencieusement des chemins qu’on croyait exclus.
new Options(
domain: 'exemple.fr',
mode: Mode::Sdk,
sampleRate: 0.5,
queryParams: ['utm_source'],
exclude: ['/app', '/account'],
scrubUrl: '(u) => u.split("?")[0]',
); spa). Le respect de Do-Not-Track est actif par défaut ; ne passe respectDnt: false (ou data-respect-dnt="false") que si tu sais exactement pourquoi.Options::fromArray(array $a): self construit les options depuis un tableau et accepte en plus les alias snake_case (exclude_localhost, not_found, file_extensions, sample_rate, track_query, query_params, respect_dnt, scrub_url, redact_routes, route_templates, route_template). C’est ce qui rend les bridges Laravel et Symfony configurables par fichier de config. Deux constantes évitent de recopier les URLs : Options::HOSTED_ORIGIN (https://taktlytics.com) et Options::HOSTED_ENDPOINT (https://taktlytics.com/api/event). Si tu assembles toi-même une balise inline, SnippetRenderer::neutralizeScriptClose(string $js): string est publique : elle échappe les fermetures de balise script contenues dans un bundle.
Masquage de routes
Le nettoyage des URLs retire la query et le hash, mais le chemin part en clair : /verify/abc123 transmet le jeton. Depuis la 0.6.0, trois options remplacent ces chemins par le motif de la route, avec les règles du SDK navigateur (voir Masquage de routes). Elles vivent dans le SDK complet et requièrent Mode::Sdk : poser redactRoutes ou routeTemplates dans un autre mode lève une InvalidArgumentException à la construction du SnippetRenderer, puisque le snippet minimal ne sait pas masquer. Mode::Sdk charge @vskstudio/takt-core 0.10.0, première version qui gère ces options.
new Options(
domain: 'exemple.fr',
mode: Mode::Sdk,
redactRoutes: ['/verify/{token}', '/reset/:code', '/factures/[id].pdf'],
); redactRoutes liste les routes qui portent un secret. Un chemin qui correspond à l’un des motifs part sous la forme du motif, tous les autres gardent leur chemin réel. Motifs acceptés :
| Syntaxe | Exemple | Sens |
|---|---|---|
[p] | /verify/[token] | Un segment |
[[p]] | /blog/[[page]] | Segment optionnel |
[...p] | /docs/[...rest] | Reste du chemin |
(groupe) | /(app)/verify/[token] | Groupe ignoré |
:p, :p? | /reset/:code | Segment, optionnel avec ? |
*, ** | /files/** | Joker de segment, reste du chemin |
{p}, {p?} | /users/{id} | Syntaxe Laravel / Symfony, optionnel avec ? |
{p<regex>} | /users/{id<\d+>} | Segment qui doit satisfaire la regex, voir Contraintes ci-dessous |
Un segment peut mêler texte et paramètre ([id].pdf). Le motif envoyé est normalisé : les groupes disparaissent, ainsi que les contraintes et valeurs par défaut ({id<\d+>?1} devient {id}).
Contraintes : {id<\d+>} ne correspond qu’à un segment entier qui satisfait la regex, sans franchir / et sans tenir compte de la casse. La regex suit une grammaire restreinte, pas PCRE : alternation au premier niveau seulement, littéraux, ., \d \D \w \W \s \S, classes [...] sans imbrication, quantificateurs * + ? {n} {n,} {n,m} bornés à 100, sans quantificateur paresseux ni possessif, au plus deux quantificateurs variables et 100 caractères au plus. Les groupes, lookarounds, rétroréférences, ancres et / sont refusés. \d, \w et \s ne lisent que l’ASCII, et la regex compte des octets, pas des caractères.
Une route est inutilisable dans ces cas :
- une contrainte sort de la grammaire ou de ses bornes, un segment porte plus d’un paramètre contraint, ou un
{ou}reste orphelin dans un segment ; - son degré d’ambiguïté dépasse 1. Un segment compte ses variables moins 1, avec un plancher à zéro : chaque paramètre sans contrainte compte une variable, et un paramètre contraint compte les quantificateurs variables de sa regex. Un segment optionnel (
/{a?}) compte une variable de plus avant ce calcul. On somme les segments :/{a?}/{b}{c}donne 1 pour{a?}et 1 pour{b}{c}, soit 2, donc elle est refusée ;/{a?}/{b}donne 1 et passe ; - elle contient plus d’un segment REST (
**,*nom,:nom*,:nom+,[...nom]).
Exemples : /{a}{b}/{c} est acceptée, /{a?}/{b} aussi, alors que /{a}{b}/{c}{d}, /{p<a*a*>}/{q}{r} et /{a?}/{b}{c} sont refusées. /a/**/b est acceptée, /a/**/b/** est refusée. Une route inutilisable masque en /[redacted] tout chemin de même forme ; les autres chemins ne sont pas touchés. Un segment contraint de plus de 256 octets est masqué avant la vérification.
Le SDK navigateur ne lit pas la syntaxe à accolades : le renderer lui transmet {token} sous la forme [token] et {page?} sous la forme [[page]]. Une pageview du navigateur remonte donc /verify/[token], alors qu’un événement envoyé par le client serveur Takt remonte /verify/{token}. Écris le motif entre crochets si les deux doivent tomber sur la même ligne du classement Pages.
new Options(
domain: 'exemple.fr',
mode: Mode::Sdk,
routeTemplates: true,
routeTemplate: '/users/{id}',
); routeTemplates: true envoie chaque page sous la forme de son gabarit : /users/42 devient /users/{id}. PHP rend chaque page côté serveur, passe donc le gabarit de la route courante dans routeTemplate : il est émis comme résolveur constant (routeTemplate: () => "/users/{id}"), sous forme canonique ({id?} devient {id}). Ce gabarit n’est pas traduit en crochets : avec routeTemplates, la pageview du navigateur et l’événement serveur remontent tous deux /users/{id}. Sans routeTemplate, redactRoutes s’applique quand même, sinon le chemin réel part.
Options::withRouteTemplate(?string $routeTemplate): self et SnippetRenderer::withRouteTemplate(?string $routeTemplate): self renvoient une copie qui porte le gabarit de la requête courante, sans reconstruire toute la configuration :
echo $renderer->withRouteTemplate($routeCourante)->render(); Les bridges Laravel et Symfony remplissent ce gabarit pour toi. Le plugin WordPress n’expose pas ces options.
Le masquage du snippet et celui du client serveur se règlent séparément : redactRoutes d’Options ne s’applique pas au client Takt, qui prend sa propre liste (voir Masquer les routes en S2S).
Takt : le client serveur-à-serveur
Takt poste des événements sur POST /api/event avec une clé d’API portant la permission events:write (préfixe takt_ik_), liée au site dont le domaine correspond exactement à domain. Sinon l’ingest répond 401. Idéal pour tracer des actions qui n’ont pas lieu dans le navigateur (webhook de paiement, job en file…).
L’endpoint du client S2S accepte l’origine (Options::HOSTED_ORIGIN, ou celle de ton proxy first-party) comme l’URL de collecte complète (Options::HOSTED_ENDPOINT) : /api/event n’est ajouté que s’il manque, jamais en double. Il doit être absolu.
use Vskstudio\Takt\Options;
use Vskstudio\Takt\Revenue;
use Vskstudio\Takt\Takt;
// Forwarde l'IP + l'User-Agent du visiteur réel pour l'attribution
// (adapte la lecture de la requête à ton framework) :
$takt = (new Takt(
endpoint: Options::HOSTED_ORIGIN, // https://taktlytics.com
domain: 'exemple.fr',
apiKey: $_ENV['TAKT_API_KEY'],
))->withVisitor($_SERVER['REMOTE_ADDR'] ?? null, $_SERVER['HTTP_USER_AGENT'] ?? null);
$takt->event('Signup', ['plan' => 'pro'], null, 'https://exemple.fr/inscription');
$takt->event(
'Purchase',
['plan' => 'pro'],
new Revenue(amount: '29.00', currency: 'EUR'),
'https://exemple.fr/merci',
);
$takt->pageview('https://exemple.fr/merci'); __construct(string $endpoint, string $domain, ?string $apiKey = null, ?ClientInterface $httpClient = null, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null, array $redactRoutes = [], bool $redactUndecodablePaths = true):$httpClient,$requestFactoryet$streamFactoryinjectent le transport PSR (indispensable pour tester) ; laissés ànull, ils sont découverts.$redactRoutesliste les routes sensibles, voir Masquer les routes en S2S.$redactUndecodablePaths(défauttrue) règle les chemins illisibles, voir Chemins illisibles.withVisitor(?string $ip, ?string $userAgent): self: lie le visiteur réel avant l’envoi (renvoie une instance depuis laquelle envoyer)event(string $name, array $props = [], ?Revenue $revenue = null, ?string $url = null, ?string $referrer = null, ?string $route = null): void:$propsest unarray<string,scalar>dont chaque valeur est castée en chaîne (truepart en'1')pageview(?string $url = null, ?string $referrer = null, ?string $route = null): voidwithRoute(string|\Closure|null $route): self: gabarit de route par défaut de tous les appels du clone renvoyéstrict(): self: renvoie un clone qui lève sur erreur de transport ou sur toute réponse ≠202(pratique en test)- Fire-and-forget : en mode par défaut, erreurs de transport comme réponses d’erreur sont avalées (l’analytics ne doit jamais casser l’app). Un
202signifie « accepté », pas « enregistré » : un User-Agent classé bot ou un opt-out (DNT / GPC) est jeté côté serveur, avec un202malgré tout.
$url en URL absolue http(s) pour rattacher l'événement à la bonne page. Omise, l'événement est rattaché à la page d'accueil du site (https:// suivi de domain, puis /) : c'est utile pour un hook sans contexte de page (achat WooCommerce, job en file), mais toutes ces actions tombent alors sur la même page.withVisitor(), les events S2S sont attribués au serveur applicatif, pas au visiteur réel. Forwarde l'IP / l'User-Agent depuis la requête courante.Revenue est un value object en lecture seule (amount, currency) : le montant est une chaîne décimale, la devise un code à 3 lettres majuscules (miroir du SDK JS). Les deux sont validés à la construction : '29,00' ou 'eur' lèvent une InvalidArgumentException.
Masquer les routes en S2S
Le client Takt masque les routes avec les mêmes motifs que le snippet (voir Masquage de routes), mais il se règle à part :
redactRoutes, passé au constructeur, s’applique à chaque appel : l’URL de la page et le referrer du même site partent sous la forme du motif quand ils correspondent.redactUndecodablePaths, le dernier argument du constructeur (depuis 0.7.0, défauttrue), règle le sort des chemins illisibles : voir Chemins illisibles. Il ne règle que les envois serveur : le snippet rendu par PHP garde le défaut, masqué.- L’argument
routedepageview()etevent()envoie cet appel sous le gabarit fourni. withRoute()renvoie un clone dont tous les appels partent sous un gabarit par défaut. Il prend une chaîne, ou une closure évaluée à l’envoi (une closure qui lève ou ne renvoie pas de chaîne est ignorée). Unrouteexplicite prime sur ce défaut.
$takt = new Takt(
endpoint: Options::HOSTED_ORIGIN,
domain: 'exemple.fr',
apiKey: $_ENV['TAKT_API_KEY'],
redactRoutes: ['/verify/{token}'],
);
$takt->pageview('https://exemple.fr/verify/abc123');
$takt->event('Verified', url: 'https://exemple.fr/users/42', route: '/users/{id}');
$parRequete = $takt->withRoute(fn (): ?string => $router->currentTemplate()); La première URL part en https://exemple.fr/verify/{token}, la deuxième en https://exemple.fr/users/{id}. Côté serveur, les accolades ne sont pas traduites en crochets. Avec un gabarit (route ou withRoute()), un referrer du même site est réduit à l’origine, puisque son gabarit n’est pas connu.