customergauge/session
A Laravel package that captures and persists visitor session data such as landing pages, referrers, UTM parameters, and campaign attribution, making it easy to track how users arrive and move through your application across requests with minimal configuration.
This package bridges PHP's native $_SESSION superglobal into Laravel's authentication layer, so an app that already calls session_start() (legacy codebase, shared bootstrap, an old front controller) can still use Auth::user(), Auth::login(), and the auth middleware without rewriting session handling first. Treat it as an interop shim, not a general session driver.
Install and register
composer require customergauge/session
The service provider is auto-discovered on Laravel 5.5+. If you're on an older skeleton, add it to config/app.php manually. Publish any config the package ships:
php artisan vendor:publish --provider="CustomerGauge\Session\SessionServiceProvider"
Where to look first
src/ — identify the two classes that matter: the session store/adapter (implements the Illuminate\Contracts\Session\Session surface or wraps $_SESSION) and the service provider that binds it. Read them before wiring anything; the package is small enough that the source is the documentation.composer.json — confirm the illuminate/* constraints match your Laravel version. With a single-digit star count and zero dependents, version compatibility is on you to verify.public/index.php / bootstrap — decide where session_start() lives relative to bootstrap/app.php.First use case: legacy login, Laravel auth
You have $_SESSION['user_id'] set by an old login script and want Laravel controllers to behave normally:
// config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
Point the session driver at the native store (via the published config or a Auth::extend call in a service provider), make sure session_start() runs before the request is dispatched, then verify:
Route::get('/whoami', fn () => Auth::user()?->email ?? 'guest');
If that returns your legacy user, the bridge is working. If it returns null, dump $_SESSION and session_id() at the top of the route — the problem is almost always ordering or a second, competing session.
Register a custom guard rather than swapping the global session driver. The safest integration is to leave Laravel's own session untouched (CSRF, flash messages, validation errors all depend on it) and only bridge authentication:
// app/Providers/AuthServiceProvider.php
Auth::extend('php-session', function ($app, $name, array $config) {
return new SessionGuard(
$name,
$app['auth']->createUserProvider($config['provider']),
$app['session.store'], // or the package's native adapter
);
});
Then 'driver' => 'php-session' in config/auth.php. This keeps blast radius small.
Ordering is the whole game. In a legacy front controller:
session_start(); // native session first
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
$app->handleRequest(Request::capture());
If StartSession middleware or the package's bootstrapper runs first, you get two session stores fighting over state and intermittent "user randomly logged out" reports.
Dual-write during migration. While porting a legacy app, write identity to both stores on login and clear both on logout. This lets you migrate route groups at a time instead of a big-bang cutover:
public function login(Request $request)
{
// legacy write
$_SESSION['user_id'] = $user->id;
// laravel write
Auth::login($user);
return redirect()->intended('/dashboard');
}
Keep the legacy logout contract. Native sessions are usually destroyed by session_destroy() plus setcookie(session_name(), '', time() - 3600). Hook your logout path so both worlds are cleaned:
public function logout()
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
$_SESSION = [];
if (session_status() === PHP_SESSION_ACTIVE) {
session_regenerate_id(true);
session_destroy();
}
return redirect('/');
}
Middleware for hybrid route groups. Apply a small middleware to legacy-area routes that asserts a native session exists and 302s to the old login page — don't rely on Laravel's auth middleware, which will redirect to route('login') and confuse users mid-migration.
Testing. Native sessions are global process state; reset them in setUp():
protected function setUp(): void
{
parent::setUp();
$_SESSION = [];
@session_start();
}
Then seed identity directly ($_SESSION['user_id'] = $user->id) instead of going through a login endpoint.
laravel_session; PHP uses PHPSESSID. If both are active you'll see users authenticated in one request and anonymous in the next. Give them distinct cookie names and, ideally, distinct path/domain scopes while migrating.Cache-Control headers. Because both Laravel's StartSession middleware and PHP's native session module emit cache headers, you can end up with two Cache-Control values on the response — confusing proxies/CDNs and breaking strict header assertions in tests. Fixed in 2.3.0 (PR #12). If you're on 2.2.x or earlier, either upgrade or strip the duplicate in a small middleware ($response->headers->set('Cache-Control', ...)) before it reaches the client.session.save_path.session_start() after output. Any whitespace before <?php in a legacy include produces "headers already sent" warnings and a silently broken session. Grep your includes for trailing newlines and BOMs.$_SESSION is process-global and leaks across requests. Do not use this package in that environment without an explicit per-request reset — and honestly, don't use it there at all.Auth::user() inside a job or Artisan command will be null or throw. Guard calls with app()->runningInConsole() or pass the user ID explicitly into jobs.session_write_close() as early as safely possible, or switch session.save_handler to Redis.session_regenerate_id(true) — the old ID must be destroyed, not just replaced. Verify with a browser devtools cookiejar that PHPSESSID actually rotates.remember_token cookie flow lives in SessionGuard; a thin native adapter may not implement viaRemember(). Test "stay logged in across browser restart" explicitly — it's the most common silent regression.session_status(), session_id(), array_keys($_SESSION ?? []), Session::getId(), and Auth::id(). Nine times out of ten one column is empty and tells you exactly which layer dropped the state."customergauge/session": "2.3.0") and keep a fallback: a 40-line Auth::extend closure that reads $_SESSION['user_id'] directly is a viable escape hatch if the package stalls.Auth::extend and the session-store binding in the container are both swappable from your own service provider. If the package's adapter doesn't cover a case (e.g. getId() semantics, invalidate()), subclass it rather than patching vendor/.How can I help you explore Laravel packages today?