mirror of
https://github.com/skipperbent/simple-php-router.git
synced 2026-07-11 12:22:13 +00:00
Merge pull request #43 from skipperbent/development
[FEATURE] Subdomain-routing + Improvements
This commit is contained in:
@@ -17,6 +17,8 @@ Add the latest version pf Simple PHP Router to your ```composer.json```
|
|||||||
|
|
||||||
## Notes
|
## 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
|
### Features
|
||||||
|
|
||||||
- Basic routing (get, post, put, delete) with support for custom multiple verbs.
|
- 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.
|
- Route prefixes.
|
||||||
- CSRF protection.
|
- CSRF protection.
|
||||||
- Optional parameters
|
- Optional parameters
|
||||||
|
- Sub-domain routing
|
||||||
|
|
||||||
### Features currently "in-the-works"
|
### Features currently "in-the-works"
|
||||||
|
|
||||||
- Global Constraints
|
- Global Constraints
|
||||||
- Sub-domain routing
|
|
||||||
|
|
||||||
## Initialising the router
|
## 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.
|
* 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
|
// Restful resource
|
||||||
SimpleRouter::resource('/rest', 'ControllerRessource');
|
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
|
### 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.
|
The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```RouterBase``` class.
|
||||||
|
|||||||
@@ -38,13 +38,17 @@ class RouterBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Loop through each route-request
|
||||||
|
|
||||||
$activeGroup = null;
|
$activeGroup = null;
|
||||||
|
|
||||||
|
$routesCount = count($routes);
|
||||||
|
|
||||||
/* @var $route RouterEntry */
|
/* @var $route RouterEntry */
|
||||||
foreach($routes as $route) {
|
for($i = 0; $i < $routesCount; $i++) {
|
||||||
|
|
||||||
|
$route = $routes[$i];
|
||||||
|
|
||||||
$route->setGroup($group);
|
$route->setGroup($group);
|
||||||
|
|
||||||
@@ -60,15 +64,15 @@ class RouterBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$newPrefixes = $prefixes;
|
$newPrefixes = $prefixes;
|
||||||
$mergedSettings = array_merge($settings, $route->getMergeableSettings());
|
|
||||||
|
|
||||||
if($route->getPrefix()) {
|
if($route->getPrefix()) {
|
||||||
array_push($newPrefixes, rtrim($route->getPrefix(), '/'));
|
array_push($newPrefixes, rtrim($route->getPrefix(), '/'));
|
||||||
}
|
}
|
||||||
$route->addSettings($mergedSettings);
|
|
||||||
|
$route->addSettings($settings);
|
||||||
|
|
||||||
if(!($route instanceof RouterGroup)) {
|
if(!($route instanceof RouterGroup)) {
|
||||||
if(is_array($newPrefixes) && count($newPrefixes) && $backstack) {
|
if(is_array($newPrefixes) && count($newPrefixes) && $backStack) {
|
||||||
$route->setUrl( join('/', $newPrefixes) . $route->getUrl() );
|
$route->setUrl( join('/', $newPrefixes) . $route->getUrl() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,18 +80,21 @@ class RouterBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->currentRoute = $route;
|
$this->currentRoute = $route;
|
||||||
|
|
||||||
if($route instanceof RouterGroup && is_callable($route->getCallback())) {
|
if($route instanceof RouterGroup && is_callable($route->getCallback())) {
|
||||||
$route->renderRoute($this->request);
|
$route->renderRoute($this->request);
|
||||||
$activeGroup = $route;
|
$activeGroup = $route;
|
||||||
|
$mergedSettings = array_merge($route->getMergeableSettings(), $settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->currentRoute = null;
|
$this->currentRoute = null;
|
||||||
|
|
||||||
if(count($this->backstack)) {
|
if(count($this->backstack)) {
|
||||||
$backstack = $this->backstack;
|
$backStack = $this->backstack;
|
||||||
$this->backstack = array();
|
$this->backstack = array();
|
||||||
|
|
||||||
// Route any routes added to the backstack
|
// Route any routes added to the backstack
|
||||||
$this->processRoutes($backstack, $mergedSettings, $newPrefixes, true, $activeGroup);
|
$this->processRoutes($backStack, $mergedSettings, $newPrefixes, true, $activeGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,8 +114,13 @@ class RouterBase {
|
|||||||
|
|
||||||
$routeNotAllowed = false;
|
$routeNotAllowed = false;
|
||||||
|
|
||||||
|
$max = count($this->controllerUrlMap);
|
||||||
|
|
||||||
/* @var $route RouterEntry */
|
/* @var $route RouterEntry */
|
||||||
foreach($this->controllerUrlMap as $route) {
|
for($i = 0; $i < $max; $i++) {
|
||||||
|
|
||||||
|
$route = $this->controllerUrlMap[$i];
|
||||||
|
|
||||||
$routeMatch = $route->matchRoute($this->request);
|
$routeMatch = $route->matchRoute($this->request);
|
||||||
|
|
||||||
if($routeMatch && !($routeMatch instanceof RouterGroup)) {
|
if($routeMatch && !($routeMatch instanceof RouterGroup)) {
|
||||||
@@ -265,8 +277,12 @@ class RouterBase {
|
|||||||
$c = '';
|
$c = '';
|
||||||
$method = null;
|
$method = null;
|
||||||
|
|
||||||
|
$max = count($this->controllerUrlMap);
|
||||||
|
|
||||||
/* @var $route RouterRoute */
|
/* @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
|
// Check an alias exist, if the matches - use it
|
||||||
if($route instanceof RouterRoute && strtolower($route->getAlias()) === strtolower($controller)) {
|
if($route instanceof RouterRoute && strtolower($route->getAlias()) === strtolower($controller)) {
|
||||||
@@ -287,7 +303,10 @@ class RouterBase {
|
|||||||
$c = '';
|
$c = '';
|
||||||
|
|
||||||
// No match has yet been found, let's try to guess what url that should be returned
|
// 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) {
|
if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) {
|
||||||
$c = $route->getClass();
|
$c = $route->getClass();
|
||||||
} else if($route instanceof RouterController || $route instanceof RouterResource) {
|
} else if($route instanceof RouterController || $route instanceof RouterResource) {
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ abstract class RouterEntry {
|
|||||||
|
|
||||||
protected $settings;
|
protected $settings;
|
||||||
protected $callback;
|
protected $callback;
|
||||||
protected $parameters;
|
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->settings = array();
|
$this->settings = array();
|
||||||
$this->settings['requestMethods'] = array();
|
$this->settings['requestMethods'] = array();
|
||||||
$this->settings['parametersRegex'] = array();
|
$this->settings['parametersRegex'] = array();
|
||||||
$this->parameters = array();
|
$this->settings['parameters'] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,7 +136,7 @@ abstract class RouterEntry {
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getParameters(){
|
public function getParameters(){
|
||||||
return $this->parameters;
|
return ($this->parameters === null) ? array() : $this->parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,7 +215,7 @@ abstract class RouterEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamicially access settings value
|
* Dynamically access settings value
|
||||||
*
|
*
|
||||||
* @param $name
|
* @param $name
|
||||||
* @return mixed|null
|
* @return mixed|null
|
||||||
@@ -243,6 +242,87 @@ abstract class RouterEntry {
|
|||||||
return new $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];
|
||||||
|
}
|
||||||
|
|
||||||
|
$routeLength = strlen($route);
|
||||||
|
for($i = 0; $i < $routeLength; $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();
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
throw new RouterException('Missing required parameter ' . $name['name'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters[$name['name']] = $parameterValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function loadMiddleware(Request $request) {
|
public function loadMiddleware(Request $request) {
|
||||||
if($this->getMiddleware()) {
|
if($this->getMiddleware()) {
|
||||||
if(is_array($this->getMiddleware())) {
|
if(is_array($this->getMiddleware())) {
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ class RouterGroup extends RouterEntry {
|
|||||||
parent::__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) {
|
public function renderRoute(Request $request) {
|
||||||
// Check if request method is allowed
|
// Check if request method is allowed
|
||||||
$hasAccess = (!$this->method);
|
$hasAccess = (!$this->method);
|
||||||
@@ -26,6 +37,8 @@ class RouterGroup extends RouterEntry {
|
|||||||
throw new RouterException('Method not allowed');
|
throw new RouterException('Method not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->matchDomain();
|
||||||
|
|
||||||
return parent::renderRoute($request);
|
return parent::renderRoute($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,90 +26,26 @@ class RouterRoute extends RouterEntry {
|
|||||||
// Match on custom defined regular expression
|
// Match on custom defined regular expression
|
||||||
if($this->regexMatch) {
|
if($this->regexMatch) {
|
||||||
$parameters = array();
|
$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]);
|
$this->parameters = (!is_array($parameters[0]) ? array($parameters[0]) : $parameters[0]);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make regular expression based on route
|
// Make regular expression based on route
|
||||||
|
|
||||||
$route = rtrim($this->url, '/') . '/';
|
$route = rtrim($this->url, '/') . '/';
|
||||||
|
|
||||||
$parameterNames = array();
|
$parameters = $this->parseParameters($route, $url);
|
||||||
$regex = '';
|
|
||||||
$lastCharacter = '';
|
|
||||||
$isParameter = false;
|
|
||||||
$parameter = '';
|
|
||||||
|
|
||||||
for($i = 0; $i < strlen($route); $i++) {
|
if($parameters !== null) {
|
||||||
|
|
||||||
$character = $route[$i];
|
if(is_array($this->parameters)) {
|
||||||
|
$this->parameters = array_merge($this->parameters, $parameters);
|
||||||
// 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;
|
|
||||||
} else {
|
} 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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +64,6 @@ class RouterRoute extends RouterEntry {
|
|||||||
* @return self
|
* @return self
|
||||||
*/
|
*/
|
||||||
public function setUrl($url) {
|
public function setUrl($url) {
|
||||||
|
|
||||||
$parameters = array();
|
$parameters = array();
|
||||||
$matches = array();
|
$matches = array();
|
||||||
|
|
||||||
@@ -137,9 +72,11 @@ class RouterRoute extends RouterEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(count($parameters)) {
|
if(count($parameters)) {
|
||||||
|
$tmp = array();
|
||||||
foreach($parameters as $param) {
|
foreach($parameters as $param) {
|
||||||
$this->parameters[$param] = '';
|
$tmp[$param] = '';
|
||||||
}
|
}
|
||||||
|
$this->parameters = $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->url = $url;
|
$this->url = $url;
|
||||||
|
|||||||
Reference in New Issue
Block a user