diff --git a/src/Message/Message.php b/src/Message/Message.php index 217bdc8..f39e8ac 100644 --- a/src/Message/Message.php +++ b/src/Message/Message.php @@ -20,6 +20,11 @@ abstract class Message implements MessageInterface $this->body = new NullStream(); } + public function __clone() + { + $this->headers = clone $this->headers; + } + // ------------------------------------------------------------------------ // Psr\Http\Message\MessageInterface @@ -171,9 +176,13 @@ abstract class Message implements MessageInterface */ public function withHeader($name, $value) { + $values = $this->getValidatedHeaders($name, $value); + $message = clone $this; unset($message->headers[$name]); - $message->headers[$name] = $value; + foreach ($values as $value) { + $message->headers[$name] = (string) $value; + } return $message; } @@ -196,8 +205,12 @@ abstract class Message implements MessageInterface */ public function withAddedHeader($name, $value) { + $values = $this->getValidatedHeaders($name, $value); + $message = clone $this; - $message->headers[$name] = $value; + foreach ($values as $value) { + $message->headers[$name] = (string) $value; + } return $message; } @@ -252,9 +265,23 @@ abstract class Message implements MessageInterface // ------------------------------------------------------------------------ - public function __clone() + private function getValidatedHeaders($name, $value) { - $this->headers = clone $this->headers; + $is_allowed = function ($item) { + return is_string($item) || is_numeric($item); + }; + + if (!is_string($name)) { + throw new \InvalidArgumentException("Header name must be a string"); + } + + if ($is_allowed($value)) { + return [$value]; + } elseif (is_array($value) && count($value) === count(array_filter($value, $is_allowed))) { + return $value; + } else { + throw new \InvalidArgumentException("Header values must be a string or string[]"); + } } } diff --git a/test/tests/unit/Message/MessageTest.php b/test/tests/unit/Message/MessageTest.php index f4b1642..c294eaf 100644 --- a/test/tests/unit/Message/MessageTest.php +++ b/test/tests/unit/Message/MessageTest.php @@ -17,6 +17,21 @@ class MessageTest extends \PHPUnit_Framework_TestCase $this->assertNotNull($message); } + /** + * @covers WellRESTed\Message\Message::__clone + */ + public function testCloneMakesDeepCopyOfHeaders() + { + $message1 = $this->getMockForAbstractClass('\WellRESTed\Message\Message'); + $message1 = $message1->withHeader("Content-type", "text/plain"); + $message2 = $message1->withHeader("Content-type", "application/json"); + $this->assertEquals(["text/plain"], $message1->getHeader("Content-type")); + $this->assertEquals(["application/json"], $message2->getHeader("Content-type")); + } + + // ------------------------------------------------------------------------ + // Protocol Version + /** * @covers WellRESTed\Message\Message::getProtocolVersion */ @@ -46,27 +61,49 @@ class MessageTest extends \PHPUnit_Framework_TestCase $this->assertEquals("1.0", $message->getProtocolVersion()); } + // ------------------------------------------------------------------------ + // Headers + /** * @covers WellRESTed\Message\Message::withHeader + * @covers WellRESTed\Message\Message::getValidatedHeaders + * @dataProvider validHeaderValueProvider */ - public function testWithHeaderSetsHeader() + public function testWithHeaderReplacesHeader($expected, $value) { $message = $this->getMockForAbstractClass('\WellRESTed\Message\Message'); - $message = $message->withHeader("Content-type", "application/json"); - $this->assertEquals(["application/json"], $message->getHeader("Content-type")); + $message = $message->withHeader("X-foo", "Original value"); + $message = $message->withHeader("X-foo", $value); + $this->assertEquals($expected, $message->getHeader("X-foo")); + } + + public function validHeaderValueProvider() + { + return [ + [["0"], 0], + [["molly","bear"],["molly","bear"]] + ]; } /** * @covers WellRESTed\Message\Message::withHeader + * @covers WellRESTed\Message\Message::getValidatedHeaders + * @expectedException \InvalidArgumentException + * @dataProvider invalidHeaderProvider */ - public function testWithHeaderReplacesValue() + public function testWithHeaderThrowExceptionWithInvalidArgument($name, $value) { $message = $this->getMockForAbstractClass('\WellRESTed\Message\Message'); - $message = $message->withHeader("Set-Cookie", "cat=Molly"); - $message = $message->withHeader("Set-Cookie", "dog=Bear"); - $cookies = $message->getHeader("Set-Cookie"); - $this->assertNotContains("cat=Molly", $cookies); - $this->assertContains("dog=Bear", $cookies); + $message->withHeader($name, $value); + } + + public function invalidHeaderProvider() + { + return [ + [0, 1024], + ["Content-length", false], + ["Content-length", [false]], + ]; } /** @@ -85,8 +122,8 @@ class MessageTest extends \PHPUnit_Framework_TestCase public function testWithAddedHeaderAppendsValue() { $message = $this->getMockForAbstractClass('\WellRESTed\Message\Message'); - $message = $message->withAddedHeader("Set-Cookie", "cat=Molly"); - $message = $message->withAddedHeader("Set-Cookie", "dog=Bear"); + $message = $message->withAddedHeader("Set-Cookie", ["cat=Molly"]); + $message = $message->withAddedHeader("Set-Cookie", ["dog=Bear"]); $cookies = $message->getHeader("Set-Cookie"); $this->assertContains("cat=Molly", $cookies); $this->assertContains("dog=Bear", $cookies); @@ -222,6 +259,9 @@ class MessageTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, $headers); } + // ------------------------------------------------------------------------ + // Body + /** * @covers WellRESTed\Message\Message::getBody * @uses WellRESTed\Message\NullStream @@ -245,16 +285,4 @@ class MessageTest extends \PHPUnit_Framework_TestCase $message = $message->withBody($stream); $this->assertSame($stream, $message->getBody()); } - - /** - * @covers WellRESTed\Message\Message::__clone - */ - public function testCloneMakesDeepCopyOfHeaders() - { - $message1 = $this->getMockForAbstractClass('\WellRESTed\Message\Message'); - $message1 = $message1->withHeader("Content-type", "text/plain"); - $message2 = $message1->withHeader("Content-type", "application/json"); - $this->assertEquals(["text/plain"], $message1->getHeader("Content-type")); - $this->assertEquals(["application/json"], $message2->getHeader("Content-type")); - } }