PageRenderTime 16ms CodeModel.GetById 2ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/Slim/Http/Response.php

http://github.com/codeguy/Slim
PHP | 480 lines | 212 code | 41 blank | 227 comment | 20 complexity | da31b50d9e6bc2d59621e45d1bef29b5 MD5 | raw file
  1<?php
  2/**
  3 * Slim Framework (https://slimframework.com)
  4 *
  5 * @link      https://github.com/slimphp/Slim
  6 * @copyright Copyright (c) 2011-2017 Josh Lockhart
  7 * @license   https://github.com/slimphp/Slim/blob/3.x/LICENSE.md (MIT License)
  8 */
  9namespace Slim\Http;
 10
 11use InvalidArgumentException;
 12use Psr\Http\Message\ResponseInterface;
 13use Psr\Http\Message\StreamInterface;
 14use Psr\Http\Message\UriInterface;
 15use Slim\Interfaces\Http\HeadersInterface;
 16
 17/**
 18 * Response
 19 *
 20 * This class represents an HTTP response. It manages
 21 * the response status, headers, and body
 22 * according to the PSR-7 standard.
 23 *
 24 * @link https://github.com/php-fig/http-message/blob/master/src/MessageInterface.php
 25 * @link https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php
 26 */
 27class Response extends Message implements ResponseInterface
 28{
 29    /**
 30     * Status code
 31     *
 32     * @var int
 33     */
 34    protected $status = 200;
 35
 36    /**
 37     * Reason phrase
 38     *
 39     * @var string
 40     */
 41    protected $reasonPhrase = '';
 42
 43    /**
 44     * Status codes and reason phrases
 45     *
 46     * @var array
 47     */
 48    protected static $messages = [
 49        //Informational 1xx
 50        100 => 'Continue',
 51        101 => 'Switching Protocols',
 52        102 => 'Processing',
 53        //Successful 2xx
 54        200 => 'OK',
 55        201 => 'Created',
 56        202 => 'Accepted',
 57        203 => 'Non-Authoritative Information',
 58        204 => 'No Content',
 59        205 => 'Reset Content',
 60        206 => 'Partial Content',
 61        207 => 'Multi-Status',
 62        208 => 'Already Reported',
 63        226 => 'IM Used',
 64        //Redirection 3xx
 65        300 => 'Multiple Choices',
 66        301 => 'Moved Permanently',
 67        302 => 'Found',
 68        303 => 'See Other',
 69        304 => 'Not Modified',
 70        305 => 'Use Proxy',
 71        306 => '(Unused)',
 72        307 => 'Temporary Redirect',
 73        308 => 'Permanent Redirect',
 74        //Client Error 4xx
 75        400 => 'Bad Request',
 76        401 => 'Unauthorized',
 77        402 => 'Payment Required',
 78        403 => 'Forbidden',
 79        404 => 'Not Found',
 80        405 => 'Method Not Allowed',
 81        406 => 'Not Acceptable',
 82        407 => 'Proxy Authentication Required',
 83        408 => 'Request Timeout',
 84        409 => 'Conflict',
 85        410 => 'Gone',
 86        411 => 'Length Required',
 87        412 => 'Precondition Failed',
 88        413 => 'Request Entity Too Large',
 89        414 => 'Request-URI Too Long',
 90        415 => 'Unsupported Media Type',
 91        416 => 'Requested Range Not Satisfiable',
 92        417 => 'Expectation Failed',
 93        418 => 'I\'m a teapot',
 94        421 => 'Misdirected Request',
 95        422 => 'Unprocessable Entity',
 96        423 => 'Locked',
 97        424 => 'Failed Dependency',
 98        426 => 'Upgrade Required',
 99        428 => 'Precondition Required',
100        429 => 'Too Many Requests',
101        431 => 'Request Header Fields Too Large',
102        444 => 'Connection Closed Without Response',
103        451 => 'Unavailable For Legal Reasons',
104        499 => 'Client Closed Request',
105        //Server Error 5xx
106        500 => 'Internal Server Error',
107        501 => 'Not Implemented',
108        502 => 'Bad Gateway',
109        503 => 'Service Unavailable',
110        504 => 'Gateway Timeout',
111        505 => 'HTTP Version Not Supported',
112        506 => 'Variant Also Negotiates',
113        507 => 'Insufficient Storage',
114        508 => 'Loop Detected',
115        510 => 'Not Extended',
116        511 => 'Network Authentication Required',
117        599 => 'Network Connect Timeout Error',
118    ];
119
120    /**
121     * EOL characters used for HTTP response.
122     *
123     * @var string
124     */
125     const EOL = "\r\n";
126
127    /**
128     * Create new HTTP response.
129     *
130     * @param int                   $status  The response status code.
131     * @param HeadersInterface|null $headers The response headers.
132     * @param StreamInterface|null  $body    The response body.
133     */
134    public function __construct($status = 200, HeadersInterface $headers = null, StreamInterface $body = null)
135    {
136        $this->status = $this->filterStatus($status);
137        $this->headers = $headers ? $headers : new Headers();
138        $this->body = $body ? $body : new Body(fopen('php://temp', 'r+'));
139    }
140
141    /**
142     * This method is applied to the cloned object
143     * after PHP performs an initial shallow-copy. This
144     * method completes a deep-copy by creating new objects
145     * for the cloned object's internal reference pointers.
146     */
147    public function __clone()
148    {
149        $this->headers = clone $this->headers;
150    }
151
152    /*******************************************************************************
153     * Status
154     ******************************************************************************/
155
156    /**
157     * Gets the response status code.
158     *
159     * The status code is a 3-digit integer result code of the server's attempt
160     * to understand and satisfy the request.
161     *
162     * @return int Status code.
163     */
164    public function getStatusCode()
165    {
166        return $this->status;
167    }
168
169    /**
170     * Return an instance with the specified status code and, optionally, reason phrase.
171     *
172     * If no reason phrase is specified, implementations MAY choose to default
173     * to the RFC 7231 or IANA recommended reason phrase for the response's
174     * status code.
175     *
176     * This method MUST be implemented in such a way as to retain the
177     * immutability of the message, and MUST return an instance that has the
178     * updated status and reason phrase.
179     *
180     * @link http://tools.ietf.org/html/rfc7231#section-6
181     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
182     * @param int $code The 3-digit integer result code to set.
183     * @param string $reasonPhrase The reason phrase to use with the
184     *     provided status code; if none is provided, implementations MAY
185     *     use the defaults as suggested in the HTTP specification.
186     * @return static
187     * @throws \InvalidArgumentException For invalid status code arguments.
188     */
189    public function withStatus($code, $reasonPhrase = '')
190    {
191        $code = $this->filterStatus($code);
192
193        if (!is_string($reasonPhrase) && !method_exists($reasonPhrase, '__toString')) {
194            throw new InvalidArgumentException('ReasonPhrase must be a string');
195        }
196
197        $clone = clone $this;
198        $clone->status = $code;
199        if ($reasonPhrase === '' && isset(static::$messages[$code])) {
200            $reasonPhrase = static::$messages[$code];
201        }
202
203        if ($reasonPhrase === '') {
204            throw new InvalidArgumentException('ReasonPhrase must be supplied for this code');
205        }
206
207        $clone->reasonPhrase = $reasonPhrase;
208
209        return $clone;
210    }
211
212    /**
213     * Filter HTTP status code.
214     *
215     * @param  int $status HTTP status code.
216     * @return int
217     * @throws \InvalidArgumentException If an invalid HTTP status code is provided.
218     */
219    protected function filterStatus($status)
220    {
221        if (!is_integer($status) || $status<100 || $status>599) {
222            throw new InvalidArgumentException('Invalid HTTP status code');
223        }
224
225        return $status;
226    }
227
228    /**
229     * Gets the response reason phrase associated with the status code.
230     *
231     * Because a reason phrase is not a required element in a response
232     * status line, the reason phrase value MAY be null. Implementations MAY
233     * choose to return the default RFC 7231 recommended reason phrase (or those
234     * listed in the IANA HTTP Status Code Registry) for the response's
235     * status code.
236     *
237     * @link http://tools.ietf.org/html/rfc7231#section-6
238     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
239     * @return string Reason phrase; must return an empty string if none present.
240     */
241    public function getReasonPhrase()
242    {
243        if ($this->reasonPhrase) {
244            return $this->reasonPhrase;
245        }
246        if (isset(static::$messages[$this->status])) {
247            return static::$messages[$this->status];
248        }
249        return '';
250    }
251
252    /*******************************************************************************
253     * Body
254     ******************************************************************************/
255
256    /**
257     * Write data to the response body.
258     *
259     * Note: This method is not part of the PSR-7 standard.
260     *
261     * Proxies to the underlying stream and writes the provided data to it.
262     *
263     * @param string $data
264     * @return $this
265     */
266    public function write($data)
267    {
268        $this->getBody()->write($data);
269
270        return $this;
271    }
272
273    /*******************************************************************************
274     * Response Helpers
275     ******************************************************************************/
276
277    /**
278     * Redirect.
279     *
280     * Note: This method is not part of the PSR-7 standard.
281     *
282     * This method prepares the response object to return an HTTP Redirect
283     * response to the client.
284     *
285     * @param  string|UriInterface $url    The redirect destination.
286     * @param  int|null            $status The redirect HTTP status code.
287     * @return static
288     */
289    public function withRedirect($url, $status = null)
290    {
291        $responseWithRedirect = $this->withHeader('Location', (string)$url);
292
293        if (is_null($status) && $this->getStatusCode() === 200) {
294            $status = 302;
295        }
296
297        if (!is_null($status)) {
298            return $responseWithRedirect->withStatus($status);
299        }
300
301        return $responseWithRedirect;
302    }
303
304    /**
305     * Json.
306     *
307     * Note: This method is not part of the PSR-7 standard.
308     *
309     * This method prepares the response object to return an HTTP Json
310     * response to the client.
311     *
312     * @param  mixed  $data   The data
313     * @param  int    $status The HTTP status code.
314     * @param  int    $encodingOptions Json encoding options
315     * @throws \RuntimeException
316     * @return static
317     */
318    public function withJson($data, $status = null, $encodingOptions = 0)
319    {
320        $response = $this->withBody(new Body(fopen('php://temp', 'r+')));
321        $response->body->write($json = json_encode($data, $encodingOptions));
322
323        // Ensure that the json encoding passed successfully
324        if ($json === false) {
325            throw new \RuntimeException(json_last_error_msg(), json_last_error());
326        }
327
328        $responseWithJson = $response->withHeader('Content-Type', 'application/json;charset=utf-8');
329        if (isset($status)) {
330            return $responseWithJson->withStatus($status);
331        }
332        return $responseWithJson;
333    }
334
335    /**
336     * Is this response empty?
337     *
338     * Note: This method is not part of the PSR-7 standard.
339     *
340     * @return bool
341     */
342    public function isEmpty()
343    {
344        return in_array($this->getStatusCode(), [204, 205, 304]);
345    }
346
347    /**
348     * Is this response informational?
349     *
350     * Note: This method is not part of the PSR-7 standard.
351     *
352     * @return bool
353     */
354    public function isInformational()
355    {
356        return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
357    }
358
359    /**
360     * Is this response OK?
361     *
362     * Note: This method is not part of the PSR-7 standard.
363     *
364     * @return bool
365     */
366    public function isOk()
367    {
368        return $this->getStatusCode() === 200;
369    }
370
371    /**
372     * Is this response successful?
373     *
374     * Note: This method is not part of the PSR-7 standard.
375     *
376     * @return bool
377     */
378    public function isSuccessful()
379    {
380        return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
381    }
382
383    /**
384     * Is this response a redirect?
385     *
386     * Note: This method is not part of the PSR-7 standard.
387     *
388     * @return bool
389     */
390    public function isRedirect()
391    {
392        return in_array($this->getStatusCode(), [301, 302, 303, 307]);
393    }
394
395    /**
396     * Is this response a redirection?
397     *
398     * Note: This method is not part of the PSR-7 standard.
399     *
400     * @return bool
401     */
402    public function isRedirection()
403    {
404        return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
405    }
406
407    /**
408     * Is this response forbidden?
409     *
410     * Note: This method is not part of the PSR-7 standard.
411     *
412     * @return bool
413     * @api
414     */
415    public function isForbidden()
416    {
417        return $this->getStatusCode() === 403;
418    }
419
420    /**
421     * Is this response not Found?
422     *
423     * Note: This method is not part of the PSR-7 standard.
424     *
425     * @return bool
426     */
427    public function isNotFound()
428    {
429        return $this->getStatusCode() === 404;
430    }
431
432    /**
433     * Is this response a client error?
434     *
435     * Note: This method is not part of the PSR-7 standard.
436     *
437     * @return bool
438     */
439    public function isClientError()
440    {
441        return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
442    }
443
444    /**
445     * Is this response a server error?
446     *
447     * Note: This method is not part of the PSR-7 standard.
448     *
449     * @return bool
450     */
451    public function isServerError()
452    {
453        return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
454    }
455
456    /**
457     * Convert response to string.
458     *
459     * Note: This method is not part of the PSR-7 standard.
460     *
461     * @return string
462     */
463    public function __toString()
464    {
465        $output = sprintf(
466            'HTTP/%s %s %s',
467            $this->getProtocolVersion(),
468            $this->getStatusCode(),
469            $this->getReasonPhrase()
470        );
471        $output .= Response::EOL;
472        foreach ($this->getHeaders() as $name => $values) {
473            $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)) . Response::EOL;
474        }
475        $output .= Response::EOL;
476        $output .= (string)$this->getBody();
477
478        return $output;
479    }
480}