Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
36 / 36 |
CRAP | |
100.00% |
120 / 120 |
Response | |
100.00% |
1 / 1 |
|
100.00% |
36 / 36 |
71 | |
100.00% |
120 / 120 |
make($content = '', $code = 200, array $headers = array() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
fromException(HttpExceptionInterface $e, array $headers = array() | |
100.00% |
1 / 1 |
3 | |
100.00% |
3 / 3 |
|||
__construct($content = '', $code = 200, array $headers = array() | |
100.00% |
1 / 1 |
2 | |
100.00% |
9 / 9 |
|||
__toString() | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
setHeaderWrapper(HeaderWrapperInterface $headerWrapper = null) | |
100.00% |
1 / 1 |
3 | |
100.00% |
9 / 9 |
|||
sendHeaders() | |
100.00% |
1 / 1 |
3 | |
100.00% |
8 / 8 |
|||
sendCookies() | |
100.00% |
1 / 1 |
3 | |
100.00% |
6 / 6 |
|||
sendContent() | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
send() | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
prepare(Request $request) | |
100.00% |
1 / 1 |
10 | |
100.00% |
20 / 20 |
|||
addCookie(Cookie $cookie) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
addHeader($name, $value, $overwrite = false) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setStatusCode($code = 200, $txt = null) | |
100.00% |
1 / 1 |
4 | |
100.00% |
9 / 9 |
|||
getStatusCode() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setContent($content = '') | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
appendContent($content = '') | |
100.00% |
1 / 1 |
3 | |
100.00% |
6 / 6 |
|||
getContent() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setDate(DateTime $date) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getDate() | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
setProtocolVersion($version = '1.0') | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getProtocolVersion() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setExpires(DateTime $date = null) | |
100.00% |
1 / 1 |
2 | |
100.00% |
6 / 6 |
|||
getExpires() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setCharset($charset) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getCharset() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isSuccess() | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
isOk() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isNotFound() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isForbidden() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isRedirect() | |
100.00% |
1 / 1 |
3 | |
100.00% |
1 / 1 |
|||
isClientError() | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
isServerError() | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
isEmpty() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isInvalid() | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
isInformational() | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
validateContent($content) | |
100.00% |
1 / 1 |
4 | |
100.00% |
3 / 3 |
<?php namespace Modulework\Modules\Http; | |
/* | |
* (c) Christian Gärtner <christiangaertner.film@googlemail.com> | |
* This file is part of the Modulework Framework | |
* License: View distributed LICENSE file | |
*/ | |
use DateTime; | |
use DateTimeZone; | |
use RunTimeException; | |
use InvalidArgumentException; | |
use Modulework\Modules\Http\Cookie; | |
use Modulework\Modules\Http\Request; | |
use Modulework\Modules\Http\Utilities\ArrayCase; | |
use Modulework\Modules\Http\Utilities\HeaderCase; | |
use Modulework\Modules\Http\Utilities\HeaderWrapper; | |
use Modulework\Modules\Http\Utilities\HeaderWrapperInterface; | |
use Modulework\Modules\Http\Exceptions\HttpExceptionInterface; | |
/** | |
* Response | |
* This class should represent the HTTP response, | |
* done by the application. | |
*/ | |
class Response | |
{ | |
/** | |
* The main content | |
* @var string | |
*/ | |
protected $content; | |
/** | |
* The HTTP status code | |
* @var integer | |
*/ | |
protected $statusCode; | |
/** | |
* The HTTP status Text | |
* @var string | |
*/ | |
protected $statusText; | |
/** | |
* The charset (header) | |
* @var string | |
*/ | |
protected $charset; | |
/** | |
* The HTTP protocol version | |
* @var string | |
*/ | |
protected $protocolVersion = '1.0'; | |
/** | |
* The status code registry | |
* List according to {@link http://www.iana.org/assignments/http-status-codes/http-status-codes.txt} | |
* @var array | |
*/ | |
public static $statusCodeRegistry = array( | |
100 => 'Continue', | |
101 => 'Switching Protocols', | |
102 => 'Processing', | |
200 => 'OK', | |
201 => 'Created', | |
202 => 'Accepted', | |
203 => 'Non-Authoritative Information', | |
204 => 'No Content', | |
205 => 'Reset Content', | |
206 => 'Partial Content', | |
207 => 'Multi-Status', | |
208 => 'Already Reported', | |
226 => 'IM Used', | |
300 => 'Multiple Choices', | |
301 => 'Moved Permanently', | |
302 => 'Found', | |
303 => 'See Other', | |
304 => 'Not Modified', | |
305 => 'Use Proxy', | |
306 => 'Reserved', | |
307 => 'Temporary Redirect', | |
308 => 'Permanent Redirect', | |
400 => 'Bad Request', | |
401 => 'Unauthorized', | |
402 => 'Payment Required', | |
403 => 'Forbidden', | |
404 => 'Not Found', | |
405 => 'Method Not Allowed', | |
406 => 'Not Acceptable', | |
407 => 'Proxy Authentication Required', | |
408 => 'Request Timeout', | |
409 => 'Conflict', | |
410 => 'Gone', | |
411 => 'Length Required', | |
412 => 'Precondition Failed', | |
413 => 'Request Entity Too Large ', | |
414 => 'Request-URI Too Long', | |
415 => 'Unsupported Media Type', | |
416 => 'Requested Range Not Satisfiable', | |
417 => 'Expectation Failed', | |
422 => 'Unprocessable Entity', | |
423 => 'Locked', | |
424 => 'Failed Dependency ', | |
425 => 'Unassigned', | |
426 => 'Upgrade Required', | |
427 => 'Unassigned', | |
428 => 'Precondition Required', | |
429 => 'Too Many Requests', | |
430 => 'Unassigned', | |
431 => 'Request Header Fields Too Large ', | |
500 => 'Internal Server Error', | |
501 => 'Not Implemented', | |
502 => 'Bad Gateway', | |
503 => 'Service Unavailable', | |
504 => 'Gateway Timeout', | |
505 => 'HTTP Version Not Supported', | |
506 => 'Variant Also Negotiates (Experimental)', | |
507 => 'Insufficient Storage', | |
508 => 'Loop Detected', | |
509 => 'Unassigned', | |
510 => 'Not Extended', | |
511 => 'Network Authentication Required' | |
); | |
/** | |
* The headers to get sent | |
* @var \Modulework\Modules\Http\Utilities\ArrayCase | |
*/ | |
public $headers; | |
/** | |
* The cookies to get sent | |
* @var \Modulework\Modules\Http\Utilities\ArrayCase | |
*/ | |
public $cookies; | |
/** | |
* The HeaderWrapper | |
* @var \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | |
*/ | |
protected $headerWrapper; | |
/** | |
* Factory for the Response object | |
* @param string $content The body of the HTTP response | |
* @param integer $code The HTTP status code | |
* @param array $headers The HTTP headers | |
* | |
* @param \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null $headerWrapper The wrapper for PHP' s native header releated functions | |
* | |
* @return \Modulework\Modules\Http\Response The new Response object | |
* | |
* @throws \InvalidArgumentException (from Constructor) | |
*/ | |
public static function make($content = '', $code = 200, array $headers = array(), HeaderWrapperInterface $headerWrapper = null) | |
{ | |
return new static($content, $code, $headers, $headerWrapper); | |
} | |
/** | |
* Factory for the Response object | |
* Create a Response from a HttpException | |
* @param HttpExceptionInterface $e The exception | |
* @param array $headers The HTTP headers | |
* | |
* @param \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null $headerWrapper The wrapper for PHP' s native header releated functions | |
* | |
* @return \Modulework\Modules\Http\Response The new Response object | |
* | |
* @throws \InvalidArgumentException (from Constructor) | |
*/ | |
public static function fromException(HttpExceptionInterface $e, array $headers = array(), HeaderWrapperInterface $headerWrapper = null) | |
{ | |
$content = isset(self::$statusCodeRegistry[$e->getStatusCode()]) ? self::$statusCodeRegistry[$e->getStatusCode()] : ''; | |
$content = $e->getMessage() ?: $content; | |
return new static($content, $e->getStatusCode(), $headers, $headerWrapper); | |
} | |
/** | |
* Constructor. | |
* @param string $content The body of the HTTP response | |
* @param integer $code The HTTP status code | |
* @param array $headers The HTTP headers | |
* | |
* @param \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null $headerWrapper The wrapper for PHP' s native header releated functions | |
* | |
* @return \Modulework\Modules\Http\Response The new Response object | |
* | |
* @throws \InvalidArgumentException (from setContent) | |
*/ | |
public function __construct($content = '', $code = 200, array $headers = array(), HeaderWrapperInterface $headerWrapper = null) | |
{ | |
$this->setStatusCode($code); | |
$this->setContent($content); | |
$this->headers = new HeaderCase($headers); | |
$this->cookies = new ArrayCase(); | |
if (!$this->headers->has('Date')) { | |
$this->setDate(new DateTime(null, new DateTimeZone('UTC'))); | |
} | |
$this->setHeaderWrapper($headerWrapper); | |
} | |
/** | |
* PHP' s magic method __toString | |
* Format: | |
* HTTP/{VERSION} {STATUSCODE} {STATUSTEXT} | |
* {HEADERS} | |
* {BODY} | |
* | |
* @return string The response as string | |
*/ | |
public function __toString() | |
{ | |
return | |
sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->statusCode, $this->statusText) . "\r\n" . | |
$this->headers->showForResponse() . "\r\n" . | |
$this->getContent(); | |
} | |
/** | |
* Dependency injection for the HeaderWrapper (also availbe thru the constructor) | |
* If null is passed it will create a new instance of \Modulework\Modules\Http\Utilities\HeaderWrapper | |
* | |
* It returns the "previous" HeaderWrapper or null | |
* | |
* @param \Modulework\Modules\Http\Utilities\HeaderWrapperInterface $headerWrapper The HeaderWrapper | |
* | |
* @return \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null "previous" HeaderWrapper | null | |
*/ | |
public function setHeaderWrapper(HeaderWrapperInterface $headerWrapper = null) | |
{ | |
if ($this->headerWrapper !== null) { | |
$ret = $this->headerWrapper; | |
} else { | |
$ret = null; | |
} | |
if ($headerWrapper === null) { | |
$this->headerWrapper = new HeaderWrapper; | |
} else { | |
$this->headerWrapper = $headerWrapper; | |
} | |
return $ret; | |
} | |
/** | |
* Send the headers and cookies to the client | |
* @uses sendCookies | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function sendHeaders() | |
{ | |
if ($this->headerWrapper->headers_sent()) { | |
return $this; | |
} | |
$this->headerWrapper->header(sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->statusCode, $this->statusText)); | |
foreach ($this->headers->all() as $name => $value) { | |
$this->headerWrapper->header($name . ': ' . $value); | |
} | |
$this->sendCookies(); | |
return $this; | |
} | |
/** | |
* Send the cookies only to the client | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function sendCookies() | |
{ | |
if ($this->headerWrapper->headers_sent()) { | |
return $this; | |
} | |
foreach ($this->cookies->all() as $cookie) { | |
$this->headerWrapper->setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); | |
} | |
return $this; | |
} | |
/** | |
* Sends the content to the client | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function sendContent() | |
{ | |
echo $this->content; | |
return $this; | |
} | |
/** | |
* Send the response to the client (headers, cookies, content) | |
* @uses sendHeaders | |
* @uses sendContent | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function send() | |
{ | |
$this->sendHeaders(); | |
$this->sendContent(); | |
return $this; | |
} | |
/** | |
* Prepares the response based on the Request object | |
* This method is not essential and can be cut out of the chain. | |
* However it cleans up the headers and does some other stuff under the hood. | |
* @param Request $req The request object | |
* | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function prepare(Request $request) | |
{ | |
if ($this->isInvalid()) { | |
throw new RunTimeException('Response is invalid (' . $this->statusCode . ')'); | |
} | |
if ($this->isInformational()) { | |
$this->content = null; | |
} | |
$this->charset = $this->charset ?: 'UTF-8'; | |
if (!$this->headers->has('Content-Type')) { | |
$this->headers->set('Content-Type', 'text/html; charset=' . $this->charset); | |
} | |
// This method tries may cause some issues, if 200 is REQUIRED even when it' s | |
// redirect response. If you want to change the status code just call it AFTER | |
// this method: | |
// e. g. [...]->prepare()->setStatusCode(301)[...] | |
if ($this->statusCode === 200 && $this->headers->has('Location')) { | |
$this->setStatusCode('302'); | |
} | |
if ($request->isMethod('HEAD')) { | |
$this->content = null; | |
} | |
if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) { | |
$this->headers->set('pragma', 'no-cache'); | |
$this->headers->set('expires', -1); | |
} | |
return $this; | |
} | |
/** | |
* Add a cookie to the response | |
* @param Cookie $cookie The cookie object | |
*/ | |
public function addCookie(Cookie $cookie) | |
{ | |
$this->cookies->push($cookie); | |
} | |
/** | |
* Add a header to the response | |
* @param string $name The name of the header (e.g. "Location") | |
* @param string $value The value of the header (e.g. "foo.bar") | |
* @param boolean $overwrite Whether it should replaces existing headers | |
*/ | |
public function addHeader($name, $value, $overwrite = false) | |
{ | |
$this->headers->set($name, $value, $overwrite); | |
} | |
/** | |
* Set the status code of the response | |
* If the text is null it will try to determine the text from the internal lib | |
* @param integer $code The status code | |
* @param string $txt The status text | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function setStatusCode($code = 200, $txt = null) | |
{ | |
$this->statusCode = $code; | |
if ($txt === null) { | |
$this->statusText = isset(self::$statusCodeRegistry[$code]) ? self::$statusCodeRegistry[$code] : ''; | |
return $this; | |
} | |
if ($txt === false) { | |
$this->statusText = ''; | |
return $this; | |
} | |
$this->statusText = $txt; | |
return $this; | |
} | |
/** | |
* Returns the status code of this response | |
* @return integer The status code | |
*/ | |
public function getStatusCode() | |
{ | |
return $this->statusCode; | |
} | |
/** | |
* Set the content for this response | |
* @param string $content The content | |
* @return \Modulework\Modules\Http\Response THIS | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public function setContent($content = '') | |
{ | |
if (!$this->validateContent($content)) { | |
throw new InvalidArgumentException('The Response content must be a string or an object implementing __toString() magic method, "' . gettype($content) . '" given.'); | |
} | |
$this->content = (string) $content; | |
return $this; | |
} | |
/** | |
* Append to the content for this response | |
* @param string $content The content to append | |
* @return \Modulework\Modules\Http\Response THIS | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public function appendContent($content = '') | |
{ | |
if (!$this->validateContent($content)) { | |
throw new InvalidArgumentException('The Response content must be a string or an object implementing __toString() magic method, "' . gettype($content) . '" given.'); | |
} | |
if ($this->content === '') { | |
return $this->setContent($content); | |
} | |
$this->content .= (string) $content; | |
return $this; | |
} | |
/** | |
* Returns the content of this response | |
* @return string The content | |
*/ | |
public function getContent() | |
{ | |
return $this->content; | |
} | |
/** | |
* Set the date for this request | |
* @param DateTime $date The DateTime object | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function setDate(DateTime $date) | |
{ | |
$this->headers->set('Date', $date->format('D, d M Y H:i:s') . ' GMT'); | |
return $this; | |
} | |
/** | |
* Returns the date of this response | |
* @return string The Date | |
*/ | |
public function getDate() | |
{ | |
$default = new DateTime(); | |
$default = $default->format('D, d M Y H:i:s') . 'GMT'; | |
return $this->headers->get('Date', $default); | |
} | |
/** | |
* Set the HTTP protocol version for this response | |
* @param string $version The HTTP protocol version | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function setProtocolVersion($version = '1.0') | |
{ | |
$this->protocolVersion = $version; | |
return $this; | |
} | |
/** | |
* Returns the HTTP protocol version of this response | |
* @return string The HTTP protocol version | |
*/ | |
public function getProtocolVersion() | |
{ | |
return $this->protocolVersion; | |
} | |
/** | |
* Set the Expires HTTP header. | |
* To remove it pass NULL as parameter | |
* @param \DateTime|null $date A \DateTime instance | null | |
* | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function setExpires(DateTime $date = null) | |
{ | |
if ($date === null) { | |
$this->headers->remove('Expires'); | |
} else { | |
$date->setTimezone(new DateTimeZone('UTC')); | |
$this->headers->set('Expires', $date->format('D, d M Y H:i:s') . ' GMT'); | |
} | |
return $this; | |
} | |
/** | |
* Get the Expires HTTP header (as \DateTime instance). | |
* | |
* @return \DateTime The DateTime instance | null | |
*/ | |
public function getExpires() | |
{ | |
return new DateTime($this->headers->get('Expires')); | |
} | |
/** | |
* Set the charset for this response | |
* @param string $version The charset | |
* @return \Modulework\Modules\Http\Response THIS | |
*/ | |
public function setCharset($charset) | |
{ | |
$this->charset = $charset; | |
return $this; | |
} | |
/** | |
* Returns the charset of this response | |
* @return string The charset | |
*/ | |
public function getCharset() | |
{ | |
return $this->charset; | |
} | |
/** | |
* Is the response successful? | |
* @return boolean Whether the response is successful | |
*/ | |
public function isSuccess() | |
{ | |
return ($this->statusCode >= 200 && $this->statusCode < 300); | |
} | |
/** | |
* Is the response OK? | |
* @return boolean Whether the response is OK | |
*/ | |
public function isOk() | |
{ | |
return (200 === $this->statusCode); | |
} | |
/** | |
* Is the response a not found error? | |
* @return boolean Whether the response is a not found error | |
*/ | |
public function isNotFound() | |
{ | |
return (404 === $this->statusCode); | |
} | |
/** | |
* Is the response forbidden? | |
* @return boolean Whether the response is forbidden | |
*/ | |
public function isForbidden() | |
{ | |
return (403 === $this->statusCode); | |
} | |
/** | |
* Is the response a redirect? | |
* @return boolean Whether the response is a redirect | |
*/ | |
public function isRedirect() | |
{ | |
return ($this->headers->has('Location') && ($this->statusCode >= 300 && $this->statusCode < 400)); | |
} | |
/** | |
* Is the response a client error? | |
* @return boolean Whether the response is a client error | |
*/ | |
public function isClientError() | |
{ | |
return ($this->statusCode >= 400 && $this->statusCode < 500); | |
} | |
/** | |
* Is the response a server error? | |
* @return boolean Whether the response is a server error | |
*/ | |
public function isServerError() | |
{ | |
return ($this->statusCode >= 500 && $this->statusCode < 600); | |
} | |
/** | |
* Is the response empty? | |
* @return boolean Whether the response is empty | |
*/ | |
public function isEmpty() | |
{ | |
return in_array($this->statusCode, array(201, 204, 304)); | |
} | |
/** | |
* Is the response invalid? | |
* @return boolean Whether the response is invalid | |
*/ | |
public function isInvalid() | |
{ | |
return ($this->statusCode < 100 || $this->statusCode >= 600); | |
} | |
/** | |
* Is the response informational? | |
* @return boolean Whether the response is informational | |
*/ | |
public function isInformational() | |
{ | |
return ($this->statusCode >= 100 && $this->statusCode < 200); | |
} | |
/** | |
* Validate the content | |
* Allowed types: | |
* - string | |
* - integers | |
* - objects implementing __toString() | |
* | |
* @param mixed $content The content to check | |
* @return bool Whether the content is valid | |
*/ | |
protected static function validateContent($content) | |
{ | |
if (!is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { | |
return false; | |
} | |
return true; | |
} | |