A powerful PHP Router for PSR7 messages inspired by the Laravel API.

Overview

Rare Router

Latest Stable Version CI Coverage Status

A simple PHP router built on AltoRouter but inspired by the Laravel API.

Installation

composer require rareloop/router

Usage

Creating Routes

Map

Creating a route is done using the map function:

use Rareloop\Router\Router;

$router = new Router;

// Creates a route that matches the uri `/posts/list` both GET 
// and POST requests. 
$router->map(['GET', 'POST'], 'posts/list', function () {
    return 'Hello World';
});

map() takes 3 parameters:

  • methods (array): list of matching request methods, valid values:
    • GET
    • POST
    • PUT
    • PATCH
    • DELETE
    • OPTIONS
  • uri (string): The URI to match against
  • action (function|string): Either a closure or a Controller string

Route Parameters

Parameters can be defined on routes using the {keyName} syntax. When a route matches that contains parameters, an instance of the RouteParams object is passed to the action.

$router->map(['GET'], 'posts/{id}', function(RouteParams $params) {
    return $params->id;
});

If you need to add constraints to a parameter you can pass a regular expression pattern to the where() function of the defined Route:

$router->map(['GET'], 'posts/{id}/comments/{commentKey}', function(RouteParams $params) {
    return $params->id;
})->where('id', '[0-9]+')->where('commentKey', '[a-zA-Z]+');

// or

$router->map(['GET'], 'posts/{id}/comments/{commentKey}', function(RouteParams $params) {
    return $params->id;
})->where([
    'id', '[0-9]+',
    'commentKey', '[a-zA-Z]+',
]);

Optional route Parameters

Sometimes your route parameters needs to be optional, in this case you can add a ? after the parameter name:

$router->map(['GET'], 'posts/{id?}', function(RouteParams $params) {
    if (isset($params->id)) {
        // Param provided
    } else {
        // Param not provided
    }
});

Named Routes

Routes can be named so that their URL can be generated programatically:

$router->map(['GET'], 'posts/all', function () {})->name('posts.index');

$url = $router->url('posts.index');

If the route requires parameters you can be pass an associative array as a second parameter:

$router->map(['GET'], 'posts/{id}', function () {})->name('posts.show');

$url = $router->url('posts.show', ['id' => 123]);

If a passed in parameter fails the regex constraint applied, a RouteParamFailedConstraintException will be thrown.

HTTP Verb Shortcuts

Typically you only need to allow one HTTP verb for a route, for these cases the following shortcuts can be used:

$router->get('test/route', function () {});
$router->post('test/route', function () {});
$router->put('test/route', function () {});
$router->patch('test/route', function () {});
$router->delete('test/route', function () {});
$router->options('test/route', function () {});

Setting the basepath

The router assumes you're working from the route of a domain. If this is not the case you can set the base path:

$router->setBasePath('base/path');
$router->map(['GET'], 'route/uri', function () {}); // `/base/path/route/uri`

Controllers

If you'd rather use a class to group related route actions together you can pass a Controller String to map() instead of a closure. The string takes the format {name of class}@{name of method}. It is important that you use the complete namespace with the class name.

Example:

// TestController.php
namespace \MyNamespace;

class TestController
{
    public function testMethod()
    {
        return 'Hello World';
    }
}

// routes.php
$router->map(['GET'], 'route/uri', '\MyNamespace\TestController@testMethod');

Creating Groups

It is common to group similar routes behind a common prefix. This can be achieved using Route Groups:

$router->group('prefix', function ($group) {
    $group->map(['GET'], 'route1', function () {}); // `/prefix/route1`
    $group->map(['GET'], 'route2', function () {}); // `/prefix/route2§`
});

Middleware

PSR-15/7 Middleware can be added to both routes and groups.

Adding Middleware to a route

At it's simplest, adding Middleware to a route can be done by passing an object to the middleware() function:

$middleware = new AddHeaderMiddleware('X-Key1', 'abc');

$router->get('route/uri', '\MyNamespace\TestController@testMethod')->middleware($middleware);

Multiple middleware can be added by passing more params to the middleware() function:

$header = new AddHeaderMiddleware('X-Key1', 'abc');
$auth = new AuthMiddleware();

