Simple, fast and yet powerful PHP router that is easy to get integrated and in any project.

Overview

simple-router

Simple, fast and yet powerful PHP router that is easy to get integrated and in any project. Heavily inspired by the way Laravel handles routing, with both simplicity and expand-ability in mind.

With simple-router you can create a new project fast, without depending on a framework.

It only takes a few lines of code to get started:

SimpleRouter::get('/', function() {
    return 'Hello world';
});

Support the project

If you like simple-router and wish to see the continued development and maintenance of the project, please consider showing your support by buying me a coffee. Supporters will be listed under the credits section of this documentation.

You can donate any amount of your choice by clicking here.

Table of Contents


Getting started

Add the latest version of the simple-router project running this command.

composer require pecee/simple-router

Notes

The goal of this project is to create a router that is more or less 100% compatible with the Laravel documentation, while remaining as simple as possible, and as easy to integrate and change without compromising either speed or complexity. Being lightweight is the #1 priority.

We've included a simple demo project for the router which can be found here. This project should give you a basic understanding of how to setup and use simple-php-router project.

Please note that the demo-project only covers how to integrate the simple-php-router in a project without an existing framework. If you are using a framework in your project, the implementation might vary.

You can find the demo-project here: https://github.com/skipperbent/simple-router-demo

What we won't cover:

  • How to setup a solution that fits your need. This is a basic demo to help you get started.
  • Understanding of MVC; including Controllers, Middlewares or ExceptionHandlers.
  • How to integrate into third party frameworks.

What we cover:

  • How to get up and running fast - from scratch.
  • How to get ExceptionHandlers, Middlewares and Controllers working.
  • How to setup your webservers.

Requirements

  • PHP 7.1 or greater (version 3.x and below supports PHP 5.5+)
  • PHP JSON extension enabled.

Features

  • Basic routing (GET, POST, PUT, PATCH, UPDATE, DELETE) with support for custom multiple verbs.
  • Regular Expression Constraints for parameters.
  • Named routes.
  • Generating url to routes.
  • Route groups.
  • Middleware (classes that intercepts before the route is rendered).
  • Namespaces.
  • Route prefixes.
  • CSRF protection.
  • Optional parameters
  • Sub-domain routing
  • Custom boot managers to rewrite urls to "nicer" ones.
  • Input manager; easily manage GET, POST and FILE values.
  • IP based restrictions.
  • Easily extendable.

Installation

  1. Navigate to your project folder in terminal and run the following command:
composer require pecee/simple-router

Setting up Nginx

If you are using Nginx please make sure that url-rewriting is enabled.

You can easily enable url-rewriting by adding the following configuration for the Nginx configuration-file for the demo-project.

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Setting up Apache

Nothing special is required for Apache to work. We've include the .htaccess file in the public folder. If rewriting is not working for you, please check that the mod_rewrite module (htaccess support) is enabled in the Apache configuration.

.htaccess example

Below is an example of an working .htaccess file used by simple-php-router.

Simply create a new .htaccess file in your projects public directory and paste the contents below in your newly created file. This will redirect all requests to your index.php file (see Configuration section below).

RewriteEngine on
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-l
RewriteRule ^(.*)$ index.php/$1

Setting up IIS

On IIS you have to add some lines your web.config file in the public folder or create a new one. If rewriting is not working for you, please check that your IIS version have included the url rewrite module or download and install them from Microsoft web site.

web.config example

Below is an example of an working web.config file used by simple-php-router.

Simply create a new web.config file in your projects public directory and paste the contents below in your newly created file. This will redirect all requests to your index.php file (see Configuration section below). If the web.config file already exists, add the section inside the branch.

">
xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
	<rewrite>
	  <rules>
		
		<rule name="RewriteRequestsToPublic">
		  <match url="^(.*)$" />
		  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
		  conditions>
		  <action type="Rewrite" url="/{R:0}" />
		rule>

		
		<rule name="Imported Rule 1" stopProcessing="true">
		  <match url="^(.*)$" ignoreCase="true" />
		  <conditions logicalGrouping="MatchAll">
			<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
			<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
		  conditions>
		  <action type="Rewrite" url="/index.php/{R:1}" appendQueryString="true" />
		rule>
	  rules>
	rewrite>
    system.webServer>
configuration>

Troubleshooting

If you do not have a favicon.ico file in your project, you can get a NotFoundHttpException (404 - not found).

To add favicon.ico to the IIS ignore-list, add the following line to the group:

">

  

You can also make one exception for files with some extensions:

">

  

If you are using $_SERVER['ORIG_PATH_INFO'], you will get \index.php\ as part of the returned value.

Example:

/index.php/test/mypage.php

Configuration

Create a new file, name it routes.php and place it in your library folder. This will be the file where you define all the routes for your project.

WARNING: NEVER PLACE YOUR ROUTES.PHP IN YOUR PUBLIC FOLDER!

In your index.php require your newly-created routes.php and call the SimpleRouter::start() method. This will trigger and do the actual routing of the requests.

It's not required, but you can set SimpleRouter::setDefaultNamespace('\Demo\Controllers'); to prefix all routes with the namespace to your controllers. This will simplify things a bit, as you won't have to specify the namespace for your controllers on each route.

This is an example of a basic index.php file:


use Pecee\SimpleRouter\SimpleRouter;

/* Load external routes file */
require_once 'routes.php';

/**
 * 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 on your routes.
 */

SimpleRouter::setDefaultNamespace('\Demo\Controllers');

// Start the routing
SimpleRouter::start();

Helper functions

We recommend that you add these helper functions to your project. These will allow you to access functionality of the router more easily.

To implement the functions below, simply copy the code to a new file and require the file before initializing the router or copy the helpers.php we've included in this library.

getInputHandler()->value($index, $defaultValue, ...$methods); } return request()->getInputHandler(); } /** * @param string $url * @param int|null $code */ function redirect(string $url, ?int $code = null): void { if ($code !== null) { response()->httpCode($code); } response()->redirect($url); } /** * Get current csrf-token * @return string|null */ function csrf_token(): ?string { $baseVerifier = Router::router()->getCsrfVerifier(); if ($baseVerifier !== null) { return $baseVerifier->getTokenProvider()->getToken(); } return null; } ">
use Pecee\SimpleRouter\SimpleRouter as Router;
use Pecee\Http\Url;
use Pecee\Http\Response;
use Pecee\Http\Request;

/**
 * Get url for a route by using either name/alias, class or method name.
 *
 * The name parameter supports the following values:
 * - Route name
 * - Controller/resource name (with or without method)
 * - Controller class name
 *
 * When searching for controller/resource by name, you can use this syntax "route.name@method".
 * You can also use the same syntax when searching for a specific controller-class "MyController@home".
 * If no arguments is specified, it will return the url for the current loaded route.
 *
 * @param string|null $name
 * @param string|array|null $parameters
 * @param array|null $getParams
 * @return \Pecee\Http\Url
 * @throws \InvalidArgumentException
 */
function url(?string $name = null, $parameters = null, ?array $getParams = null): Url
{
    return Router::getUrl($name, $parameters, $getParams);
}

/**
 * @return \Pecee\Http\Response
 */
function response(): Response
{
    return Router::response();
}

/**
 * @return \Pecee\Http\Request
 */
function request(): Request
{
    return Router::request();
}

/**
 * Get input class
 * @param string|null $index Parameter index name
 * @param string|mixed|null $defaultValue Default return value
 * @param array ...$methods Default methods
 * @return \Pecee\Http\Input\InputHandler|array|string|null
 */
function input($index = null, $defaultValue = null, ...$methods)
{
    if ($index !== null) {
        return request()->getInputHandler()->value($index, $defaultValue, ...$methods);
    }

    return request()->getInputHandler();
}

/**
 * @param string $url
 * @param int|null $code
 */
function redirect(string $url, ?int $code = null): void
{
    if ($code !== null) {
        response()->httpCode($code);
    }

    response()->redirect($url);
}

/**
 * Get current csrf-token
 * @return string|null
 */
function csrf_token(): ?string
{
    $baseVerifier = Router::router()->getCsrfVerifier();
    if ($baseVerifier !== null) {
        return $baseVerifier->getTokenProvider()->getToken();
    }

    return null;
}

Routes

Remember the routes.php file you required in your index.php? This file be where you place all your custom rules for routing.

Basic routing

Below is a very basic example of setting up a route. First parameter is the url which the route should match - next parameter is a Closure or callback function that will be triggered once the route matches.

SimpleRouter::get('/', function() {
    return 'Hello world';
});

Class hinting

You can use class hinting to load a class & method like this:

SimpleRouter::get('/', [MyClass::class, 'myMethod']);

Available methods

Here you can see a list over all available routes:

SimpleRouter::get($url, $callback, $settings);
SimpleRouter::post($url, $callback, $settings);
SimpleRouter::put($url, $callback, $settings);
SimpleRouter::patch($url, $callback, $settings);
SimpleRouter::delete($url, $callback, $settings);
SimpleRouter::options($url, $callback, $settings);

Multiple HTTP-verbs

Sometimes you might need to create a route that accepts multiple HTTP-verbs. If you need to match all HTTP-verbs you can use the any method.

SimpleRouter::match(['get', 'post'], '/', function() {
    // ...
});

SimpleRouter::any('foo', function() {
    // ...
});

We've created a simple method which matches GET and POST which is most commonly used:

SimpleRouter::form('foo', function() {
    // ...
});

Route parameters

Required parameters

You'll properly wondering by know how you parse parameters from your urls. For example, you might want to capture the users id from an url. You can do so by defining route-parameters.

SimpleRouter::get('/user/{id}', function ($userId) {
    return 'User with id: ' . $userId;
});

You may define as many route parameters as required by your route:

SimpleRouter::get('/posts/{post}/comments/{comment}', function ($postId, $commentId) {
    // ...
});

Note: Route parameters are always encased within { } braces and should consist of alphabetic characters. Route parameters can only contain certain characters like A-Z, a-z, 0-9, - and _. If your route contain other characters, please see Custom regex for matching parameters.

Optional parameters

Occasionally you may need to specify a route parameter, but make the presence of that route parameter optional. You may do so by placing a ? mark after the parameter name. Make sure to give the route's corresponding variable a default value:

SimpleRouter::get('/user/{name?}', function ($name = null) {
    return $name;
});

SimpleRouter::get('/user/{name?}', function ($name = 'Simon') {
    return $name;
});

Regular expression constraints

You may constrain the format of your route parameters using the where method on a route instance. The where method accepts the name of the parameter and a regular expression defining how the parameter should be constrained:

SimpleRouter::get('/user/{name}', function ($name) {
    
    // ... do stuff
    
})->where([ 'name' => '[A-Za-z]+' ]);

SimpleRouter::get('/user/{id}', function ($id) {
    
    // ... do stuff
    
})->where([ 'id' => '[0-9]+' ]);

