takt

Laravel

Le bridge Laravel vskstudio/takt-laravel câble le cœur PHP dans le conteneur : une directive Blade pour le snippet, une façade pour le S2S et une config publiable. 0.6.x, PHP 8.1+, Laravel 10 / 11 / 12.

composer require vskstudio/takt-laravel

Le TaktServiceProvider est auto-découvert : SnippetRenderer (rendu du snippet) et Takt (client S2S) sont liés dans le conteneur d’après la config, et l’IP / l’User-Agent sont forwardés au client S2S lorsqu’une requête HTTP est disponible.

Cette page est la référence des options. Pour l’intégration de bout en bout et son piège principal, l’attribution des envois S2S émis hors requête visiteur (job en file, commande Artisan ou webhook) : voir le guide analytics sans cookie dans une application Laravel.

Configuration

php artisan vendor:publish --tag=takt-config

takt-config est le seul tag publiable du paquet. config/takt.php lit les variables d’environnement :

TAKT_DOMAIN=exemple.fr
TAKT_API_KEY=tk_…               # requis seulement pour le S2S
TAKT_MODE=inline                # inline (défaut) | cdn | asset | sdk
TAKT_ENDPOINT=https://taktlytics.com
TAKT_SCRIPT_ORIGIN=https://analytics.exemple.fr  # origine first-party servant le tracker
TAKT_EXCLUDE_LOCALHOST=false    # défaut : true, rien n'est mesuré depuis localhost
TAKT_NONCE=…                    # nonce CSP de la balise script
TAKT_OUTBOUND=true
TAKT_FILES=true
TAKT_TAGGED=true
TAKT_NOT_FOUND=true
TAKT_FILE_EXTENSIONS=pdf,zip,docx

# Options avancées: laisser vide garde le défaut du tracker
TAKT_SAMPLE_RATE=0.5            # n'envoie qu'une fraction (0 à 1) des hits
TAKT_TRACK_QUERY=true           # conserve la query string + le hash (défaut : strippés)
TAKT_QUERY_PARAMS=utm_source,utm_medium  # allowlist quand track_query est off
TAKT_EXCLUDE=/app,/account      # préfixes de chemin jamais suivis: requiert TAKT_MODE=sdk
TAKT_RESPECT_DNT=false          # cesse de respecter Do-Not-Track
TAKT_ENABLED=false              # coupe-circuit : snippet no-op
TAKT_REDACT_ROUTES=/verify/{token}   # routes sensibles, requiert TAKT_MODE=sdk
TAKT_ROUTE_TEMPLATES=true       # chaque page en gabarit de route, requiert TAKT_MODE=sdk
TAKT_REDACT_UNDECODABLE_PATHS=false  # chemins illisibles comparés aux routes, événements serveur seulement (défaut `true`)

TAKT_EXCLUDE_LOCALHOST vaut true par défaut : en développement local, rien n’est mesuré tant que tu ne l’as pas passé à false.

Modes de rendu

TAKT_MODE choisit la source du runtime navigateur :

TAKT_MODERendu
inline (défaut)Le bundle takt.auto.js vendoré est embarqué dans une balise <script> inline : aucune requête en plus, mais du JS inline dans chaque page. Sous CSP stricte, renseigne TAKT_NONCE
cdn<script defer src="https://cdn.jsdelivr.net/npm/@vskstudio/[email protected]/dist/takt.auto.js">
asset<script defer src="/takt/takt.auto.js"> : copie auto-hébergée, préfixée de TAKT_SCRIPT_ORIGIN si l’origine est définie
sdk<script type="module">import{init}…;init({…})</script> : SDK complet, chargé depuis jsDelivr ou depuis /takt/takt.esm.js sur TAKT_SCRIPT_ORIGIN

TAKT_OUTBOUND, TAKT_FILES, TAKT_TAGGED et TAKT_NOT_FOUND ajoutent chacun un token (outbound, downloads, tagged, 404) à l’unique attribut data-auto que lit le bundle, en modes inline, cdn et asset ; en mode sdk ce sont des clés booléennes (outbound, files, tagged, notFound) de l’objet passé à init().

Le paquet ne publie que sa configuration : aucune commande ne dépose le bundle dans public/. Pour TAKT_MODE=asset, copie vendor/vskstudio/takt-core-php/resources/takt.auto.js vers public/takt/takt.auto.js, par exemple depuis un script post-update-cmd de ton composer.json, pour que la copie suive les mises à jour du paquet.

