Skip to content

fix: worker mode shapes multipart fields and files as PHP does - #64

Merged
kwhorne merged 1 commit into
mainfrom
worker-post-shape
Oct 25, 2026
Merged

kwhorne merged 1 commit into
mainfrom
worker-post-shape

Conversation

@kwhorne

@kwhorne kwhorne commented Oct 25, 2026

Copy link
Copy Markdown
Owner

The problem

In worker mode, $r['post'] had each field's name as a key, exactly as sent:

  • tags[] was a key of its own, and a second tags[] overwrote the first;
  • user[name] was never nested.

Laravel saw neither tags nor user in any form with a file input, which are the forms that post multipart. The new smoke probe showed [null, null, null] for tags, user and user.avatar.

The fix

  • The shim registers each field with php_register_variable_safe, the way main/rfc1867.c builds $_POST:
    • [] appends and [x] nests;
    • a repeated name keeps its last value;
    • . and space in a top-level name become _;
    • at most max_input_vars fields.
  • Not through sapi_module.input_filter, unlike rfc1867. I found this while testing: ext/filter's filter writes POST variables into the real $_POST and its filter_input() store, then returns 0. In a worker, which is one request that never ends, that would grow with every request and carry fields from one request into the next. The only thing skipped is filter.default, which PHP has deprecated.
  • The max_input_vars warning goes to stderr. Raised inside askr_handle_request(), an app's error handler would turn it into an exception in the middle of the worker loop.
  • laravel-worker.php nests the files the same way, with one parse_str() of the field names. user[avatar] now reaches $request->file('user.avatar'); before, only name and name[] worked.

Verification

  • e2e a_worker_s_post_fields_are_shaped_as_php_shapes_post uses PHP itself as the oracle: the same body is sent to a per-request server, where PHP builds $_POST.
    • A second request shows that nothing carries over, and that the worker's own $_POST is never written.

    • It fails on the old shim:

      left:  {"tags[]":"b","note":"again","user[name]":"kh",...,"a.b":"1","x y":"2",...}
      right: {"tags":["a","b"],"note":"again","user":{"name":"kh","roles":["admin"]},"a_b":"1","x_y":"2","arr":{"k.l":"3"}}
      
  • Laravel smoke: tags[], user[name] and user[avatar] reach Laravel. Passes on Laravel 12 and on a fresh Laravel 13.
  • phpt: all 32 as expected.

A worker script that read raw names ($r['post']['tags[]']) now gets PHP's shape. That is in the CHANGELOG.

$r['post'] had each field's name as a key exactly as sent: "tags[]" was a
key of its own, a second "tags[]" overwrote the first, "user[name]" was
never nested. Laravel saw neither tags nor user in any form with a file
input (those post multipart).

The shim now registers each field with php_register_variable_safe, as
main/rfc1867.c builds $_POST: [] appends, [x] nests, a repeated name keeps
its last value, . and space in a top-level name become _, at most
max_input_vars fields. Not through sapi_module.input_filter: ext/filter's
filter writes POST variables into the real $_POST and its filter_input()
store and returns 0, which in a worker (one never-ending request) would
grow and carry fields between requests. The over-limit warning goes to
stderr, not to an app error handler mid-loop.

examples/laravel-worker.php nests the files the same way, with one
parse_str() of the field names (user[avatar] reaches
$request->file('user.avatar'); only name and name[] worked before).

- e2e a_worker_s_post_fields_are_shaped_as_php_shapes_post: PHP itself is
  the oracle (same body to a per-request server); a second request shows
  nothing carries over and $_POST is never written. Fails on the old shim
  with "tags[]":"b".
- laravel-smoke: tags[], user[name] and user[avatar] reach Laravel
  (all three null before); Laravel 12 and a fresh 13.
@kwhorne
kwhorne merged commit 5c1ffa2 into main Oct 25, 2026
6 checks passed
@kwhorne
kwhorne deleted the worker-post-shape branch October 25, 2026 10:53
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