Merge pull request #1 from skipperbent/master

Latest version
This commit is contained in:
Simon Sessingø
2015-10-17 21:53:54 +02:00
15 changed files with 1020 additions and 353 deletions
+2
View File
@@ -0,0 +1,2 @@
.idea
composer.lock
+111 -13
View File
@@ -1,6 +1,8 @@
# Simple PHP router # Simple PHP router
Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router. Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router.
**Please note: this project has just been added and is still a work in progress. Please use with caution until a stable release version is available :)**
## Installation ## Installation
Add the latest version pf Simple PHP Router to your ```composer.json``` Add the latest version pf Simple PHP Router to your ```composer.json```
@@ -15,6 +17,16 @@ Add the latest version pf Simple PHP Router to your ```composer.json```
} }
``` ```
## Notes
### Features currently "in-the-works"
- Global Constraints
- Named Routes
- Sub-Domain Routing
- CSRF Protection
- Optinal/required parameters
## Initialising the router ## Initialising the router
In your ```index.php``` require your ```routes.php``` and call the ```routeRequest()``` method when all your custom routes has been loaded. This will trigger and do the actual routing of the requests. In your ```index.php``` require your ```routes.php``` and call the ```routeRequest()``` method when all your custom routes has been loaded. This will trigger and do the actual routing of the requests.
@@ -22,24 +34,25 @@ In your ```index.php``` require your ```routes.php``` and call the ```routeReque
This is an example of a basic ```index.php``` file: This is an example of a basic ```index.php``` file:
```php ```php
use \Pecee\SimpleRouter;
require_once 'routes.php'; // change this to whatever makes sense in your project require_once 'routes.php'; // change this to whatever makes sense in your project
// Initialise the router // The apps default namespace (so we don't have to specify it each time we use MyController@home)
$router = \Pecee\SimpleRouter::GetInstance(); $defaultControllerNamespace = 'MyWebsite\\Controller';
// Do the actual routing // Do the routing
$router->routeRequest() SimpleRouter::init($defaultControllerNamespace);
``` ```
## Adding routes ## Adding routes
Remember the ```routes.php``` file you required in your ```index.php```? This file will contain all your custom rules for routing. Remember the ```routes.php``` file you required in your ```index.php```? This file will contain all your custom rules for routing.
This router is heavily inspired by the Laravel 5.* router, so anything you find in the Laravel documentation should work here as well. This router is heavily inspired by the Laravel 5.* router, so anything you find in the Laravel documentation should work here as well.
### Basic example ### Basic example
```php ```php
using \Pecee\Router; use Pecee\SimpleRouter\SimpleRouter;
/* /*
* This route will match the url /v1/services/answers/1/ * This route will match the url /v1/services/answers/1/
@@ -49,11 +62,23 @@ using \Pecee\Router;
* the request, for instance if a user is not authenticated. * the request, for instance if a user is not authenticated.
*/ */
Router::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\SomeMiddlewareClass'], function() { SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\SomeMiddlewareClass'], function() {
Router::group(['prefix' => 'services'], function() { SimpleRouter::group(['prefix' => 'services'], function() {
Router::get('/answers/{id}', 'ControllerAnswers@show'); SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show')
->where(['id' => '[0-9]+');
// Resetful ressource
SimpleRouter::ressource('/rest', 'ControllerRessource');
// Load the entire controller (where url matches method names - getIndex(), postIndex() etc)
SimpleRouter::controller('/controller', 'ControllerDefault');
// Example of providing callback instead of Controller
SimpleRouter::get('/something', function() {
die('Callback example');
});
}); });
}); });
@@ -61,13 +86,14 @@ Router::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\SomeMidd
### Doing it the object oriented (hardcore) way ### Doing it the object oriented (hardcore) way
The ```Router``` class is just a simple helper class that knows how to communicate with the ```SimpleRouter``` class. If you are up for a challenge, want the full control or simply just want to create your own ```Router``` helper class, this example is for you. The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```RouterBase``` class.
If you are up for a challenge, want the full control or simply just want to create your own ```Router``` helper class, this example is for you.
```php ```php
use \Pecee\SimpleRouter; use \Pecee\SimpleRouter\RouterBase;
use \Pecee\Router\RouterRoute; use \Pecee\SimpleRouter\RouterRoute;
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$route = new RouterRoute('/answer/1', function() { $route = new RouterRoute('/answer/1', function() {
die('this callback will match /answer/1'); die('this callback will match /answer/1');
@@ -81,6 +107,78 @@ $route->setPrefix('v1');
$router->addRoute($route); $router->addRoute($route);
``` ```
This is a simple example of an integration into a framework.
The framework has it's own ```Router``` class which inherits from the ```SimpleRouter``` class. This allows the framework to add custom functionality.
```php
namespace MyProject;
use Pecee\Handler\ExceptionHandler;
use Pecee\SimpleRouter\SimpleRouter;
class Router extends SimpleRouter {
protected static $exceptionHandlers = array();
public static function start() {
Debug::getInstance()->add('Router initialised.');
// Load routes.php
$file = $_ENV['basePath'] . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'routes.php';
if(file_exists($file)) {
require_once $file;
}
// Init locale settings
Locale::getInstance();
// Set default namespace
$defaultNamespace = '\\'.Registry::getInstance()->get('AppName') . '\\Controller';
// Handle exceptions
try {
parent::start($defaultNamespace);
} catch(\Exception $e) {
/* @var $handler ExceptionHandler */
foreach(self::$exceptionHandlers as $handler) {
$class = new $handler();
$class->handleError($e);
}
throw $e;
}
}
public static function addExceptionHandler($handler) {
self::$exceptionHandlers[] = $handler;
}
}
```
This is a basic example of a helper function for generating urls.
```php
use Pecee\SimpleRouter\RouterBase;
function url($controller, $parameters = null, $getParams = null) {
RouterBase::getInstance()->getRoute($controller, $parameters, $getParams);
}
```
In ```routes.php``` we have added this route:
```SimpleRouter::get('/item/{id}', 'myController@show');```
In the template we then call:
```url('myController@show', ['id' => 22], ['category' => 'shoes']);```
Result url is:
```/item/22?category=shoes ```
## Documentation ## Documentation
While I work on a better documentation, please refer to the Laravel 5 routing documentation here: While I work on a better documentation, please refer to the Laravel 5 routing documentation here:
+26
View File
@@ -0,0 +1,26 @@
{
"name": "pecee/simple-router",
"description": "Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router.",
"keywords": [ "router", "routing", "laravel", "pecee" ],
"license": "MIT",
"support": {
"source": "https://github.com/skipperbent/simple-php-router/issues"
},
"authors": [
{
"name": "Simon Sessingø",
"email": "simon.sessingoe@gmail.com"
}
],
"require": {
},
"require-dev": {
"phpunit/phpunit": "4.7.7"
},
"autoload": {
"psr-4": {
"Pecee\\": "src/"
}
}
}
-21
View File
@@ -1,21 +0,0 @@
<?php
namespace Pecee\Router;
class RouterGroup extends RouterEntry {
public function __construct() {
parent::__construct();
}
public function getRoute($requestMethod, &$url) {
// Check if request method is allowed
if(count($this->requestTypes) === 0 || in_array($requestMethod, $this->requestTypes)) {
return $this;
}
// No match here, move on...
return null;
}
}
-106
View File
@@ -1,106 +0,0 @@
<?php
namespace Pecee\Router;
use Pecee\Registry;
use Pecee\SimpleRouter;
class RouterRoute extends RouterEntry {
protected $url;
public function __construct($url, $callback) {
parent::__construct();
$this->url = $url;
$this->callback = $callback;
$this->settings['aliases'] = array();
// Set default namespace
$this->namespace = Registry::GetInstance()->get(SimpleRouter::SETTINGS_APPNAME, false) . '\\' . 'Controller';
}
public function getRoute($requestMethod, &$url) {
// Check if request method is allowed
if(count($this->requestTypes) === 0 || in_array($requestMethod, $this->requestTypes)) {
$url = explode('/', trim($url, '/'));
$route = explode('/', trim($this->url, '/'));
// Check if url parameter count matches
if(count($url) === count($route)) {
$parameters = array();
$matches = true;
// Check if url matches
foreach($route as $i => $path) {
$parameter = $this->parseParameter($path);
// Check if parameter of path matches, otherwise quit..
if(is_null($parameter) && strtolower($path) != strtolower($url[$i])) {
$matches = false;
break;
}
// Save parameter if we have one
if($parameter) {
$parameters[$parameter] = $url[$i];
}
}
// This route matches
if($matches) {
$this->parameters = $parameters;
return $this;
}
}
}
// No match here, move on...
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
* @return self
*/
public function setUrl($url) {
$this->url = $url;
return $this;
}
/**
* @param array $aliases
* @return self
*/
public function setAliases(array $aliases) {
$this->aliases = $aliases;
return $this;
}
/**
* Add alias
*
* @param $alias
* @return self
*/
public function addAlias($alias) {
$this->aliases[] = $alias;
return $this;
}
public function getAliases() {
$this->aliases;
}
}
-149
View File
@@ -1,149 +0,0 @@
<?php
namespace Pecee\Router;
class SimpleRouter {
const SETTINGS_APPNAME = 'AppName';
protected static $instance;
protected $currentRoute;
protected $routes;
protected $backstack;
protected $requestUri;
protected $requestMethod;
protected $loadedClass;
protected $appName;
public function __construct() {
$this->appName = Registry::GetInstance()->get(self::SETTINGS_APPNAME, false);
if (!$this->appName) {
throw new RouterException('"AppName" registry key not defined!', 2);
}
$this->routes = array();
$this->backstack = array();
$this->requestUri = rtrim($_SERVER['REQUEST_URI'], '/');
$this->requestMethod = strtolower(isset($_GET['_method']) ? $_GET['_method'] : $_SERVER['REQUEST_METHOD']);
}
public function addRoute(RouterEntry $route) {
if($this->currentRoute !== null) {
$this->backstack[] = $route;
} else {
$this->routes[] = $route;
}
}
public function route($url, $callback) {
$route = new RouterRoute($url, $callback);
$this->addRoute($route);
return $route;
}
protected function loadClass($name) {
if(!class_exists($name)) {
throw new RouterException(sprintf('Class %s does not exist', $name));
}
return new $name();
}
public function renderRoute(RouterEntry $route) {
$this->currentRoute = $route;
// Load middlewares if any
if($route->getMiddleware()) {
$this->loadClass($route->getMiddleware());
}
if(is_object($route->getCallback()) && is_callable($route->getCallback())) {
// When the callback is a function
call_user_func_array($route->getCallback(), $route->getParameters());
} else if(stripos($route->getCallback(), '@') > 0) {
// When the callback is a method
$controller = explode('@', $route->getCallback());
$class = $route->getNamespace() . '\\' . $controller[0];
$class = $this->loadClass($class);
$this->loadedClass = $class;
$method = $controller[1];
if(!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist', $method));
}
call_user_func_array(array($class, $method), $route->getParameters());
}
}
protected function renderBackstack(array $routes, &$settings, &$prefixes) {
// Loop through each route-request
/* @var $route RouterEntry */
foreach($routes as $route) {
$settings = array_merge($settings, $route->getMergeableSettings());
if($route->getPrefix()) {
array_push($prefixes, $route->getPrefix());
}
// If the route is a group
if($route instanceof RouterRoute) {
$route->setSettings($settings);
$route->setUrl( '/' . join('/', $prefixes) . $route->getUrl() );
}
// Stop if the route matches
$route = $route->getRoute($this->requestMethod, $this->requestUri);
if($route) {
$this->renderRoute($route);
}
// Remove itself from backstack
array_shift($this->backstack);
// Route any routes added to the backstack
$this->renderBackstack($this->backstack, $settings, $prefixes);
}
}
public function routeRequest() {
// Loop through each route-request
/* @var $route RouterEntry */
foreach($this->routes as $route) {
// Reset variables
$settings = array();
$prefixes = array();
$settings = array_merge($settings, $route->getMergeableSettings());
if($route->getPrefix()) {
array_push($prefixes, $route->getPrefix());
}
// Stop if the route matches
$route = $route->getRoute($this->requestMethod, $this->requestUri);
if($route) {
$this->renderRoute($route);
}
// Route any routes added to the backstack
$this->renderBackstack($this->backstack, $settings, $prefixes);
}
}
public static function GetInstance() {
if(self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace Pecee\SimpleRouter;
interface IRouteEntry {
}
+280
View File
@@ -0,0 +1,280 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Url;
class RouterBase {
protected static $instance;
protected $currentRoute;
protected $routes;
protected $processedRoutes;
protected $controllerUrlMap;
protected $backstack;
protected $requestUri;
protected $requestMethod;
protected $loadedRoute;
protected $defaultNamespace;
// TODO: make interface for controller routers, so they can be easily detected
// TODO: clean up - cut some of the methods down to smaller pieces
public function __construct() {
$this->routes = array();
$this->backstack = array();
$this->controllerUrlMap = array();
$this->requestUri = $_SERVER['REQUEST_URI'];
$this->requestMethod = (isset($_POST['_method'])) ? strtolower($_POST['_method']) : strtolower($_SERVER['REQUEST_METHOD']);
}
public function addRoute(RouterEntry $route) {
if($this->currentRoute !== null) {
$this->backstack[] = $route;
} else {
$this->routes[] = $route;
}
}
protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), $backstack = false) {
// Loop through each route-request
/* @var $route RouterEntry */
foreach($routes as $route) {
if($this->defaultNamespace) {
$namespace = null;
if ($route->getNamespace()) {
$namespace = $this->defaultNamespace . '\\' . $route->getNamespace();
} else {
$namespace = $this->defaultNamespace;
}
$route->setNamespace($namespace);
}
$newPrefixes = $prefixes;
$mergedSettings = array_merge($settings, $route->getMergeableSettings());
if($route->getPrefix()) {
array_push($newPrefixes, rtrim($route->getPrefix(), '/'));
}
$route->addSettings($mergedSettings);
if(!($route instanceof RouterGroup)) {
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->requestMethod);
}
$this->currentRoute = null;
if(count($this->backstack)) {
$backstack = $this->backstack;
$this->backstack = array();
// Route any routes added to the backstack
$this->processRoutes($backstack, $mergedSettings, $newPrefixes, true);
}
}
}
public function routeRequest() {
// Loop through each route-request
$this->processRoutes($this->routes);
// Make sure the urls is in the right order when comparing
usort($this->controllerUrlMap, function($a, $b) {
return strcmp($b->getUrl(), $a->getUrl());
});
foreach($this->controllerUrlMap as $route) {
$routeMatch = $route->matchRoute($this->requestMethod, rtrim($this->requestUri, '/') . '/');
if($routeMatch && !($routeMatch instanceof RouterGroup)) {
$this->loadedRoute = $routeMatch;
$routeMatch->renderRoute($this->requestMethod);
break;
}
}
if(!$this->loadedRoute) {
throw new RouterException(sprintf('Route not found: %s', $this->requestUri), 404);
}
}
/**
* @return string
*/
public function getDefaultNamespace(){
return $this->defaultNamespace;
}
/**
* @param string $defaultNamespace
*/
public function setDefaultNamespace($defaultNamespace) {
$this->defaultNamespace = $defaultNamespace;
}
/**
* @return RouterEntry
*/
public function getLoadedRoute() {
if(!($this->loadedRoute instanceof RouterGroup)) {
return $this->loadedRoute;
}
return null;
}
/**
* @return string
*/
public function getRequestMethod() {
return $this->requestMethod;
}
/**
* @return string
*/
public function getRequestUri() {
return $this->requestUri;
}
/**
* @return array
*/
public function getBackstack() {
return $this->backstack;
}
/**
* @return RouterEntry
*/
public function getCurrentRoute(){
return $this->currentRoute;
}
/**
* @return array
*/
public function getRoutes(){
return $this->routes;
}
protected function processUrl($route, $method = null, $parameters = null, $getParams = null) {
$url = rtrim($route->getUrl(), '/') . '/';
if($method !== null) {
$url .= $method . '/';
}
if($route instanceof RouterController || $route instanceof RouterRessource) {
if(count($parameters)) {
$url .= join('/', $parameters);
}
} else {
/* @var $route RouterEntry */
$params = $route->getParameters();
if(count($params)) {
$i = 0;
foreach($params as $param => $value) {
$value = (isset($parameters[$param])) ? $parameters[$param] : $value;
$url = str_ireplace('{' . $param. '}', $value, $route->getUrl());
$i++;
}
}
}
$p = '';
if($getParams !== null && count($getParams)) {
$p = '?'.Url::arrayToParams($getParams);
}
$url .= $p;
return $url;
}
public function getRoute($controller = null, $parameters = null, $getParams = null) {
if($parameters !== null && !is_array($parameters)) {
throw new \InvalidArgumentException('Invalid type for parameter. Must be array or null');
}
if($getParams !== null && !is_array($getParams)) {
throw new \InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
$c = '';
$method = null;
/* @var $route RouterRoute */
foreach($this->controllerUrlMap as $route) {
if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) {
$c = $route->getCallback();
} else if($route instanceof RouterController || $route instanceof RouterRessource) {
$c = $route->getController();
}
if($c === $controller || strpos($c, $controller) === 0) {
if(stripos($c, '@') !== false) {
$tmp = explode('@', $route->getCallback());
$method = strtolower($tmp[1]);
}
return $this->processUrl($route, $method, $parameters, $getParams);
}
}
// No match has yet been found, let's try to guess what url that should be returned
foreach($this->controllerUrlMap as $route) {
if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) {
$c = $route->getCallback();
if(stripos($controller, '@') !== false) {
$tmp = explode('@', $controller);
$c = $tmp[0];
}
} else if($route instanceof RouterController || $route instanceof RouterRessource) {
$c = $route->getController();
}
if(stripos($controller, '@') !== false) {
$tmp = explode('@', $controller);
$controller = $tmp[0];
$method = $tmp[1];
}
if($controller == $c) {
return $this->processUrl($route, $method, $parameters, $getParams);
}
}
// Nothing found - return current route
if($this->loadedRoute) {
$getParams = ($getParams === null) ? array() : $getParams;
$params = ($this->loadedRoute->getParameters() == null) ? array() : $this->loadedRoute->getParameters();
$parameters = ($parameters === null) ? array() : $parameters;
return $this->processUrl($this->loadedRoute, null, array_merge($params, $parameters), array_merge($_GET, $getParams));
}
return '/';
}
public static function getInstance() {
if(self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
}
@@ -0,0 +1,88 @@
<?php
namespace Pecee\SimpleRouter;
class RouterController extends RouterEntry {
const DEFAULT_METHOD = 'index';
protected $url;
protected $controller;
protected $method;
public function __construct($url, $controller) {
parent::__construct();
$this->url = $url;
$this->controller = $controller;
}
public function matchRoute($requestMethod, $url) {
$url = parse_url($url);
$url = rtrim($url['path'], '/') . '/';
if(strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$path = explode('/', $strippedUrl);
if(count($path)) {
$method = (!isset($path[0]) || trim($path[0]) === '') ? self::DEFAULT_METHOD : $path[0];
$this->method = $method;
array_shift($path);
$this->parameters = $path;
// Set callback
$this->setCallback($this->controller . '@' . $this->method);
return $this;
}
}
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
*/
public function setUrl($url) {
$url = rtrim($url, '/') . '/';
$this->url = $url;
}
/**
* @return string
*/
public function getController() {
return $this->controller;
}
/**
* @param string $controller
*/
public function setController($controller) {
$this->controller = $controller;
}
/**
* @return string
*/
public function getMethod() {
return $this->method;
}
/**
* @param string $method
*/
public function setMethod($method) {
$this->method = $method;
}
}
@@ -1,6 +1,6 @@
<?php <?php
namespace Pecee\Router; namespace Pecee\SimpleRouter;
abstract class RouterEntry { abstract class RouterEntry {
@@ -17,26 +17,36 @@ abstract class RouterEntry {
); );
protected $settings; protected $settings;
protected $requestTypes;
protected $callback; protected $callback;
protected $parameters; protected $parameters;
protected $parametersRegex;
public function __construct() { public function __construct() {
$this->settings = array(); $this->settings = array();
$this->requestTypes = array();
$this->parameters = array(); $this->parameters = array();
$this->parametersRegex = array();
} }
protected function parseParameter($path) { protected function loadClass($name) {
$parameters = array(); if(!class_exists($name)) {
throw new RouterException(sprintf('Class %s does not exist', $name));
preg_match('/{([A-Za-z\-\_]*?)}/is', $path, $parameters);
if(isset($parameters[1]) && count($parameters[1]) > 0) {
return $parameters[1];
} }
return null; return new $name();
}
/**
* Returns callback name/identifier for the current route based on the callback.
* Useful if you need to get a unique identifier for the loaded route, for instance
* when using translations etc.
*
* @return string
*/
public function getIdentifier() {
if(strpos($this->callback, '@') !== false) {
return $this->callback;
}
return 'function_' . md5($this->callback);
} }
/** /**
@@ -55,51 +65,12 @@ abstract class RouterEntry {
return $this->callback; return $this->callback;
} }
/**
* Add request type
*
* @param $type
* @return self
* @throws RouterException
*/
public function addRequestType($type) {
if(!in_array($type, self::$allowedRequestTypes)) {
throw new RouterException('Invalid request method: ' . $type);
}
$this->requestTypes[] = $type;
return $this;
}
/**
* @return mixed
*/
public function getRequestTypes() {
return $this->requestTypes;
}
/**
* @return mixed
*/
public function getParameters(){
return $this->parameters;
}
/**
* @param mixed $parameters
* @return self
*/
public function setParameters($parameters) {
$this->parameters = $parameters;
return $this;
}
/** /**
* @param string $prefix * @param string $prefix
* @return self * @return self
*/ */
public function setPrefix($prefix) { public function setPrefix($prefix) {
$this->prefix = trim($prefix, '/'); $this->prefix = '/' . trim($prefix, '/') . '/';
return $this; return $this;
} }
@@ -149,6 +120,33 @@ abstract class RouterEntry {
return $this->settings; return $this->settings;
} }
/**
* @return mixed
*/
public function getParameters(){
return $this->parameters;
}
/**
* @param mixed $parameters
* @return self
*/
public function setParameters($parameters) {
$this->parameters = $parameters;
return $this;
}
/**
* Add regular expression parameter match
*
* @param array $options
* @return self
*/
public function where(array $options) {
$this->parametersRegex = array_merge($this->parametersRegex, $options);
return $this;
}
/** /**
* Get settings that are allowed to be inherited by child routes. * Get settings that are allowed to be inherited by child routes.
* *
@@ -168,12 +166,26 @@ abstract class RouterEntry {
return $settings; return $settings;
} }
/**
* @param array $settings
* @return self
*/
public function addSettings(array $settings) {
array_merge($this->settings, $settings);
return $this;
}
/** /**
* @param array $settings * @param array $settings
* @return self * @return self
*/ */
public function setSettings($settings) { public function setSettings($settings) {
$this->settings = $settings; $this->settings = $settings;
if($settings['prefix']) {
$this->setPrefix($settings['prefix']);
}
return $this; return $this;
} }
@@ -197,6 +209,35 @@ abstract class RouterEntry {
$this->settings[$name] = $value; $this->settings[$name] = $value;
} }
abstract function getRoute($requestMethod, &$url); public function renderRoute($requestMethod) {
// Load middleware
if($this->getMiddleware()) {
$this->loadClass($this->getMiddleware());
}
if(is_object($this->getCallback()) && is_callable($this->getCallback())) {
// When the callback is a function
call_user_func_array($this->getCallback(), $this->getParameters());
} else {
// When the callback is a method
$controller = explode('@', $this->getCallback());
$className = $this->getNamespace() . '\\' . $controller[0];
$class = $this->loadClass($className);
$method = $requestMethod . ucfirst($controller[1]);
if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
call_user_func_array(array($class, $method), $this->getParameters());
return $class;
}
return null;
}
abstract function matchRoute($requestMethod, $url);
} }
@@ -1,3 +1,3 @@
<?php <?php
namespace Pecee\Router; namespace Pecee\SimpleRouter;
class RouterException extends \Exception { } class RouterException extends \Exception { }
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Pecee\SimpleRouter;
class RouterGroup extends RouterEntry {
public function __construct() {
parent::__construct();
}
public function matchRoute($requestMethod, $url) {
// Check if request method is allowed
if(strtolower($url) == strtolower($this->prefix) || stripos($url, $this->prefix) === 0) {
$hasAccess = (!$this->method);
if($this->method) {
if(is_array($this->method)) {
$hasAccess = (in_array($requestMethod, $this->method));
} else {
$hasAccess = strtolower($this->method) == strtolower($requestMethod);
}
}
if(!$hasAccess) {
throw new RouterException('Method not allowed');
}
return $this;
}
// No match here, move on...
return null;
}
}
+155
View File
@@ -0,0 +1,155 @@
<?php
namespace Pecee\SimpleRouter;
class RouterRessource extends RouterEntry {
const DEFAULT_METHOD = 'index';
protected $url;
protected $controller;
protected $method;
protected $postMethod;
public function __construct($url, $controller) {
parent::__construct();
$this->url = $url;
$this->controller = $controller;
$this->postMethod = strtolower(($_SERVER['REQUEST_METHOD'] != 'GET') ? 'post' : 'get');
}
public function renderRoute($requestMethod) {
// Load middleware
if($this->getMiddleware()) {
$this->loadClass($this->getMiddleware());
}
if(is_object($this->getCallback()) && is_callable($this->getCallback())) {
// When the callback is a function
call_user_func_array($this->getCallback(), $this->getParameters());
} else {
// When the callback is a method
$controller = explode('@', $this->getCallback());
$className = $this->getNamespace() . '\\' . $controller[0];
$class = $this->loadClass($className);
$method = strtolower($controller[1]);
if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
call_user_func_array(array($class, $method), $this->getParameters());
return $class;
}
return null;
}
protected function call($method, $parameters) {
$this->setCallback($this->controller . '@' . $method);
$this->parameters = $parameters;
return $this;
}
public function matchRoute($requestMethod, $url) {
$url = parse_url($url);
$url = rtrim($url['path'], '/') . '/';
if(strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
$url = rtrim($url, '/');
$strippedUrl = trim(substr($url, strlen($this->url)), '/');
$path = explode('/', $strippedUrl);
$args = $path;
$action = '';
if(count($args)) {
$action = $args[0];
array_shift($args);
}
if (count($path)) {
// Delete
if($requestMethod === self::REQUEST_TYPE_DELETE && $this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('destroy', $args);
}
// Update
if(in_array($requestMethod, array('put', 'patch')) && $this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('update', array_merge(array($action), $args));
}
// Edit
if(isset($args[0]) && strtolower($args[0]) === 'edit' && $this->postMethod === self::REQUEST_TYPE_GET) {
return $this->call('edit', array_merge(array($action), array_slice($args, 1)));
}
// Create
if(strtolower($action) === 'create' && $requestMethod === self::REQUEST_TYPE_GET) {
return $this->call('create', $args);
}
// Save
if($this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('store', $args);
}
// Show
if($action && $this->postMethod === self::REQUEST_TYPE_GET) {
return $this->call('show', array_merge(array($action), $args));
}
// Index
return $this->call('index', $args);
}
}
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
*/
public function setUrl($url) {
$url = rtrim($url, '/') . '/';
$this->url = $url;
}
/**
* @return string
*/
public function getController() {
return $this->controller;
}
/**
* @param string $controller
*/
public function setController($controller) {
$this->controller = $controller;
}
/**
* @return string
*/
public function getMethod() {
return $this->method;
}
/**
* @param string $method
*/
public function setMethod($method) {
$this->method = $method;
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Registry;
use Pecee\Router;
class RouterRoute extends RouterEntry {
protected $url;
protected $requestTypes;
public function __construct($url, $callback) {
parent::__construct();
$this->setUrl($url);
$this->setCallback($callback);
$this->settings['aliases'] = array();
$this->requestTypes = array();
}
protected function parseParameters($url) {
$parameters = array();
preg_match_all('/{([A-Za-z\-\_]*?)}/is', $url, $parameters);
if(isset($parameters[1]) && count($parameters[1]) > 0) {
return $parameters[1];
}
return null;
}
protected function parseParameter($path) {
$parameters = array();
preg_match('/{([A-Za-z\-\_]*?)}/is', $path, $parameters);
if(isset($parameters[1]) && count($parameters[1]) > 0) {
return $parameters[1];
}
return null;
}
public function matchRoute($requestMethod, $url) {
// Check if request method is allowed
if(count($this->requestTypes) === 0 || in_array($requestMethod, $this->requestTypes)) {
$url = parse_url($url);
$url = $url['path'];
$url = explode('/', trim($url, '/'));
$route = explode('/', trim($this->url, '/'));
// Check if url parameter count matches
if(count($url) === count($route)) {
$parameters = array();
$matches = true;
// Check if url matches
foreach($route as $i => $path) {
$parameter = $this->parseParameter($path);
// Check if parameter of path matches, otherwise quit..
if(is_null($parameter) && strtolower($path) != strtolower($url[$i])) {
$matches = false;
break;
}
// Save parameter if we have one
if($parameter) {
$parameterValue = $url[$i];
$regex = (isset($this->parametersRegex[$parameter]) ? $this->parametersRegex[$parameter] : null);
if($regex !== null) {
// Use the regular expression rule provided to filter the value
$matches = array();
preg_match('/'.$regex.'/is', $url[$i], $matches);
if(count($matches)) {
$parameterValue = $matches[0];
}
}
// Add parameter value
$parameters[$parameter] = $parameterValue;
}
}
// This route matches
if($matches) {
$this->parameters = $parameters;
return $this;
}
}
}
// No match here, move on...
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
* @return self
*/
public function setUrl($url) {
$parameters = $this->parseParameters($url);
if($parameters !== null) {
foreach($parameters as $param) {
$this->parameters[$param] = '';
}
}
$this->url = $url;
return $this;
}
/**
* @param array $aliases
* @return self
*/
public function setAliases(array $aliases) {
$this->aliases = $aliases;
return $this;
}
/**
* Add alias
*
* @param $alias
* @return self
*/
public function addAlias($alias) {
$this->aliases[] = $alias;
return $this;
}
public function getAliases() {
$this->aliases;
}
/**
* Add request type
*
* @param $type
* @return self
* @throws RouterException
*/
public function addRequestType($type) {
if(!in_array($type, self::$allowedRequestTypes)) {
throw new RouterException('Invalid request method: ' . $type);
}
$this->requestTypes[] = $type;
return $this;
}
/**
* @return mixed
*/
public function getRequestTypes() {
return $this->requestTypes;
}
}
@@ -7,17 +7,21 @@
* This class is added so calls can be made staticly like Router::get() making the code look more pretty. * This class is added so calls can be made staticly like Router::get() making the code look more pretty.
*/ */
namespace Pecee; namespace Pecee\SimpleRouter;
use Pecee\Router\SimpleRouter; class SimpleRouter {
class Router { public static function start($defaultNamespace = null) {
$router = RouterBase::GetInstance();
$router->setDefaultNamespace($defaultNamespace);
$router->routeRequest();
}
public static function get($url, $callback) { public static function get($url, $callback) {
$route = new RouterRoute($url, $callback); $route = new RouterRoute($url, $callback);
$route->addRequestType(RouterRoute::REQUEST_TYPE_GET); $route->addRequestType(RouterRoute::REQUEST_TYPE_GET);
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$router->addRoute($route); $router->addRoute($route);
return $route; return $route;
@@ -27,7 +31,7 @@ class Router {
$route = new RouterRoute($url, $callback); $route = new RouterRoute($url, $callback);
$route->addRequestType(RouterRoute::REQUEST_TYPE_POST); $route->addRequestType(RouterRoute::REQUEST_TYPE_POST);
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$router->addRoute($route); $router->addRoute($route);
return $route; return $route;
@@ -37,7 +41,7 @@ class Router {
$route = new RouterRoute($url, $callback); $route = new RouterRoute($url, $callback);
$route->addRequestType(RouterRoute::REQUEST_TYPE_PUT); $route->addRequestType(RouterRoute::REQUEST_TYPE_PUT);
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$router->addRoute($route); $router->addRoute($route);
return $route; return $route;
@@ -47,7 +51,7 @@ class Router {
$route = new RouterRoute($url, $callback); $route = new RouterRoute($url, $callback);
$route->addRequestType(RouterRoute::REQUEST_TYPE_DELETE); $route->addRequestType(RouterRoute::REQUEST_TYPE_DELETE);
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$router->addRoute($route); $router->addRoute($route);
return $route; return $route;
@@ -61,7 +65,7 @@ class Router {
$group->setSettings($settings); $group->setSettings($settings);
} }
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$router->addRoute($group); $router->addRoute($group);
return $group; return $group;
@@ -73,10 +77,38 @@ class Router {
$route->addRequestType($requestType); $route->addRequestType($requestType);
} }
$router = SimpleRouter::GetInstance(); $router = RouterBase::getInstance();
$router->addRoute($route); $router->addRoute($route);
return $route; return $route;
} }
public static function all($url, $callback) {
$route = new RouterRoute($url, $callback);
$router = RouterBase::getInstance();
$router->addRoute($route);
return $route;
}
public static function controller($url, $controller) {
$route = new RouterController($url, $controller);
$router = RouterBase::getInstance();
$router->addRoute($route);
return $route;
}
public static function ressource($url, $controller) {
$route = new RouterRessource($url, $controller);
$router = RouterBase::getInstance();
$router->addRoute($route);
return $route;
}
public function getRoute($controller = null, $parameters = null, $getParams = null) {
return RouterBase::getInstance()->getRoute($controller, $parameters, $getParams);
}
} }