SimpleRouter::get('/user/{id}/{name}', function ($id, $name) {
    
    // ... do stuff
    
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

Regular expression route-match

You can define a regular-expression match for the entire route if you wish.

This is useful if you for example are creating a model-box which loads urls from ajax.

The example below is using the following regular expression: /ajax/([\w]+)/?([0-9]+)?/? which basically just matches /ajax/ and exspects the next parameter to be a string - and the next to be a number (but optional).

Matches: /ajax/abc/, /ajax/abc/123/

Won't match: /ajax/

Match groups specified in the regex will be passed on as parameters:

SimpleRouter::all('/ajax/abc/123', function($param1, $param2) {
	// param1 = abc
	// param2 = 123
})->setMatch('/\/ajax\/([\w]+)\/?([0-9]+)?\/?/is');

Custom regex for matching parameters

By default simple-php-router uses the [\w\-]+ regular expression. It will match A-Z, a-z, 0-9, - and _ characters in parameters. This decision was made with speed and reliability in mind, as this match will match both letters, number and most of the used symbols on the internet.

However, sometimes it can be necessary to add a custom regular expression to match more advanced characters like foreign letters æ ø å etc.

You can test your custom regular expression by using on the site Regex101.com.

Instead of adding a custom regular expression to all your parameters, you can simply add a global regular expression which will be used on all the parameters on the route.

Note: If you the regular expression to be available across, we recommend using the global parameter on a group as demonstrated in the examples below.

Example

This example will ensure that all parameters use the [\w\-\æ\ø\å]+ (a-z, A-Z, -, _, 0-9, æ, ø, å) regular expression when parsing.

SimpleRouter::get('/path/{parameter}', 'VideoController@home', ['defaultParameterRegex' => '[\w\-\æ\ø\å]+']);

You can also apply this setting to a group if you need multiple routes to use your custom regular expression when parsing parameters.

SimpleRouter::group(['defaultParameterRegex' => '[\w\-\æ\ø\å]+'], function() {

    SimpleRouter::get('/path/{parameter}', 'VideoController@home');

});

Named routes

Named routes allow the convenient generation of URLs or redirects for specific routes. You may specify a name for a route by chaining the name method onto the route definition:

SimpleRouter::get('/user/profile', function () {
    // Your code here
})->name('profile');

You can also specify names for Controller-actions:

SimpleRouter::get('/user/profile', 'UserController@profile')->name('profile');

Generating URLs To Named Routes

Once you have assigned a name to a given route, you may use the route's name when generating URLs or redirects via the global url helper-function (see helpers section):

// Generating URLs...
$url = url('profile');

If the named route defines parameters, you may pass the parameters as the second argument to the url function. The given parameters will automatically be inserted into the URL in their correct positions:

SimpleRouter::get('/user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = url('profile', ['id' => 1]);

For more information on urls, please see the Urls section.

Router groups

Route groups allow you to share route attributes, such as middleware or namespaces, across a large number of routes without needing to define those attributes on each individual route. Shared attributes are specified in an array format as the first parameter to the SimpleRouter::group method.

Middleware

To assign middleware to all routes within a group, you may use the middleware key in the group attribute array. Middleware are executed in the order they are listed in the array:

SimpleRouter::group(['middleware' => \Demo\Middleware\Auth::class], function () {
    SimpleRouter::get('/', function ()    {
        // Uses Auth Middleware
    });

    SimpleRouter::get('/user/profile', function () {
        // Uses Auth Middleware
    });
});

Namespaces

Another common use-case for route groups is assigning the same PHP namespace to a group of controllers using the namespace parameter in the group array:

Note

Group namespaces will only be added to routes with relative callbacks. For example if your route has an absolute callback like \Demo\Controller\DefaultController@home, the namespace from the route will not be prepended. To fix this you can make the callback relative by removing the \ in the beginning of the callback.

SimpleRouter::group(['namespace' => 'Admin'], function () {
    // Controllers Within The "App\Http\Controllers\Admin" Namespace
});

You can add parameters to the prefixes of your routes.

Parameters from your previous routes will be injected into your routes after any route-required parameters, starting from oldest to newest.

SimpleRouter::group(['prefix' => '/lang/{lang}'], function ($language) {
    
    SimpleRouter::get('/about', function($language) {
    	
    	// Will match /lang/da/about
    	
    });
    
});

Subdomain-routing

Route groups may also be used to handle sub-domain routing. Sub-domains may be assigned route parameters just like route urls, allowing you to capture a portion of the sub-domain for usage in your route or controller. The sub-domain may be specified using the domain key on the group attribute array:

SimpleRouter::group(['domain' => '{account}.myapp.com'], function () {
    SimpleRouter::get('/user/{id}', function ($account, $id) {
        //
    });
});

Route prefixes

The prefix group attribute may be used to prefix each route in the group with a given url. For example, you may want to prefix all route urls within the group with admin:

SimpleRouter::group(['prefix' => '/admin'], function () {
    SimpleRouter::get('/users', function ()    {
        // Matches The "/admin/users" URL
    });
});

You can also use parameters in your groups:

SimpleRouter::group(['prefix' => '/lang/{language}'], function ($language) {
    SimpleRouter::get('/users', function ($language)    {
        // Matches The "/lang/da/users" URL
    });
});

Partial groups

Partial router groups has the same benefits as a normal group, but are only rendered once the url has matched in contrast to a normal group which are always rendered in order to retrieve it's child routes. Partial groups are therefore more like a hybrid of a traditional route with the benefits of a group.

This can be extremely useful in situations where you only want special routes to be added, but only when a certain criteria or logic has been met.

NOTE: Use partial groups with caution as routes added within are only rendered and available once the url of the partial-group has matched. This can cause url() not to find urls for the routes added within before the partial-group has been matched and is rendered.

Example:

SimpleRouter::partialGroup('/plugin/{name}', function ($plugin) {

    // Add routes from plugin

});

Form Method Spoofing

HTML forms do not support PUT, PATCH or DELETE actions. So, when defining PUT, PATCH or DELETE routes that are called from an HTML form, you will need to add a hidden _method field to the form. The value sent with the _method field will be used as the HTTP request method:

">
"hidden" name="_method" value="PUT" />

Accessing The Current Route

You can access information about the current route loaded by using the following method:

SimpleRouter::request()->getLoadedRoute();
request()->getLoadedRoute();

Other examples

You can find many more examples in the routes.php example-file below:


use Pecee\SimpleRouter\SimpleRouter;

/* Adding custom csrfVerifier here */
SimpleRouter::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());

SimpleRouter::group(['middleware' => \Demo\Middlewares\Site::class, 'exceptionHandler' => \Demo\Handlers\CustomExceptionHandler::class], function() {


    SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show', ['where' => ['id' => '[0-9]+']]);

	/**
     * Class hinting is supported too
     */
     
     SimpleRouter::get('/answers/{id}', [ControllerAnswers::class, 'show'], ['where' => ['id' => '[0-9]+']]);

    /**
     * Restful resource (see IRestController interface for available methods)
     */

    SimpleRouter::resource('/rest', ControllerResource::class);


    /**
     * Load the entire controller (where url matches method names - getIndex(), postIndex(), putIndex()).
     * The url paths will determine which method to render.
     *
     * For example:
     *
     * GET  /animals         => getIndex()
     * GET  /animals/view    => getView()
     * POST /animals/save    => postSave()
     *
     * etc.
     */

    SimpleRouter::controller('/animals', ControllerAnimals::class);

});

SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']);

CSRF Protection

Any forms posting to POST, PUT or DELETE routes should include the CSRF-token. We strongly recommend that you enable CSRF-verification on your site to maximize security.

You can use the BaseCsrfVerifier to enable CSRF-validation on all request. If you need to disable verification for specific urls, please refer to the "Custom CSRF-verifier" section below.

By default simple-php-router will use the CookieTokenProvider class. This provider will store the security-token in a cookie on the clients machine. If you want to store the token elsewhere, please refer to the "Creating custom Token Provider" section below.

Adding CSRF-verifier

When you've created your CSRF-verifier you need to tell simple-php-router that it should use it. You can do this by adding the following line in your routes.php file:

SimpleRouter::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());

Getting CSRF-token

When posting to any of the urls that has CSRF-verification enabled, you need post your CSRF-token or else the request will get rejected.

You can get the CSRF-token by calling the helper method:

csrf_token();

You can also get the token directly:

return SimpleRouter::router()->getCsrfVerifier()->getTokenProvider()->getToken();

The default name/key for the input-field is csrf_token and is defined in the POST_KEY constant in the BaseCsrfVerifier class. You can change the key by overwriting the constant in your own CSRF-verifier class.

Example:

The example below will post to the current url with a hidden field "csrf_token".

">
<form method="post" action="">
    <input type="hidden" name="csrf_token" value="">
    
form>

Custom CSRF-verifier

Create a new class and extend the BaseCsrfVerifier middleware class provided by default with the simple-php-router library.

Add the property except with an array of the urls to the routes you want to exclude/whitelist from the CSRF validation. Using * at the end for the url will match the entire url.

Here's a basic example on a CSRF-verifier class:

namespace Demo\Middlewares;

use Pecee\Http\Middleware\BaseCsrfVerifier;

class CsrfVerifier extends BaseCsrfVerifier
{
	/**
	 * CSRF validation will be ignored on the following urls.
	 */
	protected $except = ['/api/*'];
}

Custom Token Provider

By default the BaseCsrfVerifier will use the CookieTokenProvider to store the token in a cookie on the clients machine.

If you need to store the token elsewhere, you can do that by creating your own class and implementing the ITokenProvider class.

class SessionTokenProvider implements ITokenProvider
{

    /**
     * Refresh existing token
     */
    public function refresh(): void
    {
        // Implement your own functionality here...
    }

    /**
     * Validate valid CSRF token
     *
     * @param string $token
     * @return bool
     */
    public function validate($token): bool
    {
        // Implement your own functionality here...
    }
    
    /**
     * Get token token
     *
     * @param string|null $defaultValue
     * @return string|null
     */
    public function getToken(?string $defaultValue = null): ?string 
    {
        // Implement your own functionality here...
    }

}

Next you need to set your custom ITokenProvider implementation on your BaseCsrfVerifier class in your routes file:

$verifier = new \Demo\Middlewares\CsrfVerifier();
$verifier->setTokenProvider(new SessionTokenProvider());

SimpleRouter::csrfVerifier($verifier);

Middlewares

Middlewares are classes that loads before the route is rendered. A middleware can be used to verify that a user is logged in - or to set parameters specific for the current request/route. Middlewares must implement the IMiddleware interface.

Example

namespace Demo\Middlewares;

use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;

class CustomMiddleware implements IMiddleware {

    public function handle(Request $request): void 
    {
    
        // Authenticate user, will be available using request()->user
        $request->user = User::authenticate();

        // If authentication failed, redirect request to user-login page.
        if($request->user === null) {
            $request->setRewriteUrl(url('user.login'));
        }

    }
}

ExceptionHandlers

ExceptionHandler are classes that handles all exceptions. ExceptionsHandlers must implement the IExceptionHandler interface.

Handling 404, 403 and other errors

If you simply want to catch a 404 (page not found) etc. you can use the SimpleRouter::error($callback) static helper method.

This will add a callback method which is fired whenever an error occurs on all routes.

The basic example below simply redirect the page to /not-found if an NotFoundHttpException (404) occurred. The code should be placed in the file that contains your routes.

SimpleRouter::get('/not-found', 'PageController@notFound');
SimpleRouter::get('/forbidden', 'PageController@notFound');

SimpleRouter::error(function(Request $request, \Exception $exception) {

    switch($exception->getCode()) {
        // Page not found
        case 404:
            response()->redirect('/not-found');
        // Forbidden
        case 403:
            response()->redirect('/forbidden');
    }
    
});

The example above will redirect all errors with http-code 404 (page not found) to /not-found and 403 (forbidden) to /forbidden.

If you do not want a redirect, but want the error-page rendered on the current-url, you can tell the router to execute a rewrite callback like so:

$request->setRewriteCallback('ErrorController@notFound');

Using custom exception handlers

This is a basic example of an ExceptionHandler implementation (please see "Easily overwrite route about to be loaded" for examples on how to change callback).

namespace Demo\Handlers;

use Pecee\Http\Request;
use Pecee\SimpleRouter\Handlers\IExceptionHandler;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;

class CustomExceptionHandler implements IExceptionHandler
{
	public function handleError(Request $request, \Exception $error): void
	{

		/* You can use the exception handler to format errors depending on the request and type. */

		if ($request->getUrl()->contains('/api')) {

			response()->json([
				'error' => $error->getMessage(),
				'code'  => $error->getCode(),
			]);

		}

		/* The router will throw the NotFoundHttpException on 404 */
		if($error instanceof NotFoundHttpException) {

			// Render custom 404-page
			$request->setRewriteCallback('Demo\Controllers\PageController@notFound');
			return;
			
		}

		throw $error;

	}

}

You can add your custom exception-handler class to your group by using the exceptionHandler settings-attribute. exceptionHandler can be either class-name or array of class-names.

SimpleRouter::group(['exceptionHandler' => \Demo\Handlers\CustomExceptionHandler::class], function() {

    // Your routes here

});

Prevent merge of parent exception-handlers

By default the router will merge exception-handlers to any handlers provided by parent groups, and will be executed in the order of newest to oldest.

If you want your groups exception handler to be executed independently, you can add the mergeExceptionHandlers attribute and set it to false.

SimpleRouter::group(['prefix' => '/', 'exceptionHandler' => \Demo\Handlers\FirstExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {

	SimpleRouter::group(['prefix' => '/admin', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class], function() {
	
		// Both SecondExceptionHandler and FirstExceptionHandler will trigger (in that order).
	
	});
	
	SimpleRouter::group(['prefix' => '/user', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {
	
		// Only SecondExceptionHandler will trigger.
	
	});

});

Urls

By default all controller and resource routes will use a simplified version of their url as name.

You easily use the url() shortcut helper function to retrieve urls for your routes or manipulate the current url.

url() will return a Url object which will return a string when rendered, so it can be used safely in templates etc. but contains all the useful helpers methods in the Url class like contains, indexOf etc. Check the Useful url tricks below.

Get the current url

It has never been easier to get and/or manipulate the current url.

The example below shows you how to get the current url:

# output: /current-url
url();

Get by name (single route)

SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']);

# output: /product-view/22/?category=shoes
url('product', ['id' => 22], ['category' => 'shoes']);

# output: /product-view/?category=shoes
url('product', null, ['category' => 'shoes']);

Get by name (controller route)

SimpleRouter::controller('/images', ImagesController::class, ['as' => 'picture']);

# output: /images/view/?category=shows
url('picture@getView', null, ['category' => 'shoes']);

# output: /images/view/?category=shows
url('picture', 'getView', ['category' => 'shoes']);

# output: /images/view/
url('picture', 'view');

Get by class

SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']);
SimpleRouter::controller('/images', 'ImagesController');

# output: /product-view/22/?category=shoes
url('ProductsController@show', ['id' => 22], ['category' => 'shoes']);

# output: /images/image/?id=22
url('ImagesController@getImage', null, ['id' => 22]);

Using custom names for methods on a controller/resource route

SimpleRouter::controller('gadgets', GadgetsController::class, ['names' => ['getIphoneInfo' => 'iphone']]);

url('gadgets.iphone');

# output
# /gadgets/iphoneinfo/

Getting REST/resource controller urls

SimpleRouter::resource('/phones', PhonesController::class);

# output: /phones/
url('phones');

# output: /phones/
url('phones.index');

# output: /phones/create/
url('phones.create');

# output: /phones/edit/
url('phones.edit');

Manipulating url

You can easily manipulate the query-strings, by adding your get param arguments.

# output: /current-url?q=cars

url(null, null, ['q' => 'cars']);

You can remove a query-string parameter by setting the value to null.

The example below will remove any query-string parameter named q from the url but keep all others query-string parameters:

$url = url()->removeParam('q');

For more information please check the Useful url tricks section of the documentation.

Useful url tricks

Calling url will always return a Url object. Upon rendered it will return a string of the relative url, so it's safe to use in templates etc.

However this allow us to use the useful methods on the Url object like indexOf and contains or retrieve specific parts of the url like the path, querystring parameters, host etc. You can also manipulate the url like removing- or adding parameters, changing host and more.

In the example below, we check if the current url contains the /api part.

if(url()->contains('/api')) {
    // ... do stuff
}

As mentioned earlier, you can also use the Url object to show specific parts of the url or control what part of the url you want.

# Grab the query-string parameter id from the current-url.
$id = url()->getParam('id');

# Get the absolute url for the current url.
$absoluteUrl = url()->getAbsoluteUrl();

For more available methods please check the Pecee\Http\Url class.

Input & parameters

simple-router offers libraries and helpers that makes it easy to manage and manipulate input-parameters like $_POST, $_GET and $_FILE.

Using the Input class to manage parameters

You can use the InputHandler class to easily access and manage parameters from your request. The InputHandler class offers extended features such as copying/moving uploaded files directly on the object, getting file-extension, mime-type etc.

Get single parameter value

input($index, $defaultValue, ...$methods);

To quickly get a value from a parameter, you can use the input helper function.

This will automatically trim the value and ensure that it's not empty. If it's empty the $defaultValue will be returned instead.

Note: This function returns a string unless the parameters are grouped together, in that case it will return an array of values.

Example:

This example matches both POST and GET request-methods and if name is empty the default-value "Guest" will be returned.

$name = input('name', 'Guest', 'post', 'get');

Get parameter object

When dealing with file-uploads it can be useful to retrieve the raw parameter object.

Search for object with default-value across multiple or specific request-methods:

The example below will return an InputItem object if the parameter was found or return the $defaultValue. If parameters are grouped, it will return an array of InputItem objects.

$object = input()->find($index, $defaultValue = null, ...$methods);

Getting specific $_GET parameter as InputItem object:

The example below will return an InputItem object if the parameter was found or return the $defaultValue. If parameters are grouped, it will return an array of InputItem objects.

$object = input()->get($index, $defaultValue = null);

Getting specific $_POST parameter as InputItem object:

The example below will return an InputItem object if the parameter was found or return the $defaultValue. If parameters are grouped, it will return an array of InputItem objects.

$object = input()->post($index, $defaultValue = null);

Getting specific $_FILE parameter as InputFile object:

The example below will return an InputFile object if the parameter was found or return the $defaultValue. If parameters are grouped, it will return an array of InputFile objects.

$object = input()->file($index, $defaultValue = null);

Managing files

*/ /* @var $image \Pecee\Http\Input\InputFile */ foreach(input()->file('images', []) as $image) { if($image->getMime() === 'image/jpeg') { $destinationFilname = sprintf('%s.%s', uniqid(), $image->getExtension()); $image->move(sprintf('/uploads/%s', $destinationFilename)); } } ">
/**
 * Loop through a collection of files uploaded from a form on the page like this
 * 
 */

/* @var $image \Pecee\Http\Input\InputFile */
foreach(input()->file('images', []) as $image)
{
    if($image->getMime() === 'image/jpeg') 
    {
        $destinationFilname = sprintf('%s.%s', uniqid(), $image->getExtension());
        $image->move(sprintf('/uploads/%s', $destinationFilename));
    }
}

Get all parameters

# Get all
$values = input()->all();

# Only match specific keys
$values = input()->all([
    'company_name',
    'user_id'
]);

All object implements the IInputItem interface and will always contain these methods:

  • getIndex() - returns the index/key of the input.
  • setIndex() - set the index/key of the input.
  • getName() - returns a human friendly name for the input (company_name will be Company Name etc).
  • setName() - sets a human friendly name for the input (company_name will be Company Name etc).
  • getValue() - returns the value of the input.
  • setValue() - sets the value of the input.

InputFile has the same methods as above along with some other file-specific methods like:

  • getFilename - get the filename.
  • getTmpName() - get file temporary name.
  • getSize() - get file size.
  • move($destination) - move file to destination.
  • getContents() - get file content.
  • getType() - get mime-type for file.
  • getError() - get file upload error.
  • hasError() - returns bool if an error occurred while uploading (if getError is not 0).
  • toArray() - returns raw array

Check if parameters exists

You can easily if multiple items exists by using the exists method. It's simular to value as it can be used to filter on request-methods and supports both string and array as parameter value.

Example:

if(input()->exists(['name', 'lastname'])) {
	// Do stuff
}

/* Similar to code above */
if(input()->exists('name') && input()->exists('lastname')) {
	// Do stuff
}

Events

This section will help you understand how to register your own callbacks to events in the router. It will also cover the basics of event-handlers; how to use the handlers provided with the router and how to create your own custom event-handlers.

Available events

This section contains all available events that can be registered using the EventHandler.

All event callbacks will retrieve a EventArgument object as parameter. This object contains easy access to event-name, router- and request instance and any special event-arguments related to the given event. You can see what special event arguments each event returns in the list below.

Name Special arguments Description
EVENT_ALL - Fires when a event is triggered.
EVENT_INIT - Fires when router is initializing and before routes are loaded.
EVENT_LOAD loadedRoutes Fires when all routes has been loaded and rendered, just before the output is returned.
EVENT_ADD_ROUTE route
isSubRoute
Fires when route is added to the router. isSubRoute is true when sub-route is rendered.
EVENT_REWRITE rewriteUrl
rewriteRoute
Fires when a url-rewrite is and just before the routes are re-initialized.
EVENT_BOOT bootmanagers Fires when the router is booting. This happens just before boot-managers are rendered and before any routes has been loaded.
EVENT_RENDER_BOOTMANAGER bootmanagers
bootmanager
Fires before a boot-manager is rendered.
EVENT_LOAD_ROUTES routes Fires when the router is about to load all routes.
EVENT_FIND_ROUTE name Fires whenever the findRoute method is called within the Router. This usually happens when the router tries to find routes that contains a certain url, usually after the EventHandler::EVENT_GET_URL event.
EVENT_GET_URL name
parameters
getParams
Fires whenever the SimpleRouter::getUrl method or url-helper function is called and the router tries to find the route.
EVENT_MATCH_ROUTE route Fires when a route is matched and valid (correct request-type etc). and before the route is rendered.
EVENT_RENDER_ROUTE route Fires before a route is rendered.
EVENT_LOAD_EXCEPTIONS exception
exceptionHandlers
Fires when the router is loading exception-handlers.
EVENT_RENDER_EXCEPTION exception
exceptionHandler
exceptionHandlers
Fires before the router is rendering a exception-handler.
EVENT_RENDER_MIDDLEWARES route
middlewares
Fires before middlewares for a route is rendered.
EVENT_RENDER_CSRF csrfVerifier Fires before the CSRF-verifier is rendered.

Registering new event

To register a new event you need to create a new instance of the EventHandler object. On this object you can add as many callbacks as you like by calling the registerEvent method.

When you've registered events, make sure to add it to the router by calling SimpleRouter::addEventHandler(). We recommend that you add your event-handlers within your routes.php.

Example:

use Pecee\SimpleRouter\Handlers\EventHandler;
use Pecee\SimpleRouter\Event\EventArgument;

// --- your routes goes here ---

$eventHandler = new EventHandler();

// Add event that fires when a route is rendered
$eventHandler->register(EventHandler::EVENT_RENDER_ROUTE, function(EventArgument $argument) {
   
   // Get the route by using the special argument for this event.
   $route = $argument->route;
   
   // DO STUFF...
    
});

SimpleRouter::addEventHandler($eventHandler);

Custom EventHandlers

EventHandler is the class that manages events and must inherit from the IEventHandler interface. The handler knows how to handle events for the given handler-type.

Most of the time the basic \Pecee\SimpleRouter\Handler\EventHandler class will be more than enough for most people as you simply register an event which fires when triggered.

Let's go over how to create your very own event-handler class.

Below is a basic example of a custom event-handler called DatabaseDebugHandler. The idea of the sample below is to logs all events to the database when triggered. Hopefully it will be enough to give you an idea on how the event-handlers work.

namespace Demo\Handlers;

use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;

class DatabaseDebugHandler implements IEventHandler
{

    /**
     * Debug callback
     * @var \Closure
     */
    protected $callback;

    public function __construct()
    {
        $this->callback = function (EventArgument $argument) {
            // todo: store log in database
        };
    }

    /**
     * Get events.
     *
     * @param string|null $name Filter events by name.
     * @return array
     */
    public function getEvents(?string $name): array
    {
        return [
            $name => [
                $this->callback,
            ],
        ];
    }

    /**
     * Fires any events registered with given event-name
     *
     * @param Router $router Router instance
     * @param string $name Event name
     * @param array ...$eventArgs Event arguments
     */
    public function fireEvents(Router $router, string $name, ...$eventArgs): void
    {
        $callback = $this->callback;
        $callback(new EventArgument($router, $eventArgs));
    }

    /**
     * Set debug callback
     *
     * @param \Closure $event
     */
    public function setCallback(\Closure $event): void
    {
        $this->callback = $event;
    }

}

Advanced

Disable multiple route rendering

By default the router will try to execute all routes that matches a given url. To stop the router from executing any further routes any method can return a value.

This behavior can be easily disabled by setting SimpleRouter::enableMultiRouteRendering(false) in your routes.php file. This is the same behavior as version 3 and below.

Restrict access to IP

You can white and/or blacklist access to IP's using the build in IpRestrictAccess middleware.

Create your own custom Middleware and extend the IpRestrictAccess class.

The IpRestrictAccess class contains two properties ipBlacklist and ipWhitelist that can be added to your middleware to change which IP's that have access to your routes.

You can use * to restrict access to a range of ips.

use \Pecee\Http\Middleware\IpRestrictAccess;

class IpBlockerMiddleware extends IpRestrictAccess 
{

    protected $ipBlacklist = [
        '5.5.5.5',
        '8.8.*',
    ];

    protected $ipWhitelist = [
        '8.8.2.2',
    ];

}

You can add the middleware to multiple routes by adding your middleware to a group.

Setting custom base path

Sometimes it can be useful to add a custom base path to all of the routes added.

This can easily be done by taking advantage of the Event Handlers support of the project.

$basePath = '/basepath';

$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $event) use($basePath) {

	$route = $event->route;

	// Skip routes added by group as these will inherit the url
	if(!$event->isSubRoute) {
		return;
	}
	
	switch (true) {
		case $route instanceof ILoadableRoute:
			$route->prependUrl($basePath);
			break;
		case $route instanceof IGroupRoute:
			$route->prependPrefix($basePath);
			break;

	}
	
});

SimpleRouter::addEventHandler($eventHandler);

In the example shown above, we create a new EVENT_ADD_ROUTE event that triggers, when a new route is added. We skip all subroutes as these will inherit the url from their parent. Then, if the route is a group, we change the prefix
otherwise we change the url.

Url rewriting

Changing current route

Sometimes it can be useful to manipulate the route about to be loaded. simple-php-router allows you to easily manipulate and change the routes which are about to be rendered. All information about the current route is stored in the \Pecee\SimpleRouter\Router instance's loadedRoute property.

For easy access you can use the shortcut helper function request() instead of calling the class directly \Pecee\SimpleRouter\SimpleRouter::router().

request()->setRewriteCallback('Example\MyCustomClass@hello');

// -- or you can rewrite by url --

request()->setRewriteUrl('/my-rewrite-url');

Bootmanager: loading routes dynamically

Sometimes it can be necessary to keep urls stored in the database, file or similar. In this example, we want the url /my-cat-is-beatiful to load the route /article/view/1 which the router knows, because it's defined in the routes.php file.

To interfere with the router, we create a class that implements the IRouterBootManager interface. This class will be loaded before any other rules in routes.php and allow us to "change" the current route, if any of our criteria are fulfilled (like coming from the url /my-cat-is-beatiful).

use Pecee\Http\Request;
use Pecee\SimpleRouter\IRouterBootManager;
use Pecee\SimpleRouter\Router;

class CustomRouterRules implement IRouterBootManager 
{

    /**
     * Called when router is booting and before the routes is loaded.
     *
     * @param \Pecee\SimpleRouter\Router $router
     * @param \Pecee\Http\Request $request
     */
    public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void
    {

        $rewriteRules = [
            '/my-cat-is-beatiful' => '/article/view/1',
            '/horses-are-great'   => '/article/view/2',
        ];

        foreach($rewriteRules as $url => $rule) {

            // If the current url matches the rewrite url, we use our custom route

            if($request->getUrl()->contains($url)) {
                $request->setRewriteUrl($rule);
            }
        }

    }

}

The above should be pretty self-explanatory and can easily be changed to loop through urls store in the database, file or cache.

What happens is that if the current route matches the route defined in the index of our $rewriteRules array, we set the route to the array value instead.

By doing this the route will now load the url /article/view/1 instead of /my-cat-is-beatiful.

The last thing we need to do, is to add our custom boot-manager to the routes.php file. You can create as many bootmanagers as you like and easily add them in your routes.php file.

SimpleRouter::addBootManager(new CustomRouterRules());

Adding routes manually

The SimpleRouter class referenced in the previous example, is just a simple helper class that knows how to communicate with the Router class. If you are up for a challenge, want the full control or simply just want to create your own Router helper class, this example is for you.

use \Pecee\SimpleRouter\Router;
use \Pecee\SimpleRouter\Route\RouteUrl;

/* Create new Router instance */
$router = new Router();

$route = new RouteUrl('/answer/1', function() {

    die('this callback will match /answer/1');

});

$route->addMiddleware(\Demo\Middlewares\AuthMiddleware::class);
$route->setNamespace('\Demo\Controllers');
$route->setPrefix('v1');

/* Add the route to the router */
$router->addRoute($route);

Custom class loader

You can easily extend simple-router to support custom injection frameworks like php-di by taking advantage of the ability to add your custom class-loader.

Class-loaders must inherit the IClassLoader interface.

Example:

class MyCustomClassLoader implements IClassLoader
{
    /**
     * Load class
     *
     * @param string $class
     * @return object
     * @throws NotFoundHttpException
     */
    public function loadClass(string $class)
    {
        if (\class_exists($class) === false) {
            throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
        }

        return new $class();
    }
    
    /**
     * Called when loading class method
     * @param object $class
     * @param string $method
     * @param array $parameters
     * @return object
     */
    public function loadClassMethod($class, string $method, array $parameters)
    {
        return call_user_func_array([$class, $method], array_values($parameters));
    }

    /**
     * Load closure
     *
     * @param Callable $closure
     * @param array $parameters
     * @return mixed
     */
    public function loadClosure(Callable $closure, array $parameters)
    {
        return \call_user_func_array($closure, array_values($parameters));
    }

}

Next, we need to configure our routes.php so the router uses our MyCustomClassLoader class for loading classes. This can be done by adding the following line to your routes.php file.

SimpleRouter::setCustomClassLoader(new MyCustomClassLoader());

Integrating with php-di

php-di support was discontinued by version 4.3, however you can easily add it again by creating your own class-loader like the example below:

container->get($class); } catch (\Exception $e) { throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious()); } } /** * Called when loading class method * @param object $class * @param string $method * @param array $parameters * @return object */ public function loadClassMethod($class, string $method, array $parameters) { try { return $this->container->call([$class, $method], $parameters); } catch (\Exception $e) { throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious()); } } /** * Load closure * * @param Callable $closure * @param array $parameters * @return mixed */ public function loadClosure(callable $closure, array $parameters) { try { return $this->container->call($closure, $parameters); } catch (\Exception $e) { throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious()); } } } ">
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;