Options réservées au mode sdk

TAKT_SCRUB_URL (une fonction JS brute de réécriture d’URL, injectée verbatim dans la page, à réserver au dev : ne la construis jamais à partir d’entrées utilisateur) et TAKT_EXCLUDE n’existent que dans le SDK complet. Posées dans un autre mode, elles ne sont pas ignorées : la construction du SnippetRenderer lève une InvalidArgumentException, donc une 500 sur toutes les pages qui rendent le snippet.

TAKT_MODE=sdk
TAKT_SCRUB_URL="(u) => u.split('#')[0]"
TAKT_EXCLUDE=/app,/account

TAKT_REDACT_ROUTES et TAKT_ROUTE_TEMPLATES suivent la même règle, voir Masquage de routes.

Endpoint et origine first-party

TAKT_ENDPOINT alimente deux consommateurs : le snippet rendu par @takt, qui a besoin de l’URL de collecte complète, et la façade Takt, qui a besoin de l’origine à laquelle elle ajoute /api/event. Le paquet accepte donc les deux formes et les normalise pour chacun, si bien que ces deux réglages sont équivalents :

TAKT_ENDPOINT=https://taktlytics.com
TAKT_ENDPOINT=https://taktlytics.com/api/event
TAKT_ENDPOINTLe snippet POSTe surLa façade POSTe sur
https://taktlytics.com (défaut du paquet)https://taktlytics.com/api/eventhttps://taktlytics.com/api/event
https://taktlytics.com/api/eventhttps://taktlytics.com/api/eventhttps://taktlytics.com/api/event
/collect (proxy same-origin)/collect 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.

TAKT_SCRIPT_ORIGIN est l’origine first-party que tu proxifies vers Takt pour esquiver les bloqueurs. Elle est rendue en data-script-origin et sert le fichier du tracker en modes asset et sdk. Avec l’endpoint hébergé (sous l’une ou l’autre forme ci-dessus), aucun endpoint n’est rendu et le tracker dérive le point d’ingestion de cette origine, suivie de /api/event. Avec toute autre valeur, data-endpoint est rendu et prime sur l’origine, qui ne sert alors qu’à charger le fichier.

Directive Blade @takt

Place @takt dans le <head> de ton layout pour rendre le snippet :

<head>
  <meta charset="utf-8">
  @takt
</head>

La directive ne prend pas d’argument : elle compile un appel fixe à SnippetRenderer::render(), et toute expression écrite entre parenthèses est ignorée sans erreur. Le snippet est donc rendu à partir de la seule configuration.

Pour un rendu différent, typiquement un nonce CSP propre à chaque requête, rebinde le renderer avant le rendu de la vue, depuis un middleware. Avec TAKT_ROUTE_TEMPLATES, reporte le gabarit de la route courante :

use Illuminate\Support\Facades\App;
use Vskstudio\Takt\Laravel\RouteTemplate;
use Vskstudio\Takt\Options;
use Vskstudio\Takt\SnippetRenderer;

App::bind(SnippetRenderer::class, fn () => new SnippetRenderer(Options::fromArray([
    'nonce' => $nonce,
    'scriptOrigin' => config('takt.script_origin'),
    'route_template' => RouteTemplate::of(request()),
] + config('takt'))));

Options::fromArray() lit les autres clés en snake_case, comme la config (route_template compris) ; scriptOrigin est la seule attendue en camelCase.

Façade Takt (serveur-à-serveur)

use Vskstudio\Takt\Laravel\Facades\Takt;
use Vskstudio\Takt\Revenue;

Takt::event('Signup', ['plan' => 'pro']);

Takt::event('Purchase', ['plan' => 'pro'], new Revenue(amount: '29', currency: 'EUR'));

