ricbra/kissmetrics-bundle
Symfony bundle that integrates KISSmetrics analytics into your app, exposing the KISSmetrics client as a service so you can identify users and track events from controllers, services, or templates.
Read three files in this order before writing any code. The bundle is ~100 lines of glue, so a 10-minute read tells you everything:
DependencyInjection/Configuration.php — reveals the exact config root key (ricbra_kiss_metrics) and that the only real node is api_key (plus endpoint override).Resources/config/services.xml — gives you the service id you'll inject everywhere. Confirm whether an alias exists (e.g. ricbra.kiss_metrics vs ricbra_kiss_metrics) rather than trusting a blog post.Client class in ricbra/php-kissmetrics-api — this is the object you actually call. Its public surface is identify, alias, record, set, clear. Everything else in the bundle is wiring.Framework reality check first. This is a Symfony 2.x bundle, not a Laravel package. If you're on Laravel or Symfony 3+, Composer will refuse the constraint and the service container conventions differ. Do not fight it — treat the bundle as a reference implementation of how to wire the Kissmetrics client, and reproduce the wiring natively (see Implementation Patterns). Only install the bundle directly if you are on a genuine Symfony 2.x app.
Configure the key once, outside VCS.
# app/config/config.yml
ricbra_kiss_metrics:
api_key: "%kissmetrics_api_key%"
Put the value in parameters.yml / environment config. Never commit it.
First use case — record a signup. Inject the client (confirm the id from services.xml) into a listener or controller and emit an event:
$km->identify($user->getEmail());
$km->record('Signed Up', ['plan' => 'trial']);
Then open Client::record() in php-kissmetrics-api to see the actual _k/_p/_n parameters transmitted. Understanding that mapping early prevents schema surprises later — Kissmetrics treats _p (properties) as the payload, and you want to know exactly what leaves your server.
Inject, don't get(). For a Symfony 2 app, constructor-inject the service into your listeners/services. The underlying client is a plain object with no container dependencies:
app.registration_tracker:
class: AppBundle\Analytics\RegistrationTracker
arguments: ['@ricbra_kiss_metrics']
Wrap the client behind a domain-oriented tracker. The raw API is a generic event recorder. Do not scatter $km->record(...) calls across controllers. Introduce one small class (App\Analytics\Tracker) exposing domain methods — signedUp(User $u), upgraded(User $u, $plan), trialExpired(User $u) — so consent checks, property normalization, logging, and a no-op driver for dev/test live in exactly one place. This is the single most valuable thing you'll do, because it isolates the one place that knows about Kissmetrics from the many places that know about your product.
Identify on login, alias on registration. The canonical flow:
alias($newIdentity) before identify($canonicalIdentity) so pre-conversion behavior is stitched to the account.identify($canonicalIdentity) (usually email or your stable user id) and then set([...]) for durable, slowly-changing properties (plan, company size, acquisition source).Keep the identity choice stable and boring. Changing it later invalidates cohorts.
Emit authoritative events server-side; use JS for enrichment only. Backend calls are the trustworthy channel for money- and lifecycle-related events (order completed, refund, upgrade, cancellation) because they can't be spoofed and aren't blocked by ad blockers. Let the client-side snippet handle pageviews and UI-level hints. Don't try to make the SDK do both jobs.
Run through a queue for anything on a hot path. A synchronous call inside a request couples user-facing latency to Kissmetrics' availability. In a legacy Symfony 2 app, dispatch a job (old-school JMSJobQueueBundle, a DB-backed queue table, or at minimum a kernel.terminate listener) and have the worker call the tracker. This is also where retry/backoff and idempotency keys belong — the SDK won't provide them.
Integration tips:
test and dev so the tracker never makes network calls in CI. Bind the real client only in prod.php-kissmetrics-api client behind your Tracker interface so it can be replaced by a direct HTTP adapter or a different vendor without touching call sites.The Symfony 2 constraint is a hard blocker, not a warning. On Symfony 3+ / Laravel, Composer will error on install. The bundle is too thin to justify forking for its service wiring alone — reimplement the ~50 lines natively (one service provider, one config file, one bind). If you're on Legacy Symfony 2, pin the dependency and document that decision; it is unmaintained (1 star, 0 dependents).
The config root key is easy to get wrong. It's ricbra_kiss_metrics (underscored), not ricbra_kissmetrics or ricbra.kiss_metrics. Read Configuration.php rather than guessing from the package name — this is the #1 first-hour trap.
Service id ambiguity. There may or may not be an alias. Always open services.xml and use the id you find there; a wrong id yields a confusing ServiceNotFoundException, not a useful hint.
The client is thin, so reliability is your job. The SDK sends HTTP and (at best) returns an error on failure. It does not give you retry/backoff, dead-letter handling, batching guarantees, or idempotency. Duplicate events on retry are a real risk — attach an idempotency key (e.g. event_id in properties) so Kissmetrics can reconcile, and put retries in your queue layer, not around the client call.
PII and consent are not handled. The wrapper sends whatever properties you give it. If you send raw emails, addresses, or free-text, they leave your boundary and land in a vendor's system. Redact/whitelist properties inside your Tracker, gate on consent at the boundary, and check the vendor DPA before sending anything sensitive. A bare SDK wrapper provides no deletion flow — plan for GDPR/CCPA erasure requests separately.
TLS and HTTP transport may be stale. Symfony 2-era code often sits on old Guzzle/cURL configs. Verify that outbound calls work on your runtime — modern PHP versions may reject legacy TLS negotiation or deprecate the transport. Test with a real staging call to a KISSmetrics endpoint, not just a unit test with a mock.
Failure modes to instrument. API downtime, rate limiting (429), invalid credentials (401), TLS/network errors, queue exhaustion (dropped events), and identity/alias mismatches. Add counters and alerts around queue depth and error rate — otherwise "the funnel looks wrong" becomes an expensive debugging exercise.
Debugging checklist when events vanish:
api_key correct and is the endpoint right (staging vs prod)?Client::record() at all, or did a wrapper/guard swallow it?_k/_p/_n values what Kissmetrics expects? Inspect outgoing parameters via a network log or a local HTTP interceptor.alias fire before identify? Wrong ordering produces orphaned identities that never merge in cohorts.clear() and drop identifying state before your event?Extension points are minimal — plan accordingly. You can swap the service binding, wrap the client, or provide your own implementation behind your Tracker interface, but don't expect the bundle itself to grow. Treat it as a starting scaffold; own the fork or the replacement. For a longer-lived integration, the correct end state is a small internal client (Laravel Http/Symfony HttpClient + a queued job, ~one engineer-day) that talks directly to the Kissmetrics HTTP API and lets you route to another vendor behind the same interface.
How can I help you explore Laravel packages today?