$router->get('route/uri', '\MyNamespace\TestController@testMethod')->middleware($header, $auth);

Or alternatively, you can also pass an array of middleware:

$header = new AddHeaderMiddleware('X-Key1', 'abc');
$auth = new AuthMiddleware();

$router->get('route/uri', '\MyNamespace\TestController@testMethod')->middleware([$header, $auth]);

Adding Middleware to a group

Middleware can also be added to a group. To do so you need to pass an array as the first parameter of the group() function instead of a string.

$header = new AddHeaderMiddleware('X-Key1', 'abc');

$router->group(['prefix' => 'my-prefix', 'middleware' => $header]), function ($group) {
    $group->map(['GET'], 'route1', function () {}); // `/my-prefix/route1`
    $group->map(['GET'], 'route2', function () {}); // `/my-prefix/route2§`
});

You can also pass an array of middleware if you need more than one:

$header = new AddHeaderMiddleware('X-Key1', 'abc');
$auth = new AuthMiddleware();

$router->group(['prefix' => 'my-prefix', 'middleware' => [$header, $auth]]), function ($group) {
    $group->map(['GET'], 'route1', function () {}); // `/my-prefix/route1`
    $group->map(['GET'], 'route2', function () {}); // `/my-prefix/route2§`
});

Defining Middleware on Controllers

You can also apply Middleware on a Controller class too. In order to do this your Controller must extend the Rareloop\Router\Controller base class.

Middleware is added by calling the middleware() function in your Controller's __constructor().

use Rareloop\Router\Controller;

class MyController extends Controller
{
    public function __construct()
    {
        // Add one at a time
        $this->middleware(new AddHeaderMiddleware('X-Key1', 'abc'));
        $this->middleware(new AuthMiddleware());

        // Add multiple with one method call
        $this->middleware([
            new AddHeaderMiddleware('X-Key1', 'abc',
            new AuthMiddleware(),
        ]);
    }
}

By default all Middleware added via a Controller will affect all methods on that class. To limit what methods Middleware applies to you can use only() and except():

use Rareloop\Router\Controller;

class MyController extends Controller
{
    public function __construct()
    {
        // Only apply to `send()` method
        $this->middleware(new AddHeaderMiddleware('X-Key1', 'abc'))->only('send');

        // Apply to all methods except `show()` method
        $this->middleware(new AuthMiddleware())->except('show');

        // Multiple methods can be provided in an array to both methods
        $this->middleware(new AuthMiddleware())->except(['send', 'show']);
    }
}

Matching Routes to Requests

Once you have routes defined, you can attempt to match your current request against them using the match() function. match() accepts an instance of Symfony's Request and returns an instance of Symfony's Response:

$request = Request::createFromGlobals();
$response = $router->match($request);
$response->send();

Return values

If you return an instance of Response from your closure it will be sent back un-touched. If however you return something else, it will be wrapped in an instance of Response with your return value as the content.

Responsable objects

If you return an object from your closure that implements the Responsable interface, it's toResponse() object will be automatically called for you.

class MyObject implements Responsable
{
    public function toResponse(RequestInterface $request) : ResponseInterface
    {
        return new TextResponse('Hello World!');
    }
}

$router->get('test/route', function () {
    return new MyObject();
});

404

If no route matches the request, a Response object will be returned with it's status code set to 404;

Accessing current route

The currently matched Route can be retrieved by calling:

$route = $router->currentRoute();

If no route matches or match() has not been called, null will be returned.

You can also access the name of the currently matched Route by calling:

$name = $router->currentRouteName();

If no route matches or match() has not been called or the matched route has no name, null will be returned.

Using with a Dependency Injection Container

The router can also be used with a PSR-11 compatible Container of your choosing. This allows you to type hint dependencies in your route closures or Controller methods.

To make use of a container, simply pass it as a parameter to the Router's constructor:

use MyNamespace\Container;
use Rareloop\Router\Router;

$container = new Container();
$router = new Router($container);

After which, your route closures and Controller methods will be automatically type hinted:

$container = new Container();

$testServiceInstance = new TestService();
$container->set(TestService::class, $testServiceInstance);

$router = new Router($container);

$router->get('/my/route', function (TestService $service) {
    // $service is now the same object as $testServiceInstance
});
Comments
  • Add tests to ensure AltoRouter can match against root paths

