Analytics - v1.0.0-beta1

Contracts

ArtisanPack UI Analytics provides interfaces (contracts) that allow you to create custom implementations.

AnalyticsProviderInterface

Interface for analytics providers.

Definition

namespace ArtisanPackUI\Analytics\Contracts;

use ArtisanPackUI\Analytics\Data\EventData;
use ArtisanPackUI\Analytics\Data\PageViewData;

interface AnalyticsProviderInterface
{
    /**
     * Track a page view.
     */
    public function trackPageView(PageViewData $data): void;

    /**
     * Track a custom event.
     */
    public function trackEvent(EventData $data): void;

    /**
     * Check if the provider is enabled.
     */
    public function isEnabled(): bool;

    /**
     * Get the provider name.
     */
    public function getName(): string;
}

Implementation Example

use ArtisanPackUI\Analytics\Contracts\AnalyticsProviderInterface;
use ArtisanPackUI\Analytics\Data\EventData;
use ArtisanPackUI\Analytics\Data\PageViewData;

class MixpanelProvider implements AnalyticsProviderInterface
{
    public function __construct(
        private MixpanelClient $client,
    ) {}

    public function trackPageView(PageViewData $data): void
    {
        $this->client->track('Page View', [
            'path' => $data->path,
            'title' => $data->title,
        ]);
    }

    public function trackEvent(EventData $data): void
    {
        $this->client->track($data->name, $data->properties ?? []);
    }

    public function isEnabled(): bool
    {
        return config('services.mixpanel.enabled', false);
    }

    public function getName(): string
    {
        return 'mixpanel';
    }
}

Registering Custom Providers

// In a service provider
use ArtisanPackUI\Analytics\Facades\Analytics;

public function boot(): void
{
    Analytics::extend('mixpanel', function ($app) {
        return new MixpanelProvider(
            $app->make(MixpanelClient::class)
        );
    });
}

Then enable in config:

// config/artisanpack/analytics.php
'active_providers' => ['local', 'mixpanel'],

SiteResolverInterface

Interface for resolving the current site in multi-tenant setups.

Definition

namespace ArtisanPackUI\Analytics\Contracts;

use ArtisanPackUI\Analytics\Models\Site;
use Illuminate\Http\Request;

interface SiteResolverInterface
{
    /**
     * Resolve the site from the request.
     */
    public function resolve(Request $request): ?Site;

    /**
     * Get the resolver priority (lower runs first).
     */
    public function getPriority(): int;
}

Built-in Resolvers

Resolver Priority Resolution Method
ApiKeyResolver 10 API key in header or query
HeaderResolver 50 Custom header (X-Site-ID)
SubdomainResolver 90 Subdomain extraction
DomainResolver 100 Full domain matching

Implementation Example

use ArtisanPackUI\Analytics\Contracts\SiteResolverInterface;
use ArtisanPackUI\Analytics\Models\Site;
use Illuminate\Http\Request;

class TenantIdResolver implements SiteResolverInterface
{
    public function resolve(Request $request): ?Site
    {
        // Get tenant from authenticated user
        $user = $request->user();

        if (!$user || !$user->tenant_id) {
            return null;
        }

        return Site::where('tenant_id', $user->tenant_id)->first();
    }

    public function getPriority(): int
    {
        return 20; // Run early, after API key
    }
}

Registering Custom Resolvers

// config/artisanpack/analytics.php
'multi_tenant' => [
    'resolvers' => [
        \ArtisanPackUI\Analytics\Resolvers\ApiKeyResolver::class,
        \App\Analytics\TenantIdResolver::class, // Your custom resolver
        \ArtisanPackUI\Analytics\Resolvers\DomainResolver::class,
    ],
],

TenantResolverInterface

Legacy interface for tenant resolution.

Definition

namespace ArtisanPackUI\Analytics\Contracts;

interface TenantResolverInterface
{
    /**
     * Get the current tenant ID.
     */
    public function getCurrentTenantId(): int|string|null;

    /**
     * Set the current tenant ID.
     */
    public function setCurrentTenantId(int|string $tenantId): void;
}

Implementation Example

use ArtisanPackUI\Analytics\Contracts\TenantResolverInterface;

class SessionTenantResolver implements TenantResolverInterface
{
    public function getCurrentTenantId(): int|string|null
    {
        return session('current_tenant_id');
    }

    public function setCurrentTenantId(int|string $tenantId): void
    {
        session(['current_tenant_id' => $tenantId]);
    }
}

Registration

// config/artisanpack/analytics.php
'multi_tenant' => [
    'resolver' => \App\Analytics\SessionTenantResolver::class,
],

Creating Custom Contracts

You can extend the package with your own contracts:

namespace App\Analytics\Contracts;

use ArtisanPackUI\Analytics\Models\PageView;

interface PageViewEnricherInterface
{
    /**
     * Enrich a page view with additional data.
     */
    public function enrich(PageView $pageView): PageView;
}

Implementation:

class GeoEnricher implements PageViewEnricherInterface
{
    public function __construct(
        private GeoIpService $geoService,
    ) {}

    public function enrich(PageView $pageView): PageView
    {
        $location = $this->geoService->lookup($pageView->ip_address);

        $pageView->custom_data = array_merge(
            $pageView->custom_data ?? [],
            ['geo' => $location]
        );

        return $pageView;
    }
}

Register in service provider:

$this->app->bind(
    PageViewEnricherInterface::class,
    GeoEnricher::class
);

Dependency Injection

All contracts can be injected into your classes:

class MyService
{
    public function __construct(
        private AnalyticsProviderInterface $provider,
        private SiteResolverInterface $resolver,
    ) {}

    public function doSomething(): void
    {
        if ($this->provider->isEnabled()) {
            // Use provider
        }
    }
}

Testing with Contracts

Use contracts for easy mocking in tests:

use ArtisanPackUI\Analytics\Contracts\AnalyticsProviderInterface;

test('tracks page view', function () {
    $provider = Mockery::mock(AnalyticsProviderInterface::class);
    $provider->shouldReceive('trackPageView')->once();

    app()->instance(AnalyticsProviderInterface::class, $provider);

    // Test code that triggers page view tracking
});