Skip to content

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

Open
Guikingone wants to merge 8 commits into
illegalstudio:mainfrom
Guikingone:feat/sapi-sessions
Open

feat(web): implement PHP session support for --web mode#455
Guikingone wants to merge 8 commits 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,
$_SESSION, session constants, file persistence, custom handlers, runtime
configuration, upload progress, and trans_sid rewriting.

The implementation is version-aware across every maintained PHP minor. Use
--php-version=8.2|8.3|8.4|8.5 (default: 8.5) to select version-dependent
session behavior. The audit references the official php-src branches
PHP-8.2, PHP-8.3, PHP-8.4, and PHP-8.5 and their ext/session PHPT
suites.

The branch is squashed to one commit and rebased onto current main.

Session surface

  • $_SESSION, SID, PHP_SESSION_NONE, PHP_SESSION_ACTIVE, and
    PHP_SESSION_DISABLED.
  • All 23 PHP session functions exposed by ext/session.
  • SessionHandlerInterface, SessionIdInterface,
    SessionUpdateTimestampHandlerInterface, and SessionHandler.
  • files, object, and legacy callable save handlers.
  • php, php_binary, and php_serialize wire formats, including binary
    strings and embedded NUL bytes.
  • Strict mode, lazy writes, GC, SID configuration, cache limiters, cookies,
    read_and_close, GET/POST SID transport, referer checks, regeneration,
    shutdown write-close, upload progress, and trans-SID rewriting.

Maintained PHP version compatibility

  • Correct common defaults for PHP 8.2–8.5:
    session.cookie_httponly=0, empty SameSite, and
    session.use_strict_mode=0.
  • PHP 8.4 gates:
    • legacy procedural session_set_save_handler() deprecation;
    • GET/POST/trans-SID, referer, SID length, and SID bits deprecations;
    • invalid gc_probability / gc_divisor warnings and rejection;
    • the 256-character session_create_id() prefix ValueError.
  • PHP 8.5 gates:
    • CHIPS session.cookie_partitioned, partitioned cookie parameters, and
      Secure enforcement;
    • string-only session_start() option keys;
    • scalar option values and int-compatible read_and_close validation;
    • invalid | session-key warning for the php serializer.
  • session_create_id() now distinguishes invalid characters, embedded NULs,
    and the versioned prefix-length boundary.
  • session_set_cookie_params() handles case-insensitive keys, numeric/unknown
    key warnings, the at-least-one-valid-key rule, cookie-disable state, and the
    PHP 8.5-only partitioned key.

Runtime and lifecycle

  • File records use PHP's [depth;[mode;]]path grammar, flock, safe open
    flags, complete I/O loops, recursive GC, and correct lock/unlink ordering.
  • Handler state is active during callbacks; read failures abort startup;
    abort/reset/destroy/regenerate follow PHP ordering and persistence semantics.
  • Lazy write touches unchanged files or calls updateTimestamp() for capable
    user handlers.
  • Session byte transport separates inbound staging from outbound publication,
    retaining request-scoped owners so bridge calls cannot invalidate borrowed
    payloads.
  • Request globals, static state, bridge buffers, and locks are reset or released
    between requests.

Runtime configuration and diagnostics

  • ini_get, ini_set, and ini_get_all cover the session directive surface;
    session.cookie_partitioned appears only in the PHP 8.5 profile.
  • PHP-compatible INI strings and access metadata, including PERDIR
    auto-start/upload-progress settings.
  • trigger_error and error_log route PHP-style notices, warnings, and
    deprecations to worker stderr; file append logging is supported.
  • session.auto_start and upload-progress deployment settings can be seeded
    through their ELEPHC_SESSION_* environment variables.

Upload progress and trans-SID

  • Multipart upload progress is persisted during body draining through short
    locked read-modify-write cycles, so another worker can poll it concurrently.
  • Cookie, GET, POST, php, php_binary, and php_serialize paths are covered.
  • Trans-SID rewrites eligible same-origin URLs, forms, and Location headers
    before compression while leaving external, unsafe, already-tagged, non-HTML,
    and non-UTF-8 content untouched.

Tests and validation

The branch now contains 41 end-to-end HTTP session scenarios plus bridge,
codegen, ownership, callable, optimizer, and constant regressions.

Post-rebase local validation:

  • cargo build
  • cargo test -p elephc-web session50 passed
  • cargo test --test web_session_tests37 passed in the grouped run;
    the subsequently added PHP 8.5 invalid-key case and updated version matrix
    were rerun focused
  • PHP 8.2/8.3/8.4/8.5 profile matrix under the CI nextest profile, sequentially
    and with per-test timeouts — 4 profiles passed in 72.6 seconds total
  • PHP 8.3/8.4 deprecation gate — passed
  • PHP 8.4/8.5 invalid-key warning gate — passed
  • CLI parsing/default tests for --php-version3 passed
  • focused partitioned-cookie, trans-SID, callable-handler, binary/NUL,
    regeneration, abort, and ownership regressions — passed
  • cargo fmt --all -- --check
  • git diff --check

CI owns the complete supported-target matrix: macOS AArch64, Linux AArch64,
and Linux x86_64.

