diff options
author | Arnold Daniels <arnold@jasny.net> | 2014-10-08 12:40:22 +0200 |
---|---|---|
committer | Arnold Daniels <arnold@jasny.net> | 2014-10-08 12:40:22 +0200 |
commit | 47c0dd632bb05496f16448141f3a77dae639f84d (patch) | |
tree | 32b9c5bbf119cd9cb944137a3194b759d3a28edd | |
parent | 48af26269f1b3b7e55faee49e0a06572d8edfe50 (diff) | |
download | router-47c0dd632bb05496f16448141f3a77dae639f84d.zip router-47c0dd632bb05496f16448141f3a77dae639f84d.tar.gz router-47c0dd632bb05496f16448141f3a77dae639f84d.tar.bz2 |
Added `Request::supportInputType()` and `Request::acceptOrigin`
Router no longer exits
Added Success response functions to Controller
Added response checks to Controller
-rw-r--r-- | src/Controller.php | 134 | ||||
-rw-r--r-- | src/Request.php | 361 | ||||
-rw-r--r-- | src/Router.php | 126 |
3 files changed, 462 insertions, 159 deletions
diff --git a/src/Controller.php b/src/Controller.php index 8a2381f..b3c8d46 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -32,6 +32,8 @@ abstract class Controller public function __construct($router=null) { $this->router = $router; + + // Static classes are instantiated to make it easier to use custom versions $this->request = new Request(); $this->flash = new Flash(); } @@ -48,6 +50,51 @@ abstract class Controller /** + * Get the request input data, decoded based on Content-Type header. + * + * @param string|array $supportedFormats Supported input formats (mime or short format) + * @return mixed + */ + protected function getInput($supportedFormats = null) + { + if (isset($supportedFormats)) { + $failed = $this->router ? function($message) { + $this->router->badRequest($message, 415); + } : null; + + $this->request->supportInputFormat($supportedFormats, $failed); + } + + return $this->request->getInput(); + } + + + /** + * Set the headers with HTTP status code and content type. + * + * @param int $httpCode HTTP status code (may be omitted) + * @param string $format Mime or simple format + * @return $this + */ + protected function respondWith($httpCode, $format=null) + { + $this->request->respondWith($httpCode, $format); + return $this; + } + + /** + * Output data + * + * @param array $data + * @param string $format Mime or content format + * @return $this + */ + protected function output($data, $format = null) + { + $this->request->output($data, $format); + } + + /** * Show a view. * * @param string $name Filename of Twig template @@ -65,42 +112,44 @@ abstract class Controller /** - * Get the request input data, decoded based on Content-Type header. + * Respond with 200 Ok. + * This is the default state, so you usually don't have to set it explicitly. * - * @return mixed + * @return $this; */ - protected function getInput() + protected function ok() { - return $this->request->getInput(); + $this->request->respondWith(200); } /** - * Set the headers with HTTP status code and content type. + * Respond with 201 Created * - * @param int $httpCode HTTP status code (may be omitted) - * @param string $format Mime or simple format - * @return Controller $this + * @param string $location Location of the created resource + * @return $this; */ - protected function respondWith($httpCode, $format=null) + protected function created($location = null) { - $this->request->respondWith($httpCode, $format); - return $this; + $this->request->respondWith(201); + if (isset($location)) header("Location: $location"); } /** - * Output data + * Respond with 204 No Content * - * @param array $data + * @return $this; */ - protected function output($data) + protected function noContent() { - $this->request->output($data); + $this->request->respondWith(204); } /** * Redirect to previous page. * Must be on this website, otherwise redirect to home. + * + * @return $this; */ protected function back() { @@ -122,8 +171,6 @@ abstract class Controller header("Location: $url"); echo 'You are being redirected to <a href="' . $url . '">' . $url . '</a>'; } - - exit(); } @@ -140,8 +187,6 @@ abstract class Controller } else { $this->request->outputError($httpCode, $message); } - - exit(); } /** @@ -169,8 +214,6 @@ abstract class Controller if (!isset($message)) $message = "Sorry, you are not allowed to view this page"; $this->request->outputError($httpCode, $message); } - - exit(); } /** @@ -187,8 +230,6 @@ abstract class Controller if (!isset($message)) $message = "Sorry, this page does not exist"; $this->request->outputError($httpCode, $message); } - - exit(); } /** @@ -205,7 +246,50 @@ abstract class Controller } else { $this->request->outputError($httpCode, $message); } - - exit(); + } + + + /** + * Check if response is 2xx succesful + * + * @return boolean + */ + protected function isSuccessful() + { + $code = http_response_code(); + return $code >= 200 && $code < 300; + } + + /** + * Check if response is a 3xx redirect + * + * @return boolean + */ + protected function isRedirection() + { + $code = http_response_code(); + return $code >= 300 && $code < 400; + } + + /** + * Check if response is a 4xx client error + * + * @return boolean + */ + protected function isClientError() + { + $code = http_response_code(); + return $code >= 400 && $code < 500; + } + + /** + * Check if response is a 5xx redirect + * + * @return boolean + */ + protected function isServerError() + { + $code = http_response_code(); + return $code >= 500; } } diff --git a/src/Request.php b/src/Request.php index 2fa0b12..2c759a0 100644 --- a/src/Request.php +++ b/src/Request.php @@ -12,39 +12,39 @@ class Request * @var array */ static public $httpStatusCodes = [ - 200 => '200 OK', - 201 => '201 Created', - 202 => '202 Accepted', - 204 => '204 No Content', - 205 => '205 Reset Content', - 206 => '206 Partial Content', - 301 => '301 Moved Permanently', - 302 => '302 Found', - 303 => '303 See Other', - 304 => '304 Not Modified', - 307 => '307 Temporary Redirect', - 308 => '308 Permanent Redirect', - 400 => "400 Bad Request", - 401 => "401 Unauthorized", - 402 => "402 Payment Required", - 403 => "403 Forbidden", - 404 => "404 Not Found", - 405 => "405 Method Not Allowed", - 406 => "406 Not Acceptable", - 409 => "409 Conflict", - 410 => "410 Gone", - 415 => "415 Unsupported Media Type", - 429 => "429 Too Many Requests", - 500 => "500 Internal server error", - 501 => "501 Not Implemented", - 503 => "503 Service unavailable" + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 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', + 409 => 'Conflict', + 410 => 'Gone', + 415 => 'Unsupported Media Type', + 429 => 'Too Many Requests', + 500 => 'Internal server error', + 501 => 'Not Implemented', + 503 => 'Service unavailable' ]; /** * Common input and output formats with associated MIME * @var array */ - static public $contentFormats = [ + public static $contentFormats = [ 'text/html' => 'html', 'application/json' => 'json', 'application/xml' => 'xml', @@ -64,34 +64,49 @@ class Request * File extensions to format mapping * @var array */ - static public $fileExtension = [ + public static $fileExtension = [ 'jpg' => 'jpeg', 'txt' => 'text' ]; /** + * Allow the use $_POST['_method'] as request method. + * @var boolean + */ + public static $allowMethodOverride = false; + + /** + * Always set 'Content-Type' to 'text/plain' with a 4xx or 5xx response. + * This is useful when handing jQuery AJAX requests, since jQuery doesn't deserialize errors. + * + * @var boolean + */ + public static $forceTextErrors = false; + + + /** * Get the HTTP protocol * * @return string; */ protected static function getProtocol() { - return isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; + return isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; } /** * Return the request method. * - * Usually REQUEST_METHOD, but this can be overwritten by $_POST['_method']. - * Method is alway uppercase. - * * @return string */ public static function getMethod() { - return strtoupper(!empty($_POST['_method']) ? $_POST['_method'] : $_SERVER['REQUEST_METHOD']); + return static::$allowMethodOverride && !empty($_POST['_method']) ? + strtoupper(!empty($_POST['_method'])) : + strtoupper($_SERVER['REQUEST_METHOD']); } + /** * Get the input format. * Uses the 'Content-Type' request header. @@ -99,17 +114,62 @@ class Request * @param string $as 'short' or 'mime' * @return string */ - public static function getInputFormat($as='short') + public static function getInputFormat($as) { - if (empty($_SERVER['CONTENT_TYPE'])) { - $mime = trim(explode(';', $_SERVER['CONTENT_TYPE'])[0]); - } + if (empty($_SERVER['CONTENT_TYPE'])) return null; + + $mime = trim(explode(';', $_SERVER['CONTENT_TYPE'])[0]); return $as !== 'mime' && isset(static::$contentFormats[$mime]) ? static::$contentFormats[$mime] : $mime; } + /** + * Check `Content-Type` request header to see if input format is supported. + * Respond with "415 Unsupported Media Type" if the format isn't supported. + * + * @param string|array $support Supported formats (short or mime) + * @param callback $failed Callback when format is not supported + */ + public static function supportInputFormat($support, $failed = null) + { + $mime = static::getInputFormat('mime'); + + if (!isset($mime)) { + if (file_get_contents('php://input', false, null, -1, 1) === '') return; + } else { + if (static::matchMime($mime, $support)) return; + } + + // Not supported + $message = isset($mime) ? + "The request body is in an unsupported format" : + "The 'Content-Type' request header isn't set"; + + if (isset($failed)) call_user_func($failed, $message, $support); + + static::outputError("415 Unsupported Media Type", $message); + exit(); + } + + /** + * Check the Content-Type of the request. + * + * @param string $mime + * @param string|array $formats Short format or MIME, may contain wildcard + * @return mixed + */ + protected static function matchMime($mime, $formats) + { + $fnWildcardMatch = function($ret, $pattern) use ($mime) { + return $ret || fnmatch($pattern, $mime); + }; + + return + isset(static::$contentFormats[$mime]) && in_array(static::$contentFormats[$mime], $formats) || + array_reduce($formats, $fnWildcardMatch, false); + } /** * Get the request input data, decoded based on Content-Type header. @@ -118,22 +178,81 @@ class Request */ public static function getInput() { - switch (static::getInputFormat()) { - case 'post': return $_FILES + $_POST; - case 'json': return json_decode(file_get_contents('php://input')); + switch (static::getInputFormat('short')) { + case 'post': $_POST + static::getPostedFiles(); + case 'json': return json_decode(file_get_contents('php://input'), true); case 'xml': return simplexml_load_string(file_get_contents('php://input')); default: return file_get_contents('php://input'); } } /** + * Get $_FILES properly grouped. + * + * @return array + */ + protected static function getPostedFiles() + { + $files = $_FILES; + + foreach ($files as &$file) { + if (!is_array($file['error'])) continue; + + $group = []; + foreach (array_keys($file['error']) as $key) { + foreach (array_keys($file) as $elem) { + $group[$key][$elem] = $file[$elem][$key]; + } + } + $file = $group; + } + + return $files; + } + + + /** + * Check if request is an AJAX request. + * + * @return boolean + */ + public static function isAjax() + { + return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && + strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + } + + /** + * Check if requested to wrap JSON as JSONP response. + * Assumes that the output format is json. + * + * @return boolean + */ + public static function isJsonp() + { + return !empty($_GET['callback']); + } + + /** + * Returns the HTTP referer if it is on the current host. + * + * @return string + */ + public static function getLocalReferer() + { + return !empty($_SERVER['HTTP_REFERER']) && parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) == + $_SERVER['HTTP_HOST'] ? $_SERVER['HTTP_REFERER'] : null; + } + + + /** * Get the output format. * Tries 'Content-Type' response header, otherwise uses 'Accept' request header. * * @param string $as 'short' or 'mime' * @return string */ - public static function getOutputFormat($as='short') + public static function getOutputFormat($as) { // Explicitly set as Content-Type response header foreach (headers_list() as $header) { @@ -160,47 +279,120 @@ class Request $ext = pathinfo(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), PATHINFO_EXTENSION); if ($ext) { if (isset(static::$fileExtension[$ext])) $ext = static::$fileExtension[$ext]; - if ($as === 'mime') return $ext; - return array_search($ext, static::$contentFormats) ?: $ext; + return ($as === 'mime') ? (array_search($ext, static::$contentFormats) ?: $ext) : $ext; } // Don't know (default to HTML) return $as === 'mime' ? '*/*' : 'html'; } - + /** - * Check if request is an AJAX request. + * Accept requests from a specific origin. * - * @return boolean + * Sets HTTP Access-Control headers (CORS). + * @link http://www.w3.org/TR/cors/ + * + * The following settings are available: + * - expose-headers + * - max-age + * - allow-credentials + * - allow-methods (default '*') + * - allow-headers (default '*') + * + * <code> + * Request::allowOrigin('same'); + * Request::allowOrigin('www.example.com'); + * Request::allowOrigin('*.example.com'); + * Request::allowOrigin(['*.example.com', 'www.example.net']); + * Request::allowOrigin('*'); + * + * Request::allowOrigin('same', [], function() { + * static::respondWith("403 forbidden", 'html'); + * echo "<h1>Forbidden</h1><p>Sorry, we have a strict same-origin policy.</p>"; + * exit(); + * }); + * </code> + * + * @param string|array $urls Allowed URL/URLs, may use wildcards or "same" + * @param array $settings + * @param callback $failed Called when origin is not allowed */ - public static function isAjax() + public static function allowOrigin($urls, array $settings = [], $failed = null) { - return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && - strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + $origin = static::matchOrigin($urls); + + static::setAllowOriginHeaders($origin ?: $urls, $settings); + + if (!isset($origin)) { + $message = "Origin not allowed"; + if (isset($failed)) call_user_func($failed, $message, $urls); + static::outputError("403 forbidden", $message); + exit(); + } } /** - * Check if requested to wrap JSON as JSONP response. - * Assumes that the output format is json. + * Match `Origin` header to supplied urls. * - * @return boolean + * @param string|array $urls + * @return string */ - public static function isJsonp() + protected function matchOrigin($urls) { - return !empty($_GET['callback']); + if ($urls === '*') return '*'; + + if (!is_array($urls)) $urls = (array)$urls; + + $origin = parse_url($_SERVER['origin']) + ['port' => 80]; + + foreach ($urls as &$url) { + if ($url === 'same') $url = '//' . $_SERVER['HTTP_HOST']; + if (strpos($url, ':') && substr($url, 0, 2) !== '//') $url = '//' . $url; + + $match = parse_url($url); + $found = + (!isset($match['scheme']) || $match['scheme'] === $origin['schema']) && + (!isset($match['port']) || $match['port'] === $origin['port']) && + fnmatch($match['domain'], $origin['port']); + + if ($found) return $_SERVER['origin']; + } + + return null; } /** - * Returns the HTTP referer if it is on the current host. + * Sets HTTP Access-Control headers (CORS). * - * @return string + * @param string|array $origin + * @param array $settings */ - public static function getLocalReferer() + protected function setAllowOriginHeaders($origin, array $settings = []) { - return !empty($_SERVER['HTTP_REFERER']) && parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) == - $_SERVER['HTTP_HOST'] ? $_SERVER['HTTP_REFERER'] : null; + foreach ((array)$origin as $url) { + header("Access-Control-Allow-Origin: $url"); + } + + if (isset($settings['expose-headers'])) { + header("Access-Control-Allow-Credentials: " . join(', ', (array)$settings['expose-headers'])); + } + + if (isset($settings['max-age'])) { + header("Access-Control-Max-Age: " . join(', ', (array)$settings['max-age'])); + } + + if (isset($settings['allow-credentials'])) { + $set = $settings['allow-credentials']; + header("Access-Control-Allow-Credentials: " . (is_string($set) ? $set : ($set ? 'true' : 'false'))); + } + + $methods = isset($settings['allow-methods']) ? $settings['allow-methods'] : '*'; + header("Access-Control-Allow-Methods: " . join(', ', (array)$methods)); + + $headers = isset($settings['allow-headers']) ? $settings['allow-headers'] : '*'; + header("Access-Control-Allow-Headers: " . join(', ', (array)$headers)); } - + /** * Set the headers with HTTP status code and content type. @@ -208,25 +400,26 @@ class Request * * Examples: * <code> - * static::respondWith(200, 'json'); - * static::respondWith(200, 'application/json'); - * static::respondWith(204); - * static::respondWith('json'); + * Request::respondWith(200, 'json'); + * Request::respondWith(200, 'application/json'); + * Request::respondWith(204); + * Request::respondWith("204 Created"); + * Request::respondWith('json'); + * Request::respondWith(['json', 'xml']); * </code> * - * @param int $httpCode HTTP status code (may be omitted) - * @param string $format Mime or content format + * @param int $httpCode HTTP status code (may be omitted) + * @param string|array $format Mime or content format + * @param callback $failed Called if format isn't accepted */ - public static function respondWith($httpCode, $format=null) + public static function respondWith($httpCode, $format = null) { - if (!isset($format) && !is_int($httpCode) && !ctype_digit($httpCode)) { - $format = $httpCode; - $httpCode = null; + // Shift arguments if $httpCode is omitted + if (!is_int($httpCode) && !preg_match('/^\d{3}\b/', $httpCode)) { + list($httpCode, $format) = array_merge([null], func_get_args()); } - if (isset($httpCode)) { - header(static::getProtocol() . ' ' . static::$httpStatusCodes[$httpCode]); - } + if (isset($httpCode)) http_response_code((int)$httpCode); if (isset($format)) { $contentType = array_search($format, static::$contentFormats) ?: $format; @@ -237,11 +430,12 @@ class Request /** * Output result as json, xml or image. * - * @param mixed $data + * @param mixed $data + * @param string $format Mime or content format */ - public function output($data) + public function output($data, $format = null) { - $format = static::getOutputFormat(); + if (!isset($format)) $format = static::getOutputFormat(); switch ($format) { case 'json': static::outputJSON($data); break; @@ -256,8 +450,7 @@ class Request default: $type = (is_object($data) ? get_class($data) . ' ' : '') . gettype($data); - $a = in_array($type[0], explode('', 'aeiouAEIOU')) ? 'an' : 'a'; - trigger_error("Don't know how to convert $a $type to $format", E_USER_ERROR); + trigger_error("Don't know how to convert a $type to $format", E_USER_ERROR); } } @@ -270,8 +463,7 @@ class Request { if (!$result instanceof \DOMNode && !$result instanceof \SimpleXMLElement) { $type = (is_object($result) ? get_class($result) . ' ' : '') . gettype($result); - $a = in_array($type[0], explode('', 'aeiouAEIOU')) ? 'an' : 'a'; - throw new \Exception("Was expecting a DOMNode or SimpleXMLElement object, got $a $type"); + throw new \Exception("Was expecting a DOMNode or SimpleXMLElement object, got a $type"); } static::respondWith('xml'); @@ -287,7 +479,7 @@ class Request */ protected function outputJSON($result) { - if ($this->request->isJsonp()) { + if (static::isJsonp()) { static::respondWith(200, 'js'); echo $_GET['callback'] . '(' . json_encode($result) . ')'; return; @@ -307,8 +499,7 @@ class Request { if (!is_resource($image)) { $type = (is_object($image) ? get_class($image) . ' ' : '') . gettype($image); - $a = in_array($type[0], explode('', 'aeiouAEIOU')) ? 'an' : 'a'; - throw new \Exception("Was expecting a GD resource, got $a $type"); + throw new \Exception("Was expecting a GD resource, got a $type"); } static::respondWith("image/$format"); @@ -316,7 +507,7 @@ class Request $out = 'image' . $format; $out($image); } - + /** * Output an HTTP error @@ -358,7 +549,7 @@ class Request */ protected static function outputErrorJson($httpCode, $error) { - $result = ['_error' => $error, '_httpCode' => $httpCode]; + $result = ['error' => $error, 'httpCode' => $httpCode]; static::respondWith($httpCode, 'json'); static::output($result); diff --git a/src/Router.php b/src/Router.php index 38d9f45..4ed5964 100644 --- a/src/Router.php +++ b/src/Router.php @@ -88,6 +88,10 @@ class Router { if (is_object($routes)) $routes = get_object_vars($routes); + foreach ($routes as &$route) { + if ($route instanceof \Closure) $route = (object)['fn' => $route]; + } + $this->routes = $routes; $this->route = null; @@ -113,6 +117,8 @@ class Router continue; } + if ($route instanceof \Closure) $route = (object)['fn' => $route]; + $this->routes[$path] = $route; } @@ -341,28 +347,25 @@ class Router $route->$key = $value; } - // Route to file - if (isset($route->file)) { - $file = rtrim($_SERVER['DOCUMENT_ROOT'], '/') . '/' . ltrim($this->rebase($route->file), '/'); - - if (!file_exists($file)) { - trigger_error("Failed to route using '{$route->route}': File '$file' doesn't exist." - , E_USER_WARNING); - return false; - } - - return include $file; - } + if (isset($route->controller)) return $this->routeToController($route); + if (isset($route->fn)) return $this->routeToCallback($route); + if (isset($route->file)) return $this->routeToFile($route); - // Route to controller - if (empty($route->controller) || empty($route->action)) { - trigger_error("Failed to route using '{$route->route}': " - . (empty($route->controller) ? 'Controller' : 'Action') . " is not set", E_USER_WARNING); - return false; - } + trigger_error("Failed to route using '{$route->route}': Neither 'controller', 'fn' or 'file' is set", + E_USER_WARNING); + return false; + } + /** + * Route to controller action + * + * @param object $route + * @return mixed|boolean + */ + protected function routeToController($route) + { $class = $this->getControllerClass($route->controller); - $method = $this->getActionMethod($route->action); + $method = $this->getActionMethod(isset($route->action) ? $route->action : null); $args = isset($route->args) ? $route->args : []; if (!class_exists($class)) return false; @@ -373,6 +376,50 @@ class Router $ret = call_user_func_array([$controller, $method], $args); return isset($ret) ? $ret : true; } + + /** + * Route to a callback function + * + * @param object $route + * @return mixed|boolean + */ + protected function routeToCallback($route) + { + if (!is_callable($route->fn)) { + trigger_error("Failed to route using '{$route->route}': Invalid callback." + , E_USER_WARNING); + return false; + } + + $args = isset($route->args) ? $route->args : []; + + return call_user_func_array($route->fn, $args); + } + + /** + * Route to a file + * + * @param object $route + * @return mixed|boolean + */ + protected function routeToFile($route) + { + $file = ltrim($route->file, '/'); + + if (!file_exists($file)) { + trigger_error("Failed to route using '{$route->route}': File '$file' doesn't exist.", E_USER_WARNING); + return false; + } + + if ($route->file[0] === '~' || strpos($route->file, '..') !== false || strpos($route->file, ':') !== false) { + trigger_error("Won't route using '{$route->route}': '~', '..' and ':' not allowed in filename.", + E_USER_WARNING); + return false; + } + + return include $file; + } + /** * Execute the action. @@ -423,7 +470,7 @@ class Router } if ($this->prevErrorHandler) { - //call_user_func($this->prevErrorHandler, $code, $message, $file, $line, $context); + call_user_func($this->prevErrorHandler, $code, $message, $file, $line, $context); } if ($code & (E_RECOVERABLE_ERROR | E_USER_ERROR)) { @@ -439,14 +486,15 @@ class Router */ public function onException($exception) { + $this->error(null, 500, $exception); if ($this->prevExceptionHandler) call_user_func($this->prevExceptionHandler, $exception); - $this->error(null, 500, $exception); + exit(); } /** - * Redirect to another page and exit + * Redirect to another page * * @param string $url * @param int $httpCode 301 (Moved Permanently), 303 (See Other) or 307 (Temporary Redirect) @@ -461,11 +509,10 @@ class Router header("Location: $url"); echo 'You are being redirected to <a href="' . $url . '">' . $url . '</a>'; - exit(); } /** - * Give a 400 Bad Request response and exit + * Give a 400 Bad Request response * * @param string $message * @param int $httpCode Alternative HTTP status code, eg. 406 (Not Acceptable) @@ -474,7 +521,6 @@ class Router public function badRequest($message, $httpCode=400) { $this->respond(400, $message, $httpCode, array_slice(func_get_args(), 2)); - exit(); } /** @@ -484,7 +530,6 @@ class Router public function requireLogin() { $this->routeTo(401) || $this->forbidden(); - exit(); } /** @@ -502,12 +547,10 @@ class Router if (!isset($message)) $message = "Sorry, you are not allowed to view this page"; self::outputError($httpCode, $message, $this->getOutputFormat()); } - - exit(); } /** - * Give a 404 Not Found response and exit + * Give a 404 Not Found response * * @param string $message * @param int $httpCode Alternative HTTP status code, eg. 410 (Gone) @@ -524,31 +567,16 @@ class Router self::outputError($httpCode, $message, $this->getOutputFormat()); } - - exit(); } /** - * Give a 500 Internal Server Error response and exit + * Give a 5xx Server Error response * - * @param string $message - * @param int $httpCode Alternative HTTP status code, eg. 503 (Service unavailable) - * @param mixed $.. Additional arguments are passed to action - */ - protected function error($message=null, $httpCode=500) - { - call_user_func_array([$this, 'displayError'], func_get_args()); - exit(); - } - - /** - * Give a 500 Internal Server Error response (but don't exit) - * - * @param string $message - * @param int $httpCode Alternative HTTP status code, eg. 503 (Service unavailable) - * @param mixed $.. Additional arguments are passed to action + * @param string $message + * @param int|string $httpCode HTTP status code, eg. "500 Internal Server Error" or 503 + * @param mixed $.. Additional arguments are passed to action */ - protected function displayError($message=null, $httpCode=500) + public function error($message=null, $httpCode=500) { if (ob_get_level() > 1) ob_end_clean(); |