Search by

builtbyberry / laravel-site-analytics

dberry37388

First-party Laravel event storage, reporting, exclusions and retention without a frontend dependency.

Package info

github.com/builtbyberry/laravel-site-analytics

pkg:composer/builtbyberry/laravel-site-analytics

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-09-24 01:05 UTC

This package is auto-updated.

Last update: 2026-09-24 01:07:12 UTC


README

First-party event storage, recording, reporting, traffic exclusions and retention for Laravel 13 / PHP 8.3+. Build your own analytics screen in Blade, React/Inertia or another frontend. The package has no frontend, route, auth, mail, queue or application-namespace dependencies.

Boundary

The package owns:

  • SiteEvent storage and casts, catalog-based dimension filtering, request context and HMAC hashing, failure-tolerant database recording.
  • IgnoredSiteIp encrypted address storage, normalized IP hashing and exclusion matching.
  • Report queries: totals, daily timezone buckets, popular pages, referral hostnames, campaigns, consumer-defined funnel counts, recent events and journeys.
  • Thirteen-month retention through Eloquent MassPrunable and publishable migrations.

The application owns its event catalog, allowed attributes, public-page policy, authentication, controllers, views, frontend tracking, funnels, scheduling and timezone. The package creates no public endpoint, applies no middleware, sends no email and schedules no task automatically. Bind AnalyticsEventCatalog in the application's provider; override TrafficExclusion to compose site policy with DatabaseTrafficExclusion.

Installation

composer require builtbyberry/laravel-site-analytics:^0.1

For a new installation, publish the migrations once, review them, then run them against the intended database:

php artisan vendor:publish --tag=site-analytics-migrations
php artisan migrate

Migrations create site_events and ignored_site_ips. They are deliberately publish-only, not auto-loaded. If your application already owns these tables and the corresponding historical migrations, keep those migrations and data; do not publish duplicate table creation, rename tables or export/import rows. Back up the database before upgrading. A code rollback should leave analytics tables and their migration history intact.

Configuration is merged automatically. Applications may provide config/analytics.php or publish site-analytics-config. Keep hashing secrets stable. A dedicated secret is optional; the package default uses APP_KEY when it is absent or blank. Existing consumers can override this expression to preserve historical hashes.

A minimal application catalog could look like this:

namespace App\Analytics;

use BuiltByBerry\SiteAnalytics\Contracts\AnalyticsEventCatalog;

final class WebsiteEventCatalog implements AnalyticsEventCatalog
{
    public function contains(string $event): bool
    {
        return in_array($event, ['page_view', 'reader_signup_requested'], true);
    }

    public function label(string $event): string
    {
        return match ($event) {
            'page_view' => 'Page view',
            'reader_signup_requested' => 'Signup request',
            default => $event,
        };
    }

    public function allowedDimensions(string $event): array
    {
        return [];
    }
}

Bind it in your application provider:

// Application provider
$this->app->bind(
    \BuiltByBerry\SiteAnalytics\Contracts\AnalyticsEventCatalog::class,
    \App\Analytics\WebsiteEventCatalog::class,
);
// After applying the application's public-route/privacy policy:
$context = \BuiltByBerry\SiteAnalytics\AnalyticsContext::fromRequest(
    $request,
    app(\BuiltByBerry\SiteAnalytics\Contracts\TrafficExclusion::class),
    ['path' => '/writing/example'],
);
app(\BuiltByBerry\SiteAnalytics\Contracts\AnalyticsRecorder::class)
    ->record('page_view', $context);

Run collection only after a successful public response. Exclude admin/authentication routes, private signed links, form values and signed-in traffic as appropriate for your site. DatabaseTrafficExclusion only matches explicitly ignored IP addresses; route, bot and prefetch policies belong in the application. Database recording failures return null and are reported through Laravel. If collection must tolerate a missing exclusion table, catch that failure in the application policy as well.

The catalog must declare every event and dimension. The recorder limits scalar dimensions and drops undeclared keys/events. It strips query strings and fragments from paths. Consumers must still exclude sensitive paths and must never allow sensitive dimension values. It does not infer whether an arbitrary string contains personal information. Request metadata is opt-in: pass only sanitized referral hostnames/campaign fields. Raw visitor IPs are not event attributes; explicitly managed exclusion addresses are encrypted for admin display.

Reporting

$report = app(\BuiltByBerry\SiteAnalytics\BuildAnalyticsReport::class)->handle(
    days: 30,
    conversionEvent: 'reader_signup_requested',
    pageGroups: ['writing' => ['/writing/'], 'books' => ['/books/']],
);

page_view is the shared page-view event name. Other events, labels and conversion events are application-defined. Report timezone and label come from analytics.timezone and analytics.timezone_label. handle($days, $funnels, $conversionEvent, $pageGroups) returns presentation-neutral arrays. Range is bounded to 1–366 days; the applications expose 7/30/90. Page groups contain trusted path prefixes. Report boundaries are converted from local midnight to UTC; daily buckets handle DST in PHP. Future-dated rows are excluded. The daily scan uses a cursor.

Visits mean distinct hashed Laravel sessions in the selected period, not unique people or timed sessions. No new analytics cookie is issued. Existing framework sessions supply the identifier. Referrals can count the same session under multiple hosts. Funnels preserve the source's independent distinct-session count per step; they are not ordered/cohort conversion funnels. Recent journeys/events retain the source limits (12 sessions / 15 events); a session's journey can still contain many events. This remains a small-site reporting implementation, without rollups or cross-site aggregation.

Retention

Schedule retention explicitly in the consumer:

Schedule::command('model:prune', [
    '--model' => [\BuiltByBerry\SiteAnalytics\Models\SiteEvent::class],
])->daily();

Run schedule:run using the hosting scheduler. No newsletter worker is involved. retention_months defaults to 13 and has a minimum of one month. Pruning affects only analytics event rows, not exclusions or subscribers.

Validation

composer install
composer test

Standalone Testbench tests cover a non-civic catalog, recording/privacy, exclusions, unavailable storage, DST reports and retention. The GitHub Actions suite runs on PHP 8.3, 8.4 and 8.5. Application integration suites also exercise existing-data compatibility and both Blade and React/Inertia consumers. Database tests currently use SQLite; PostgreSQL production behavior has not been verified by this package suite.

Laravel package discovery and configuration follow Laravel's package documentation.

License

MIT. See LICENSE.