cesargb / event-tracker
Requires
- php: ^8.4
- posthog/posthog-php: ^4.12
Requires (Dev)
- illuminate/support: ^12.0|^13.0
- laravel/pint: ^1.27
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^13.1.6
Suggests
- illuminate/support: Required to use the Laravel integration (TrackerServiceProvider).
README
Send events to an analytics driver (currently PostHog) from plain PHP, or automatically from any event dispatched through Laravel's event system.
Installation
composer require cesargb/event-tracker
Plain PHP usage
use Cesargb\Event\Track; use Cesargb\Event\Track\Drivers\PostHogDriver; use Cesargb\Event\Track\Event; Track::init(new PostHogDriver('phc_your_api_key')); Track::capture(new Event( name: 'user_signup', userId: '42', timestamp: time(), properties: ['plan' => 'pro'], ));
Laravel usage
The service provider is auto-discovered. Publish the config file and set your PostHog API key:
php artisan vendor:publish --tag=event-tracker-config
POSTHOG_API_KEY=phc_your_api_key POSTHOG_HOST=https://eu.i.posthog.com # Optional: disable tracking entirely (e.g. in local/CI), defaults to true. EVENT_TRACKER_ENABLED=true
Auto-tracking events
Mark any class dispatched through Laravel's event system with the ShouldBeTracked marker interface and
it is sent automatically — no call to Track::capture() needed:
use Cesargb\Event\Track\Laravel\Contracts\ShouldBeTracked; class OrderShipped implements ShouldBeTracked { public function __construct(public readonly Order $order) {} } event(new OrderShipped($order));
This sends an order_shipped event with no properties and no user attribution. ShouldBeTracked has no
required methods — you implement only the ones you want to override:
| Method | Default when not implemented |
|---|---|
trackerName(): string |
Snake-case class basename (order_shipped) |
trackerProperties(): array |
[] |
trackerUserId(): ?string |
null — the event is anonymous/personless in PostHog |
trackerTimestamp(): int |
The current timestamp |
class OrderShipped implements ShouldBeTracked { public function __construct(public readonly Order $order) {} public function trackerProperties(): array { return ['total' => $this->order->total]; } public function trackerUserId(): ?string { return (string) $this->order->user_id; } }
If you don't override trackerUserId(), the event still reaches PostHog but can't be attributed to a
person — set it whenever you want the event tied to a user.
Tracking the authenticated user
For the common case of attributing an event to whoever is logged in, use the
TracksAuthenticatedUser trait instead of writing trackerUserId() by hand:
use Cesargb\Event\Track\Laravel\Concerns\TracksAuthenticatedUser; class OrderShipped implements ShouldBeTracked { use TracksAuthenticatedUser; public function __construct(public readonly Order $order) {} }
The id is read from the default guard when the event is captured. With no authenticated user
(console commands, queued jobs, guests) the event stays anonymous, exactly as if the trait
weren't there. A trackerUserId() declared on the event itself takes precedence over the trait.
Tracking public properties
For the common case of sending the event's own state as properties, use the
TracksPublicProperties trait instead of writing trackerProperties() by hand:
use Cesargb\Event\Track\Laravel\Concerns\TracksPublicProperties; class OrderShipped implements ShouldBeTracked { use TracksPublicProperties; public function __construct(public readonly int $orderId, public readonly float $total) {} }
This sends ['order_id' => ..., 'total' => ...] — every public property, keyed by its
snake-cased name. Only values that reduce to a primitive are included:
| Value | Result |
|---|---|
int, float, string, bool |
sent as-is |
BackedEnum |
its ->value |
UnitEnum (pure enum) |
its ->name |
DateTimeInterface |
ISO 8601 (->format(DateTimeInterface::ATOM)) |
object with __toString() |
cast to string |
null, arrays, any other object |
omitted |
static, protected, and private properties are never included, and neither is a typed
property left uninitialized. Override trackerExcludedProperties() to keep specific properties
out — for example an API token that happens to be public:
class OrderShipped implements ShouldBeTracked { use TracksPublicProperties; public function __construct(public readonly int $orderId, public readonly string $apiToken) {} protected function trackerExcludedProperties(): array { return ['apiToken']; } }
Note the exclusion list uses the property's name as declared on the class (apiToken), not its
snake_cased key. Because the trait sends everything public, keep anything sensitive protected
or add it to trackerExcludedProperties(). A trackerProperties() declared on the event itself
takes precedence over the trait, and both tracker traits can be combined on the same event.
A failure while tracking an event (a misconfigured driver, a network error, a broken tracker method) is reported via Laravel's exception handler and swallowed — it never bubbles up to the code that dispatched the event.
Testing
composer test
vendor/bin/pint --test