class MyCustomClassLoader implements IClassLoader
{

    protected $container;

    public function __construct()
    {
        // Create our new php-di container
        $this->container = (new \DI\ContainerBuilder())
                    ->useAutowiring(true)
                    ->build();
    }

    /**
     * Load class
     *
     * @param string $class
     * @return object
     * @throws NotFoundHttpException
     */
    public function loadClass(string $class)
    {
        if (class_exists($class) === false) {
            throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
        }

		try {
			return $this->container->get($class);
		} catch (\Exception $e) {
			throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
		}
    }
    
    /**
     * Called when loading class method
     * @param object $class
     * @param string $method
     * @param array $parameters
     * @return object
     */
    public function loadClassMethod($class, string $method, array $parameters)
    {
		try {
			return $this->container->call([$class, $method], $parameters);
		} catch (\Exception $e) {
			throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
		}
    }

    /**
     * Load closure
     *
     * @param Callable $closure
     * @param array $parameters
     * @return mixed
     */
    public function loadClosure(callable $closure, array $parameters)
    {
		try {
			return $this->container->call($closure, $parameters);
		} catch (\Exception $e) {
			throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
		}
    }
}

Parameters

This section contains advanced tips & tricks on extending the usage for parameters.

Extending

This is a simple example of an integration into a framework.

