zumba/amplitude-php
A PHP client for Amplitude analytics that simplifies sending server-side events and user identification data to Amplitude's HTTP API. It offers a lightweight integration for Laravel and other PHP apps, with a simple API for tracking events and user properties.
Install via Composer and pull in the singleton:
composer require zumba/amplitude-php
use Zumba\Amplitude\Amplitude;
$amplitude = Amplitude::getInstance();
$amplitude->init('YOUR_API_KEY'); // once per process
$amplitude->logEvent('page_viewed', 'user-123', [
'path' => '/pricing',
'plan' => 'pro',
]);
Where to look first
README.md — the API surface is small; read the three usage examples end-to-end before touching any code.src/Amplitude.php — the public surface (init, logEvent, logEventAsync, queueEvent, identify, flush, setUserId, setDeviceId, setSessionId, setEventProperties, setUserProperties, reset). This is the only class most apps need.src/Queue/ (QueueInterface, SimpleQueue) — how batching works, and where you plug in a persistent queue.src/Response.php (or equivalent response object) — what the API actually returned; this is where "why did my event not show up in Amplitude?" gets answered.First use case to wire up: a single "user_signed_up" event. Call init() at bootstrap (service provider, front controller, or CLI entrypoint), set the user context once (setUserId), then emit the event. Confirm it lands in the Amplitude live event stream and only then add queueing or Identify.
Context-first, event-second. The library keeps user/device/session state on the singleton. Set it once per request/worker context, then log many events:
$amplitude->setUserId($user->id)
->setDeviceId($request->header('X-Device-Id'))
->setSessionId($session->id)
->setEventProperties(['app_version' => '2.4.1']);
$amplitude->logEvent('checkout_started');
$amplitude->logEvent('checkout_completed', null, ['order_total' => 4200]);
This is the idiomatic pattern in a framework: a service provider calls init(), middleware injects the user/device/session, controllers/events log.
Pick the right dispatch method:
| Method | Behavior | Use when |
|---|---|---|
logEvent() |
Synchronous HTTP call, blocks | Low volume, must know success (server-side critical events) |
logEventAsync() |
curl_multi-style fire-and-forget, flushes at end of request |
Request-path analytics, high volume |
queueEvent() + flush() |
Appends to queue, you control when | CLI workers, batch jobs, need persistence/retry |
Use a persistent queue for anything that matters. SimpleQueue is in-memory and is lost on crash or between processes. Implement QueueInterface against Redis/DB, and run a scheduled flush():
$amplitude->setQueue(new RedisAmplitudeQueue($redis));
// cron / worker:
$amplitude->flush();
Identify before events, not after. Amplitude applies user properties in receipt order; setting $email/$plan after the event that you want to segment often appears as a missing property on that event. Send identify() (or setUserProperties()) before logEvent() on signup/profile changes.
Use insert_id for idempotency. If you retry or replay events, pass a deterministic insert ID (order ID, UUID from your DB). Amplitude dedupes on it, which is the only defense against double-counting when you combine retries with at-least-once delivery.
Wrap in your own thin client. The singleton is painful to mock. In Laravel/Symfony, bind an interface (AnalyticsClient) to a small adapter that internally calls Amplitude::getInstance(); tests then swap in a no-op or in-memory fake. Don't inject Amplitude directly into every class.
Batch related events. In a job that emits N events, queueEvent() in a loop then a single flush() — one HTTP round-trip rather than N.
init() before anything else. Calling logEvent() without init() will throw or silently no-op depending on version; assert it in your bootstrap.reset() at the top of each unit of work, or re-init with the correct context.invalid/error entries when some events in a batch were dropped (bad user IDs, schema issues). Parse the response object rather than trusting the status code. logEventAsync() and flush() give you almost no visibility here — add logging.min_id_length. If you use short internal user IDs (autoincrement integers), you must both lower min_id_length in init() options and enable it in the Amplitude project settings, or every event is silently rejected. Long numeric IDs are the default-safe path.setEventProperties() accumulates; it does not clear after logEvent(). Reset or you'll ship stale properties on unrelated events.exit(), a fatal error, or SIGKILL before the destructor runs. Flush explicitly at the end of every job.init() options and never call logEvent() on a user-facing hot path without it — analytics must not take down checkout.init()/options, otherwise subclass and override the request method.min_id_length, wrong user_id, or a queued event that never flushed.composer.json constraint against your runtime; the API is stable enough that pinning to ^1.0-style ranges is safe, and this package is small enough that a major bump is easy to absorb.How can I help you explore Laravel packages today?