/** * Matches a route and HTTP method/s against the request path, and if successful, invokes a controller and **exits**. * * When a route and method/s are matched, the HTTP response code is automatically set to `200`. * The controller is then invoked and responsible for the response code from then on. * * Otherwise a response code of either `404` or `405` is set. * * This also means that within a subrouter the HTTP response can go from an initial `200` to `404`/`405` if those subroutes fail. * * To allow safe fallthrough, a `404` response code will not replace an existing `405`. * * @param string[] $methods HTTP request methods, or empty to accept any method. * @param string $route A literal path with leading slash, or a regular expression. * Regular expressions are enclosed by anything other than a slash, since slashes are used for literal paths. * @param callable $controller Given either the [preg_match()](http://php.net/preg-match) array, or the literal path. * The controller can output content directly if it chooses, but it must also output headers and **exit**. * If not, the return value is given to {@link \H\O::mixed()}, which **exits**. */ public static function route($methods, $route, callable $controller) { $match = $path = I::path(); if ($route === $path or $route[0] !== '/' && preg_match($route, $path, $match)) { if (empty($methods) or in_array(I::method(), $methods)) { O::code(200); O::mixed(call_user_func($controller, $match)); // exits } else { O::code(405); } } elseif (O::code() !== 405) { O::code(404); } }
/** * Sends all pending {@link $headers}. * This will **exit** on the following conditions: * * - The request method is `HEAD` * - The response code is `3xx`, `204`, or `205`. * - The response has a `Last-Modified` header that is equal to the `If-Modified-Since` request header (if present). * This case also results in a `304` response code. */ public static function headers() { if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) and isset(self::$headers['Last-Modified']) and strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === strtotime(self::$headers['Last-Modified'])) { self::code(304); } foreach (array_filter(self::$headers, 'strlen') as $name => $value) { header("{$name}: {$value}"); } if (I::method() === 'HEAD' or preg_match('/^3|(20[45])$/', self::code())) { exit; } }