Skip to content

feat(web): implement PHP session support for --web mode#455

Draft
Guikingone wants to merge 1 commit into
illegalstudio:mainfrom
Guikingone:feat/sapi-sessions
Draft

feat(web): implement PHP session support for --web mode#455
Guikingone wants to merge 1 commit into
illegalstudio:mainfrom
Guikingone:feat/sapi-sessions

Conversation

@Guikingone

@Guikingone Guikingone commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Overview

Full PHP session support for elephc's --web mode: the session_*() API, the $_SESSION superglobal, PHP_SESSION_* / SID constants, file-based storage with flock concurrency safety, and cookie / cache-limiter handling — plus complete parity for custom save handlers, session runtime configuration, a warning channel, real-time upload progress, and trans_sid URL rewriting.

Sessions are --web-only: the bridge (crates/elephc-web) provides C-ABI session primitives and the web prelude (src/web_prelude.rs) defines the PHP-visible functions on top of them. Per-worker state lives in Rust process statics (no PHP globals to leak) and is reset each request.

What's included

Core & parity

  • All session_*() functions; $_SESSION; predefined PHP_SESSION_* constants; file-based storage matching session.save_handler = files in sys_get_temp_dir() by default.
  • Serialize handlers (php / php_serialize / php_binary), strict mode, lazy_write, probabilistic auto-GC (gc_probability/gc_divisor/gc_maxlifetime), sid_length/sid_bits_per_character, complete cache_limiter headers.
  • session_abort / session_reset / session_gc / session_encode / session_decode / session_commit / session_register_shutdown, with session_write_close() auto-called at handler end via a finally block.

Custom save handlers

  • Object form: SessionHandlerInterface, SessionHandler, SessionIdInterface, SessionUpdateTimestampHandlerInterface.
  • Legacy 6-callable form (deprecated in PHP 8.4): function-name strings, closures, and instance array callables [$obj, 'method']. Static array callables ['Class', 'method'] use the object form.

Runtime configuration (ini)

  • ini_get() / ini_set() / ini_get_all() scoped to the session.* directive surface (PHP ini_get string convention — integers as decimals, booleans as '1'/''; unknown directives return false).
  • session.auto_start seeded per worker from ELEPHC_SESSION_AUTO_START (the php.ini-PERDIR analog in a compiled binary); when on, the request bootstrap auto-starts the session.
  • session.referer_check: a cookie-supplied id whose request Referer lacks the configured substring is invalidated, starting a fresh session (session-fixation defense).

Warning channel

  • E_* error-level constants (global, like PHP).
  • error_log() and trigger_error() write to the worker's stderr via fwrite(STDERR, …) (error_log type 3 appends to a file); session misuse emits the real PHP warning/notice text (e.g. session_start() while active → E_NOTICE; mutating id/name/save_path/cookie-params while active → E_WARNING). "Headers already sent" cannot occur because output is buffered.

Streaming upload progress

  • session.upload_progress is tracked in real time as the multipart request body is drained frame-by-frame (the body is still buffered in full for the handler), then written into the session file under short flock(LOCK_EX) read-modify-write cycles — the lock is never held across the upload, so a concurrent poll request reads progress via the flock'd file as the cross-worker channel.
  • Progress array follows php.net's documented shape; serialized in the session's configured handler (php + php_serialize); cleanup on/off. Config is read at drain time (before the handler), so it is process-level via ELEPHC_SESSION_UPLOAD_PROGRESS_{ENABLED,CLEANUP}, mirroring session.auto_start.

URL rewriting (trans_sid)

  • When session.use_trans_sid=1, session.use_only_cookies=0, and the client sent no session cookie, the buffered HTML response is rewritten (before gzip) to propagate the session id: NAME=ID appended to same-origin href/src attributes (a/area/frame per session.trans_sid_tags) and a hidden <input> injected into <form> tags; same-origin Location headers are rewritten too. Off-host, mailto:/javascript:, fragment-only, and already-tagged URLs are left untouched; non-text/html and non-UTF-8 bodies pass through byte-identical. Default config (use_only_cookies=1) never rewrites: zero overhead.

Enabling compiler fixes

  • EIR: dispatch boxed-Mixed instance array callables [$obj, 'method']; call_user_func / call_user_func_array accept a Mixed/Union callback dispatched by runtime tag.
  • EIR static-property store ownership: acquire a borrowed refcounted value so it survives the borrow (custom-handler dispatch), and release the previous occupant of a boxed Mixed/nullable-object slot on overwrite instead of leaking one object per reassignment.
  • Gate superglobal type seeding on --web, fixing a pre-existing CLI SIGSEGV when a non-web program reads $_SERVER/$_SESSION before assignment.