The framework has it's own Router class which inherits from the SimpleRouter class. This allows the framework to add custom functionality like loading a custom routes.php file or add debugging information etc.

namespace Demo;

use Pecee\SimpleRouter\SimpleRouter;

class Router extends SimpleRouter {

    public static function start() {

        // change this to whatever makes sense in your project
        require_once 'routes.php';

        // change default namespace for all routes
        parent::setDefaultNamespace('\Demo\Controllers');

        // Do initial stuff
        parent::start();

    }

}

Help and support

This section will go into details on how to debug the router and answer some of the commonly asked questions- and issues.

Common issues and fixes

This section will go over common issues and how to resolve them.

Parameters won't match or route not working with special characters

Often people experience this issue when one or more parameters contains special characters. The router uses a sparse regular-expression that matches letters from a-z along with numbers when matching parameters, to improve performance.

All other characters has to be defined via the defaultParameterRegex option on your route.

You can read more about adding your own custom regular expression for matching parameters by clicking here.

Multiple routes matches? Which one has the priority?

The router will match routes in the order they're added and will render multiple routes, if they match.

If you want the router to stop when a route is matched, you simply return a value in your callback or stop the execution manually (using response()->json() etc.) or simply by returning a result.

Any returned objects that implements the __toString() magic method will also prevent other routes from being rendered.

If you want the router only to execute one route per request, you can disabling multiple route rendering.

Using the router on sub-paths

Please refer to Setting custom base path part of the documentation.

Debugging

This section will show you how to write unit-tests for the router, view useful debugging information and answer some of the frequently asked questions.

It will also covers how to report any issue you might encounter.

Creating unit-tests

The easiest and fastest way to debug any issues with the router, is to create a unit-test that represents the issue you are experiencing.

Unit-tests use a special TestRouter class, which simulates a request-method and requested url of a browser.

The TestRouter class can return the output directly or render a route silently.

'[\w\p{L}\s-]+']); // Start the routing and simulate the url "/cursos/listado/especialidad/cirugía local". TestRouter::debugNoReset('/cursos/listado/especialidad/cirugía local', 'GET'); // Verify that the url for the loaded route matches the expected route. $this->assertEquals('/cursos/listado/{listado?}/{category?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl()); // Start the routing and simulate the url "/test/Dermatología" using "GET" as request-method. TestRouter::debugNoReset('/test/Dermatología', 'GET'); // Another route containing one parameter with special spanish characters like "í". TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-\í]+']); // Get all parameters parsed by the loaded route. $parameters = TestRouter::request()->getLoadedRoute()->getParameters(); // Check that the parameter named "param" matches the exspected value. $this->assertEquals('Dermatología', $parameters['param']); // Add route testing danish special characters like "ø". TestRouter::get('/category/økse', 'DummyController@method1', ['defaultParameterRegex' => '[\w\ø]+']); // Start the routing and simulate the url "/kategory/økse" using "GET" as request-method. TestRouter::debugNoReset('/category/økse', 'GET'); // Validate that the URL of the loaded-route matches the expected url. $this->assertEquals('/category/økse/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl()); // Reset the router, so other tests wont inherit settings or the routes we've added. TestRouter::router()->reset(); } ">
public function testUnicodeCharacters()
{
    // Add route containing two optional paramters with special spanish characters like "í".
    TestRouter::get('/cursos/listado/{listado?}/{category?}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-]+']);
    
    // Start the routing and simulate the url "/cursos/listado/especialidad/cirugía local".
    TestRouter::debugNoReset('/cursos/listado/especialidad/cirugía local', 'GET');
    
    // Verify that the url for the loaded route matches the expected route.
    $this->assertEquals('/cursos/listado/{listado?}/{category?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
    
    // Start the routing and simulate the url "/test/Dermatología" using "GET" as request-method.
    TestRouter::debugNoReset('/test/Dermatología', 'GET');

    // Another route containing one parameter with special spanish characters like "í".
    TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-\í]+']);

    // Get all parameters parsed by the loaded route.
    $parameters = TestRouter::request()->getLoadedRoute()->getParameters();

    // Check that the parameter named "param" matches the exspected value.
    $this->assertEquals('Dermatología', $parameters['param']);

    // Add route testing danish special characters like "ø".
    TestRouter::get('/category/økse', 'DummyController@method1', ['defaultParameterRegex' => '[\w\ø]+']);
    
    // Start the routing and simulate the url "/kategory/økse" using "GET" as request-method.
    TestRouter::debugNoReset('/category/økse', 'GET');
    
    // Validate that the URL of the loaded-route matches the expected url.
    $this->assertEquals('/category/økse/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());

    // Reset the router, so other tests wont inherit settings or the routes we've added.
    TestRouter::router()->reset();
}

