diff options
Diffstat (limited to 'src/Controller.php')
-rw-r--r-- | src/Controller.php | 420 |
1 files changed, 402 insertions, 18 deletions
diff --git a/src/Controller.php b/src/Controller.php index ddec2c0..057653e 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -23,6 +23,26 @@ abstract class Controller protected $response = null; /** + * Common input and output formats with associated MIME + * @var array + */ + protected $contentFormats = [ + 'text/html' => 'html', + 'application/json' => 'json', + 'application/xml' => 'xml', + 'text/xml' => 'xml', + 'text/plain' => 'text', + 'application/javascript' => 'js', + 'text/css' => 'css', + 'image/png' => 'png', + 'image/gif' => 'gif', + 'image/jpeg' => 'jpeg', + 'image/x-icon' => 'ico', + 'application/x-www-form-urlencoded' => 'post', + 'multipart/form-data' => 'post' + ]; + + /** * Run the controller * * @return ResponseInterface @@ -65,6 +85,205 @@ abstract class Controller } /** + * Set the headers with HTTP status code and content type. + * @link http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + * + * Examples: + * <code> + * $this->responseWith(200, 'json'); + * $this->responseWith(200, 'application/json'); + * $this->responseWith(204); + * $this->responseWith("204 Created"); + * $this->responseWith('json'); + * </code> + * + * @param int $code HTTP status code (may be omitted) + * @param string|array $format Mime or content format + * @return ResponseInterface $response + */ + public function responseWith($code, $format = null) + { + $response = $this->getResponse(); + + // Shift arguments if $code is omitted + if (!is_int($code) && !preg_match('/^\d{3}\b/', $code)) { + list($code, $format) = array_merge([null], func_get_args()); + } + + if ($code) { + $response = $response->withStatus((int)$code); + } + + if ($format) { + $contentType = $this->getContentType($format); + $response = $response->withHeader('Content-Type', $contentType); + } + + return $response; + } + + /** + * Response with success 200 code + * + * @return ResponseInterface $response + */ + public function ok() + { + return $this->responseWith(200); + } + + /** + * Response with created 201 code, and optionaly redirect to created location + * + * @param string $location Url of created resource + * @return ResponseInterface $response + */ + public function created($location = '') + { + $response = $this->responseWith(201); + + if ($location) { + $response = $response->withHeader('Location', $location); + } + + return $response; + } + + /** + * Response with 204 'No Content' + * + * @return ResponseInterface $response + */ + public function noContent() + { + return $this->responseWith(204); + } + + /** + * Redirect to url + * + * @param string $url + * @param int $code 301 (Moved Permanently), 303 (See Other) or 307 (Temporary Redirect) + * @return ResponseInterface $response + */ + public function redirect($url, $code = 303) + { + $response = $this->responseWith($code, 'html'); + $response = $response->withHeader('Location', $url); + $response->getBody()->write('You are being redirected to <a href="' . $url . '">' . $url . '</a>'); + + return $response; + } + + /** + * Redirect to previous page, or to home page + * + * @return ResponseInterface $response + */ + public function back() + { + return $this->redirect($this->getLocalReferer() ?: '/'); + } + + /** + * Route to 401 + * Note: While the 401 route is used, we don't respond with a 401 http status code. + * + * @return ResponseInterface $response + */ + public function requireLogin() + { + return $this->redirect('/401'); + } + + /** + * Alias of requireLogin + * + * @return ResponseInterface $response + */ + public function requireAuth() + { + return $this->requireLogin(); + } + + /** + * Set response to error 'Bad Request' state + * + * @param string $message + * @param int $code HTTP status code + * @return ResponseInterface $response + */ + public function badRequest($message, $code = 400) + { + return $this->error($message, $code); + } + + /** + * Set response to error 'Forbidden' state + * + * @param string $message + * @param int $code HTTP status code + * @return ResponseInterface $response + */ + public function forbidden($message, $code = 403) + { + return $this->error($message, $code); + } + + /** + * Set response to error 'Not Found' state + * + * @param string $message + * @param int $code HTTP status code + * @return ResponseInterface $response + */ + public function notFound($message, $code = 404) + { + return $this->error($message, $code); + } + + /** + * Set response to error 'Conflict' state + * + * @param string $message + * @param int $code HTTP status code + * @return ResponseInterface $response + */ + public function conflict($message, $code = 409) + { + return $this->error($message, $code); + } + + /** + * Set response to error 'Too Many Requests' state + * + * @param string $message + * @param int $code HTTP status code + * @return ResponseInterface $response + */ + public function tooManyRequests($message, $code = 429) + { + return $this->error($message, $code); + } + + /** + * Set response to error state + * + * @param string $message + * @param int $code HTTP status code + * @return ResponseInterface $response + */ + public function error($message, $code = 400) + { + $response = $this->getResponse(); + + $errorResponse = $response->withStatus($code); + $errorResponse->getBody()->write($message); + + return $errorResponse; + } + + /** * Check if response is 2xx succesful, or empty * * @return boolean @@ -111,25 +330,190 @@ abstract class Controller } /** - * Check if response is 4xx or 5xx error - * - * @return boolean - */ - public function isError() - { - return $this->isClientError() || $this->isServerError(); - } - - /** - * Get status code of response - * - * @return int - */ - protected function getResponseStatusCode() - { + * Check if response is 4xx or 5xx error + * + * @return boolean + */ + public function isError() + { + return $this->isClientError() || $this->isServerError(); + } + + /** + * Check if request is GET request + * + * @return boolean + */ + public function isGetRequest() + { + $method = $this->getRequestMethod(); + + return !$method || $method === 'GET'; + } + + /** + * Check if request is POST request + * + * @return boolean + */ + public function isPostRequest() + { + return $this->getRequestMethod() === 'POST'; + } + + /** + * Check if request is PUT request + * + * @return boolean + */ + public function isPutRequest() + { + return $this->getRequestMethod() === 'PUT'; + } + + /** + * Check if request is DELETE request + * + * @return boolean + */ + public function isDeleteRequest() + { + return $this->getRequestMethod() === 'DELETE'; + } + + /** + * Check if request is HEAD request + * + * @return boolean + */ + public function isHeadRequest() + { + return $this->getRequestMethod() === 'HEAD'; + } + + /** + * Returns the HTTP referer if it is on the current host + * + * @return string + */ + public function getLocalReferer() + { + $request = $this->getRequest(); + $referer = $request->getHeaderLine('HTTP_REFERER'); + $host = $request->getHeaderLine('HTTP_HOST'); + + return $referer && parse_url($referer, PHP_URL_HOST) === $host ? $referer : ''; + } + + /** + * Output result + * + * @param mixed $data + * @param string $format + * @return ResponseInterface $response + */ + public function output($data, $format) + { $response = $this->getResponse(); + $contentType = $this->getContentType($format); + $response = $response->withHeader('Content-Type', $contentType); + $content = is_scalar($data) ? $data : $this->encodeData($data, $format); - return $response ? $response->getStatusCode() : 0; - } + $response->getBody()->write($content); + + return $response; + } + + /** + * Encode data to send to client + * + * @param mixed $data + * @param string $format + * @return string + */ + public function encodeData($data, $format) + { + switch ($format) { + case 'json': return $this->encodeDataAsJson($data); + case 'xml': return $this->encodeDataAsXml($data); + case 'html': + throw new \InvalidArgumentException("To encode HTML please use a view"); + default: + throw new \InvalidArgumentException("Can not encode data for format '$format'"); + } + } + + /** + * Encode data as xml + * + * @param \SimpleXMLElement $data + * @return string + */ + protected function encodeDataAsXml(\SimpleXMLElement $data) + { + return $data->asXML(); + } + + /** + * Encode data as json + * + * @param mixed + * @return string + */ + protected function encodeDataAsJson($data) + { + $data = json_encode($data); + + return $this->isJsonp() ? + $this->getRequest()->getQueryParams()['callback'] . '(' . $data . ')' : + $data; + } + + /** + * Check if we should respond with jsonp + * + * @return boolean + */ + protected function isJsonp() + { + $request = $this->getRequest(); + + return $request && !empty($request->getQueryParams()['callback']); + } + + /** + * Get status code of response + * + * @return int + */ + protected function getResponseStatusCode() + { + $response = $this->getResponse(); + + return $response ? $response->getStatusCode() : 0; + } + + /** + * Get valid content type by simple word description + * + * @param string $format + * @return string + */ + protected function getContentType($format) + { + return array_search($format, $this->contentFormats) ?: $format; + } + + /** + * Get method of request + * + * @return string + */ + protected function getRequestMethod() + { + $request = $this->getRequest(); + + return $request ? $request->getMethod() : ''; + } } |