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] [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 79062df..9471de2 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 12c967e..be513d3 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -60,12 +60,12 @@ class RouterBase { } $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 @@ class RouterBase { 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 7adc53e..88af19b 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 @@ abstract class RouterEntry { } /** - * Dynamicially access settings value + * Dynamically access settings value * * @param $name * @return mixed|null @@ -243,6 +242,83 @@ abstract class RouterEntry { 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 61e4d41..0556708 100644 --- a/src/Pecee/SimpleRouter/RouterGroup.php +++ b/src/Pecee/SimpleRouter/RouterGroup.php @@ -10,6 +10,17 @@ class RouterGroup extends RouterEntry { 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 @@ class RouterGroup extends RouterEntry { 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 9c17f79..fb8f143 100644 --- a/src/Pecee/SimpleRouter/RouterRoute.php +++ b/src/Pecee/SimpleRouter/RouterRoute.php @@ -36,80 +36,16 @@ class RouterRoute extends RouterEntry { // 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; + $this->parameters = $parameters; } - $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; return $this; }