Testing

  • Full web_session end-to-end suite (27/0): object and 6-callable save-handler round-trips, session_abort/reset semantics, ini_get/ini_set, referer_check, error_log/trigger_error (incl. stderr capture), streaming upload-progress round-trip, and trans_sid rewrite / no-rewrite.
  • Unit tests: boxed-Mixed callable dispatch, upload-progress serializer (cross-checked against php -r) and raw-splice read-modify-write, trans_sid rewriter, and the E_* constants.
  • Zero warnings (Rust build and PHP compilation), git diff --check clean, module preambles + function docblocks present, assembly-comment alignment preserved.

Scope & notes

  • --web only — sessions require the HTTP request lifecycle; the ini, warning, and trans_sid surfaces are --web-scoped alongside them.
  • SID stays empty: automatic URL rewriting supersedes manually appending SID, matching use_trans_sid=1 usage.
  • upload_progress / trans_sid config is honored, with the drain-time upload_progress toggles configurable via the ELEPHC_SESSION_* env vars (the php.ini analog).

Docs

docs/php/sessions.md, docs/specs/session-support.md, ROADMAP.md (Phase 5 / 5.1 / 5.2), and an examples/web-session demo (session counter + ini_set/ini_get).

@Guikingone Guikingone marked this pull request as draft July 2, 2026 20:30
@Guikingone Guikingone changed the title feat: implement PHP session support for --web mode feat(web): implement PHP session support for --web mode Jul 2, 2026
@Guikingone Guikingone force-pushed the feat/sapi-sessions branch 2 times, most recently from bf526ec to 8c4ef8f Compare July 4, 2026 12:07
Full PHP session support for elephc's --web mode: the session_*() API, the
$_SESSION superglobal, PHP_SESSION_* / SID constants, file-based storage with
flock concurrency safety, and cookie/cache-limiter handling — plus complete
parity for custom save handlers and session runtime configuration.

Core & parity:
- All session_*() functions; $_SESSION; predefined PHP_SESSION_* constants;
  serialize handlers (php / php_serialize / php_binary), strict mode,
  lazy_write, probabilistic auto-GC, sid_length/bits, cache_limiter headers,
  session_abort/reset/gc/encode/decode/commit/register_shutdown.
- Custom save handlers via the object form (SessionHandlerInterface,
  SessionHandler, SessionIdInterface, SessionUpdateTimestampHandlerInterface)
  and the legacy 6-callable form (function-name, closure, and instance array
  callables).

Runtime configuration (ini):
- ini_get()/ini_set()/ini_get_all() scoped to the session.* directive surface
  (PHP ini_get string convention; unknown directives return false).
- session.auto_start seeded per worker from ELEPHC_SESSION_AUTO_START; when on,
  the request bootstrap auto-starts the session.
- session.referer_check: a cookie-supplied id whose request Referer lacks the
  configured substring is invalidated (session-fixation defense).

Warning channel:
- E_* error-level constants; error_log() and trigger_error() write to the
  worker stderr via fwrite(STDERR); session misuse emits the real PHP
  warning/notice text.

Streaming upload progress:
- session.upload_progress is tracked in real time as the multipart request body
  is drained (frame-by-frame) and written into the session file under short
  flock(LOCK_EX) read-modify-write cycles, so a concurrent poll request reads
  progress via the flock'd file; cleanup on/off; php + php_serialize handlers.

URL rewriting (trans_sid):
- When session.use_trans_sid=1, use_only_cookies=0, and no session cookie was
  sent, the buffered HTML response is rewritten (before gzip) to propagate the
  session id: NAME=ID appended to same-origin href/src attributes and a hidden
  input injected into <form> tags; same-origin Location headers rewritten too.

Enabling compiler fixes:
- EIR: dispatch boxed-Mixed instance array callables [$obj, 'method'];
  call_user_func/_array accept a Mixed/Union callback dispatched by runtime tag.
- EIR static-property store ownership: acquire a borrowed value so it survives
  the borrow, and release the previous occupant of a boxed Mixed/nullable slot
  on overwrite instead of leaking it.
- Gate superglobal type seeding on --web (fixes a pre-existing CLI SIGSEGV).

Tests: full web-session e2e suite plus unit coverage for the callable dispatch,
upload-progress serializer/RMW, trans_sid rewriter, and error constants. Docs
(docs/php/sessions.md, spec, ROADMAP) and three runnable examples
(examples/web-session, web-session-upload streaming progress, and
web-session-trans-sid cookieless URL rewriting) included.
@Guikingone Guikingone force-pushed the feat/sapi-sessions branch from 8c4ef8f to 22c7437 Compare July 4, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant