Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

Sessions

HTTP has no memory. Sessions fix that. But async PHP changes the rules.

You will learn

  • Why HTTP is stateless and what that means for your app
  • How sessions give the server a memory across requests
  • The critical difference between $_SESSION and $g->session in ZealPHP
  • Build a session-backed feature that persists across page loads

The problem

Click the counter button from the last lesson. Now open a new tab and visit the same page. Your count is gone. HTTP is stateless — every request is a stranger. The server doesn't know that the person who clicked 5 times is the same person loading the page now.

This is by design. HTTP was built for documents, not applications. But you're building an application. You need the server to remember who someone is across requests.

Sessions: name badges for the web

Think of a store where customers wear masks — the clerk can't tell them apart. Sessions work like name badges:

  1. First visit: the server hands the browser a name badge (a cookie containing a session ID)
  2. Next visit: the browser wears the badge. The server reads the ID and opens a personal file folder with that visitor's data
  3. The badge is just an ID — the actual data stays on the server, safe from tampering

Sessions in traditional PHP

session_start();
$_SESSION['name'] = 'Alice';
echo $_SESSION['name']; // "Alice" — even across page loads

session_start() reads the session cookie, loads the session file, and populates $_SESSION. At the end of the request, PHP writes $_SESSION back to disk. The data persists.

The ZealPHP twist: coroutines

In traditional PHP, each request runs in its own process. $_SESSION is safe because no two requests share the same process at the same time.

ZealPHP is different. One worker process handles many requests simultaneously using coroutines. If two users make requests at the same time, they run in the same process. If both wrote to $_SESSION, their data would leak between requests.

// ZealPHP coroutine mode — the right way
$g = \ZealPHP\G::instance();
$g->session['name'] = 'Alice';
echo $g->session['name']; // "Alice" — per-coroutine, safe

The API is almost identical. $g->session behaves like $_SESSION, but it's scoped to the current coroutine. When the request ends, the framework writes it to a session file (just like PHP does), and the next request with the same cookie picks it up.

🔎 How does session_start() work in ZealPHP?

ZealPHP uses the uopz extension to override session_start(), session_destroy(), and friends at boot time. When your code calls session_start(), ZealPHP's replacement reads the session cookie from the current request's G::instance(), loads the session data from file, and populates $g->session. At end of request, it writes back. This means session_start() still works — but it writes to the per-coroutine $g->session, not to the global $_SESSION.

Putting it together

Here's how the counter from Lesson 5 persists across requests — it stores the count in the session:

// The counter endpoint (route/learn.php)
$app->route('/api/learn/demo/incr', function ($request, $response) {
    session_start();
    $g = G::instance();
    $g->session['demo_counter'] = ($g->session['demo_counter'] ?? 0) + 1;
    return App::renderToString('/components/_counter_button', [
        'n' => $g->session['demo_counter'],
    ]);
});

The counter now survives page reloads, tab closures, and even server restarts (because the session is written to disk).

Why can't ZealPHP use $_SESSION directly in coroutine mode?

Key Takeaways

  • HTTP is stateless — sessions bridge the gap by giving each visitor a cookie + server-side storage
  • In ZealPHP, use $g->session instead of $_SESSION (per-coroutine isolation)
  • session_start() still works — uopz redirects it to the coroutine-safe implementation
  • Session data persists to disk — survives page reloads and server restarts