Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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');
Expand All @@ -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.
Expand Down
39 changes: 29 additions & 10 deletions src/Pecee/SimpleRouter/RouterBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -60,34 +64,37 @@ 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) {
if(is_array($newPrefixes) && count($newPrefixes) && $backStack) {
$route->setUrl( join('/', $newPrefixes) . $route->getUrl() );
}

$this->controllerUrlMap[] = $route;
}

$this->currentRoute = $route;

if($route instanceof RouterGroup && is_callable($route->getCallback())) {
$route->renderRoute($this->request);
$activeGroup = $route;
$mergedSettings = array_merge($route->getMergeableSettings(), $settings);
}

$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);
}
}
}
Expand All @@ -107,8 +114,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)) {
Expand Down Expand Up @@ -265,8 +277,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)) {
Expand All @@ -287,7 +303,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) {
Expand Down
88 changes: 84 additions & 4 deletions src/Pecee/SimpleRouter/RouterEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -137,7 +136,7 @@ public function getSettings() {
* @return mixed
*/
public function getParameters(){
return $this->parameters;
return ($this->parameters === null) ? array() : $this->parameters;
}

/**
Expand Down Expand Up @@ -216,7 +215,7 @@ public function setSettings($settings) {
}

/**
* Dynamicially access settings value
* Dynamically access settings value
*
* @param $name
* @return mixed|null
Expand All @@ -243,6 +242,87 @@ 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];
}

$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) {
if($this->getMiddleware()) {
if(is_array($this->getMiddleware())) {
Expand Down
13 changes: 13 additions & 0 deletions src/Pecee/SimpleRouter/RouterGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -26,6 +37,8 @@ public function renderRoute(Request $request) {
throw new RouterException('Method not allowed');
}

$this->matchDomain();

return parent::renderRoute($request);
}

Expand Down
Loading