Understanding how Laravel sessions work

If we don't know how something works, it's more confusing when things behave differently than expected.

Joel Clermont
Joel Clermont
2023-11-08

Someone recently asked me for help to debug why a session value wasn't being set as expected.

They had a controller action which was setting a session value if empty, and then returning that value. But when they added some debug statements, they found that the value was always empty, even if they refreshed the page multiple times.

The expectation was that it should be empty the first time, but then be present on additional calls.

Here's a simplified version of what they were trying:

// inside the controller action and greatly simplified to show in one block

if (session()->has('some-key')) {
  dump('key exists');
  $value = session('some-key');
} else {
  dump('key does not exist');
  session(['some-key' => 'some-value']);
  $value = session('some-key');
}

dd($value);

Every time they refreshed the page, it would output key does not exist and some-value. Based on this, they knew the session value wasn't being persisted, but why?

Here's where it helps to know how Laravel sessions work. They don't use the underlying PHP session mechanism, but are instead implemented via the Laravel SessionManager and the StartSession middleware.

Let's peek at the relevant method in that middleware (I stripped out all comments, except my one comment, to keep it simple)

protected function handleStatefulRequest(Request $request, $session, Closure $next)
{
    $request->setLaravelSession(
        $this->startSession($request, $session)
    );

    $this->collectGarbage($session);

    $response = $next($request); // <--- This is where your controller action runs 

    $this->storeCurrentUrl($request, $session);

    $this->addCookieToResponse($response, $session);

    $this->saveSession($request);

    return $response;
}

Note that the saveSession method isn't called until the response side of the middleware, which runs after the controller action.

So by putting a dd() inside our controller action, we kill the PHP request before this middleware can finish its job. If he had used dump instead of dd, it would have worked as expected.

Mystery solved!

Here to help,

Joel

P.S. dd is useful at times, but Xdebug is an even better debugging tool. If you need help getting Xdebug running in your project, let me know.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.