    Add tests to ensure AltoRouter can match against root paths

    Waiting on AltoRouter to do a release including https://github.com/dannyvankooten/AltoRouter/pull/247

    Issue: https://github.com/dannyvankooten/AltoRouter/issues/258

    • [x] Add tests
    • [x] Test on updated AltoRouter release
    • [x] Increase minimum AltoRouter version in composer.json
    opened by joelambert 6
  • Having trouble fetching the request object

    Having trouble fetching the request object

    Good day, I'm just trying to fetch my request object in the callable:

    $router->post('/api/customers', function(RequestInterface $request) {
       //$request->getParsedBody() etc
    });
    

    I'm aware of Responsable, but that would require me to create a separate class for each route. Could you provide example code which allows me to fetch the request object, or at least the posted body data, in the closure? Thanks.

    opened by mlambley 5
  • Install ran into a problem with my PHP version.

    Install ran into a problem with my PHP version.

    In trying to run the lumberjack composer installation, I reached this requirement not met issue: rareloop/router v4.4.0 requires php ^7.1 -> your php version (8.0.3) does not satisfy that requirement.

    opened by graphicagenda 3
  • Add support for PHP 8

    Add support for PHP 8

    This introduces a breaking change in the function signature of RequestFactory::create().

    It also removes support for PHP 7.1 & 7.2 which are now both EOL.

    opened by joelambert 2
  • Problem of integrating Symfony Request with Rareloop Router

    Problem of integrating Symfony Request with Rareloop Router

    As in the document, I use following code to integrate the router.

    
    $request = Request::createFromGlobals();
    $response = $router->match($request);
    $response->send();
    
    

    However, I get the error that

    PHP Fatal error: Uncaught TypeError: Argument 1 passed to Rareloop\Router\Router::match() must be an instance of Psr\Http\Message\ServerRequestInterface, instance of Symfony\Component\HttpFoundation\Request given

    Because the document also mentioned that "match() accepts an instance of Symfony's Request", it is quite confused for me for the error.

    Appreciate if any advice! Thanks.

    opened by StevenRoan 2
  • Migrate to GitHub Actions

    Migrate to GitHub Actions

    Move away from TravisCI and use GitHub Actions instead. Coveralls and Slack notifications have not yet been re-implemented.

    This PR also increases the minimum AltoRouter version to 2.0.1 as tests failed when using 2.0.

    opened by joelambert 0
  • Abandoned pakage

    Abandoned pakage

    Package zendframework/zend-diactoros is abandoned, you should avoid using it. Use laminas/laminas-diactoros instead.

    Package http-interop/http-server-middleware is abandoned, you should avoid using it. Use psr/http-server-middleware instead.

    how can I solve this?

