AI blog · DotApp PHP Framework 2.0
How to use sessions in DotApp PHP Framework (DSM)
Application state belongs in DSM::use('Shop'). That is the session bag for the Shop module: cart lines, wizard steps, flash flags.
Do not call session_start(). Do not read or write $_SESSION from Shop. Cookie flags in app/config.php configure the driver; they do not change this rule.
get() returns null when a key is missing — not false. status() returns $this (misleading name: it is not PHP’s session status integer).
Drivers: default, file, file2, db, redis, registered in app/config.php. Prefer default or file unless you need shared sessions.
Reserved keys belong to the framework. A custom driver must implement twelve methods, not only the six that Config::sessionDriver validates.
This article is a complete Shop cart: set, get, delete, save — plus cookie flags and the driver matrix.
Common mistakes
| Wrong | Right |
|---|---|
$_SESSION['cart'] = $items; or session_start(). |
DSM::use('Shop')->set('cart', $items)->save(); |
Treat get('cart') miss as false. |
Miss is null. Use ?? []. |
if ($dsm->status()) to see if the session is open. |
status() returns $this. Do not test it as an int. |
Store the cart under _enc_key or _bridge.x. |
Those names are reserved. Use cart inside DSM::use('Shop'). |
Switch to Redis with empty redis_password and empty prefix. |
Redis throws on construct if any required redis_* value is empty. Fill them first. |
Call regenerate_id() on the DB driver and assume it is solid. |
The DB driver has regenerate_id bugs. Prefer default or file. |
Register a custom driver with only load/save/get/set/delete/clear. |
DSM also calls start, destroy, status, regenerate_id, session_id, gc — twelve methods. |
| Put the public item list in DSM so “it persists”. | Shared catalogs go in Cache::use('Shop'). DSM is per visitor. |
When to use DSM — and when not
Use DSM for state that belongs to this browser: a cart, a multi-step checkout index, a “just added” flag.
Do not use it for data every visitor should share (cache that). Do not use it as a database. Do not use it for secrets that belong in Config::module.
Cookie flags (secure, httponly, samesite) belong in app/config.php. They harden the cookie the driver sets. Shop code still never touches $_SESSION.
Always DSM::use('Shop') (module namespace). A null name is a framework internal bag for ID helpers — not your cart.
Open the Shop bag
use Dotsystems\App\Parts\DSM;
$sess = DSM::use('Shop');
$sess->set('cart', $items);
$cart = $sess->get('cart') ?? [];
$sess->delete('cart');
$sess->save();
use($sessname = null) returns a singleton per name and calls load() on first construct.
Chain set / save. After delete, still save() if the driver needs an explicit persist (file/db/redis).
API table
| Method | Args | Exact return |
|---|---|---|
DSM::use($sessname = null) |
Pass 'Shop' |
DSM singleton |
get($name) |
Key inside the bag | Value, or null when missing — not false |
set($name, $value) |
Key + value | $this |
delete($name) / clear() / gc() |
Key, or none | Driver return (often void / mixed) |
load() / save() / start() / destroy() |
None | $this |
regenerate_id($deleteOld = false) |
Whether to drop the old id | $this — DB driver is buggy here |
session_id() |
None | string |
session_id($new) |
New id | Driver result — throws if that ID already exists |
status() |
None | $this — not PHP’s session status int |
Reserved keys
The framework already stores values in session space. Do not reuse these names inside Shop:
| Key / pattern | Owner |
|---|---|
_enc_key |
Framework |
_bridge.* |
Bridge store |
_router.* |
Router |
_request.auth |
Request auth |
_formCSRF |
Form CSRF helper |
_default_limiter |
Default limiter |
DSM::use('Shop') already namespaces the bag. Still pick plain keys such as cart, checkout.step. Do not start Shop keys with _ plus those prefixes.
A lone CSRF token is a narrow guarantee. Pair form posts with crcCheck() and, for Bridge traffic, the Bridge contract — not a raw token check alone.
Bridge: How to call PHP from JavaScript with DotBridge.
Forms: How to create secure forms in DotApp PHP Framework.
Drivers
| default | file | file2 | db | redis | |
|---|---|---|---|---|---|
| Storage | Driver-internal PHP session slot for the bag name | One file per session | File per {id}_{sessname} |
DB row | Redis key |
| Needs | — | Writable session.file_driver_dir |
file_driver_dir2 |
Table session.database_table |
Redis extension + filled session.redis_* |
| GC | PHP | Cron include | Cron | SQL delete | TTL scan |
| Notes | Simplest | All names in one file | More files, smaller writes | regenerate_id bugs |
Throws if a required redis_* is empty |
The default driver’s internals may sit on PHP’s session array. That is the driver’s problem. Shop still uses only DSM::use('Shop').
Register and select in app/config.php before new DotApp():
use Dotsystems\App\Parts\SessionDriverDefault;
use Dotsystems\App\Parts\SessionDriverFile;
use Dotsystems\App\Parts\SessionDriverFile2;
use Dotsystems\App\Parts\SessionDriverDB;
use Dotsystems\App\Parts\SessionDriverRedis;
Config::sessionDriver('default', SessionDriverDefault::driver());
Config::session('driver', 'default');
Config::session('lifetime', 3600);
Config::sessionDriver('file', SessionDriverFile::driver());
Config::session('file_driver_dir', '/app/runtime/SessionDriverFile');
Config::sessionDriver('file2', SessionDriverFile2::driver());
Config::sessionDriver('db', SessionDriverDB::driver());
Config::session('database_table', 'users_sessions');
Config::sessionDriver('redis', SessionDriverRedis::driver());
Config::session('redis_host', '127.0.0.1');
Config::session('redis_port', 6379);
Config::session('redis_timeout', 2);
Config::session('redis_database', 0);
Config::session('redis_prefix', 'session:');
Redis construction throws \Exception if any of the required session keys are null or '' (host, port, timeout, database, prefix, plus cookie name / lifetime / path / secure / httponly / samesite). Password may be empty. Fill the rest before you switch Config::session('driver', 'redis').
Config file: How app/config.php works in DotApp PHP Framework.
Session cookie flags in config.php
These flags configure the session driver. They are not an API for $_SESSION.
Config::session('lifetime', 3600);
Config::session('cookie_name', 'dotapp_session');
Config::session('path', '/');
Config::session('secure', true); // HTTPS
Config::session('httponly', true);
Config::session('samesite', 'Strict');
Getter Config::session('lifetime') returns the value or null. Setter returns void. Passing false is a set, not a get (null is the getter).
Custom driver: twelve methods
Config::sessionDriver($name, $driver) only validates that load, save, get, set, delete, clear exist and are callable. Incomplete map → \Exception "Incompatible driver !" or "All driver functions must be callable !".
DSM still invokes start, destroy, status, regenerate_id, session_id, gc. Implement all twelve or Shop will fatal when those methods run.
Getter Config::sessionDriver($name) returns the array or throws if undefined.
Config::sessionDriver('memory', [
'load' => function ($dsm) {},
'save' => function ($dsm) {},
'get' => function ($name, $dsm) { return null; },
'set' => function ($name, $value, $dsm) {},
'delete' => function ($name, $dsm) {},
'clear' => function ($dsm) {},
'start' => function ($dsm) {},
'destroy' => function ($dsm) {},
'status' => function ($dsm) {},
'regenerate_id' => function ($deleteOld, $dsm) {},
'session_id' => function ($new, $dsm) { return ''; },
'gc' => function ($dsm) {},
]);
Complete module.init.php
<?php
namespace Dotsystems\App\Modules\Shop;
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Router;
class Module extends \Dotsystems\App\Parts\Module
{
public function initialize($dotApp)
{
Config::module('Shop', 'prefix') ?? Config::module('Shop', 'prefix', '/shop');
$p = Config::module('Shop', 'prefix');
Router::get($p . '/', 'Shop:Cart@index!', Router::STATIC_ROUTE);
Router::post($p . '/cart/add', 'Shop:Cart@add!', Router::STATIC_ROUTE);
Router::post($p . '/cart/remove', 'Shop:Cart@remove!', Router::STATIC_ROUTE);
Router::post($p . '/cart/clear', 'Shop:Cart@clearCart!', Router::STATIC_ROUTE);
}
public function initializeRoutes()
{
return ['/shop', '/shop/*'];
}
public function initializeCondition($routeMatch)
{
return $routeMatch;
}
}
new Module($dotApp);
Complete cart: set / get / delete / save
File: app/modules/Shop/Controllers/Cart.php.
JSON posts: Router + Response::json + $request->crcCheck().
After a passing check, read fields from $request->data(true)['data'] (unprotected payload).
If you also use Input groups, call form() with an error callback — a mismatch returns false / null or throws.
<?php
namespace Dotsystems\App\Modules\Shop\Controllers;
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\DSM;
use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Renderer;
use Dotsystems\App\Parts\Response;
class Cart extends \Dotsystems\App\Parts\Controller
{
public static function index($request)
{
$cart = DSM::use('Shop')->get('cart') ?? [];
$html = Renderer::new()
->module('Shop')
->setView('cart')
->setViewVar('title', 'Cart')
->setViewVar('cart', $cart)
->setViewVar('prefix', Config::module('Shop', 'prefix'))
->renderView();
if ($html === '') {
Logger::use()->error('Shop cart view produced empty output');
return new Response(500, 'Template error');
}
return $html;
}
public static function add($request)
{
if (!$request->crcCheck()) {
return Response::json(['status' => 0, 'message' => 'Bad request'], 400);
}
$payload = $request->data(true)['data'] ?? [];
$id = (int) ($payload['item_id'] ?? 0);
$qty = (int) ($payload['qty'] ?? 1);
if ($id < 1 || $qty < 1) {
return Response::json(['status' => 0, 'message' => 'Invalid item'], 400);
}
$sess = DSM::use('Shop');
$cart = $sess->get('cart') ?? [];
$cart[$id] = ($cart[$id] ?? 0) + $qty;
$sess->set('cart', $cart);
$sess->save();
return Response::json(['status' => 1, 'cart' => $cart]);
}
public static function remove($request)
{
if (!$request->crcCheck()) {
return Response::json(['status' => 0, 'message' => 'Bad request'], 400);
}
$payload = $request->data(true)['data'] ?? [];
$id = (int) ($payload['item_id'] ?? 0);
$sess = DSM::use('Shop');
$cart = $sess->get('cart') ?? [];
unset($cart[$id]);
if ($cart === []) {
$sess->delete('cart');
} else {
$sess->set('cart', $cart);
}
$sess->save();
return Response::json(['status' => 1, 'cart' => $cart]);
}
public static function clearCart($request)
{
if (!$request->crcCheck()) {
return Response::json(['status' => 0, 'message' => 'Bad request'], 400);
}
$sess = DSM::use('Shop');
$sess->delete('cart');
$sess->save();
return Response::json(['status' => 1, 'cart' => []]);
}
}
File: app/modules/Shop/views/cart.view.php.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{ var: $title }}</title>
</head>
<body>
<h1>{{ var: $title }}</h1>
<ul>
{{ foreach $cart as $id => $qty }}
<li>Item {{ var: $id }} × {{ var: $qty }}</li>
{{ /foreach }}
</ul>
<script src="/assets/dotapp/dotapp.js"></script>
</body>
</html>
Gotchas
status()returns$this. Never compare it toPHP_SESSION_ACTIVE.session_id($new)throws if that ID already exists.- DB driver: bugs in
regenerate_id. Do not pickdbonly to “be safer” on login rotation. - Redis driver: throws in the constructor when a required
redis_*(or cookie flag used as required) is empty. It also usesKEYSscans — fine for small installs, costly at scale. - Do not confuse DSM with
Cache::use('Shop'). Cache misses arenulltoo, but the lifetime and audience are different. Config::session('secure', true)only affects the cookie. HTTPS still has to be real on the host.
FAQ
Why is $_SESSION forbidden if the default driver uses it internally?
The driver owns that mapping. Shop must go through DSM::use('Shop') so reserved keys, namespacing, and driver swaps stay consistent.
What does get() return for a missing cart?
null. Write DSM::use('Shop')->get('cart') ?? [].
How do I read PHP session status?
You do not, from Shop. status() is chainable and returns $this. It is not the PHP status int.
I set redis_host and still get an exception.
Every required Redis session key must be non-empty, including prefix, port, timeout, database, and the cookie flags the driver validates. Empty string counts as missing.
Is regenerate_id safe on the DB driver?
No. That driver has known bugs there. Prefer default or file unless you need shared sessions and accept the gap.
Config::sessionDriver accepted my six callables. Why does start() crash?
Validation only checks six keys. DSM calls twelve. Add the rest.
Can I cache the cart to speed it up?
No. The cart is visitor state. Cache::use('Shop') is for rebuildable shared (or explicitly contextual) data. See How to use cache in DotApp PHP Framework.
Is storing a token in DSM enough for POST safety?
A lone CSRF token is a narrow guarantee. Use crcCheck() on JSON posts, and formName + Bridge where those stacks apply. Do not invent a parallel token protocol.
See also
- How app/config.php works in DotApp PHP Framework
- How to use cache in DotApp PHP Framework
- How to create a module in DotApp PHP Framework
- How module initialization works in DotApp PHP Framework
- How to create secure forms in DotApp PHP Framework
- How to call PHP from JavaScript with DotBridge
- Official documentation