Overview
  • Namespace
  • Class
  • Tree

Namespaces

  • Modulework
    • Modules
      • Http
        • Exceptions
        • Utilities
  • PHP

Classes

  • Cookie
  • JsonResponse
  • RedirectResponse
  • Request
  • Response
  1 <?php namespace Modulework\Modules\Http;
  2 /*
  3  * (c) Christian Gärtner <christiangaertner.film@googlemail.com>
  4  * This file is part of the Modulework Framework
  5  * License: View distributed LICENSE file
  6  */
  7 
  8 use DateTime;
  9 use DateTimeZone;
 10 use RunTimeException;
 11 use InvalidArgumentException;
 12 use Modulework\Modules\Http\Cookie;
 13 use Modulework\Modules\Http\Request;
 14 use Modulework\Modules\Http\Utilities\ArrayCase;
 15 use Modulework\Modules\Http\Utilities\HeaderCase;
 16 use Modulework\Modules\Http\Utilities\HeaderWrapper;
 17 use Modulework\Modules\Http\Utilities\HeaderWrapperInterface;
 18 use Modulework\Modules\Http\Exceptions\HttpExceptionInterface;
 19 
 20 
 21 /**
 22 * Response
 23 * This class should represent the HTTP response,
 24 * done by the application.
 25 */
 26 class Response
 27 {
 28 
 29     /**
 30      * The main content
 31      * @var string
 32      */
 33     protected $content;
 34 
 35     /**
 36      * The HTTP status code
 37      * @var integer
 38      */
 39     protected $statusCode;
 40 
 41     /**
 42      * The HTTP status Text
 43      * @var string
 44      */
 45     protected $statusText;
 46 
 47     /**
 48      * The charset (header)
 49      * @var string
 50      */
 51     protected $charset;
 52 
 53     /**
 54      * The HTTP protocol version
 55      * @var string
 56      */
 57     protected $protocolVersion = '1.0';
 58 
 59     /**
 60      * The status code registry
 61      * List according to {@link http://www.iana.org/assignments/http-status-codes/http-status-codes.txt}
 62      * @var array
 63      */
 64     public static $statusCodeRegistry = array(
 65         100 => 'Continue',
 66         101 => 'Switching Protocols',
 67         102 => 'Processing',
 68         200 => 'OK',
 69         201 => 'Created',
 70         202 => 'Accepted',
 71         203 => 'Non-Authoritative Information',
 72         204 => 'No Content',
 73         205 => 'Reset Content',
 74         206 => 'Partial Content',
 75         207 => 'Multi-Status',
 76         208 => 'Already Reported',
 77         226 => 'IM Used',
 78         300 => 'Multiple Choices',
 79         301 => 'Moved Permanently',
 80         302 => 'Found',
 81         303 => 'See Other',
 82         304 => 'Not Modified',
 83         305 => 'Use Proxy',
 84         306 => 'Reserved',
 85         307 => 'Temporary Redirect',
 86         308 => 'Permanent Redirect',
 87         400 => 'Bad Request',
 88         401 => 'Unauthorized',
 89         402 => 'Payment Required',
 90         403 => 'Forbidden',
 91         404 => 'Not Found',
 92         405 => 'Method Not Allowed',
 93         406 => 'Not Acceptable',
 94         407 => 'Proxy Authentication Required',
 95         408 => 'Request Timeout',
 96         409 => 'Conflict',
 97         410 => 'Gone',
 98         411 => 'Length Required',
 99         412 => 'Precondition Failed',
100         413 => 'Request Entity Too Large ',
101         414 => 'Request-URI Too Long',
102         415 => 'Unsupported Media Type',
103         416 => 'Requested Range Not Satisfiable',
104         417 => 'Expectation Failed',
105         422 => 'Unprocessable Entity',
106         423 => 'Locked',
107         424 => 'Failed Dependency ',
108         425 => 'Unassigned',
109         426 => 'Upgrade Required',
110         427 => 'Unassigned',
111         428 => 'Precondition Required',
112         429 => 'Too Many Requests',
113         430 => 'Unassigned',
114         431 => 'Request Header Fields Too Large ',
115         500 => 'Internal Server Error',
116         501 => 'Not Implemented',
117         502 => 'Bad Gateway',
118         503 => 'Service Unavailable',
119         504 => 'Gateway Timeout',
120         505 => 'HTTP Version Not Supported',
121         506 => 'Variant Also Negotiates (Experimental)',
122         507 => 'Insufficient Storage',
123         508 => 'Loop Detected',
124         509 => 'Unassigned',
125         510 => 'Not Extended',
126         511 => 'Network Authentication Required'
127         );
128     
129     /**
130      * The headers to get sent
131      * @var \Modulework\Modules\Http\Utilities\ArrayCase
132      */
133     public $headers;
134 
135     /**
136      * The cookies to get sent
137      * @var \Modulework\Modules\Http\Utilities\ArrayCase
138      */
139     public $cookies;
140 
141     /**
142      * The HeaderWrapper
143      * @var \Modulework\Modules\Http\Utilities\HeaderWrapperInterface
144      */
145     protected $headerWrapper;
146 
147     /**
148      * Factory for the Response object
149      * @param  string  $content The body of the HTTP response
150      * @param  integer $code    The HTTP status code
151      * @param  array   $headers The HTTP headers
152      * 
153      * @param  \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null $headerWrapper The wrapper for PHP' s native header releated functions
154      * 
155      * @return \Modulework\Modules\Http\Response The new Response object
156      *
157      * @throws \InvalidArgumentException (from Constructor)
158      */
159     public static function make($content = '', $code = 200, array $headers = array(), HeaderWrapperInterface $headerWrapper = null)
160     {
161         return new static($content, $code, $headers, $headerWrapper);
162     }
163 
164     /**
165      * Factory for the Response object
166      * Create a Response from a HttpException
167      * @param  HttpExceptionInterface $e             The exception
168      * @param  array                  $headers       The HTTP headers
169      *
170      * @param  \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null $headerWrapper The wrapper for PHP' s native header releated functions
171      * 
172      * @return \Modulework\Modules\Http\Response The new Response object
173      *
174      * @throws \InvalidArgumentException (from Constructor)
175      */
176     public static function fromException(HttpExceptionInterface $e, array $headers = array(), HeaderWrapperInterface $headerWrapper = null)
177     {
178         $content = isset(self::$statusCodeRegistry[$e->getStatusCode()]) ? self::$statusCodeRegistry[$e->getStatusCode()] : '';
179         $content = $e->getMessage() ?: $content;
180         
181         return new static($content, $e->getStatusCode(), $headers, $headerWrapper);
182     }
183 
184     /**
185      * Constructor.
186      * @param  string  $content The body of the HTTP response
187      * @param  integer $code    The HTTP status code
188      * @param  array   $headers The HTTP headers
189      * 
190      * @param  \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null $headerWrapper The wrapper for PHP' s native header releated functions
191      * 
192      * @return \Modulework\Modules\Http\Response The new Response object
193      *
194      * @throws \InvalidArgumentException (from setContent)
195      */
196     public function __construct($content = '', $code = 200, array $headers = array(), HeaderWrapperInterface $headerWrapper = null)
197     {
198         $this->setStatusCode($code);
199         $this->setContent($content);
200         
201         $this->headers = new HeaderCase($headers);
202         $this->cookies = new ArrayCase();
203 
204         if (!$this->headers->has('Date')) {
205             $this->setDate(new DateTime(null, new DateTimeZone('UTC')));
206         }
207 
208         $this->setHeaderWrapper($headerWrapper);
209     }
210 
211     /**
212      * PHP' s magic method __toString
213      * Format:
214      * HTTP/{VERSION} {STATUSCODE} {STATUSTEXT}
215      * {HEADERS}
216      * {BODY}
217      * 
218      * @return string The response as string
219      */
220     public function __toString()
221     {
222         return
223         sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->statusCode, $this->statusText) . "\r\n" .
224         $this->headers->showForResponse() . "\r\n" .
225         $this->getContent();
226     }
227 
228     /**
229      * Dependency injection for the HeaderWrapper (also availbe thru the constructor)
230      * If null is passed it will create a new instance of \Modulework\Modules\Http\Utilities\HeaderWrapper
231      *
232      * It returns the "previous" HeaderWrapper or null
233      * 
234      * @param \Modulework\Modules\Http\Utilities\HeaderWrapperInterface $headerWrapper The HeaderWrapper
235      *
236      * @return \Modulework\Modules\Http\Utilities\HeaderWrapperInterface | null     "previous" HeaderWrapper | null
237      */
238     public function setHeaderWrapper(HeaderWrapperInterface $headerWrapper = null)
239     {
240         if ($this->headerWrapper !== null) {
241             $ret = $this->headerWrapper;
242         } else {
243             $ret = null;
244         }
245 
246         if ($headerWrapper === null) {
247             $this->headerWrapper = new HeaderWrapper;
248         } else {
249             $this->headerWrapper = $headerWrapper;
250         }
251 
252         return $ret;
253     }
254 
255     /**
256      * Send the headers and cookies to the client
257      * @uses sendCookies
258      * @return \Modulework\Modules\Http\Response THIS
259      */
260     public function sendHeaders()
261     {
262         if ($this->headerWrapper->headers_sent()) {
263             return $this;
264         }
265 
266         $this->headerWrapper->header(sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->statusCode, $this->statusText));
267 
268         foreach ($this->headers->all() as $name => $value) {
269             $this->headerWrapper->header($name . ': ' . $value);
270         }
271 
272         $this->sendCookies();
273 
274         return $this;
275 
276     }
277 
278     /**
279      * Send the cookies only to the client
280      * @return \Modulework\Modules\Http\Response THIS
281      */
282     public function sendCookies()
283     {
284         if ($this->headerWrapper->headers_sent()) {
285             return $this;
286         }
287 
288         foreach ($this->cookies->all() as $cookie) {
289             $this->headerWrapper->setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
290         }
291         return $this;
292     }
293 
294     /**
295      * Sends the content to the client
296      * @return \Modulework\Modules\Http\Response THIS
297      */
298     public function sendContent()
299     {
300         echo $this->content;
301 
302         return $this;
303     }
304 
305     /**
306      * Send the response to the client (headers, cookies, content)
307      * @uses sendHeaders
308      * @uses sendContent
309      * @return \Modulework\Modules\Http\Response THIS
310      */
311     public function send()
312     {
313         $this->sendHeaders();
314         $this->sendContent();
315 
316         return $this;
317     }
318 
319     /**
320      * Prepares the response based on the Request object
321      * This method is not essential and can be cut out of the chain.
322      * However it cleans up the headers and does some other stuff under the hood.
323      * @param  Request $req The request object
324      * 
325      * @return \Modulework\Modules\Http\Response THIS
326      */
327     public function prepare(Request $request)
328     {
329         if ($this->isInvalid()) {
330             throw new RunTimeException('Response is invalid (' . $this->statusCode . ')');
331             
332         }
333 
334         if ($this->isInformational()) {
335             $this->content = null;
336         }
337 
338         $this->charset = $this->charset ?: 'UTF-8';
339         if (!$this->headers->has('Content-Type')) {
340             $this->headers->set('Content-Type', 'text/html; charset=' . $this->charset);
341         }
342 
343         // This method tries may cause some issues, if 200 is REQUIRED even when it' s
344         // redirect response. If you want to change the status code just call it AFTER
345         // this method:
346         // e. g. [...]->prepare()->setStatusCode(301)[...]
347         if ($this->statusCode === 200 && $this->headers->has('Location')) {
348             $this->setStatusCode('302');
349         }
350 
351         if ($request->isMethod('HEAD')) {
352             $this->content = null;
353         }
354 
355         if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) {
356             $this->headers->set('pragma', 'no-cache');
357             $this->headers->set('expires', -1);
358         }
359 
360         return $this;
361     }
362 
363     /**
364      * Add a cookie to the response
365      * @param Cookie $cookie The cookie object
366      */
367     public function addCookie(Cookie $cookie)
368     {
369         $this->cookies->push($cookie);
370     }
371 
372     /**
373      * Add a header to the response
374      * @param string  $name      The name of the header (e.g. "Location")
375      * @param string  $value     The value of the header (e.g. "foo.bar")
376      * @param boolean $overwrite Whether it should replaces existing headers
377      */
378     public function addHeader($name, $value, $overwrite = false)
379     {
380         $this->headers->set($name, $value, $overwrite);
381     }
382 
383     /**
384      * Set the status code of the response
385      * If the text is null it will try to determine the text from the internal lib
386      * @param integer $code The status code
387      * @param string  $txt  The status text
388      * @return \Modulework\Modules\Http\Response THIS
389      */
390     public function setStatusCode($code = 200, $txt = null)
391     {
392         $this->statusCode = $code;
393 
394         if ($txt === null) {
395             $this->statusText = isset(self::$statusCodeRegistry[$code]) ? self::$statusCodeRegistry[$code] : '';
396             return $this;
397         }
398 
399         if ($txt === false) {
400             $this->statusText = '';
401             return $this;
402         }
403 
404         $this->statusText = $txt;
405 
406         return $this;
407     }
408 
409     /**
410      * Returns the status code of this response
411      * @return integer The status code
412      */
413     public function getStatusCode()
414     {
415         return $this->statusCode;
416     }
417 
418     /**
419      * Set the content for this response
420      * @param string $content The content
421      * @return \Modulework\Modules\Http\Response THIS
422      *
423      * @throws \InvalidArgumentException
424      */
425     public function setContent($content = '')
426     {
427         if (!$this->validateContent($content)) {
428             throw new InvalidArgumentException('The Response content must be a string or an object implementing __toString() magic method, "' . gettype($content) . '" given.');
429         }
430 
431         $this->content = (string) $content;
432         
433         return $this;
434     }
435 
436     /**
437      * Append to the content for this response
438      * @param string $content The content to append
439      * @return \Modulework\Modules\Http\Response THIS
440      *
441      * @throws \InvalidArgumentException
442      */
443     public function appendContent($content = '')
444     {
445         if (!$this->validateContent($content)) {
446             throw new InvalidArgumentException('The Response content must be a string or an object implementing __toString() magic method, "' . gettype($content) . '" given.');
447         }
448 
449         if ($this->content === '') {
450             return $this->setContent($content);
451         }
452 
453         $this->content .= (string) $content;
454         
455         return $this;
456     }
457     /**
458      * Returns the content of this response
459      * @return string          The content
460      */
461     public function getContent()
462     {
463         return $this->content;
464     }
465 
466     /**
467      * Set the date for this request
468      * @param DateTime $date The DateTime object
469      * @return \Modulework\Modules\Http\Response THIS
470      */
471     public function setDate(DateTime $date)
472     {
473         $this->headers->set('Date', $date->format('D, d M Y H:i:s') . ' GMT');
474         return $this;
475     }
476 
477     /**
478      * Returns the date of this response
479      * @return string The Date
480      */
481     public function getDate()
482     {
483         $default = new DateTime();
484         $default = $default->format('D, d M Y H:i:s') . 'GMT';
485 
486         return $this->headers->get('Date', $default);
487     }
488 
489     /**
490      * Set the HTTP protocol version for this response
491      * @param string $version The HTTP protocol version
492      * @return \Modulework\Modules\Http\Response THIS
493      */
494     public function setProtocolVersion($version = '1.0')
495     {
496         $this->protocolVersion = $version;
497         return $this;
498     }
499 
500     /**
501      * Returns the HTTP protocol version of this response
502      * @return string The HTTP protocol version
503      */
504     public function getProtocolVersion()
505     {
506         return $this->protocolVersion;
507     }
508 
509     /**
510      * Set the Expires HTTP header.
511      * To remove it pass NULL as parameter
512      * @param \DateTime|null $date A \DateTime instance | null
513      * 
514      * @return \Modulework\Modules\Http\Response THIS
515      */
516     public function setExpires(DateTime $date = null)
517     {
518         if ($date === null) {
519             $this->headers->remove('Expires');
520         } else {
521             $date->setTimezone(new DateTimeZone('UTC'));
522             $this->headers->set('Expires', $date->format('D, d M Y H:i:s') . ' GMT');
523         }
524 
525         return $this;
526     }
527 
528     /**
529      * Get the Expires HTTP header (as \DateTime instance).
530      * 
531      * @return \DateTime The DateTime instance | null
532      */
533     public function getExpires()
534     {
535         return new DateTime($this->headers->get('Expires'));
536     }
537 
538     /**
539      * Set the charset for this response
540      * @param string $version The charset
541      * @return \Modulework\Modules\Http\Response THIS
542      */
543     public function setCharset($charset)
544     {
545         $this->charset = $charset;
546         return $this;
547     }
548 
549     /**
550      * Returns the charset of this response
551      * @return string The charset
552      */
553     public function getCharset()
554     {
555         return $this->charset;
556     }
557 
558     /**
559      * Is the response successful?
560      * @return boolean Whether the response is successful
561      */
562     public function isSuccess()
563     {
564         return ($this->statusCode >= 200 && $this->statusCode < 300);
565     }
566 
567     /**
568      * Is the response OK?
569      * @return boolean Whether the response is OK
570      */
571     public function isOk()
572     {
573         return (200 === $this->statusCode);
574     }
575 
576     /**
577      * Is the response a not found error?
578      * @return boolean Whether the response is a not found error
579      */
580     public function isNotFound()
581     {
582         return (404 === $this->statusCode);
583     }
584 
585     /**
586      * Is the response forbidden?
587      * @return boolean Whether the response is forbidden
588      */
589     public function isForbidden()
590     {
591         return (403 === $this->statusCode);
592     }
593 
594     /**
595      * Is the response a redirect?
596      * @return boolean Whether the response is a redirect
597      */
598     public function isRedirect()
599     {
600         return ($this->headers->has('Location') && ($this->statusCode >= 300 && $this->statusCode < 400));
601     }
602 
603     /**
604      * Is the response a client error?
605      * @return boolean Whether the response is a client error
606      */
607     public function isClientError()
608     {
609         return ($this->statusCode >= 400 && $this->statusCode < 500);
610     }
611 
612     /**
613      * Is the response a server error?
614      * @return boolean Whether the response is a server error
615      */
616     public function isServerError()
617     {
618         return ($this->statusCode >= 500 && $this->statusCode < 600);
619     }
620 
621     /**
622      * Is the response empty?
623      * @return boolean Whether the response is empty
624      */
625     public function isEmpty()
626     {
627         return in_array($this->statusCode, array(201, 204, 304));
628     }
629 
630     /**
631      * Is the response invalid?
632      * @return boolean Whether the response is invalid
633      */
634     public function isInvalid()
635     {
636         return ($this->statusCode < 100 || $this->statusCode >= 600);
637     }
638 
639     /**
640      * Is the response informational?
641      * @return boolean Whether the response is informational
642      */
643     public function isInformational()
644     {
645         return ($this->statusCode >= 100 && $this->statusCode < 200);
646     }
647 
648     /**
649      * Validate the content
650      * Allowed types:
651      * - string
652      * - integers
653      * - objects implementing __toString()
654      * 
655      * @param  mixed $content The content to check
656      * @return bool          Whether the content is valid
657      */
658     protected static function validateContent($content)
659     {
660         if (!is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) {
661             return false;
662         }
663 
664         return true;
665     }
666 
667 
668 
669 
670 }
API documentation generated by ApiGen 2.8.0