diff --git a/README.md b/README.md index 7b6a68d..3b94a41 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,89 @@ # Simple PHP router Simple, fast and yet powerful PHP router that is easy to get integrated and in any project. Heavily inspired by the way Laravel handles routing, with both simplicity and expandability in mind. -## Installation +**Note: this documentation is currently work-in-progress. Feel free to contribute.** + +### Notes + +The goal of this project is to create a router that is more or less 100% compatible with the Laravel documentation, while remaining as simple as possible, and as easy to integrate and change without compromising either speed or complexity. Being lightweight is the #1 priority. + +### Ideas and issues + +If you want a great new feature or experience any issues what-so-ever, please feel free to leave an issue and i'll look into it whenever possible. + +--- + +## Table of Contents + +- Gettings started + - Requirements + - Notes + - Features + - Installation + - Setting up Apache + - Setting up Nginx + - Setting up simple-php-router + - Helper methods + +- Routes + - Basic routing + - Available methods + - Multiple HTTP-verbs + - Route parameters + - Required parameters + - Optional parameters + - Regular expression constraints + - Regular expression route-match + - Named routes + - Generating URLs To Named Routes + - Router groups + - Middleware + - Namespaces + - Subdomain-routing + - Route prefixes + - Form Method Spoofing + - Accessing The Current Route + - Other examples + +- CSRF-protection + - Adding CSRF-verifier + - Getting CSRF-token + +- Middleware + - Example +- ExceptionHandler + - Example + +- Urls + - Get by name (single route) + - Get by name (controller route) + - Get by class + - Get by custom names for methods on a controller/resource route + - Getting REST/resource controller urls + +- Input & parameters + - Return single parameter value + - Return single parameter object + - Managing files + - Return all parameters + +- Advanced + - Bootmanager: loading routes dynamically + - Ovewrite route about to be loaded + - Examples + - Rewriting to new route + - Changing callback + + - Adding routes manually + - Extending + +- Credits + - Sites + - License + +___ + +# Getting started Add the latest version of Simple PHP Router running this command. ``` @@ -11,12 +93,29 @@ composer require pecee/simple-router ## Requirements - PHP 5.4 or greater +- mcrypt extension ## 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. +We've included a simple demo project for the router which can be found in the `demo-project` folder. This project should give you a basic understanding of how to setup and use simple-php-router project. -### Features +Please note that the demo-project only covers how to integrate the `simple-php-router` in a project without an existing framework. If you are using a framework in your project, the implementation might vary. + +You can find the demo-project here: [https://github.com/skipperbent/simple-router-demo]() + +**What we won't cover:** + +- How to setup a solution that fits your need. This is a basic demo to help you get started. +- Understanding of MVC; including Controllers, Middlewares or ExceptionHandlers. +- How to integrate into third party frameworks. + +**What we cover:** + +- How to get up and running fast - from scratch. +- How to get ExceptionHandlers, Middlewares and Controllers working. +- How to setup your webservers. + +## Features - Basic routing (`GET`, `POST`, `PUT`, `PATCH`, `UPDATE`, `DELETE`) with support for custom multiple verbs. - Regular Expression Constraints for parameters. @@ -32,27 +131,50 @@ The goal of this project is to create a router that is 100% compatible with the - Custom boot managers to rewrite urls to "nicer" ones. - Input manager; easily manage `GET`, `POST` and `FILE` values. -## Installation and demo +## Installation -We've included a simple demo project for the router which can be found in the `demo-project` folder. - -Please refer to the demo-project documentation for further reading on how to setup and install simple-php-router: - -[Link to demo documentation](demo-project/README.md) - -## 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. - -This is an example of a basic ```index.php``` file: +1. Navigate to your project folder in terminal and run the following command: ```php -use \Pecee\SimpleRouter\SimpleRouter; +composer require pecee/simple-router +``` -// Load external routes file +### Setting up Nginx + +If you are using Nginx please make sure that url-rewriting is enabled. + +You can easily enable url-rewriting by adding the following configuration for the Nginx configuration-file for the demo-project. + +``` +location / { + try_files $uri $uri/ /index.php?$query_string; +} +``` + +### Setting up Apache + +Nothing special is required for Apache to work. We've include the `.htaccess` file in the `public` folder. If rewriting is not working for you, please check that the `mod_rewrite` module (htaccess support) is enabled in the Apache configuration. + +### Setting up simple-php-router + +Create a new file, name it `routes.php` and place it in your library folder. This will be the file where you define all the routes for your project. + +**WARNING: NEVER PLACE YOUR ROUTES.PHP IN YOUR PUBLIC FOLDER!** + +In your ```index.php``` require your newly-created ```routes.php``` and call the ```SimpleRouter::start()``` method. This will trigger and do the actual routing of the requests. + +It's not required, but you can set `SimpleRouter::setDefaultNamespace('\Demo\Controllers');` to prefix all routes with the namespace to your controllers. This will simplify things a bit, as you won't have to specify the namespace for your controllers on each route. + +**This is an example of a basic ```index.php``` file:** + +```php + 'Middlewares\Site', 'exceptionHandler' => 'Handlers\CustomExceptionHandler'], function() { - - - SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show', ['where' => ['id' => '[0-9]+']]); - - /** - * Using optional parameters - */ - SimpleRouter::get('/answers/{id?}', 'ControllerAnswers@show'); - - - /** - * This example will route url when matching the regular expression to the method. - * For example route: domain.com/ajax/music/world -> ControllerAjax@process (parameter: music/world) - */ - - SimpleRouter::all('/ajax', 'ControllerAjax@process')->setMatch('.*?\\/ajax\\/([A-Za-z0-9\\/]+)'); - - - /** - * Restful resource (see IRestController interface for available methods) - */ - - SimpleRouter::resource('/rest', 'ControllerRessource'); - - - /** - * Load the entire controller (where url matches method names - getIndex(), postIndex(), putIndex()). - * The url paths will determine which method to render. - * - * For example: - * - * GET /animals => getIndex() - * GET /animals/view => getView() - * POST /animals/save => postSave() - * - * etc. - */ - - SimpleRouter::controller('/animals', 'ControllerAnimals'); - - - /** - * Example of providing callback instead of Controller - */ - - SimpleRouter::post('/something', function() { - - die('Callback example'); - - }); - -}); - -SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']); - -``` - -#### ExceptionHandler example - -This is a basic example of an ExceptionHandler implementation (please see "[Easily overwrite route about to be loaded](#easily-overwrite-route-about-to-be-loaded)" for examples on how to change callback). - -```php -namespace Demo\Handlers; - -use Pecee\Handlers\IExceptionHandler; -use Pecee\Http\Request; -use Pecee\SimpleRouter\Exceptions\NotFoundHttpException; -use Pecee\SimpleRouter\Route\ILoadableRoute; - -class CustomExceptionHandler implements IExceptionHandler -{ - public function handleError(Request $request, ILoadableRoute &$route = null, \Exception $error) - { - - /* You can use the exception handler to format errors depending on the request and type. */ - - if (stripos($request->getUri(), '/api') !== false) { - - response()->json([ - 'error' => $error->getMessage(), - 'code' => $error->getCode(), - ]); - - } - - /* The router will throw the NotFoundHttpException on 404 */ - if($error instanceof NotFoundHttpException) { - - /* - * Render your own custom 404-view, rewrite the request to another route, - * or simply return the $request object to ignore the error and continue on rendering the route. - * - * The code below will make the router render our page.notfound route. - */ - - $request->setUri(url('page.notfound')); - return $request; - - } - - throw $error; - - } - -} -``` - -### 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) { - // Do stuff... - }); - -}); -``` - -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: - -### Adding routes manually (with no helper) - -The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```Router``` 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 -use \Pecee\SimpleRouter\Router; -use \Pecee\SimpleRouter\Route\RouteUrl; - -/* Grap the router instance */ -$router = Router::getInstance(); - -$route = new RouteUrl('/answer/1', function() { - - die('this callback will match /answer/1'); - -}); - -$route->setMiddleware('\Demo\Middlewares\AuthMiddleware'); -$route->setNamespace('\Demo\Controllers'); -$route->setPrefix('v1'); - -/* Add the route to the router */ -$router->addRoute($route); -``` - -### Extending - -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 like loading a custom `routes.php` file or add debugging information etc. - -```php -namespace Demo; - -use Pecee\SimpleRouter\SimpleRouter; - -class Router extends SimpleRouter { - - public static function start() { - - // change this to whatever makes sense in your project - require_once 'routes.php'; - - // change default namespace for all routes - parent::setDefaultNamespace('\Demo\Controllers'); - - // Do initial stuff - parent::start(); - - } - -} -``` - -## Helper functions - -To simplify to use of simple-router functionality, we recommend you add these helper functions to your project. +To implement the functions below, simply copy the code to a new file and require the file before initializing the router. ```php +where('name', '[A-Za-z]+'); + +SimpleRouter::get('/user/{id}', function ($id) { + // +})->where('id', '[0-9]+'); + +SimpleRouter::get('/user/{id}/{name}', function ($id, $name) { + // +})->where(['id' => '[0-9]+', 'name' => '[a-z]+']); +``` + +### Regular expression route-match + +You can define a regular-expression match for the entire route if you wish. + +This is useful if you for example are creating a model-box which loads urls from ajax. + +The example below is using the following regular expression: `/ajax/([\w]+)/?([0-9]+)?/?` which basically just matches `/ajax/` and exspects the next parameter to be a string - and the next to be a number (but optional). + +**Matches:** `/ajax/abc/`, `/ajax/abc/123/` + +**Doesn't match:** `/ajax/` + +Match groups specified in the regex will be passed on as parameters: + +```php +SimpleRouter::all('/ajax/abc/123', function($param1, $param2) { + // param1 = abc + // param2 = 123 +})->setMatch('/\/ajax\/([\w]+)\/?([0-9]+)?\/?/is'); +``` + +## Named routes + +Named routes allow the convenient generation of URLs or redirects for specific routes. You may specify a name for a route by chaining the name method onto the route definition: + +```php +SimpleRouter::get('/user/profile', function () { + // +})->name('profile'); +``` + +You can also specify names for Controller-actions: + +```php +SimpleRouter::get('/user/profile', 'UserController@profile')->name('profile'); +``` + +### Generating URLs To Named Routes + +Once you have assigned a name to a given route, you may use the route's name when generating URLs or redirects via the global `url` helper-function (see helpers section): + +```php +// Generating URLs... +$url = url('profile'); +``` + +If the named route defines parameters, you may pass the parameters as the second argument to the `url` function. The given parameters will automatically be inserted into the URL in their correct positions: + +```php +SimpleRouter::get('/user/{id}/profile', function ($id) { + // +})->name('profile'); + +$url = url('profile', ['id' => 1]); +``` + +For more information on urls, please see the [Urls](#urls) section. + +## Router groups + +Route groups allow you to share route attributes, such as middleware or namespaces, across a large number of routes without needing to define those attributes on each individual route. Shared attributes are specified in an array format as the first parameter to the `SimpleRouter::group` method. + +### Middleware + +To assign middleware to all routes within a group, you may use the middleware key in the group attribute array. Middleware are executed in the order they are listed in the array: + +```php +SimpleRouter::group(['middleware' => '\Demo\Middleware\Auth'], function () { + SimpleRouter::get('/', function () { + // Uses Auth Middleware + }); + + SimpleRouter::get('/user/profile', function () { + // Uses Auth Middleware + }); +}); +``` + +### Namespaces + +Another common use-case for route groups is assigning the same PHP namespace to a group of controllers using the `namespace` parameter in the group array: + +```php +SimpleRouter::group(['namespace' => 'Admin'], function () { + // Controllers Within The "App\Http\Controllers\Admin" Namespace +}); +``` + +### Subdomain-routing + +Route groups may also be used to handle sub-domain routing. 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 +SimpleRouter::group(['domain' => '{account}.myapp.com'], function () { + SimpleRouter::get('/user/{id}', function ($account, $id) { + // + }); +}); +``` + +### Route prefixes + +The `prefix` group 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`: + +```php +SimpleRouter::group(['prefix' => '/admin'], function () { + SimpleRouter::get('/users', function () { + // Matches The "/admin/users" URL + }); +}); +``` + +## Form Method Spoofing + +HTML forms do not support `PUT`, `PATCH` or `DELETE` actions. So, when defining `PUT`, `PATCH` or `DELETE` routes that are called from an HTML form, you will need to add a hidden `_method` field to the form. The value sent with the `_method` field will be used as the HTTP request method: + +```php + +``` + +## Accessing The Current Route + +You can access information about the current route loaded by using the following method: + +```php +SimpleRouter::router()->getLoadedRoute(); +``` + +## Other examples + +You can find many more examples in the `routes.php` example-file below: + +```php + '\Demo\Middlewares\Site', 'exceptionHandler' => 'Handlers\CustomExceptionHandler'], function() { + + + SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show', ['where' => ['id' => '[0-9]+']]); + + + /** + * Restful resource (see IRestController interface for available methods) + */ + + SimpleRouter::resource('/rest', 'ControllerRessource'); + + + /** + * Load the entire controller (where url matches method names - getIndex(), postIndex(), putIndex()). + * The url paths will determine which method to render. + * + * For example: + * + * GET /animals => getIndex() + * GET /animals/view => getView() + * POST /animals/save => postSave() + * + * etc. + */ + + SimpleRouter::controller('/animals', 'ControllerAnimals'); + +}); + +SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']); + +``` + +--- + +# CSRF Protection + +Any forms posting to `POST`, `PUT` or `DELETE` routes should include the CSRF-token. We strongly recommend that you create your enable CSRF-verification on your site. + +Create a new class and extend the ```BaseCsrfVerifier``` middleware class provided with simple-php-router. + +Add the property ```except``` with an array of the urls to the routes you would like to exclude/whitelist from the CSRF validation. Using ```*``` at the end for the url will match the entire url. + +**Here's a basic example on a CSRF-verifier class:** + +```php +namespace Demo\Middlewares; + +use Pecee\Http\Middleware\BaseCsrfVerifier; + +class CsrfVerifier extends BaseCsrfVerifier +{ + /** + * CSRF validation will be ignored on the following urls. + */ + protected $except = ['/api/*']; +} +``` + +## Adding CSRF-verifier + +When you've created your CSRF verifier - you need to tell simple-php-router that it should use it. You can do this by adding the following line in your `routes.php` file: + +```php +Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier()); +``` + +## Getting CSRF-token + +When posting to any of the urls that has CSRF-verification enabled, you need post your CSRF-token or else the request will get rejected. + +You can get the CSRF-token by calling the helper method: + +```php +csrf_token(); +``` + +--- + +# Middlewares + +Middlewares are classes that loads before the route is rendered. A middleware can be used to verify that a user is logged in - or to set parameters specific for the current request/route. Middlewares must implement the `IMiddleware` interface. + +## Example + +```php +namespace Demo\Middlewares; + +use Pecee\Http\Middleware\IMiddleware; +use Pecee\Http\Request; +use Pecee\SimpleRouter\Route\ILoadableRoute; + +class CustomMiddleware implements Middleware { + + public function handle(Request $request, ILoadableRoute &$route) { + + $request->setUri(url('home')); + + } +} +``` + +--- + +# ExceptionHandler + +ExceptionHandler are classes that handles all exceptions. ExceptionsHandlers must implement the `IExceptionHandler` interface. + +## Example + +Resource controllers can implement the `IRestController` interface, but is not required. + +This is a basic example of an ExceptionHandler implementation (please see "[Easily overwrite route about to be loaded](#easily-overwrite-route-about-to-be-loaded)" for examples on how to change callback). + +```php +namespace Demo\Handlers; + +use Pecee\Handlers\IExceptionHandler; +use Pecee\Http\Request; +use Pecee\SimpleRouter\Exceptions\NotFoundHttpException; +use Pecee\SimpleRouter\Route\ILoadableRoute; + +class CustomExceptionHandler implements IExceptionHandler +{ + public function handleError(Request $request, ILoadableRoute &$route = null, \Exception $error) + { + + /* You can use the exception handler to format errors depending on the request and type. */ + + if (stripos($request->getUri(), '/api') !== false) { + + response()->json([ + 'error' => $error->getMessage(), + 'code' => $error->getCode(), + ]); + + } + + /* The router will throw the NotFoundHttpException on 404 */ + if($error instanceof NotFoundHttpException) { + + /* + * Render your own custom 404-view, rewrite the request to another route, + * or simply return the $request object to ignore the error and continue on rendering the route. + * + * The code below will make the router render our page.notfound route. + */ + + $request->setUri(url('page.notfound')); + return $request; + + } + + throw $error; + + } + +} +``` + +--- + +# Urls By default all controller and resource routes will use a simplified version of their url as name. -**Get routes using custom name (single route)** +### Get routes using custom name (single route) ```php SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']); @@ -348,7 +674,7 @@ url('product', null, ['category' => 'shoes']); # /product-view/?category=shoes ``` -**Getting the url using the name (controller route)** +### Getting the url using the name (controller route) ```php SimpleRouter::controller('/images', 'ImagesController', ['as' => 'picture']); @@ -363,7 +689,7 @@ url('picture', 'view'); # /images/view/ ``` -**Getting the url using class** +### Getting the url using class ```php SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']); @@ -377,7 +703,7 @@ url('ImagesController@getImage', null, ['id' => 22]); # /images/image/?id=22 ``` -**Using custom names for methods on a controller/resource route** +### Using custom names for methods on a controller/resource route ```php SimpleRouter::controller('gadgets', 'GadgetsController', ['names' => ['getIphoneInfo' => 'iphone']]); @@ -388,7 +714,7 @@ url('gadgets.iphone'); # /gadgets/iphoneinfo/ ``` -**Getting REST/resource controller urls** +### Getting REST/resource controller urls ```php SimpleRouter::resource('/phones', 'PhonesController'); @@ -406,7 +732,8 @@ url('phones.edit'); # /phones/edit/ ``` -**Return the current url** +### Return the current url + ```php url(); url(null, null, ['q' => 'cars']); @@ -416,152 +743,7 @@ url(null, null, ['q' => 'cars']); # /CURRENT-URL/?q=cars ``` -## Custom CSRF verifier - -Create a new class and extend the ```BaseCsrfVerifier``` middleware class provided with simple-php-router. - -Add the property ```except``` with an array of the urls to the routes you would like to exclude from the CSRF validation. Using ```*``` at the end for the url will match the entire url. - -Querystrings are ignored. - -```php -use Pecee\Http\Middleware\BaseCsrfVerifier; - -class CsrfVerifier extends BaseCsrfVerifier { - - protected $except = [ - '/companies/*', - '/api', - ]; - -} -``` - -Register the new class in your ```routes.php```, custom ```Router``` class or wherever you register your routes. - -```php -SimpleRouter::csrfVerifier(new \Demo\Middleware\CsrfVerifier()); -``` - -## Using router bootmanager to make custom rewrite rules - -Sometimes it can be necessary to keep urls stored in the database, file or similar. In this example, we want the url ```/my-cat-is-beatiful``` to load the route ```/article/view/1``` which the router knows, because it's defined in the ```routes.php``` file. - -To interfere with the router, we create a class that implements the ```IRouterBootManager``` interface. This class will be loaded before any other rules in ```routes.php``` and allow us to "change" the current route, if any of our criteria are fulfilled (like coming from the url ```/my-cat-is-beatiful```). - -```php -use Pecee\Http\Request; -use Pecee\SimpleRouter\IRouterBootManager; - -class CustomRouterRules implement IRouterBootManager { - - public function boot(Request $request) { - - $rewriteRules = [ - '/my-cat-is-beatiful' => '/article/view/1', - '/horses-are-great' => '/article/view/2' - ]; - - foreach($rewriteRules as $url => $rule) { - - // If the current uri matches the url, we use our custom route - - if($request->getUri() === $url) { - $request->setUri($rule); - } - } - - return $request; - } - -} -``` - -The above should be pretty self-explanatory and can easily be changed to loop through urls store in the database, file or cache. - -What happens is that if the current route matches the route defined in the index of our ```$rewriteRules``` array, we set the route to the array value instead. - -By doing this the route will now load the url ```/article/view/1``` instead of ```/my-cat-is-beatiful```. - -The last thing we need to do, is to add our custom boot-manager to the ```routes.php``` file. You can create as many bootmanagers as you like and easily add them in your ```routes.php``` file. - -## Easily overwrite route about to be loaded -Sometimes it can be useful to manipulate the route about to be loaded. -simple-php-router allows you to easily change the route about to be executed. -All information about the current route is stored in the ```\Pecee\SimpleRouter\Router``` instance's `loadedRoute` property. - -For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`. - - -```php -use Pecee\SimpleRouter; -$route = SimpleRouter::router()->getLoadedRoute(); - -$route->setCallback('Example\MyCustomClass@hello'); - -// -- or you can rewrite by doing -- - -$route->setClass('Example\MyCustomClass'); -$route->setMethod('hello'); -``` - - -### Examples - -It's only possible to change the route BEFORE the route has initially been loaded. If you want to redirect to another route, we highly recommend that you -modify the `IRoute` object from a `Middleware` or `ExceptionHandler`, like the examples below. - -#### Rewriting to new route - -The example below will cause the router to re-route the request with another url. We are using the `url()` helper function to get the uri to another route added in the `routes.php` file. - -**NOTE: Use this method if you want to fully load another route using it's settings (request method etc).** - - -```php -namespace Demo\Middlewares; - -use Pecee\Http\Middleware\IMiddleware; -use Pecee\Http\Request; -use Pecee\SimpleRouter\Route\ILoadableRoute; - -class CustomMiddleware implements Middleware { - - public function handle(Request $request, ILoadableRoute &$route) { - - $request->setUri(url('home')); - - } - -} -``` - -#### Changing callback -You can also change the callback by modifying the `$route` parameter. This is perfect if you just want to display a view quickly - or change the callback depending -on some criteria's for the request. - -The callback below will fire immediately after the `Middleware` or `ExceptionHandler` has been loaded, as they are loaded before the route is rendered. -If you wish to change the callback from outside, please have this in mind. - -**NOTE: Use this method if you want to load another controller. No additional middlewares or rules will be loaded.** - -```php -namespace Demo\Middlewares; - -use Pecee\Http\Middleware\IMiddleware; -use Pecee\Http\Request; -use Pecee\SimpleRouter\Route\ILoadableRoute; - -class CustomMiddleware implements Middleware { - - public function handle(Request $request, ILoadableRoute &$route) { - - $route->callback('DefaultController@home'); - - } - -} -``` +# Input & parameters ## Using the Input class to manage parameters @@ -675,6 +857,191 @@ Below example requires you to have the helper functions added. Please refer to t $siteId = input()->get('site_id', 2); ``` +--- + +# Advanced + +## Load routes dynamicially using the router bootmanager + +Sometimes it can be necessary to keep urls stored in the database, file or similar. In this example, we want the url ```/my-cat-is-beatiful``` to load the route ```/article/view/1``` which the router knows, because it's defined in the ```routes.php``` file. + +To interfere with the router, we create a class that implements the ```IRouterBootManager``` interface. This class will be loaded before any other rules in ```routes.php``` and allow us to "change" the current route, if any of our criteria are fulfilled (like coming from the url ```/my-cat-is-beatiful```). + +```php +use Pecee\Http\Request; +use Pecee\SimpleRouter\IRouterBootManager; + +class CustomRouterRules implement IRouterBootManager { + + public function boot(Request $request) { + + $rewriteRules = [ + '/my-cat-is-beatiful' => '/article/view/1', + '/horses-are-great' => '/article/view/2' + ]; + + foreach($rewriteRules as $url => $rule) { + + // If the current uri matches the url, we use our custom route + + if($request->getUri() === $url) { + $request->setUri($rule); + } + } + + return $request; + } + +} +``` + +The above should be pretty self-explanatory and can easily be changed to loop through urls store in the database, file or cache. + +What happens is that if the current route matches the route defined in the index of our ```$rewriteRules``` array, we set the route to the array value instead. + +By doing this the route will now load the url ```/article/view/1``` instead of ```/my-cat-is-beatiful```. + +The last thing we need to do, is to add our custom boot-manager to the ```routes.php``` file. You can create as many bootmanagers as you like and easily add them in your ```routes.php``` file. + +```php +SimpleRouter::addBootManager(new CustomRouterRules()); +``` + +## Easily overwrite route about to be loaded +Sometimes it can be useful to manipulate the route about to be loaded. +simple-php-router allows you to easily change the route about to be executed. +All information about the current route is stored in the ```\Pecee\SimpleRouter\Router``` instance's `loadedRoute` property. + +For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`. + + +```php +use Pecee\SimpleRouter; +$route = SimpleRouter::router()->getLoadedRoute(); + +$route->setCallback('Example\MyCustomClass@hello'); + +// -- or you can rewrite by doing -- + +$route->setClass('Example\MyCustomClass'); +$route->setMethod('hello'); +``` + +### Examples + +It's only possible to change the route BEFORE the route has initially been loaded. If you want to redirect to another route, we highly recommend that you +modify the `IRoute` object from a `Middleware` or `ExceptionHandler`, like the examples below. + +#### Rewriting to new route + +The example below will cause the router to re-route the request with another url. We are using the `url()` helper function to get the uri to another route added in the `routes.php` file. + +**NOTE: Use this method if you want to fully load another route using it's settings (request method etc).** + + +```php +namespace Demo\Middlewares; + +use Pecee\Http\Middleware\IMiddleware; +use Pecee\Http\Request; +use Pecee\SimpleRouter\Route\ILoadableRoute; + +class CustomMiddleware implements Middleware { + + public function handle(Request $request, ILoadableRoute &$route) { + + $request->setUri(url('home')); + + } +} +``` + +#### Changing callback +You can also change the callback by modifying the `$route` parameter. This is perfect if you just want to display a view quickly - or change the callback depending +on some criteria's for the request. + +The callback below will fire immediately after the `Middleware` or `ExceptionHandler` has been loaded, as they are loaded before the route is rendered. +If you wish to change the callback from outside, please have this in mind. + +**NOTE: Use this method if you want to load another controller. No additional middlewares or rules will be loaded.** + +```php +namespace Demo\Middlewares; + +use Pecee\Http\Middleware\IMiddleware; +use Pecee\Http\Request; +use Pecee\SimpleRouter\Route\ILoadableRoute; + +class CustomMiddleware implements Middleware { + + public function handle(Request $request, ILoadableRoute &$route) { + + $route->callback('DefaultController@home'); + + } + +} +``` + +## Adding routes manually + +The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```Router``` 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 +use \Pecee\SimpleRouter\Router; +use \Pecee\SimpleRouter\Route\RouteUrl; + +/* Grap the router instance */ +$router = Router::getInstance(); + +$route = new RouteUrl('/answer/1', function() { + + die('this callback will match /answer/1'); + +}); + +$route->setMiddleware('\Demo\Middlewares\AuthMiddleware'); +$route->setNamespace('\Demo\Controllers'); +$route->setPrefix('v1'); + +/* Add the route to the router */ +$router->addRoute($route); +``` + +## Extending + +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 like loading a custom `routes.php` file or add debugging information etc. + +```php +namespace Demo; + +use Pecee\SimpleRouter\SimpleRouter; + +class Router extends SimpleRouter { + + public static function start() { + + // change this to whatever makes sense in your project + require_once 'routes.php'; + + // change default namespace for all routes + parent::setDefaultNamespace('\Demo\Controllers'); + + // Do initial stuff + parent::start(); + + } + +} +``` + +--- + +# Credits + ## Sites This is some sites that uses the simple-router project in production. @@ -683,18 +1050,9 @@ This is some sites that uses the simple-router project in production. - [bookandbegin.com](https://bookandbegin.com) - [dscuz.com](https://www.dscuz.com) -## Documentation -While I work on a better documentation, please refer to the Laravel 5 routing documentation here: +## License -http://laravel.com/docs/5.1/routing - -## Easily extendable -The router can be easily extended to customize your needs. - -## Ideas and issues -If you want a great new feature or experience any issues what-so-ever, please feel free to leave an issue and i'll look into it whenever possible. - -## The MIT License (MIT) +### The MIT License (MIT) Copyright (c) 2016 Simon Sessingø / simple-php-router diff --git a/src/Pecee/Http/Input/Input.php b/src/Pecee/Http/Input/Input.php index fa23eca..f14f2a2 100644 --- a/src/Pecee/Http/Input/Input.php +++ b/src/Pecee/Http/Input/Input.php @@ -189,11 +189,12 @@ class Input * * @param string $index * @param string|null $default + * @param string|null $method * @return InputItem|string */ - public function get($index, $default = null) + public function get($index, $default = null, $method = null) { - $input = $this->getObject($index, $default); + $input = $this->getObject($index, $default, $method); if ($input instanceof InputItem) { return (trim($input->getValue()) === '') ? $default : $input->getValue(); diff --git a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php index af58695..ba10ff8 100644 --- a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php +++ b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php @@ -20,7 +20,7 @@ class BaseCsrfVerifier implements IMiddleware $this->csrfToken = new CsrfToken(); // Generate or get the CSRF-Token from Cookie. - $this->token = (!$this->hasToken()) ? $this->generateToken() : $this->csrfToken->getToken(); + $this->token = ($this->hasToken() === false) ? $this->generateToken() : $this->csrfToken->getToken(); } /** @@ -58,7 +58,7 @@ class BaseCsrfVerifier implements IMiddleware public function handle(Request $request, ILoadableRoute &$route = null) { - if ($request->getMethod() !== 'get' && !$this->skip($request)) { + if (in_array($request->getMethod(), ['post', 'put', 'delete']) === true && $this->skip($request) === false) { $token = $request->getInput()->get(static::POST_KEY, null, 'post'); diff --git a/src/Pecee/Http/Request.php b/src/Pecee/Http/Request.php index 4b2a06c..584dbab 100644 --- a/src/Pecee/Http/Request.php +++ b/src/Pecee/Http/Request.php @@ -17,7 +17,7 @@ class Request $this->parseHeaders(); $this->host = $this->getHeader('http-host');; $this->uri = $this->getHeader('request-uri'); - $this->method = strtolower($this->getHeader('request-method')); + $this->method = $this->input->get('_method', strtolower($this->getHeader('request-method'))); $this->input = new Input($this); } diff --git a/src/Pecee/SimpleRouter/Route/RouteUrl.php b/src/Pecee/SimpleRouter/Route/RouteUrl.php index e7a73f6..23f47cf 100644 --- a/src/Pecee/SimpleRouter/Route/RouteUrl.php +++ b/src/Pecee/SimpleRouter/Route/RouteUrl.php @@ -19,8 +19,12 @@ class RouteUrl extends LoadableRoute // Match on custom defined regular expression if ($this->regex !== null) { $parameters = []; - if (preg_match('/(' . $this->regex . ')/is', $request->getHost() . $url, $parameters)) { - $this->parameters = (array)$parameters[0]; + if (preg_match($this->regex, $request->getHost() . $url, $parameters)) { + /* Remove global match */ + if(count($parameters) > 1) { + array_shift($parameters); + $this->parameters = $parameters; + } return true; }