Merge pull request #8194 from kenjis/feat-Message-addHeader

feat: add Message::addHeader() to add header with the same name
This commit is contained in:
kenjis 2023-11-21 06:22:50 +09:00 committed by GitHub
commit 9954ccf67a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 168 additions and 11 deletions

View File

@ -64,7 +64,7 @@ class Header
/**
* Gets the raw value of the header. This may return either a string
* of an array, depending on whether the header has multiple values or not.
* or an array, depending on whether the header has multiple values or not.
*
* @return array<int|string, array<string, string>|string>|string
*/

View File

@ -11,6 +11,8 @@
namespace CodeIgniter\HTTP;
use InvalidArgumentException;
/**
* An HTTP message
*
@ -112,6 +114,13 @@ class Message implements MessageInterface
*/
public function getHeaderLine(string $name): string
{
if ($this->hasMultipleHeaders($name)) {
throw new InvalidArgumentException(
'The header "' . $name . '" already has multiple headers.'
. ' You cannot use getHeaderLine().'
);
}
$origName = $this->getHeaderName($name);
if (! array_key_exists($origName, $this->headers)) {

View File

@ -62,7 +62,7 @@ interface MessageInterface
/**
* Returns an array containing all Headers.
*
* @return array<string, Header> An array of the Header objects
* @return array<string, Header|list<Header>> An array of the Header objects
*/
public function headers(): array;
@ -83,7 +83,7 @@ interface MessageInterface
*
* @param string $name
*
* @return array|Header|null
* @return Header|list<Header>|null
*/
public function header($name);

View File

@ -12,6 +12,7 @@
namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use InvalidArgumentException;
/**
* Message Trait
@ -25,7 +26,11 @@ trait MessageTrait
/**
* List of all HTTP request headers.
*
* @var array<string, Header>
* [name => Header]
* or
* [name => [Header1, Header2]]
*
* @var array<string, Header|list<Header>>
*/
protected $headers = [];
@ -93,7 +98,7 @@ trait MessageTrait
$this->setHeader($header, $_SERVER[$key]);
// Add us to the header map so we can find them case-insensitively
// Add us to the header map, so we can find them case-insensitively
$this->headerMap[strtolower($header)] = $header;
}
}
@ -102,7 +107,7 @@ trait MessageTrait
/**
* Returns an array containing all Headers.
*
* @return array<string, Header> An array of the Header objects
* @return array<string, Header|list<Header>> An array of the Header objects
*/
public function headers(): array
{
@ -122,7 +127,7 @@ trait MessageTrait
*
* @param string $name
*
* @return array|Header|null
* @return Header|list<Header>|null
*/
public function header($name)
{
@ -140,9 +145,14 @@ trait MessageTrait
*/
public function setHeader(string $name, $value): self
{
$this->checkMultipleHeaders($name);
$origName = $this->getHeaderName($name);
if (isset($this->headers[$origName]) && is_array($this->headers[$origName]->getValue())) {
if (
isset($this->headers[$origName])
&& is_array($this->headers[$origName]->getValue())
) {
if (! is_array($value)) {
$value = [$value];
}
@ -158,6 +168,23 @@ trait MessageTrait
return $this;
}
private function hasMultipleHeaders(string $name): bool
{
$origName = $this->getHeaderName($name);
return isset($this->headers[$origName]) && is_array($this->headers[$origName]);
}
private function checkMultipleHeaders(string $name): void
{
if ($this->hasMultipleHeaders($name)) {
throw new InvalidArgumentException(
'The header "' . $name . '" already has multiple headers.'
. ' You cannot change them. If you really need to change, remove the header first.'
);
}
}
/**
* Removes a header from the list of headers we track.
*
@ -179,6 +206,8 @@ trait MessageTrait
*/
public function appendHeader(string $name, ?string $value): self
{
$this->checkMultipleHeaders($name);
$origName = $this->getHeaderName($name);
array_key_exists($origName, $this->headers)
@ -188,6 +217,33 @@ trait MessageTrait
return $this;
}
/**
* Adds a header (not a header value) with the same name.
* Use this only when you set multiple headers with the same name,
* typically, for `Set-Cookie`.
*
* @return $this
*/
public function addHeader(string $name, string $value): static
{
$origName = $this->getHeaderName($name);
if (! isset($this->headers[$origName])) {
$this->setHeader($name, $value);
return $this;
}
if (! $this->hasMultipleHeaders($name) && isset($this->headers[$origName])) {
$this->headers[$origName] = [$this->headers[$origName]];
}
// Add the header.
$this->headers[$origName][] = new Header($origName, $value);
return $this;
}
/**
* Adds an additional header value to any headers that accept
* multiple values (i.e. are an array or implement ArrayAccess)
@ -196,6 +252,8 @@ trait MessageTrait
*/
public function prependHeader(string $name, string $value): self
{
$this->checkMultipleHeaders($name);
$origName = $this->getHeaderName($name);
$this->headers[$origName]->prependValue($value);

View File

@ -13,6 +13,7 @@ namespace CodeIgniter\HTTP;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\Test\CIUnitTestCase;
use InvalidArgumentException;
/**
* @internal
@ -207,7 +208,7 @@ final class MessageTest extends CIUnitTestCase
/**
* @dataProvider provideArrayHeaderValue
*
* @param mixed $arrayHeaderValue
* @param array $arrayHeaderValue
*/
public function testSetHeaderWithExistingArrayValuesAppendStringValue($arrayHeaderValue): void
{
@ -220,7 +221,7 @@ final class MessageTest extends CIUnitTestCase
/**
* @dataProvider provideArrayHeaderValue
*
* @param mixed $arrayHeaderValue
* @param array $arrayHeaderValue
*/
public function testSetHeaderWithExistingArrayValuesAppendArrayValue($arrayHeaderValue): void
{
@ -304,4 +305,73 @@ final class MessageTest extends CIUnitTestCase
$_SERVER = $original; // restore so code coverage doesn't break
}
public function testAddHeaderAddsFirstHeader(): void
{
$this->message->addHeader(
'Set-Cookie',
'logged_in=no; Path=/'
);
$header = $this->message->header('Set-Cookie');
$this->assertInstanceOf(Header::class, $header);
$this->assertSame('logged_in=no; Path=/', $header->getValue());
}
public function testAddHeaderAddsTwoHeaders(): void
{
$this->message->addHeader(
'Set-Cookie',
'logged_in=no; Path=/'
);
$this->message->addHeader(
'Set-Cookie',
'sessid=123456; Path=/'
);
$headers = $this->message->header('Set-Cookie');
$this->assertCount(2, $headers);
$this->assertSame('logged_in=no; Path=/', $headers[0]->getValue());
$this->assertSame('sessid=123456; Path=/', $headers[1]->getValue());
}
public function testAppendHeaderWithMultipleHeaders(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'The header "Set-Cookie" already has multiple headers. You cannot change them. If you really need to change, remove the header first.'
);
$this->message->addHeader(
'Set-Cookie',
'logged_in=no; Path=/'
);
$this->message->addHeader(
'Set-Cookie',
'sessid=123456; Path=/'
);
$this->message->appendHeader('Set-Cookie', 'HttpOnly');
}
public function testGetHeaderLineWithMultipleHeaders(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'The header "Set-Cookie" already has multiple headers. You cannot use getHeaderLine().'
);
$this->message->addHeader(
'Set-Cookie',
'logged_in=no; Path=/'
);
$this->message->addHeader(
'Set-Cookie',
'sessid=123456; Path=/'
);
$this->message->getHeaderLine('Set-Cookie');
}
}

View File

@ -299,6 +299,8 @@ Others
usage in your view files, which was supported by CodeIgniter 3.
- **CSP:** Added ``ContentSecurityPolicy::clearDirective()`` method to clear
existing CSP directives. See :ref:`csp-clear-directives`.
- **HTTP:** Added ``Message::addHeader()`` method to add another header with
the same name. See :php:meth:`CodeIgniter\\HTTP\\Message::addHeader()`.
Message Changes
***************

View File

@ -7,7 +7,7 @@ requests and responses, including the message body, protocol version, utilities
the headers, and methods for handling content negotiation.
This class is the parent class that both the :doc:`Request Class <../incoming/request>` and the
:doc:`Response Class <../outgoing/response>` extend from.
:doc:`Response Class <../outgoing/response>` extend from, and it is not used directly.
***************
Class Reference
@ -146,6 +146,20 @@ Class Reference
.. literalinclude:: message/009.php
.. php:method:: addHeader($name, $value)
.. versionadded:: 4.5.0
:param string $name: The name of the header to add.
:param string $value: The value of the header.
:returns: The current message instance
:rtype: CodeIgniter\\HTTP\\Message
Adds a header (not a header value) with the same name.
Use this only when you set multiple headers with the same name,
.. literalinclude:: message/011.php
.. php:method:: getProtocolVersion()
:returns: The current HTTP protocol version

View File

@ -0,0 +1,4 @@
<?php
$message->addHeader('Set-Cookie', 'logged_in=no; Path=/');
$message->addHeader('Set-Cookie', 'sessid=123456; Path=/');