Takt::pageview('https://exemple.fr/merci');
  • event(string $name, array $props = [], ?Revenue $revenue = null, ?string $url = null, ?string $referrer = null, ?string $route = null): void
  • pageview(?string $url = null, ?string $referrer = null, ?string $route = null): void
  • withRoute(string|\Closure|null $route): Takt : gabarit de route par défaut, voir Masquage de routes
  • Sans $url, l’événement est rattaché à la page d’accueil du site (https:// suivi de domain, puis /).
  • Fire-and-forget : un 202 vaut succès ; par défaut les erreurs de transport et les statuts non-202 sont avalés, l’analytics ne devant jamais casser l’app. Aucune exception ne remonte. Valide l’ingestion depuis le dashboard. Takt::strict() renvoie une instance qui lève sur non-202 : réserve-la aux tests.

La façade résout le service Takt du conteneur, lié par requête. L’IP et l’User-Agent proviennent de la requête courante via $request->ip() et $request->userAgent().

Derrière un proxy ou un load-balancer, configure les proxys de confiance (App\Http\Middleware\TrustProxies en Laravel 10, trustProxies() dans bootstrap/app.php en Laravel 11 / 12) : sans cela, Takt reçoit l'IP du proxy et toute l'audience S2S est attribuée à une poignée d'adresses d'infrastructure. Hors requête HTTP (job en file, commande Artisan, webhook), il n'y a pas de visiteur à forwarder : passe l'IP et l'User-Agent réels toi-même avec withVisitor().

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 réglages remplacent les chemins réels par le motif de la route, dans le snippet @takt comme dans la façade Takt (voir Masquage de routes). Côté snippet, ils requièrent TAKT_MODE=sdk : dans un autre mode, la construction du SnippetRenderer lève une InvalidArgumentException, donc une 500 sur toutes les pages qui rendent le snippet, puisque le snippet minimal ne sait pas masquer.

Clé de configVariable d’environnementDéfautRôle
redact_routesTAKT_REDACT_ROUTES (liste séparée par des virgules)[]Routes sensibles envoyées sous la forme du motif
route_templatesTAKT_ROUTE_TEMPLATESfalseEnvoie chaque page sous la forme de son gabarit de route Laravel
redact_undecodable_pathsTAKT_REDACT_UNDECODABLE_PATHStrueChemins illisibles comparés aux routes au lieu de /[redacted] ; événements serveur seulement

redact_routes

TAKT_MODE=sdk
TAKT_REDACT_ROUTES=/verify/{token},/reset/{code}

Ou directement dans config/takt.php :

'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 Laravel ({token}, {page?}) comme [p], [[p]], [...p], (groupe), :p, :p?, * et ** (détail sur la page cœur PHP).

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 la façade remonte /verify/{token}. Écris /verify/[token] si les deux doivent tomber sur la même ligne.

redact_undecodable_paths

TAKT_REDACT_UNDECODABLE_PATHS=false

Ou dans config/takt.php (paquet 0.7.0 et suivants) : 'redact_undecodable_paths' => false,. Par défaut à true, un chemin illisible part en /[redacted] dès qu’une route est configurée. Avec false, il est comparé aux routes comme les autres (voir Chemins illisibles). Seules les valeurs false, off, 0, no et (false) coupent l’option : une valeur vide, null ou inconnue la laisse active. Le réglage ne concerne que la façade Takt (événements serveur) ; le snippet @takt garde le défaut, masqué.

route_templates

TAKT_MODE=sdk
TAKT_ROUTE_TEMPLATES=true

Chaque page part sous la forme de son gabarit de route : /users/42 devient /users/{id}. Le paquet lit le gabarit de la route qui a répondu à la requête courante ($request->route()->uri()), le rend dans le snippet @takt et s’en sert comme route par défaut de chaque Takt::pageview() et Takt::event() émis pendant cette requête. Ce gabarit 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.

Gabarit explicite et RouteTemplate

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

use Vskstudio\Takt\Laravel\Facades\Takt;

Takt::event('Verified', url: url()->current(), route: '/verify/{token}');

Takt::withRoute('/factures/{id}')->pageview(url()->current());

Takt::withRoute() renvoie une instance dont tous les appels partent sous ce gabarit. Il accepte aussi une closure, évaluée à l’envoi.

Vskstudio\Takt\Laravel\RouteTemplate::of(?Request $request): ?string renvoie le gabarit de la route d’une requête (/users/{id}), ou null sans route correspondante. C’est la fonction qu’utilise le paquet ; sers-t’en si tu rebindes toi-même SnippetRenderer (clé route_template d’Options::fromArray(), voir la directive Blade) ou Takt (withRoute()), sinon ces bindings perdent le gabarit.

Depuis la 0.6.0, le binding SnippetRenderer est scoped et non plus singleton : sous un worker longue durée comme Octane, le snippet rendu suit la route de la requête courante au lieu de garder celle de la première requête servie.