Laravel Database Configuration and .env File Best Practices
Configure your database connections and learn environment variable best practices.
Sessions provide a way to store information about users across multiple requests. Laravel sessions are secure, driver-based, and easy to use for maintaining state in your web applications.
<?php
// config/session.php
return [
// Default session driver
'driver' => env('SESSION_DRIVER', 'file'),
// Session lifetime (minutes)
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
// Session encryption
'encrypt' => false,
// Session file location (for file driver)
'files' => storage_path('framework/sessions'),
// Database connection (for database driver)
'connection' => env('SESSION_CONNECTION'),
'table' => 'sessions',
// Cache store (for cache driver)
'store' => env('SESSION_STORE'),
// Sweeping lottery (garbage collection)
'lottery' => [2, 100],
// Cookie settings
'cookie' => env('SESSION_COOKIE', 'laravel_session'),
'path' => '/',
'domain' => env('SESSION_DOMAIN'),
'secure' => env('SESSION_SECURE_COOKIE'),
'http_only' => true,
'same_site' => 'lax',
];
1. File Driver (Default)
// .env
SESSION_DRIVER=file
// Sessions stored in: storage/framework/sessions/
2. Cookie Driver
SESSION_DRIVER=cookie
// Limited to 4KB, encrypted automatically
3. Database Driver
// .env
SESSION_DRIVER=database
SESSION_CONNECTION=mysql
// Create sessions table
php artisan session:table
php artisan migrate
4. Redis Driver
// .env
SESSION_DRIVER=redis
SESSION_CONNECTION=session
// config/database.php
'redis' => [
'session' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_SESSION_DB', 1),
],
],
5. Memcached Driver
// .env
SESSION_DRIVER=memcached
6. Array Driver (Testing)
// .env
SESSION_DRIVER=array
// Volatile, only for testing
<?php
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class UserController extends Controller
{
public function storePreferences(Request $request)
{
// Store single value
Session::put('theme', $request->theme);
Session::put('language', $request->language);
// Store multiple values
Session::put([
'notifications_enabled' => $request->has('notifications'),
'timezone' => $request->timezone,
'results_per_page' => $request->results_per_page,
]);
return redirect()->back()->with('success', 'Preferences saved!');
}
public function addToCart(Request $request)
{
$productId = $request->product_id;
$quantity = $request->quantity;
// Push to array session value
Session::push('cart.items', [
'product_id' => $productId,
'quantity' => $quantity,
'added_at' => now(),
]);
// Increment cart count
Session::increment('cart.total_items', $quantity);
return response()->json([
'message' => 'Item added to cart',
'cart_count' => Session::get('cart.total_items', 0)
]);
}
}
public function updateProfile(Request $request)
{
// Store using request instance
$request->session()->put('user_profile_in_progress', true);
$request->session()->put('profile_data', $request->except('_token'));
return redirect()->route('profile.step2');
}
public function setUserTimezone(Request $request)
{
// Using global helper
session(['timezone' => $request->timezone]);
session(['last_timezone_update' => now()]);
return response()->json(['success' => true]);
}
public function showDashboard(Request $request)
{
// Get session value with default
$theme = Session::get('theme', 'light');
$language = session('language', 'en');
// Check if session value exists
if (Session::has('user_preferences')) {
$preferences = Session::get('user_preferences');
}
// Get all session data
$allSessionData = Session::all();
// Get and remove session value
$temporaryData = Session::pull('temp_data');
return view('dashboard', compact('theme', 'language', 'preferences'));
}
public function manageCart(Request $request)
{
// Get cart items or initialize empty array
$cartItems = Session::get('cart.items', []);
// Check if item exists in cart
$itemIndex = array_search(
$request->product_id,
array_column($cartItems, 'product_id')
);
if ($itemIndex !== false) {
// Update existing item quantity
$cartItems[$itemIndex]['quantity'] += $request->quantity;
Session::put('cart.items', $cartItems);
}
// Calculate total
$total = array_sum(array_column($cartItems, 'quantity'));
Session::put('cart.total_items', $total);
return view('cart', compact('cartItems', 'total'));
}
public function clearPreferences(Request $request)
{
// Remove single value
Session::forget('theme');
Session::forget('language');
// Remove multiple values
Session::forget([
'notifications_enabled',
'timezone',
'results_per_page'
]);
// Remove all session data
// Session::flush(); // Use carefully!
return redirect()->back()->with('info', 'Preferences cleared');
}
public function removeFromCart(Request $request)
{
$productId = $request->product_id;
$cartItems = Session::get('cart.items', []);
// Filter out the item to remove
$cartItems = array_filter($cartItems, function($item) use ($productId) {
return $item['product_id'] != $productId;
});
// Re-index array
$cartItems = array_values($cartItems);
Session::put('cart.items', $cartItems);
// Update total
$total = array_sum(array_column($cartItems, 'quantity'));
Session::put('cart.total_items', $total);
return response()->json([
'success' => true,
'cart_count' => $total
]);
}
Flash data is session data that is available only for the next request. It's automatically cleared after being accessed.
<?php
// app/Http/Controllers/AuthController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthController extends Controller
{
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials)) {
// Flash success message
Session::flash('success', 'Welcome back!');
// Flash user-specific data
Session::flash('login_time', now());
Session::flash('user_ip', $request->ip());
return redirect()->intended('dashboard');
}
// Flash error message
Session::flash('error', 'Invalid credentials. Please try again.');
return back()->withInput();
}
public function register(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
$user = User::create($validated);
Auth::login($user);
// Flash multiple values
Session::flash('welcome_message', true);
Session::flash('user_name', $user->name);
return redirect()->route('onboarding.start');
}
}
public function multiStepForm(Request $request)
{
if ($request->isMethod('POST')) {
// Store form data in session
Session::put('form_data.step1', $request->except('_token'));
// Flash validation message
Session::flash('message', 'Step 1 completed!');
// Keep flash data for next request
Session::reflash();
return redirect()->route('form.step2');
}
return view('form.step1');
}
public function step2Form(Request $request)
{
// Flash data from step1 is still available
$step1Data = Session::get('form_data.step1');
if ($request->isMethod('POST')) {
Session::put('form_data.step2', $request->except('_token'));
// Keep specific flash data
Session::keep(['message', 'user_name']);
return redirect()->route('form.review');
}
return view('form.step2', compact('step1Data'));
}
<?php
// app/Extensions/MongoSessionHandler.php
namespace App\Extensions;
use Illuminate\Contracts\Session\Session;
use MongoDB\Client;
class MongoSessionHandler implements \SessionHandlerInterface
{
protected $collection;
protected $lifetime;
public function __construct()
{
$client = new Client(env('MONGO_URI'));
$this->collection = $client->selectDatabase('sessions')
->selectCollection('sessions');
$this->lifetime = config('session.lifetime') * 60;
}
public function open($savePath, $sessionName): bool
{
return true;
}
public function close(): bool
{
return true;
}
public function read($sessionId): string
{
$session = $this->collection->findOne([
'session_id' => $sessionId,
'expires_at' => ['$gt' => time()]
]);
return $session ? $session['data'] : '';
}
public function write($sessionId, $data): bool
{
$this->collection->updateOne(
['session_id' => $sessionId],
[
'$set' => [
'data' => $data,
'expires_at' => time() + $this->lifetime,
'last_activity' => time()
]
],
['upsert' => true]
);
return true;
}
public function destroy($sessionId): bool
{
$this->collection->deleteOne(['session_id' => $sessionId]);
return true;
}
public function gc($lifetime): int
{
return $this->collection->deleteMany([
'expires_at' => ['$lt' => time()]
])->getDeletedCount();
}
}
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
use App\Extensions\MongoSessionHandler;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Session::extend('mongo', function ($app) {
return new MongoSessionHandler;
});
}
}
<?php
// app/Providers/EventServiceProvider.php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
// Session started event
\Illuminate\Session\Events\SessionStarted::class => [
\App\Listeners\LogSessionStart::class,
],
// Session regenerated event
\Illuminate\Session\Events\SessionRegenerated::class => [
\App\Listeners\UpdateSessionTimestamp::class,
],
];
}
<?php
// app/Listeners/LogSessionStart.php
namespace App\Listeners;
use Illuminate\Session\Events\SessionStarted;
use Illuminate\Support\Facades\Log;
class LogSessionStart
{
public function handle(SessionStarted $event): void
{
Log::info('Session started', [
'session_id' => $event->session->getId(),
'user_id' => auth()->id() ?? 'guest',
'ip_address' => request()->ip(),
]);
}
}
// app/Listeners/UpdateSessionTimestamp.php
namespace App\Listeners;
use Illuminate\Session\Events\SessionRegenerated;
use Illuminate\Support\Facades\DB;
class UpdateSessionTimestamp
{
public function handle(SessionRegenerated $event): void
{
if (auth()->check()) {
// Update user's last activity
DB::table('users')
->where('id', auth()->id())
->update(['last_session_activity' => now()]);
}
}
}
<?php
// app/Http/Controllers/CartController.php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class CartController extends Controller
{
public function showCart()
{
$cartItems = Session::get('cart.items', []);
$total = Session::get('cart.total', 0);
$itemCount = Session::get('cart.item_count', 0);
// Get product details for cart items
$productIds = array_column($cartItems, 'product_id');
$products = Product::whereIn('id', $productIds)->get()->keyBy('id');
return view('cart.show', compact('cartItems', 'products', 'total', 'itemCount'));
}
public function addToCart(Request $request)
{
$request->validate([
'product_id' => 'required|exists:products,id',
'quantity' => 'required|integer|min:1',
]);
$product = Product::findOrFail($request->product_id);
$cartItems = Session::get('cart.items', []);
// Check if product already in cart
$existingIndex = $this->findCartItemIndex($cartItems, $product->id);
if ($existingIndex !== false) {
// Update existing item
$cartItems[$existingIndex]['quantity'] += $request->quantity;
} else {
// Add new item
$cartItems[] = [
'product_id' => $product->id,
'quantity' => $request->quantity,
'price' => $product->price,
'added_at' => now()->toDateTimeString(),
];
}
// Update session
Session::put('cart.items', $cartItems);
$this->updateCartTotals();
return response()->json([
'success' => true,
'cart_count' => Session::get('cart.item_count', 0),
'message' => 'Product added to cart'
]);
}
public function updateCart(Request $request)
{
$cartItems = Session::get('cart.items', []);
foreach ($request->quantities as $productId => $quantity) {
$index = $this->findCartItemIndex($cartItems, $productId);
if ($index !== false) {
if ($quantity <= 0) {
// Remove item if quantity is 0 or less
unset($cartItems[$index]);
} else {
// Update quantity
$cartItems[$index]['quantity'] = $quantity;
}
}
}
// Re-index array
$cartItems = array_values($cartItems);
Session::put('cart.items', $cartItems);
$this->updateCartTotals();
Session::flash('success', 'Cart updated successfully');
return redirect()->route('cart.show');
}
public function clearCart()
{
Session::forget('cart');
Session::flash('info', 'Cart cleared successfully');
return redirect()->route('cart.show');
}
protected function findCartItemIndex($cartItems, $productId)
{
foreach ($cartItems as $index => $item) {
if ($item['product_id'] == $productId) {
return $index;
}
}
return false;
}
protected function updateCartTotals()
{
$cartItems = Session::get('cart.items', []);
$total = 0;
$itemCount = 0;
foreach ($cartItems as $item) {
$total += $item['quantity'] * $item['price'];
$itemCount += $item['quantity'];
}
Session::put('cart.total', $total);
Session::put('cart.item_count', $itemCount);
}
}
<?php
// app/Http/Controllers/RegistrationController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
class RegistrationController extends Controller
{
public function showStep1()
{
$data = Session::get('registration.data', []);
return view('registration.step1', compact('data'));
}
public function postStep1(Request $request)
{
$validated = $request->validate([
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
// Store in session
Session::put('registration.data.step1', $validated);
Session::put('registration.current_step', 2);
return redirect()->route('registration.step2');
}
public function showStep2()
{
$this->ensureStepAccess(2);
$data = Session::get('registration.data', []);
return view('registration.step2', compact('data'));
}
public function postStep2(Request $request)
{
$this->ensureStepAccess(2);
$validated = $request->validate([
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'phone' => 'required|string|max:20',
]);
Session::put('registration.data.step2', $validated);
Session::put('registration.current_step', 3);
return redirect()->route('registration.step3');
}
public function showStep3()
{
$this->ensureStepAccess(3);
$data = Session::get('registration.data', []);
return view('registration.step3', compact('data'));
}
public function postStep3(Request $request)
{
$this->ensureStepAccess(3);
$validated = $request->validate([
'company_name' => 'nullable|string|max:255',
'job_title' => 'nullable|string|max:255',
'newsletter' => 'boolean',
]);
Session::put('registration.data.step3', $validated);
return redirect()->route('registration.review');
}
public function showReview()
{
$data = Session::get('registration.data', []);
if (empty($data['step1']) || empty($data['step2'])) {
return redirect()->route('registration.step1')
->with('error', 'Please complete all required steps');
}
return view('registration.review', compact('data'));
}
public function completeRegistration()
{
$data = Session::get('registration.data', []);
// Validate all required data is present
if (empty($data['step1']) || empty($data['step2'])) {
return redirect()->route('registration.step1')
->with('error', 'Please complete all required steps');
}
// Create user
$user = User::create([
'email' => $data['step1']['email'],
'password' => Hash::make($data['step1']['password']),
'first_name' => $data['step2']['first_name'],
'last_name' => $data['step2']['last_name'],
'phone' => $data['step2']['phone'],
'company_name' => $data['step3']['company_name'] ?? null,
'job_title' => $data['step3']['job_title'] ?? null,
'newsletter_opt_in' => $data['step3']['newsletter'] ?? false,
]);
// Clear registration session
Session::forget('registration');
// Flash success message
Session::flash('success', 'Registration completed successfully!');
auth()->login($user);
return redirect()->route('dashboard');
}
protected function ensureStepAccess($step)
{
$currentStep = Session::get('registration.current_step', 1);
if ($step > $currentStep) {
return redirect()->route("registration.step{$currentStep}")
->with('error', 'Please complete previous steps first');
}
}
}
// config/session.php
return [
'driver' => env('SESSION_DRIVER', 'database'),
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
'encrypt' => env('SESSION_ENCRYPT', false),
'cookie' => env('SESSION_COOKIE', 'laravel_session'),
// Security settings
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax', // or 'strict' for better security
];
<?php
// app/Http/Middleware/ValidateSessions.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ValidateSessions
{
public function handle(Request $request, Closure $next)
{
// Regenerate session ID periodically for security
if ($this->shouldRegenerateSession()) {
$request->session()->regenerate();
}
// Validate user session
if (Auth::check() && $this->sessionExpired($request)) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login')
->with('error', 'Session expired. Please log in again.');
}
return $next($request);
}
protected function shouldRegenerateSession(): bool
{
// Regenerate every 100 requests or 30 minutes
$lastRegeneration = session('last_session_regeneration', 0);
return (time() - $lastRegeneration > 1800) ||
(session('request_count', 0) >= 100);
}
protected function sessionExpired(Request $request): bool
{
$lastActivity = $request->session()->get('last_activity');
if (!$lastActivity) {
return false;
}
return time() - $lastActivity > config('session.lifetime') * 60;
}
}
<?php
// tests/Feature/SessionTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class SessionTest extends TestCase
{
use RefreshDatabase;
public function test_session_data_storage_and_retrieval()
{
// Set session data
$this->withSession(['theme' => 'dark', 'language' => 'fr'])
->get('/dashboard');
// Verify session data persists
$this->get('/preferences')
->assertSessionHas('theme', 'dark')
->assertSessionHas('language', 'fr');
}
public function test_flash_data_availability()
{
// Set flash data
$response = $this->post('/login', [
'email' => 'user@example.com',
'password' => 'password'
]);
// Flash data should be available for next request
$response->assertSessionHas('success');
// But not after subsequent requests
$this->get('/dashboard')
->assertSessionMissing('success');
}
public function test_shopping_cart_functionality()
{
$user = \App\Models\User::factory()->create();
$this->actingAs($user)
->post('/cart/add', [
'product_id' => 1,
'quantity' => 2
])
->assertSessionHas('cart.items')
->assertSessionHas('cart.total_items', 2);
// Verify cart contents
$this->get('/cart')
->assertSee('Shopping Cart')
->assertSessionHas('cart.total_items', 2);
}
public function test_session_clearing()
{
$this->withSession([
'preferences' => ['theme' => 'dark'],
'cart' => ['items' => []]
]);
$this->post('/session/clear')
->assertSessionMissing('preferences')
->assertSessionMissing('cart');
}
public function test_multi_step_form_session_persistence()
{
$response = $this->post('/registration/step1', [
'email' => 'test@example.com',
'password' => 'password123',
'password_confirmation' => 'password123'
]);
$response->assertSessionHas('registration.data.step1')
->assertRedirect('/registration/step2');
// Continue to step 2 with session data
$this->get('/registration/step2')
->assertOk();
}
}
// tests/Unit/SessionUnitTest.php
class SessionUnitTest extends TestCase
{
public function test_session_helper_functions()
{
// Test session helper
session(['test_key' => 'test_value']);
$this->assertEquals('test_value', session('test_key'));
// Test session exists
$this->assertTrue(session()->has('test_key'));
// Test session forget
session()->forget('test_key');
$this->assertFalse(session()->has('test_key'));
}
public function test_session_flash_data()
{
session()->flash('message', 'Hello World');
// Flash data should exist
$this->assertTrue(session()->has('message'));
$this->assertEquals('Hello World', session('message'));
// Simulate next request
session()->ageFlashData();
// Flash data should be gone
$this->assertFalse(session()->has('message'));
}
}
// For high-traffic applications, use Redis or database
SESSION_DRIVER=redis
// For development, use file or array
SESSION_DRIVER=file
public function storeUserPreferences(Request $request)
{
// Store only necessary data
$preferences = [
'theme' => $request->theme,
'language' => $request->language,
// Don't store large objects in session
];
Session::put('preferences', $preferences);
// For large data, consider database storage
if (auth()->check()) {
auth()->user()->update(['preferences' => $preferences]);
}
}
put() stores data for the entire session duration, while flash() stores data only for the next request. Flash data is automatically cleared after being accessed.
Use database driver for load-balanced applications or when you need to share sessions across multiple servers. Use file driver for single-server applications or development environments.
Use HTTPS, set secure cookies, enable http_only flags, use same_site cookies, regenerate session IDs periodically, and set appropriate session lifetimes.
It depends on the driver: file/system limits for file driver, 4KB for cookie driver, database column limits for database driver. Keep session data minimal for best performance.
APIs are typically stateless and don't use sessions. Use token-based authentication (JWT, Sanctum) instead of session-based authentication for APIs.
Performance degrades. For file driver, disk I/O increases. For database driver, queries slow down. Always keep session data minimal and consider storing large data in cache or database.
// Check session configuration
// Ensure same_site is not too restrictive
'same_site' => 'lax', // Instead of 'strict'
// Check domain configuration
'domain' => '.yourdomain.com', // For subdomains
// Verify secure cookie setting in production
'secure' => true,
// Regenerate session ID properly
$request->session()->regenerate(); // Preserves data
$request->session()->invalidate(); // Clears data
// Handle session timeouts
public function checkSession(Request $request)
{
if ($request->session()->has('last_activity')) {
$timeout = config('session.lifetime') * 60;
if (time() - session('last_activity') > $timeout) {
auth()->logout();
$request->session()->invalidate();
return redirect('/login');
}
}
session(['last_activity' => time()]);
}
You’ve now mastered Laravel session management and can effectively manage user state across requests! In our next post, we’ll dive into Laravel Service Container: The Heart of Laravel Explained Simply to help you understand how Laravel resolves dependencies and manages class bindings behind the scenes.