    opened by AftabFalak 2
Releases(v3.2.1)
Owner
Rareloop
Rareloop
A simple PHP package for sending messages to Slack, with a focus on ease of use and elegant syntax.

Slack for PHP | A simple PHP package for sending messages to Slack with incoming webhooks, focused on ease-of-use and elegant syntax. supports: PHP 7.

null 128 Nov 28, 2022
It validates PSR-7 messages (HTTP request/response) against OpenAPI specifications

OpenAPI PSR-7 Message (HTTP Request/Response) Validator This package can validate PSR-7 messages against OpenAPI (3.0.x) specifications expressed in Y

The League of Extraordinary Packages 421 Jan 3, 2023
Simple, extensible and powerful enumeration implementation for Laravel.

About Laravel Enum Simple, extensible and powerful enumeration implementation for Laravel. Enum key value pairs as class constants Full featured suite

Ben Sampson 1.8k Dec 23, 2022
Simple and effective multi-format Web API Server to host your PHP API as Pragmatic REST and / or RESTful API

Luracast Restler ![Gitter](https://badges.gitter.im/Join Chat.svg) Version 3.0 Release Candidate 5 Restler is a simple and effective multi-format Web

Luracast 1.4k Dec 14, 2022
Laravel api tool kit is a set of tools that will help you to build a fast and well-organized API using laravel best practices.

Laravel API tool kit and best API practices Laravel api tool kit is a set of tools that will help you to build a fast and well-organized API using lar

Ahmed Esa 106 Nov 22, 2022
微信支付 API v3 的 PHP Library,同时也支持 API v2

微信支付 WeChatPay OpenAPI SDK [A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP 概览 微信支付 APIv2&APIv3 的Guzzle HttpClient封装组合, APIv2已内置请求数据签名及XML转换器,应

null 275 Jan 5, 2023
Simple PHP API client for tube-hosting.com rest API

Tube-Hosting API PHP client Explanation This PHP library is a simple api wrapper/client for the tube-hosting.com api. It is based on the provided docu

null 4 Sep 12, 2022
Chargebee API PHP Client (for API version 2 and Product Catalog version 2.0)

chargebee-php-sdk Overview This package provides an API client for Chargebee subscription management services. It connects to Chargebee REST APIs for

GLOBALIS media systems 8 Mar 8, 2022
This API aims to present a brief to consume a API resources, mainly for students in the early years of Computer Science courses and the like.

Simple PHP API v.1.0 This API aims to present a brief to consume a API resources, mainly for students in the early years of Computer Science courses a

Edson M. de Souza 14 Nov 18, 2021
This API provides functionality for creating and maintaining users to control a simple To-Do-List application. The following shows the API structure for users and tasks resources.

PHP API TO-DO-LIST v.2.0 This API aims to present a brief to consume a API resources, mainly for students in the early years of Computer Science cours

Edson M. de Souza 6 Oct 13, 2022
API documentation API SCB EASY APP

SCB-API-EASY V3.0 API documentation SIAM COMMERCIAL BANK PUBLIC COMPANY LTD. API SCB Easy V3 endpoint = https://fasteasy.scbeasy.link 1.0. Get balance

SCB API Esay team 2 Sep 28, 2021
Courier API adalah project API untuk mengetahui ongkos kirim Logistik-logistik pengiriman barang antar kota & International

Courier API Courier API adalah project API untuk mengetahui ongkos kirim Logistik-logistik pengiriman barang antar kota (dalam negeri) & International

Rangga Darmajati 2 Sep 24, 2021
LaraBooks API - Simple API for iOS SwiftUI app tests.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

Konrad Podrygalski 1 Nov 13, 2021
Best resources restful api for developers (with JSON:API standar specification design)

List API Best resources restful api for developers (with JSON:API standar specification design). API Resource Endpoint Name Resource Description Al Qu

Noval 2 Jan 18, 2022
GraphQL API to Studio Ghibli REST API

GhibliQL GhibliQL is a GraphQL wrapper to the Studio Ghibli REST API Usage First, you'll need a GraphQL client to query GhibliQL, like GraphQL IDE Con

Sebastien Bizet 8 Nov 5, 2022
API for Symbiota using the Lumen PHP PHP Micro-Framework By Laravel

symbiota-api API for Symbiota using the Lumen PHP PHP Micro-Framework By Laravel Laravel Lumen Official Documentation Documentation for the Lumen fram

Biodiversity Knowledge Integration Center 2 Jan 3, 2022
A Laravel Fractal package for building API responses, giving you the power of Fractal with Laravel's elegancy.

Laravel Responder is a package for building API responses, integrating Fractal into Laravel and Lumen. It can transform your data using transformers,

Alexander Tømmerås 776 Dec 25, 2022
Laravel API 文档生成器,可以将基于 Laravel 项目的项目代码,自动生成 json 或 md 格式的描述文件。

Thresh Laravel API 文档生成器,可以将基于 Laravel 项目的项目代码,自动生成 json 或 md 格式的描述文件。 安装 $ composer require telstatic/thresh -vvv 功能 生成 Markdown 文档 生成 Postman 配置文件 生

静止 5 Jul 12, 2021
The 1Password Connect PHP SDK provides your PHP applications access to the 1Password Connect API hosted on your infrastructure and leverage the power of 1Password Secrets Automation

1Password Connect PHP SDK The 1Password Connect PHP SDK provides your PHP applications access to the 1Password Connect API hosted on your infrastructu

Michelangelo van Dam 12 Dec 26, 2022