Using the TestRouter helper

Depending on your test, you can use the methods below when rendering routes in your unit-tests.

Method Description
TestRouter::debug($url, $method) Will render the route without returning anything. Exceptions will be thrown and the router will be reset automatically.
TestRouter::debugOutput($url, $method) Will render the route and return any value that the route might output. Manual reset required by calling TestRouter::router()->reset().
TestRouter::debugNoReset($url, $method); Will render the route without resetting the router. Useful if you need to get loaded route, parameters etc. from the router. Manual reset required by calling TestRouter::router()->reset().

Debug information

The library can output debug-information, which contains information like loaded routes, the parsed request-url etc. It also contains info which are important when reporting a new issue like PHP-version, library version, server-variables, router debug log etc.

You can activate the debug-information by calling the alternative start-method.

The example below will start the routing an return array with debugging-information

Example:

$debugInfo = SimpleRouter::startDebug();
echo sprintf('
%s
'
, var_export($debugInfo)); exit;

The example above will provide you with an output containing:

Key Description
url The parsed request-uri. This url should match the url in the browser.
method The browsers request method (example: GET, POST, PUT, PATCH, DELETE etc).
host The website host (example: domain.com).
loaded_routes List of all the routes that matched the url and that has been rendered/loaded.
all_routes All available routes
boot_managers All available BootManagers
csrf_verifier CsrfVerifier class
log List of debug messages/log from the router.
router_output The rendered callback output from the router.
library_version The version of simple-php-router you are using.
php_version The version of PHP you are using.
server_params List of all $_SERVER variables/headers.

Benchmark and logging

You can activate benchmark debugging/logging by calling setDebugEnabled method on the Router instance.

You have to enable debugging BEFORE starting the routing.

Example:

SimpleRouter::router()->setDebugEnabled(true);
SimpleRouter::start();

When the routing is complete, you can get the debug-log by calling the getDebugLog() on the Router instance. This will return an array of log-messages each containing execution time, trace info and debug-message.

Example:

$messages = SimpleRouter::router()->getDebugLog();

Reporting a new issue

Before reporting your issue, make sure that the issue you are experiencing aren't already answered in the Common errors section or by searching the closed issues page on GitHub.

To avoid confusion and to help you resolve your issue as quickly as possible, you should provide a detailed explanation of the problem you are experiencing.

Procedure for reporting a new issue

  1. Go to this page to create a new issue.
  2. Add a title that describes your problems in as few words as possible.
  3. Copy and paste the template below in the description of your issue and replace each step with your own information. If the step is not relevant for your issue you can delete it.

Issue template

Copy and paste the template below into the description of your new issue and replace it with your own information.

You can check the Debug information section to see how to generate the debug-info.

### Description

The library fails to render the route `/user/æsel` which contains one parameter using a custom regular expression for matching special foreign characters. Routes without special characters like `/user/tom` renders correctly.

### Steps to reproduce the error

1. Add the following route:

```php
SimpleRouter::get('/user/{name}', 'UserController@show')->where(['name' => '[\w]+']);
```

2. Navigate to `/user/æsel` in browser.

3. `NotFoundHttpException` is thrown by library.

### Route and/or callback for failing route

*Route:*

```php
SimpleRouter::get('/user/{name}', 'UserController@show')->where(['name' => '[\w]+']);
```

*Callback:*

```php
public function show($username) {
    return sprintf('Username is: %s', $username);
}
```

### Debug info

```php

[PASTE YOUR DEBUG-INFO HERE]

```

Remember that a more detailed issue- description and debug-info might suck to write, but it will help others understand- and resolve your issue without asking for the information.

Note: please be as detailed as possible in the description when creating a new issue. This will help others to more easily understand- and solve your issue. Providing the necessary steps to reproduce the error within your description, adding useful debugging info etc. will help others quickly resolve the issue you are reporting.

Feedback and development

If the library is missing a feature that you need in your project or if you have feedback, we'd love to hear from you. Feel free to leave us feedback by creating a new issue.

Experiencing an issue?

Please refer to our Help and support section in the documentation before reporting a new issue.

Contribution development guidelines

  • Please try to follow the PSR-2 codestyle guidelines.

  • Please create your pull requests to the development base that matches the version number you want to change. For example when pushing changes to version 3, the pull request should use the v3-development base/branch.

  • Create detailed descriptions for your commits, as these will be used in the changelog for new releases.

  • When changing existing functionality, please ensure that the unit-tests working.

  • When adding new stuff, please remember to add new unit-tests for the functionality.


Credits

Sites

This is some sites that uses the simple-router project in production.

License

The MIT License (MIT)

