takt

Symfony

Le bridge Symfony vskstudio/takt-symfony expose le cœur PHP sous forme de bundle : une fonction Twig pour le snippet et un service Takt autowiré pour le S2S. Compatible Symfony 6.4 / 7.x, PHP 8.1+.

composer require vskstudio/takt-symfony

Le paquet n’expose pas de recette Flex : enregistre le bundle à la main.

// config/bundles.php
return [
    // …
    Vskstudio\Takt\Symfony\TaktBundle::class => ['all' => true],
];

Le TaktBundle enregistre ensuite ses services depuis la config sémantique : le SnippetRenderer derrière la fonction Twig, et le client S2S Takt. Ce dernier est RequestStack-aware : il forwarde l’IP et l’User-Agent de la requête courante pour l’attribution.

Cette page est la référence des options. Pour l’intégration de bout en bout et ses pièges (placement du snippet sous Turbo, arbitrage contrôleur de retour / webhook pour les conversions) : voir le guide analytics sans cookie dans une application Symfony.

Configuration

config/packages/takt.yaml :

takt:
    domain: 'exemple.fr'
    endpoint: 'https://taktlytics.com'
    api_key: '%env(TAKT_API_KEY)%'                 # S2S uniquement: omets la clé sinon
    mode: inline
    outbound: true
    files: true
    tagged: true
    not_found: true
    file_extensions: ['pdf', 'zip']

Un %env(...)% doit correspondre à une variable déclarée : sans elle, le conteneur lève EnvNotFoundException sur toutes les pages qui rendent le snippet.

# .env
TAKT_API_KEY=tk_…

Référence des clés

CléDéfautRôle
domain''Domaine du site (attribut data-domain, et domaine porté par chaque event S2S)
endpointhttps://taktlytics.comCible d’ingestion : l’origine ou l’URL de collecte complète, voir ci-dessous
script_originnullOrigine first-party qui sert le runtime et dont l’endpoint est dérivé (contourne les ad-blockers) ; endpoint prime
api_keynullClé d’API scope=ingest, utilisée uniquement par le client S2S
modeinlineSource du runtime : inline, cdn, asset ou sdk
outboundfalseSuit les liens sortants (token outbound)
filesfalseSuit les téléchargements (token downloads)
taggedfalseSuit les éléments marqués data-takt-event (token tagged)
not_foundfalseSuit les pages 404 (token 404)
file_extensions[]Restreint les téléchargements comptés ; vide = liste par défaut du tracker
exclude_localhosttrueIgnore les hits émis depuis localhost : la raison la plus fréquente d’un « rien ne remonte » en dev
noncenullNonce CSP posé sur la balise <script> (indispensable en mode: inline sous CSP stricte)
redact_routes[]Routes sensibles envoyées sous la forme du motif, requiert mode: sdk (voir Masquage de routes)
route_templatesfalseEnvoie chaque page sous la forme du chemin de sa route, requiert mode: sdk
redact_undecodable_pathstrueChemins illisibles comparés aux routes au lieu de /[redacted] ; événements S2S seulement
sample_ratenullN’envoie qu’une fraction (0 à 1) des hits
track_querynullConserve la query string + le hash (défaut : strippés)
query_params[]Allowlist de paramètres conservés quand track_query est off
exclude[]Préfixes de chemins jamais suivis (requiert mode: sdk)
respect_dntnullfalse cesse de respecter Do-Not-Track
enablednullfalse = coupe-circuit, snippet no-op
scrub_urlnullFonction JS brute de réécriture d’URL (requiert mode: sdk)

Les cinq dernières options avancées valent null par défaut (« non définie » : le défaut du tracker s’applique) ; ne les pose que pour une valeur non-défaut, sample_rate: 0.5 jette la moitié du trafic et enabled: false coupe toute la mesure.

Les modes :

  • inline : embarque le bundle takt.auto.js vendoré dans la balise (zéro requête en plus).
  • cdn : loader <script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js">.
  • asset : loader vers /takt/takt.auto.js, une copie que tu héberges toi-même (voir le callout en fin de page).
  • sdk : <script type="module"> qui boote le SDK complet.

mode n’est pas validé : une valeur inconnue retombe silencieusement sur inline.

La clé endpoint

La même clé alimente deux consommateurs : le snippet Twig, qui a besoin de l’URL de collecte complète, et le service Takt (S2S), qui a besoin de l’origine à laquelle il ajoute /api/event. Le bundle accepte donc les deux formes et les normalise pour chacun :

Valeur d’endpointLe snippet POSTe surLe service S2S POSTe sur
https://taktlytics.com (défaut)https://taktlytics.com/api/eventhttps://taktlytics.com/api/event
https://taktlytics.com/api/eventhttps://taktlytics.com/api/eventhttps://taktlytics.com/api/event
/api/event (proxy same-origin)/api/event sur ton domainechemin relatif : aucun envoi n’aboutit

Une valeur qui porte un autre chemin est prise telle quelle comme URL de collecte : c’est le cas d’un proxy first-party servi depuis ton propre domaine. Un chemin relatif ne convient qu’au snippet ; si tu envoies aussi des événements serveur, garde une URL absolue.

script_origin ne sert à dériver l’endpoint du snippet qu’avec l’endpoint hébergé (sous l’une ou l’autre forme ci-dessus) : le tracker POSTe alors sur {script_origin}/api/event. Avec toute autre valeur, l’endpoint est émis en data-endpoint et prime.

mode: sdk

