lendable/visitor-tracking-bundle
A Symfony bundle for tracking visitor activity across your application. It captures page views and related request data, helping you build lightweight analytics, audit traffic, and understand usage without relying on a third-party service.
This is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/visitor_tracking.php. Treat it as a reference implementation first and a dependency second — it was archived in 2020, has zero dependents, and will not receive Symfony 6/7 or PHP 8.2+ compatibility fixes.
Minimal path in a Symfony app (to evaluate the code):
composer require lendable/visitor-tracking-bundle — expect to pin Symfony 4/5-era versions. If Composer refuses, clone the repo and read it instead; that is often the more useful exercise.config/bundles.php (or AppKernel::registerBundles() on older skeletons).DependencyInjection/Configuration.php for the real config tree — the README is thin and the Configuration class is the source of truth for keys like enable/disable toggles, cookie name/lifetime, and which paths to ignore.bin/console doctrine:schema:update --force (or generate a Doctrine migration) — the bundle ships Doctrine ORM entities, not migrations, so you own the schema.visitor/visit rows.Where to look first:
README.md — install steps only.EventListener/ (or EventSubscriber/) — the kernel.request listener is the whole product; this is the file to port.Entity/ — Visitor and Visit-style models and their relations.DependencyInjection/Configuration.php — config keys and defaults.Resources/config/services.* — wiring, listener priority, and which service you can decorate.First use case: drop-in request logging — one row per request with IP, user agent, referrer, path, and a cookie-backed visitor identity so you can group visits into sessions.
For a Laravel app, the realistic "getting started" is: read the listener, port ~100–200 lines into a middleware + Eloquent models, and skip the dependency entirely.
Kernel listener → middleware. The bundle's core is a kernel.request event subscriber. The direct Laravel translation is a global middleware registered in bootstrap/app.php (Laravel 11) or app/Http/Kernel.php, running after StartSession so you can attach the visitor to the session, and using terminate() for the write so it happens after the response is flushed.
Visitor identity via cookie. The bundle mints a token, sets it as a cookie, and reuses it on subsequent requests to group visits. In Laravel:
$token = $request->cookie('visitor_token') ?? Str::uuid();
Cookie::queue('visitor_token', $token, 60 * 24 * 365);
Keep the token opaque; do not put user IDs in it. Link it to auth()->id() in a column instead, so anonymous → logged-in stitching works without changing the cookie.
Separate visitor from visit. Two tables, one row per visitor (stable identity, first seen, last seen) and one row per request (path, method, referrer, UA, IP, timestamp). This is the pattern worth copying; it keeps the visitor table small and makes retention pruning trivial (DELETE FROM visits WHERE created_at < ...).
Decouple the write. A synchronous insert on every request (including assets, health checks, and bots) is the main performance trap. Mirror the bundle's ignore-list config in a Laravel config file and skip early:
if ($request->is('admin/*', 'horizon/*', 'up') || $request->isMethod('OPTIONS')) {
return $next($request);
}
Then move the insert into a queued job or at minimum terminate() so the response is not blocked on the DB.
Config-driven behavior. The bundle uses Symfony's config tree; in Laravel put the equivalent in config/visitor-tracking.php with enabled, ignore_paths, track_bots, anonymize_ip, and retention_days, and read it through config(). This is the single biggest maintainability win over hardcoding.
Extension points. In Symfony you would decorate the tracker service or add a listener with a higher priority to enrich the visit (e.g. resolve geo-IP, attach tenant ID). The Laravel analogue is a VisitorResolver interface bound in a service provider, with the middleware depending on the contract — so tests can swap in a fake and you can add enrichment without editing the middleware.
Testing. Use RefreshDatabase and a feature test that hits a route, then asserts exactly one visits row and a stable visitor_id across two requests with the cookie carried over. Assert the ignore-list works, and assert no row is written when tracking is disabled — that's the config regression that bites in production.
composer audit will flag it or its transitive Doctrine constraints eventually.composer require this into Laravel and get models. The entities extend nothing useful in an Eloquent context; port the schema, don't try to bridge the ORMs.(visitor_id, created_at) and created_at alone, and ship a scheduled model:prune or a visits:prune command from day one.REMOTE_ADDR unless the framework trusts proxies. In Laravel, configure TrustProxies correctly or every visitor resolves to your load balancer's IP and the data is worthless. Same for X-Forwarded-Proto if you branch on scheme.Secure, HttpOnly, and SameSite=Lax — the last one matters if you also want the cookie on top-level navigations from external referrers. A SameSite=Strict cookie silently fragments visitor sessions.bin/console cache:clear after editing config — a stale container is the usual "my config change did nothing" cause.kernel.request fired. In a ported Laravel middleware, DB::listen() plus Log::debug() in handle()/terminate() is faster than guessing.How can I help you explore Laravel packages today?