Copyright (c) 2016 Simon Sessingø / simple-php-router

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Comments
  • Input-parsing and validation

    Input-parsing and validation

    Evening, Hello @skipperbent,

    after my PRs #497 and #519 are still not included into the main project, I would like to intoduce an what I think is a "better" method of allowing users like me customize their InputHandler to their own needs.

    Input Handler

    I created an example InputHandler for this PR in https://github.com/DeveloperMarius/router-input-handler-plugin (Not ready for a release, just a short example).

    You could easily include your own InputHandler using:

    $request = new \Pecee\Http\Request();
    $request->setInputHandler(new InputHandler());
    SimpleRouter::setRequest($request);
    
    $inputHandler = SimpleRouter::request()->getInputHandler();
    

    Normal users wouldn't recognice a difference.

    But i would preferr integrating my InputHandler. But this InputHandler would come with breaking changes. Maybe integrate it into v5 of the router.

    Input Validation

    Now you can validate inputs by adding the following code to the route:

    TestRouter::get('/my/test/url', 'DummyController@method3')
                ->validateInputs([
                        'username' => 'string|max:50',
                        'password' => 'string|max:20',
                        'remember_me' => 'integer|range:0,1'
                ]);
    

    or using a more OOP way:

    TestRouter::get('/my/test/url', 'DummyController@method3')
                ->validateInputs(
                    InputValidator::make()
                          ->add(InputValidatorItem::make('username')->isString()->max(50))
                          ->add(InputValidatorItem::make('password')->isString()->max(20))
                );
    

    or validate inside the controller function:

    $request->validate(
        InputValidator::make()
            ->add(InputValidatorItem::make('username')->isString()->max(50))
            ->add(InputValidatorItem::make('password')->isString()->max(20))
    );
    

    Additional you can use custom rules by providing the full classname:

    TestRouter::get('/my/test/url', 'DummyController@method3')
                ->validateInputs([
                    'customParam' => 'Dummy\InputValidatorRules\ValidatorRuleCustom'
                ]);
    

    or by defining a namespace for custom rules:

    InputValidator::setCustomValidatorRuleNamespace('Dummy\InputValidatorRules');
    TestRouter::get('/my/test/url', 'DummyController@method3')
                ->validateInputs([
                    'customParam' => 'custom'
                ]);
    

    For custom validator rules in a namespace, please name the classes ValidatorRule<rulename> where the rulename have to be in camel case. You can provide multiple attributes to the rule functions using the following syntax: <rule1>|<rule2>:<attr1>|<rule3>:<attr1>,<attr2>.

    My todo:

    • [x] Fix RegEx multiple attributes
    • [x] Better handling of Error Messages + getter
    • [x] Better formatting of Error Messages
    • [x] Transfer validator messages from https://github.com/DeveloperMarius/router-input-handler-plugin
    • [x] Parse validator rule tag in camel case from word_word to WordWord to allow Classnames like ValidatorRuleStartsWith (Tag would be starts_with)
    • [ ] README
    • [x] Email @skipperbent to ask him to review the PRs
    • [ ] Allow input keys like parentkey.childkey when parsing for example json data. This would be more easy with my InputHandler in my InputItem because there is the method getInputItems() which would make it access them way more easily.

    Referring to the last bullet point, I am currently thinking about throwing the custom InputHandler thing away and just implement mine. What do you think?

    When someone is interested in features, let me know! When someone wants to work on a point, let me know so that we don't work on the same one. After that please create a fork of my fork and create a PR in my fork. I will then take a look and merge it into this PR here. I would like to hear your feedback @skipperbent.

    ~ Marius

    opened by DeveloperMarius 34
  • Use getHeader('unencoded_url') for IIS instead of $this->getHeader('request-uri')

    Use getHeader('unencoded_url') for IIS instead of $this->getHeader('request-uri')

    Hi simon,

    I'm using IIS and i'm not sure if this issue happens also on Apache and another Web Servers.

    When i have one url with utf8 characters, like: http://www.misite.com/especialidad/Dermatolog%C3%ADa or http://www.misite.com/especialidad/Dermatología, i'm getting a strangely encoded version of Dermatología, but not Dermatología or Dermatolog%C3%ADa

    Strangely, for something like http://www.misite.com/?especialidad=Dermatolog%C3%ADa or http://www.misite.com/?especialidad=Dermatología i get Dermatología or Dermatolog%C3%ADa for $_GET['especialidad']

    I've researched and, apparently, IIS encodes somehow the part that corresponds to the path of the url, but not on querystring part.

    You can get more information here: https://github.com/tjanczuk/iisnode/pull/486 and here: https://forums.iis.net/t/1153679.aspx

    Then, i have made this change on __construct() method of Peece\Http\Request class: $this->uri = urldecode($this->getHeader('unencoded_url')) ?: $this->getHeader('request-uri');

    And it's working like a charm.

    unencoded_url seems to be present only when url contains utf8 characters.

    bug 
    opened by jatubio 30
  • Deprecated: call_user_func_array() expects parameter 1 to be a valid callback, non-static method

    Deprecated: call_user_func_array() expects parameter 1 to be a valid callback, non-static method

    I ve tried add simple route:

    SimpleRouter::post('/todo/add/', [\App\Controller\TodoController::class, 'add']);

    But have this deprecated message: Deprecated: call_user_func_array() expects parameter 1 to be a valid callback, non-static method App\Controller\TodoController::add() should not be called statically in

    My controller:

    <?php
    namespace App\Controller;
    
    class TodoController
    {
        public function add()
        {
            return 'Add function called';
        }
    }
    

    PHP version: 7.4.16 Nginx 1.19.10

    Is it normal?

    opened by Carsak 21
  • Nedd help with Class hitting

    Nedd help with Class hitting

    Hi, i'm always get this error below:

    Fatal error: Uncaught Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException: Class "\App\Controllers\App\Controllers\ServiceController" does not exist in /data/wwwroot/vendor/pecee/simple-router/src/Pecee/SimpleRouter/ClassLoader/ClassLoader.php:19 Stack trace: #0 /data/wwwroot/vendor/pecee/simple-router/src/Pecee/SimpleRouter/Route/Route.php(95): Pecee\SimpleRouter\ClassLoader\ClassLoader->loadClass() #1 /data/wwwroot/vendor/pecee/simple-router/src/Pecee/SimpleRouter/Router.php(410): Pecee\SimpleRouter\Route\Route->renderRoute() #2 /data/wwwroot/vendor/pecee/simple-router/src/Pecee/SimpleRouter/Router.php(339): Pecee\SimpleRouter\Router->routeRequest() #3 /data/wwwroot/vendor/pecee/simple-router/src/Pecee/SimpleRouter/SimpleRouter.php(68): Pecee\SimpleRouter\Router->start() #4 /data/wwwroot/index.php(15): Pecee\SimpleRouter\SimpleRouter::start() #5 {main} thrown in /data/wwwroot/vendor/pecee/simple-router/src/Pecee/SimpleRouter/ClassLoader/ClassLoader.php on line 19

    this is my index file:

    <?php
    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);
    
    require 'vendor/autoload.php';
    
    use Pecee\SimpleRouter\SimpleRouter;
    
    require_once 'helpers.php';
    require_once 'routes.php';
    
    SimpleRouter::setDefaultNamespace('\App\Controllers');
    
    SimpleRouter::start();
    

    this is my routes file:

    <?php
    
    use App\Controllers\ServiceController;
    use Pecee\SimpleRouter\SimpleRouter;
    
    SimpleRouter::get('/services', [ServiceController::class, 'showServices']);
    

    and this is my controller file:

    <?php
    
    
    namespace App\Controllers;
    
    
    class ServiceController
    {
    
        public static function showServices(): string
        {
            return "Hallo";
        }
    }
    

    what am I doing wrong here? I think I did everything just like in the wiki. Can you help me please?

    opened by MrPresidentWhite 17
  • GitHub actions

    GitHub actions

    Hey,

    you asked about a CI when the user are creating a pull request or you are pushing.

    I had to delete syntaxCheck="false", because it throws an error. But this option was useless according to the author of PHPUnit.

    The Problem is, that the PHPUnit tests are printing the following:

    PHPUnit 9.5.3 by Sebastian Bergmann and contributors. Warning: No code coverage driver available Warning: Your XML configuration validates against a deprecated schema. Suggestion: Migrate your XML configuration using "--migrate-configuration"! ...............Middleware loaded!............................................ 59 / 59 (100%)

    I'm not good with unit tests and don't know how to migrate this. I think it has something to do that you are using Version 6 and github/ php-actions/phpunit@v2 is using 9.5.3. Maby you can specify the unittest version?

    I will look more into it tomorrow. Maby you have an idea until then.

    ~ Marius

    feature 
    opened by DeveloperMarius 16
  • Input Validation

    Input Validation

    Hey,

    This pull requests contains a validation for InputItems and a requireParameters for the InputHandler. The developer can enable and disable the throwing of errors for the Input validation in Router::setValidationErrors. I maby would change this with the time to $input_errors and work more with errors in the InputHandler and add this to InputHandler::exists() that users who work a lot with the ExceptionHandler and error catching there have it easyer in their controllers.

    The requireParameters function can look like the following:

    SimpleRouter::request()->input()->requireParameters(array(
        'password',
        'new_password' => function($value){
            return $value->validator()->require()->minLength(6)->isString()->valid();
        }
    ));
    

    Maby I also would change the source and content for the error messages (maby using a config?). What do you think @skipperbent? What do you think in general of my approach? Yes using the check() method this is a bit complicated, but it's also simpler.

    ~ Marius

    enhancement 
    opened by DeveloperMarius 15
  • new inputhandler

    new inputhandler

    Hello @skipperbent ,

    while the project wasn't updated in a while I cloned your project into a private repository to change some things in the InputHandler. Due to the fact that you now seem to create a new version and include changes I want to ask you if you can apply my changes too?

    I also wanted to add that I do not changed the tests, but I think it has to be done due to the fact that I changed some methods.

    Let's begin:

    I stored the content-type header in the Request object to use it later in the InputHandler. Important is that this parameter has to be initialized before the InputHandler object is created.

    I made the main changes in the InputHandler. In the parseInputs() function I automatically load the POST vars if the method is post. Because when the request is not post, all values are stored in the request body. Lets come to the request body. I added two new parameters to the object. "body_plain" which basically stores the recieved body in plain text and the "body". "body" included all parsed body contents. Now I used the content-type from the request object to check if the body has a json or urlencoded content. In the result I just parsed the contents into InputItems. It is important to use the contains function, becuase you can have a content-type like "application/x-www-form-urlencoded; charset=UTF-8". Using ajax people can for example in a PATCH request submit a json object via the body or urlencoded contents. So I basically added support for that. Then I just added the body params to the find() and all() functions. I created a toValue() functions which simply parses a InputItem or an array of Input Items into a value or array. I used foreach $key => $value to add support of sequential and associative arrays. So if the array is sequential the index is simply used as key. This does not make any difference in handling these arrays:

    $arr = json_decode($input, true);
    $arr2 = array();
    foreach($arr as $key => $value){
        $arr2[$key] = $value;
    }
    echo json_encode($arr2); 
    
    $input = '["a","b","c"]'; -> ["a","b","c"]
    $input = '{"A":"a","B":"b","C":"c"}'; -> {"A":"a","B":"b","C":"c"}
    

    I think the biggest "change" was that the get(), post(), etc. functions now return the real mixed content and not the InputItem. I think you mostly need to work with the value and not with the InputItem. When you really need the InputItem you can not just use postItem(), getItem(), etc. This changes also impact the find() and all() function. I do not added a function to return the InputItems for these, but it can be added easily. I think this changes makes the value() function kind of unnessesary, but I'm not sure. I removed the repeatedly parsing of the $_GET, $_POST and body in the all function and just accessed the already parsed parameters, because I do not see an advantage in fetching them again. I'm now also returning null if the parameter is not in the request, but I think you already added that into your 4.3.0.0 version.

    The last change I made is with the InputItem and the value. I hated it, that when I submitted data from js via ajax I do get a different type when the data was urlencoded. So I added a parser that checks if the value is a boolean and then parses it. Also when I submitted a null value it never came to the request values or was an empty string. For that I just declerated all empty string as null. This is a big change but I think it is very usefull and when you just add a "can be null" to your db or check before inserting if the value is null and create an empty string it realy does the same.

    I think it is usefull when you change the value of the InputItems into a mixed type. It allowes us to work with booleans and integers instead of checking every time if it is an boolean $value === 'true'. Later on it would be great to add support for integers, but For now I'm interested in hearing what you think.

    In the end I just wanted to thank you for this awesome project and really hope, that my changes are interesting for you.

    Your sincerely,

    Marius K.

    feature 
    opened by DeveloperMarius 15
  • Version 4.3.0.0

    Version 4.3.0.0

    Breaking changes

    THIS UPDATE CONTAINS BREAKING CHANGES! PLEASE READ THE NOTES CAREFULLY BEFORE UPDATING ANY LIVE ENVIRONMENT.

    • php-di no longer integrated by default php-di integration is still possible, however a custom class-loader is now used to create integrations with frameworks of choice. See the "Class Loader" section in the documentation for more information and examples on how to migrate your existing php-di integration.

    • Return type for input() helper The input helper function and associated method InputHandler::value has been fixed to return the raw value as originally intended. As a result IInputItem is no longer returned when calling input(). All references to input('my-input')->getValue() should therefore be changed to input('my-input'). The IInputItem object can still be accessed by using input()->find('my-input') instead.

    Changelog

    • Feature: Added better ip-parsing when calling Request::getIp() and added new optional $safeMode parameter.
    • Feature: Added Request::getContentType for content-type header-parsing.
    • Feature: Added better support for nested file/arrays in InputHandler.
    • Feature: Added support for GitHub actions thanks to @DeveloperMarius
    • Feature #438: Added support for mixed value types in InputItem.
    • Feature #477.: Removed php-di
    • Feature #452: Added option to disable multi-route rendering by calling Router::setRenderMultipleRoutes($bool)).
    • Feature: Added alias for easier access SimpleRouter::enableMultiRouteRendering($bool).
    • Feature #491: Added support for class hinting on routes.
    • Feature #453: Added option to get/set the filterEmptyParams option on IRoute classes.
    • Feature #446 #507: Default-namespace optimisations.
    • Feature: Added new Request::getFirstHeader method that returns the first header found from array list- used to simplify the code.
    • Feature: Added new InputHandler::getValueFromArray method that loops through input-items to ensure that value is always returned when calling the InputHandler->value() method.
    • Feature: Added new ClassNotFoundHttpException thrown when class is not found.
    • Feature: Added support for objects like array etc as default-value. Value is now less strict and accept mixed objects.
    • Feature: Added $_FILE support for Request::all method.
    • Feature: Parameters are by default now using regex [\w\-]+ (supports dashes) to avoid any confusion.
    • Feature #461: input()->all() will now always return keys defined. If the key doesn't exists the value will be set to null.
    • Feature: Added Request::isPostBack helper method that returns true if request-method is of type that could contain data in body.
    • Feature: Optimized namespace handling. When namespaces starts with \ they will always overwrite the default-namespace.
    • Issue #446: Router::setDefaultNamespace() no longer has to be set in the beginning of routes.php.
    • Issue #468: Fixed group not matching domain when using domain with no parameters.
    • Issue #439: Fixed multiple request-type on same routes.
    • Issue #456: Fixed issue with child groups not loading when using partialGroups.
    • Issue #437: Fixed CSRF-token returning null on first refresh after cookies are removed.
    • Issue #503: Fixed issue with custom-regex maching both host-name and url.
    • Issue #448: Fixed findRoute not working in BootManager.
    • Issue #450: Fixed issue with cookie-expiration timestamp for 32-bit PHP versions.
    • Issue #449: Fix 'must be an instance of Closure, array given' error when $closure is a object method.
    • Issue: Fixed calling SimpleRouter::getUrl with array as parameters option would throw an error.
    • Issue: Fixed SimpleRouter::getUrl having wrong nullable return type.
    • Issue: Fixed typo in getIp method when server is using the x-forwarded-for header.
    • Issue: Fixed possible bug causing InputHandler not to get the correct request-method.
    • Simplified constructor in Request class.
    • ClassNotFoundHttpException is now thrown when class/method is not found (backwards compatible).
    • Removed all references to php-di from composer + code.
    • Added tryParse argument to the Request->getHeader method. When enabled the method will try to parse headers from both server and client-side (enabled by default).
    • Simplified references that checks for both variants of header (http/non http).
    • Simplified getIp method of the Request-class.
    • Optimized InputHandler to better support for nested values.
    • Removed unused exception from PHP-docs.
    • Fixed types not same as declared.
    • Removed unnecessary type casting.
    • Declared functions as static (better scoping + performance).
    • Moved \is_callable($callback) === false as the execution costs less than previous in Router.php.
    • Changed ob_get_contents to ob_get_clean.
    • Added type hints to methods parameters/return type.
    • Moved request-types constants from abstract Route class to global Request-class and changed references.
    • Changed code to use new global request-type constants.
    • Optimized InputHandler class so it only parses inputs once when calling all-method.
    • Forced csrf-token post-value are now available for all Request::$requestTypePost request-methods.
    • Change variable name $values to $settings in Route::setSettings and related methods.
    • Updated link to demo-project in README.
    • Updated README.
    • Removed unused class references.
    • Removed escape from - in reg-ex as it's only required when next to character-class.
    • Removed legacy .yml configuration.
    • Removed .idea folder.
    • Other minor cleanup, bugfixes & optimisations.
    • Tests: Added unit-tests for default-namespace tests (rewrite + append cases).
    • Tests: Added more comprehensive php-unit tests for bootmanagers including findUrl.
    • Tests: Changed TestRouter so host-name is always set when using php-unit.
    • Tests: Added php-unit-test for input()->all() method.
    • Tests: Fixed issues with reg-ex and php-unit tests.
    • Tests: Added ClassLoader php-unit tests.
    • Tests: Added php-unit tests for enabled/disabled multi-routing.
    • Tests: Added file tests for InputHandler.
    • Tests: Added unit-tests for Request::getContentType parsing.
    • Tests: Added unit tests for file arrays.
    • Tests: Added unit-tests for Request::getIp.
    • Tests: Added unit-tests for group domain when using domain with no parameter.
    awaiting-release 
    opened by skipperbent 15
  • Route with parameters don't work

    Route with parameters don't work

    Here is my code:

    <?php
    
    require "./vendor/autoload.php";
    
    use \Pecee\SimpleRouter\SimpleRouter as Route;
    
    Route::get('/', function() {
        echo "index";
    });
    
    Route::get('/{foo}', function($foo) {
        echo $foo;
    });
    
    Route::start();
    

    When I access "/", it works fine. But when I access the URL "http://localhost/shit", it will throw an exception:

    Fatal error: Uncaught exception 'Pecee\SimpleRouter\RouterException' with message 'Route not found: /shit' in E:\wwwroot\route-test\vendor\pecee\simple-router\src\Pecee\SimpleRouter\RouterBase.php:131 Stack trace: #0 E:\wwwroot\route-test\vendor\pecee\simple-router\src\Pecee\SimpleRouter\SimpleRouter.php(24): Pecee\SimpleRouter\RouterBase->routeRequest() #1 E:\wwwroot\route-test\index.php(21): Pecee\SimpleRouter\SimpleRouter::start() #2 {main} thrown in E:\wwwroot\route-test\vendor\pecee\simple-router\src\Pecee\SimpleRouter\RouterBase.php on line 131

    What's the problem?

    opened by prinsss 15
  • Get the ip when the user is using a proxy

    Get the ip when the user is using a proxy

    Hello,

    multiple sources also use the HTTP_CLIENT_IP header with the HTTP_X_FORWARDED_FOR to check if the user is using a proxy. Here is an explaination, what the difference is and here is an example that explains the headers. But as pointed out in this comment and with some thinking back to our custom/ client headers with the http- prefix, the user is able to edit this header. This could lead to security issues. I know only some people can do this, but it is still an issue.

    I added the cloudflare ip header to the non save headers, because a user can set this header when the website isn't using cloudflare and like the other two headers, submit any value he wants.

    ~ Marius

    opened by DeveloperMarius 13
  • Route::error not working

    Route::error not working

    Hello.

    IDK what I'm doing wrong but I can't get the error handling working well. I've placed all the routes as simple as possible to try the error get working but I can't. The normal routes work well just the error handling don't.

    Here is my Routes file.

      namespace Routes;
      use \Pecee\SimpleRouter\SimpleRouter as Router;
      
      include_once 'Controls/Errors.php';
    
      Router::get('/', function() {
          return 'Hello world';
      });
      Router::get('/{id}', function($id) {
          return $id;
      });
      Router::get('/App/{id}', function($id) {
          return $id;
      });
      
      Router::get('/not-found', 'Errors@notFound');
      Router::error(function(Request $request, \Exception $exception) {
          if($exception instanceof NotFoundHttpException && $exception->getCode() === 404) {
              response()->redirect('/not-found');
          }
      });
    

    here is what I have on "Controls/Error.php"

    namespace Controls;
    
    class Errors
    {
        public function notFound():string{
            return "Not found";
        }
    }
    

    I always get this error instead of the error handle response I want: Fatal error: Uncaught Pecee\SimpleRouter\Exceptions\NotFoundHttpException: Route not found: "/asd/asd/s/" in **PATH TO LIB**\vendor\pecee\simple-router\src\Pecee\SimpleRouter\Router.php:450 Stack trace: #0 **PATH TO LIB**\vendor\pecee\simple-router\src\Pecee\SimpleRouter\Router.php(339): Pecee\SimpleRouter\Router->routeRequest() #1 **PATH TO LIB**\vendor\pecee\simple-router\src\Pecee\SimpleRouter\SimpleRouter.php(68): Pecee\SimpleRouter\Router->start() #2 **PATH TO LIB**\Pecee\SimpleRouter\SimpleRouter::start() #3 **MAIN PATH**\index.php(20): Router::read(false) #4 {main} thrown in **PATH TO LIB**\vendor\pecee\simple-router\src\Pecee\SimpleRouter\Router.php on line 450

    Here is my index.php

    require_once '**PATH TO LIB**/Router.php';
    Router::read(false);
    

    Here is 'PATH TO LIB/Router.php'

    require_once '**PATH TO LIB**/vendor/autoload.php';
    use \Pecee\SimpleRouter\SimpleRouter;
    
    class Router extends SimpleRouter 
    {
        public static function read($isRunningLocally)
        {
            $prefix = ($isRunningLocally) ? '/test' : null;
            static::group(['prefix' => $prefix], function() {
                require_once 'Routes.php';
            });
            parent::start();
        }
    }
    

    Could you please help me guys?.

    bug awaiting-release 
    opened by kztneda 12
  • InputItem::offset returntype

    InputItem::offset returntype

    Deprecated: Return type of Pecee\Http\Input\InputItem::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

    Fix: Add the #[\ReturnTypeWillChange] attribute to the offsetGet method or add return type to be bool|null

    opened by Reviewmaximizer2022 1
  • Loaded route not available to middelware?

    Loaded route not available to middelware?

    I'm using the latest version of this package (4.3.7.2)

    I just tried to create a small middleware to check if a user is authorized to do a certain action. I was using SimpleRouter::request()->getLoadedRoute()->getParameters() inside the middleware to get the parameters from the route, but that failed since the value from getLoadedRoute() was null.

    An easy fix would be to move Router.php line 408 ($this->request->addLoadedRoute($route);) up before the event EVENT_MATCH_ROUTE is triggered to make the loaded route available to the events. I haven't noted any negative side effects by making this change.

    But the question is if this is 'by design' or if I should open a pull request?

    https://user-images.githubusercontent.com/4438511/196339422-e62d4ed0-569e-4978-8b6c-b2920ba1560f.mov

    opened by esbenboye 1
  • Inject something between route definition and final return? (Response-middleware)

    Inject something between route definition and final return? (Response-middleware)

    Brief elaboration on my use case(s):

    When running my unit tests (environment = testing) it would be an advantage if I could return an entire response object from my controller, so I could analyse response headers (e.g. status code) and content.

    When running in any other environment I would want my response to just return the rendered html without having to modify all my controller to have an if/else to check environment.

    I also think it could be advantageous if I could just return a model from my controller and then have the "response middleware" check if the request accepted json or xml and then do the appropriate conversion, so I don't have to think about it in the controller.

    Is this possible somehow?

    opened by esbenboye 0
  • Can't get full url instead of root/article/33

    Can't get full url instead of root/article/33

    I'm trying to make it show full url (for example: https://site.com/article/33) instead of root/article/33 but the Router::getUrl() returns an object instead of string and you know...

    $out = url('search',null, ['q' => 'mitsubishi']);
    dump($out);
    

    How can I do that way simple? https://site.com/article/33

    opened by amirandev 2
  • A

    A "1" ends up in the rendered markup when using this router

    I have a simple PHP site I was modernizing (mostly just to see if I could). I found this routing library that seemed good and I wanted to replace the one I wrote because this one seemed better. However, when I added it in, everything routed correctly, but I got the number "1" appearing in the markup of every page, just before the end of the body tag. When I switched back to my custom router it went away.

    Here is the source code to the project in question. src/routes.php contains the routes, public/index.php is the main index file. Also I was using php -S 0.0.0.0:80 -t public/ to serve the app in development if that's important (didn't want to have to set up a whole Apache/Nginx server in docker in order to develop the app. Reconsidering that lately but this should probably work correctly with PHP's built-in server regardless).

    opened by leggettc18 0
