Repository navigation
fix: make getVar() read merged superglobals instead of stale $_REQUEST - #10587
rahul05ranjan wants to merge 14 commits into
Conversation
|
Hi there, @rahul05ranjan! 👋 Thank you for sending this PR! We expect the following in all Pull Requests (PRs).
Important We expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works. If pull requests do not comply with the above, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work See https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md |
a5e40a0 to
908744f
Compare
|
Hi. See #10205 |
|
Thanks for the pointer, @neznaika0 — I see now that #10205 took this same Based on the discussion in #9872, the preferred direction is to avoid mutating I'll drop the |
|
Reworked as discussed. The
Changes:
@neznaika0 @paulbalandan — does this match the direction you had in mind from #9872? |
7229261 to
1bae1ba
Compare
|
Update: the rework is complete and all commits are now GPG-signed. Summary of the final state:
The Carson checks (signed-commits, no-merge-commits, pr-title-linter) are all passing. The remaining GitHub Actions workflows are showing @neznaika0 @paulbalandan — whenever you have a moment, could you take a look? Thanks! |
neznaika0
left a comment
There was a problem hiding this comment.
I’m not very good at evaluating PR — I don’t know the exact direction in this matter. For myself, I’ve decided not to use $_REQUEST and getVar(). Do you know any specific cases where this is 100 % necessary? In most cases, it’s better to use an explicit data source (GET, POST, COOKIE).
Earlier, it was said that this is a system class (for tests), and for the user, you need to use Request.
Wait for the member answers.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical and moderate findings remain around request overrides and synchronizing $_REQUEST.
Review effort: Lite
Findings: 2
Open (2)
What changed in this PR
Updates request-variable retrieval so getVar() reflects current GET/POST/COOKIE data after URI parsing.
Changes:
- Adds merged request-data retrieval.
- Refactors reusable array-fetching logic.
- Updates request and validation tests.
| File | Summary |
|---|---|
tests/system/Validation/ValidationTest.php |
Adjusts validation fixtures to use POST data. |
tests/system/SuperglobalsTest.php |
Tests merged request-data behavior. |
tests/system/HTTP/IncomingRequestTest.php |
Updates request-variable fixtures. |
system/Superglobals.php |
Builds merged request data. |
system/HTTP/RequestTrait.php |
Extracts reusable array-fetching logic. |
system/HTTP/IncomingRequest.php |
Uses merged data in getVar(). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@neznaika0 thanks for the review — and your instinct here is exactly right. To be clear, this PR doesn't add any new reliance on The direction matches what @paulbalandan suggested in #9872: instead of mutating On "100% necessary" cases: I agree — there's no case where |
b2abc93 to
983c2e3
Compare
|
@paulbalandan — gentle ping on this one. The rework is complete and follows the direction you outlined in #9872: All Carson checks pass (signed-commits, no-merge-commits, pr-title-linter). The remaining GitHub Actions workflows are showing |
paulbalandan
left a comment
There was a problem hiding this comment.
Hi, thanks for picking this up. Just a few initial comments:
- Since the approach changed, the PR title and body should be edited to reflect the new approach taken. This is because the PR would be squashed on merge with the PR title as the commit message.
- The merging of superglobals seems to be not the way PHP would do it. First, when the data contains a numeric key, like when parsing
100=foo, the array_merge would just renumber it. Second, it is not recursive unlike what PHP do when it creates a $_REQUEST. - Docs should be updated to match new behavior. Plus a changelog line.
|
Also, could you remove those backslashes? 😅 |
|
@paulbalandan thanks for the review! All three points are addressed, and the backslashes are gone 😅
Let me know if there's anything else you'd like adjusted. Thanks! |
paulbalandan
left a comment
There was a problem hiding this comment.
Thanks for the changes. A few more comments.
|
@paulbalandan thanks for the follow-up! All three points addressed:
Let me know if there's anything else. Thanks! |
|
Can you check on the failing tests? |
paulbalandan
left a comment
There was a problem hiding this comment.
Thanks for checking that ini_set won't work for request_order. Minor comments, otherwise looks good:
- incomingrequest.rst line 173 still reads that getVar can fallback to $_REQUEST. it should now be at the merged array
- the phpstan-ignore at the test demonstrated that the array shapes at Superglobals are incorrectly typed. out of scope of this PR though
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Request-order parsing diverges from PHP semantics, and the new protected method introduces a patch-line compatibility risk.
Review effort: Balanced
Findings: 1
Open (2)
Resolved since last review (2)
michalsn
left a comment
There was a problem hiding this comment.
I still have two concerns:
- Calling
fetchGlobal('request', ...)beforegetVar()populates the request cache.getVar()then treats it as an explicit override and returns stale REQUEST data instead of merged GPC data. We should distinguish cached data from an explicitsetGlobal()override. - An explicitly empty
request_order=""should produce an empty result. PHP falls back tovariables_orderonly whenrequest_orderis unset, but this implementation treats both cases the same.
Could we add regression tests for these cases?
SiteURIFactory updates \ (and \['QUERY_STRING']) when it detects the route path, but \ was left stale. Since PHP populates \ only once at the start of the request, getVar() (which reads \) returned outdated values, breaking \->withRequest() for GET parameters. Add Superglobals::syncRequest() to rebuild \ from \, \, and \ according to request_order/variables_order, and call it from SiteURIFactory after setGetArray(). Fixes codeigniter4#9872
$_REQUEST is populated only once at the start of the request, so it becomes stale when SiteURIFactory updates $_GET during URI parsing. getVar() previously read the stale $_REQUEST, breaking withRequest() for GET parameters. Instead of mutating $_REQUEST (the approach rejected in codeigniter4#10205), this change makes getVar() return a merged view of $_GET, $_POST, and $_COOKIE according to request_order, leaving $_REQUEST untouched. - Add Superglobals::getRequestData() returning the merged data. - Extract RequestTrait::fetchFromArray() to reuse the filtering logic. - Update getVar() to use getRequestData() + fetchFromArray(). - Revert the SiteURIFactory syncRequest() calls. - Update tests. Fixes codeigniter4#9872
- fetchFromArray() must be protected so IncomingRequest (a subclass) can call it. - Replace the short ternary in getRequestData() with explicit checks to satisfy the static analysis rules. - Drop the cookie assertion from the test since request_order defaults to GP (no cookies).
- Align phpdoc @PARAM annotations in RequestTrait::fetchFromArray. - Align match arm => operators in Superglobals::getRequestData.
- Make Superglobals::getRequestData() accept an optional request_order override so precedence and cookie branches can be tested deterministically. - Add tests for cookie merging, order-sensitive overwrite behavior, and unknown order types. - Add a regression test proving getVar() reflects $_GET changes even when $_REQUEST is stale.
Rector's IfToNullCoalescingAssignRector flags the if-null block.
Use array_replace_recursive() instead of array_merge() so numeric keys are preserved and array values are merged recursively, matching PHP's php_autoglobal_merge. Add tests for both behaviors, update the getVar() user guide docs, and add a changelog entry.
…set in tests Address review feedback: remove the test-only \ parameter and cast ini_get() results to string so they compare against the empty string. Tests now set request_order via ini_set(). Also reorder the changelog entry alphabetically under HTTP.
Reuse fetchGlobal for merged request data, preserve explicit request globals, and clear temporary data after filtering. Remove the added protected helper to avoid subclass signature collisions. Normalize request-order case and process each source once.
f095413 to
9d7d143
Compare
|
@michalsn Thanks for flagging both cases. The cached-request case was real: For the empty I also rebased away the merge commit that was failing Carson's check. All branch commits are signed. Focused PHP 8.2 tests pass: 117 IncomingRequest, 56 Superglobals, and 40 Request tests. Carson's no-merge-commits and signed-commits checks now pass. The remaining workflows on this new head are awaiting maintainer approval to run. Could a maintainer approve them when reviewing? PHPUnit run |
Ok, sounds good. |
|
The two Coding Standards jobs flagged the same one-line docblock on the new request-override property. I changed it to the layout PHP CS Fixer requested. The focused PHP 8.2 fixer check now reports 0 files to fix. The updated branch is at |
michalsn
left a comment
There was a problem hiding this comment.
I've spent quite a while thinking about this, and my main concern is the complexity of the proposed behavior.
getVar() now combines JSON handling, explicit REQUEST overrides, fresh GPC merging, and temporarily replacing and restoring the REQUEST cache. Each part has a reason, but together they make the method harder to understand and maintain.
This also introduces different data lifetimes within the same request object. After GET data changes through the Superglobals service, getVar() sees the updated value, while getGet() may still return a previously cached value. If setGlobal('request', ...) is called, getVar() uses that override instead of fresh GPC data. Understanding the result therefore requires knowing both which method is called and how the request object was previously modified.
The finally block handles cache restoration correctly, but future changes, custom fetchGlobal() implementations, and filter callbacks need to account for that temporary state.
I don't think these mechanisms are inherently wrong. I'm questioning whether this additional complexity is justified for a bug caused by GET normalization in SiteURIFactory. Could we reconsider a one-time REQUEST update at that point, while preserving application-defined REQUEST values? That would keep the fix closer to its cause and preserve getVar() existing lookup behavior.
The user guide says this method exists for backward compatibility, so I would prefer to keep this fix as narrow as possible.
If we decide that the behavior introduced by this PR is what we want long term, I think we should target the 4.8 branch instead of develop, since it changes the existing contract of getVar().
Thoughts?
|
@michalsn Thanks for the careful review. I agree the current I avoided changing I'd be happy to rework this around that normalization point, keeping regression tests for |
18f97f1 to
59ff334
Compare
|
To answer directly: #10205 was closed because I preferred a merged view at the time, not because a one-time refresh was ruled out in #9872. Having seen where the merged view ends up, I agree with @michalsn. Let's do it as two steps. Please open a new PR against develop with the one-time |
|
That sounds good to me. Once the one-time refresh fixes the bug, I would prefer to keep For 4.8, we could formally deprecate both That said, I would prefer to keep these changes gradual and avoid changing too much at once. Deprecation should give users a clear migration path while keeping existing applications working. I'm also wondering whether we would need replacement methods with clearly defined rules for selecting input data, or whether the existing alternatives are sufficient and we should focus on promoting them. |
|
Thanks, @paulbalandan and @michalsn. I opened #10606 from current The focused tests and file-scoped checks pass, and the commit is verified. I'll close this PR in favor of #10606; the broader deprecation discussion can stay separate for 4.8. |


Description
Fixes #9872.
SiteURIFactoryupdates GET data after PHP initializes$_REQUEST, sogetVar()can return stale values toValidation::withRequest().This changes
getVar()to read a fresh merged view of GET, POST, and COOKIE data, respectingrequest_order/variables_order, without mutating$_REQUEST. ExplicitsetGlobal('request', ...)overrides and JSON requests retain their existing lookup behavior.The merge preserves numeric keys, merges nested arrays recursively, normalizes lowercase order letters, and processes each source only once. It reuses the existing
fetchGlobal()filtering path and clears temporary merged data infinally, avoiding a new protected helper that could conflict with application subclasses. The user guide and changelog describe the updated data source.Checklist:
Testing:
IncomingRequestTest.php116 tests,SuperglobalsTest.php55 tests,RequestTest.php40 tests, and the three affected Validation cases pass (214 tests total).