Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions system/HTTP/IncomingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,10 @@ public function getDefaultLocale(): string
}

/**
* Fetch an item from JSON input stream with fallback to $_REQUEST object. This is the simplest way
* to grab data from the request object and can be used in lieu of the
* other get* methods in most cases.
* Fetch an item from JSON input stream with fallback to the merged
* $_GET, $_POST, and $_COOKIE data. This is the simplest way to grab data
* from the request object and can be used in lieu of the other get*
* methods in most cases.
*
* @param list<string>|string|null $index
* @param int|null $filter Filter constant
Expand All @@ -387,7 +388,12 @@ public function getVar($index = null, $filter = null, $flags = null)
return $this->getJsonVar($index, false, $filter, $flags);
}

return $this->fetchGlobal('request', $index, $filter, $flags);
// $_REQUEST is populated only once at the start of the request, so it
// can become stale when $_GET is modified later (e.g. by SiteURIFactory).
// Merge the current superglobals instead of reading the stale $_REQUEST.
$data = service('superglobals')->getRequestData();

return $this->fetchFromArray($data, $index, $filter, $flags);
Comment thread
rahul05ranjan marked this conversation as resolved.
}

/**
Expand Down
26 changes: 21 additions & 5 deletions system/HTTP/RequestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,22 @@ public function fetchGlobal(string $name, $index = null, ?int $filter = null, $f
$this->populateGlobals($name);
}

return $this->fetchFromArray($this->globals[$name], $index, $filter, $flags);
}

/**
* Fetches one or more items from an array, applying the same filtering
* and index resolution as fetchGlobal().
*
* @param array<string, mixed> $data
* @param int|list<string>|string|null $index
* @param int|null $filter Filter constant
* @param array<string, mixed>|int|null $flags Options
*
* @return mixed
*/
protected function fetchFromArray(array $data, $index = null, ?int $filter = null, $flags = null)
{
// Null filters cause null values to return.
$filter ??= FILTER_UNSAFE_RAW;
$flags = is_array($flags) ? $flags : (is_numeric($flags) ? (int) $flags : 0);
Expand All @@ -299,9 +315,9 @@ public function fetchGlobal(string $name, $index = null, ?int $filter = null, $f
if ($index === null) {
$values = [];

foreach ($this->globals[$name] as $key => $value) {
foreach ($data as $key => $value) {
$values[$key] = is_array($value)
? $this->fetchGlobal($name, $key, $filter, $flags)
? $this->fetchFromArray($data, $key, $filter, $flags)
: filter_var($value, $filter, $flags);
}

Expand All @@ -313,15 +329,15 @@ public function fetchGlobal(string $name, $index = null, ?int $filter = null, $f
$output = [];

foreach ($index as $key) {
$output[$key] = $this->fetchGlobal($name, $key, $filter, $flags);
$output[$key] = $this->fetchFromArray($data, $key, $filter, $flags);
}

return $output;
}

// Does the index contain array notation?
if (is_string($index) && ($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) {
$value = $this->globals[$name];
$value = $data;

for ($i = 0; $i < $count; $i++) {
$key = trim($matches[0][$i], '[]');
Expand All @@ -338,7 +354,7 @@ public function fetchGlobal(string $name, $index = null, ?int $filter = null, $f
}
}

$value ??= $this->globals[$name][$index] ?? null;
$value ??= $data[$index] ?? null;

if (is_array($value)
&& (
Expand Down
42 changes: 42 additions & 0 deletions system/Superglobals.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,48 @@ public function setRequestArray(array $array): self
return $this;
}

/**
* Returns the merged $_GET, $_POST, and $_COOKIE data according to the
* `request_order` (or `variables_order`) ini setting, without mutating
* $_REQUEST.
*
* PHP populates $_REQUEST only once at the start of the request. When
* $_GET is modified later (e.g. by SiteURIFactory), $_REQUEST becomes
* stale. This method returns the current merged values so callers can
* read up-to-date request data without relying on the stale $_REQUEST.
*
* @param string|null $requestOrder Overrides the ini setting for testing.
*
* @return array<string, request_items>
*/
public function getRequestData(?string $requestOrder = null): array
{
if ($requestOrder === null) {
$requestOrder = ini_get('request_order');
}

if ($requestOrder === false || $requestOrder === '') {
$requestOrder = ini_get('variables_order');
}

if ($requestOrder === false || $requestOrder === '') {
$requestOrder = 'GP';
}

$request = [];

foreach (str_split($requestOrder) as $type) {
match ($type) {
'G' => $request = array_merge($request, $this->get),
'P' => $request = array_merge($request, $this->post),
'C' => $request = array_merge($request, $this->cookie),
Comment thread
rahul05ranjan marked this conversation as resolved.
default => null,
};
}

return $request;
}

/**
* Get all $_FILES values.
*
Expand Down
17 changes: 14 additions & 3 deletions tests/system/HTTP/IncomingRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,23 @@ private function createRequest(?App $config = null, false|string|null $body = nu

public function testCanGrabRequestVars(): void
{
service('superglobals')->setRequest('TEST', '5');
service('superglobals')->setGet('TEST', '5');

$this->assertSame('5', $this->request->getVar('TEST'));
$this->assertNull($this->request->getVar('TESTY'));
}

public function testGetVarReflectsGetChangesWhenRequestIsStale(): void
{
// Simulate the state after SiteURIFactory updates $_GET: $_REQUEST
// still holds the original value while $_GET has been refreshed.
service('superglobals')
->setGetArray(['code' => 'good'])
->setRequestArray(['code' => 'stale']);

$this->assertSame('good', $this->request->getVar('code'));
}

public function testCanGrabGetVars(): void
{
service('superglobals')->setGet('TEST', '5');
Expand Down Expand Up @@ -525,8 +536,8 @@ public function testGetVarWorksWithJsonAndGetParams(): void
$config->baseURL = 'http://example.com/';

// GET method
service('superglobals')->setRequest('foo', 'bar');
service('superglobals')->setRequest('fizz', 'buzz');
service('superglobals')->setGet('foo', 'bar');
service('superglobals')->setGet('fizz', 'buzz');

$request = $this->createRequest($config);
$request = $request->withMethod('GET');
Expand Down
57 changes: 57 additions & 0 deletions tests/system/SuperglobalsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,63 @@ public function testRequestSetArray(): void
$this->assertSame($data, $_REQUEST);
}

public function testGetRequestDataMergesGetAndPost(): void
{
$this->superglobals->setGetArray(['get_key' => 'get_value']);
$this->superglobals->setPostArray(['post_key' => 'post_value']);

$data = $this->superglobals->getRequestData();

$this->assertSame('get_value', $data['get_key']);
$this->assertSame('post_value', $data['post_key']);
}

public function testGetRequestDataReflectsGetChanges(): void
{
$this->superglobals->setGetArray(['key' => 'old']);

$this->assertSame('old', $this->superglobals->getRequestData()['key']);

// Simulate SiteURIFactory updating $_GET after the request started.
$this->superglobals->setGetArray(['key' => 'new']);

$this->assertSame('new', $this->superglobals->getRequestData()['key']);
}

public function testGetRequestDataMergesCookie(): void
{
$this->superglobals->setGetArray(['get_key' => 'get_value']);
$this->superglobals->setPostArray(['post_key' => 'post_value']);
$this->superglobals->setCookieArray(['cookie_key' => 'cookie_value']);

$data = $this->superglobals->getRequestData('GPC');

$this->assertSame('get_value', $data['get_key']);
$this->assertSame('post_value', $data['post_key']);
$this->assertSame('cookie_value', $data['cookie_key']);
}

public function testGetRequestDataRespectsOrder(): void
{
$this->superglobals->setGetArray(['shared' => 'get']);
$this->superglobals->setPostArray(['shared' => 'post']);
$this->superglobals->setCookieArray(['shared' => 'cookie']);

// Later sources overwrite earlier ones, matching PHP's request_order.
$this->assertSame('post', $this->superglobals->getRequestData('GP')['shared']);
$this->assertSame('cookie', $this->superglobals->getRequestData('GPC')['shared']);
$this->assertSame('get', $this->superglobals->getRequestData('PG')['shared']);
}

public function testGetRequestDataIgnoresUnknownOrderTypes(): void
{
$this->superglobals->setGetArray(['get_key' => 'get_value']);

$data = $this->superglobals->getRequestData('GX');

$this->assertSame(['get_key' => 'get_value'], $data);
}

// $_FILES tests
public function testFilesGetArray(): void
{
Expand Down
6 changes: 3 additions & 3 deletions tests/system/Validation/ValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,7 @@ public function testRulesForSingleRuleWithAsteriskWillReturnNoError(): void
$config = new App();
$config->baseURL = 'http://example.com/';

service('superglobals')->setRequestArray([
service('superglobals')->setPostArray([
'id_user' => [
1,
3,
Expand All @@ -1316,7 +1316,7 @@ public function testRulesForSingleRuleWithAsteriskWillReturnError(): void
$config = new App();
$config->baseURL = 'http://example.com/';

service('superglobals')->setRequestArray([
service('superglobals')->setPostArray([
'id_user' => [
'1dfd',
3,
Expand Down Expand Up @@ -1366,7 +1366,7 @@ public function testRulesForSingleRuleWithSingleValue(): void
$config = new App();
$config->baseURL = 'http://example.com/';

service('superglobals')->setRequestArray([
service('superglobals')->setPostArray([
'id_user' => 'gh',
]);

Expand Down
Loading