From 2128a24f1ca1a71928390f9ba5f8caed2260abad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sessing=C3=B8?= Date: Thu, 10 Dec 2015 01:34:35 +0100 Subject: [PATCH 1/5] [FEATURE] Added support for domain regex matching. - Also improved regex matching when using custom regular expressions. --- src/Pecee/SimpleRouter/RouterRoute.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Pecee/SimpleRouter/RouterRoute.php b/src/Pecee/SimpleRouter/RouterRoute.php index 65e75c9c..9c17f791 100644 --- a/src/Pecee/SimpleRouter/RouterRoute.php +++ b/src/Pecee/SimpleRouter/RouterRoute.php @@ -26,14 +26,14 @@ public function matchRoute(Request $request) { // Match on custom defined regular expression if($this->regexMatch) { $parameters = array(); - if(preg_match('/('.$this->regexMatch.')/is', $url, $parameters)) { + if(preg_match('/('.$this->regexMatch.')/is', request()->getHost() . $url, $parameters)) { $this->parameters = (!is_array($parameters[0]) ? array($parameters[0]) : $parameters[0]); return $this; } + return null; } // Make regular expression based on route - $route = rtrim($this->url, '/') . '/'; $parameterNames = array(); From c74d83796f4d631e33275654b2e057e385cc388b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sessing=C3=B8?= Date: Thu, 10 Dec 2015 03:31:57 +0100 Subject: [PATCH 2/5] [FEATURE] Added sub-domain routing. - Updated documentation. --- README.md | 22 ++++++- src/Pecee/SimpleRouter/RouterBase.php | 5 +- src/Pecee/SimpleRouter/RouterEntry.php | 82 +++++++++++++++++++++++++- src/Pecee/SimpleRouter/RouterGroup.php | 13 ++++ src/Pecee/SimpleRouter/RouterRoute.php | 74 ++--------------------- 5 files changed, 119 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 79062dfa..9471de25 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Add the latest version pf Simple PHP Router to your ```composer.json``` ## Notes +The goal of this project is to create a router that is 100% compatible with the Laravel documentation, but as simple as possible and as easy to integrate and change as possible. + ### Features - Basic routing (get, post, put, delete) with support for custom multiple verbs. @@ -29,11 +31,11 @@ Add the latest version pf Simple PHP Router to your ```composer.json``` - Route prefixes. - CSRF protection. - Optional parameters +- Sub-domain routing ### Features currently "in-the-works" - Global Constraints -- Sub-domain routing ## Initialising the router @@ -81,9 +83,9 @@ SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\So /** * This example will route url when matching the regular expression to the method. - * For example route: /ajax/music/world -> ControllerAjax@process (parameter: music/world) + * For example route: domain.com/ajax/music/world -> ControllerAjax@process (parameter: music/world) */ - SimpleRouter::all('/ajax', 'ControllerAjax@process')->match('ajax\\/([A-Za-z0-9\\/]+)'); + SimpleRouter::all('/ajax', 'ControllerAjax@process')->match('.*?\\/ajax\\/([A-Za-z0-9\\/]+)'); // Restful resource SimpleRouter::resource('/rest', 'ControllerRessource'); @@ -100,6 +102,20 @@ SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\So }); ``` +### Sub-domain routing + +Route groups may also be used to route wildcard sub-domains. Sub-domains may be assigned route parameters just like route URIs, allowing you to capture a portion of the sub-domain for usage in your route or controller. The sub-domain may be specified using the ```domain``` key on the group attribute array: + +```php +Route::group(['domain' => '{account}.myapp.com'], function () { + Route::get('user/{id}', function ($account, $id) { + // + }); +}); +``` + +The prefix group array attribute may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with admin: + ### Doing it the object oriented (hardcore) way The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```RouterBase``` class. diff --git a/src/Pecee/SimpleRouter/RouterBase.php b/src/Pecee/SimpleRouter/RouterBase.php index 12c967eb..be513d3a 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -60,12 +60,12 @@ protected function processRoutes(array $routes, array $settings = array(), array } $newPrefixes = $prefixes; - $mergedSettings = array_merge($settings, $route->getMergeableSettings()); if($route->getPrefix()) { array_push($newPrefixes, rtrim($route->getPrefix(), '/')); } - $route->addSettings($mergedSettings); + + $route->addSettings($settings); if(!($route instanceof RouterGroup)) { if(is_array($newPrefixes) && count($newPrefixes) && $backstack) { @@ -79,6 +79,7 @@ protected function processRoutes(array $routes, array $settings = array(), array if($route instanceof RouterGroup && is_callable($route->getCallback())) { $route->renderRoute($this->request); $activeGroup = $route; + $mergedSettings = array_merge($settings, $route->getMergeableSettings()); } $this->currentRoute = null; diff --git a/src/Pecee/SimpleRouter/RouterEntry.php b/src/Pecee/SimpleRouter/RouterEntry.php index 7adc53e7..88af19b4 100644 --- a/src/Pecee/SimpleRouter/RouterEntry.php +++ b/src/Pecee/SimpleRouter/RouterEntry.php @@ -23,13 +23,12 @@ abstract class RouterEntry { protected $settings; protected $callback; - protected $parameters; public function __construct() { $this->settings = array(); $this->settings['requestMethods'] = array(); $this->settings['parametersRegex'] = array(); - $this->parameters = array(); + $this->settings['parameters'] = array(); } /** @@ -216,7 +215,7 @@ public function setSettings($settings) { } /** - * Dynamicially access settings value + * Dynamically access settings value * * @param $name * @return mixed|null @@ -243,6 +242,83 @@ protected function loadClass($name) { return new $name(); } + protected function parseParameters($route, $url, $parameterRegex = '[a-z0-9]*?') { + $parameterNames = array(); + $regex = ''; + $lastCharacter = ''; + $isParameter = false; + $parameter = ''; + + // Use custom parameter regex if it exists + if(is_array($this->parametersRegex) && isset($this->parametersRegex[$parameter])) { + $parameterRegex = $this->parametersRegex[$parameter]; + } + + for($i = 0; $i < strlen($route); $i++) { + + $character = $route[$i]; + + // Skip "/" if we are at the end of a parameter + if($lastCharacter === '}' && $character === '/') { + $lastCharacter = $character; + continue; + } + + if($character === '{') { + // Remove "/" and "\" from regex + if(substr($regex, strlen($regex)-1) === '/') { + $regex = substr($regex, 0, strlen($regex) - 2); + } + + $isParameter = true; + } elseif($isParameter && $character === '}') { + $required = true; + // Check for optional parameter + if($lastCharacter === '?') { + $parameter = substr($parameter, 0, strlen($parameter)-1); + $regex .= '(?:(?:\/{0,1}(?P<'.$parameter.'>'.$parameterRegex.')){0,1}\\/{0,1})'; + $required = false; + } else { + $regex .= '(?:\\/{0,1}(?P<' . $parameter . '>'. $parameterRegex .')\\/{0,1})'; + } + $parameterNames[] = array('name' => $parameter, 'required' => $required); + $parameter = ''; + $isParameter = false; + + } elseif($isParameter) { + $parameter .= $character; + } elseif($character === '/') { + $regex .= '\\' . $character; + } else { + $regex .= str_replace('.', '\\.', $character); + } + + $lastCharacter = $character; + } + + $parameterValues = array(); + + if(preg_match('/^'.$regex.'$/is', $url, $parameterValues)) { + $parameters = array(); + + if(count($parameterNames)) { + foreach($parameterNames as $name) { + $parameterValue = (isset($parameterValues[$name['name']]) && !empty($parameterValues[$name['name']])) ? $parameterValues[$name['name']] : null; + + if($name['required'] && $parameterValue === null) { + throw new RouterException('Missing required parameter ' . $name['name'], 404); + } + + $parameters[$name['name']] = $parameterValue; + } + } + + return $parameters; + } + + return null; + } + public function loadMiddleware(Request $request) { if($this->getMiddleware()) { if(is_array($this->getMiddleware())) { diff --git a/src/Pecee/SimpleRouter/RouterGroup.php b/src/Pecee/SimpleRouter/RouterGroup.php index 61e4d41f..05567087 100644 --- a/src/Pecee/SimpleRouter/RouterGroup.php +++ b/src/Pecee/SimpleRouter/RouterGroup.php @@ -10,6 +10,17 @@ public function __construct() { parent::__construct(); } + protected function matchDomain() { + if($this->domain !== null) { + + $parameters = $this->parseParameters($this->domain, request()->getHost(), '[^.]*'); + + if($parameters !== null) { + $this->parameters = $parameters; + } + } + } + public function renderRoute(Request $request) { // Check if request method is allowed $hasAccess = (!$this->method); @@ -26,6 +37,8 @@ public function renderRoute(Request $request) { throw new RouterException('Method not allowed'); } + $this->matchDomain(); + return parent::renderRoute($request); } diff --git a/src/Pecee/SimpleRouter/RouterRoute.php b/src/Pecee/SimpleRouter/RouterRoute.php index 9c17f791..fb8f1433 100644 --- a/src/Pecee/SimpleRouter/RouterRoute.php +++ b/src/Pecee/SimpleRouter/RouterRoute.php @@ -36,80 +36,16 @@ public function matchRoute(Request $request) { // Make regular expression based on route $route = rtrim($this->url, '/') . '/'; - $parameterNames = array(); - $regex = ''; - $lastCharacter = ''; - $isParameter = false; - $parameter = ''; + $parameters = $this->parseParameters($route, $url); - for($i = 0; $i < strlen($route); $i++) { + if($parameters !== null) { - $character = $route[$i]; - - // Skip "/" if we are at the end of a parameter - if($lastCharacter === '}' && $character === '/') { - $lastCharacter = $character; - continue; - } - - if($character === '{') { - // Remove "/" and "\" from regex - if(substr($regex, strlen($regex)-1) === '/') { - $regex = substr($regex, 0, strlen($regex) - 2); - } - - $isParameter = true; - } elseif($isParameter && $character === '}') { - $required = true; - // Check for optional parameter - if($lastCharacter === '?') { - $parameter = substr($parameter, 0, strlen($parameter)-1); - $regex .= '(?:(?:\/{0,1}(?P<'.$parameter.'>[a-z0-9]*?)){0,1}\\/)'; - $required = false; - } else { - // Use custom parameter regex if it exists - $parameterRegex = '[a-z0-9]*?'; - - if(is_array($this->parametersRegex) && isset($this->parametersRegex[$parameter])) { - $parameterRegex = $this->parametersRegex[$parameter]; - } - - $regex .= '(?:\\/{0,1}(?P<' . $parameter . '>'. $parameterRegex .')\\/)'; - } - $parameterNames[] = array('name' => $parameter, 'required' => $required); - $parameter = ''; - $isParameter = false; - - } elseif($isParameter) { - $parameter .= $character; - } elseif($character === '/') { - $regex .= '\\' . $character; + if(is_array($this->parameters)) { + $this->parameters = array_merge($this->parameters, $parameters); } else { - $regex .= $character; - } - - $lastCharacter = $character; - } - - $parameterValues = array(); - - if(preg_match('/^'.$regex.'$/is', $url, $parameterValues)) { - - $parameters = array(); - - if(count($parameterNames)) { - foreach($parameterNames as $name) { - $parameterValue = (isset($parameterValues[$name['name']]) && !empty($parameterValues[$name['name']])) ? $parameterValues[$name['name']] : null; - - if($name['required'] && $parameterValue === null) { - throw new RouterException('Missing required parameter ' . $name['name'], 404); - } - - $parameters[$name['name']] = $parameterValue; - } + $this->parameters = $parameters; } - $this->parameters = $parameters; return $this; } From d36880e9a03ade6f00d8dd90c933c4068420b425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sessing=C3=B8?= Date: Thu, 10 Dec 2015 03:36:50 +0100 Subject: [PATCH 3/5] [OPTIMISATION] Optimised routes loop. --- src/Pecee/SimpleRouter/RouterBase.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Pecee/SimpleRouter/RouterBase.php b/src/Pecee/SimpleRouter/RouterBase.php index be513d3a..0e94242b 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -38,13 +38,17 @@ public function addRoute(RouterEntry $route) { } } - protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), $backstack = false, $group = null) { + protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), $backStack = false, $group = null) { // Loop through each route-request $activeGroup = null; + $routesCount = count($routes); + /* @var $route RouterEntry */ - foreach($routes as $route) { + for($i = 0; $i < $routesCount; $i++) { + + $route = $routes[$i]; $route->setGroup($group); @@ -68,7 +72,7 @@ protected function processRoutes(array $routes, array $settings = array(), array $route->addSettings($settings); if(!($route instanceof RouterGroup)) { - if(is_array($newPrefixes) && count($newPrefixes) && $backstack) { + if(is_array($newPrefixes) && count($newPrefixes) && $backStack) { $route->setUrl( join('/', $newPrefixes) . $route->getUrl() ); } @@ -84,11 +88,11 @@ protected function processRoutes(array $routes, array $settings = array(), array $this->currentRoute = null; if(count($this->backstack)) { - $backstack = $this->backstack; + $backStack = $this->backstack; $this->backstack = array(); // Route any routes added to the backstack - $this->processRoutes($backstack, $mergedSettings, $newPrefixes, true, $activeGroup); + $this->processRoutes($backStack, $mergedSettings, $newPrefixes, true, $activeGroup); } } } From 3ba2cec8af479a3cddd8c5f77c68167c26fa2244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sessing=C3=B8?= Date: Thu, 10 Dec 2015 03:41:10 +0100 Subject: [PATCH 4/5] [OPTIMISATION] Optimised more foreach loops to improve performance. --- src/Pecee/SimpleRouter/RouterBase.php | 18 +++++++++++++++--- src/Pecee/SimpleRouter/RouterEntry.php | 7 +++++-- src/Pecee/SimpleRouter/RouterRoute.php | 6 ++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Pecee/SimpleRouter/RouterBase.php b/src/Pecee/SimpleRouter/RouterBase.php index 0e94242b..7d0ea012 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -112,8 +112,13 @@ public function routeRequest() { $routeNotAllowed = false; + $max = count($this->controllerUrlMap); + /* @var $route RouterEntry */ - foreach($this->controllerUrlMap as $route) { + for($i = 0; $i < $max; $i++) { + + $route = $this->controllerUrlMap[$i]; + $routeMatch = $route->matchRoute($this->request); if($routeMatch && !($routeMatch instanceof RouterGroup)) { @@ -270,8 +275,12 @@ public function getRoute($controller = null, $parameters = null, $getParams = nu $c = ''; $method = null; + $max = count($this->controllerUrlMap); + /* @var $route RouterRoute */ - foreach($this->controllerUrlMap as $route) { + for($i = 0; $i < $max; $i++) { + + $route = $this->controllerUrlMap[$i]; // Check an alias exist, if the matches - use it if($route instanceof RouterRoute && strtolower($route->getAlias()) === strtolower($controller)) { @@ -292,7 +301,10 @@ public function getRoute($controller = null, $parameters = null, $getParams = nu $c = ''; // No match has yet been found, let's try to guess what url that should be returned - foreach($this->controllerUrlMap as $route) { + for($i = 0; $i < $max; $i++) { + + $route = $this->controllerUrlMap[$i]; + if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) { $c = $route->getClass(); } else if($route instanceof RouterController || $route instanceof RouterResource) { diff --git a/src/Pecee/SimpleRouter/RouterEntry.php b/src/Pecee/SimpleRouter/RouterEntry.php index 88af19b4..ee7641ba 100644 --- a/src/Pecee/SimpleRouter/RouterEntry.php +++ b/src/Pecee/SimpleRouter/RouterEntry.php @@ -301,8 +301,11 @@ protected function parseParameters($route, $url, $parameterRegex = '[a-z0-9]*?') if(preg_match('/^'.$regex.'$/is', $url, $parameterValues)) { $parameters = array(); - if(count($parameterNames)) { - foreach($parameterNames as $name) { + $max = count($parameterNames); + + if(count($max)) { + for($i = 0; $i < $max; $i++) { + $name = $parameterNames[$i]; $parameterValue = (isset($parameterValues[$name['name']]) && !empty($parameterValues[$name['name']])) ? $parameterValues[$name['name']] : null; if($name['required'] && $parameterValue === null) { diff --git a/src/Pecee/SimpleRouter/RouterRoute.php b/src/Pecee/SimpleRouter/RouterRoute.php index fb8f1433..83d5359a 100644 --- a/src/Pecee/SimpleRouter/RouterRoute.php +++ b/src/Pecee/SimpleRouter/RouterRoute.php @@ -72,8 +72,10 @@ public function setUrl($url) { $parameters = $matches[1]; } - if(count($parameters)) { - foreach($parameters as $param) { + $max = count($parameters); + if($max) { + for($i = 0; $i < $max; $i++) { + $param = $parameters[$i]; $this->parameters[$param] = ''; } } From 59956d5fca02039df4649acb70901af74aa92935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sessing=C3=B8?= Date: Thu, 10 Dec 2015 04:12:44 +0100 Subject: [PATCH 5/5] [BUGFIX] Optimisations + bugfixes. --- src/Pecee/SimpleRouter/RouterBase.php | 4 +++- src/Pecee/SimpleRouter/RouterEntry.php | 5 +++-- src/Pecee/SimpleRouter/RouterRoute.php | 11 +++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Pecee/SimpleRouter/RouterBase.php b/src/Pecee/SimpleRouter/RouterBase.php index 7d0ea012..b3cf31dc 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -80,11 +80,13 @@ protected function processRoutes(array $routes, array $settings = array(), array } $this->currentRoute = $route; + if($route instanceof RouterGroup && is_callable($route->getCallback())) { $route->renderRoute($this->request); $activeGroup = $route; - $mergedSettings = array_merge($settings, $route->getMergeableSettings()); + $mergedSettings = array_merge($route->getMergeableSettings(), $settings); } + $this->currentRoute = null; if(count($this->backstack)) { diff --git a/src/Pecee/SimpleRouter/RouterEntry.php b/src/Pecee/SimpleRouter/RouterEntry.php index ee7641ba..934c2708 100644 --- a/src/Pecee/SimpleRouter/RouterEntry.php +++ b/src/Pecee/SimpleRouter/RouterEntry.php @@ -136,7 +136,7 @@ public function getSettings() { * @return mixed */ public function getParameters(){ - return $this->parameters; + return ($this->parameters === null) ? array() : $this->parameters; } /** @@ -254,7 +254,8 @@ protected function parseParameters($route, $url, $parameterRegex = '[a-z0-9]*?') $parameterRegex = $this->parametersRegex[$parameter]; } - for($i = 0; $i < strlen($route); $i++) { + $routeLength = strlen($route); + for($i = 0; $i < $routeLength; $i++) { $character = $route[$i]; diff --git a/src/Pecee/SimpleRouter/RouterRoute.php b/src/Pecee/SimpleRouter/RouterRoute.php index 83d5359a..5e6e34b1 100644 --- a/src/Pecee/SimpleRouter/RouterRoute.php +++ b/src/Pecee/SimpleRouter/RouterRoute.php @@ -64,7 +64,6 @@ public function getUrl() { * @return self */ public function setUrl($url) { - $parameters = array(); $matches = array(); @@ -72,12 +71,12 @@ public function setUrl($url) { $parameters = $matches[1]; } - $max = count($parameters); - if($max) { - for($i = 0; $i < $max; $i++) { - $param = $parameters[$i]; - $this->parameters[$param] = ''; + if(count($parameters)) { + $tmp = array(); + foreach($parameters as $param) { + $tmp[$param] = ''; } + $this->parameters = $tmp; } $this->url = $url;