diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index d355cab241d2..5ede629fc063 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -63,6 +63,13 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess */ protected $routeResolver; + /** + * The cached Accept header value used to detect changes. + * + * @var string|null + */ + protected $cachedAcceptHeader; + /** * Create a new Illuminate HTTP request from server variables. * @@ -812,4 +819,23 @@ public function __get($key) { return Arr::get($this->all(), $key, fn () => $this->route($key)); } + + /** + * {@inheritdoc} + * + * Override to clear cache when Accept header changes. + */ + #[\Override] + public function getAcceptableContentTypes(): array + { + $currentAcceptHeader = $this->headers->get('Accept'); + + if ($this->cachedAcceptHeader !== $currentAcceptHeader) { + $this->acceptableContentTypes = null; + } + + $this->cachedAcceptHeader = $currentAcceptHeader; + + return parent::getAcceptableContentTypes(); + } } diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index ccf62d8e60d0..199f710ee499 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -1425,6 +1425,79 @@ public function testFormatReturnsAcceptsCharset() $this->assertTrue($request->accepts('application/baz+json')); } + public function testWantsJsonRespectsHeaderChanges() + { + $request = Request::create('/', 'GET', [], [], [], ['HTTP_ACCEPT' => '*/*']); + + $this->assertFalse($request->wantsJson()); + + $this->assertTrue($request->acceptsAnyContentType()); + + $request->headers->set('Accept', 'application/json'); + + $this->assertTrue($request->wantsJson(), 'wantsJson() should return true after Accept header is changed to application/json'); + } + + public function testAcceptsJsonRespectsHeaderChanges() + { + $request = Request::create('/', 'GET', [], [], [], ['HTTP_ACCEPT' => '*/*']); + + $this->assertTrue($request->acceptsAnyContentType()); + + $request->headers->set('Accept', 'application/json'); + + $this->assertTrue($request->acceptsJson(), 'acceptsJson() should return true after Accept header is changed to application/json'); + } + + public function testPrefersRespectsHeaderChanges() + { + $request = Request::create('/', 'GET', [], [], [], ['HTTP_ACCEPT' => '*/*']); + + $this->assertTrue($request->acceptsAnyContentType()); + + $request->headers->set('Accept', 'application/json'); + + $this->assertSame('json', $request->prefers(['html', 'json']), 'prefers() should return json after Accept header is changed to application/json'); + } + + public function testWantsJsonWorksWhenHeaderSetBeforeFirstCall() + { + $request = Request::create('/', 'GET', [], [], [], []); + + $request->headers->set('Accept', 'application/json'); + + $this->assertTrue($request->wantsJson(), 'wantsJson() should return true when Accept header is set to application/json'); + } + + public function testCacheClearedWhenTransitioningFromUnsetToSetHeader() + { + $request = Request::create('/', 'GET', [], [], [], []); + + $request->getAcceptableContentTypes(); + + $request->headers->set('Accept', 'application/json'); + + $this->assertTrue($request->wantsJson(), 'wantsJson() should return true after Accept header is set from null to application/json'); + + $this->assertTrue($request->acceptsJson(), 'acceptsJson() should return true after Accept header is set from null to application/json'); + } + + public function testAcceptsJsonWorksWhenHeaderChangedMultipleTimes() + { + $request = Request::create('/', 'GET', [], [], [], ['HTTP_ACCEPT' => 'text/html']); + + $this->assertFalse($request->acceptsJson()); + + $request->headers->set('Accept', 'application/json'); + $this->assertTrue($request->acceptsJson()); + + $request->headers->set('Accept', 'text/html'); + $this->assertFalse($request->acceptsJson()); + + $request->headers->set('Accept', 'application/json'); + $this->assertTrue($request->acceptsJson()); + } + public function testBadAcceptHeader() { $request = Request::create('/', 'GET', [], [], [], ['HTTP_ACCEPT' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)']);