PHPackages                             quiet-metrics/symfony-metrics - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. quiet-metrics/symfony-metrics

ActiveSymfony-bundle

quiet-metrics/symfony-metrics
=============================

Bundle Symfony du SDK Quiet Metrics (La Boîte à Code) : pageviews serveur automatiques via kernel.terminate et configuration sémantique.

v0.3.0(today)02↑2900%[1 PRs](https://github.com/Quiet-Metrics/symfony-metrics/pulls)MITPHPPHP &gt;=8.1CI failing

Since Jul 24Pushed todayCompare

[ Source](https://github.com/Quiet-Metrics/symfony-metrics)[ Packagist](https://packagist.org/packages/quiet-metrics/symfony-metrics)[ Docs](https://quietmetrics.dev)[ RSS](/packages/quiet-metrics-symfony-metrics/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (6)Versions (5)Used By (0)

quiet-metrics/symfony-metrics
=============================

[](#quiet-metricssymfony-metrics)

[![Quiet Metrics: Symfony bundle](art/banner.png)](art/banner.png)

> 🇫🇷 [Version française](README.fr.md)

Symfony bundle (6.4 and 7.x) for the [Quiet Metrics](https://quietmetrics.dev) 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
------------

[](#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

[](#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
-------------

[](#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
-----

[](#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
-------------------------

[](#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
----------------

[](#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
------------

[](#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: `GET` requests, 2xx responses, HTML `Content-Type`, excluding AJAX requests.
- The context is read from the `Request` object (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-Timestamp` and `X-QM-Signature` headers); the visitor IP and User-Agent carried by the SDK are then trusted on the collection side.

License
-------

[](#license)

MIT. A [La Boîte à Code](https://laboiteacode.fr) product for [Quiet Metrics](https://quietmetrics.dev).

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 88.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~11 days

Total

4

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7fd1bf4d557c8949ae533b8e84a5b90bf966d491397089abc0fb6e97cb3569cb?d=identicon)[Alexandre Ribes](/maintainers/Alexandre%20Ribes)

---

Top Contributors

[![RibesAlexandre](https://avatars.githubusercontent.com/u/818564?v=4)](https://github.com/RibesAlexandre "RibesAlexandre (15 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (2 commits)")

---

Tags

symfonyanalyticsprivacyrgpdserver-sidesans-cookies

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/quiet-metrics-symfony-metrics/health.svg)

```
[![Health](https://phpackages.com/badges/quiet-metrics-symfony-metrics/health.svg)](https://phpackages.com/packages/quiet-metrics-symfony-metrics)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
