Router.php
어느 정도 코드 정리가 된것 같아 공유합니다.
api 전체코드는 주말에 플러그인 게시판에 업로드 될것 같습니다.
[code]
<?php /** * @author Bram(us) Van Damme <bramus@bram.us> * @copyright Copyright (c), 2013 Bram(us) Van Damme * @license MIT public license */ /** * Class Router. */ class Router { /** * @var array The route patterns and their handling functions */ private $afterRoutes = array(); /** * @var array The before middleware route patterns and their handling functions */ private $beforeRoutes = array(); /** * @var object|callable The function to be executed when no route has been matched */ protected $notFoundCallback; /** * @var string Current base route, used for (sub)route mounting */ private $baseRoute = ''; /** * @var string The Request Method that needs to be handled */ private $requestedMethod = ''; /** * @var string The Server Base Path for Router Execution */ private $serverBasePath; /** * @var string Default Controllers Namespace */ private $namespace = ''; private $statusCode = 200; private $responseBody = ""; private $config = []; public function __construct($config = null) { if($config) { $this->config = $config; } } public function getConfig($key = null) { if($key) { if(array_key_exists($key, $this->config)) { return $this->config[$key]; } return null; } else { return $this->config; } } public function setConfig($config) { return $this->config = $config; } public function addConfig($key, $value) { $this->config[$key] = $value; } public function getPdoConnection() { return isset($this->config['pdo_db']) ? $this->config['pdo_db'] : null; } /** * Store a before middleware route and a handling function to be executed when accessed using one of the specified methods. * * @param string $methods Allowed methods, | delimited * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function before($methods, $pattern, $fn) { $pattern = $this->baseRoute . '/' . trim($pattern, '/'); $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern; foreach (explode('|', $methods) as $method) { $this->beforeRoutes[$method][] = array( 'pattern' => $pattern, 'fn' => $fn, ); } } /** * Store a route and a handling function to be executed when accessed using one of the specified methods. * * @param string $methods Allowed methods, | delimited * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function match($methods, $pattern, $fn) { $pattern = $this->baseRoute . '/' . trim($pattern, '/'); $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern; foreach (explode('|', $methods) as $method) { $this->afterRoutes[$method][] = array( 'pattern' => $pattern, 'fn' => $fn, ); } } /** * Shorthand for a route accessed using any method. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function all($pattern, $fn) { $this->match('GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD', $pattern, $fn); } /** * Shorthand for a route accessed using GET. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function get($pattern, $fn) { $this->match('GET', $pattern, $fn); } /** * Shorthand for a route accessed using POST. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function post($pattern, $fn) { $this->match('POST', $pattern, $fn); } /** * Shorthand for a route accessed using PATCH. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function patch($pattern, $fn) { $this->match('PATCH', $pattern, $fn); } /** * Shorthand for a route accessed using DELETE. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function delete($pattern, $fn) { $this->match('DELETE', $pattern, $fn); } /** * Shorthand for a route accessed using PUT. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function put($pattern, $fn) { $this->match('PUT', $pattern, $fn); } /** * Shorthand for a route accessed using OPTIONS. * * @param string $pattern A route pattern such as /about/system * @param object|callable $fn The handling function to be executed */ public function options($pattern, $fn) { $this->match('OPTIONS', $pattern, $fn); } /** * Mounts a collection of callbacks onto a base route. * * @param string $baseRoute The route sub pattern to mount the callbacks on * @param callable $fn The callback method */ public function mount($baseRoute, $fn) { // Track current base route $curBaseRoute = $this->baseRoute; // Build new base route string $this->baseRoute .= $baseRoute; // Call the callable call_user_func($fn); // Restore original base route $this->baseRoute = $curBaseRoute; } /** * Get all request headers. * * @return array The request headers */ public function getRequestHeaders() { $headers = array(); // If getallheaders() is available, use that if (function_exists('getallheaders')) { $headers = getallheaders(); // getallheaders() can return false if something went wrong if ($headers !== false) { return $headers; } } // Method getallheaders() not available or went wrong: manually extract 'm foreach ($_SERVER as $name => $value) { if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) { $headers[str_replace(array(' ', 'Http'), array('-', 'HTTP'), ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } } return $headers; } /** * Get the request method used, taking overrides into account. * * @return string The Request method to handle */ public function getRequestMethod() { // Take the method as found in $_SERVER $method = $_SERVER['REQUEST_METHOD']; // If it's a HEAD request override it to being GET and prevent any output, as per HTTP Specification // @url http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { ob_start(); $method = 'GET'; } // If it's a POST request, check for a method override header elseif ($_SERVER['REQUEST_METHOD'] == 'POST') { $headers = $this->getRequestHeaders(); if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], array('PUT', 'DELETE', 'PATCH'))) { $method = $headers['X-HTTP-Method-Override']; } } return $method; } /** * Set a Default Lookup Namespace for Callable methods. * * @param string $namespace A given namespace */ public function setNamespace($namespace) { if (is_string($namespace)) { $this->namespace = $namespace; } } /** * Get the given Namespace before. * * @return string The given Namespace if exists */ public function getNamespace() { return $this->namespace; } /** * Execute the router: Loop all defined before middleware's and routes, and execute the handling function if a match was found. * * @param object|callable $callback Function to be executed after a matching route was handled (= after router middleware) * * @return bool */ public function run($callback = null) { // Define which method we need to handle $this->requestedMethod = $this->getRequestMethod(); // Handle all before middlewares if (isset($this->beforeRoutes[$this->requestedMethod])) { $this->handle($this->beforeRoutes[$this->requestedMethod]); } // Handle all routes $numHandled = 0; if (isset($this->afterRoutes[$this->requestedMethod])) { $numHandled = $this->handle($this->afterRoutes[$this->requestedMethod], true); } // If no route was handled, trigger the 404 (if any) if ($numHandled === 0) { $this->trigger404(); } // If a route was handled, perform the finish callback (if any) else { if ($callback && is_callable($callback)) { $callback(); } } // If it originally was a HEAD request, clean up after ourselves by emptying the output buffer if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { ob_end_clean(); } echo $this->responseBody; // Return true if a route was handled, false otherwise return $numHandled !== 0; } /** * Set the 404 handling function. * * @param object|callable $fn The function to be executed */ public function set404($fn) { $this->notFoundCallback = $fn; } /** * Triggers 404 response */ public function trigger404(){ if ($this->notFoundCallback) { $this->invoke($this->notFoundCallback); } else { header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); } } /** * Handle a a set of routes: if a match is found, execute the relating handling function. * * @param array $routes Collection of route patterns and their handling functions * @param bool $quitAfterRun Does the handle function need to quit after one route was matched? * * @return int The number of routes handled */ private function handle($routes, $quitAfterRun = false) { // Counter to keep track of the number of routes we've handled $numHandled = 0; // The current page URL $uri = $this->getCurrentUri(); // Loop all routes foreach ($routes as $route) { // Replace all curly braces matches {} into word patterns (like Laravel) $route['pattern'] = preg_replace('/\/{(.*?)}/', '/(.*?)', $route['pattern']); // we have a match! if (preg_match_all('#^' . $route['pattern'] . '$#', $uri, $matches, PREG_OFFSET_CAPTURE)) { // Rework matches to only contain the matches, not the orig string $matches = array_slice($matches, 1); // Extract the matched URL parameters (and only the parameters) $params = array_map(function ($match, $index) use ($matches) { // We have a following parameter: take the substring from the current param position until the next one's position (thank you PREG_OFFSET_CAPTURE) if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && is_array($matches[$index + 1][0])) { if ($matches[$index + 1][0][1] > -1) { return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/'); } } // We have no following parameters: return the whole lot return isset($match[0][0]) && $match[0][1] != -1 ? trim($match[0][0], '/') : null; }, $matches, array_keys($matches)); // Call the handling function with the URL parameters if the desired input is callable $this->invoke($route['fn'], $params); ++$numHandled; // If we need to quit, then quit if ($quitAfterRun) { break; } } } // Return the number of routes handled return $numHandled; } private function invoke($fn, $params = array()) { if (is_callable($fn)) { try { $returnval = call_user_func_array($fn, $params); $this->responseBody = $returnval; } catch (Exception $ex) { header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); exit; } } // If not, check the existence of special parameters elseif (stripos($fn, '@') !== false) { // Explode segments of given route list($controller, $method) = explode('@', $fn); // Adjust controller class if namespace has been set if ($this->getNamespace() !== '') { $controller = $this->getNamespace() . '\\' . $controller; } // Make sure it's callable if (is_callable(array($controller, $method))) { if ((new \ReflectionMethod($controller, $method))->isStatic()) { forward_static_call_array(array($controller, $method), $params); } else { // Make sure we have an instance, to prevent "non-static method … should not be called statically" warnings if (\is_string($controller)) { $controller = new $controller(); } call_user_func_array(array($controller, $method), $params); } } } } /** * Define the current relative URI. * * @return string */ public function getCurrentUri() { // Get the current Request URI and remove rewrite base path from it (= allows one to run the router in a sub folder) $uri = substr(rawurldecode($_SERVER['REQUEST_URI']), strlen($this->getBasePath())); // Don't take query params into account on the URL if (strstr($uri, '?')) { $uri = substr($uri, 0, strpos($uri, '?')); } // Remove trailing slash + enforce a slash at the start return '/' . trim($uri, '/'); } /** * Return server base Path, and define it if isn't defined. * * @return string */ public function getBasePath() { // Check if server base path is defined, if not define it. if ($this->serverBasePath === null) { $this->serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/'; } return $this->serverBasePath; } /** * Explicilty sets the server base path. To be used when your entry script path differs from your entry URLs. * @see https://github.com/bramus/router/issues/82#issuecomment-466956078 * * @param string */ public function setBasePath($serverBasePath) { $this->serverBasePath = $serverBasePath; } public function withStatus($code) { $this->statusCode = $code; switch ($code) { case 100: $text = 'Continue'; break; case 101: $text = 'Switching Protocols'; break; case 200: $text = 'OK'; break; case 201: $text = 'Created'; break; case 202: $text = 'Accepted'; break; case 203: $text = 'Non-Authoritative Information'; break; case 204: $text = 'No Content'; break; case 205: $text = 'Reset Content'; break; case 206: $text = 'Partial Content'; break; case 300: $text = 'Multiple Choices'; break; case 301: $text = 'Moved Permanently'; break; case 302: $text = 'Moved Temporarily'; break; case 303: $text = 'See Other'; break; case 304: $text = 'Not Modified'; break; case 305: $text = 'Use Proxy'; break; case 400: $text = 'Bad Request'; break; case 401: $text = 'Unauthorized'; break; case 402: $text = 'Payment Required'; break; case 403: $text = 'Forbidden'; break; case 404: $text = 'Not Found'; break; case 405: $text = 'Method Not Allowed'; break; case 406: $text = 'Not Acceptable'; break; case 407: $text = 'Proxy Authentication Required'; break; case 408: $text = 'Request Time-out'; break; case 409: $text = 'Conflict'; break; case 410: $text = 'Gone'; break; case 411: $text = 'Length Required'; break; case 412: $text = 'Precondition Failed'; break; case 413: $text = 'Request Entity Too Large'; break; case 414: $text = 'Request-URI Too Large'; break; case 415: $text = 'Unsupported Media Type'; break; case 500: $text = 'Internal Server Error'; break; case 501: $text = 'Not Implemented'; break; case 502: $text = 'Bad Gateway'; break; case 503: $text = 'Service Unavailable'; break; case 504: $text = 'Gateway Time-out'; break; case 505: $text = 'HTTP Version not supported'; break; default: $text = 'Internal Server Error'; //exit('Unknown http status code "' . htmlentities($code) . '"'); break; } $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); header($protocol . ' ' . $code . ' ' . $text); } public function withJson($result, $code = 200) { if ($code) { $this->withStatus($code); } Header('Content-Type: application/json; charset=UTF-8'); return json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } public function withBody($result, $code = 200) { if ($code) { $this->withStatus($code); } return $result; } }
[/code]
기본 베이스는 다들 아시다시피 https://github.com/bramus/router 입니다.
단일 1개의 파일로 구성되어 있어 composer 를 사용하지 않는 장점(?)이 있습니다.
예전에 쓰던 slim 프레임웍과 닮아서 적응이 쉬웠습니다.
api 는 대부분 json 응답을 쓰기 때문에 해당 코드에 두가지 메소드 withJson, withStatus 를 추가했습니다.
이 또한 slim에서 쓰던 문법 형태가 편해 보여서 추가하였습니다.
Router 기본 코드에는 설정값을 추가하는 부분이 없어서, 설정값을 외부에서 주입할수 있게 추가하였습니다.
addConfig(), setConfig(), getConfig()
이 메소드들을 통해 그누보드 설정값과 db접속정보들을 라우터 내부에 전달합니다.
언제쯤이 될지 모르지만, 그누보드도 composer 베이스로 리뉴얼 될거라고 봅니다. 그때까지 좀 어중간하지만 이 형태의 코드를 쓰는것도 좋은 선택이 될것입니다. 그럼
|
댓글을 작성하시려면 로그인이 필요합니다.
댓글 3개