tobento/service-routing
Composer 安装命令:
composer require tobento/service-routing
包简介
A flexible PHP router.
README 文档
README
The Routing Service provides a flexible way to build routes for any PHP application.
Table of Contents
- Getting started
- Documentation
- Credits
Getting started
Add the latest version of the routing service running this command.
composer require tobento/service-routing
Requirements
- PHP 8.4 or greater
Highlights
- Basic routing (GET, POST, PUT, PATCH, UPDATE, DELETE)
- Domain routing
- Group routing
- Resource routing
- Named routes
- Matched route handling
- Url and signed url generation
- PSR-15 middleware support
- Localization
- Autowiring
- Framework-agnostic, will work with any project
- Decoupled design
- Easily extendable or customizable
Documentation
Router
use Tobento\Service\Routing\Router; use Tobento\Service\Routing\RequestData; use Tobento\Service\Routing\UrlGenerator; use Tobento\Service\Routing\RouteFactory; use Tobento\Service\Routing\RouteDispatcher; use Tobento\Service\Routing\Constrainer\Constrainer; use Tobento\Service\Routing\RouteHandler; use Tobento\Service\Routing\MatchedRouteHandler; use Tobento\Service\Routing\RouteResponseParser; // Any PSR-11 container $container = new \Tobento\Service\Container\Container(); $router = new Router( new RequestData( $_SERVER['REQUEST_METHOD'] ?? 'GET', rawurldecode($_SERVER['REQUEST_URI'] ?? ''), 'example.com', ), new UrlGenerator( 'https://example.com/basepath', 'a-random-32-character-secret-signature-key', ), new RouteFactory(), new RouteDispatcher($container, new Constrainer()), new RouteHandler($container), new MatchedRouteHandler($container), new RouteResponseParser(), ); $router->setBaseUri('/path/app/');
Basic Routing
Routing methods
$router->get('blog', [Controller::class, 'method']); $router->post('blog', [Controller::class, 'method']); $router->put('blog', [Controller::class, 'method']); $router->patch('blog', [Controller::class, 'method']); $router->delete('blog', [Controller::class, 'method']); $router->head('blog', [Controller::class, 'method']); $router->options('blog', [Controller::class, 'method']); // Route multiple $router->route('GET|POST', 'blog', [Controller::class, 'method']); // Route any $router->route('*', 'blog', [Controller::class, 'method']);
Uri definitions
$router->get('blog/{slug}', 'Controller::method'); // you can define as many as you want: $router->get('blog/{slug}/comment/{id}', 'Controller::method'); // using a question mark for optional parameters $router->get('{?locale}/blog/{?id}', 'Controller::method'); // or using wildcard to allow any path $router->get('blog/{path*}', 'Controller::method');
Handler definitions
The default RouteHandler supports autowiring and the following handler definitions.
// By providing class and method name: $router->get('blog', [Controller::class, 'method']); $router->get('blog', [new Controller(), 'method']); $router->get('blog', Controller::class); // __invoke method called // Using Class::method syntax: $router->get('blog', 'Controller::method'); // Using closure: $router->get('blog', function() { return 'welcome'; }); // You might provide data for build-in method parameters: $router->get('blog', [Controller::class, 'method', ['name' => 'value']]);
Parameters
Name a route:
The main purpose for named routes is the generation of URLs. But they might be useful for any other cases too.
$router->get('blog', 'Controller::method')->name('blog');
⚠️ Named routes should be unique, otherwise the route got overwritten.
Adding middleware: see also With PSR-15 Middleware
$router->get('blog', 'Controller::method') ->middleware(Middleware::class, Another::Middleware);
Where constraint parameter: see also Constrainer
$router->get('blog/{slug}', 'Controller::method') ->where('slug', '[a-z]+'); $router->get('{path*}', 'Controller::method') ->where('path', '[^?]+');
Query constraint parameter:
// do not allow any uri query parameters: $router->get('blog/{slug}', 'Controller::method')->query(null); // allow only certain query characters: $router->get('blog/{slug}', 'Controller::method')->query('/^[a-zA-Z0-9=&\/,\[\]-]+?$/');
Domain: see also Domain Routing
$router->get('blog', [Controller::class, 'method']) ->domain('sub.example.com');
Signed: see also Signed Routing
$router->get('blog', [Controller::class, 'method']) ->signed('blog', validate: false); // default validate: true
Matches:
use Tobento\Service\Routing\RouteInterface; $router->get('{slug}', 'BlogController::method') ->matches(function(SlugsRepo $slugs, RouteInterface $route): null|RouteInterface { // we would need call matches handler later on RouteDispatcher in order to have request data $slug = $slugs->find($route->getParameter('request_parameters')['slug']); if (!$slug || $slug->getResourceKey() !== 'blog') { return null; } $requestParams = $route->getParameter('request_parameters', []); $requestParams['id'] = $slug->getResourceId(); unset($requestParams['slug']); $route->parameter('request_parameters', $requestParams); return $route; }); $router->get('{slug}', 'ProductsController::method') ->matches(function(SlugsRepo $slugs, RouteInterface $route): null|RouteInterface { $slug = $slugs->find($route->getParameter('request_parameters')['slug']); if (!$slug || $slug->getResourceKey() !== 'products') { return null; } $requestParams = $route->getParameter('request_parameters', []); $requestParams['id'] = $slug->getResourceId(); unset($requestParams['slug']); $route->parameter('request_parameters', $requestParams); return $route; });
BaseUrl:
$router->get('blog', [Controller::class, 'method']) ->baseUrl('https:://sub.example.com/app');
Adding custom parameters:
$router->get('blog', [Controller::class, 'method']) ->parameter('name', 'value');
Url Generation
The router provides a url() method for generating URLs from named routes:
use Tobento\Service\Routing\UrlInterface; $router->get('blog', [Controller::class, 'method']) ->name('blog'); var_dump($router->url('blog') instanceof UrlInterface); // bool(true) $blogUrl = $router->url('blog')->get(); $blogUrl = (string) $router->url('blog'); // if your route uri has parameters: $router->get('blog/edit/{id}', [Controller::class, 'method']) ->name('blog.edit'); $blogUrl = $router->url('blog.edit', ['id' => 5])->get();
Throwing vs. Non-Throwing Behavior
By default, the router throws a UrlException if the route name does not exist:
use Tobento\Service\Routing\UrlException; $router->url('missing.route'); // throws UrlException
Safe Mode (throw: false)
You may disable exceptions by passing throw: false:
use Tobento\Service\Routing\NullUrl; $url = $router->url(name: 'missing.route', parameters: [], throw: false);
In this case, the router returns an instance of NullUrl, which also implements UrlInterface.
A NullUrl:
- can be safely cast to a string (resulting in an empty string)
- provides the attempted route name via
name() - provides the attempted parameters via
parameters() - supports all
UrlInterfacemethods with safe defaults
This is useful when generating links in views, notifications, or emails where the route may not exist in all application contexts.
More Routes methods
Custom Routes:
use Tobento\Service\Routing\RouteInterface; // must implement RouteInterface $router->addRoute(new CustomRoute()); $router->addRoutes([ new CustomRoute(), new CustomRoute(), ]);
Get All Routes:
use Tobento\Service\Routing\RouteInterface; $routes = $router->getRoutes(); // returns: array<int|string, RouteInterface>
Get Named Route:
use Tobento\Service\Routing\RouteInterface; $route = $router->getRoute('name'); // returns: null|RouteInterface
Get Matched Route:
use Tobento\Service\Routing\RouteInterface; $matchedRoute = $router->getMatchedRoute(); // returns: null|RouteInterface
Group Routing
You might use groups to share parameters across routes:
use Tobento\Service\Routing\RouteGroupInterface; $router->group('admin', function(RouteGroupInterface $group) { // supports any basic routing methods: $group->get('blog', [Controller::class, 'method'])->name('admin.blog'); // resources: $group->resource('products', ProductsController::class); // The group uri 'admin' gets prepended to route names for resources only. // $router->getRoute('admin.products.index'); // $router->url('admin.products.index'); // group: $group->group('account', function(RouteGroupInterface $group) { // define routes. }); // you might overwrite the group parameters by defining it: $group->get('blog', [Controller::class, 'method']) ->middleware(Middleware::class); })->domain('sub.example.com') ->middleware(Middleware::class) ->baseUrl('sub.example.com') ->parameter('name', 'value') ->locale('de') ->locales(['de', 'en']) ->localeOmit('de') ->localeName('locale') ->localeFallbacks(['de' => 'en']);
If the group uri definition has parameters, they are available on the routes:
use Tobento\Service\Routing\RouteGroupInterface; $router->group('{locale}', function(RouteGroupInterface $group) { // locale is available too. $group->get('blog/{id}', function($locale, $id) { // do something }); })->where('locale', ':in:de:fr');
Resource Routing
You may use resource routing for convenience:
$router->resource('products', ProductsController::class);
This will produce the following routes:
| Method | Uri | Action / Controller method | Route name |
|---|---|---|---|
| GET | products | index | products.index |
| GET | products/create | create | products.create |
| POST | products | store | products.store |
| GET | products/{id} | show | products.show |
| GET | products/{id}/edit | edit | products.edit |
| PUT/PATCH | products/{id} | update | products.update |
| DELETE | products/{id} | delete | products.delete |
You might route only specific actions:
$router->resource('products', ProductsController::class) ->only(['index', 'show']); $router->resource('products', ProductsController::class) ->except(['delete']);
You might change the default where constraint:
$router->resource('products', ProductsController::class) ->where('[a-z0-9]+'); // default is [0-9]+
Adding new or overwriting existing actions:
// creating new action: $router->resource('products', ProductsController::class) ->action( action: 'display', method: 'GET', uri: '/display/{id}', parameters: ['constraints' => ['id' => '[0-9]+']], ); // GET, products/display/{id}, display, products.display // overwriting index action: $router->resource('products', ProductsController::class) ->action( action: 'index', method: 'GET', uri: '/index', parameters: [], ); // GET, products/index, index, products.index
Middleware:
$router->resource('products', ProductsController::class) ->middleware( ['show'], // empty array for all actions Middleware::class, AnotherMiddleware::class, );
Adding additional route parameters for an action:
$router->resource('products', ProductsController::class) ->parameter( action: 'index', name: 'foo', value: 'bar', );
Adding additional route parameters for all actions:
$router->resource('products', ProductsController::class) ->sharedParameter( name: 'foo', value: 'bar', );
With localization and translation:
$router->resource('{?locale}/{products}', ProductsResource::class) // specify the name as above we set the uri: ->name('products') // specify the locales: ->locales(['de', 'en']) ->localeOmit('en') ->localeFallbacks(['de' => 'en']) // specify the translations for the verbs: ->trans('create', ['de' => 'neu', 'en' => 'create'], action: 'create') ->trans('edit', ['de' => 'bearbeiten', 'en' => 'edit'], action: 'edit') // for products: ->trans('products', ['de' => 'produkte', 'en' => 'products']); // Example with new action: $router->resource('products', ProductsController::class) ->action( action: 'display', method: 'GET', uri: '/display/{id}', // set without {display} parameters: ['constraints' => ['id' => '[0-9]+']], ) // specify the locales: ->locales(['de', 'en']) ->localeOmit('en') ->localeFallbacks(['de' => 'en']) // specify the translations for the display verb: ->trans('display', ['de' => 'ansicht', 'en' => 'display'], action: 'display');
Domain Routing
Domain Routes
// route: $router->get('blog', [Controller::class, 'method']) ->domain('example.com'); // group: $router->group('api', function($group) {}) ->domain('api.example.com'); // resource: $router->resource('products', ProductsController::class) ->domain('example.com');
multiple domains
You may add a route for multiple domains:
$router->get('blog', [Controller::class, 'method']) ->domain('example.ch') ->domain('example.de');
domain specific parameters
You may set domain specific parameters for each domain:
use Tobento\Service\Routing\RouteInterface; $router->get('{?locale}/{about}', function($locale, $about) { return [$locale, $about]; }) ->name('about') // default parameters will be used if not specified on domain: ->trans('about', ['de' => 'ueber-uns', 'en' => 'about', 'fr' => 'se-presente']) ->domain('example.ch', function(RouteInterface $route): void { $route->locales(['de', 'fr']) ->localeOmit('de') ->localeFallbacks(['fr' => 'de']) ->trans('about', ['de' => 'ueber-uns', 'fr' => 'se-presente']); })->domain('example.de', function(RouteInterface $route): void { $route->locales(['de', 'en']) ->localeOmit('de') ->localeFallbacks(['en' => 'de']); });
Domain Url Generation
$router->get('blog', [Controller::class, 'method']) ->name('blog') ->domain('example.ch') ->domain('example.de'); // current domain url: $url = $router->url('blog'); // get specific domain url: $url = $router->url('blog')->domain('example.de'); // get all domained urls: $urls = $router->url('blog')->domained(); /*[ 'example.ch' => 'https://example.ch/blog', 'example.de' => 'https://example.de/blog', ]*/ // you may get all translated urls from current or specific domain: $urls = $router->url('blog')->translated(); $urls = $router->url('blog')->domain('example.de')->translated();
Managing Domains
You may specify the domains in order to managing them at one place.
use Tobento\Service\Routing\Router; use Tobento\Service\Routing\RequestData; use Tobento\Service\Routing\UrlGenerator; use Tobento\Service\Routing\RouteFactory; use Tobento\Service\Routing\RouteDispatcher; use Tobento\Service\Routing\Constrainer\Constrainer; use Tobento\Service\Routing\RouteHandler; use Tobento\Service\Routing\MatchedRouteHandler; use Tobento\Service\Routing\RouteResponseParser; use Tobento\Service\Routing\Domains; use Tobento\Service\Routing\Domain; // Any PSR-11 container $container = new \Tobento\Service\Container\Container(); // Domains $domains = new Domains( new Domain(key: 'example.ch', domain: 'ch.localhost', uri: 'http://ch.localhost'), new Domain(key: 'example.de', domain: 'de.localhost', uri: 'http://de.localhost'), ); $router = new Router( new RequestData( $_SERVER['REQUEST_METHOD'] ?? 'GET', rawurldecode($_SERVER['REQUEST_URI'] ?? ''), 'ch.localhost', ), new UrlGenerator( 'https://example.com/basepath', 'a-random-32-character-secret-signature-key', ), new RouteFactory($domains), // pass domains new RouteDispatcher($container, new Constrainer()), new RouteHandler($container), new MatchedRouteHandler($container), new RouteResponseParser(), ); // Adding Routes: // Set the domain name as the key specified above: $router->get('blog', [Controller::class, 'method']) ->domain('example.ch') ->domain('example.de');
Signed Routing
Signed Routes
Add a signed route:
use Tobento\Service\Routing\InvalidSignatureException; $router->get('unsubscribe/{user}', [Controller::class, 'method']) ->signed('unsubscribe'); try { $matchedRoute = $router->dispatch(); } catch (InvalidSignatureException $e) { // handle invalid signature }
Add a signed route with validating on handler for custom response:
use Tobento\Service\Routing\RouterInterface; $router->get('unsubscribe/{user}', function(RouterInterface $router, $user) { $matchedRoute = $router->getMatchedRoute(); $requestUri = $router->getRequestData()->uri(); if (! $router->getUrlGenerator()->hasValidSignature($matchedRoute->getUri(), $requestUri)) { // handle invalid signature. } }) ->signed('unsubscribe', validate: false);
Signed Url Generation
use Tobento\Service\Dater\Dater; $router->get('unsubscribe/{user}', [Controller::class, 'method']) ->signed('unsubscribe'); // generate a signed url with no expiring. $url = (string) $router->url('unsubscribe', ['user' => 5])->sign(); // https://example.com/basepath/unsubscribe/5/a0df83344703b26cd1f9cdcb05196082a6a7799e84b4748a5610d3256b556c55 // generate a signed url which expires in 10 days. $url = (string) $router->url('unsubscribe', ['user' => 5])->sign(new Dater()->addDays(10)); // https://example.com/basepath/unsubscribe/5/a0df83344703b26cd1f9cdcb05196082a6a7799e84b4748a5610d3256b556c55/1630752459 // generate a signed url with no expiring and add signature data as query parameters. $url = (string) $router->url('unsubscribe', ['user' => 5])->sign(withQuery: true); // https://example.com/basepath/unsubscribe/5?signature=6d632a4a8981b1fb017ad6f82067370d6c98ddcd8c6d18cb4fc30c1d44e0f67e // generate a signed url which expires in 10 days and add signature data as query parameters. $url = (string) $router->url('unsubscribe', ['user' => 5])->sign(new Dater()->addDays(10), true); // https://example.com/basepath/unsubscribe/5?expires=1630752540&signature=6d632a4a8981b1fb017ad6f82067370d6c98ddcd8c6d18cb4fc30c1d44e0f67e
Localization and Translation Routing
Localize Routes
$router->get('{?locale}/about', [Controller::class, 'method']) ->name('about'); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/about // get specific locale url: $url = (string) $router->url('about')->locale('en'); // https://example.com/basepath/en/about // get all translated urls: $urls = $router->url('about')->translated(); /*[]*/ // get/create specific translated urls: $urls = $router->url('about')->translated(['de', 'fr']); /*[ 'de' => 'https://example.com/basepath/de/about', 'fr' => 'https://example.com/basepath/fr/about', ]*/
Support only specific locales:
$router->get('{?locale}/about', [Controller::class, 'method']) ->name('about') ->locales(['de', 'en']); // the supported locales, MUST be called before any other locale methods. // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/about // get specific locale url: $url = (string) $router->url('about')->locale('en'); // https://example.com/basepath/en/about // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'de' => 'https://example.com/basepath/de/about', 'en' => 'https://example.com/basepath/en/about', ]*/ // get/create specific translated urls: $urls = $router->url('about')->translated(['de', 'fr']); /*[ 'de' => 'https://example.com/basepath/about', 'fr' => 'https://example.com/basepath/fr/about', ]*/
Omit locale in request uri:
$router->get('{?locale}/about', [Controller::class, 'method']) ->name('about') ->locales(['de', 'en']) ->localeOmit('en'); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/about // get specific locale url: $url = (string) $router->url('about')->locale('en'); // https://example.com/basepath/about // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'de' => 'https://example.com/basepath/de/about', 'en' => 'https://example.com/basepath/about', ]*/
Define current locale:
$router->get('{locale}/about', [Controller::class, 'method']) ->name('about') ->locale('en'); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/en/about // get specific locale url: $url = (string) $router->url('about')->locale('de'); // https://example.com/basepath/de/about // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'en' => 'https://example.com/basepath/en/about', ]*/
Rename locale uri definition:
$router->get('{?loc}/about', [Controller::class, 'method']) ->name('about') ->localeName('loc'); // the locale uri definition name, 'locale' is the default name
Translatable Routes
Without locale uri definition
$router->get('/{about}', [Controller::class, 'method']) ->name('about') ->locale('de') // set the current locale. ->trans('about', ['de' => 'ueber-uns', 'en' => 'about']); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/ueber-uns // get specific locale url: $url = (string) $router->url('about')->locale('en'); // https://example.com/basepath/about // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'de' => 'https://example.com/basepath/ueber-uns', 'en' => 'https://example.com/basepath/about', ]*/
Support only specific locales:
$router->get('/{about}', [Controller::class, 'method']) ->name('about') ->locales(['en']) // the supported locales, MUST be called before any other locale methods. ->locale('de') // set the current locale. ->trans('about', ['de' => 'ueber-uns', 'en' => 'about']); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/ueber-uns // get specific locale url: $url = (string) $router->url('about')->locale('en'); // https://example.com/basepath/about // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'en' => 'https://example.com/basepath/about', ]*/
Define locale fallbacks:
$router->get('/{about}', [Controller::class, 'method']) ->name('about') ->locales(['en', 'de', 'fr']) ->localeFallbacks(['fr' => 'en']) ->locale('de') // set the current locale. ->trans('about', ['de' => 'ueber-uns', 'en' => 'about']); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/ueber-uns // get specific locale url: $url = (string) $router->url('about')->locale('fr'); // https://example.com/basepath/about // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'en' => 'https://example.com/basepath/about', 'de' => 'https://example.com/basepath/ueber-uns', 'fr' => 'https://example.com/basepath/about', ]*/
With locale uri definition:
$router->get('{?locale}/{about}', [Controller::class, 'method']) ->name('about') ->localeOmit('en') ->trans('about', ['de' => 'ueber-uns', 'en' => 'about']); // get current locale url: $url = (string) $router->url('about'); // https://example.com/basepath/about // get specific locale url: $url = (string) $router->url('about')->locale('de'); // https://example.com/basepath/de/ueber-uns // get all translated urls: $urls = $router->url('about')->translated(); /*[ 'de' => 'https://example.com/basepath/de/ueber-uns', 'en' => 'https://example.com/basepath/about', ]*/
Default parameters are always prioritized:
$router->get('/{about}', [Controller::class, 'method']) ->name('about') ->locale('de') // set the current locale. ->trans('about', ['de' => 'ueber-uns', 'en' => 'about']); $url = (string) $router->url('about', ['locale' => 'de', 'about' => 'ueberuns'])->locale('en'); // https://example.com/basepath/ueberuns
Matched Route Event
The default MatchedRouteHandler supports autowiring.
$router->get('blog/edit', [Controller::class, 'method'])->name('blog.edit'); $router->matched('blog.edit', function() { // do something after the route has been matched. });
Constrainer
Add rule constraint to route:
// instead of: $router->get('blog/{word}', 'Controller::method') ->where('word', '(foo|bar)'); // you can use a rule: $router->get('blog/{word}', 'Controller::method') ->where('word', ':in:foo:bar'); // using rule with array syntax. $router->get('blog/{word}', 'Controller::method') ->where('word', ['in', 'foo', 'bar']);
Available Rules:
| Rule | Regex | Description |
|---|---|---|
| :alpha | [a-zA-Z]+ | |
| :alpha:2 | [a-zA-Z]{2} | n{x} Matches any string that contains a sequence of X n's |
| :alpha:2:5 | [a-zA-Z]{2,5} | n{x,y} Matches any string that contains a sequence of X to Y n's |
| :alpha:2: | [a-zA-Z]{2,} | n{x,} Matches any string that contains a sequence of at least X n's |
| :num | [0-9]+ | |
| :num:2 | [0-9]{2} | n{x} Matches any string that contains a sequence of X n's |
| :num:2:5 | [0-9]{2,5} | n{x,y} Matches any string that contains a sequence of X to Y n's |
| :num:2: | [0-9]{2,} | n{x,} Matches any string that contains a sequence of at least X n's |
| :alphaNum | [a-zA-Z0-9]+ | |
| :alphaNum:2 | [a-zA-Z0-9]{2} | n{x} Matches any string that contains a sequence of X n's |
| :alphaNum:2:5 | [a-zA-Z0-9]{2,5} | n{x,y} Matches any string that contains a sequence of X to Y n's |
| :alphaNum:2: | [a-zA-Z0-9]{2,} | n{x,} Matches any string that contains a sequence of at least X n's |
| 🆔1:5 | 🆔minNumber:maxLength | |
| :id | Uses the default parameters from the rule 🆔1:21 | |
| :in:foo:bar:baz | If the value is is one of foo, bar, baz |
Custom Rules:
// rule with regex: $router->getRouteDispatcher() ->rule('slug') ->regex('[a-z0-9-]+'); // rule with regex closure: $router->getRouteDispatcher() ->rule('slug') ->regex(function(array $parameters): null|string { // build the regex based on the parameters }); // rule with matches: $router->getRouteDispatcher() ->rule('slug') ->matches(function(string $value, array $parameters): bool { // handle }); // or by adding a rule. $router->getRouteDispatcher()->addRule('slug', new SlugRule());
Dispatching Strategies
There are different ways of handling the matched route, depending on your needs.
Simple
No middleware support though.
use Tobento\Service\Routing\RouteNotFoundException; use Tobento\Service\Routing\InvalidSignatureException; use Tobento\Service\Routing\TranslationException; use Tobento\Service\Routing\RouteInterface; try { $matchedRoute = $router->dispatch(); var_dump($matchedRoute instanceof RouteInterface); // bool(true) } catch (RouteNotFoundException $e) { // handle exception } catch (InvalidSignatureException $e) { // handle exception } catch (TranslationException $e) { // handle exception } // call matched route handler for handling registered matched event actions. $router->getMatchedRouteHandler()->handle($matchedRoute); // handle the matched route. $routeResponse = $router->getRouteHandler()->handle($matchedRoute);
With PSR-7 Response
No middleware support though.
use Tobento\Service\Routing\RouteNotFoundException; use Tobento\Service\Routing\InvalidSignatureException; use Tobento\Service\Routing\TranslationException; try { $matchedRoute = $router->dispatch(); } catch (RouteNotFoundException $e) { // handle exception } catch (InvalidSignatureException $e) { // handle exception } catch (TranslationException $e) { // handle exception } // call matched route handler for handling registered matched event actions. $router->getMatchedRouteHandler()->handle($matchedRoute); // handle the matched route. $routeResponse = $router->getRouteHandler()->handle($matchedRoute); // create response. $response = new \Nyholm\Psr7\Factory\Psr17Factory()->createResponse(200); // parse the route response. $response = $router->getRouteResponseParser()->parse($response, $routeResponse); // emitting response. new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter()->emit($response);
With PSR-15 Middleware
You will need to define your MiddlewareDispatcher implementation on the container. You might customize this behaviour by your own RouteHandler though.
use Tobento\Service\Middleware\MiddlewareDispatcherInterface; use Tobento\Service\Middleware\MiddlewareDispatcher; use Tobento\Service\Middleware\AutowiringMiddlewareFactory; use Tobento\Service\Middleware\FallbackHandler; // adjust route parameters passed to the request attributes if needed: $router->setRequestAttributes(['uri', 'name', 'request_uri']); // After PreRouting or Routing Middleware: $request->getAttribute('route.name'); // Middleware Handling: $container->set(MiddlewareDispatcherInterface::class, function($container) { return new MiddlewareDispatcher( new FallbackHandler(new \Nyholm\Psr7\Factory\Psr17Factory()->createResponse(200)), new AutowiringMiddlewareFactory($container) ); }); $middlewareDispatcher = $container->get(MiddlewareDispatcherInterface::class); // add MethodOverride middleware if needed. $middlewareDispatcher->add(\Tobento\Service\Routing\Middleware\MethodOverride::class); // add PreRouting middleware if needed. $middlewareDispatcher->add(\Tobento\Service\Routing\Middleware\PreRouting::class); // ... more middlewares $middlewareDispatcher->add(\Tobento\Service\Routing\Middleware\Routing::class); $request = new \Nyholm\Psr7\Factory\Psr17Factory()->createServerRequest('GET', 'https://example.com'); $response = $middlewareDispatcher->handle($request); // emitting response. new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter()->emit($response);
Credits
tobento/service-routing 适用场景与选型建议
tobento/service-routing 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 302 次下载、GitHub Stars 达 0, 最近一次更新时间为 2021 年 09 月 16 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「routing」 「package」 「router」 「tobento」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 tobento/service-routing 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 tobento/service-routing 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 tobento/service-routing 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Write down your routing mapping at one place
Flight routing is a simple, fast PHP router that is easy to get integrated with other routers.
Router
PMVC App Action Router
Simple ASCII output of array data
统计信息
- 总下载量: 302
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 22
- 依赖项目数: 3
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2021-09-16