← Security

SECURITY FEATURE

Session Payloads

Session state should be explicit, typed, and reviewable — not a bag of magic keys spread across handlers.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Semitexa treats session state as a typed contract. We do not allow auth state to spread through $this->session->get('current_user') and other string-key guesses.

How it works

A dedicated class marked with #[SessionSegment] owns the session shape. Handlers read and write that payload through SessionInterface::getPayload() and SessionInterface::setPayload(), while semantic methods such as requireUserId() or clear() live on the payload itself.

Why it matters

This kills one of the most persistent sources of auth drift in PHP apps: invisible session conventions. The contract becomes explicit, reviewable, and refactor-safe instead of hiding behind magic key names and duplicated null checks.

Key concepts

#[SessionSegment]
Declares a typed session payload and binds it to one named session segment.
Session Payload
The explicit DTO-like class that owns one session concern instead of scattering string keys across handlers.
SessionInterface::getPayload()
Typed session access API that hydrates the declared payload instead of returning loose values by string key.
string-key session chaos
The legacy pattern where session contracts exist only as ad hoc names like current_user, auth_user, or user_id.

Session Contract

We ended string-key session chaos

Semitexa does not treat session state as a random key-value bag. If some state matters, it must live in a dedicated Session Payload with a typed shape and meaningful methods.

What usually goes wrong

  • String keys like current_user, auth_user, or user_id drift across handlers, middleware, and listeners until nobody knows the real contract anymore.
  • Every caller starts duplicating has/get/null checks because the session shape is implicit and fragile.
  • Renaming one key becomes a distributed grep problem instead of a refactor-safe code change.
0 magic session keys tolerated
1 typed session contract
100% refactor-safe session access

Legacy Session Mess

Handlers guess keys and patch around nulls

Session reads happen through ad hoc strings, so every caller has to remember both the key names and the failure cases.

The real auth contract lives in tribal knowledge, not in code.

Typed Session Contract

Session state is one explicit payload

A dedicated Session Payload owns the shape, and handlers ask for that payload directly through SessionInterface.

Reviewers can see the contract immediately, and refactors stay local.

Semitexa rule

  • Session state belongs to a dedicated payload class marked with #[SessionSegment].
  • Code reads session state through SessionInterface::getPayload(), not arbitrary string keys.
  • Meaningful methods such as requireUserId() or clear() belong on the payload itself.
  • If no Session Payload exists for a concern, that concern should not be writing random session keys.
#[SessionSegment] typed session contract no string keys SessionInterface::getPayload()

Verified against Semitexa Ultimate 2026.09.19.1020

Session Payloads

Semitexa treats session state as a typed contract, not as an unstructured key-value dump.

How it works

Every piece of session state belongs to a dedicated class marked with #[SessionSegment]. Handlers read and write session state through SessionInterface::getPayload() and SessionInterface::setPayload(). Arbitrary string key access is not available — if no Session Payload exists for a concern, that concern should not be writing to the session.

Why this matters

String-key sessions rot. Keys drift across handlers, middleware, and listeners until nobody knows the real contract. Renaming one key becomes a distributed grep problem. Typed Session Payloads make the contract reviewable, refactor-safe, and local to a single class.

© Harold Abelson: "Programs must be written for people to read, and only incidentally for machines to execute."

Legacy Session Access Implementation slice
<?phpdeclare(strict_types=1);namespace App\Handler\Auth;final class LegacyLoginController{    public function __invoke(Request $request, SessionInterface $session): Response    {        $user = $this->users->findByEmail($request->input('email'));        if ($user === null) {            return $this->error('Invalid credentials.');        }        if (!$user->passwordMatches($request->input('password'))) {            return $this->error('Invalid credentials.');        }        $session->set('current_user', $user->getId());        $session->set('current_user_name', $user->getDisplayName());        $session->set('auth_stage', 'authenticated');        return $this->redirect('/account');    }}

How it works

A dedicated class marked with #[SessionSegment] owns the session shape. Handlers read and write that payload through SessionInterface::getPayload() and SessionInterface::setPayload(), while semantic methods such as requireUserId() or clear() live on the payload itself.

Why it matters

This kills one of the most persistent sources of auth drift in PHP apps: invisible session conventions. The contract becomes explicit, reviewable, and refactor-safe instead of hiding behind magic key names and duplicated null checks.

Key concepts

#[SessionSegment]
Declares a typed session payload and binds it to one named session segment.
Session Payload
The explicit DTO-like class that owns one session concern instead of scattering string keys across handlers.
SessionInterface::getPayload()
Typed session access API that hydrates the declared payload instead of returning loose values by string key.
string-key session chaos
The legacy pattern where session contracts exist only as ad hoc names like current_user, auth_user, or user_id.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal