• Stars
    star
    1,085
  • Rank 42,662 (Top 0.9 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 11 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A lightweight and simple object oriented PHP Router

bramus/router

Build Status Source Version Downloads License

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

Features

Prerequisites/Requirements

Installation

Installation is possible using Composer

composer require bramus/router ~1.6

Demo

A demo is included in the demo subfolder. Serve it using your favorite web server, or using PHP 5.4+'s built-in server by executing php -S localhost:8080 on the shell. A .htaccess for use with Apache is included.

Additionally a demo of a mutilingual router is also included. This can be found in the demo-multilang subfolder and can be ran in the same manner as the normal demo.

Usage

Create an instance of \Bramus\Router\Router, define some routes onto it, and run it.

// Require composer autoloader
require __DIR__ . '/vendor/autoload.php';

// Create Router instance
$router = new \Bramus\Router\Router();

// Define routes
// ...

// Run it!
$router->run();

Routing

Hook routes (a combination of one or more HTTP methods and a pattern) using $router->match(method(s), pattern, function):

$router->match('GET|POST', 'pattern', function() { … });

bramus/router supports GET, POST, PUT, PATCH, DELETE, HEAD (see note), and OPTIONS HTTP request methods. Pass in a single request method, or multiple request methods separated by |.

When a route matches against the current URL (e.g. $_SERVER['REQUEST_URI']), the attached route handling function will be executed. The route handling function must be a callable. Only the first route matched will be handled. When no matching route is found, a 404 handler will be executed.

Routing Shorthands

Shorthands for single request methods are provided:

$router->get('pattern', function() { /* ... */ });
$router->post('pattern', function() { /* ... */ });
$router->put('pattern', function() { /* ... */ });
$router->delete('pattern', function() { /* ... */ });
$router->options('pattern', function() { /* ... */ });
$router->patch('pattern', function() { /* ... */ });

You can use this shorthand for a route that can be accessed using any method:

$router->all('pattern', function() { … });

Note: Routes must be hooked before $router->run(); is being called.

Note: There is no shorthand for match() as bramus/router will internally re-route such requrests to their equivalent GET request, in order to comply with RFC2616 (see note).

Route Patterns

Route Patterns can be static or dynamic:

  • Static Route Patterns contain no dynamic parts and must match exactly against the path part of the current URL.
  • Dynamic Route Patterns contain dynamic parts that can vary per request. The varying parts are named subpatterns and are defined using either Perl-compatible regular expressions (PCRE) or by using placeholders

Static Route Patterns

A static route pattern is a regular string representing a URI. It will be compared directly against the path part of the current URL.

Examples:

  • /about
  • /contact

Usage Examples:

// This route handling function will only be executed when visiting http(s)://www.example.org/about
$router->get('/about', function() {
    echo 'About Page Contents';
});

Dynamic PCRE-based Route Patterns

This type of Route Patterns contain dynamic parts which can vary per request. The varying parts are named subpatterns and are defined using regular expressions.

Examples:

  • /movies/(\d+)
  • /profile/(\w+)

Commonly used PCRE-based subpatterns within Dynamic Route Patterns are:

  • \d+ = One or more digits (0-9)
  • \w+ = One or more word characters (a-z 0-9 _)
  • [a-z0-9_-]+ = One or more word characters (a-z 0-9 _) and the dash (-)
  • .* = Any character (including /), zero or more
  • [^/]+ = Any character but /, one or more

Note: The PHP PCRE Cheat Sheet might come in handy.

The subpatterns defined in Dynamic PCRE-based Route Patterns are converted to parameters which are passed into the route handling function. Prerequisite is that these subpatterns need to be defined as parenthesized subpatterns, which means that they should be wrapped between parens:

// Bad
$router->get('/hello/\w+', function($name) {
    echo 'Hello ' . htmlentities($name);
});

// Good
$router->get('/hello/(\w+)', function($name) {
    echo 'Hello ' . htmlentities($name);
});

Note: The leading / at the very beginning of a route pattern is not mandatory, but is recommended.

When multiple subpatterns are defined, the resulting route handling parameters are passed into the route handling function in the order they are defined in:

$router->get('/movies/(\d+)/photos/(\d+)', function($movieId, $photoId) {
    echo 'Movie #' . $movieId . ', photo #' . $photoId;
});

Dynamic Placeholder-based Route Patterns

This type of Route Patterns are the same as Dynamic PCRE-based Route Patterns, but with one difference: they don't use regexes to do the pattern matching but they use the more easy placeholders instead. Placeholders are strings surrounded by curly braces, e.g. {name}. You don't need to add parens around placeholders.

Examples:

  • /movies/{id}
  • /profile/{username}

Placeholders are easier to use than PRCEs, but offer you less control as they internally get translated to a PRCE that matches any character (.*).

$router->get('/movies/{movieId}/photos/{photoId}', function($movieId, $photoId) {
    echo 'Movie #' . $movieId . ', photo #' . $photoId;
});

Note: the name of the placeholder does not need to match with the name of the parameter that is passed into the route handling function:

$router->get('/movies/{foo}/photos/{bar}', function($movieId, $photoId) {
    echo 'Movie #' . $movieId . ', photo #' . $photoId;
});

Optional Route Subpatterns

Route subpatterns can be made optional by making the subpatterns optional by adding a ? after them. Think of blog URLs in the form of /blog(/year)(/month)(/day)(/slug):

$router->get(
    '/blog(/\d+)?(/\d+)?(/\d+)?(/[a-z0-9_-]+)?',
    function($year = null, $month = null, $day = null, $slug = null) {
        if (!$year) { echo 'Blog overview'; return; }
        if (!$month) { echo 'Blog year overview'; return; }
        if (!$day) { echo 'Blog month overview'; return; }
        if (!$slug) { echo 'Blog day overview'; return; }
        echo 'Blogpost ' . htmlentities($slug) . ' detail';
    }
);

The code snippet above responds to the URLs /blog, /blog/year, /blog/year/month, /blog/year/month/day, and /blog/year/month/day/slug.

Note: With optional parameters it is important that the leading / of the subpatterns is put inside the subpattern itself. Don't forget to set default values for the optional parameters.

The code snipped above unfortunately also responds to URLs like /blog/foo and states that the overview needs to be shown - which is incorrect. Optional subpatterns can be made successive by extending the parenthesized subpatterns so that they contain the other optional subpatterns: The pattern should resemble /blog(/year(/month(/day(/slug)))) instead of the previous /blog(/year)(/month)(/day)(/slug):

$router->get('/blog(/\d+(/\d+(/\d+(/[a-z0-9_-]+)?)?)?)?', function($year = null, $month = null, $day = null, $slug = null) {
    // ...
});

Note: It is highly recommended to always define successive optional parameters.

To make things complete use quantifiers to require the correct amount of numbers in the URL:

$router->get('/blog(/\d{4}(/\d{2}(/\d{2}(/[a-z0-9_-]+)?)?)?)?', function($year = null, $month = null, $day = null, $slug = null) {
    // ...
});

Subrouting / Mounting Routes

Use $router->mount($baseroute, $fn) to mount a collection of routes onto a subroute pattern. The subroute pattern is prefixed onto all following routes defined in the scope. e.g. Mounting a callback $fn onto /movies will prefix /movies onto all following routes.

$router->mount('/movies', function() use ($router) {

    // will result in '/movies/'
    $router->get('/', function() {
        echo 'movies overview';
    });

    // will result in '/movies/id'
    $router->get('/(\d+)', function($id) {
        echo 'movie id ' . htmlentities($id);
    });

});

Nesting of subroutes is possible, just define a second $router->mount() in the callable that's already contained within a preceding $router->mount().

Class@Method calls

We can route to the class action like so:

$router->get('/(\d+)', '\App\Controllers\User@showProfile');

When a request matches the specified route URI, the showProfile method on the User class will be executed. The defined route parameters will be passed to the class method.

The method can be static (recommended) or non-static (not-recommended). In case of a non-static method, a new instance of the class will be created.

If most/all of your handling classes are in one and the same namespace, you can set the default namespace to use on your router instance via setNamespace()

$router->setNamespace('\App\Controllers');
$router->get('/users/(\d+)', 'User@showProfile');
$router->get('/cars/(\d+)', 'Car@showProfile');

Custom 404

The default 404 handler sets a 404 status code and exits. You can override this default 404 handler by using $router->set404(callable);

$router->set404(function() {
    header('HTTP/1.1 404 Not Found');
    // ... do something special here
});

You can also define multiple custom routes e.x. you want to define an /api route, you can print a custom 404 page:

$router->set404('/api(/.*)?', function() {
    header('HTTP/1.1 404 Not Found');
    header('Content-Type: application/json');

    $jsonArray = array();
    $jsonArray['status'] = "404";
    $jsonArray['status_text'] = "route not defined";

    echo json_encode($jsonArray);
});

Also supported are Class@Method callables:

$router->set404('\App\Controllers\Error@notFound');

The 404 handler will be executed when no route pattern was matched to the current URL.

💡 You can also manually trigger the 404 handler by calling $router->trigger404()

$router->get('/([a-z0-9-]+)', function($id) use ($router) {
    if (!Posts::exists($id)) {
        $router->trigger404();
        return;
    }

    // …
});

Before Route Middlewares

bramus/router supports Before Route Middlewares, which are executed before the route handling is processed.

Like route handling functions, you hook a handling function to a combination of one or more HTTP request methods and a specific route pattern.

$router->before('GET|POST', '/admin/.*', function() {
    if (!isset($_SESSION['user'])) {
        header('location: /auth/login');
        exit();
    }
});

Unlike route handling functions, more than one before route middleware is executed when more than one route match is found.

Before Router Middlewares

Before route middlewares are route specific. Using a general route pattern (viz. all URLs), they can become Before Router Middlewares (in other projects sometimes referred to as before app middlewares) which are always executed, no matter what the requested URL is.

$router->before('GET', '/.*', function() {
    // ... this will always be executed
});

After Router Middleware / Run Callback

Run one (1) middleware function, name the After Router Middleware (in other projects sometimes referred to as after app middlewares) after the routing was processed. Just pass it along the $router->run() function. The run callback is route independent.

$router->run(function() { … });

Note: If the route handling function has exit()ed the run callback won't be run.

Overriding the request method

Use X-HTTP-Method-Override to override the HTTP Request Method. Only works when the original Request Method is POST. Allowed values for X-HTTP-Method-Override are PUT, DELETE, or PATCH.

Subfolder support

Out-of-the box bramus/router will run in any (sub)folder you place it into … no adjustments to your code are needed. You can freely move your entry script index.php around, and the router will automatically adapt itself to work relatively from the current folder's path by mounting all routes onto that basePath.

Say you have a server hosting the domain www.example.org using public_html/ as its document root, with this little entry script index.php:

$router->get('/', function() { echo 'Index'; });
$router->get('/hello', function() { echo 'Hello!'; });
  • If your were to place this file (along with its accompanying .htaccess file or the like) at the document root level (e.g. public_html/index.php), bramus/router will mount all routes onto the domain root (e.g. /) and thus respond to https://www.example.org/ and https://www.example.org/hello.

  • If you were to move this file (along with its accompanying .htaccess file or the like) into a subfolder (e.g. public_html/demo/index.php), bramus/router will mount all routes onto the current path (e.g. /demo) and thus repsond to https://www.example.org/demo and https://www.example.org/demo/hello. There's no need for $router->mount(…) in this case.

Disabling subfolder support

In case you don't want bramus/router to automatically adapt itself to the folder its being placed in, it's possible to manually override the basePath by calling setBasePath(). This is necessary in the (uncommon) situation where your entry script and your entry URLs are not tightly coupled (e.g. when the entry script is placed into a subfolder that does not need be part of the URLs it responds to).

// Override auto base path detection
$router->setBasePath('/');

$router->get('/', function() { echo 'Index'; });
$router->get('/hello', function() { echo 'Hello!'; });

$router->run();

If you were to place this file into a subfolder (e.g. public_html/some/sub/folder/index.php), it will still mount the routes onto the domain root (e.g. /) and thus respond to https://www.example.org/ and https://www.example.org/hello (given that your .htaccess file – placed at the document root level – rewrites requests to it)

Integration with other libraries

Integrate other libraries with bramus/router by making good use of the use keyword to pass dependencies into the handling functions.

$tpl = new \Acme\Template\Template();

$router->get('/', function() use ($tpl) {
    $tpl->load('home.tpl');
    $tpl->setdata(array(
        'name' => 'Bramus!'
    ));
});

$router->run(function() use ($tpl) {
    $tpl->display();
});

Given this structure it is still possible to manipulate the output from within the After Router Middleware

A note on working with PUT

There's no such thing as $_PUT in PHP. One must fake it:

$router->put('/movies/(\d+)', function($id) {

    // Fake $_PUT
    $_PUT  = array();
    parse_str(file_get_contents('php://input'), $_PUT);

    // ...

});

A note on making HEAD requests

When making HEAD requests all output will be buffered to prevent any content trickling into the response body, as defined in RFC2616 (Hypertext Transfer Protocol -- HTTP/1.1):

The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.

To achieve this, bramus/router but will internally re-route HEAD requests to their equivalent GET request and automatically suppress all output.

Unit Testing & Code Coverage

bramus/router ships with unit tests using PHPUnit.

  • If PHPUnit is installed globally run phpunit to run the tests.

  • If PHPUnit is not installed globally, install it locally throuh composer by running composer install --dev. Run the tests themselves by calling vendor/bin/phpunit.

    The included composer.json will also install php-code-coverage which allows one to generate a Code Coverage Report. Run phpunit --coverage-html ./tests-report (XDebug required), a report will be placed into the tests-report subfolder.

Acknowledgements

bramus/router is inspired upon Klein, Ham, and JREAM/route . Whilst Klein provides lots of features it is not object oriented. Whilst Ham is Object Oriented, it's bad at separation of concerns as it also provides templating within the routing class. Whilst JREAM/route is a good starting point it is limited in what it does (only GET routes for example).

License

bramus/router is released under the MIT public license. See the enclosed LICENSE for details.

More Repositories

1

react-native-maps-directions

Directions Component for `react-native-maps`
JavaScript
1,219
star
2

mixed-content-scan

Scan your HTTPS-enabled website for Mixed Content
PHP
522
star
3

photoshop-google-maps-tile-cutter

PS_Bramus.GoogleMapsTileCutter - A Photoshop script that cuts image tiles from any image for direct use with Google Maps
JavaScript
201
star
4

js-pagination-sequence

Generate a sequence of numbers for use in a pagination system, the clever way.
JavaScript
140
star
5

monolog-colored-line-formatter

Colored/ANSI Line Formatter for Monolog
PHP
132
star
6

freshinstall

Automatically configure and install software onto your new Mac
Shell
106
star
7

composer-autocomplete

Bash/Shell autocompletion for Composer
94
star
8

mastodon-redirector

View Mastodon profiles on your Mastodon instance
JavaScript
93
star
9

ansi-php

ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps
PHP
89
star
10

specificity

JavaScript Package to calculate the Specificity of CSS Selectors
JavaScript
68
star
11

viewport-resize-behavior

Viewport vs Virtual Keyboard Resize Behavior Explainer
45
star
12

simple-rest-api-explorer

A simple way to showcasing and exploring all endpoints of your RESTful API.
JavaScript
37
star
13

mercator-puzzle-redux

Dynamic take on the original Mercator Puzzle by Google
HTML
37
star
14

scroll-driven-animations-debugger-extension

Browser extension to debug Scroll-Driven Animations
36
star
15

ion-drawer-vertical

A vertical drawer for Ionic 1
JavaScript
35
star
16

sda-utilities

Collection of utility functions for use with Scroll-Driven Animations
JavaScript
28
star
17

style-observer

MutationObserver for CSS
TypeScript
26
star
18

view-transitions-demos

HTML
20
star
19

react-native-maps-directions-example

Example app that uses react-native-maps-directions
Java
20
star
20

ws2-sws-course-materials

Course materials for the course Serverside Webscripting, part of the Professional Bachelor ICT study programme.
PHP
13
star
21

chrome-dark-mode-toggle

Real control over Light Mode / Dark Mode on a per-site basis!
13
star
22

web-dev-rss

Cloudflare Worker that generates RSS Feeds for web.dev
JavaScript
12
star
23

webhid-elgato-stream-deck-daft-punk-soundboard

WebHID Demo: Elgato Stream Deck Daft Punk Soundboard
JavaScript
12
star
24

bramus_cssextras

bramus_cssextras is a plugin for TinyMCE which enables the usage of CSS classes and CSS ids on any HTML element within TinyMCE. Together with an external CSS file, set via the TinyMCE content_css setting, bramus_cssextras bridges the (visual) gap between the content entered through TinyMCE and the actual output
JavaScript
12
star
25

ria-course-materials

Course materials for the course Web & Mobile Development (formerly Rich Internet Applications), part of the Professional Bachelor ICT study programme.
JavaScript
12
star
26

ws1-sws-course-materials

Course materials for the course Serverside Webscripting, part of the Professional Bachelor ICT study programme.
PHP
11
star
27

css-houdini-voronoi

A CSS Houdini Paint Worklet that draws a Voronoi Diagram as a background image
JavaScript
10
star
28

google-maps-polygon-moveto

Programmatically move a google.maps.Polygon to a new google.maps.LatLng using Google Maps V3
JavaScript
9
star
29

js-range

JavaScript
7
star
30

ws2-sws-fiddles-silex

Silex Code Examples/Fiddles
PHP
7
star
31

css-houdini-circles

A CSS Houdini PaintWorklet to draw background circles.
JavaScript
7
star
32

PS_BRAMUS.TextConvert

PSD2TXT and TXT2PSD for the masses!
JavaScript
6
star
33

enumeration

Yet Another Enumeration Implementation for PHP
PHP
5
star
34

chrome-for-developers-rss

Cloudflare Worker that generates RSS Feeds for developer.chrome.com
JavaScript
4
star
35

wec-web-ui-summit-2023-demos

WEC Web UI Summit 2023 Demos
HTML
4
star
36

reflection

A library that tries to make PHP's built-in Reflection better.
PHP
3
star
37

github-toggle-chrome-extension

Quickly toggle between a GitHub Repo and its GitHub Pages
JavaScript
3
star
38

jquery_classdata

jQuery ClassData is a jQuery plugin that allows you to access and manipulate data saved in the class property of DOM-elements in a nice and easy to use way.
JavaScript
3
star
39

js-pagination-sequence-demos

Demos that use @bramus/pagination-sequence
JavaScript
2
star
40

css-selector-benchmark

CSS Selector Benchmarks, using PerfTestRunner and Puppeteer
HTML
2
star
41

wmd-course-materials

Course materials for the course Web & Mobile Development, part of the Professional Bachelor ICT study programme.
JavaScript
2
star
42

yummy

A self hosted Delicious (with del.icio.us import)
PHP
1
star
43

meetingzone

Find a meeting time across timezones
JavaScript
1
star
44

gcloud-kms-scripts

A collection of scripts here to help interact with Google's Cloud Key Management Service (KMS)
Shell
1
star
45

tokenphrase

TokenPhrase is a PHP class that generates unique phrases for you to use in your app as tokens
PHP
1
star
46

terraform-gcloud-event-cloud-function

Terraform deploy a local folder to a Google Cloud Function that can be triggered via an Event
HCL
1
star
47

remix-example-app

JavaScript
1
star
48

parcel-css-demo

JavaScript
1
star
49

bramus_cssextras-demo

Demo for bramus_cssextras (https://github.com/bramus/bramus_cssextras) | bramus_cssextras is a plugin for TinyMCE which enables the usage of CSS classes and CSS ids on any HTML element within TinyMCE. Together with an external CSS file, set via the TinyMCE content_css setting, bramus_cssextras bridges the (visual) gap between the content entered through TinyMCE and the actual output
1
star