大卫塞萨拉诺 / embryo-routing
PHP路由器PSR兼容。
Requires
- php: >=7.1
- davidecesarano/embryo-container: dev-master
- davidecesarano/embryo-emitter: dev-master
- davidecesarano/embryo-http: dev-master
- davidecesarano/embryo-middleware: dev-master
Requires (Dev)
- phpstan/phpstan: ^0.12.40
README
一个轻量级、快速且PSR兼容的PHP路由器。
功能
- PSR(7、11、15)兼容。
- 静态、动态和可选路由模式。
- 支持GET、POST、PUT、PATCH、DELETE和OPTIONS请求方法。
- 支持路由中间件。
- 支持路由分组。
- 支持通过容器解析。
- 支持子文件夹。
要求
- PHP >= 7.1
- URL重写
- PSR-7 http消息实现和PSR-17 http工厂实现(例如:Embryo-Http)
- PSR-11容器实现(例如:Embryo-Container)
- PSR-15 http服务器请求处理器实现(例如:Embryo-Middleware)
安装
使用Composer
$ composer require davidecesarano/embryo-routing
示例
在定义应用程序路由之前,需要创建以下对象
Container
ServerRequestFactory
ResponseFactory
RequestHandler
Emitter
use Embryo\Container\Container; use Embryo\Http\Emitter\Emitter; use Embryo\Http\Factory\{ServerRequestFactory, ResponseFactory}; use Embryo\Http\Server\RequestHandler; use Embryo\Routing\Router; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; $container = new Container; $request = (new ServerRequestFactory)->createServerRequestFromServer(); $response = (new ResponseFactory)->createResponse(200); $requestHandler = new RequestHandler; $emitter = new Emitter;
稍后,可以使用Router
对象定义路由
$router = new Router; $router->get('/', function(Request $request, Response $response){ $response->getBody()->write('Hello World!'); return $response; });
现在,创建PSR-15中间件队列,添加所需的路由中间件
MethodOverrideMiddleware
用于重写HTTP请求方法。RoutingMiddleware
用于匹配路由和处理器的发现。RequestHandlerMiddleware
用于执行路由器发现的请求处理器。
$requestHandler->add(new Embryo\Routing\Middleware\MethodOverrideMiddleware); $requestHandler->add(new Embryo\Routing\Middleware\RoutingMiddleware($router)); $requestHandler->add(new Embryo\Routing\Middleware\RequestHandlerMiddleware($container)); $response = $requestHandler->dispatch($request, $response);
最后,可以使用Emitter
对象生成响应的输出。
$emitter->emit($response);
用法
创建路由
您可以使用Router对象上的方法定义应用程序路由。每个方法接受两个参数
- 路由模式(带有可选的占位符)
- 路由回调(一个闭包,一个
class@method
字符串或一个['class', 'method']
数组)
// GET Route $router->get('/blog/{id}', function(Request $request, Response $response, int $id) { $response->getBody()->write('This is post with id '.$id); return $response; });
请注意,您可以使用以下方式使用“/”或不使用“/”开头来编写模式:blog/{id}
。
方法
Embryo Routing支持GET、POST、PUT、PATCH、DELETE和OPTIONS请求方法。每个请求方法都对应于Router对象的一个方法:get()
、post()
、put()
、patch()
、delete()
和options()
。您可以使用all()
和map()
方法来支持所有方法或特定路由方法。
// All methods $router->all('pattern', function(Request $request, Response $response) { //... }); // Match methods $router->map(['GET', 'POST'], 'pattern', function(Request $request, Response $response) { //... });
重写请求方法
使用X-HTTP-Method-Override
来重写HTTP请求方法。仅当原始请求方法为POST时才有效。X-HTTP-Method-Override的允许值为PUT
、DELETE
或PATCH
。Embryo使用MethodOverrideMiddleware
来管理HTTP-Method-Override。
回调函数
每个路由方法都接受一个回调函数作为其最后一个参数。默认情况下,此参数至少接受两个参数
- 请求。第一个参数是代表当前HTTP请求的
Psr\Http\Message\ServerRequestInterface
对象。 - 响应。第二个参数是代表当前HTTP响应的
Psr\Http\Message\ResponseInterface
对象。 - 占位符。每个路由占位符都有一个单独的参数。
向响应写入内容
有三种方法可以将内容写入HTTP响应
- 您可以直接在路由回调中
echo()
内容:这些内容将被附加到当前的HTTP响应对象中。 - 您可以返回一个
Psr\Http\Message\ResponseInterface
对象。 - 当返回数组时,您可以返回
json
内容。
// echo $router->get('/hello/{name}', function (Request $request, Response $response, string $name) { echo $name; }); // response object $router->get('/hello/{name}', function (Request $request, Response $response, string $name) { $response->getBody()->write($name); return $response; }); // json $router->get('/hello/{name}', function ($Request $request, Response $response, string $name) { return [ 'name' => $name ]; });
闭包绑定
如果您使用闭包实例作为路由回调,闭包的状态将被绑定到 Container
实例。这意味着您可以通过 $this
关键字在闭包内部访问DI容器实例。
$router->get('/hello/{name}', function (Request $request, Response $response, string $name) { $myservice = $this->get('myservice'); //... });
访问当前路由
如果您获取路由的对象,您可以使用请求属性来使用它。
$router->get('/hello/{name}', function (Request $request, Response $response, string $name) { $route = $request->getAttribute('route'); echo $route->getUri(); // /hello/name });
占位符
路由模式可以使用命名占位符来动态匹配HTTP请求URI段。
格式
路由模式占位符以 {
开始,后跟占位符名称,以 }
结束。名称和值占位符可以是字母、数字,包括下划线(_)。
$router->get('/hello/{name}', function (Request $request, Response $response, string $name) { echo $name; });
可选
要使占位符可选,请将其包裹在方括号中
$router->get('/hello[/{name}]', function (Request $request, Response $response, string $name = null) { if ($name) { echo $name; } //... });
您可以使用多个可选参数
$router->get('/blog[/{year}][/{month}][/{day}]', function(Request $request, Response $response, int $year = 2018, int $month = 12, int $day = 31){ $response->getBody()->write('Blog! Year: '.$year.', Month: '.$month.', Day: '.$day); return $response; });
对于“简单缩写”可选参数,您可以这样做
$router->get('/blog[/{year}][/{slug}]', function(Request $request, Response $response, int $year = 2018, string $slug = null){ //... })->where('slug', '[\/\w\-]+');
在这个例子中,URI为/blog/2018/my-post-title
将导致$year
(2018)和$slug
(my-post-title)参数。
设置正则表达式路由
默认情况下,占位符可以接受用于组成URI的任何字符,除了 /
字符。然而,占位符也可以要求HTTP请求URI与特定的正则表达式匹配。为此,您可以使用 where()
方法
$router->get('/blog/{id}/{name}', function(Request $request, Response $response, int $id, string $name){ //... })->where([ 'id' => '[0-9]+', 'name' => '[a-z]+', ]);
设置路由名称
您可以使用 name()
方法在路由中指定名称
$router->get('/hello/{name}', function (Request $request, Response $response, string $name) { //... })->name('route');
创建路由分组
您可以使用 group()
方法将路由组织成逻辑组。如果您想添加路由前缀,可以使用 prefix()
方法
$router->prefix('/api')->group(function($router) { $router->get('/user/{id}', function(Request $request, Response $response, int $id) { //... }); });
在这个例子中,URI是例如 /api/user/1。
向路由添加中间件
您也可以将PSR-15中间件附加到任何路由或路由组。 middleware()
方法接受字符串、数组或 Psr\Http\Server\MiddlewareInterface
实例。
路由中间件
您可以使用 middleware()
方法在路由上分配一个或多个中间件
$router->get('/users', function(Request $request, Response $response) { //... })->middleware('App\TestMiddleware1', 'App\TestMiddleware2');
组中间件
除了路由之外,您还可以将一个或多个中间件分配给一个组以及组内的单个路由
$router->prefix('/api')->middleware('App\GroupMiddlewareTest')->group(function($router) { $router->get('/user/{id}', function(Request $request, Response $response, int $id){ //... })->middleware('App\RouteMiddlewareTest'); });
通过容器解析
在回调中,除了闭包之外,您还可以使用 class@method
字符串或 ['className', 'methodName']
数组
// string $router->get('/user/{id}', 'user@getById'); // array use Path\To\User; $router->get('/user/{id}', [User::class, 'geById']);
它转换为一个类似这样的类/方法调用
use Embryo\Routing\Controller; use Psr\Http\Message\ResponseInterface; class User extends Controller { public function getById(int $id): ResponseInterface { $this->response->getBody()->write('The User id is: '.$id); return $this->response; } }
在这个例子中,您将可以通过 $this
关键字在类内部访问DI容器实例。如果您想访问容器中的服务,请使用 $this->get('myservice')
。如果您想访问请求或响应实例,请使用 $this->request
和 $this->response
。此外,您还可以使用 type-hinting
来调用方法参数,这意味着您可以在方法中放置所需的类,服务容器将自动解决它
use Path\To\MyService; use Embryo\Routing\Controller; use Psr\Http\Message\ResponseInterface; class User extends Controller { public function getById(MyService $service, int $id): ResponseInterface { //... } }
设置默认命名空间
您可以使用 setNamespace()
方法设置您控制器的默认命名空间
$router = new Router; $router->setNamespace('App\\Controller'); $router->get('/', 'PageController@index'); // App\Controller\PageController //...
在子文件夹中工作
Embryo Routing可以通过设置路径使用 setBasePath()
方法在子目录中工作
$router = new Router; $router->setBasePath('/path/subdirectory'); $router->get('/', function(Request $request, Response $response) { $response->getBody()->write('Hello World!'); return $response; }); //...