Releases(4.3.7.2)
  • 4.3.7.2(Jul 17, 2021)

  • 4.3.7.1(Jul 17, 2021)

  • 4.3.7.0(Jul 17, 2021)

    • Added new Group attribute mergeExceptionHandlers to prevent router from merging inherited exception-handlers (issue: #573).
    • RouteGroup: Added setMergeExceptionHandlers and getMergeExceptionHandlers methods.
    • IRouteGroup: Added setMergeExceptionHandlers and getMergeExceptionHandlers method.
    • Updated documentation to reflect changes.
    • Added unit-tests.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.6.2(Jul 16, 2021)

  • 4.3.6.1(Jun 15, 2021)

    • Fixed DebugHandler::fireEvent not providing correct arguments when calling fireEvents.
    • Fixed custom regex setMatch not setting parsed parameters correctly (issue: #566).
    • Added unit-tests for catching issue in the future.
    • Added php-stan typehints.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.6.0(Jun 9, 2021)

    • Fixed issue causing default-namespace to add duplicate namespace when using type-hints (issue: #561).
    • Fixed phpstan issues.
    • Tests: Fixed TestRouter not resetting namespace upon reset.
    • Tests: Added NSController (namespace controller) class.
    • Tests: added test for class hint + default namespace case.
    • Composer: added phpstan support + configuration.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.5.0(May 20, 2021)

    • Feature: Added support for InputHandler::exists to check array of indexes exists.
    • Simplified RouteController and RouteResource by moving common group-match code to parent method.
    • Updated documentation to reflect changes.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.4.2(May 19, 2021)

    • Issue #551: Fixed issue with SimpleRouter::error not firing within group.
    • Fixed variable incorrect variable reference in InputItem class.
    • Added new Router::addExceptionHandler method.
    • Added parameter types in Url class.
    • Fixed phpdoc parameter-type for Request::getHeader.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.4.1(May 19, 2021)

    • Issue #551: fixed issue causing SimpleRouter::error helper not to work when used within a group.
    • Tests: added unit test for nested group calls to SimpleRouter::error.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.4.0(May 18, 2021)

    • InputItem can now be used like array (for example: input()->post('items')[0]) if value is array.
    • Changed default-value parameter for get, post and file methods to allow for mixed object as return-type (input()->post('form', new InputItem('post')).
    • Fixed InputItem->__toString() to correctly parse null default-value.
    • Tests: changed parameter in call_user_func_array for PHP 8 compatibility in unit-tests.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.3.0(May 2, 2021)

    • [FEATURE] Namespace overwrite now works globally. Service will append to any existing namespace whereas \Service will overwrite it.
    • [FEATURE] Exception handlers are now rendered in reverse order from newest to oldest. This allows for exceptions to be handled from parent exceptions which were otherwise ignored.
    • Fixed incorrect return type for InputFile::getError to correct nullable int.
    • Added return type to all, match, controller and resource in SimpleRouter class.
    • Fixed incorrect return-type usage of parse_str function in Url::setQueryString method.
    • Fixed incorrect expected value in array_flip function in Url::removeParams method.
    • Tests: added namespace overwrite group unit-tests.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.2.3(Apr 28, 2021)

  • 4.3.2.2(Apr 28, 2021)

  • 4.3.2.1(Apr 1, 2021)

    Changelog

    • Feature: Added optional includeParams parameter to Url::getRelativeUrl and Url::getAbsoluteUrl methods.
    • Issue: Fixed issue with BaseCsrfVerifier matching urls against urls with parameters.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.2.0(Apr 1, 2021)

    Changelog

    • Feature: Added IP-access restrictions with credits to @DeveloperMarius.
    • Feature: Added new include property to BaseCsrfVerifier for routes that should be allowed when used in conjunction with the exclude property and url-ranges (like /admin/*).
    • Feature: Added https scheme support to Request::setUri (used when calling $request->getUrl()->getAbsoluteUrl().
    • Feature: Added isSubRoute event parameter for EVENT_ADD_ROUTE.
    • Feature: Added method IGroupRoute::prependPrefix.
    • Feature: Added custom base path example to the documentation.
    • Tests: Added BaseCsrfVerifier unit-tests.
    • Tests: Added custom base path event unit-tests.
    • Updated documentation with changes & updates.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.1.0(Mar 29, 2021)

    Release notes

    • Router now uses IClassLoader to load class methods. If you use a custom class-loader, make sure that you implement the new loadClassMethod from the IClassLoader interface.

    Changelog

    • Feature: Added class + method loading to IClassLoader.
    • Feature: Added support for parameters in group prefix.
    • Issue: Fixed PHP 8 compatibility issues with call_user_func_array in ClassLoader.
    • Issue: Fixed parameters from parent-routes not correctly passed to child routes.
    • Issue: Fixed possible error causing parameters not to be set properly when using some groups.
    • Issue: Fixed InputHandler::find and InputHandler::value failing when using array in methods parameter.
    • Issue: Fixed csrf-token postback not being recognized.
    • Cleanup: Removed unused import reference.
    • Tests: Added more partial-group tests.
    • Tests: Added deep route-parameters pass unit-test
    • Tests: Added InputHandler::find tests.
    • Updated documentation to reflect changes.
    Source code(tar.gz)
    Source code(zip)
  • 4.3.0.0(Mar 28, 2021)

    Breaking changes

    THIS UPDATE CONTAINS BREAKING CHANGES! PLEASE READ THE NOTES CAREFULLY BEFORE UPDATING ANY LIVE ENVIRONMENT.

    • php-di no longer integrated by default php-di integration is still possible, however a custom class-loader is now used to create integrations with frameworks of choice. See the "Class Loader" section in the documentation for more information and examples on how to migrate your existing php-di integration.

    • Return type for input() helper The input helper function and associated method InputHandler::value has been fixed to return the raw value as originally intended. As a result IInputItem is no longer returned when calling input('name'). All references to input('my-input')->getValue() should therefore be changed to input('my-input'). The IInputItem object can still be accessed by using input()->find('my-input') instead.

    Changelog

    • Feature: Added better ip-parsing when calling Request::getIp() and added new optional $safeMode parameter.
    • Feature: Added Request::getContentType for content-type header-parsing.
    • Feature: Added better support for nested file/arrays in InputHandler.
    • Feature: Added support for GitHub actions thanks to @DeveloperMarius
    • Feature #438: Added support for mixed value types in InputItem.
    • Feature #477: Removed php-di
    • Feature #452: Added option to disable multi-route rendering by calling Router::setRenderMultipleRoutes($bool)).
    • Feature: Added alias for easier access SimpleRouter::enableMultiRouteRendering($bool).
    • Feature #491: Added support for class hinting on routes.
    • Feature #453: Added option to get/set the filterEmptyParams option on IRoute classes.
    • Feature #446 #507: Default-namespace optimisations.
    • Feature: Added new Request::getFirstHeader method that returns the first header found from array list- used to simplify the code.
    • Feature: Added new InputHandler::getValueFromArray method that loops through input-items to ensure that value is always returned when calling the InputHandler->value() method.
    • Feature: Added new ClassNotFoundHttpException thrown when class is not found.
    • Feature: Added support for objects like array etc as default-value. Value is now less strict and accept mixed objects.
    • Feature: Added $_FILE support for Request::all method.
    • Feature: Parameters are by default now using regex [\w\-]+ (supports dashes) to avoid any confusion.
    • Feature #461: input()->all() will now always return keys defined. If the key doesn't exists the value will be set to null.
    • Feature: Added Request::isPostBack helper method that returns true if request-method is of type that could contain data in body.
    • Feature: Optimized namespace handling. When namespaces starts with \ they will always overwrite the default-namespace.
    • Issue #446: Router::setDefaultNamespace() no longer has to be set in the beginning of routes.php.
    • Issue #468: Fixed group not matching domain when using domain with no parameters.
    • Issue #439: Fixed multiple request-type on same routes.
    • Issue #456: Fixed issue with child groups not loading when using partialGroups.
    • Issue #437: Fixed CSRF-token returning null on first refresh after cookies are removed.
    • Issue #503: Fixed issue with custom-regex maching both host-name and url.
    • Issue #448: Fixed findRoute not working in BootManager.
    • Issue #450: Fixed issue with cookie-expiration timestamp for 32-bit PHP versions.
    • Issue #449: Fix 'must be an instance of Closure, array given' error when $closure is a object method.
    • Issue: Fixed calling SimpleRouter::getUrl with array as parameters option would throw an error.
    • Issue: Fixed SimpleRouter::getUrl having wrong nullable return type.
    • Issue: Fixed typo in getIp method when server is using the x-forwarded-for header.
    • Issue: Fixed possible bug causing InputHandler not to get the correct request-method.
    • Simplified constructor in Request class.
    • ClassNotFoundHttpException is now thrown when class/method is not found (backwards compatible).
    • Removed all references to php-di from composer + code.
    • Added tryParse argument to the Request->getHeader method. When enabled the method will try to parse headers from both server and client-side (enabled by default).
    • Simplified references that checks for both variants of header (http/non http).
    • Simplified getIp method of the Request-class.
    • Optimized InputHandler to better support for nested values.
    • Removed unused exception from PHP-docs.
    • Fixed types not same as declared.
    • Removed unnecessary type casting.
    • Declared functions as static (better scoping + performance).
    • Moved \is_callable($callback) === false as the execution costs less than previous in Router.php.
    • Changed ob_get_contents to ob_get_clean.
    • Added type hints to methods parameters/return type.
    • Moved request-types constants from abstract Route class to global Request-class and changed references.
    • Changed code to use new global request-type constants.
    • Optimized InputHandler class so it only parses inputs once when calling all-method.
    • Forced csrf-token post-value are now available for all Request::$requestTypePost request-methods.
    • Change variable name $values to $settings in Route::setSettings and related methods.
    • Updated link to demo-project in README.
    • Updated README.
    • Removed unused class references.
    • Removed escape from - in reg-ex as it's only required when next to character-class.
    • Removed legacy .yml configuration.
    • Removed .idea folder.
    • Other minor cleanup, bugfixes & optimisations.
    • Tests: Added unit-tests for default-namespace tests (rewrite + append cases).
    • Tests: Added more comprehensive php-unit tests for bootmanagers including findUrl.
    • Tests: Changed TestRouter so host-name is always set when using php-unit.
    • Tests: Added php-unit-test for input()->all() method.
    • Tests: Fixed issues with reg-ex and php-unit tests.
    • Tests: Added ClassLoader php-unit tests.
    • Tests: Added php-unit tests for enabled/disabled multi-routing.
    • Tests: Added file tests for InputHandler.
    • Tests: Added unit-tests for Request::getContentType parsing.
    • Tests: Added unit tests for file arrays.
    • Tests: Added unit-tests for Request::getIp.
    • Tests: Added unit-tests for group domain when using domain with no parameter.
    Source code(tar.gz)
    Source code(zip)
  • 4.2.0.6(Nov 24, 2018)

    • Fix for __invoke methods (issue: #429)
    • Fixed not being able to parse body of PUT requests.
    • BaseCsrfVerifier expects the field name to be "csrf-token" (issue: #432)
    • Minor optimisations
    Source code(tar.gz)
    Source code(zip)
  • 4.2.0.5(Aug 30, 2018)

    • Settings parameter in group method are no longer optional.
    • Updated README to contain PHP JSON-extension under requirements.
    • Updated composer.json to include php json extension.
    • Minor optimisations.
    Source code(tar.gz)
    Source code(zip)
  • 4.2.0.4(Aug 24, 2018)

  • 4.2.0.3(Apr 22, 2018)

  • 4.2.0.2(Apr 6, 2018)

    • Fixed 403 (not allowed or not found) exception is now thrown as NotFoundHttpException.
    • Added REQUEST_TYPE_HEAD to Route class.
    • Minor optimizations.
    Source code(tar.gz)
    Source code(zip)
  • 4.2.0.1(Apr 6, 2018)

  • 4.2.0.0(Apr 6, 2018)

    • Added new SimpleRouter::redirect method.
    • Changed method-names in InputHandler for better description.
    • Fixed return-types for InputHandler when retrieving collections.
    • Added unit-tests for InputHandler (get, post).
    • Optimisations and bugfixes.
    • Updated documentation.

    Changelog

    Methods in InputHandler has changed to provide better support for object-types.

    Source code(tar.gz)
    Source code(zip)
  • 4.1.0.0(Apr 2, 2018)

    • Added new event when adding route.
    • Added prependUrl method to LoadableRoute class.
    • Added unit-test for add-route event.
    • Updated documentation to reflect new changes.
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0.13(Apr 1, 2018)

  • 4.0.0.12(Apr 1, 2018)

    • Fixed hasParam not working returning expected value in Url class.
    • Chained the remaining methods in the Url class.
    • Simplified removeParam method in Url class.
    • Added new removeParams method to Url class for removal of multiple params.
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0.9(Mar 30, 2018)

  • 4.0.0.11(Mar 30, 2018)

  • 4.0.0.10(Mar 30, 2018)

Owner
Simon Sessingø
Simon Sessingø
PHP Router class - A simple Rails inspired PHP router class.

PHP Router class A simple Rails inspired PHP router class. Usage of different HTTP Methods REST / Resourceful routing Reversed routing using named rou

Danny van Kooten 565 Jan 8, 2023
PhpRouter is a powerful, lightweight, and very fast HTTP URL router for PHP projects.

PhpRouter PhpRouter is a powerful, lightweight, and very fast HTTP URL router for PHP projects. Some of the provided features: Route parameters Predef

Milad Rahimi 152 Dec 28, 2022
PHPRouter is an easy-to-use, fast, and flexible PHP router package with express-style routing.

PHP-Router is a modern, fast, and adaptable composer package that provides express-style routing in PHP without a framework.

Ayodeji O. 4 Oct 20, 2022
klein.php is a fast & flexible router for PHP 5.3+

Klein.php klein.php is a fast & flexible router for PHP 5.3+ Flexible regular expression routing (inspired by Sinatra) A set of boilerplate methods fo

null 2.6k Jan 7, 2023
:tada: Release 2.0 is released! Very fast HTTP router for PHP 7.1+ (incl. PHP8 with attributes) based on PSR-7 and PSR-15 with support for annotations and OpenApi (Swagger)

HTTP router for PHP 7.1+ (incl. PHP 8 with attributes) based on PSR-7 and PSR-15 with support for annotations and OpenApi (Swagger) Installation compo

Sunrise // PHP 151 Jan 5, 2023
Pux is a fast PHP Router and includes out-of-box controller tools

Pux Pux is a faster PHP router, it also includes out-of-box controller helpers. 2.0.x Branch Build Status (This branch is under development) Benchmark

Yo-An Lin 1.3k Dec 21, 2022
A lightweight and fast router for PHP

Piko Router A lightweight and blazing fast router (see benchmarks) using a radix trie to store dynamic routes. This router maps routes to user defined

Piko framework 62 Dec 27, 2022
Fast request router for PHP

FastRoute - Fast request router for PHP This library provides a fast implementation of a regular expression based router. Blog post explaining how the

Nikita Popov 4.7k Dec 23, 2022
A fast & flexible router

Klein.php klein.php is a fast & flexible router for PHP 5.3+ Flexible regular expression routing (inspired by Sinatra) A set of boilerplate methods fo

null 2.6k Dec 28, 2022
A lightweight and simple object oriented PHP Router

bramus/router A lightweight and simple object oriented PHP Router. Built by Bram(us) Van Damme (https://www.bram.us) and Contributors Features Support

Bramus! 935 Jan 1, 2023
:bird: Simple PHP router

Macaw Macaw is a simple, open source PHP router. It's super small (~150 LOC), fast, and has some great annotated source code. This class allows you to

Noah Buscher 895 Dec 21, 2022
A simple PHP Router

Panda Router Description the panda-router is a small alternative PHP router that can be used for small projects. With this router you can use differen

Jan Behrens 1 Dec 27, 2021
The simple PHP router

Macaw Macaw is a simple, open source PHP router. It's super small (~150 LOC), fast, and has some great annotated source code. This class allows you to

Noah Buscher 895 Dec 21, 2022
Simple and minimal yet another PHP 7 Framework

DemirApp Minimal PHP Framework Introduction Simple and minimal yet another PHP 7 Framework Features Simple routing Simple container (Dependency Inject

yidemir 12 Sep 19, 2022
Toro is a PHP router for developing RESTful web applications and APIs.

Toro Toro is a PHP router for developing RESTful web applications and APIs. It is designed for minimalists who want to get work done. Quick Links Offi

Kunal Anand 1.2k Dec 27, 2022
Thruway - an open source client and router implementation of WAMP (Web Application Messaging Protocol), for PHP.

PHP Client and Router Library for Autobahn and WAMP (Web Application Messaging Protocol) for Real-Time Application Messaging

Voryx 661 Nov 14, 2022
A lightweight and very basic PHP router.

Katya A lightweight PHP router Configuration Para servidor Apache, en el directorio del proyecto crea y edita un archivo .htaccess con lo siguiente: <

Luis Rodríguez 0 Apr 4, 2022
A web router implementation for PHP.

Aura.Router Powerful, flexible web routing for PSR-7 requests. Installation and Autoloading This package is installable and PSR-4 autoloadable via Com

Aura for PHP 469 Jan 1, 2023
A PHP Router Package

Router A PHP Router Package Basic Concepts A router package is a utility that, once all http requests are redirected to an entry point, can configure

null 0 Aug 26, 2022