An alternative to the original Snowplow Tracker.
This Tracker provides:
- an object-oriented API for event tracking
- extension points in order to integrate your own domain logic
- abstractions to swap dependencies
With composer:
composer require tijmenwierenga/snowplow-tracker
The Snowplow Tracker is instantiable by providing an emitter and optionally additional configuration.
Currently, only a single emitter is available: the HttpClientEmitter
.
The HttpClientEmitter
sends the payload to a collector over HTTP.
If you want to use this emitter a PSR-7, PSR-17 and PSR-18 compliant implementation needs to be installed.
Popular PSR-18 compliant HTTP Clients are:
Popular PSR-7 compliant libraries are:
By default, the php-http/discovery
will discover the installed HTTP Client and Request Factory so no additional configuration is required.
If you wish to configure your HTTP client yourself you can pass in your own. Same goes for the Request Factory.
With auto-discovery:
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Tracker;
use TijmenWierenga\SnowplowTracker\Emitters\HttpClientEmitter;
$emitter = new HttpClientEmitter('https://your-collector-uri')
$tracker = new Tracker($emitter)
Without auto-discovery (with Symfony's HTTP client):
<?php
declare(strict_types=1);
use Symfony\Component\HttpClient\Psr18Client;
use TijmenWierenga\SnowplowTracker\Emitters\HttpClientEmitter;
use TijmenWierenga\SnowplowTracker\Tracker;
// Instantiate your own HTTP Client
$httpClient = new Psr18Client();
// Pass it to the emitter
$emitter = new HttpClientEmitter('https://your-collector-uri', $httpClient);
$tracker = new Tracker($emitter);
In order to customize the tracker's configuration you can pass an additional configuration object:
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Tracker;
use TijmenWierenga\SnowplowTracker\Config\Platform;
use TijmenWierenga\SnowplowTracker\Config\TrackerConfig;
$config = new TrackerConfig(
Platform::WEB, // The platform you're sending events from
'My Tracker Name', // The name of your tracker
'my-app-id' // A unique identifier for your app
);
$tracker = new Tracker(config: $config);
Whenever emitting an event fails, you don't want to lose the data.
Therefore, a TijmenWierenga\SnowplowTracker\Emitters\FallbackStrategy
exists in order to help you recover from failures.
The FallbackStrategy
is called whenever the TijmenWierenga\SnowplowTracker\Emitters\FailedToEmit
exception is raised.
By default, a void fallback strategy is used, which means nothing happens when an event failed emitting.
It's advised to implement an own implementation that stores the failed payloads in order to attempt to send the failed events at a later time.
Configure the fallback strategy as a constructor argument for the Tracker
:
<?php
declare(strict_types=1);
$fallbackStrategy = new MyFallbackStrategy();
$tracker = new Tracker(fallbackStrategy: $fallbackStrategy);
If a FailedToEmit
exception is raised, the Tracker
will rethrow the exception after calling the fallback strategy.
This could potentially lead to client-facing errors which may be undesirable.
This behaviour can be changed by setting the $throwOnError
constructor argument for the Tracker
:
<?php
declare(strict_types=1);
$tracker = new Tracker(throwOnError: false);
Please note that without a sufficient fallback strategy, ignoring exceptions will lead to data loss without anyone noticing.
Tracking events is done by calling the track()
method on the Tracker
instance:
This library implements 6 type of events.
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\Pageview;
$tracker->track(new Pageview(
'https://github.com/TijmenWierenga',
'Tijmen Wierenga (Tijmen Wierenga)',
'https://twitter.com/TijmenWierenga'
));
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\PagePing;
$tracker->track(new PagePing(
0, // min horizontal offset
500, // max horizontal offset
250, // min vertical offset
300 // max vertical offset
));
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\EcommerceTransaction;
$tracker->track(new EcommerceTransaction(
'd85e7b63-c046-47ac-b9a9-039d33ef3b3b', // order ID
49.95, // total value
));
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\TransactionItem;
$tracker->track(new TransactionItem(
'd85e7b63-c046-47ac-b9a9-039d33ef3b3b', // order ID
'48743-48284-24', // SKU
12.95, // price
4 // quantity
));
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\StructuredEvent;
$tracker->track(new StructuredEvent(
'my-category',
'my-action'
));
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\UnstructuredEvent;
use TijmenWierenga\SnowplowTracker\Schemas\Schema;
use TijmenWierenga\SnowplowTracker\Schemas\Version;
$tracker->track(new UnstructuredEvent(
new Schema(
'com.snowplowanalytics.snowplow',
'ad_impression',
new Version(1, 0, 0)
),
[
'impressionId' => 'dcefa2cc-9e82-4d7e-bbeb-eef0e9dad57d'
]
));
All events extend from a base event class which implements all properties currently available in the Snowplow Tracker Protocol.
These properties are publicly available in every event.
The example below shows how to add a userId
to an event to identify a user:
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\Pageview;
$pageviewEvent = new Pageview('https://github.com/TijmenWierenga');
$pageviewEvent->userId = 'TijmenWierenga';
Sometimes you want to add additional context to an event.
Custom contexts are self-describing JSON schema's which can be implemented by creating a class that implements TijmenWierenga\SnowplowTracker\Events\Schemable
.
The example below shows an implementation of the existing Timing JSON Schema as defined by Snowplow Analytics.
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\Schemable;
use TijmenWierenga\SnowplowTracker\Schemas\Schema;
use TijmenWierenga\SnowplowTracker\Schemas\Version;
final class Timing implements Schemable
{
public function __construct(
private readonly string $category,
private readonly string $variable,
private readonly int $timing,
private readonly ?string $label = null
) {
}
public function getSchema(): Schema
{
return new Schema(
'com.snowplowanalytics.snowplow',
'timing',
new Version(1, 0, 0)
);
}
public function getData(): array|string|int|float|bool|JsonSerializable
{
return array_filter([
'category' => $this->category,
'variable' => $this->variable,
'timing' => $this->timing,
'label' => $this->label
]);
}
}
As an example, let's include context about the page load in a pageview event:
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\Pageview;
$pageviewEvent = new Pageview('https://github.com/TijmenWierenga');
$pageLoad = new Timing(
'pageLoad',
'ms',
21
);
$pageviewEvent->addContext($pageLoad);
Middlewares provides a way to act on events that are tracked. Every piece of middleware is a callable that receives the event as an argument and must return the (modified) event to the next piece of middleware:
callable(Event $event): Event
This is incredibly useful when you want to add contextual information to every event.
As an example, middleware is added that adds the userId
of the currently authenticated user to the event.
<?php
declare(strict_types=1);
use TijmenWierenga\SnowplowTracker\Events\Event;
use TijmenWierenga\SnowplowTracker\Events\Pageview;
use TijmenWierenga\SnowplowTracker\Events\StructuredEvent;
use TijmenWierenga\SnowplowTracker\Tracker;
final class AddUserIdMiddleware
{
public function __construct(
private readonly AuthenticatedUserIdProvider $userIdProvider
) {
}
public function __invoke(Event $event): Event
{
$event->userId = $this->userIdProvider->getUserId();
return $event;
}
}
$addUserIdMiddleware = new AddUserIdMiddleware(/** ... */);
$tracker = new Tracker(middleware: [$addUserIdMiddleware]);
$pageviewEvent = new Pageview('https://github.com/TijmenWierenga');
$structuredEvent = new StructuredEvent('my-category', 'my-action');
$tracker->track($pageviewEvent);
$tracker->track($structuredEvent);
In the example above both events will now have a userId
attached.