mode: sdk charge le SDK en module ES. C’est le seul mode capable d’exprimer scrub_url, une fonction JS brute de réécriture d’URL injectée verbatim dans la page, et exclude, les préfixes de chemins jamais suivis. Le snippet minimal n’embarque ni l’un ni l’autre : les poser dans un autre mode lève une exception au rendu plutôt que de les laisser filer silencieusement. Réserve scrub_url au dev : ne la construis jamais à partir d’entrées utilisateur.

takt:
    mode: sdk
    exclude: ['/app', '/compte']
    scrub_url: "(u) => u.split('#')[0]"

redact_routes et route_templates suivent la même règle, voir Masquage de routes.

Fonction Twig takt()

Rends le snippet dans le <head> de ton template de base :

<head>
    <meta charset="utf-8">
    {{ takt() }}
</head>

takt() ne prend aucun argument : l’autocapture (outbound, files, tagged, not_found) et toutes les autres options se règlent uniquement dans config/packages/takt.yaml. Un argument passé depuis un template est ignoré sans erreur. Il n’y a pas de surcharge par template.

Service Takt autowiré (serveur-à-serveur)

Type-hinte simplement Vskstudio\Takt\Takt, l’autowiring fait le reste :

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Vskstudio\Takt\Revenue;
use Vskstudio\Takt\Takt;

final class CheckoutController
{
    public function __construct(private readonly Takt $takt) {}

    public function success(): Response
    {
        $this->takt->event('Purchase', ['plan' => 'pro'], new Revenue(amount: '29', currency: 'EUR'));

        return new Response('Merci');
    }
}

L’envoi est fire-and-forget : un 202 vaut succès, et les erreurs de transport sont avalées pour ne jamais casser la requête. $this->takt->strict() renvoie une instance qui lève sur tout statut différent de 202 (utile en test). pageview() et withVisitor() sont détaillés sur la page cœur PHP.

Le service dérive l'IP du visiteur via Request::getClientIp(), qui n'honore X-Forwarded-For que si framework.trusted_proxies est configuré. Derrière un CDN ou un reverse proxy non déclaré, tous les events S2S sont attribués à l'IP du proxy.
En mode: asset, le loader pointe vers /takt/takt.auto.js, mais le bundle ne publie aucun asset : copie toi-même vendor/vskstudio/takt-core-php/resources/takt.auto.js dans public/takt/takt.auto.js. En mode: sdk avec script_origin, le module est chargé depuis /takt/takt.esm.js sur cette origine. Ce fichier n'est pas vendoré non plus, c'est à toi de le servir.

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 (cœur PHP 0.6), deux nœuds de configuration remplacent les chemins réels par le motif de la route, dans le snippet takt() comme dans le service Takt autowiré (voir Masquage de routes). Côté snippet, ils requièrent mode: sdk : dans un autre mode, le SnippetRenderer lève une exception, puisque le snippet minimal ne sait pas masquer.

redact_routes

takt:
    mode: sdk
    redact_routes: ['/verify/{token}', '/reset/{code}']

Un chemin qui correspond à l’un des motifs part sous la forme du motif, tous les autres gardent leur chemin réel. Les motifs acceptent la syntaxe Symfony ({token}, {page?}) comme [p], [[p]], [...p], (groupe), :p, :p?, * et ** (détail sur la page cœur PHP). Les contraintes et valeurs par défaut en ligne sont retirées du motif envoyé ({id<\d+>?1} devient {id}).

Le SDK navigateur ne lit pas les accolades : {token} lui est transmis sous la forme [token]. Une pageview du navigateur remonte donc /verify/[token], alors qu’un événement envoyé par le service Takt remonte /verify/{token}. Écris /verify/[token] si les deux doivent tomber sur la même ligne.

route_templates

takt:
    mode: sdk
    route_templates: true

Chaque page part sous la forme du chemin de sa route : /users/42 devient /users/{id}. Le bundle enregistre alors un service privé Vskstudio\Takt\Symfony\RouteTemplate, qui résout le _route de la requête principale via le routeur (Route::getPath()). Ce chemin est rendu dans le snippet takt() et sert de route par défaut à chaque pageview() et event() du service Takt autowiré. Il n’est pas traduit en crochets : navigateur et serveur remontent tous deux /users/{id}. Une requête sans route correspondante garde son chemin réel, toujours soumis à redact_routes.

route_templates convient à une application privée ; sur un site public, il fusionne tous tes articles en une seule ligne du classement Pages.

Résoudre le chemin d'une route charge la collection de routes du routeur. Sous PHP-FPM, où rien ne persiste d'une requête à l'autre, ce mode ajoute donc un petit coût à chaque requête qui rend le snippet ou envoie un event S2S.

redact_undecodable_paths

takt:
    redact_undecodable_paths: false

Booléen, true par défaut, depuis le bundle 0.7.0 : un chemin illisible (échappement % mal formé ou UTF-8 invalide une fois décodé) part en /[redacted] dès qu’une route est configurée. Avec false, il est comparé aux routes comme les autres (voir Chemins illisibles). Le nœud ne règle que le service Takt autowiré (événements serveur) ; le snippet takt() garde le défaut, masqué.

Gabarit explicite

Chaque appel serveur peut choisir son propre gabarit, qui prime sur le défaut :

$this->takt->event('Verified', url: $request->getUri(), route: '/verify/{token}');

$this->takt->withRoute('/factures/{id}')->pageview($request->getUri());

withRoute() renvoie une instance dont tous les appels partent sous ce gabarit ; il accepte aussi une closure, évaluée à l’envoi. Détail des arguments sur la page cœur PHP.