From dc23400f449e6a6388438c4f9d6c6d01c6361eb1 Mon Sep 17 00:00:00 2001 From: Anderson Salas <6117373+andersonsalas@users.noreply.github.com> Date: Sat, 14 Apr 2018 18:08:02 -0400 Subject: [PATCH] Improved current url detection and minor changes --- readme.md | 5 +- src/Hook.php | 172 ++++++++++++++++++++++++++++++++----------- src/Middleware.php | 41 +++++------ src/Route.php | 178 +++++++++++++++++++++++++++++++++------------ src/RouteParam.php | 52 +++++++++++-- 5 files changed, 328 insertions(+), 120 deletions(-) diff --git a/readme.md b/readme.md index 577846a..7e68ac3 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,6 @@ [![Total Downloads](https://poser.pugx.org/luthier/luthier/downloads?format=flat-square)](https://packagist.org/packages/luthier/luthier) [![Latest Unstable Version](https://poser.pugx.org/luthier/luthier/v/unstable?format=flat-square)](https://packagist.org/packages/luthier/luthier) [![License](https://poser.pugx.org/luthier/luthier/license?format=flat-square)](https://packagist.org/packages/luthier/luthier) -[![composer.lock](https://poser.pugx.org/luthier/luthier/composerlock?format=flat-square)](https://packagist.org/packages/luthier/luthier) Laravel-like routing and Middleware support for CodeIgniter 3. **Luthier-CI** makes the development of CodeIgniter apps even more enjoyable and simple! @@ -226,13 +225,13 @@ inside the group will share the *prefix* (first argument) and, optionally, anoth // Prefix only Route::group('prefix', function(){ Route::get('bar','test@bar'); - Route::get('baz','test@baz); + Route::get('baz','test@baz'); }); // Prefix and shared properties Route::group('prefix', ['namespace' => 'foo', 'middleware' => ['Admin','IPFilter']], function(){ Route::get('bar','test@bar'); - Route::get('baz','test@baz); + Route::get('baz','test@baz'); }); ``` diff --git a/src/Hook.php b/src/Hook.php index 341ef5c..c754eb6 100644 --- a/src/Hook.php +++ b/src/Hook.php @@ -17,7 +17,7 @@ final class Hook * Defines all required hooks in order to boot Luthier-CI * * @param array $hooks Existing hooks - + * * @return array * * @access public @@ -53,29 +53,27 @@ public static function getHooks($hooks = []) $hooks['post_controller'][] = $_hook; } - // - // Pre system hook - // - $hooks['pre_system'][] = function() { - // Defining some constants - define('LUTHIER_CI_VERSION', 1.0); + // + // Luthier-CI Initialization + // + // Defining some constants, creating and loading required files, etc. + // + + define('LUTHIER_CI_VERSION', '0.2.0'); $isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; $isCli = is_cli(); $isWeb = !$isCli; - // Including facades require_once __DIR__ . '/Facades/Route.php' ; - // Route files if( !file_exists(APPPATH . '/routes') ) { mkdir( APPPATH . '/routes' ); } - // Middleware folder if( !file_exists(APPPATH . '/middleware') ) { mkdir( APPPATH . '/middleware' ); @@ -101,7 +99,6 @@ public static function getHooks($hooks = []) require_once(APPPATH . '/routes/api.php'); } - // CLI routes file // (NOT WORKING YET!!!) if( !file_exists(APPPATH . '/routes/cli.php') ) { @@ -114,20 +111,24 @@ public static function getHooks($hooks = []) Route::get('/', Route::DEFAULT_CONTROLLER . '@index' ); } - // Default (fake) controller if( !file_exists(APPPATH . '/controllers/' . Route::DEFAULT_CONTROLLER . '.php')) { copy(__DIR__ . '/Resources/DefaultController.php', APPPATH.'/controllers/'. Route::DEFAULT_CONTROLLER .'.php' ); } - // Global functions require_once( __DIR__ . '/Functions.php'); - // Compile all routes Route::compileAll(); - // This allows us to use any HTTP Verb through a form with a hidden field + + // + // HTTP verbs in forms fix + // + // Since we only can perform GET and POST request via traditional html forms, + // this allows us to use any HTTP Verb if the form contains a hidden field // named "_method" + // + if(isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) == 'post') { if(isset($_POST['_method'])) @@ -136,23 +137,127 @@ public static function getHooks($hooks = []) } } - // Parsing uri and setting the current Luthier route (if exists) - // FIXME: Maybe this isn't sufficient - $uri = '/'; - if(isset($_SERVER['PATH_INFO'])) + // + // Parsing the current url + // + // This is the same code of the CI_URI class, but since we can't load it here + // (because 'undefined constant' errors) we have not choice that copy the + // needed code: + // + + $uriProtocol = config_item('uri_protocol'); + + $removeRelativeDirectory = function($uri) + { + $uris = array(); + $tok = strtok($uri, '/'); + while ($tok !== FALSE) + { + if (( ! empty($tok) OR $tok === '0') && $tok !== '..') + { + $uris[] = $tok; + } + $tok = strtok('/'); + } + + return implode('/', $uris); + }; + + $parseRequestUri = function() use($removeRelativeDirectory) + { + $uri = parse_url('http://dummy'.$_SERVER['REQUEST_URI']); + $query = isset($uri['query']) ? $uri['query'] : ''; + $uri = isset($uri['path']) ? $uri['path'] : ''; + + if (isset($_SERVER['SCRIPT_NAME'][0])) + { + if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) + { + $uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME'])); + } + elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) + { + $uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME']))); + } + } + + if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0) + { + $query = explode('?', $query, 2); + $uri = $query[0]; + $_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : ''; + } + else + { + $_SERVER['QUERY_STRING'] = $query; + } + + parse_str($_SERVER['QUERY_STRING'], $_GET); + + if ($uri === '/' OR $uri === '') + { + $uri = '/'; + } + + $uri = $removeRelativeDirectory($uri); + + return $uri; + }; + + if($uriProtocol == 'REQUEST_URI') + { + $url = $parseRequestUri(); + } + elseif($uriProtocol == 'QUERY_STRING') + { + $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING'); + + if (trim($uri, '/') === '') + { + $uri = ''; + } + elseif (strncmp($uri, '/', 1) === 0) + { + $uri = explode('?', $uri, 2); + $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : ''; + $uri = $uri[0]; + } + + parse_str($_SERVER['QUERY_STRING'], $_GET); + + $url = $removeRelativeDirectory($uri); + } + elseif($uriProtocol == 'PATH_INFO') + { + $url = isset($_SERVER['PATH_INFO']) ? trim($_SERVER['PATH_INFO']) : $parseRequestUri(); + } + else { - $uri = trim($_SERVER['PATH_INFO'],'/'); + show_error('Unsupported uri protocol', 500, 'Luthier-CI boot error'); } + if(empty($url)) + { + $url = '/'; + } + + + // + // Setting the current Luthier-CI route + // + // With the url correctly parsed, now it's time to find what + // route matches that url + // + try { - $currentRoute = Route::getByUrl($uri); + $currentRoute = Route::getByUrl($url); } catch(RouteNotFoundException $e) { - Route::addCompiledRoute($uri); - $currentRoute = Route::any($uri, function(){ + Route::addCompiledRoute($url); + $currentRoute = Route::any($url, function(){ if(!is_cli() && is_callable(Route::get404())) { $_404 = Route::get404(); @@ -169,10 +274,6 @@ public static function getHooks($hooks = []) Route::setCurrentRoute($currentRoute); }; - // - // Pre controller hook - // - $hooks['pre_controller'][] = function() { global $params, $URI, $class, $method; @@ -223,7 +324,7 @@ public static function getHooks($hooks = []) { $route->params[$pcount]->value = $URI->segment($i+1); - // Removing "sticky" route parameters from CI callback parameters + // Removing "sticky" route parameters if(substr($route->params[$pcount]->getName(), 0, 1) == '_') { unset($params[$pcount]); @@ -236,21 +337,12 @@ public static function getHooks($hooks = []) Route::setCurrentRoute($route); }; - // - // Post controller constructor hook - // - $hooks['post_controller_constructor'][] = function() { global $params; - // Loading required URL helper ci()->load->helper('url'); - - // Injecting current route ci()->route = Route::getCurrentRoute(); - - // Injecting middleware class ci()->middleware = new Middleware(); if(method_exists(ci(), 'preMiddleware')) @@ -263,7 +355,7 @@ public static function getHooks($hooks = []) ci()->middleware->run($middleware); } - // Seting "sticky" route params values as default for current route + // Setting "sticky" route parameters values as default for current route foreach(ci()->route->params as &$param) { if(substr($param->getName(),0,1) == '_') @@ -291,9 +383,6 @@ public static function getHooks($hooks = []) } }; - // - // Post controller hook - // $hooks['post_controller'][] = function() { @@ -303,6 +392,7 @@ public static function getHooks($hooks = []) } }; + return $hooks; } } \ No newline at end of file diff --git a/src/Middleware.php b/src/Middleware.php index 8f3b0f7..1696c88 100644 --- a/src/Middleware.php +++ b/src/Middleware.php @@ -16,26 +16,24 @@ class Middleware * * @param string|callable $middleware * @param mixed ...$args + * * @return void * * @access public */ final public function run($middleware, ...$args) { - $CI =& get_instance(); - - if(is_callable($middleware)) + if(is_array($middleware)) { - - if(empty($args)) - { - $args[] =& $CI; - } - else + foreach($middleware as $run) { - $args = array_unshift($args, $CI); + $this->run($run, $args); } + return; + } + if(is_callable($middleware)) + { call_user_func_array($middleware, $args); } else @@ -44,16 +42,17 @@ final public function run($middleware, ...$args) { require_once(APPPATH . '/middleware/' . $middleware . '.php'); - $middlewareInstance = new $middleware(); + $instance = new $middleware(); - if(!method_exists($middlewareInstance,'run')) + if(!method_exists($instance,'run')) { show_error('Your middleware doesn\'t have a run() method'); } - $middlewareInstance->CI = $CI; + // Injecting CodeIgniter instance in the middleware + $instance->CI = ci(); - call_user_func([$middlewareInstance, 'run'], $args); + call_user_func([$instance, 'run'], $args); } else { @@ -75,8 +74,6 @@ final public function run($middleware, ...$args) */ final public function addHook($hook, $middleware, ...$args) { - $CI =& get_instance(); - if(is_callable($middleware)) { if(empty($args)) @@ -84,19 +81,17 @@ final public function addHook($hook, $middleware, ...$args) $args[] =& get_instance(); } - if(isset($CI->hooks->hooks[$hook]) && !is_array($CI->hooks->hooks[$hook])) + if(isset(ci()->hooks->hooks[$hook]) && !is_array(ci()->hooks->hooks[$hook])) { - $_hook = $CI->hooks->hooks[$hook]; - $CI->hooks->hooks[$hook] = [ - $_hook, - ]; + $_hook = ci()->hooks->hooks[$hook]; + ci()->hooks->hooks[$hook] = [ $_hook ]; } - $CI->hooks->hooks[$hook][] = call_user_func_array($middleware, $args); + ci()->hooks->hooks[$hook][] = call_user_func_array($middleware, $args); } else { - $CI->hooks->hooks[$hook][] = call_user_function_array([$this,'run'], [ $middleware, $args] ); + ci()->hooks->hooks[$hook][] = call_user_function_array([$this,'run'], [ $middleware, $args] ); } } } diff --git a/src/Route.php b/src/Route.php index c5c4f54..66be8dc 100644 --- a/src/Route.php +++ b/src/Route.php @@ -13,6 +13,7 @@ class Route { + const DEFAULT_CONTROLLER = 'Luthier'; const HTTP_VERBS = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS','TRACE']; @@ -21,8 +22,6 @@ class Route /** * Luthier routes * - * (This us used internally by Luthier) - * * @var static $routes * * @access private @@ -33,8 +32,6 @@ class Route /** * Luthier routing context * - * (This us used internally by Luthier) - * * @var static $context * * @access private @@ -59,8 +56,6 @@ class Route /** * Compiled routes * - * (This us used internally by Luthier) - * * @var static $compiled * * @access private @@ -76,6 +71,8 @@ class Route /** * Current active route * + * (This us used internally by Luthier-CI) + * * @var static $current * * @access private @@ -86,7 +83,7 @@ class Route /** * Custom 404 route * - * It could be both a path to an controller or a callaback + * It could be both a path to an controller or a callback * * @var static $_404 * @@ -185,7 +182,6 @@ class Route private $hasOptionalParams = false; - /** * Is the current route a 404 page? * @@ -226,13 +222,12 @@ public static function __callStatic($callback, array $args) } - /** - * Creates a route group with path $prefix and shared $attributes + * Creates a route group with * - * @param string $prefix - * @param array|callback $attributes - * @param callback $routes (Optional) + * @param string $prefix Group path prefix + * @param array|callback $attributes Group shared attributes/Routes + * @param callback $routes (Optional) Routes * * @return void * @@ -289,7 +284,7 @@ public static function group($prefix, $attributes, $routes = null) /** * Defines a route middleware in global context * - * @param string|array $middlewares + * @param string|array $middleware * @param string $point (Optional) the point of execution of the middleware * * @return void @@ -297,25 +292,27 @@ public static function group($prefix, $attributes, $routes = null) * @access public * @static */ - public static function middleware($middlewares, $point = 'pre_controller') + public static function middleware($middleware, $point = 'pre_controller') { - if(!is_array($middlewares)) + if(!is_array($middleware)) { - $middlewares = [ $middlewares ]; + $middleware = [ $middleware ]; } - foreach($middlewares as $middleware) + foreach($middleware as $_middleware) { - if(!in_array($middleware, self::$context['middleware']['global'][$point])) + if(!in_array($_middleware, self::$context['middleware']['global'][$point])) { - self::$context['middleware']['global'][$point][] = $middleware; + self::$context['middleware']['global'][$point][] = $_middleware; } } } /** - * Compile all routes to CI format + * Returns an array of all compiled Luthier-CI routes, in the native framework format + * + * (This us used internally by Luthier-CI) * * @return array * @@ -374,7 +371,9 @@ public static function compileAll() /** * Fetch route by path * - * @param string $path + * (This us used internally by Luthier-CI) + * + * @param string $path Current URI path * * @return Route * @@ -410,7 +409,7 @@ public static function getByUrl($url, $requestMethod = null) /** * Fetch route by name * - * @param string $name + * @param string $name Route name * * @return Route * @@ -430,7 +429,9 @@ public static function getByName($name) /** - * Get all compiled routes (CI Format) + * Get all compiled routes (CodeIgniter format) + * + * (This us used internally by Luthier-CI) * * @return array * @@ -446,6 +447,8 @@ public static function getRoutes() /** * Get the current active route * + * (This us used internally by Luthier-CI) + * * @return Route * * @access public @@ -460,6 +463,8 @@ public static function getCurrentRoute() /** * Get global middleware * + * (This us used internally by Luthier-CI) + * * @return array * * @access public @@ -472,7 +477,9 @@ public static function getGlobalMiddleware() /** - * Get the custom 404 route + * Get the current custom 404 route + * + * (This us used internally by Luthier-CI) * * @return string|callback * @@ -494,7 +501,7 @@ public static function get404() /** * Set the current active route * - * (This us used internally by Luthier) + * (This us used internally by Luthier-CI) * * @param Route $route * @@ -510,9 +517,9 @@ public static function setCurrentRoute(Route $route) /** - * Add a compiled (CI Format) route + * Add a compiled (CodeIgniter Format) route at boot time * - * (This is used internally by Luthier) + * (This us used internally by Luthier-CI) * * @param string $path * @param string $target (Optional) @@ -567,12 +574,12 @@ public static function getDefaultParams() /** - * Defines a CI reserved route + * Defines a CodeIgniter reserved or custom Luthier configuration * - * @param [add type] $name - * @param [add type] $value + * @param string $name + * @param mixed $value * - * @return [add type] + * @return void * * @access public * @static @@ -597,8 +604,8 @@ public static function set($name, $value) /** * Class constructor * - * @param string|array $methods - * @param array|callable $route + * @param string|array $methods HTTP Verbs + * @param array|callable $route Route definition * * @return void * @access public @@ -624,9 +631,8 @@ public function __construct($methods, $route) $this->methods[] = strtoupper($method); } - // Essential route attributes + // Required route attributes list($path, $action) = $route; - $this->path = trim($path, '/') == '' ? '/' : trim($path, '/'); if(!is_callable($action) && count(explode('@', $action)) != 2) @@ -638,7 +644,6 @@ public function __construct($methods, $route) $attributes = isset($route[2]) && is_array($route[2]) ? $route[2] : NULL; // Route group-inherited attributes - if(!empty(self::$context['prefix'])) { $prefixes = self::$context['prefix']; @@ -672,7 +677,6 @@ public function __construct($methods, $route) } // Optional route attributes - if($attributes !== NULL) { if(isset($attributes['namespace'])) @@ -697,9 +701,7 @@ public function __construct($methods, $route) } // Parsing route arguments - $_names = []; - $fullPath = trim($this->prefix,'/') != '' ? $this->prefix . '/' . $this->path : $this->path; $fullPath = trim($fullPath, '/'); @@ -732,7 +734,7 @@ public function __construct($methods, $route) } } - // Automatically set the default controller if path is "/" (root) + // Automatically set the default controller if path is "/" if($path == '/' && in_array('GET', $this->methods)) { self::$compiled['reserved']['default_controller'] = is_string($action) @@ -745,11 +747,11 @@ public function __construct($methods, $route) /** - * Compiles the Luthier route to CodeIgniter plain route array (with subroutes) + * Compiles a Luthier-CI route into a CodeIgniter native route * * (This is used internally by Luthier) * - * @param string $currentMethod (Optional) Set the current method during a recursive callback + * @param string $currentMethod (Optional) Current HTTP Verb * * @return array * @@ -775,7 +777,6 @@ public function compile($currentMethod = null ) $methods = [ $currentMethod ]; } - foreach($methods as $verb) { $path = $this->path; @@ -813,7 +814,7 @@ public function compile($currentMethod = null ) if($this->hasOptionalParams && $currentMethod === null) { - $route = clone $this; + $route = clone $this; do { @@ -839,13 +840,13 @@ public function compile($currentMethod = null ) $routes[][$_path][$verb] = $_target; - }while($isOptional); + } while( $isOptional ); } $routes[][$path][$verb] = $target; } - $last = array_pop($routes); + $last = array_pop($routes); array_unshift($routes, $last); $routes = array_reverse($routes); @@ -853,6 +854,16 @@ public function compile($currentMethod = null ) } + /** + * Get or set a route parameter + * + * @param string $name Parameter name + * @param string $value (Optional) Parameter value + * + * @return null|string + * + * @access public + */ public function param($name, $value = null) { foreach($this->params as &$_param) @@ -869,6 +880,15 @@ public function param($name, $value = null) } + /** + * Check if the route has a specific parameter + * + * @param string $name + * + * @return bool + * + * @access public + */ public function hasParam($name) { foreach($this->params as &$_param) @@ -882,6 +902,15 @@ public function hasParam($name) } + /** + * Builds the route URL with the provided parameters (if any) + * + * @param string|array $params Route parameters + * + * @return sting + * + * @access public + */ public function parseUrl($params) { $defaultParams = self::getDefaultParams(); @@ -979,40 +1008,95 @@ public function setName($name) } + /** + * Get route path + * + * @return string + * + * @access public + */ public function getPath() { return $this->path; } + /** + * Set route path + * + * @param string $path + * + * @return void + * + * @access public + */ public function setPath($path) { $this->path = $path; } + /** + * Get route prefix + * + * @return string + * + * @access public + */ public function getPrefix() { return $this->prefix; } + /** + * Get route action + * + * @return string|callback + * + * @access public + */ public function getAction() { return $this->action; } + + /** + * Set route action + * + * @param string|callback $action + * + * @return void + * + * @access public + */ public function setAction($action) { $this->action = $action; } + /** + * Get route middleware + * + * @return array + * + * @access public + */ public function getMiddleware() { return $this->middleware; } + + /** + * Get route namespace + * + * @return string + * + * @access public + */ public function getNamespace() { return $this->namespace; diff --git a/src/RouteParam.php b/src/RouteParam.php index a2c44b4..b6a19d1 100644 --- a/src/RouteParam.php +++ b/src/RouteParam.php @@ -88,7 +88,7 @@ final class RouteParam /** - * CI route placeholder to actual regex + * CodeIgniter route placeholder to regex * * @var static $placeholderReplacements * @@ -100,6 +100,14 @@ final class RouteParam ]; + /** + * Get the CodeIgniter route placeholder to Regex + * + * @return array + * + * @access public + * @static + */ public static function getPlaceholderReplacements() { return self::$placeholderReplacements; @@ -126,8 +134,6 @@ public function __construct($segment, $default = null) { $this->placeholder = '(' . $matches[1] . ')'; $this->regex = $matches[1]; - - $name = preg_replace('/\((.*)\):/', '', $segment, 1); } else @@ -139,7 +145,6 @@ public function __construct($segment, $default = null) if($segment != $parsedSegment ) { $this->placeholder = $replacement; - $this->regex = preg_replace(array_keys(self::$placeholderReplacements), array_values(self::$placeholderReplacements), $replacement,1); $name = preg_replace(['/num:/', '/any:/'], '', $segment, 1); break; @@ -151,31 +156,66 @@ public function __construct($segment, $default = null) $this->name = substr($name,1, !$this->optional ? -1 : -2); } - + + /** + * Get parameter name + * + * @return string + * + * @access public + */ public function getName() { return $this->name; } + /** + * Get original segment + * + * @return string + * + * @access public + */ public function getSegment() { return $this->segment; } - + + /** + * Get segment regex + * + * @return string + * + * @access public + */ public function getRegex() { return $this->regex; } + /** + * Get segment placeholder + * + * @return string + * + * @access public + */ public function getPlaceholder() { return $this->placeholder; } + /** + * Is the segment optional? + * + * @return bool + * + * @access public + */ public function isOptional() { return $this->optional;