Compare commits

...

50 Commits

Author SHA1 Message Date
Simon Sessingø da6b5af19f Merge pull request #157 from skipperbent/v2-development
Bugfixes
2016-11-19 09:47:19 +02:00
Simon Sessingø 808d59d3d3 Bugfixes 2016-11-19 08:46:48 +01:00
Simon Sessingø 914ec9d1b7 Merge pull request #155 from skipperbent/v2-development
Small optimisations
2016-11-19 07:02:23 +02:00
Simon Sessingø d1f33d9b01 Small optimisation 2016-11-19 06:01:53 +01:00
Simon Sessingø 5f72755a98 Merge pull request #153 from skipperbent/v2-development
V2 development
2016-11-19 06:25:08 +02:00
Simon Sessingø d4a04920b8 More development 2016-11-19 05:22:51 +01:00
Simon Sessingø 4e12cb8bc3 More changes 2016-11-19 05:06:47 +01:00
Simon Sessingø 8f33cc1a39 Development 2016-11-19 04:41:29 +01:00
Simon Sessingø ed1ac74e7a Development
- Fixed updatae causing middlewares to sometimes load on wrong routes.
- Converted project to PSR/2.
- Updated InputCollection class and added get method for easy access to values.
- Complete refactor of RouterBase.
- Added findRoute method to RouterBase.
- It's now possible to change parameter modifiers and symbol by overwriting properties on RouterBase.
- Added RouterUrlTest unit-test for testing route-urls.
- Added IRestController that can be easily implemented in custom ResourceController-classes.
- It's now possible to use "-" instead of "_" when using getHeader method in Request class.
- Added PHPDocs.
- Fixed "/" route sometimes returning "//" as url.
- Optimisations and bugfixes.
2016-11-19 02:48:19 +01:00
Simon Sessingø 41d15d3acd Merge pull request #151 from skipperbent/v2-development
Re-added missing methods from version 1.
2016-11-17 19:01:17 +02:00
Simon Sessingø a4447313f6 Re-added missing methods from version 1. 2016-11-17 17:55:34 +01:00
Simon Sessingø ded9c8ebe0 Merge pull request #149 from skipperbent/v2-development
Updated documentation
2016-11-17 17:33:49 +02:00
Simon Sessingø 99f869b57d Updated documentation 2016-11-17 16:33:27 +01:00
Simon Sessingø 394f7beb8b Merge pull request #147 from skipperbent/v2-development
V2 development
2016-11-17 17:25:30 +02:00
Simon Sessingø c94523740b Development
- Fixed ExceptionHandlers loading in reverse order.
- Fixed rewrite when modifying `Request` didn't work properly.
2016-11-17 16:24:24 +01:00
Simon Sessingø 305c0ab7c8 Removed clone from RouterBase 2016-11-17 15:17:27 +01:00
Simon Sessingø baab004482 Merge pull request #145 from skipperbent/v2-development
Added default null parameter value to RouterController and RouterResource
2016-11-17 16:04:38 +02:00
Simon Sessingø eb160ff7bb Added default null parameter value to RouterController and RouterResource 2016-11-17 15:03:18 +01:00
Simon Sessingø 00c0cad211 Merge pull request #143 from skipperbent/v2-development
Bugfix: set parameters to null when new url is set
2016-11-17 15:21:24 +02:00
Simon Sessingø 2db0601e20 - Fixed: set parameters to null when new url is set 2016-11-17 14:20:29 +01:00
Simon Sessingø c6e85676da Merge pull request #141 from skipperbent/v2-development
Bugfixes
2016-11-17 06:37:03 +02:00
Simon Sessingø c59ab12e1a Bugfixes 2016-11-17 05:36:44 +01:00
Simon Sessingø 5a74c9d27d Merge pull request #139 from skipperbent/v2-development
Development
2016-11-17 05:03:30 +02:00
Simon Sessingø b298665d33 Changed demo-project to use v2 router 2016-11-17 04:02:53 +01:00
Simon Sessingø 9ba531d559 Removed uncommented code 2016-11-17 04:00:59 +01:00
Simon Sessingø 73ee4521bc Bugfixes
- Array arguments are now longer automaticially merged.
- Added domain-route parameter unit-test.
2016-11-17 03:50:33 +01:00
Simon Sessingø b5f8d9410f Bugfixes 2016-11-17 03:17:11 +01:00
Simon Sessingø b5eef6f3ee Merge pull request #137 from skipperbent/v2-development
Bugfixes
2016-11-16 16:16:22 +02:00
Simon Sessingø 75566dc2ba Bugfixes 2016-11-16 15:15:51 +01:00
Simon Sessingø ad0eceb814 Merge pull request #135 from skipperbent/v2-development
Bugfixes
2016-11-16 15:32:32 +02:00
Simon Sessingø 8478899eb6 Bugfixes 2016-11-16 14:30:19 +01:00
Simon Sessingø 4bec2bc5fb Merge pull request #133 from skipperbent/v2-development
Csrf bugfixes
2016-11-15 09:46:54 +02:00
Simon Sessingø 93562bd758 Csrf bugfixes 2016-11-15 08:46:14 +01:00
Simon Sessingø 4f47463497 Merge pull request #131 from skipperbent/v2-development
V2 development
2016-11-15 08:13:09 +02:00
Simon Sessingø 9c413a3c53 Bugfixes 2016-11-15 06:38:39 +01:00
Simon Sessingø 669d318a12 Documentation updates 2016-11-15 05:57:58 +01:00
Simon Sessingø 9513e38009 Fixed to unit tests 2016-11-15 05:57:04 +01:00
Simon Sessingø a13bcd4768 Updated demo project + documentation 2016-11-15 05:48:08 +01:00
Simon Sessingø 83c73a4240 Callback, faking route and documentation updates 2016-11-15 05:44:24 +01:00
Simon Sessingø bc14790a67 Updated documentation 2016-11-15 03:55:28 +01:00
Simon Sessingø d5e7a13d89 Development + bugfixes 2016-11-15 03:43:26 +01:00
Simon Sessingø e5c86c1822 Merge pull request #129 from skipperbent/v2-development
Bugfixes
2016-11-15 01:12:41 +02:00
Simon Sessingø 6de0700e17 Bugfixes
- Removed debugging constructor.
- Updated documentation.
2016-11-15 00:11:57 +01:00
Simon Sessingø 32c305bd2c Merge pull request #124 from skipperbent/v2-development
V2 development
2016-11-08 18:22:49 +02:00
Simon Sessingø 28ffa30d3e Development
- all() in Input class now returns correct array.
- all() now supports json data.
- Minor bugfixes.
2016-11-08 18:21:21 +02:00
Simon Sessingø 540ebb31ac Updated documentation 2016-11-07 05:48:02 +01:00
Simon Sessingø 48317ded7a Updated documentation 2016-11-07 05:40:32 +01:00
Simon Sessingø 28c3370b67 Fixed group not adding multiple middlewares 2016-11-07 04:55:43 +01:00
Simon Sessingø 7ee42c98a7 Updated documentaiton 2016-11-07 04:45:18 +01:00
Simon Sessingø 8740db9582 Development
- Isolated Http classes from router.
- Removed getInstance from Request object - current router request should now be obtained through SimpleRouter::request().
- Fixed broken test cases.
- Added test for regular expression match.
- Updated documentation to reflect changes.
- Added more helper examples to documentation.
- Added helpers to demo-project.
- Optimisations.
2016-11-07 04:40:28 +01:00
42 changed files with 3093 additions and 2267 deletions
+145 -66
View File
@@ -18,7 +18,7 @@ The goal of this project is to create a router that is 100% compatible with the
### 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.
- Regular Expression Constraints for parameters. - Regular Expression Constraints for parameters.
- Named routes. - Named routes.
- Generating url to routes. - Generating url to routes.
@@ -29,8 +29,8 @@ The goal of this project is to create a router that is 100% compatible with the
- CSRF protection. - CSRF protection.
- Optional parameters - Optional parameters
- Sub-domain routing - Sub-domain routing
- Custom boot managers to redirect urls to other routes - Custom boot managers to rewrite urls to "nicer" ones.
- Input manager; to manage `GET`, `POST` params. - Input manager; easily manage `GET`, `POST` and `FILE` values.
## Installation and demo ## Installation and demo
@@ -49,13 +49,18 @@ This is an example of a basic ```index.php``` file:
```php ```php
use \Pecee\SimpleRouter\SimpleRouter; use \Pecee\SimpleRouter\SimpleRouter;
require_once 'routes.php'; // change this to whatever makes sense in your project // Load external routes file
require_once 'routes.php';
// The apps default namespace (so we don't have to specify it each time we use MyController@home) /*
$defaultControllerNamespace = 'MyWebsite\\Controller'; * The default namespace for route-callbacks, so we don't have to specify it each time.
* Can be overwritten by using the namespace config option.
*/
SimpleRouter::setDefaultNamespace('MyWebsite\Controller');
// Do the routing // Start the routing
SimpleRouter::start($defaultControllerNamespace); SimpleRouter::start();
``` ```
## Adding routes ## Adding routes
@@ -66,6 +71,7 @@ This router is heavily inspired by the Laravel 5.* router, so anything you find
- ExceptionsHandlers must implement the `IExceptionHandler` interface. - ExceptionsHandlers must implement the `IExceptionHandler` interface.
- Middlewares must implement the `IMiddleware` interface. - Middlewares must implement the `IMiddleware` interface.
- Resource controllers can inherit the `IRestController` interface, but is not required.
```php ```php
use Pecee\SimpleRouter\SimpleRouter; use Pecee\SimpleRouter\SimpleRouter;
@@ -81,7 +87,9 @@ use Pecee\SimpleRouter\SimpleRouter;
// Add CSRF support (if needed) // Add CSRF support (if needed)
SimpleRouter::csrfVerifier(new \Pecee\Http\Middleware\BaseCsrfVerifier()); SimpleRouter::csrfVerifier(new \Pecee\Http\Middleware\BaseCsrfVerifier());
SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\SomeMiddlewareClass'], function() { SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']);
SimpleRouter::group(['prefix' => '/v1', 'middleware' => '\MyWebsite\Middleware\SomeMiddlewareClass'], function() {
SimpleRouter::group(['prefix' => '/services', 'exceptionHandler' => '\MyProject\Handler\CustomExceptionHandler'], function() { SimpleRouter::group(['prefix' => '/services', 'exceptionHandler' => '\MyProject\Handler\CustomExceptionHandler'], function() {
@@ -96,7 +104,7 @@ SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\So
*/ */
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 (see IRestController interface for available methods)
SimpleRouter::resource('/rest', 'ControllerRessource'); SimpleRouter::resource('/rest', 'ControllerRessource');
// Load the entire controller (where url matches method names - getIndex(), postIndex() etc) // Load the entire controller (where url matches method names - getIndex(), postIndex() etc)
@@ -127,16 +135,22 @@ class CustomExceptionHandler implements IExceptionHandler {
// If the error-code is 404; show another route which contains the page-not-found // If the error-code is 404; show another route which contains the page-not-found
if($error->getCode() === 404) { if($error->getCode() === 404) {
// Load your custom 404-page view
// Throw your custom 404-page view
// - or -
// load another route with our 404 page
// - or -
// you can return the $request object to ignore the error and continue on rendering the route.
return $request->setUri(url('page.notfound'));
} }
// Output error as json if on api path. // Output error as json if on api path.
if(stripos($request->getUri(), '/api') !== false) { if(stripos($request->getUri(), '/api') !== false) {
response()->json(['error' => $error->getMessage()]); response()->json([ 'error' => $error->getMessage() ]);
} }
// Otherwise default exception will be thrown by the router. // Otherwise default exception will be thrown by the router.
} }
} }
@@ -190,33 +204,33 @@ use Pecee\SimpleRouter\SimpleRouter;
class Router extends SimpleRouter { class Router extends SimpleRouter {
public static function start($defaultNamespace = null) { public static function start() {
// change this to whatever makes sense in your project // change this to whatever makes sense in your project
require_once 'routes.php'; require_once 'routes.php';
// change default namespace for all routes
parent::setDefaultNamespace('\Demo');
// Do initial stuff // Do initial stuff
parent::start();
parent::start('\\Demo\\Controllers');
} }
} }
``` ```
#### Helper functions examples ## Helper functions
**This is a basic example of a helper function for generating urls.**
To simplify to use of simple-router functionality, we recommend you add these helper functions to your project.
```php ```php
use Pecee\SimpleRouter\RouterBase; use Pecee\SimpleRouter\SimpleRouter;
function url($controller, $parameters = null, $getParams = null) { function url($controller, $parameters = null, $getParams = null) {
RouterBase::getInstance()->getRoute($controller, $parameters, $getParams); SimpleRouter::getRoute($controller, $parameters, $getParams);
} }
```
**This is a basic example for getting the current csrf token**
```php
/** /**
* Get current csrf-token * Get current csrf-token
* @return null|string * @return null|string
@@ -225,6 +239,30 @@ function csrf_token() {
$token = new \Pecee\CsrfToken(); $token = new \Pecee\CsrfToken();
return $token->getToken(); return $token->getToken();
} }
/**
* Get request object
* @return \Pecee\Http\Request
*/
function request() {
return SimpleRouter::request();
}
/**
* Get response object
* @return \Pecee\Http\Response
*/
function response() {
return SimpleRouter::response();
}
/**
* Get input class
* @return \Pecee\Http\Input\Input
*/
function input() {
return SimpleRouter::request()->getInput();
}
``` ```
## Getting urls ## Getting urls
@@ -260,7 +298,10 @@ use Pecee\Http\Middleware\BaseCsrfVerifier;
class CsrfVerifier extends BaseCsrfVerifier { class CsrfVerifier extends BaseCsrfVerifier {
protected $except = ['/companies/*', '/user/save']; protected $except = [
'/companies/*',
'/api'
];
} }
``` ```
@@ -314,68 +355,122 @@ By doing this the route will now load the url ```/article/view/1``` instead of `
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. 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.
**routes.php example:**
```php
// Add new bootmanager
SimpleRouter::addBootManager(new CustomRouterRules());
// This rule is what our custom bootmanager will use.
SimpleRouter::get('/article/view/{id}', 'ControllerArticle@view');
```
## Easily overwrite route about to be loaded ## Easily overwrite route about to be loaded
Sometimes it can be useful to manipulate the route that's about to be loaded, for instance if a user is not authenticated or if an error occurred within your Middleware that requires Sometimes it can be useful to manipulate the route about to be loaded.
some other route to be initialised. Simple PHP Router allows you to easily change the route about to be executed. All information about the current route is stored in simple-php-router allows you to easily change the route about to be executed.
the ```\Pecee\SimpleRouter\Http\Request``` object. All information about the current route is stored in the ```\Pecee\SimpleRouter\RouterBase``` instance's `loadedRoute` property.
For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`.
**Note:** Please note that it's only possible to change the route BEFORE any route has initially been loaded, so doing this in your custom ExceptionHandler or Middleware is highly recommended.
```php ```php
$route = Request::getInstance()->getLoadedRoute(); use Pecee\SimpleRouter;
$route = SimpleRouter::router()->getLoadedRoute();
$route->setCallback('Example\MyCustomClass@hello'); $route->setCallback('Example\MyCustomClass@hello');
// -- or -- // -- or you can rewrite by doing --
$route->setClass('Example\MyCustomClass'); $route->setClass('Example\MyCustomClass');
$route->setMethod('hello'); $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 `RouterEntry` 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\RouterEntry;
class CustomMiddleware implements Middleware {
public function handle(Request $request, RouterEntry &$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\RouterEntry;
class CustomMiddleware implements Middleware {
public function handle(Request $request, RouterEntry &$route) {
$route->callback('DefaultController@home');
}
}
```
## Using the Input class to manage parameters ## Using the Input class to manage parameters
We've added the `Input` class to easy access parameters from your Controller-classes. We've added the `Input` class to easy access parameters from your Controller-classes.
**Return single parameter value (matches both GET, POST, FILE):** **Return single parameter value (matches both GET, POST, FILE):**
```php ```php
$value = Request::getInstance()->getInput()->get('name'); $value = input()->get('name');
``` ```
**Return parameter object (matches both GET, POST, FILE):** **Return parameter object (matches both GET, POST, FILE):**
```php ```php
$object = Request::getInstance()->getInput()->getObject('name'); $object = input()->getObject('name');
``` ```
**Return specific GET parameter (where name is the name of your parameter):** **Return specific GET parameter (where name is the name of your parameter):**
```php ```php
$object = Request::getInstance()->getInput()->get->name; $object = input()->get->name;
$object = Request::getInstance()->getInput()->post->name; $object = input()->post->name;
$object = Request::getInstance()->getInput()->file->name; $object = input()->file->name;
// -- or --
$object = input()->get->get($key, $defaultValue);
$object = input()->post->get($key, $defaultValue);
$object = input()->file->get($key, $defaultValue);
``` ```
**Return all parameters:** **Return all parameters:**
```php ```php
// Get all // Get all
$objects = Request::getInstance()->getInput()->all(); $values = input()->all();
// Only match certain keys // Only match certain keys
$objects = Request::getInstance()->getInput()->all([ $values = input()->all([
'company_name', 'company_name',
'user_id' 'user_id'
]); ]);
``` ```
All object inherits from `InputItem` class and will always contain these methods: All object inherits from `InputItem` class and will always contain these methods:
- `getValue()` - returns the value of the input. - `getValue()` - returns the value of the input.
- `getIndex()` - returns the index/key of the input. - `getIndex()` - returns the index/key of the input.
- `getName()` - returns a human friendly name for the input (company_name will be Company Name etc). - `getName()` - returns a human friendly name for the input (company_name will be Company Name etc).
@@ -388,27 +483,11 @@ All object inherits from `InputItem` class and will always contain these methods
- `getType()` - get mime-type for file. - `getType()` - get mime-type for file.
- `getError()` - get file upload error. - `getError()` - get file upload error.
Below example requires you to have the helper functions added. Please refer to the helper functions section in the documentation.
### Easy access your input
Create a helper function to easily get access to the input elements.
Example:
```php
/**
* Get input class
* @return \Pecee\Http\Input\Input
*/
function input() {
return \Pecee\Http\Request::getInstance()->getInput();
}
```
Then you can easily do something like this in your controller:
```php ```php
// Get parameter site_id or default-value 2 // Get parameter site_id or default-value 2
$value = input()->get('site_id', '2'); $siteId = input()->get('site_id', 2);
``` ```
## Sites ## Sites
+10 -16
View File
@@ -1,22 +1,16 @@
<?php <?php
namespace Demo\Controllers; namespace Demo\Controllers;
use Pecee\Http\Request; class ApiController
{
public function index()
{
// The variable authenticated is set to true in the ApiVerification middleware class.
header('content-type: application/json');
class ApiController { echo json_encode([
'authenticated' => request()->authenticated
public function index() { ]);
}
// The variable authenticated is set to true in the ApiVerification middleware class.
$request = Request::getInstance();
header('content-type: application/json');
echo json_encode([
'authenticated' => $request->authenticated
]);
}
} }
@@ -1,29 +1,27 @@
<?php <?php
namespace Demo\Controllers; namespace Demo\Controllers;
class DefaultController { class DefaultController
{
public function index()
{
// implement
echo sprintf('DefaultController -> index (?fun=%s)', input()->get('fun'));
}
public function index() { public function contact()
{
echo 'DefaultController -> contact';
}
// implement public function companies($id = null)
echo 'DefaultController -> index'; {
echo 'DefaultController -> companies -> id: ' . $id;
}
} public function notFound()
{
public function contact() { echo 'Page not found';
}
echo 'DefaultController -> contact';
}
public function companies($id = null) {
echo 'DefaultController -> companies -> id: ' . $id;
}
public function notFound() {
echo 'Page not found';
}
} }
@@ -5,30 +5,30 @@ use Pecee\Handler\IExceptionHandler;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry; use Pecee\SimpleRouter\RouterEntry;
class CustomExceptionHandler implements IExceptionHandler { class CustomExceptionHandler implements IExceptionHandler
{
public function handleError(Request $request, RouterEntry &$route = null, \Exception $error)
{
// Return json errors if we encounter an error on /api.
if (stripos($request->getUri(), '/api') !== false) {
header('content-type: application/json');
echo json_encode([
'error' => $error->getMessage(),
'code' => $error->getCode()
]);
die();
}
public function handleError( Request $request, RouterEntry $router = null, \Exception $error) { // else we just throw the error
if ($error->getCode() == 404) {
// Return json errors if we encounter an error on /api. // Return 404 path
if(stripos($request->getUri(), '/api') !== false) { $request->setUri('/404');
header('content-type: application/json'); return $request;
echo json_encode([
'error' => $error->getMessage(),
'code' => $error->getCode()
]);
die();
}
// else we just throw the error }
if($error->getCode() == 404) {
// Return 404 path throw $error;
$request->setUri('/404'); }
return $request;
}
throw $error;
}
} }
@@ -3,14 +3,16 @@ namespace Demo\Middlewares;
use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class ApiVerification implements IMiddleware { class ApiVerification implements IMiddleware
{
public function handle(Request $request, RouterEntry &$route)
{
// Do authentication
$request->authenticated = true;
public function handle(Request $request) { return $request;
}
// Do authentication
$request->authenticated = true;
}
} }
@@ -3,11 +3,11 @@ namespace Demo\Middlewares;
use Pecee\Http\Middleware\BaseCsrfVerifier; use Pecee\Http\Middleware\BaseCsrfVerifier;
class CsrfVerifier extends BaseCsrfVerifier { class CsrfVerifier extends BaseCsrfVerifier
{
/** /**
* CSRF validation will be ignored on the following urls. * CSRF validation will be ignored on the following urls.
*/ */
protected $except = ['/api/*']; protected $except = ['/api/*'];
} }
+12 -11
View File
@@ -1,25 +1,26 @@
<?php <?php
/** /**
* Custom router which handles default middlewares, default exceptions and things * Custom router which handles default middlewares, default exceptions and things
* that should be happen before and after the router is initialised. * that should be happen before and after the router is initialised.
*/ */
namespace Demo; namespace Demo;
use Pecee\SimpleRouter\SimpleRouter; use Pecee\SimpleRouter\SimpleRouter;
class Router extends SimpleRouter { class Router extends SimpleRouter
{
public static function start($defaultNamespace = null)
{
// Load our helpers
require_once 'helpers.php';
public static function start($defaultNamespace = null) { // Load our custom routes
require_once 'routes.php';
// change this to whatever makes sense in your project parent::setDefaultNamespace('\Demo');
require_once 'routes.php';
// Do initial stuff // Do initial stuff
parent::start();
parent::start('\\Demo\\Controllers'); }
}
} }
+44
View File
@@ -0,0 +1,44 @@
<?php
use Pecee\SimpleRouter\SimpleRouter;
function url($controller, $parameters = null, $getParams = null)
{
SimpleRouter::getRoute($controller, $parameters, $getParams);
}
/**
* Get current csrf-token
* @return null|string
*/
function csrf_token()
{
$token = new \Pecee\CsrfToken();
return $token->getToken();
}
/**
* Get request object
* @return \Pecee\Http\Request
*/
function request()
{
return SimpleRouter::request();
}
/**
* Get response object
* @return \Pecee\Http\Response
*/
function response()
{
return SimpleRouter::response();
}
/**
* Get input class
* @return \Pecee\Http\Input\Input
*/
function input()
{
return SimpleRouter::request()->getInput();
}
+10 -10
View File
@@ -7,17 +7,17 @@ use Demo\Router;
Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier()); Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
Router::group(['exceptionHandler' => 'Demo\Handlers\CustomExceptionHandler'], function() { Router::group(['exceptionHandler' => 'Demo\Handlers\CustomExceptionHandler'], function () {
Router::get('/', 'DefaultController@index')->setAlias('home'); Router::get('/', 'DefaultController@index')->setAlias('home');
Router::get('/contact', 'DefaultController@contact')->setAlias('contact'); Router::get('/contact', 'DefaultController@contact')->setAlias('contact');
Router::get('/404', 'DefaultController@notFound')->setAlias('404'); Router::get('/404', 'DefaultController@notFound')->setAlias('404');
Router::basic('/companies', 'DefaultController@companies')->setAlias('companies'); Router::basic('/companies', 'DefaultController@companies')->setAlias('companies');
Router::basic('/companies/{id}', 'DefaultController@companies')->setAlias('companies'); Router::basic('/companies/{id}', 'DefaultController@companies')->setAlias('companies');
// Api // Api
Router::group(['prefix' => '/api', 'middleware' => 'Demo\Middlewares\ApiVerification'], function() { Router::group(['prefix' => '/api', 'middleware' => 'Demo\Middlewares\ApiVerification'], function () {
Router::resource('/demo', 'ApiController'); Router::resource('/demo', 'ApiController');
}); });
}); });
+1 -1
View File
@@ -10,7 +10,7 @@
"type": "project", "type": "project",
"require": { "require": {
"php": ">=5.4.0", "php": ">=5.4.0",
"pecee/simple-router": "1.*" "pecee/simple-router": "2.*"
}, },
"require-dev": { "require-dev": {
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Pecee\Controller;
interface IRestController {
/**
* @return void
*/
function index();
/**
* @param mixed $id
* @return void
*/
function show($id);
/**
* @return void
*/
function store();
/**
* @return void
*/
function create();
/**
* View
* @param mixed $id
* @return void
*/
function edit($id);
/**
* @param mixed $id
* @return void
*/
function update($id);
/**
* @param mixed $id
* @return void
*/
function destroy($id);
}
+57 -51
View File
@@ -1,62 +1,68 @@
<?php <?php
namespace Pecee; namespace Pecee;
class CsrfToken { class CsrfToken
{
const CSRF_KEY = 'XSRF-TOKEN';
const CSRF_KEY = 'XSRF-TOKEN'; protected $token;
protected $token; /**
* Generate random identifier for CSRF token
*
* @return string
*/
public static function generateToken()
{
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
}
return bin2hex(openssl_random_pseudo_bytes(32));
}
/** /**
* Generate random identifier for CSRF token * Validate valid CSRF token
* @return string *
*/ * @param string $token
public static function generateToken() { * @return bool
if (function_exists('mcrypt_create_iv')) { */
return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); public function validate($token)
} {
return bin2hex(openssl_random_pseudo_bytes(32)); if ($token !== null && $this->getToken() !== null) {
} return hash_equals($token, $this->getToken());
}
return false;
}
/** /**
* Validate valid CSRF token * Set csrf token cookie
* *
* @param string $token * @param $token
* @return bool */
*/ public function setToken($token)
public function validate($token) { {
if($token !== null && $this->getToken() !== null) { setcookie(static::CSRF_KEY, $token, time() + 60 * 120, '/');
return hash_equals($token, $this->getToken()); }
}
return false;
}
/** /**
* Set csrf token cookie * Get csrf token
* * @return string|null
* @param $token */
*/ public function getToken()
public function setToken($token) { {
setcookie(static::CSRF_KEY, $token, time() + 60 * 120, '/'); if ($this->hasToken()) {
} return $_COOKIE[static::CSRF_KEY];
}
return null;
}
/** /**
* Get csrf token * Returns whether the csrf token has been defined
* @return string|null * @return bool
*/ */
public function getToken(){ public function hasToken()
if($this->hasToken()) { {
return $_COOKIE[static::CSRF_KEY]; return isset($_COOKIE[static::CSRF_KEY]);
} }
return null;
}
/**
* Returns whether the csrf token has been defined
* @return bool
*/
public function hasToken() {
return isset($_COOKIE[static::CSRF_KEY]);
}
} }
+4 -1
View File
@@ -1,3 +1,6 @@
<?php <?php
namespace Pecee\Exception; namespace Pecee\Exception;
class RouterException extends \Exception { }
class RouterException extends \Exception
{
}
@@ -1,4 +1,6 @@
<?php <?php
namespace Pecee\Exception; namespace Pecee\Exception;
class TokenMismatchException extends \Exception {} class TokenMismatchException extends \Exception
{
}
+9 -3
View File
@@ -4,8 +4,14 @@ namespace Pecee\Handler;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry; use Pecee\SimpleRouter\RouterEntry;
interface IExceptionHandler { interface IExceptionHandler
{
public function handleError(Request $request, RouterEntry $router = null, \Exception $error); /**
* @param Request $request
* @param RouterEntry|null $route
* @param \Exception $error
* @return Request|null
*/
public function handleError(Request $request, RouterEntry &$route = null, \Exception $error);
} }
+164 -148
View File
@@ -3,193 +3,209 @@ namespace Pecee\Http\Input;
use Pecee\Http\Request; use Pecee\Http\Request;
class Input { class Input
{
/**
* @var \Pecee\Http\Input\InputCollection
*/
public $get;
/** /**
* @var \Pecee\Http\Input\InputCollection * @var \Pecee\Http\Input\InputCollection
*/ */
public $get; public $post;
/** /**
* @var \Pecee\Http\Input\InputCollection * @var \Pecee\Http\Input\InputCollection
*/ */
public $post; public $file;
/** /**
* @var \Pecee\Http\Input\InputCollection * @var Request
*/ */
public $file; protected $request;
public function __construct() { public function __construct(Request $request)
$this->setGet(); {
$this->setPost(); $this->request = $request;
$this->setFile(); $this->setGet();
} $this->setPost();
$this->setFile();
}
/** /**
* Get all get/post items * Get all get/post items
* @param array|null $filter Only take items in filter * @param array|null $filter Only take items in filter
* @return array * @return array
*/ */
public function all(array $filter = null) { public function all(array $filter = null)
$output = $this->get->getData(); {
$output = array_merge($output, $this->post->getData()); $output = $_POST;
if($filter !== null) { if ($this->request->getMethod() === 'post') {
$tmp = array();
foreach($output as $key => $val) {
if(in_array($key, $filter)) {
$tmp[$key] = $val;
}
}
return $tmp;
}
return $output; $contents = file_get_contents('php://input');
}
public function getObject($index, $default = null) { if (stripos(trim($contents), '{') === 0) {
$key = (strpos($index, '[') > -1) ? substr($index, strpos($index, '[')+1, strpos($index, ']') - strlen($index)) : null; $output = json_decode($contents, true);
$index = (strpos($index, '[') > -1) ? substr($index, 0, strpos($index, '[')) : $index; if ($output === false) {
$output = array();
}
}
}
$element = $this->get->findFirst($index); $output = array_merge($_GET, $output);
if($element !== null) { if ($filter !== null) {
return ($key !== null) ? $element[$key] : $element; $output = array_filter($output, function ($key) use ($filter) {
} if (in_array($key, $filter)) {
return true;
}
if(Request::getInstance()->getMethod() !== 'get') { return false;
}, ARRAY_FILTER_USE_KEY);
}
$element = $this->post->findFirst($index); return $output;
}
if ($element !== null) { public function getObject($index, $default = null)
return ($key !== null) ? $element[$key] : $element; {
} $key = (strpos($index, '[') > -1) ? substr($index, strpos($index, '[') + 1, strpos($index, ']') - strlen($index)) : null;
$index = (strpos($index, '[') > -1) ? substr($index, 0, strpos($index, '[')) : $index;
$element = $this->file->findFirst($index); $element = $this->get->findFirst($index);
if ($element !== null) {
return ($key !== null) ? $element[$key] : $element;
}
}
return $default; if ($element !== null) {
} return ($key !== null) ? $element[$key] : $element;
}
/** if ($this->request->getMethod() !== 'get') {
* Get input element value matching index
* @param string $index
* @param string|null $default
* @return string|null
*/
public function get($index, $default = null) {
$item = $this->getObject($index); $element = $this->post->findFirst($index);
if ($element !== null) {
return ($key !== null) ? $element[$key] : $element;
}
if($item !== null) { $element = $this->file->findFirst($index);
if ($element !== null) {
return ($key !== null) ? $element[$key] : $element;
}
}
if(is_array($item) || $item instanceof InputFile) { return $default;
return $item; }
}
return (trim($item->getValue()) === '') ? $default : $item->getValue(); /**
} * Get input element value matching index
* @param string $index
* @param string|null $default
* @return string|null
*/
public function get($index, $default = null)
{
$item = $this->getObject($index);
return $default; if ($item !== null) {
}
public function exists($index) { if ($item instanceof InputCollection || $item instanceof InputFile) {
return ($this->getObject($index) !== null); return $item;
} }
public function setGet() { return (trim($item->getValue()) === '') ? $default : $item->getValue();
$this->get = new InputCollection(); }
if(count($_GET)) { return $default;
foreach($_GET as $key => $get) { }
if(!is_array($get)) {
$this->get->{$key} = new InputItem($key, $get);
continue;
}
$output = array(); public function exists($index)
{
return ($this->getObject($index) !== null);
}
foreach($get as $k => $g) { public function setGet()
$output[$k] = new InputItem($k, $g); {
} $this->get = new InputCollection();
$this->get->{$key} = $output; if (count($_GET) > 0) {
} foreach ($_GET as $key => $get) {
} if (is_array($get) === false) {
} $this->get->{$key} = new InputItem($key, $get);
continue;
}
public function setPost() { $output = new InputCollection();
$this->post = new InputCollection();
$postVars = array(); foreach ($get as $k => $g) {
$output->{$k} = new InputItem($k, $g);
}
if(isset($_SERVER['REQUEST_METHOD']) && in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'PATCH', 'DELETE'])) { $this->get->{$key} = $output;
parse_str(file_get_contents('php://input'), $postVars); }
} else { }
$postVars = $_POST; }
}
if(count($postVars)) { public function setPost()
{
$this->post = new InputCollection();
foreach($postVars as $key => $post) { $postVars = $_POST;
if(!is_array($post)) {
$this->post->{strtolower($key)} = new InputItem($key, $post);
continue;
}
$output = array(); if (in_array($this->request->getMethod(), ['put', 'patch', 'delete']) === true) {
parse_str(file_get_contents('php://input'), $postVars);
}
foreach($post as $k=>$p) { if (count($postVars) > 0) {
$output[$k] = new InputItem($k, $p);
}
$this->post->{strtolower($key)} = $output; foreach ($postVars as $key => $post) {
} if (is_array($post) === false) {
} $this->post->{strtolower($key)} = new InputItem($key, $post);
} continue;
}
public function setFile() { $output = new InputCollection();
$this->file = new InputCollection();
if(count($_FILES)) { foreach ($post as $k => $p) {
foreach($_FILES as $key => $value) { $output->{$k} = new InputItem($k, $p);
// Multiple files }
if(!is_array($value['name'])) {
// Strip empty values
if($value['error'] != '4') {
$file = new InputFile($key);
$file->setName($value['name']);
$file->setSize($value['size']);
$file->setType($value['type']);
$file->setTmpName($value['tmp_name']);
$file->setError($value['error']);
$this->file->{strtolower($key)} = $file;
}
continue;
}
$output = array(); $this->post->{strtolower($key)} = $output;
}
}
}
foreach($value['name'] as $k=>$val) { public function setFile()
// Strip empty values {
if($value['error'][$k] != '4') { $this->file = new InputCollection();
$file = new InputFile($k);
$file->setName($value['name'][$k]);
$file->setSize($value['size'][$k]);
$file->setType($value['type'][$k]);
$file->setTmpName($value['tmp_name'][$k]);
$file->setError($value['error'][$k]);
$output[$k] = $file;
}
}
$this->file->{strtolower($key)} = $output; if (count($_FILES) > 0) {
} foreach ($_FILES as $key => $values) {
}
} // Handle array input
if (is_array($values['name']) === false && trim($values['error']) !== '4') {
$values['index'] = $key;
$this->file->{strtolower($key)} = InputFile::createFromArray($values);
continue;
}
$output = new InputCollection();
foreach ($values['name'] as $k => $val) {
if (trim($val['error'][$k]) !== '4') {
$output->{$k} = InputFile::createFromArray([
'index' => $k,
'error' => $val['error'][$k],
'tmp_name' => $val['tmp_name'][$k],
'type' => $val['type'][$k],
'size' => $val['size'][$k],
'name' => $val['name'][$k]
]);
}
}
$this->file->{strtolower($key)} = $output;
}
}
}
} }
+79 -52
View File
@@ -1,67 +1,94 @@
<?php <?php
namespace Pecee\Http\Input; namespace Pecee\Http\Input;
class InputCollection implements \IteratorAggregate { class InputCollection implements \IteratorAggregate
{
protected $data = array();
protected $data = array(); /**
* Search for input element matching index.
*
* @param string $index
* @param string|null $default
* @return InputItem|mixed
*/
public function findFirst($index, $default = null)
{
if (count($this->data) > 0) {
/** if (isset($this->data[$index])) {
* Search for input element matching index. return $this->data[$index];
* Useful for searching for finding items where $index doesn't contain form name. }
*
* @param string $index
* @return mixed
*/
public function findFirst($index) {
if(count($this->data)) {
if(isset($this->data[$index])) { foreach ($this->data as $key => $input) {
return $this->data[$index]; if (strtolower($index) === strtolower($key)) {
} return $input;
}
}
}
foreach($this->data as $key => $value) { return $default;
if(strtolower($index) === strtolower($key)) { }
return $value;
}
}
}
return null; /**
} * Get input element value matching index
*
* @param string $index
* @param string|null $default
* @return string|null
*/
public function get($index, $default = null)
{
$input = $this->findFirst($index);
/** if($input !== null && trim($input->getValue()) !== '') {
* @param $index return $input->getValue();
* @throws \InvalidArgumentException }
* @return InputItem
*/
public function __get($index) {
$item = $this->findFirst($index);
// Ensure that item are always available
if($item === null) {
$this->data[$index] = new InputItem($index, null);
return $this->data[$index];
}
return $item; return $default;
} }
public function __set($index, $value) { public function getValue($index, $default = null) {
$this->data[$index] = $value;
}
public function getData() { }
return $this->data;
}
/** /**
* Retrieve an external iterator * @param string $index
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php * @throws \InvalidArgumentException
* @return \Traversable An instance of an object implementing <b>Iterator</b> or * @return InputItem
* <b>Traversable</b> */
* @since 5.0.0 public function __get($index)
*/ {
public function getIterator() { $item = $this->findFirst($index);
return new \ArrayIterator($this->data); // Ensure that item are always available
} if ($item === null) {
$this->data[$index] = new InputItem($index, null);
return $this->data[$index];
}
return $item;
}
public function __set($index, $value)
{
$this->data[$index] = $value;
}
public function getData()
{
return $this->data;
}
/**
* Retrieve an external iterator
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return \Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator()
{
return new \ArrayIterator($this->data);
}
} }
+95 -27
View File
@@ -1,68 +1,136 @@
<?php <?php
namespace Pecee\Http\Input; namespace Pecee\Http\Input;
class InputFile extends InputItem { class InputFile extends InputItem
{
protected $name; public $size;
protected $size; public $type;
protected $type; public $error;
protected $error; public $tmpName;
protected $tmpName;
/** /**
* @return string * @return string
*/ */
public function getSize() { public function getSize()
{
return $this->size; return $this->size;
} }
/** /**
* @return string * @return string
*/ */
public function getType() { public function getType()
{
return $this->type; return $this->type;
} }
/** /**
* @return string * @return string
*/ */
public function getError() { public function getError()
{
return $this->error; return $this->error;
} }
public function getMime()
{
return $this->getType();
}
/** /**
* @return string * @return string
*/ */
public function getTmpName() { public function getTmpName()
{
return $this->tmpName; return $this->tmpName;
} }
public function getExtension() { public function getExtension()
{
return pathinfo($this->getName(), PATHINFO_EXTENSION); return pathinfo($this->getName(), PATHINFO_EXTENSION);
} }
public function move($destination) { public function move($destination)
{
return move_uploaded_file($this->tmpName, $destination); return move_uploaded_file($this->tmpName, $destination);
} }
public function getContents() { public function getContents()
{
return file_get_contents($this->tmpName); return file_get_contents($this->tmpName);
} }
public function setTmpName($name) { /**
$this->tmpName = $name; * Set file temp. name
} * @param string $name
* @return static $this
*/
public function setTmpName($name)
{
$this->tmpName = $name;
return $this;
}
public function setSize($size) { /**
$this->size = $size; * Set file size
} * @param int $size
* @return static $this
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
public function setType($type) { /**
$this->type = $type; * Set type
} * @param string $type
* @return static $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
public function setError($error) { /**
$this->error = $error; * Set error
} * @param int $error
* @return static $this
*/
public function setError($error)
{
$this->error = $error;
return $this;
}
/**
* Create from array
* @param array $values
* @return static
*/
public static function createFromArray(array $values)
{
if(!isset($values['index'])) {
throw new \InvalidArgumentException('Index key is required');
}
$input = new static($values['index']);
$input->setTmpName((isset($values['error']) ? $values['error'] : null));
$input->setName((isset($values['name']) ? $values['name'] : null));
$input->setSize((isset($values['size']) ? $values['size'] : null));
$input->setType((isset($values['type']) ? $values['type'] : null));
$input->setError((isset($values['tmp_name']) ? $values['tmp_name'] : null));
return $input;
}
/**
* @return string
*/
public function getValue()
{
return $this->tmpName;
}
} }
+57 -50
View File
@@ -1,63 +1,70 @@
<?php <?php
namespace Pecee\Http\Input; namespace Pecee\Http\Input;
class InputItem { class InputItem
{
public $index;
public $name;
public $value;
protected $index; public function __construct($index, $value = null)
protected $name; {
protected $value; $this->index = $index;
$this->value = $value;
public function __construct($index, $value = null) { // Make the name human friendly, by replace _ with space
$this->index = $index; $this->name = ucfirst(str_replace('_', ' ', $this->index));
$this->value = $value; }
// Make the name human friendly, by replace _ with space /**
$this->name = ucfirst(str_replace('_', ' ', $this->index)); * @return string
} */
public function getName()
{
return $this->name;
}
/** /**
* @return array * @return string
*/ */
public function getName() { public function getValue()
return $this->name; {
} return $this->value;
}
/** /**
* @return array * @return string
*/ */
public function getValue() { public function getIndex()
return $this->value; {
} return $this->index;
}
/** /**
* @return string * Set input name
*/ * @param string $name
public function getIndex() { * @return static $this
return $this->index; */
} public function setName($name)
{
$this->name = $name;
return $this;
}
/** /**
* Set input name * Set input value
* @param string $name * @param string $value
* @return static $this * @return static $this
*/ */
public function setName($name) { public function setValue($value)
$this->name = $name; {
return $this; $this->value = $value;
} return $this;
}
/** public function __toString()
* Set input value {
* @param string $value return (string)$this->value;
* @return static $this }
*/
public function setValue($value) {
$this->value = $value;
return $this;
}
public function __toString() {
return (string)$this->getValue();
}
} }
+68 -62
View File
@@ -4,86 +4,92 @@ namespace Pecee\Http\Middleware;
use Pecee\CsrfToken; use Pecee\CsrfToken;
use Pecee\Exception\TokenMismatchException; use Pecee\Exception\TokenMismatchException;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class BaseCsrfVerifier implements IMiddleware { class BaseCsrfVerifier implements IMiddleware
{
const POST_KEY = 'csrf-token';
const HEADER_KEY = 'X-CSRF-TOKEN';
const POST_KEY = 'csrf-token'; protected $except;
const HEADER_KEY = 'X-CSRF-TOKEN'; protected $csrfToken;
protected $token;
protected $except; public function __construct()
protected $csrfToken; {
protected $token; $this->csrfToken = new CsrfToken();
public function __construct() { // Generate or get the CSRF-Token from Cookie.
$this->csrfToken = new CsrfToken(); $this->token = (!$this->hasToken()) ? $this->generateToken() : $this->csrfToken->getToken();
}
// Generate or get the CSRF-Token from Cookie. /**
$this->token = (!$this->hasToken()) ? $this->generateToken() : $this->csrfToken->getToken(); * Check if the url matches the urls in the except property
} * @param Request $request
* @return bool
*/
protected function skip(Request $request)
{
if ($this->except === null || is_array($this->except) === false) {
return false;
}
/** foreach ($this->except as $url) {
* Check if the url matches the urls in the except property $url = rtrim($url, '/');
* @param Request $request if ($url[strlen($url) - 1] === '*') {
* @return bool $url = rtrim($url, '*');
*/ $skip = (stripos($request->getUri(), $url) === 0);
protected function skip(Request $request) { } else {
$skip = ($url === rtrim($request->getUri(), '/'));
}
if($this->except === null || !is_array($this->except)) { if ($skip) {
return false; return true;
} }
}
foreach($this->except as $url) { return false;
$url = rtrim($url, '/'); }
if($url[strlen($url)-1] === '*') {
$url = rtrim($url, '*');
$skip = (stripos($request->getUri(), $url) === 0);
} else {
$skip = ($url === rtrim($request->getUri(), '/'));
}
if($skip) { public function handle(Request $request, RouterEntry &$route = null)
return true; {
}
}
return false; if ($request->getMethod() !== 'get' && !$this->skip($request)) {
}
public function handle(Request $request) { $token = $request->getInput()->post->get(static::POST_KEY);
if($request->getMethod() != 'get' && !$this->skip($request)) { // If the token is not posted, check headers for valid x-csrf-token
if ($token === null) {
$token = $request->getHeader(static::HEADER_KEY);
}
$token = (isset($_POST[static::POST_KEY])) ? $_POST[static::POST_KEY] : null; if (!$this->csrfToken->validate($token)) {
throw new TokenMismatchException('Invalid csrf-token.');
}
// If the token is not posted, check headers for valid x-csrf-token }
if($token === null) {
$token = $request->getHeader(static::HEADER_KEY);
}
if( !$this->csrfToken->validate($token) ) { }
throw new TokenMismatchException('Invalid csrf-token.');
}
} public function generateToken()
{
$token = $this->csrfToken->generateToken();
$this->csrfToken->setToken($token);
return $token;
}
} public function hasToken()
{
if ($this->token != null) {
return true;
}
public function generateToken() { return $this->csrfToken->hasToken();
$token = $this->csrfToken->generateToken(); }
$this->csrfToken->setToken($token);
return $token;
}
public function hasToken() { public function getToken()
if($this->token != null) { {
return true; return $this->token;
} }
return $this->csrfToken->hasToken();
}
public function getToken() {
return $this->token;
}
} }
+10 -2
View File
@@ -2,7 +2,15 @@
namespace Pecee\Http\Middleware; namespace Pecee\Http\Middleware;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
interface IMiddleware
{
/**
* @param Request $request
* @param RouterEntry $route
* @return Request|null
*/
public function handle(Request $request, RouterEntry &$route);
interface IMiddleware {
public function handle(Request $request);
} }
+174 -154
View File
@@ -3,184 +3,204 @@ namespace Pecee\Http;
use Pecee\Http\Input\Input; use Pecee\Http\Input\Input;
class Request { class Request
{
protected $data = array();
protected $headers;
protected $host;
protected $uri;
protected $method;
protected $input;
protected static $instance; public function __construct()
{
$this->parseHeaders();
$this->input = new Input($this);
$this->host = $this->getHeader('http-host');;
$this->uri = $this->getHeader('request-uri');
$this->method = strtolower($this->input->post->findFirst('_method', $this->getHeader('request-method')));
}
protected $data; protected function parseHeaders()
{
$this->headers = array();
/** foreach ($_SERVER as $name => $value) {
* Return new instance $this->headers[strtolower($name)] = $value;
* @return static $this->headers[strtolower(str_replace('_', '-', $name))] = $value;
*/ }
public static function getInstance() { }
if(self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
public function __construct() { public function isSecure()
$this->data = array(); {
$this->host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : array(); if ($this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443) {
$this->uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : array(); return true;
$this->method = (isset($_POST['_method'])) ? strtolower($_POST['_method']) : (isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : array()); }
$this->headers = $this->getAllHeaders();
$this->input = new Input();
}
protected function getAllHeaders() { return false;
$headers = array(); }
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) === 'HTTP_') {
$headers[strtolower(str_replace('_', '-', substr($name, 5)))] = $value;
}
}
return $headers;
}
public function getIsSecure() { /**
if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') { * @return string
return true; */
} public function getUri()
return isset($_SERVER['HTTPS']) ? true : (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === 443); {
} return $this->uri;
}
/** /**
* @return string * @return string
*/ */
public function getUri() { public function getHost()
return $this->uri; {
} return $this->host;
}
/** /**
* @return string * @return string
*/ */
public function getHost() { public function getMethod()
return $this->host; {
} return $this->method;
}
/** /**
* @return string * Get http basic auth user
*/ * @return string|null
public function getMethod() { */
return $this->method; public function getUser()
} {
return $this->getHeader('php-auth-user');
}
/** /**
* Get http basic auth user * Get http basic auth password
* @return string|null * @return string|null
*/ */
public function getUser() { public function getPassword()
return (isset($_SERVER['PHP_AUTH_USER'])) ? $_SERVER['PHP_AUTH_USER']: null; {
} return $this->getHeader('php-auth-pw');
}
/** /**
* Get http basic auth password * Get all headers
* @return string|null * @return array
*/ */
public function getPassword() { public function getHeaders()
return (isset($_SERVER['PHP_AUTH_PW'])) ? $_SERVER['PHP_AUTH_PW']: null; {
} return $this->headers;
}
/** /**
* Get headers * Get id address
* @return array * @return string
*/ */
public function getHeaders() { public function getIp()
return $this->headers; {
} if ($this->getHeader('http-cf-connecting-ip') !== null) {
return $this->getHeader('http-cf-connecting-ip');
}
/** if ($this->getHeader('http-x-forwarded-for') !== null && strlen($this->getHeader('http-x-forwarded-for'))) {
* Get id address return $this->getHeader('http-x-forwarded_for');
* @return string }
*/
public function getIp() {
if(isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
return ((isset($_SERVER['HTTP_X_FORWARDED_FOR']) && strlen($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null);
}
/** return $this->getHeader('remote-addr');
* Get referer }
* @return string
*/
public function getReferer() {
return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
}
/** /**
* Get user agent * Get referer
* @return string * @return string
*/ */
public function getUserAgent() { public function getReferer()
return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; {
} return $this->getHeader('http-referer');
}
/** /**
* Get header value by name * Get user agent
* @param string $name * @return string
* @return string|null */
*/ public function getUserAgent()
public function getHeader($name) { {
return (isset($this->headers[strtolower($name)])) ? $this->headers[strtolower($name)] : null; return $this->getHeader('http-user-agent');
} }
/** /**
* Get input class * Get header value by name
* @return Input *
*/ * @param string $name
public function getInput() { * @param object|null $defaultValue
return $this->input; *
} * @return string|null
*/
public function getHeader($name, $defaultValue = null)
{
return isset($this->headers[strtolower($name)]) ? $this->headers[strtolower($name)] : $defaultValue;
}
public function isFormatAccepted($format) { /**
return (isset($_SERVER['HTTP_ACCEPT']) && stripos($_SERVER['HTTP_ACCEPT'], $format) > -1); * Get input class
} * @return Input
*/
public function getInput()
{
return $this->input;
}
public function getAcceptFormats() { /**
if(isset($_SERVER['HTTP_ACCEPT'])) { * Is format accepted
return explode(',', $_SERVER['HTTP_ACCEPT']); *
} * @param string $format
return array(); *
} * @return bool
*/
public function isFormatAccepted($format)
{
return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) > -1);
}
public function __set($name, $value = null) { /**
$this->data[$name] = $value; * Get accept formats
} * @return array
*/
public function getAcceptFormats()
{
return explode(',', $this->getHeader('http-accept'));
}
public function __get($name) { /**
return isset($this->data[$name]) ? $this->data[$name] : null; * @param string $uri
} */
public function setUri($uri)
{
$this->uri = $uri;
}
/** /**
* Get the currently loaded route. * @param string $host
* @return \Pecee\SimpleRouter\RouterEntry */
*/ public function setHost($host)
public function getLoadedRoute() { {
return $this->loadedRoute; $this->host = $host;
} }
/** /**
* @param mixed $uri * @param string $method
*/ */
public function setUri($uri) { public function setMethod($method)
$this->uri = $uri; {
} $this->method = $method;
}
/** public function __set($name, $value = null)
* @param mixed $host {
*/ $this->data[$name] = $value;
public function setHost($host) { }
$this->host = $host;
}
/** public function __get($name)
* @param mixed $method {
*/ return isset($this->data[$name]) ? $this->data[$name] : null;
public function setMethod($method) { }
$this->method = $method;
}
} }
+99 -84
View File
@@ -1,103 +1,118 @@
<?php <?php
namespace Pecee\Http; namespace Pecee\Http;
class Response { class Response
{
protected $request;
/** public function __construct(Request $request)
* Set the http status code {
* $this->request = $request;
* @param int $code }
* @return self $this
*/
public function httpCode($code) {
http_response_code($code);
return $this;
}
/** /**
* Redirect the response * Set the http status code
* *
* @param string $url * @param int $code
* @param int $httpCode * @return static
*/ */
public function redirect($url, $httpCode = null) { public function httpCode($code)
if($httpCode !== null) { {
$this->httpCode($httpCode); http_response_code($code);
} return $this;
}
$this->header('location: ' . $url); /**
die(); * Redirect the response
} *
* @param string $url
* @param int $httpCode
*/
public function redirect($url, $httpCode = null)
{
if ($httpCode !== null) {
$this->httpCode($httpCode);
}
public function refresh() { $this->header('location: ' . $url);
$this->redirect(Request::getInstance()->getUri()); die();
} }
/** public function refresh()
* Add http authorisation {
* @param string $name $this->redirect($this->request->getUri());
* @return self $this }
*/
public function auth($name = '') {
$this->headers([
'WWW-Authenticate: Basic realm="' . $name . '"',
'HTTP/1.0 401 Unauthorized'
]);
return $this;
}
public function cache($eTag, $lastModified = 2592000) { /**
* Add http authorisation
* @param string $name
* @return static
*/
public function auth($name = '')
{
$this->headers([
'WWW-Authenticate: Basic realm="' . $name . '"',
'HTTP/1.0 401 Unauthorized'
]);
return $this;
}
$this->headers([ public function cache($eTag, $lastModified = 2592000)
'Cache-Control: public', {
'Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT',
'Etag: ' . $eTag
]);
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $lastModified || $this->headers([
isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $eTag) { 'Cache-Control: public',
'Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT',
'Etag: ' . $eTag
]);
$this->headers([ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $lastModified ||
'HTTP/1.1 304 Not Modified' isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $eTag
]); ) {
exit(); $this->headers([
} 'HTTP/1.1 304 Not Modified'
]);
return $this; exit();
} }
/** return $this;
* Json encode array }
* @param array $value
*/
public function json(array $value) {
$this->header('Content-type: application/json');
echo json_encode($value);
die();
}
/** /**
* Add header to response * Json encode array
* @param string $value * @param array $value
* @return self $this */
*/ public function json(array $value)
public function header($value) { {
header($value); $this->header('Content-type: application/json');
return $this; echo json_encode($value);
} die();
}
/** /**
* Add multiple headers to response * Add header to response
* @param array $headers * @param string $value
* @return self $this * @return static
*/ */
public function headers(array $headers) { public function header($value)
foreach($headers as $header) { {
header($header); header($value);
} return $this;
return $this; }
}
/**
* Add multiple headers to response
* @param array $headers
* @return static
*/
public function headers(array $headers)
{
foreach ($headers as $header) {
header($header);
}
return $this;
}
} }
@@ -0,0 +1,13 @@
<?php
namespace Pecee\SimpleRouter;
interface IControllerRoute
{
public function getController();
public function setController($controller);
public function getMethod();
public function setMethod($method);
}
@@ -0,0 +1,9 @@
<?php
namespace Pecee\SimpleRouter;
interface ILoadableRoute
{
public function getUrl();
public function setUrl($url);
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace Pecee\SimpleRouter;
abstract class LoadableRoute extends RouterEntry implements ILoadableRoute
{
const PARAMETERS_REGEX_MATCH = '%s([\w\-\_]*?)\%s{0,1}%s';
const PARAMETER_MODIFIERS = '{}';
const PARAMETER_OPTIONAL_SYMBOL = '?';
protected $url;
protected $alias;
public function getUrl()
{
return $this->url;
}
/**
* Set url
*
* @param string $url
* @return static
*/
public function setUrl($url)
{
$this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/';
$regex = sprintf(static::PARAMETERS_REGEX_MATCH, static::PARAMETER_MODIFIERS[0], static::PARAMETER_OPTIONAL_SYMBOL, static::PARAMETER_MODIFIERS[1]);
if (preg_match_all('/' . $regex . '/is', $this->url, $matches)) {
foreach ($matches[1] as $key) {
$this->parameters[$key] = null;
}
}
return $this;
}
/**
* Get alias for the url which can be used when getting the url route.
* @return string|array
*/
public function getAlias()
{
return $this->alias;
}
/**
* Check if route has given alias.
*
* @param string $name
* @return bool
*/
public function hasAlias($name)
{
if ($this->getAlias() !== null) {
if (is_array($this->getAlias()) === true) {
foreach ($this->getAlias() as $alias) {
if (strtolower($alias) === strtolower($name)) {
return true;
}
}
}
return strtolower($this->getAlias()) === strtolower($name);
}
return false;
}
/**
* Set the url alias for easier getting the url route.
* @param string|array $alias
* @return static
*/
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
/**
* Merge with information from another route.
*
* @param array $values
* @return static
*/
public function merge(array $values)
{
// Change as to alias
if (isset($values['as'])) {
$this->setAlias($values['as']);
}
parent::merge($values);
return $this;
}
}
+505 -430
View File
@@ -5,482 +5,557 @@ use Pecee\Exception\RouterException;
use Pecee\Handler\IExceptionHandler; use Pecee\Handler\IExceptionHandler;
use Pecee\Http\Middleware\BaseCsrfVerifier; use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\Http\Response;
class RouterBase
{
/**
* @var static
*/
protected static $instance;
/**
* Current request
* @var Request
*/
protected $request;
/**
* Response
* @var Response
*/
protected $response;
/**
* Used to keep track of whether or not a should should be added to
* the backstack-list for group-processing or not.
* @var bool
*/
protected $processingRoute;
/**
* All added routes
* @var array
*/
protected $routes;
/**
* List of
* @var array
*/
protected $controllerUrlMap;
/**
* Backstack array used to keep track of sub-routes
* @var array
*/
protected $backStack;
/**
* List of added bootmanagers
* @var array
*/
protected $bootManagers;
/**
* Csrf verifier class
* @var BaseCsrfVerifier
*/
protected $csrfVerifier;
/**
* Get exception handlers
* @var array
*/
protected $exceptionHandlers;
/**
* The current loaded route
* @var RouterRoute|null
*/
protected $loadedRoute;
/**
* List over route changes (to avoid endless-looping)
* @var array
*/
protected $routeRewrites = [];
/**
* If the route has been rewritten/changed this property will contain the original url.
* @var string
*/
protected $originalUrl;
/**
* Get current router instance
* @return static
*/
public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function __construct()
{
$this->reset();
}
public function reset()
{
$this->processingRoute = false;
$this->request = new Request();
$this->response = new Response($this->request);
$this->routes = array();
$this->bootManagers = array();
$this->backStack = array();
$this->controllerUrlMap = array();
$this->exceptionHandlers = array();
}
/**
* Add route
* @param RouterEntry $route
* @return RouterEntry
*/
public function addRoute(RouterEntry $route)
{
if ($this->processingRoute) {
$this->backStack[] = $route;
} else {
$this->routes[] = $route;
}
return $route;
}
protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), RouterEntry $parent = null)
{
// Loop through each route-request
/* @var $route RouterEntry */
foreach ($routes as $route) {
$newPrefixes = $prefixes;
$newSettings = $settings;
if($parent !== null) {
$route->setParent($parent);
}
if ($route instanceof RouterGroup) {
if ($route->getCallback() !== null && is_callable($route->getCallback())) {
$this->processingRoute = true;
$route->renderRoute($this->request);
$this->processingRoute = false;
if ($route->matchRoute($this->request)) {
// Add ExceptionHandler
if (count($route->getExceptionHandlers()) > 0) {
$this->exceptionHandlers = array_merge($route->getExceptionHandlers(), $this->exceptionHandlers);
}
}
}
}
if ($route instanceof RouterGroup) {
$newPrefixes[] = trim($route->getPrefix(), '/');
$newSettings = array_merge($settings, $route->toArray());
} else {
if (count($settings)) {
$route->merge($settings);
}
}
if ($route instanceof ILoadableRoute) {
if (count($prefixes)) {
$route->setUrl(trim(join('/', $prefixes) . $route->getUrl(), '/'));
}
$this->controllerUrlMap[] = $route;
}
if (count($this->backStack) > 0) {
$backStack = $this->backStack;
$this->backStack = [];
// Route any routes added to the backstack
$this->processRoutes($backStack, $newSettings, $newPrefixes, $route);
}
}
}
public function routeRequest($rewrite = false)
{
$this->loadedRoute = null;
$routeNotAllowed = false;
try {
// Initialize boot-managers
if (count($this->bootManagers) > 0) {
/* @var $manager RouterBootManager */
foreach ($this->bootManagers as $manager) {
$this->request = $manager->boot($this->request);
if (!($this->request instanceof Request)) {
throw new RouterException('Custom router bootmanager "' . get_class($manager) . '" must return instance of Request.');
}
}
}
if ($rewrite === false) {
class RouterBase { // Loop through each route-request
$this->processRoutes($this->routes);
protected static $instance; if ($this->csrfVerifier !== null) {
protected $request; // Verify csrf token for request
protected $currentRoute; $this->csrfVerifier->handle($this->request);
protected $routes; }
protected $processedRoutes;
protected $controllerUrlMap;
protected $backStack;
protected $defaultNamespace;
protected $bootManagers;
protected $baseCsrfVerifier;
protected $exceptionHandlers;
// TODO: clean up - cut some of the methods down to smaller pieces $this->originalUrl = $this->request->getUri();
}
public function __construct() { /* @var $route RouterEntry */
$this->request = Request::getInstance(); foreach ($this->controllerUrlMap as $route) {
$this->routes = array();
$this->backStack = array();
$this->controllerUrlMap = array();
$this->bootManagers = array();
$this->exceptionHandlers = array();
}
public function addRoute(RouterEntry $route) { if ($route->matchRoute($this->request)) {
if($this->currentRoute !== null) {
$this->backStack[] = $route;
} else {
$this->routes[] = $route;
}
}
protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), $backStack = false, $group = null) { if (count($route->getRequestMethods()) > 0 && !in_array($this->request->getMethod(), $route->getRequestMethods())) {
// Loop through each route-request $routeNotAllowed = true;
continue;
}
$routesCount = count($routes); $this->loadedRoute = $route;
$mergedSettings = array(); $this->loadedRoute->loadMiddleware($this->request, $this->loadedRoute);
/* @var $route RouterEntry */ if ($this->request->getUri() !== $this->originalUrl && !in_array($this->request->getUri(), $this->routeRewrites)) {
for($i = 0; $i < $routesCount; $i++) { $this->routeRewrites[] = $this->request->getUri();
$this->routeRequest(true);
return;
}
$route = $routes[$i]; $routeNotAllowed = false;
$this->request->setUri($this->originalUrl);
$this->loadedRoute->renderRoute($this->request);
$route->addSettings($settings); break;
}
}
if($backStack) { } catch (\Exception $e) {
$route->setGroup($group); $this->handleException($e);
} }
if($this->defaultNamespace && !$route->getNamespace()) { if ($routeNotAllowed) {
$namespace = $this->defaultNamespace; $this->handleException(new RouterException('Route or method not allowed', 403));
if ($route->getNamespace()) { }
$namespace .= '\\' . $route->getNamespace();
}
$route->setNamespace($namespace); if ($this->loadedRoute === null) {
} $this->handleException(new RouterException(sprintf('Route not found: %s', $this->request->getUri()), 404));
}
}
$newPrefixes = $prefixes; protected function handleException(\Exception $e)
{
/* @var $handler IExceptionHandler */
foreach ($this->exceptionHandlers as $handler) {
if($route->getPrefix() && trim($route->getPrefix(), '/') !== '') { $handler = new $handler();
array_push($newPrefixes, trim($route->getPrefix(), '/'));
}
/* @var $group RouterGroup */ if (!($handler instanceof IExceptionHandler)) {
$group = null; throw new RouterException('Exception handler must implement the IExceptionHandler interface.');
}
if(!($route instanceof RouterGroup)) { $request = $handler->handleError($this->request, $this->loadedRoute, $e);
if(is_array($newPrefixes) && count($newPrefixes) && $backStack) {
$route->setUrl( '/' . join('/', $newPrefixes) . $route->getUrl() );
}
$this->controllerUrlMap[] = $route; if ($request !== null && $request->getUri() !== $this->originalUrl && !in_array($request->getUri(), $this->routeRewrites)) {
} $this->routeRewrites[] = $request->getUri();
$this->routeRequest(true);
return;
}
$this->currentRoute = $route; }
if($route instanceof RouterGroup && is_callable($route->getCallback())) { throw $e;
}
$route->renderRoute($this->request); public function arrayToParams(array $getParams = null, $includeEmpty = true)
{
if (is_array($getParams) === true && count($getParams) > 0) {
if($route->matchRoute($this->request)) { if ($includeEmpty === false) {
$getParams = array_filter($getParams, function ($item) {
return (!empty($item));
});
}
$group = $route; return '?' . http_build_query($getParams);
}
$mergedSettings = array_merge($settings, $group->getMergeableSettings()); return '';
}
// Add ExceptionHandler protected function processUrl(LoadableRoute $route, $method = null, $parameters = null, $getParams = null)
if ($group->getExceptionHandler() !== null) { {
$this->exceptionHandlers[] = $route; $domain = '';
} $parent = $route->getParent();
} $parameters = (array)$parameters;
} if ($parent !== null && $parent instanceof RouterGroup && count($parent->getDomains()) > 0) {
$domain = $parent->getDomains();
$domain = '//' . $domain[0];
}
$this->currentRoute = null; $url = $domain . '/' . trim($route->getUrl(), '/');
if(count($this->backStack)) { if ($route instanceof IControllerRoute && $method !== null) {
$backStack = $this->backStack;
$this->backStack = array();
// Route any routes added to the backstack $url .= '/' . $method . '/';
$this->processRoutes($backStack, $mergedSettings, $newPrefixes, true, $group);
}
}
}
public function routeRequest($original = true) { if (count($parameters) > 0) {
$url .= join('/', (array)$parameters);
}
$originalUri = $this->request->getUri(); } else {
$routeNotAllowed = false; if ($parameters !== null && count($parameters) > 0) {
$params = array_merge($route->getParameters(), (array)$parameters);
} else {
$params = $route->getParameters();
}
try { $otherParams = array();
// Initialize boot-managers foreach ($params as $param => $value) {
if(count($this->bootManagers)) { $value = (isset($parameters[$param])) ? $parameters[$param] : $value;
/* @var $manager RouterBootManager */
foreach($this->bootManagers as $manager) {
$this->request = $manager->boot($this->request);
if(!($this->request instanceof Request)) { $param1 = LoadableRoute::PARAMETER_MODIFIERS[0] . $param . LoadableRoute::PARAMETER_MODIFIERS[1];
throw new RouterException('Custom router bootmanager "'. get_class($manager) .'" must return instance of Request.'); $param2 = LoadableRoute::PARAMETER_MODIFIERS[0] . $param . LoadableRoute::PARAMETER_OPTIONAL_SYMBOL . LoadableRoute::PARAMETER_MODIFIERS[1];
}
}
}
// Loop through each route-request if (stripos($url, $param1) !== false || stripos($url, $param) !== false) {
$this->processRoutes($this->routes); $url = str_ireplace([$param1, $param2], $value, $url);
} else {
$otherParams[$param] = $value;
}
}
if($original === true) { $url = rtrim($url, '/') . '/' . join('/', $otherParams);
// Verify csrf token for request }
if ($this->baseCsrfVerifier !== null) {
$this->baseCsrfVerifier->handle($this->request);
}
}
$max = count($this->controllerUrlMap); $url = rtrim($url, '/') . '/';
/* @var $route RouterEntry */ if ($getParams !== null) {
for ($i = 0; $i < $max; $i++) { $url .= $this->arrayToParams($getParams);
}
$route = $this->controllerUrlMap[$i]; return $url;
}
$routeMatch = $route->matchRoute($this->request); /**
* Find route by alias, class, callback or method.
*
* @param string $query
* @return LoadableRoute|null
*/
public function findRoute($query)
{
/* @var $route LoadableRoute */
foreach ($this->controllerUrlMap as $route) {
if ($routeMatch) { // Check an alias exist, if the matches - use it
// Matches either Router alias or controller alias.
if ($route->hasAlias($query)) {
return $route;
}
if (count($route->getRequestMethods()) && !in_array($this->request->getMethod(), $route->getRequestMethods())) { // Direct match to controller
$routeNotAllowed = true; if ($route instanceof IControllerRoute) {
continue; if (strtolower($route->getController()) === strtolower($query)) {
} return $route;
}
}
$routeNotAllowed = false; // Using @ is most definitely a controller@method or alias@method
if (strpos($query, '@') !== false) {
list($controller, $method) = array_map('strtolower', explode('@', $query));
$this->request->rewrite_uri = $this->request->uri; if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
$this->request->setUri($originalUri); return $route;
}
$this->request->loadedRoute = $route; }
$route->loadMiddleware($this->request);
// Use callback if it's not a function
$this->request->loadedRoute->renderRoute($this->request); if (strpos($query, '@') !== false && strpos($route->getCallback(), '@') !== false && !is_callable($route->getCallback())) {
break; if (strtolower($query) === strtolower($route->getClass())) {
} return $route;
} }
} catch(\Exception $e) { if (strtolower($route->getCallback()) === strtolower($query) || strpos($route->getCallback(), $query) === 0) {
$this->handleException($e); return $route;
} }
if($routeNotAllowed) { }
$this->handleException(new RouterException('Route or method not allowed', 403)); }
}
return null;
if(!$this->request->loadedRoute) { }
$this->handleException(new RouterException(sprintf('Route not found: %s', $this->request->getUri()), 404));
} public function getRoute($controller = null, $parameters = null, $getParams = null)
} {
if ($getParams !== null && is_array($getParams) === false) {
protected function handleException(\Exception $e) { throw new \InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
$request = null;
// Return current route if no options has been specified
/* @var $route RouterGroup */ if ($controller === null && $parameters === null) {
foreach ($this->exceptionHandlers as $route) {
$route->loadMiddleware($this->request); $getParams = ($getParams !== null) ? $getParams : $_GET;
$handler = $route->getExceptionHandler(); $url = parse_url($this->request->getUri(), PHP_URL_PATH) . $this->arrayToParams($getParams);
$handler = new $handler();
return $url;
if (!($handler instanceof IExceptionHandler)) { }
throw new RouterException('Exception handler must implement the IExceptionHandler interface.');
} // If nothing is defined and a route is loaded we use that
if ($controller === null && $this->loadedRoute !== null) {
$request = $handler->handleError($this->request, $this->request->loadedRoute, $e); return $this->processUrl($this->loadedRoute, $this->loadedRoute->getMethod(), $parameters, $getParams);
} }
if($request !== null) { $route = $this->findRoute($controller);
$this->request = $request;
$this->routeRequest(false); if ($route !== null) {
return; return $this->processUrl($route, $route->getMethod(), $parameters, $getParams);
} }
throw $e; // Using @ is most definitely a controller@method or alias@method
} if (stripos($controller, '@') !== false) {
list($controller, $method) = explode('@', $controller);
/**
* @return string /* @var $route LoadableRoute */
*/ foreach ($this->controllerUrlMap as $route) {
public function getDefaultNamespace(){
return $this->defaultNamespace; if ($route->hasAlias($controller)) {
} return $this->processUrl($route, $method, $parameters, $getParams);
}
/**
* @param string $defaultNamespace // Match controllers either by: "alias @ method" or "controller@method"
* @return static if ($route instanceof IControllerRoute && strtolower($route->getController()) === strtolower($controller)) {
*/ return $this->processUrl($route, $method, $parameters, $getParams);
public function setDefaultNamespace($defaultNamespace) { }
$this->defaultNamespace = $defaultNamespace;
return $this; }
} }
/** $url = [($controller === null) ? '/' : $controller];
* @return array
*/ if ($parameters !== null && count($parameters) > 0) {
public function getBootManagers() { $url = array_merge($url, (array)$parameters);
return $this->bootManagers; }
}
$url = '/' . trim(join('/', $url), '/') . '/';
/**
* @param array $bootManagers if ($getParams !== null) {
*/ $url .= $this->arrayToParams($getParams);
public function setBootManagers(array $bootManagers) { }
$this->bootManagers = $bootManagers;
} return $url;
}
public function addBootManager(RouterBootManager $bootManager) {
$this->bootManagers[] = $bootManager; /**
} * Get bootmanagers
* @return array
/** */
* @return RouterEntry public function getBootManagers()
*/ {
public function getLoadedRoute() { return $this->bootManagers;
if(!($this->request->loadedRoute instanceof RouterGroup)) { }
return $this->request->loadedRoute;
} /**
return null; * Set bootmanagers
} * @param array $bootManagers
*/
/** public function setBootManagers(array $bootManagers)
* @return array {
*/ $this->bootManagers = $bootManagers;
public function getBackstack() { }
return $this->backStack;
} /**
* Add bootmanager
/** * @param RouterBootManager $bootManager
* @return RouterEntry */
*/ public function addBootManager(RouterBootManager $bootManager)
public function getCurrentRoute(){ {
return $this->currentRoute; $this->bootManagers[] = $bootManager;
} }
/** /**
* @return array * @return array
*/ */
public function getRoutes(){ public function getRoutes()
return $this->routes; {
} return $this->routes;
}
/**
* Get current request /**
* * Get current request
* @return Request *
*/ * @return Request
public function getRequest() { */
return $this->request; public function getRequest()
} {
return $this->request;
/** }
* Get base csrf verifier class
* @return BaseCsrfVerifier /**
*/ * Get response
public function getBaseCsrfVerifier() { * @return Response
return $this->baseCsrfVerifier; */
} public function getResponse()
{
/** return $this->response;
* Set base csrf verifier class }
*
* @param BaseCsrfVerifier $baseCsrfVerifier /**
* @return self * Get csrf verifier class
*/ * @return BaseCsrfVerifier
public function setBaseCsrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) { */
$this->baseCsrfVerifier = $baseCsrfVerifier; public function getCsrfVerifier()
return $this; {
} return $this->csrfVerifier;
}
public function arrayToParams(array $getParams = null, $includeEmpty = true) {
/**
if(is_array($getParams) && count($getParams)) { * Set csrf verifier class
if ($includeEmpty === false) { *
$getParams = array_filter($getParams, function ($item) { * @param BaseCsrfVerifier $csrfVerifier
return (!empty($item)); * @return static
}); */
} public function setCsrfVerifier(BaseCsrfVerifier $csrfVerifier)
{
return '?' . http_build_query($getParams); $this->csrfVerifier = $csrfVerifier;
} return $this;
}
return '';
} /**
* Get loaded route
protected function processUrl(RouterRoute $route, $method = null, $parameters = null, $getParams = null) { * @return RouterRoute|null
*/
$domain = ''; public function getLoadedRoute()
{
if($route->getGroup() !== null && $route->getGroup()->getDomain() !== null) { return $this->loadedRoute;
if(is_array($route->getGroup()->getDomain())) { }
$domains = $route->getGroup()->getDomain();
$domain = array_shift($domains);
} else {
$domain = $route->getGroup()->getDomain();
}
$domain = '//' . $domain;
}
$url = $domain . '/' . trim($route->getUrl(), '/');
if(($route instanceof RouterController || $route instanceof RouterResource) && $method !== null) {
$url .= $method;
}
if($route instanceof RouterController || $route instanceof RouterResource) {
if(count($parameters)) {
$url .= join('/', $parameters);
}
} else {
/* @var $route RouterEntry */
if(is_array($parameters)) {
$params = array_merge($route->getParameters(), $parameters);
} else {
$params = $route->getParameters();
}
$otherParams = [];
$i = 0;
foreach($params as $param => $value) {
$value = (isset($parameters[$param])) ? $parameters[$param] : $value;
if(stripos($url, '{' . $param. '}') !== false || stripos($url, '{' . $param . '?}') !== false) {
$url = str_ireplace(array('{' . $param . '}', '{' . $param . '?}'), $value, $url);
} else {
$otherParams[$param] = $value;
}
$i++;
}
$url = rtrim($url, '/') . '/' . join('/', $otherParams);
}
$url = rtrim($url, '/') . '/';
if($getParams !== null) {
$url .= $this->arrayToParams($getParams);
}
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');
}
// Return current route if no options has been specified
if($controller === null && $parameters === null) {
$getParams = (is_array($getParams)) ? array_merge($_GET, $getParams) : $_GET;
$url = parse_url($this->request->getUri(), PHP_URL_PATH);
if($getParams !== null) {
$url .= $this->arrayToParams($getParams);
}
return $url;
}
if($controller === null && $this->request->loadedRoute !== null) {
return $this->processUrl($this->request->loadedRoute, $this->request->loadedRoute->getMethod(), $parameters, $getParams);
}
$c = '';
$method = null;
$max = count($this->controllerUrlMap);
/* @var $route RouterRoute */
for($i = 0; $i < $max; $i++) {
$route = $this->controllerUrlMap[$i];
// Check an alias exist, if the matches - use it
if($route instanceof RouterRoute && $route->hasAlias($controller)) {
return $this->processUrl($route, $route->getMethod(), $parameters, $getParams);
}
if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) {
$c = $route->getCallback();
} else if($route instanceof RouterController || $route instanceof RouterResource) {
$c = $route->getController();
}
if($c === $controller || strpos($c, $controller) === 0) {
return $this->processUrl($route, $route->getMethod(), $parameters, $getParams);
}
}
$c = '';
// No match has yet been found, let's try to guess what url that should be returned
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) {
$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);
}
}
$controller = ($controller === null) ? '/' : $controller;
$url = array($controller);
if(is_array($parameters)) {
foreach($parameters as $key => $value) {
array_push($url,$value);
}
}
$url = '/' . trim(join('/', $url), '/') . '/';
if($getParams !== null) {
$url .= $this->arrayToParams($getParams);
}
return $url;
}
public static function getInstance() {
if(static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
} }
+3 -4
View File
@@ -3,8 +3,7 @@ namespace Pecee\SimpleRouter;
use Pecee\Http\Request; use Pecee\Http\Request;
abstract class RouterBootManager { abstract class RouterBootManager
{
abstract public function boot(Request $request); abstract public function boot(Request $request);
} }
+80 -92
View File
@@ -4,119 +4,107 @@ namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException; use Pecee\Exception\RouterException;
use Pecee\Http\Request; use Pecee\Http\Request;
class RouterController extends RouterEntry { class RouterController extends LoadableRoute implements IControllerRoute
{
protected $defaultMethod = 'index';
protected $controller;
protected $method;
const DEFAULT_METHOD = 'index'; public function __construct($url, $controller)
{
$this->setUrl($url);
$this->controller = $controller;
}
protected $url; public function renderRoute(Request $request)
protected $controller; {
protected $method; if ($this->getCallback() !== null && is_callable($this->getCallback())) {
public function __construct($url, $controller) { // When the callback is a function
parent::__construct(); call_user_func_array($this->getCallback(), $this->getParameters());
$this->url = $url; } else {
$this->controller = $controller; // When the callback is a method
} $controller = explode('@', $this->getCallback());
$className = $this->getNamespace() . '\\' . $controller[0];
public function renderRoute(Request $request) { $class = $this->loadClass($className);
if(is_object($this->getCallback()) && is_callable($this->getCallback())) { $method = $request->getMethod() . ucfirst($controller[1]);
// When the callback is a function if (!method_exists($class, $method)) {
call_user_func_array($this->getCallback(), $this->getParameters()); throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
} else { }
// When the callback is a method
$controller = explode('@', $this->getCallback());
$className = $this->getNamespace() . '\\' . $controller[0];
$class = $this->loadClass($className); call_user_func_array(array($class, $method), $this->getParameters());
$method = $request->getMethod() . ucfirst($controller[1]);
if (!method_exists($class, $method)) { return $class;
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 null;
}
return $class; public function matchRoute(Request $request)
} {
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
return null; if (strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
}
public function matchRoute(Request $request) { $strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$url = parse_url(urldecode($request->getUri()));
$url = rtrim($url['path'], '/') . '/';
if(strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) { $path = explode('/', $strippedUrl);
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/'); if (count($path) > 0) {
$path = explode('/', $strippedUrl); $method = (!isset($path[0]) || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $method;
if(count($path)) { array_shift($path);
$this->parameters = $path;
$method = (!isset($path[0]) || trim($path[0]) === '') ? self::DEFAULT_METHOD : $path[0]; // Set callback
$this->method = $method; $this->setCallback($this->controller . '@' . $this->method);
array_shift($path); return true;
$this->parameters = $path; }
}
// Set callback return null;
$this->setCallback($this->controller . '@' . $this->method); }
return true; /**
} * @return string
} */
return null; public function getController()
} {
return $this->controller;
}
/** /**
* @return string * @param string $controller
*/ * @return static
public function getUrl() { */
return $this->url; public function setController($controller)
} {
$this->controller = $controller;
return $this;
}
/** /**
* @param string $url * @return string
* @return static */
*/ public function getMethod()
public function setUrl($url) { {
$url = rtrim($url, '/') . '/'; return $this->method;
$this->url = $url; }
return $this;
}
/** /**
* @return string * @param string $method
*/ * @return static
public function getController() { */
return $this->controller; public function setMethod($method)
} {
$this->method = $method;
/** return $this;
* @param string $controller }
* @return static
*/
public function setController($controller) {
$this->controller = $controller;
return $this;
}
/**
* @return string
*/
public function getMethod() {
return $this->method;
}
/**
* @param string $method
* @return static
*/
public function setMethod($method) {
$this->method = $method;
return $this;
}
} }
+433 -411
View File
@@ -1,420 +1,442 @@
<?php <?php
namespace Pecee\SimpleRouter; namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException; use Pecee\Exception\RouterException;
use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request; use Pecee\Http\Request;
abstract class RouterEntry { abstract class RouterEntry
{
const REQUEST_TYPE_POST = 'post'; const REQUEST_TYPE_GET = 'get';
const REQUEST_TYPE_GET = 'get'; const REQUEST_TYPE_POST = 'post';
const REQUEST_TYPE_PUT = 'put'; const REQUEST_TYPE_PUT = 'put';
const REQUEST_TYPE_PATCH = 'patch'; const REQUEST_TYPE_PATCH = 'patch';
const REQUEST_TYPE_DELETE = 'delete'; const REQUEST_TYPE_OPTIONS = 'options';
const REQUEST_TYPE_DELETE = 'delete';
public static $allowedRequestTypes = array(
self::REQUEST_TYPE_DELETE, public static $requestTypes = [
self::REQUEST_TYPE_GET, self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST, self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT, self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH self::REQUEST_TYPE_PATCH,
); self::REQUEST_TYPE_OPTIONS,
self::REQUEST_TYPE_DELETE,
protected $settings; ];
protected $callback;
protected $parent;
public function __construct() { protected $callback;
$this->settings = array();
$this->settings['requestMethods'] = array(); protected $namespace;
$this->settings['where'] = array(); protected $defaultNamespace;
$this->settings['parameters'] = array(); protected $regex;
} protected $requestMethods = array();
protected $where = array();
/** protected $parameters = array();
* Returns callback name/identifier for the current route based on the callback. protected $middlewares = array();
* Useful if you need to get a unique identifier for the loaded route, for instance
* when using translations etc. protected function loadClass($name)
* {
* @return string if (!class_exists($name)) {
*/ throw new RouterException(sprintf('Class %s does not exist', $name));
public function getIdentifier() { }
if(strpos($this->callback, '@') !== false) {
return $this->callback; return new $name();
} }
return 'function_' . md5($this->callback);
} protected function parseParameters($route, $url, $parameterRegex = '[\w]+')
{
/** $parameterNames = array();
* @param string $callback $regex = '';
* @return self; $lastCharacter = '';
*/ $isParameter = false;
public function setCallback($callback) { $parameter = '';
$this->callback = $callback;
return $this; $routeLength = strlen($route);
} for ($i = 0; $i < $routeLength; $i++) {
/** $character = $route[$i];
* @return mixed
*/ if ($character === '{') {
public function getCallback() { // Remove "/" and "\" from regex
return $this->callback; if (substr($regex, strlen($regex) - 1) === '/') {
} $regex = substr($regex, 0, strlen($regex) - 2);
}
public function getMethod() {
if(strpos($this->callback, '@') !== false) { $isParameter = true;
$tmp = explode('@', $this->callback); } elseif ($isParameter && $character === '}') {
return $tmp[1]; $required = true;
}
return null; // Check for optional parameter and use custom parameter regex if it exists
} if (is_array($this->where) === true && isset($this->where[$parameter])) {
$parameterRegex = $this->where[$parameter];
public function getClass() { }
if(strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback); if ($lastCharacter === '?') {
return $tmp[0]; $parameter = substr($parameter, 0, strlen($parameter) - 1);
} $regex .= '(?:\/?(?P<' . $parameter . '>' . $parameterRegex . ')[^\/]?)?';
return null; $required = false;
} } else {
$regex .= '\/?(?P<' . $parameter . '>' . $parameterRegex . ')[^\/]?';
public function setMethod($method) { }
$this->callback = sprintf('%s@%s', $this->getClass(), $method);
return $this; $parameterNames[] = [
} 'name' => $parameter,
'required' => $required
public function setClass($class) { ];
$this->callback = sprintf('%s@%s', $class, $this->getMethod());
return $this; $parameter = '';
} $isParameter = false;
} elseif ($isParameter) {
/** $parameter .= $character;
* @param string $prefix } elseif ($character === '/') {
* @return self $regex .= '\\' . $character;
*/ } else {
public function setPrefix($prefix) { $regex .= str_replace('.', '\\.', $character);
$this->prefix = '/' . ltrim($prefix, '/'); }
return $this;
} $lastCharacter = $character;
}
/**
* @param string $middleware $parameterValues = array();
* @return self
*/ if (preg_match('/^' . $regex . '\/?$/is', $url, $parameterValues)) {
public function setMiddleware($middleware) {
$this->middleware = $middleware; $parameters = array();
return $this;
} foreach ($parameterNames as $name) {
$parameterValue = isset($parameterValues[$name['name']]) ? $parameterValues[$name['name']] : null;
/**
* @param string $namespace if ($name['required'] && $parameterValue === null) {
* @return self throw new RouterException('Missing required parameter ' . $name['name'], 404);
*/ }
public function setNamespace($namespace) {
$this->namespace = $namespace; if ($name['required'] === false && $parameterValue === null) {
return $this; continue;
} }
/** $parameters[$name['name']] = $parameterValue;
* @return string }
*/
public function getPrefix() { return $parameters;
return $this->prefix; }
}
return null;
/** }
* @return string|array
*/ public function loadMiddleware(Request $request, RouterEntry &$route)
public function getMiddleware() { {
return $this->middleware; if (count($this->getMiddlewares()) > 0) {
} foreach ($this->getMiddlewares() as $middleware) {
/** $middleware = $this->loadClass($middleware);
* @return string if (!($middleware instanceof IMiddleware)) {
*/ throw new RouterException($middleware . ' must be instance of Middleware');
public function getNamespace() { }
return $this->namespace;
} /* @var $class IMiddleware */
$middleware->handle($request, $route);
/**
* @return array }
*/ }
public function getSettings() { }
return $this->settings;
} public function renderRoute(Request $request)
{
/** if ($this->getCallback() !== null && is_callable($this->getCallback())) {
* @return mixed
*/ // When the callback is a function
public function getParameters(){ call_user_func_array($this->getCallback(), $this->getParameters());
return ($this->parameters === null) ? array() : $this->parameters;
} } else {
/** // When the callback is a method
* @param mixed $parameters $controller = explode('@', $this->getCallback());
* @return self $className = $this->getNamespace() . '\\' . $controller[0];
*/
public function setParameters($parameters) { $class = $this->loadClass($className);
$this->parameters = $parameters; $method = $controller[1];
return $this;
} if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
/** }
* Add regular expression parameter match
* $parameters = array_filter($this->getParameters(), function ($var) {
* @param array $options return ($var !== null);
* @return self });
*/
public function where(array $options) { call_user_func_array(array($class, $method), $parameters);
$this->where = array_merge($this->where, $options);
return $this; return $class;
} }
/** return null;
* Add regular expression match for url }
*
* @param string $regex /**
* @return self * 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
public function match($regex) { * when using translations etc.
$this->regexMatch = $regex; *
return $this; * @return string
} */
public function getIdentifier()
/** {
* Get settings that are allowed to be inherited by child routes. if (strpos($this->callback, '@') !== false) {
* return $this->callback;
* @return array }
*/ return 'function_' . md5($this->callback);
public function getMergeableSettings() { }
$settings = $this->settings;
/**
if(isset($settings['prefix'])) { * Set allowed request methods
unset($settings['prefix']); *
} * @param array $methods
* @return static $this
return $settings; */
} public function setRequestMethods(array $methods)
{
/** $this->requestMethods = $methods;
* @param array $settings return $this;
* @return self }
*/
public function addSettings(array $settings = null) { /**
if(is_array($settings)) { * Get allowed request methods
$this->settings = array_merge($this->settings, $settings); * @return array
} */
return $this; public function getRequestMethods()
} {
return $this->requestMethods;
/** }
* @param array $settings
* @return self /**
*/ * @return RouterEntry
public function setSettings($settings) { */
$this->settings = $settings; public function getParent()
{
if(isset($settings['prefix'])) { return $this->parent;
$this->setPrefix($settings['prefix']); }
}
/**
return $this; * Set parent route
} *
* @param RouterEntry $parent
/** * @return static $this
* Dynamically access settings value */
* public function setParent(RouterEntry $parent)
* @param $name {
* @return mixed|null $this->parent = $parent;
*/ return $this;
public function __get($name) { }
return (isset($this->settings[$name]) ? $this->settings[$name] : null);
} /**
* @param string $callback
/** * @return static
* Dynamically set settings value */
* public function setCallback($callback)
* @param string $name {
* @param mixed|null $value $this->callback = $callback;
*/ return $this;
public function __set($name, $value = null) { }
$this->settings[$name] = $value;
} /**
* @return mixed
protected function loadClass($name) { */
if(!class_exists($name)) { public function getCallback()
throw new RouterException(sprintf('Class %s does not exist', $name)); {
} return $this->callback;
}
return new $name();
} public function getMethod()
{
protected function parseParameters($route, $url, $parameterRegex = '[\w]+') { if (strpos($this->callback, '@') !== false) {
$parameterNames = array(); $tmp = explode('@', $this->callback);
$regex = ''; return $tmp[1];
$lastCharacter = ''; }
$isParameter = false; return null;
$parameter = ''; }
$routeLength = strlen($route); public function getClass()
for($i = 0; $i < $routeLength; $i++) { {
if (strpos($this->callback, '@') !== false) {
$character = $route[$i]; $tmp = explode('@', $this->callback);
return $tmp[0];
if($character === '{') { }
// Remove "/" and "\" from regex return null;
if(substr($regex, strlen($regex)-1) === '/') { }
$regex = substr($regex, 0, strlen($regex) - 2);
} public function setMethod($method)
{
$isParameter = true; $this->callback = sprintf('%s@%s', $this->getClass(), $method);
} elseif($isParameter && $character === '}') { return $this;
$required = true; }
// Check for optional parameter
public function setClass($class)
// Use custom parameter regex if it exists {
if(is_array($this->where) && isset($this->where[$parameter])) { $this->callback = sprintf('%s@%s', $class, $this->getMethod());
$parameterRegex = $this->where[$parameter]; return $this;
} }
if($lastCharacter === '?') { /**
$parameter = substr($parameter, 0, strlen($parameter)-1); * @param string $middleware
$regex .= '(?:\/?(?P<' . $parameter . '>'. $parameterRegex .')[^\/]?)?'; * @return static
$required = false; */
} else { public function setMiddleware($middleware)
$regex .= '\/?(?P<' . $parameter . '>'. $parameterRegex .')[^\/]?'; {
} $this->middlewares[] = $middleware;
$parameterNames[] = array('name' => $parameter, 'required' => $required); return $this;
$parameter = ''; }
$isParameter = false;
public function setMiddlewares(array $middlewares)
} elseif($isParameter) { {
$parameter .= $character; $this->middlewares = $middlewares;
} elseif($character === '/') { return $this;
$regex .= '\\' . $character; }
} else {
$regex .= str_replace('.', '\\.', $character); /**
} * @param string $namespace
* @return static $this
$lastCharacter = $character; */
} public function setNamespace($namespace)
{
$parameterValues = array(); $this->namespace = $namespace;
return $this;
if(preg_match('/^'.$regex.'\/?$/is', $url, $parameterValues)) { }
$parameters = array(); /**
* @param string $namespace
$max = count($parameterNames); * @return static $this
*/
if($max) { public function setDefaultNamespace($namespace) {
for($i = 0; $i < $max; $i++) { $this->defaultNamespace = $namespace;
$name = $parameterNames[$i]; return $this;
$parameterValue = isset($parameterValues[$name['name']]) ? $parameterValues[$name['name']] : null; }
if($name['required'] && $parameterValue === null) { public function getDefaultNamespace() {
throw new RouterException('Missing required parameter ' . $name['name'], 404); return $this->defaultNamespace;
} }
if(!$name['required'] && $parameterValue === null) { /**
continue; * @return string|array
} */
public function getMiddlewares()
$parameters[$name['name']] = $parameterValue; {
} return $this->middlewares;
} }
return $parameters; /**
} * @return string
*/
return null; public function getNamespace()
} {
return ($this->namespace === null) ? $this->defaultNamespace : $this->namespace;
public function loadMiddleware(Request $request) { }
if($this->getMiddleware()) {
if(is_array($this->getMiddleware())) { /**
foreach($this->getMiddleware() as $middleware) { * @return array
$middleware = $this->loadClass($middleware); */
if (!($middleware instanceof IMiddleware)) { public function getParameters()
throw new RouterException($middleware . ' must be instance of Middleware'); {
} return $this->parameters;
}
/* @var $class IMiddleware */
$middleware->handle($request); /**
} * @param mixed $parameters
} else { * @return static
$middleware = $this->loadClass($this->getMiddleware()); */
if (!($middleware instanceof IMiddleware)) { public function setParameters($parameters)
throw new RouterException($this->getMiddleware() . ' must be instance of Middleware'); {
} $this->parameters = $parameters;
return $this;
/* @var $class IMiddleware */ }
$middleware->handle($request);
} /**
} * Add regular expression parameter match
} *
* @param array $options
public function renderRoute(Request $request) { * @return static
if(is_object($this->getCallback()) && is_callable($this->getCallback())) { */
// When the callback is a function public function where(array $options)
call_user_func_array($this->getCallback(), $this->getParameters()); {
} else { $this->where = $options;
// When the callback is a method return $this;
$controller = explode('@', $this->getCallback()); }
$className = $this->getNamespace() . '\\' . $controller[0];
/**
$class = $this->loadClass($className); * Add regular expression match for url
$method = $controller[1]; *
* @param string $regex
if (!method_exists($class, $method)) { * @return static
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404); */
} public function match($regex)
{
$parameters = array_filter($this->getParameters(), function($var){ $this->regex = $regex;
return !is_null($var); return $this;
}); }
call_user_func_array(array($class, $method), $parameters); /**
* Export route settings to array so they can be merged with another route.
return $class; *
} * @return array
*/
return null; public function toArray()
} {
$values = array();
/**
* Set allowed request methods if ($this->namespace !== null) {
* $values['namespace'] = $this->namespace;
* @param array $methods }
* @return self $this
*/ if (count($this->middlewares) > 0) {
public function setRequestMethods(array $methods) { $values['middleware'] = $this->middlewares;
$this->settings['requestMethods'] = $methods; }
return $this;
} if (count($this->where) > 0) {
$values['where'] = $this->where;
/** }
* Get allowed request methods
* if (count($this->requestMethods) > 0) {
* @return array $values['method'] = $this->requestMethods;
*/ }
public function getRequestMethods() {
if(!isset($this->settings['requestMethods']) || isset($this->settings['requestMethods']) && !is_array($this->settings['requestMethods'])) { if (count($this->parameters) > 0) {
$value = isset($this->settings['requestMethods']) ? $this->settings['requestMethods'] : null; $values['parameters'] = $this->parameters;
return array($value); }
}
return $this->settings['requestMethods']; return $values;
} }
public function getGroup() { /**
return $this->group; * Merge with information from another route.
} *
* @param array $values
public function setGroup($group) { * @return static $this
$this->group = $group; */
return $this; public function merge(array $values)
} {
if (isset($values['namespace'])) {
abstract function matchRoute(Request $request); $this->setNamespace($values['namespace']);
}
// Push middleware if multiple
if (isset($values['middleware'])) {
$this->middlewares = array_merge((array)$values['middleware'], $this->middlewares);
}
if (isset($values['method'])) {
$this->setRequestMethods((array)$values['method']);
}
if (isset($values['where'])) {
$this->where($values['where']);
}
if (isset($values['parameters'])) {
$this->setParameters($values['parameters']);
}
return $this;
}
abstract function matchRoute(Request $request);
} }
+81 -91
View File
@@ -1,116 +1,106 @@
<?php <?php
namespace Pecee\SimpleRouter; namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Http\Request; use Pecee\Http\Request;
class RouterGroup extends RouterEntry { class RouterGroup extends RouterEntry
{
protected $prefix;
protected $domains = array();
protected $exceptionHandlers = array();
public function matchDomain(Request $request) { public function matchDomain(Request $request)
if($this->domain !== null) { {
if (count($this->domains) > 0) {
foreach ($this->domains as $domain) {
if(is_array($this->domain)) { $parameters = $this->parseParameters($domain, $request->getHost(), '.*');
$max = count($this->domain); if ($parameters !== null) {
$this->parameters = $parameters;
return true;
}
}
for($i = 0; $i < $max; $i++) { return false;
$domain = $this->domain[$i]; }
$parameters = $this->parseParameters($domain, $request->getHost(), '[^.]*'); return true;
}
if($parameters !== null) { public function matchRoute(Request $request)
$this->parameters = $parameters; {
return true; // Skip if prefix doesn't match
} if ($this->prefix !== null && stripos($request->getUri(), $this->prefix) === false) {
} return false;
}
return false; return $this->matchDomain($request);
} }
$parameters = $this->parseParameters($this->domain, $request->getHost(), '[^.]*'); public function setExceptionHandlers(array $handlers)
{
$this->exceptionHandlers = $handlers;
return $this;
}
if ($parameters !== null) { public function getExceptionHandlers()
$this->parameters = $parameters; {
return true; return $this->exceptionHandlers;
} }
return false; public function getDomains()
} {
return $this->domains;
}
return true; public function setDomains(array $domains)
} {
$this->domains = $domains;
return $this;
}
public function renderRoute(Request $request) { /**
// Check if request method is allowed * @param string $prefix
$hasAccess = (!$this->method); * @return static
*/
public function setPrefix($prefix)
{
$this->prefix = '/' . trim($prefix, '/');
return $this;
}
if($this->method) { /**
if(is_array($this->method)) { * @return string
$hasAccess = (in_array($request->getMethod(), $this->getRequestMethods())); */
} else { public function getPrefix()
$hasAccess = strtolower($this->getRequestMethods()) == strtolower($request->getMethod()); {
} return $this->prefix;
} }
if(!$hasAccess) { /**
throw new RouterException('Method not allowed'); * Merge with information from another route.
} *
* @param array $values
* @return static
*/
public function merge(array $values)
{
if (isset($values['prefix'])) {
$this->setPrefix($values['prefix']);
}
$this->matchDomain($request); if (isset($values['exceptionHandler'])) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
}
return parent::renderRoute($request); if (isset($values['domain'])) {
} $this->setDomains((array)$values['domain']);
}
public function matchRoute(Request $request) { parent::merge($values);
// Skip if prefix doesn't match
if($this->getPrefix() !== null && stripos($request->getUri(), $this->getPrefix()) === false) {
return false;
}
return $this->matchDomain($request); return $this;
} }
public function setExceptionHandler($class) {
$this->exceptionHandler = $class;
return $this;
}
public function getExceptionHandler() {
return $this->exceptionHandler;
}
public function getDomain() {
return $this->domain;
}
/**
* @param array $settings
* @return self
*/
public function addSettings(array $settings = null) {
if($this->getNamespace() !== null && isset($settings['namespace'])) {
unset($settings['namespace']);
}
// Push middleware if multiple
if($this->getMiddleware() !== null && isset($settings['middleware'])) {
if(!is_array($this->getMiddleware())) {
$middlewares = [$this->getMiddleware(), $settings['middleware']];
} else {
$middlewares = array_push($settings['middleware']);
}
$settings['middleware'] = array_unique(array_reverse($middlewares));
}
if(is_array($settings)) {
$this->settings = array_merge($this->settings, $settings);
}
return $this;
}
} }
+89 -102
View File
@@ -4,133 +4,120 @@ namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException; use Pecee\Exception\RouterException;
use Pecee\Http\Request; use Pecee\Http\Request;
class RouterResource extends RouterEntry { class RouterResource extends LoadableRoute implements IControllerRoute
{
protected $controller;
protected $url; public function __construct($url, $controller)
protected $controller; {
protected $postMethod; $this->setUrl($url);
$this->controller = $controller;
}
public function __construct($url, $controller) { public function renderRoute(Request $request)
parent::__construct(); {
$this->url = $url; if ($this->getCallback() !== null && is_callable($this->getCallback())) {
$this->controller = $controller; // When the callback is a function
$this->postMethod = strtolower(($_SERVER['REQUEST_METHOD'] != 'GET') ? 'post' : 'get'); 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]);
public function renderRoute(Request $request) { if (!method_exists($class, $method)) {
if(is_object($this->getCallback()) && is_callable($this->getCallback())) { throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
// 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)) { call_user_func_array([$class, $method], $this->getParameters());
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 $class; return null;
} }
return null; protected function call($method, $parameters)
} {
$this->setCallback($this->controller . '@' . $method);
$this->parameters = $parameters;
protected function call($method, $parameters) { return true;
$this->setCallback($this->controller . '@' . $method); }
$this->parameters = $parameters;
return true;
}
public function matchRoute(Request $request) { public function matchRoute(Request $request)
$url = parse_url(urldecode($request->getUri())); {
$url = rtrim($url['path'], '/') . '/'; $url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
$route = rtrim($this->url, '/') . '/{id?}/{action?}'; $route = rtrim($this->url, '/') . '/{id?}/{action?}';
$parameters = $this->parseParameters($route, $url); $parameters = $this->parseParameters($route, $url);
if($parameters !== null) { if ($parameters !== null) {
if(is_array($parameters)) { $parameters = array_merge($this->parameters, (array)$parameters);
$parameters = array_merge($this->parameters, $parameters);
}
$action = isset($parameters['action']) ? $parameters['action'] : null; $action = isset($parameters['action']) ? $parameters['action'] : null;
unset($parameters['action']); unset($parameters['action']);
// Delete $method = request()->getMethod();
if($request->getMethod() === self::REQUEST_TYPE_DELETE && $this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('destroy', $parameters);
}
// Update // Delete
if(in_array($request->getMethod(), array(self::REQUEST_TYPE_PATCH, self::REQUEST_TYPE_PUT)) && $this->postMethod === self::REQUEST_TYPE_POST) { if (isset($parameters['id']) && $method === static::REQUEST_TYPE_DELETE) {
return $this->call('update', $parameters); return $this->call('destroy', $parameters);
} }
// Edit // Update
if(isset($action) && strtolower($action) === 'edit' && $this->postMethod === self::REQUEST_TYPE_GET) { if (isset($parameters['id']) && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT])) {
return $this->call('edit', $parameters); return $this->call('update', $parameters);
} }
// Create // Edit
if(strtolower($action) === 'create' && $request->getMethod() === self::REQUEST_TYPE_GET) { if (isset($parameters['id']) && strtolower($action) === 'edit' && $method === static::REQUEST_TYPE_GET) {
return $this->call('create', $parameters); return $this->call('edit', $parameters);
} }
// Save // Create
if($this->postMethod === self::REQUEST_TYPE_POST) { if (strtolower($action) === 'create' && $method === static::REQUEST_TYPE_GET) {
return $this->call('store', $parameters); return $this->call('create', $parameters);
} }
// Show // Save
if(isset($parameters['id']) && $this->postMethod === self::REQUEST_TYPE_GET) { if ($method === static::REQUEST_TYPE_POST) {
return $this->call('show', $parameters); return $this->call('store', $parameters);
} }
// Index // Show
return $this->call('index', $parameters); if (isset($parameters['id']) && $method === static::REQUEST_TYPE_GET) {
} return $this->call('show', $parameters);
}
return null; // Index
} return $this->call('index', $parameters);
}
/** return null;
* @return string }
*/
public function getUrl() {
return $this->url;
}
/** /**
* @param string $url * @return string
* @return static */
*/ public function getController()
public function setUrl($url) { {
$url = rtrim($url, '/') . '/'; return $this->controller;
$this->url = $url; }
return $this;
}
/** /**
* @return string * @param string $controller
*/ * @return static
public function getController() { */
return $this->controller; public function setController($controller)
} {
$this->controller = $controller;
/** return $this;
* @param string $controller }
* @return static
*/
public function setController($controller) {
$this->controller = $controller;
return $this;
}
} }
+29 -119
View File
@@ -1,132 +1,42 @@
<?php <?php
namespace Pecee\SimpleRouter; namespace Pecee\SimpleRouter;
use Pecee\Http\Request; use Pecee\Http\Request;
class RouterRoute extends RouterEntry { class RouterRoute extends LoadableRoute
{
public function __construct($url, $callback)
{
$this->setUrl($url);
$this->setCallback($callback);
}
const PARAMETERS_REGEX_MATCH = '{([A-Za-z\-\_]*?)\?{0,1}}'; public function matchRoute(Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
protected $url; // Match on custom defined regular expression
if ($this->regex !== null) {
$parameters = array();
if (preg_match('/(' . $this->regex . ')/is', $request->getHost() . $url, $parameters)) {
$this->parameters = (array)$parameters[0];
return true;
}
return null;
}
public function __construct($url, $callback) { // Make regular expression based on route
parent::__construct(); $route = rtrim($this->url, '/') . '/';
$this->setUrl($url);
$this->setCallback($callback);
}
public function matchRoute(Request $request) { $parameters = $this->parseParameters($route, $url);
$url = parse_url(urldecode($request->getUri())); if ($parameters !== null) {
$url = rtrim($url['path'], '/') . '/'; $this->parameters = array_merge($this->parameters, $parameters);
return true;
}
// Match on custom defined regular expression return null;
if($this->regexMatch) { }
$parameters = array();
if(preg_match('/('.$this->regexMatch.')/is', $request->getHost() . $url, $parameters)) {
$this->parameters = (!is_array($parameters[0]) ? array($parameters[0]) : $parameters[0]);
return true;
}
return null;
}
// Make regular expression based on route
$route = rtrim($this->url, '/') . '/';
$parameters = $this->parseParameters($route, $url);
if($parameters !== null) {
if(is_array($this->parameters)) {
$this->parameters = array_merge($this->parameters, $parameters);
} else {
$this->parameters = $parameters;
}
return true;
}
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
* @return self
*/
public function setUrl($url) {
$parameters = array();
$matches = array();
if(preg_match_all('/'.self::PARAMETERS_REGEX_MATCH.'/is', $url, $matches)) {
$parameters = $matches[1];
}
if(count($parameters)) {
$tmp = array();
foreach($parameters as $param) {
$tmp[$param] = null;
}
$this->parameters = $tmp;
}
$this->url = $url;
return $this;
}
/**
* Get alias for the url which can be used when getting the url route.
* @return string|array
*/
public function getAlias(){
return $this->alias;
}
/**
* Check if route has given alias.
*
* @param $name
* @return bool
*/
public function hasAlias($name) {
if(is_array($this->alias)) {
foreach($this->alias as $alias) {
if(strtolower($alias) === strtolower($name)) {
return true;
}
}
} else {
return strtolower($this->getAlias()) === strtolower($name);
}
return false;
}
/**
* Set the url alias for easier getting the url route.
* @param string|array $alias
* @return self
*/
public function setAlias($alias){
$this->alias = $alias;
return $this;
}
public function setSettings($settings) {
// Change as to alias
if(isset($settings{'as'})) {
$this->setAlias($settings['as']);
}
return parent::setSettings($settings);
}
} }
+301 -89
View File
@@ -1,131 +1,343 @@
<?php <?php
/** /**
* --------------------------- * ---------------------------
* Router helper class * Router helper class
* --------------------------- * ---------------------------
* This class is added so calls can be made statically like Router::get() making the code look more pretty. * This class is added so calls can be made statically like Router::get() making the code look more pretty.
*/ */
namespace Pecee\SimpleRouter; namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Http\Middleware\BaseCsrfVerifier; use Pecee\Http\Middleware\BaseCsrfVerifier;
class SimpleRouter { class SimpleRouter
{
protected static $defaultNamespace;
/** /**
* Start/route request * Start/route request
* @param null $defaultNamespace *
* @throws \Pecee\Exception\RouterException * @throws \Pecee\Exception\RouterException
*/ */
public static function start($defaultNamespace = null) { public static function start()
RouterBase::getInstance()->setDefaultNamespace($defaultNamespace)->routeRequest(); {
} static::router()->routeRequest();
}
/** /**
* Set base csrf verifier * Set default namespace which will be prepended to all routes.
* @param BaseCsrfVerifier $baseCsrfVerifier *
*/ * @param string $defaultNamespace
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) { */
RouterBase::getInstance()->setBaseCsrfVerifier($baseCsrfVerifier); public static function setDefaultNamespace($defaultNamespace)
} {
static::$defaultNamespace = $defaultNamespace;
}
public static function addBootManager(RouterBootManager $bootManager) { /**
RouterBase::getInstance()->addBootManager($bootManager); * Base CSRF verifier
} *
* @param BaseCsrfVerifier $baseCsrfVerifier
*/
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier)
{
static::router()->setCsrfVerifier($baseCsrfVerifier);
}
public static function get($url, $callback, array $settings = null) { /**
return self::match(['get'], $url, $callback, $settings); * Boot managers allows you to alter the routes before the routing occurs.
} * Perfect if you want to load pretty-urls from a file or database.
*
* @param RouterBootManager $bootManager
*/
public static function addBootManager(RouterBootManager $bootManager)
{
static::router()->addBootManager($bootManager);
}
public static function post($url, $callback, array $settings = null) { /**
return self::match(['post'], $url, $callback, $settings); * Route the given url to your callback on GET request method.
} *
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function get($url, $callback, array $settings = null)
{
return static::match(['get'], $url, $callback, $settings);
}
public static function put($url, $callback, array $settings = null) { /**
return self::match(['put'], $url, $callback, $settings); * Route the given url to your callback on POST request method.
} *
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function post($url, $callback, array $settings = null)
{
return static::match(['post'], $url, $callback, $settings);
}
public static function delete($url, $callback, array $settings = null) { /**
return self::match(['delete'], $url, $callback, $settings); * Route the given url to your callback on PUT request method.
} *
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function put($url, $callback, array $settings = null)
{
return static::match(['put'], $url, $callback, $settings);
}
public static function group($settings = array(), $callback) { /**
$group = new RouterGroup(); * Route the given url to your callback on PATCH request method.
$group->setCallback($callback); *
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function patch($url, $callback, array $settings = null)
{
return static::match(['patch'], $url, $callback, $settings);
}
if($settings !== null && is_array($settings)) { /**
$group->setSettings($settings); * Route the given url to your callback on OPTIONS request method.
} *
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function options($url, $callback, array $settings = null)
{
return static::match(['options'], $url, $callback, $settings);
}
RouterBase::getInstance()->addRoute($group); /**
* Route the given url to your callback on DELETE request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function delete($url, $callback, array $settings = null)
{
return static::match(['delete'], $url, $callback, $settings);
}
return $group; /**
} * Groups allows for encapsulating routes with special settings.
*
* @param array $settings
* @param \Closure $callback
* @throws RouterException
* @return RouterGroup
*/
public static function group(array $settings = array(), \Closure $callback)
{
$group = new RouterGroup();
$group->setCallback($callback);
$group->merge($settings);
/** if (is_callable($callback) === false) {
* Adds get + post route throw new RouterException('Invalid callback provided. Only functions or methods supported');
* }
* @param string $url
* @param callable $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function basic($url, $callback, array $settings = null) {
return self::match(['get', 'post'], $url, $callback, $settings);
}
public static function match(array $requestMethods, $url, $callback, array $settings = null) { static::router()->addRoute($group);
$route = new RouterRoute($url, $callback);
$route->setRequestMethods($requestMethods);
if($settings !== null) { return $group;
$route->addSettings($settings); }
}
RouterBase::getInstance()->addRoute($route); /**
* Alias for the form method
*
* @param string $url
* @param callable $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouterRoute
*/
public static function basic($url, $callback, array $settings = null)
{
return static::match(['get', 'post'], $url, $callback, $settings);
}
return $route; /**
} * This type will route the given url to your callback on the provided request methods.
* Route the given url to your callback on POST and GET request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouterRoute
*/
public static function form($url, $callback, array $settings = null)
{
return static::match(['get', 'post'], $url, $callback, $settings);
}
public static function all($url, $callback, array $settings = null) { /**
$route = new RouterRoute($url, $callback); * This type will route the given url to your callback on the provided request methods.
*
* @param array $requestMethods
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterEntry|RouterRoute
*/
public static function match(array $requestMethods, $url, $callback, array $settings = null)
{
$route = new RouterRoute($url, $callback);
$route->setRequestMethods($requestMethods);
$route = static::addDefaultNamespace($route);
if($settings !== null) { if ($settings !== null) {
$route->addSettings($settings); $route->merge($settings);
} }
RouterBase::getInstance()->addRoute($route); static::router()->addRoute($route);
return $route; return $route;
} }
public static function controller($url, $controller, array $settings = null) { /**
$route = new RouterController($url, $controller); * This type will route the given url to your callback and allow any type of request method
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function all($url, $callback, array $settings = null)
{
$route = new RouterRoute($url, $callback);
if($settings !== null) { $route = static::addDefaultNamespace($route);
$route->addSettings($settings);
}
RouterBase::getInstance()->addRoute($route); if ($settings !== null) {
$route->merge($settings);
}
return $route; static::router()->addRoute($route);
}
public static function resource($url, $controller, array $settings = null) { return $route;
$route = new RouterResource($url, $controller); }
if($settings !== null) { /**
$route->addSettings($settings); * This route will route request from the given url to the controller.
} *
* @param string $url
* @param string $controller
* @param array|null $settings
* @return RouterController
*/
public static function controller($url, $controller, array $settings = null)
{
$route = new RouterController($url, $controller);
RouterBase::getInstance()->addRoute($route); $route = static::addDefaultNamespace($route);
return $route; if ($settings !== null) {
} $route->merge($settings);
}
public static function getRoute($controller = null, $parameters = null, $getParams = null) { static::router()->addRoute($route);
return RouterBase::getInstance()->getRoute($controller, $parameters, $getParams);
} return $route;
}
/**
* This type will route all REST-supported requests to different methods in the provided controller.
*
* @param string $url
* @param string $controller
* @param array|null $settings
* @return RouterResource
*/
public static function resource($url, $controller, array $settings = null)
{
$route = new RouterResource($url, $controller);
if ($settings !== null) {
$route->merge($settings);
}
static::router()->addRoute($route);
return $route;
}
/**
* Get url by controller or alias.
*
* @param string $controller
* @param array|null $parameters
* @param array|null $getParams
* @return string
*/
public static function getRoute($controller = null, $parameters = null, $getParams = null)
{
return static::router()->getRoute($controller, $parameters, $getParams);
}
/**
* Get the request
*
* @return \Pecee\Http\Request
*/
public static function request()
{
return static::router()->getRequest();
}
/**
* Get the response object
*
* @return \Pecee\Http\Response
*/
public static function response()
{
return static::router()->getResponse();
}
/**
* Returns the router instance
*
* @return RouterBase
*/
public static function router()
{
return RouterBase::getInstance();
}
/**
* Prepends the default namespace to all new routes added.
*
* @param RouterEntry $route
* @return RouterEntry
*/
protected static function addDefaultNamespace(RouterEntry $route)
{
if (static::$defaultNamespace !== null) {
$namespace = static::$defaultNamespace;
if ($route->getNamespace() !== null) {
$namespace .= '\\' . $route->getNamespace();
}
$route->setDefaultNamespace($namespace);
}
return $route;
}
} }
+18 -11
View File
@@ -1,18 +1,25 @@
<?php <?php
class DummyController { class DummyController
{
public function start()
{
echo static::class . '@' . 'start() OK';
}
public function start() { public function param($params = null)
echo static::class . '@' .'start() OK'; {
} $params = func_get_args();
echo 'Params: ' . join(', ', $params);
}
public function param($params = null) { public function notFound()
$params = func_get_args(); {
echo 'Params: ' . join(', ', $params); echo 'not found';
} }
public function notFound() { public function silent() {
echo 'not found';
} }
} }
+6 -6
View File
@@ -1,14 +1,14 @@
<?php <?php
require_once 'Exceptions/MiddlewareLoadedException.php'; require_once 'Exceptions/MiddlewareLoadedException.php';
use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request; use Pecee\Http\Request;
class DummyMiddleware implements IMiddleware { class DummyMiddleware implements IMiddleware
{
public function handle(Request $request) { public function handle(Request $request, \Pecee\SimpleRouter\RouterEntry &$route = null)
throw new MiddlewareLoadedException('Middleware loaded!'); {
} throw new MiddlewareLoadedException('Middleware loaded!');
}
} }
@@ -1,2 +1,4 @@
<?php <?php
class MiddlewareLoadedException extends \Exception {} class MiddlewareLoadedException extends \Exception
{
}
+6 -4
View File
@@ -1,8 +1,10 @@
<?php <?php
class ExceptionHandler implements \Pecee\Handler\IExceptionHandler {
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\RouterEntry $router = null, \Exception $error){ class ExceptionHandler implements \Pecee\Handler\IExceptionHandler
throw $error; {
} public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\RouterEntry &$route = null, \Exception $error)
{
throw $error;
}
} }
+73 -24
View File
@@ -3,41 +3,90 @@
require_once 'Dummy/DummyMiddleware.php'; require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php'; require_once 'Dummy/DummyController.php';
class GroupTest extends PHPUnit_Framework_TestCase { use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
protected $result; class GroupTest extends PHPUnit_Framework_TestCase
{
protected $result;
protected function group() { public function testGroupLoad()
$this->result = true; {
} $this->result = false;
public function testGroup() { SimpleRouter::group(['prefix' => '/group'], function () {
$this->result = true;
});
$this->result = false; try {
SimpleRouter::start();
} catch (Exception $e) {
// ignore RouteNotFound exception
}
\Pecee\SimpleRouter\SimpleRouter::group(['prefix' => '/group'], $this->group()); $this->assertTrue($this->result);
}
try { public function testNestedGroup()
\Pecee\SimpleRouter\SimpleRouter::start(); {
} catch(Exception $e) {
} SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/api/v1/test');
SimpleRouter::request()->setMethod('get');
$this->assertTrue($this->result); SimpleRouter::group(['prefix' => '/api'], function () {
}
public function testNestedGroup() { SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setUri('/api/v1/test'); });
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setMethod('get');
\Pecee\SimpleRouter\SimpleRouter::group(['prefix' => '/api'], function() { SimpleRouter::start();
\Pecee\SimpleRouter\SimpleRouter::group(['prefix' => '/v1'], function() { }
\Pecee\SimpleRouter\SimpleRouter::get('/test', 'DummyController@start');
});
});
\Pecee\SimpleRouter\SimpleRouter::start(); public function testManyRoutes()
} {
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/match');
SimpleRouter::request()->setMethod('get');
SimpleRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
SimpleRouter::get('/my/match', 'DummyController@start');
SimpleRouter::group(['prefix' => '/service'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/no-match', 'DummyController@start');
});
});
SimpleRouter::start();
}
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/fancy/url/1');
SimpleRouter::request()->setMethod('get');
SimpleRouter::get('/my/fancy/url/1', 'DummyController@start', ['as' => 'fancy1']);
SimpleRouter::get('/my/fancy/url/2', 'DummyController@start')->setAlias('fancy2');
SimpleRouter::start();
$this->assertTrue((SimpleRouter::getRoute('fancy1') === '/my/fancy/url/1/'));
$this->assertTrue((SimpleRouter::getRoute('fancy2') === '/my/fancy/url/2/'));
}
} }
+19 -17
View File
@@ -4,27 +4,29 @@ require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php'; require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php'; require_once 'Dummy/Handler/ExceptionHandler.php';
class MiddlewareTest extends PHPUnit_Framework_TestCase { use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
public function testMiddlewareFound() { class MiddlewareTest extends PHPUnit_Framework_TestCase
{
public function testMiddlewareFound()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url');
\Pecee\Http\Request::getInstance()->setMethod('get'); SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
\Pecee\Http\Request::getInstance()->setUri('/my/test/url'); SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']);
});
\Pecee\SimpleRouter\SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function() { $found = false;
\Pecee\SimpleRouter\SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']);
});
$found = false; try {
SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof MiddlewareLoadedException);
}
try { $this->assertTrue($found);
\Pecee\SimpleRouter\SimpleRouter::start(); }
}catch(\Exception $e) {
$found = ($e instanceof MiddlewareLoadedException);
}
$this->assertTrue($found);
}
} }
+103 -72
View File
@@ -4,106 +4,137 @@ require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php'; require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php'; require_once 'Dummy/Handler/ExceptionHandler.php';
class RouterRouteTest extends PHPUnit_Framework_TestCase { use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
public function testNotFound() { class RouterRouteTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
\Pecee\Http\Request::getInstance()->setMethod('get'); public function testNotFound()
\Pecee\Http\Request::getInstance()->setUri('/test-param1-param2'); {
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
\Pecee\SimpleRouter\SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function() { SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
\Pecee\SimpleRouter\SimpleRouter::get('/non-existing-path', 'DummyController@start'); SimpleRouter::get('/non-existing-path', 'DummyController@start');
}); });
$found = false; $found = false;
try { try {
\Pecee\SimpleRouter\SimpleRouter::start(); SimpleRouter::start();
}catch(\Exception $e) { } catch (\Exception $e) {
$found = ($e instanceof \Pecee\Exception\RouterException && $e->getCode() == 404); $found = ($e instanceof \Pecee\Exception\RouterException && $e->getCode() == 404);
} }
$this->assertTrue($found); $this->assertTrue($found);
}
} public function testGet()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('get');
public function testGet() { SimpleRouter::get('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setUri('/my/test/url'); public function testPost()
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setMethod('get'); {
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
\Pecee\SimpleRouter\SimpleRouter::get('/my/test/url', 'DummyController@start'); SimpleRouter::post('/my/test/url', 'DummyController@start');
\Pecee\SimpleRouter\SimpleRouter::start(); SimpleRouter::start();
} }
public function testPost() { public function testPut()
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setUri('/my/test/url'); {
\Pecee\Http\Request::getInstance()->setMethod('post'); SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('put');
\Pecee\SimpleRouter\SimpleRouter::post('/my/test/url', 'DummyController@start'); SimpleRouter::put('/my/test/url', 'DummyController@start');
\Pecee\SimpleRouter\SimpleRouter::start(); SimpleRouter::start();
} }
public function testPut() { public function testDelete()
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setUri('/my/test/url'); {
\Pecee\Http\Request::getInstance()->setMethod('put'); SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('delete');
\Pecee\SimpleRouter\SimpleRouter::put('/my/test/url', 'DummyController@start'); SimpleRouter::delete('/my/test/url', 'DummyController@start');
\Pecee\SimpleRouter\SimpleRouter::start(); SimpleRouter::start();
} }
public function testDelete() { public function testMethodNotAllowed()
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setUri('/my/test/url'); {
\Pecee\Http\Request::getInstance()->setMethod('delete'); SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
\Pecee\SimpleRouter\SimpleRouter::delete('/my/test/url', 'DummyController@start'); SimpleRouter::get('/my/test/url', 'DummyController@start');
\Pecee\SimpleRouter\SimpleRouter::start();
} try {
SimpleRouter::start();
} catch (\Exception $e) {
$this->assertEquals(403, $e->getCode());
}
}
public function testMethodNotAllowed() { public function testSimpleParam()
\Pecee\SimpleRouter\RouterBase::getInstance()->getRequest()->setUri('/my/test/url'); {
\Pecee\Http\Request::getInstance()->setMethod('post'); SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1');
\Pecee\SimpleRouter\SimpleRouter::get('/my/test/url', 'DummyController@start'); SimpleRouter::get('/test-{param1}', 'DummyController@param');
SimpleRouter::start();
}
try { public function testMultiParam()
\Pecee\SimpleRouter\SimpleRouter::start(); {
} catch(\Exception $e) { SimpleRouter::router()->reset();
$this->assertEquals(403, $e->getCode()); SimpleRouter::request()->setMethod('get');
} SimpleRouter::request()->setUri('/test-param1-param2');
} SimpleRouter::get('/test-{param1}-{param2}', 'DummyController@param');
SimpleRouter::start();
}
public function testSimpleParam() { public function testPathParamRegex()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test/path/123123');
\Pecee\Http\Request::getInstance()->setMethod('get'); SimpleRouter::get('/test/path/{myParam}', 'DummyController@param', ['where' => ['myParam' => '([0-9]+)']]);
\Pecee\Http\Request::getInstance()->setUri('/test-param1'); SimpleRouter::start();
}
\Pecee\SimpleRouter\SimpleRouter::get('/test-{param1}', 'DummyController@param'); public function testDomainRoute()
\Pecee\SimpleRouter\SimpleRouter::start(); {
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test');
SimpleRouter::request()->setHost('hello.world.com');
} $this->result = false;
public function testMultiParam() { SimpleRouter::group(['domain' => '{subdomain}.world.com'], function () {
SimpleRouter::get('/test', function ($subdomain = null) {
$this->result = ($subdomain === 'hello');
});
});
\Pecee\Http\Request::getInstance()->setMethod('get'); SimpleRouter::start();
\Pecee\Http\Request::getInstance()->setUri('/test-param1-param2');
\Pecee\SimpleRouter\SimpleRouter::get('/test-{param1}-{param2}', 'DummyController@param'); $this->assertTrue($this->result);
\Pecee\SimpleRouter\SimpleRouter::start();
} }
public function testPathParam() {
\Pecee\Http\Request::getInstance()->setMethod('get');
\Pecee\Http\Request::getInstance()->setUri('/test/path/param1');
\Pecee\SimpleRouter\SimpleRouter::get('/test/path/{param}', 'DummyController@param');
\Pecee\SimpleRouter\SimpleRouter::start();
}
} }
+83
View File
@@ -0,0 +1,83 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class RouterUrlTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
protected function getUrl($controller = null, $parameters = null, $getParams = null) {
return SimpleRouter::getRoute($controller, $parameters, $getParams);
}
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/');
// Match normal route on alias
SimpleRouter::get('/', 'DummyController@silent', ['as' => 'home']);
SimpleRouter::group(['prefix' => '/admin'], function() {
// Match route with prefix on alias
SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'admin.home']);
// Match controller with prefix and alias
SimpleRouter::controller('/users', 'DummyController', ['as' => 'admin.users']);
// Match controller with prefix and NO alias
SimpleRouter::controller('/pages', 'DummyController');
});
// Match controller with no prefix and no alias
SimpleRouter::controller('/cats', 'CatsController');
// Pretend to load page
SimpleRouter::start();
// Should match /
$this->assertEquals($this->getUrl('home'), '/');
// Should match /admin/
$this->assertEquals($this->getUrl('DummyController@start'), '/admin/');
// Should match /admin/
$this->assertEquals($this->getUrl('admin.home'), '/admin/');
// Should match /admin/2/
$this->assertEquals($this->getUrl('admin.home', ['id' => 2]), '/admin/2/');
// Should match /admin/users/
$this->assertEquals($this->getUrl('admin.users'), '/admin/users/');
// Should match /admin/users/home/
$this->assertEquals($this->getUrl('admin.users@home'), '/admin/users/home/');
// Should match /cats/
$this->assertEquals($this->getUrl('CatsController'), '/cats/');
// Should match /cats/view/
$this->assertEquals($this->getUrl('CatsController', 'view'), '/cats/view/');
// Should match /cats/view/
$this->assertEquals($this->getUrl('CatsController', ['view']), '/cats/view/');
// Should match /cats/view/666
$this->assertEquals($this->getUrl('CatsController@view', ['666']), '/cats/view/666/');
// Should match /funny/man/
$this->assertEquals($this->getUrl('/funny/man'), '/funny/man/');
// Should match /?jackdaniels=true
$this->assertEquals($this->getUrl('home', null, ['jackdaniels' => 'true']), '/?jackdaniels=true');
}
}