Documentation and examples

  • docs/php/sessions.md
  • docs/specs/session-support.md
  • docs/compiling/cli-reference.md
  • docs/beyond-php/web.md
  • examples/web-session
  • examples/web-session-upload
  • examples/web-session-trans-sid

Scope notes

  • Sessions remain --web-only because they require an HTTP request lifecycle.
  • The built-in module is files; other backends use PHP save handlers.
  • A host php.ini and dynamically loaded PHP session modules are outside the
    standalone compiler/runtime model.
  • SID remains the predefined empty string; automatic trans-SID rewriting owns
    transparent propagation in compiled web responses.
  • Diagnostic text omits Zend's source-file/line suffixes.

@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 3 times, most recently from 8c4ef8f to 22c7437 Compare July 4, 2026 12:35
@github-actions github-actions Bot added area:eir Touches EIR definitions, lowering, validation, or passes. area:types Touches type checking, inference, or compatibility. area:web Touches --web mode, its prelude, or elephc-web. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:l Large pull request. type:feature Introduces new user-visible behavior or capabilities. labels Jul 13, 2026
@Guikingone
Guikingone force-pushed the feat/sapi-sessions branch from 22c7437 to de6f286 Compare July 15, 2026 12:17
@github-actions github-actions Bot added size:xl Very large pull request that needs deliberate review planning. and removed size:l Large pull request. labels Jul 15, 2026
@Guikingone
Guikingone force-pushed the feat/sapi-sessions branch from de6f286 to ac4de27 Compare July 15, 2026 13:07
@Guikingone Guikingone self-assigned this Jul 15, 2026
@Guikingone
Guikingone force-pushed the feat/sapi-sessions branch from ac4de27 to f8d5c2e Compare July 15, 2026 15:04
@github-actions github-actions Bot added area:codegen Touches target-aware assembly or backend lowering. and removed area:types Touches type checking, inference, or compatibility. labels Jul 15, 2026
@Guikingone
Guikingone force-pushed the feat/sapi-sessions branch 2 times, most recently from 37bc436 to e8af9e9 Compare July 15, 2026 17:54
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 e8af9e9 to f785dcb Compare July 15, 2026 18:59
@Guikingone
Guikingone marked this pull request as ready for review July 15, 2026 20:25
@Guikingone
Guikingone requested a review from nahime0 July 15, 2026 20:25
@Guikingone

Copy link
Copy Markdown
Collaborator Author

@nahime0 Here's the support for session, it only works on --web mode (interfaces can be used to extend the behavior) 🙂

@nahime0

nahime0 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Hi @Guikingone. Great, thanks!
I'll look at this first thing tomorrow morning.

@nahime0

nahime0 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Hi @Guikingone

I'm taking time for this cause the time needed for Non-Codegen tests exploded.
The issue is that the prelude needed for sessions is really huge, and is injected in every binary compiled with --web.

I'm trying to understand if the prelude can be somehow skimmed down, or maybe injected only if the code calls any sessions function.

I'll keep you posted.

@nahime0

nahime0 commented Jul 17, 2026

Copy link
Copy Markdown
Member

Hi @Guikingone,

I’ve completed the session implementation audit and the follow-up work on this PR.

The main fixes include:

  • releasing the files-handler lock when session encoding fails;
  • making SessionHandler::close() release its lock correctly;
  • preventing attacker-controlled serialized collection counts from causing excessive CPU work;
  • validating session_name() consistently to prevent invalid cookie/header names;
  • accepting prefixed session IDs independently of session.sid_length;
  • completing boxed-Mixed callable support for invokable objects, instance arrays, and static method arrays;
  • aligning upload-progress configuration with the subsequent session_start() call;
  • fixing the Linux x86_64 unserialize ownership marker regression;
  • making DCE recurse through synthetic control flow.

I also clarified that session.referer_check and the weak cookie/strict-mode defaults match PHP behavior, while documenting that
they should not be considered secure deployment defaults.

The session prelude was then optimized substantially:

  • callable wrappers, invokers, and descriptors are shared across equivalent call sites;
  • the legacy callable session-handler adapter is emitted only when reachable;
  • unused session functions are pruned from the web prelude;
  • auto-start uses a compact core path without retaining the full session_start() option parser.

On the local web-hello benchmark, this reduced compilation from roughly 14.2 seconds and 24 MB of assembly to about 0.8 seconds
and 1.3 MB. The upload-progress test that previously took 26–27 seconds now completes in roughly 3 seconds.

Web integration tests have also been moved out of the Non-Codegen jobs and split into two balanced shards per supported target.

Validation is now green across macOS AArch64, Linux AArch64, and Linux x86_64. GitHub reports 112 passing checks, no conflicts,
and a clean merge state.

A possible further optimization—sharing the remaining runtime callable selector itself—is tracked separately in #552 and is not a
blocker for this PR.

From my side, this is ready to merge. Thanks again for the substantial session implementation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:codegen Touches target-aware assembly or backend lowering. area:eir Touches EIR definitions, lowering, validation, or passes. area:web Touches --web mode, its prelude, or elephc-web. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:xl Very large pull request that needs deliberate review planning. type:feature Introduces new user-visible behavior or capabilities.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants