quiet-metrics / symfony-metrics
Bundle Symfony du SDK Quiet Metrics (La Boîte à Code) : pageviews serveur automatiques via kernel.terminate et configuration sémantique.
Package info
github.com/Quiet-Metrics/symfony-metrics
Type:symfony-bundle
pkg:composer/quiet-metrics/symfony-metrics
Requires
- php: >=8.1
- quiet-metrics/php-metrics: ^0.3
- symfony/config: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
- symfony/framework-bundle: ^6.4 || ^7.0
README
Symfony bundle (6.4 and 7.x) for the Quiet Metrics PHP SDK: audience measurement with no identification or tracking cookies, 100% server-side, unblockable by ad blockers. Page views are sent automatically on kernel.terminate, without JavaScript and without ever slowing the site down.
Installation
composer require quiet-metrics/symfony-metrics
With Symfony Flex, the bundle is registered automatically (symfony-bundle type). Without Flex, add it to config/bundles.php:
// config/bundles.php return [ // ... QuietMetrics\Symfony\QuietMetricsBundle::class => ['all' => true], ];
Before the Packagist release
symfony-metrics is not on Packagist yet, and its repository is private. Declare it (access required); the core package it depends on comes from Packagist:
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/Quiet-Metrics/symfony-metrics" }
]
}
composer require quiet-metrics/symfony-metrics:^0.3
Configuration
The configuration alias is quiet_metrics.
# config/packages/quiet_metrics.yaml quiet_metrics: public_key: '%env(QUIET_METRICS_PUBLIC_KEY)%' # site public key (required) secret_key: '%env(QUIET_METRICS_SECRET_KEY)%' # essential server-side: signs every hit (HMAC) # endpoint: 'https://quietmetrics.dev/api/v1/collect' # default: core SDK's Quiet Metrics SaaS endpoint # trust_proxy_headers: true # application behind a reverse proxy (X-Forwarded-For / X-Forwarded-Proto) # auto_pageview: false # disables the automatic pageview (manual events only)
# .env.local
QUIET_METRICS_PUBLIC_KEY=qm_pub_xxx
QUIET_METRICS_SECRET_KEY=qm_sec_xxx
Why the secret key matters. It enables signed mode, the only case where the visitor IP and User-Agent carried by your server are trusted. Without it, every hit is attributed to your server's IP: all your visitors would count as one.
Usage
Pageviews for successful HTML responses are sent on their own: nothing to do.
For custom events, inject the core SDK client (QuietMetrics\Client, wired by the bundle):
use QuietMetrics\Client; use Symfony\Component\HttpFoundation\Response; final class CheckoutController { public function __construct(private readonly Client $quietMetrics) {} public function confirm(): Response { $this->quietMetrics->event('purchase', ['amount' => 49, 'plan' => 'pro']); // ... } }
With auto_pageview: false, you keep control over page views:
// Context (URL, referrer, IP, User-Agent, language) inferred from the // current request, overridable key by key: $this->quietMetrics->pageview(); $this->quietMetrics->pageview(['url' => 'https://mysite.com/thank-you']);
Opting out of measurement
A visitor can ask to stop being counted, with no account and without writing to anyone: they visit a page of your site with ?qm_ignore=1, and ?qm_ignore=0 puts them back into measurement.
https://mysite.com/?qm_ignore=1 stop being counted
https://mysite.com/?qm_ignore=0 be counted again
The marker is a first-party cookie of your own site, named qm_ignore with the value 1 (path=/, samesite=lax, secure over https, five years). A dedicated OptOutListener takes care of it on the current request. It is registered whatever auto_pageview is set to: a refusal does not depend on a measurement option. Nothing to wire.
It holds no identifier (its value is the same for everyone), it is never transmitted to Quiet Metrics, and it exists only to stop measurement: it is an opt-out marker, not a tracker. The JS tracker additionally writes the same value to localStorage, but a server-side SDK only ever reads the cookie: one visit therefore covers both tracking modes.
Visit continuity
When the visitor fingerprint changes mid-visit (4G, then wifi), the same person would otherwise be counted as two unique visitors on the same day. A second first-party cookie of your own site closes that gap: qm_visit, value 1 (path=/, samesite=lax, secure over https), on a sliding ten-minute window pushed back by every measured hit. Each hit reports whether it was already there as the c key of the payload.
Its value is a constant, the same for everyone, so it identifies nobody: it only says that a visit is already under way in this browser. It is never written to someone who has set the opt-out marker, and never written when nothing is measured. A dedicated VisitListener writes it on kernel.response, on the very requests whose pageview TrackRequestListener sends on kernel.terminate. Unlike OptOutListener, it is registered only when auto_pageview is on: a refusal does not depend on a measurement option, but a measurement cookie does.
Note for cached sites: a measured response now carries a Set-Cookie header, which some reverse proxies and CDNs treat as a reason not to store the response.
How it works
- Sending happens on
kernel.terminate: the response has already reached the visitor, zero perceived latency. The core SDK client is itself non-blocking (write-and-forget socket, short-timeout cURL fallback, silent failures): analytics never breaks the host site. - The listener only counts real pages:
GETrequests, 2xx responses, HTMLContent-Type, excluding AJAX requests. - The context is read from the
Requestobject (never from superglobals): correct under RoadRunner and FrankenPHP, in tests, and aligned with the host application's trusted proxies. - With
secret_key, every send is HMAC-SHA256 signed (X-QM-TimestampandX-QM-Signatureheaders); the visitor IP and User-Agent carried by the SDK are then trusted on the collection side.
License
MIT. A La Boîte à Code product for Quiet Metrics.
