Symfony Bundle
Automatic server-side page views via kernel.terminate and semantic configuration. Compatible with Symfony 6.4 and 7, PHP ≥ 8.1.
Installation
composer require quiet-metrics/symfony-metrics
symfony-metrics is not on Packagist yet. Declare its GitHub repository (access required); the core package comes from Packagist:
{ "repositories": [ { "type": "vcs", "url": "https://github.com/Quiet-Metrics/symfony-metrics" } ] }
Without Symfony Flex, register the bundle:
// config/bundles.php return [ // … QuietMetrics\Symfony\QuietMetricsBundle::class => ['all' => true], ];
Configuration
# config/packages/quiet_metrics.yaml quiet_metrics: public_key: '%env(QUIET_METRICS_PUBLIC_KEY)%' # the site's public key (required) secret_key: '%env(QUIET_METRICS_SECRET_KEY)%' # optional: signs every send (HMAC) # endpoint: 'https://quietmetrics.dev/api/v1/collect' # core SDK default # auto_pageview: false # disables the automatic page view
Per environment, override in config/packages/test/quiet_metrics.yaml or via environment variables; without a valid public key, nothing is sent.
Automatic page views
The listener hooks into kernel.terminate: the response has already been sent to the visitor, so the send adds no perceived latency. Only HTML responses to successful GET requests are counted (no XHR, no errors, no JSON APIs): your REST endpoints do not pollute the statistics.
auto_pageview: false turns the listener off: you keep the client for manual sends (mixed sites, selective tracking).
Events
Inject the client (public service):
use QuietMetrics\Client; final class CheckoutController { public function __construct(private readonly Client $quietMetrics) {} public function confirm(): Response { // … $this->quietMetrics->event('purchase', ['amount' => 49, 'plan' => 'pro']); } }
From a console command or a Messenger worker, there is no current request: pass the URL explicitly, otherwise nothing is sent (silent drop):
$this->quietMetrics->event('import-completed', ['rows' => 1200], [ 'url' => 'https://mysite.com/imports', ]);
Edge cases
FrankenPHP, RoadRunner, persistent workers. The listener reads each request's Request object, never the superglobals. Nothing to configure.
Reverse proxy / CDN. The listener uses Request::getClientIp(): configure framework.trusted_proxies as usual and the visitor's real IP will follow. The SDK's trust_proxy_headers option only applies to usage outside the framework.
Tests. Replace the service with a stub to verify your events without any network:
# config/services_test.yaml services: QuietMetrics\Client: class: App\Tests\Double\NullQuietMetricsClient
Robustness contract
Non-blocking sends (fire-and-forget socket, short cURL fallback), silent failures: analytics never breaks the host application, even if the platform is unreachable.
Can't find your answer?
Support replies on business days.