PHPackages                             commandstring/router - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [CLI &amp; Console](/categories/cli)
4. /
5. commandstring/router

AbandonedArchivedLibrary[CLI &amp; Console](/categories/cli)

commandstring/router
====================

v3.4.3(3y ago)51011MITPHP

Since Oct 28Pushed 2y ago1 watchersCompare

[ Source](https://github.com/CommandString/router)[ Packagist](https://packagist.org/packages/commandstring/router)[ RSS](/packages/commandstring-router/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (3)Versions (19)Used By (0)

This is now abandonware!
========================

[](#this-is-now-abandonware)

If you still need a router in React/Http, checkout [Tnapf/Router](https://github.com/tnapf/Router#reacthttp-integration)

---

CommandString/Router
====================

[](#commandstringrouter)

A router package built that uses ReactPHP/HTTP under the hood

Table of Contents
=================

[](#table-of-contents)

- [Getting Started](#getting-started)
- [Creating routes](#routing)
- [Patterns](#route-patterns)
- [Controllers](#controllers)
- [Middleware](#middleware)
- [Dev Mode](#dev-mode)
- [Responses](#responding-to-requests)
- [Advanced Usage](#running-and-advance-usage)
- [Nodemon](#nodemon)

Installation
------------

[](#installation)

```
composer require commandstring/router

```

Getting started
---------------

[](#getting-started)

You first need to create a ReactPHP SocketServer

```
$socket = new \React\Socket\SocketServer("127.0.0.1:8000");
```

Then create a router instance

```
$router = new \Router\Http\Router($socket, true);
```

The second parameter is whether dev mode should be enabled or not you can read about dev mode [here](#dev-mode)

create some routes then begin listening for requests

```
$router->listen();
```

Routing
-------

[](#routing)

You can then add routes by using the match method

```
use Router\Http\Methods;

$router->match([Methods::GET], "/", function() { /* ... */ });
```

You can listen for more methods by adding them to the array

```
$router->match([Methods::GET, Methods::POST], "/", function() { /* ... */ });
```

Routing Shorthands
------------------

[](#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() { /* ... */ });
$router->head('pattern', function() { /* ... */ });
```

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

```
$router->all('pattern', function() { /* ... */ });
```

Route Patterns
==============

[](#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
---------------------

[](#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:

```
$router->get('/about', function($req, $res) {
    $res->getBody()->write("Hello World");
    return $res;
});
```

Dynamic PCRE-based Route Patterns
---------------------------------

[](#dynamic-pcre-based-route-patterns)

This type of Route Pattern contains 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](https://courses.cs.washington.edu/courses/cse154/15sp/cheat-sheets/php-regex-cheat-sheet.pdf) might come in handy.

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

```
// Bad
$router->get('/hello/\w+', function($req, $res, $name) {
    $res->getBody()->write('Hello '.htmlentities($name));
    return $res;
});

// Good
$router->get('/hello/(\w+)', function($req, $res, $name) {
    $res->getBody()->write('Hello '.htmlentities($name));
    return $res;
});
```

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:

```
$router->get('/movies/(\d+)/photos/(\d+)', function($req, $res, $movieId, $photoId) {
    $res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
    return $res;
});
```

Dynamic Placeholder-based Route Patterns
----------------------------------------

[](#dynamic-placeholder-based-route-patterns)

This type of Route Pattern is 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($req, $res, $movieId, $photoId) {
    $res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
    return $res;
});
```

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

```
$router->get('/movies/{foo}/photos/{bar}', function($req, $res, $movieId, $photoId) {
    $res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
    return $res;
});
```

### Optional Route Subpatterns

[](#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($req, $res, $year = null, $month = null, $day = null, $slug = null) {
		if (!$year) {
			$res->getBody()->write("Blog Overview");
			return $res;
		}

		if (!$month) {
			$res->getBody()->write("Blog year overview");
			return $res;
		}

		if (!$day) {
			$res->getBody()->write("Blog month overview");
			return $res;
		}

		if (!$slug) {
			$res->getBody()->write("Blog day overview");
			return $res;
		}

		$res->getBody()->write('Blogpost ' . htmlentities($slug) . ' detail');
		return $res;
	}
);
```

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($req, $res, $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](http://www.php.net/manual/en/regexp.reference.repetition.php) to require the correct amount of numbers in the URL:

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

Controllers
===========

[](#controllers)

When defining a route you can either pass an anonymous function or an array that contains a class along with a static method to invoke. Additionally your controller must return an implementation of the PSR7 Response Interface

Anonymous Function Controller
-----------------------------

[](#anonymous-function-controller)

```
$router->get("/home", function ($req, $res) {
    $res->getBody()->write("Welcome home!");
    return $res;
});
```

Class Controller
----------------

[](#class-controller)

I have a class with a **static** method, your handler MUST be a static method!

```
class Home {
	public static function handler($req, $res) {
		$res->getBody()->write("Welcome home!");
		return $res;
	}
}
```

I then replace the anonymous function with an array the first item being the class string and the second key being the name of the static method.

```
$router->get("/home", [Home::class, "handler"]);
```

404 Handler
-----------

[](#404-handler)

Defining a 404 handler is **required** and is similar to creating a route. You can also have different 404 pages for different patterns.

To setup a 404 handler you can invoke the map404 method and insert a pattern for the first parameter and then your controller as the second.

```
$router->map404("/(.*)", function ($req, $res) {
	$res->getBody()->write("{$req->getRequestTarget()} is not a valid route");
	return $res;
});
```

500 handler
-----------

[](#500-handler)

Defining a 500 handler is **recommended** and is exactly the same as mapping a 404 handler.

```
$router->map500("/(.*)", function ($req, $res) {
	$res->getBody()->write("An error has happened internally :(");
	return $res;
});
```

*Note that when in development mode your 500 error handler will be overrode*

Middleware
==========

[](#middleware)

Middleware is software that connects the model and view in an MVC application, facilitating the communication and data flow between these two components while also providing a layer of abstraction, decoupling the model and view and allowing them to interact without needing to know the details of how the other component operates.

A good example is having before middleware that makes sure the user is an administrator before they go to a restricted page. You could do this in your routes controller for every admin page but that would be redundant. Or for after middleware, you may have a REST API that returns a JSON response. You can have after middleware to make sure the JSON response isn't malformed.

Before Middleware
-----------------

[](#before-middleware)

You can define before middleware similar to a route by providing a method, pattern, and controller. Do note that with beforeMiddleware you're expected to create an object that implements ResponseInterface to pass to the main route.

```
$router->beforeMiddleware("/admin?(.*)", function (ServerRequest $req, Closure $next) {
	if (!isAdmin()) {
		return new RedirectResponse("/", 403);
	}

	return $next(new Response);
});
```

In the main route for after middleware
--------------------------------------

[](#in-the-main-route-for-after-middleware)

If the router detects after middleware then the third parameter will be a closure with similar functionality to beforeMiddleware

```
$router->all("/admin/roster/json", function (ServerRequest $req, Response $res, Closure $next) {
	/*
		some code that gets the admin roster and converts to json
	*/

	$res->getBody()->write($jsonString);
	$res->withHeader("content-type", "application/json");

	return $next($res);
});
```

After Middleware
----------------

[](#after-middleware)

```
$router->afterMiddleware("/admin?(.*)", function (ServerRequest $req, ResponseInterface $res) {
	/*
		some code that makes sure the body is a valid json string
	*/

	if (!jsonValid((string)$res->getBody())) {
		return new EmptyResponse(500);
	}

	return $res;
});
```

*Special note about middleware, you can pass variables from beforeMiddleware to the main route or from the main route to afterMiddleware by supplying it as the second argument in the next closure.*

Template Engine Integration
===========================

[](#template-engine-integration)

You can use [CommandString/Env](https://github.com/commandstring/env) to store your template engine object in a singleton. Then you can easily get it without trying to pass it around to your controller

```
use CommandString\Env\Env;

$env = new Env;
$env->twig = new Environment(new \Twig\Loader\FilesystemLoader("/path/to/views"));

// ...

$router->get("/home", function ($req, $res) {
	return new HtmlResponse($env->get("twig")->render("home.html"));\\\
});
```

Responding to requests
======================

[](#responding-to-requests)

All controllers **MUST** return an implemantation of `\Psr\Http\Message\ResponseInterface`. You can use the premade response object passed into the controller *or* instantiate your own. I recommend taking a look at [HttpSoft/Response](https://httpsoft.org/docs/response/v1/#usage) for prebuilt response types. This is also included with the route as it's used for the dev mode

```
$response = new HttpSoft\Response\HtmlResponse('HTML');
$response = new HttpSoft\Response\JsonResponse(['key' => 'value']);
$response = new HttpSoft\Response\JsonResponse("{key: 'value'}");
$response = new HttpSoft\Response\TextResponse('Text');
$response = new HttpSoft\Response\XmlResponse('XML');
$response = new HttpSoft\Response\RedirectResponse('https/example.com');
$response = new HttpSoft\Response\EmptyResponse();
```

Running and Advance Usage
=========================

[](#running-and-advance-usage)

If you want more control over the http server you can use the getHttpServer method to create and return the HttpServer object

```
$httpServer = $router->getHttpServer();
```

In addition to being able to retrieve the http server you can also retrieve the socket server with the getSocketServer method

```
$socketServer = $router->getSocketServer();
```

Dev Mode
========

[](#dev-mode)

As of now dev mode does one thing. When an exception is thrown on your route it returns the exception with the stack trace as a response rather than dumping it into the console.

Nodemon
=======

[](#nodemon)

I would recommend using nodemon when developing as it will restart your server with every file change. To install nodemon you'll need nodejs and npm.

`npm install -g nodemon`

then in root of your project directory create a new file named nodemon.json and put the following contents into it

```
{
    "verbose": false,
    "ignore": [
        ".git",
        ".idea"
    ],
    "execMap": {
        "php": "php"
    },
    "restartable": "r",
    "ext": "php,html,json"
}
```

Afterwards instead of using `php index.php` to start your server use `nodemon index.php` and change a file. You'll see that it says the server is restarting due to a file change. And now you don't have to repeatedly restart the server when you change files! You can also enter r into the console to restart manually if needed!

###  Health Score

27

—

LowBetter than 49% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity15

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

17

Last Release

1200d ago

Major Versions

v1.1.6 → v2.0.02022-12-22

V2.1.3 → v3.1.32023-01-20

### Community

Maintainers

![](https://www.gravatar.com/avatar/c1dc8515485fa64088fdfa03dd0f487d507dc1516824c31be2626a6ee25244dd?d=identicon)[command\_string](/maintainers/command_string)

---

Top Contributors

[![CommandString](https://avatars.githubusercontent.com/u/44886996?v=4)](https://github.com/CommandString "CommandString (21 commits)")

---

Tags

phpreactphprouterrouting

### Embed Badge

![Health badge](/badges/commandstring-router/health.svg)

```
[![Health](https://phpackages.com/badges/commandstring-router/health.svg)](https://phpackages.com/packages/commandstring-router)
```

###  Alternatives

[pantheon-systems/terminus

A command line interface for Pantheon

3391.5M13](/packages/pantheon-systems-terminus)[n98/magerun

Tools for managing Magento projects and installations

1.4k264.7k7](/packages/n98-magerun)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
